How to make a strategy game with AI
A strategy opponent written as a chain of if statements gets long, brittle and still plays badly. One scoring function over every legal move is shorter and plays better.
Jump to a section
Ask for a small grid, a handful of units and turns that alternate. The board is the easy half. The half that decides whether anybody plays twice is the opponent, and the way to get a good one is not to write its reasoning at all. Have it list every move it could legally make, give each one a score, add a little noise to the score, and play the highest.
A rule says what to do in a situation you thought of. A score says how much it likes any situation at all, including the ones you never imagined. That is why a dozen lines of arithmetic beat four hundred lines of branching, and why the arithmetic keeps working when you add a unit type.
Four steps to an opponent worth beating#
Lose on purpose if you can. You are watching how red chooses, not whether the board draws.
Every complaint maps to a missing term in the score or a weight set too low. Note the move and the turn.
Weights interact. Raising two at once tells you nothing about which one caused the new behaviour.
A unit added to a working score needs one row of stats. A unit added to a rule tree needs a rewrite.
Build a turn-based strategy game as ONE self-contained .html file. No
frameworks, no images, no libraries.
BOARD: a 9 by 9 grid drawn on a canvas. Blue is the player, red is the
opponent, and each side starts with four units on opposite edges. Four
squares in the middle are shaded objectives.
UNITS: a scout that moves three squares and hits lightly, a soldier
that moves two and hits hard, a guard that moves one and takes double
hits to kill. Two scouts, one soldier, one guard per side.
A TURN: the player clicks a unit and sees its legal squares lit up,
then clicks one. Moving onto an enemy attacks it instead. Each unit
acts once. A button ends the turn and hands the board to red.
OPPONENT: do not write if-else rules for red. Build a list of every
legal move for every red unit that has not acted. Score each entry
with one function that sums these terms: damage it would deal, minus
the damage it would take in reply next turn, plus a bonus for ending
on an objective square, plus a smaller bonus for reducing the distance
to the nearest blue unit. Then add a random amount between zero and
WOBBLE to each score. Take the highest, play it, and repeat until red
has no units left to act.
HONEST INFORMATION: red reads only the board the player can also see.
It must not consult the player's pending click, give itself extra
damage, or start with more units than blue.
READABILITY: show the running score of objectives held, and log red's
chosen move in one line of plain English above the board.
Play red's moves one at a time with MOVE_DELAY seconds between them so
the player can follow what happened.
Drive every animation from the seconds elapsed since the previous
frame, clamped to a maximum of 0.05 so a tab left in the background
does not resolve the whole turn in one jump. Include
canvas { touch-action: none; } in the stylesheet so a drag on a phone
does not scroll the page. Collect WOBBLE, MOVE_DELAY, the grid size,
the unit stats and every score weight in a single CONFIG object at the
top of the script, one commented line each.
The OPPONENT paragraph and the HONEST INFORMATION paragraph are the article. Change the weights, the stats and WOBBLE freely. If a later message asks for better play, ask for a new term in the score, never for a new branch of rules.
Score the moves, do not write the rules#
The instinct is to describe how a good player thinks. Retreat when hurt. Focus the weakest enemy. Hold the middle unless outnumbered. Each sentence becomes a branch, and each branch is a guess about a board you have not seen yet.
Two problems arrive together. The tree grows faster than the game does, because the number of situations multiplies while the number of units only adds. And whatever board you failed to describe falls through to the final else, which is usually something limp like staying put. Players find that gap quickly. An opponent that freezes once per game reads as broken, not as cautious.
Scoring inverts the job. Instead of asking what to do, generate everything that could be done and rank it. On a small grid that list is short enough to walk through in a fraction of a frame. Each entry gets a number from one expression, and the expression only has to say what the side wants, not how to get it.
Four terms carry most strategy games. Keep them separate and named, because you will be adjusting them by hand.
- What it gains. Damage dealt, or a kill. This is the term that makes the opponent attack at all, and it should be the largest.
- What it risks. Damage the moved unit could take in reply. Subtracting this is what stops a scout walking alone into three enemies.
- What it holds. A bonus for ending the move on ground that scores. This is the term that gives the opponent something to want besides blood.
- Where it is heading. A small bonus for closing distance. Without it, units with nothing to hit stand still and the game stops moving.
That is one move of lookahead and it is enough for a browser game. Searching two moves deep is a real improvement and a much bigger piece of code, so treat it as a later version rather than a starting point. Most of the play people notice comes from the weights, not the depth.
Difficulty is how much noise you add#
Adding a random amount to every score before picking the winner does two jobs at once. It stops the opponent repeating the same opening in every game, and it becomes the difficulty setting. A big random amount can overturn a clearly better move, so the opponent blunders. A small one only breaks ties, so it plays its best line every time.
| Level | Wobble | How it plays |
|---|---|---|
| Gentle | Larger than the usual gap between moves | Misses open kills and takes bad trades |
| Normal | Enough to reorder two close moves | Sensible, with the odd surprising choice |
| Hard | Small, a tie-breaker and nothing more | Punishes any unit left exposed |
| Mirror | None at all | Identical every game, which reads as a script |
Four difficulties and one value. No second opponent to write, and no second opponent to debug when the first one changes.
This beats the usual approach of giving a hard opponent extra health or extra income. Those settings change the puzzle rather than the player, and they are the first thing a tester complains about. Noise changes only how well the opponent plays, which is what difficulty is supposed to mean.
Resolution order inside a single turn#
A turn is several things happening in sequence, and the sequence is part of the rules whether you chose it or not. If you do not decide the order, the order ends up being whatever your arrays happen to be in, and it will change the next time the model rewrites a loop.
- Name the phases and keep the code in that order. Movement, then attacks, then whatever the board produces. Written down, it is also the list you check when a turn does something odd.
- Settle trades the same way every time. Two units that can each finish the other should either both die or the attacker should always survive. Both rules are fine. A rule that depends on which unit the loop reached first is not.
- Clear the dead at the end of a phase, not during it. Remove a unit mid-phase and the units behind it in the list are suddenly acting on a different board, which the player had no way to predict.
- Print the phase on screen. One line of text saying what is resolving answers most of the questions a tester would otherwise ask you.
Order matters more here than in a game played in real time, because the player is given time to work out the rules. Someone deliberating over a single move will notice an inconsistency that nobody would catch at speed.
An opponent that cheats is found out in three turns#
It is tempting to let the opponent look at things the player cannot see. Its pending move, the contents of a hidden hand, the roll before it happens. It is a short edit and it does make the opponent stronger. It also destroys the game, and faster than you would think.
The reason it gets caught so fast is that strategy players are already pattern matching. They are watching for which moves get punished, because that is how you learn a game. An opponent that answers an intention rather than a position shows up in that search immediately, long before anyone could explain why.
So hold the line in the prompt and keep holding it. State plainly that the opponent reads only what is on screen. When it plays badly, the fix is a better score, not a peek. The one exception worth allowing is reaction time, since an opponent that answers instantly feels mechanical. Pausing for a moment before it moves is honest and reads as thought.
♟️Strategy games written by AI
Common questions#
Can AI write a strategy game with a real opponent?
Yes, as long as you ask for the opponent as a scoring function rather than as rules. Models write both on request. The scoring version is shorter, plays better and stays readable once you start adding units, which is where the rule version falls apart.
Why does my hand-written opponent play so badly?
Because every branch only covers a board you thought of in advance, and most boards are not on that list. They fall through to the default, which tends to be standing still. Replace the whole tree with one function that scores every legal move and the blind spots go with it.
How do I make the opponent harder?
Shrink the random amount added to each score until it only breaks ties. Then raise the weight on damage risked, so it stops offering trades. Giving it extra health or income is the wrong lever, because that changes the puzzle instead of the play.
Should the opponent look more than one move ahead?
Not in a first build. One move of lookahead with sensible weights plays well enough that most players will not name the limitation. A two move search is a genuine step up and a far larger change, so it belongs in a second version.
My opponent is not cheating, but players say it is. Why?
Usually it is punishing a mistake you did not realise you were making, which feels like being read. Check that nothing in the code looks at the player's selection before it is confirmed. If it does not, add a short pause before each move so the choice appears to follow the board rather than your click.
How many unit types should a first strategy game have?
Three. One fast and fragile, one slow and hard hitting, one that mainly absorbs damage. That is enough for the three-way tension people recognise as strategy, and few enough that you can still tell why the opponent chose what it chose.
Where to go next#
When the opponent starts winning games you thought you had, put it up. It gets a page of its own recording the model and the prompt behind it. That matters more on a strategy game than on most things. The interesting part is the opponent, and the opponent is in the prompt rather than in the art.
The general build loop is in how to make a game with AI. The scoring idea transfers directly to the enemies in a tower defence game and to the hand a computer plays in a card game. Prompts for common faults are in the prompt guide, and the strategy games here are worth losing a few turns to first.
Built something? Put it on Agent Games.
Publishing takes a file and a minute. Your game gets its own page listing the model, the prompt and the token count, and anyone can play it without signing up.
Read next