All projects

Paintball

A turn-based Paintball simulator written in XLISP-PLUS in a strictly functional style — immutable state, no mutation, no loops — where two AI agents fight to destroy the enemy base by painting it in all three colours.

Lisp LispFunctional ProgrammingAI Agents

The problem

Project 1 of 2 for the Programming Languages course at the Universitat de les Illes Balears.

The constraint is the point: no mutation and no loops. The whole match is one immutable list, every action returns a brand-new state, and control flow is recursion and higher-order functions only — so the ordinary way of writing a game loop is off the table from the first line.

Two teams, each driven by its own AI agent, fight to destroy the enemy base by painting it in all three colours (r, g, b). The board is a grid of land and water cells with obfuscated coordinates, so units cannot deduce the map borders. Each team owns an immovable base that spends paint to spawn bolles (paintball units); a bolla can move and paint the cells around it, and both bases and bolles are destroyed once they end up covered in the colours that beat them. Every unit is controlled purely from the information it receives each turn — its vision, its resources and an optional shared memory — and returns a list of actions that the controller validates and applies. Turns alternate between the two teams and a match ends when a base is destroyed or a turn limit is reached, with a tie-break on surviving units and remaining paint.

How it works

The design leans entirely on the functional toolbox seen in class — only recursion and higher-order functions (mapcar, reduce, apply, every, some, funcall), with no iterative loops and no destructive mutation:

  • Immutable state — the whole match is a single list that is never mutated; every action returns a new state through dedicated get- / set- / update- accessors.
  • Controller — runs the main loop, alternates turns, calls each unit’s AI agent and folds the returned actions into the state with reduce; units created during a turn may also act in that same turn.
  • AI agents — one per team, each living in its own file and allowed to use only the information passed in for that turn.
  • Vision — computes the cells each unit can see (a wider radius for a base than for a bolla) in the exact format the agents consume.
  • Graphics — draws the match in the XLISP-PLUS window with differential redraw, repainting only the cells that change, plus visual traces of the paint shots and movement.
  • Maps — loads a map from a LISP expression and builds the initial state with random coordinate obfuscation; a converter turns UPC-format maps from the AI Colliseum editor into the UIB format.
  • Tail-call optimization — a defun-tco macro rewrites tail-recursive functions into an iterative form, avoiding stack overflows on deep recursion.

Running it

Requires XLISP-PLUS, developed and tested on Linux — some graphics functions behave differently on Windows.

  1. Clone the repository and open the P1/ folder so the relative map paths resolve.
  2. Start XLISP-PLUS and load the entry point:
    (load "paintball")
  3. Launch a match, optionally passing a map (it defaults to maps/uib/tiny.map):
    (inici)                        ; default map
    (inici "maps/uib/simple.map")  ; a specific map

While a match is running the controls are [N] next turn, [B] step back, [A] automatic mode and [Q] quit.