War - Boardgame to Video Game + AI (Kotlin + LibGDX)


This is the video game version of my custom chess-like strategy board game, War. Written in Kotlin + LibGDX, with a MiniMax AI opponent.

I originally designed and built the physical board game. This project brings it to a screen so I can test piece balance, experiment with AI strategies, and play test it more easily in general.

The source code can be found on GitHub.

I wrote the core game engine and AI entirely before AI coding tools were commonplace, which was fun, and I hope that my manual code skills don't degrade and whither away. XD More recently, I used Claude to help expand unit tests and search for bugs across the codebase. Having an AI review the game logic turned up several subtle bugs in the minimax tree search that would have been difficult to catch.

The Game

War is a deterministic, chess-like strategy board game. No dice, no luck -- every outcome is the result of player decisions.

The board is an 11x11 grid with variable-height terrain tiles. Pieces cannot climb more than 1 unit of elevation per step, so terrain creates natural chokepoints, defensive positions, and flanking routes.

The game ends when a player's Commander is destroyed.

Pieces

Each piece has a distinct role. There are no duplicate movement patterns:

PieceMovementAttackScore
Commander1 tile, any directionSame100
Bomber1-4 tiles H/V, ignores elevationSame5
Missile1 tile any direction2-5 tiles diagonal (one-time use)4
Air Defense1 tile any directionIntercepts Bombers/Missiles/Artillery4
Artillery1 tile any direction2-3 tiles H/V (ranged, reload)3
Excavator1 tile any directionRaises/lowers terrain3
Tank1-2 tiles H/VSame2
Sniper1-2 tiles diagonalSame2
Infantry1 tile H/V1 tile diagonal1

Key interactions:

  • Artillery fires over pieces and terrain but needs 3+ turns to reload between shots.
  • Missiles are powerful diagonal strikers but are destroyed after a single use.
  • Air Defense automatically intercepts Bombers, Missiles, and Artillery shells within 1 tile, sacrificing itself in the process.
  • Bombers fly over friendly pieces and ignore elevation, but are vulnerable to Air Defense.
  • Excavators reshape the terrain itself, creating walls or opening paths.

Movement Diversity

One of the core design goals is that horizontal/vertical and diagonal movement are balanced:

PatternCombat Movers
H/VTank (1-2), Bomber (1-4), Artillery attack (2-3)
DiagonalSniper (1-2), Missile attack (2-5, one-use)
Any directionCommander (1), Infantry (H/V move, diagonal attack)

The AI

The AI uses a MiniMax search with Monte Carlo extensions. Here is how it works:

MiniMax Search

The AI builds a game tree by simulating moves for both players, alternating turns. At each leaf node, it evaluates the board state. The AI maximizes its own score and minimizes the opponent's, classic minimax.

AI (maximize)
  -> Opponent (minimize)
    -> AI (maximize)
      -> evaluate position

Move Scoring

Each move is scored by combining four factors:

  1. Base Score The value of the piece destroyed (e.g. capturing a Bomber = 5.0 points).
  2. Commander Advance A small bonus for moving closer to the enemy Commander. Euclidean distance reduction divided by 10.
  3. Weaker Piece Bonus Encourages the AI to move cheaper pieces first (Infantry gets 0.23, Tanks/Snipers get 0.2, Bombers get 0.0). This prevents the AI from leading with its most valuable units.
  4. Noise Small random perturbation scaled inversely by depth, so the AI doesn't play the exact same game every time.

The player modifier flips the sign for opponent moves: the AI wants to maximize its own captures and minimize the opponent's.

Monte Carlo Extensions

At the bottom of the minimax tree, the AI occasionally continues searching with a random walk (5% probability). This helps it discover tactical sequences that a fixed-depth search would miss, like a sacrifice that leads to a bigger capture two moves later.

Attacks always trigger an additional search step so the AI can verify it won't immediately lose the attacking piece.

Apply / Undo

The AI doesn't copy the board for each branch. Instead, it applies moves directly to the board and undoes them when backtracking. This is significantly faster but requires every piece to correctly restore all state on undo, including things like Artillery reload timers.

Dev Log

A selection of development videos showing the game's progression:

Running It

./gradlew desktop:dist
java -XstartOnFirstThread -jar desktop/build/libs/desktop-1.0.jar  # macOS
java -jar desktop/build/libs/desktop-1.0.jar                       # Windows/Linux


Projects

Site

Games

Tags