Home > Programming > Strategy Game AI

Strategy Game AI

So I had an epiphany today. Up until this point I had been planning on using a Monte Carlo-like algorithm to create a near universal strategy game AI. I knew it was something I could do, but it would require a decent time investment and might take too long to run before it could actually generate a good quality turn.

Then I realized, I want the enemies to be zombies. And why should zombies have an intelligent AI controlling them? So I think I can make the AI in my game work in almost exactly the same way that my movement works. During the AI turn for each unit, I’ll start from what’s next to them and then fan out til I hit the first enemy. Then I’ll randomly pick one of the shortest paths to that enemy and move the unit as close as it can get during it’s turn. It’s so simple, but makes so much sense for zombies. I haven’t finished updating it for the AI, but here’s the code I have for determining the total movement area available for a given unit.

//******Set starting tile as character’s current position******
currentNewTiles.push(new Array(attachedCharacter.currentHorPos, attachedCharacter.currentVertPos));

if(startDistance == 0) {

moveSquares.unshift(new MoveSquare(new Array(), attachedCharacter.currentHorPos, attachedCharacter.currentVertPos, squareType));
this.addChild(moveSquares[0]);

}
mapCheckArray[attachedCharacter.currentHorPos][attachedCharacter.currentVertPos] = 0;

//******Iterate through each possible move******
for (var moveCount:int = 1; moveCount <= endDistance; moveCount++) {

oldNewTiles = new Array();
oldNewTiles = currentNewTiles;
currentNewTiles = new Array();

//******For each space that was reached during the last move******
for each (var oldCoord:Array in oldNewTiles) {

var oldHeight:int = Main.tileMap.mapArray[oldCoord[1]][oldCoord[0]].tileHeight;

//******For each posible direction******
for (var directionCount:int = 0; directionCount < 4; directionCount++) {

//******Set new coord equal to old + new direction, but won’t go off map******
var newCoord:Array = new Array(Math.min(Math.max(oldCoord[0] + xArray[directionCount], 0), Main.mapWidth – 1),
Math.min(Math.max(oldCoord[1] + yArray[directionCount], 0), Main.mapHeight – 1));
var moveBlocked:Boolean = false;
var oldMoveCount:int = mapCheckArray[oldCoord[0]][oldCoord[1]];
var isImpassable:Boolean = Main.tileMap.mapArray[newCoord[1]][newCoord[0]].impassable;
var movesRequired:int =
Main.tileMap.mapArray[newCoord[1]][newCoord[0]].movesRequired[attachedCharacter.moveType];
var newTeam:int;
var tempCharacter:Character = Character.getCharacterAt(newCoord[0], newCoord[1]);

if (tempCharacter == null) {

newTeam = -1;

}
else {

newTeam = tempCharacter.characterTeam;

}

if (isImpassable) {

moveBlocked = true;

}
else if((oldMoveCount + movesRequired) > endDistance) {

moveBlocked = true;

}
else if (newTeam != characterTeam && newTeam != -1) {

moveBlocked = true;

}

if (squareType != “MOVE”) {

movesRequired = 1;

if (moveCount >= startDistance) {

newTeam = -1;

}
else {

newTeam = characterTeam;

}
moveBlocked = false;

}
//******Check if tile was reached previously or impassable******
if (mapCheckArray[newCoord[0]][newCoord[1]] > (oldMoveCount + movesRequired) && !moveBlocked) {

bestPathArray[newCoord[0]][newCoord[1]] = new Array();

for each(var tempVar:String in bestPathArray[oldCoord[0]][oldCoord[1]]) {

bestPathArray[newCoord[0]][newCoord[1]].push(tempVar);

}
bestPathArray[newCoord[0]][newCoord[1]].push(“1,”.concat(directionArray[directionCount]));
if (newTeam == -1) {

var squareFound:Boolean = false;

for (var i:int = 0; i < moveSquares.length; i++) {

if (moveSquares[i].horPos == newCoord[0] && moveSquares[i].vertPos == newCoord[1]) {

squareFound = true;
moveSquares[i] = new MoveSquare(bestPathArray[newCoord[0]][newCoord[1]], newCoord[0], newCoord[1], squareType);

}

}
if(!squareFound) {

moveSquares.unshift(new MoveSquare(bestPathArray[newCoord[0]][newCoord[1]], newCoord[0], newCoord[1], squareType));

}

}
currentNewTiles.push(new Array(newCoord[0], newCoord[1]));
mapCheckArray[newCoord[0]][newCoord[1]] = oldMoveCount + movesRequired;

}

}

}

}

I’m using actionscript 3 for the code if you’re curious. And here’s the result of all that code.

What a beautiful movement area

If there’s anything you’re interested in learning about what I’ve done so far or about the game development process, let me know in the comments.

Categories: Programming Tags:
  1. Kelly
    January 13th, 2012 at 16:21 | #1

    Zombies, path of least wait-time between gray matters is a delightful idea. Keep in mind, however, that if you include variations in enemy troop types, you may end up with creatures where this tactic isn’t the representative of theme. Skeleton Archers, or Necromancers, for instance. Archers would stay within range of their target, but wouldn’t necessarily be smart enough to try to avoid the line of fire from enemy archers. Necromancers, on the other hand, are smart and scholastic students of the grave, and they’d be more likely to realize when they’re in a dangerous situation and adjust their position appropriately. Variety is the spice of life… though in this case it may be the bane of a simple design.

    • January 13th, 2012 at 22:09 | #2

      Thanks for the input. I had considered adding some code so that ranged units (archers) would only charge close enough to deal damage. If I include a leader type unit (necromancer, wizard, etc) it will probably end us as a boss on the final level or something. I’ll have to figure out some way to prioritize targets for him.

  1. No trackbacks yet.