Slowly improving Toaster AI for #1GAM

My Interdiction single player AI (named Toaster) has been coming along slowly. It’s still very far from the unbeatable cylon I want it to be, but it at least tries to defend itself by intercepting the player and attempting to build up its own lines.

I’ve never made a board game before Interdiction, much less board game AI of any kind. The approach I ended up taking is having an AI Controller entity that takes control of the Pointer entity on its turn to pick up and drop tiles from the queue. EntityPointer, on a human turn, follows the mouse cursor and interacts with entities that it touches when clicked. EntityAiController essentially just takes over the position of the pointer entity and simulates clicks to interact with its tiles.

I put the initial version of the Toaster-inclusive game online, but there’s still a lot of work to do. Toaster is a work in progress. Right now it works like this:

Pick up a random tile if one is not specified

   startTurn: function(tile) {
        // If tile is not specified...
        if (!tile) {
            this.parentSidebar.checkQueuedTiles();
            // Randomly shuffle array of AI's queued tiles
            var queuedTiles = this.parentSidebar.queuedTiles;
            ig.game.controller.shuffleArray(queuedTiles);
            // Grab first tile in shuffled array.
            var tile = queuedTiles[0];
        }
        // Pick up tile and start the move
        tile.pickUp();
        this.makeMove();
    },

Find the fullest line on the board

    makeMove: function() {
        var lineCounts = ig.game.controller.lineCounts;
        var fullestLine = {name: null, count: 0, x: 0, y: 0, xIncrement: 0, yIncrement: 0};

        // Loop through every line in lineCounts object
        for (var item in lineCounts) {
            if (lineCounts.hasOwnProperty(item)) {
                // If the item is a row or column
                if (item === 'row' || item === 'col') {
                    // Loop through each line in the array of rows or columns
                    for (var i = 0; i < lineCounts[item].length; i++) {
                        var line = lineCounts[item][i];
                        console.log(item + ' ' + i + " -> " + line.totalLineCount);
                        fullestLine = this.checkFullestLine(fullestLine,line,item);
                    }
                }
                // If the item is NOT a row or column (diagonal)
                else {
                    var line = lineCounts[item];
                    console.log(item + " -> " + lineCounts[item].totalLineCount);
                    fullestLine = this.checkFullestLine(fullestLine,line,item);
                }
            }
        }

        // If the fullest line has been found...
        if (fullestLine.name !== null) {
            // Try to play the next line on the fullest line line.
            this.playOnLine(fullestLine.name, fullestLine.x, fullestLine.y, fullestLine.xIncrement, fullestLine.yIncrement);
            fullestLine.name = null;
        }
        // Otherwise, play in a random position.
        else {
            this.playOnRandom();
        }
    },

    checkFullestLine: function(fullestLine,line,item) {
        // If line count is at least 3 and the line is owned by the human or line count is at least 1 and it is owned by Toaster...
        if (line.totalLineCount >= 3 && line.owner === 'player1' || line.totalLineCount > 0 && line.owner === 'player2') {
            // If this line count is higher than the currently defined fullest line
            if (fullestLine.count < line.totalLineCount) {
                // Set fullest line properties
                fullestLine.name = item;
                fullestLine.count = line.totalLineCount;
                fullestLine.x = line.x;
                fullestLine.y = line.y;
                fullestLine.xIncrement = line.xIncrement;
                fullestLine.yIncrement = line.yIncrement;
            }
        }    
        return fullestLine;
    },

Play chosen tile on either random or specified line

    // Play tile on a random line
    playOnRandom: function() {
        // Shuffle array of possible positions on the board
        ig.game.controller.shuffleArray(this.gridPositionsArr);
        // Select the first grid position from the shuffled array
        var gridsquare = this.gridPositionsArr[0];
        var gridPos = {x: gridsquare.pos.x, y: gridsquare.pos.y};
        // Move pointer to chosen position
        ig.game.pointer.pos.x = gridPos.x;
        ig.game.pointer.pos.y = gridPos.y;
    },

    // Play tile on the chosen line
    playOnLine: function(lineName, x, y, xIncrement, yIncrement) {
        // Get all grid squares on the board
        var allSquares = ig.game.getEntitiesByType(EntityGridsquare);
        // Loop through positions on the chosen line
        for (var i = 0; i < 5; i++) {
            var xPos = x;
            for (var n = 0; n < 5; n++) {
                var yPos = y;
            }
            for (var z = 0; z < allSquares.length; z++) {
                var thisSquare = allSquares[z];
                // If grid square is in specific position...
                if (thisSquare.pos.x === xPos && thisSquare.pos.y === yPos) {
                    // ...and is not occupied
                    if (!thisSquare.occupied) {
                        // Move pointer to that position
                        ig.game.pointer.pos.x = xPos;
                        ig.game.pointer.pos.y = yPos;
                        break;
                    }
                }
            }
            // Increment position
            x += xIncrement;
            y += yIncrement;
        }
    },

When the pointer is moved to the position of a grid tile entity and touches it, that grid tile is specified as the position where the player tile will be dropped. EntityAiController checks this position and whether the gridSquare of the player tile entity it’s holding has been updated correctly, then drops the tile:

    update: function() {
        if (ig.game.activePlayer === 'player2' && ig.game.pointer.holdingEntity) {
            if (ig.game.pointer.holdingEntity.gridSquare) {
                this.dropTile();
            }
        }
    }

Next I want to work on how Toaster chooses which tile to actually use. As I mentioned above, right now it just selects a random tile from its queue. However, which tiles you end up placing and where is actually important. For example - grenades are pointless to drop unless you’re dropping them on the opponent’s tile (or perhaps your own, though rarely), but Toaster can pick up a grenade randomly and drop it on an empty square. Same with Siege - the most beneficial use of the Siege tile would (usually) involve placing it on a tile owned by the opponent to take control of it. This is the next big improvement that needs to be made.

Toaster board game AI for One Game a Month

comments powered by Disqus