Skip to content

Instantly share code, notes, and snippets.

@evantahler
Created July 1, 2013 05:05
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save evantahler/5898472 to your computer and use it in GitHub Desktop.
Save evantahler/5898472 to your computer and use it in GitHub Desktop.
Creating the API for a tic-tac-toe game with AI
// actions/game.js
// A collection of tasks and helper methods to create a simpel tic-tac-toe game
// - single player
// - simple AI
// - each connection can only have one active game at a time
exports.gameCreate = {
name: "gameCreate",
description: "I create a new game for this connection",
inputs: {
required: [],
optional: [],
},
outputExample: {},
version: 1.0,
run: function(api, connection, next){
var game = new api.game.gamePrototype();
api.game.saveGame(connection, game, function(error){
connection.error = error;
next(connection, true);
});
}
};
exports.gameView = {
name: "gameView",
description: "I view the game board",
inputs: {
required: [],
optional: [],
},
outputExample: {},
version: 1.0,
run: function(api, connection, next){
api.game.loadGame(connection, function(error, game){
connection.error = error;
connection.response.game = game;
next(connection, true);
});
}
};
exports.gameMove = {
name: "gameMove",
description: "a move by a human player of tic-tac-toe",
inputs: {
required: ['x', 'y'],
optional: [],
},
outputExample: {},
version: 1.0,
run: function(api, connection, next){
var x = parseInt(connection.params.x);
var y = parseInt(connection.params.y);
api.game.loadGame(connection, function(error, game){
if(error != null){
connection.error = error;
next(connection, true);
}else if(game.state != "playing"){
connection.error = "this game is over";
next(connection, true);
}else if(game.board[y][x] != null){
connection.error = "you can only draw a new shape on a blank tile";
next(connection, true);
}else{
game.board[y][x] = game.playerMarker;
game.state = api.game.determineGameState(game);
api.game.aiTurn(game);
game.state = api.game.determineGameState(game);
game.turn++;
api.game.saveGame(connection, game, function(){
connection.response.game = game;
next(connection, true);
});
}
});
}
};
// initializers/game.js
exports.game = function(api, next){
api.game = {
gamePrototype: function(data){
if(data == null){
this.board = [
[null, null, null],
[null, null, null],
[null, null, null],
];
this.turn = 0;
this.state = "playing";
this.playerMarker = "X";
this.computerMarker = "O";
}else{
for(var i in data){
this[i] = data[i];
}
}
},
loadGame: function(connection, callback){
var key = "game_" + connection.id;
api.cache.load(key, function(error, data){
callback(error, new api.game.gamePrototype(data));
});
},
saveGame: function(connection, game, callback){
var key = "game_" + connection.id;
var data = {
board: game.board,
turn: game.turn,
state: game.state,
playerMarker: game.playerMarker,
computerMarker: game.computerMarker,
}
api.cache.save(key, data, function(error){
callback(error);
});
},
determineGameState: function(game){
// horizontal win
for(var i in game.board){
var row = game.board[i]
if(row[0] != null && row[0] == row[1] && row[0] == row[2]){
return api.game.determineWinningState(game, row[0]);
}
};
// vertical win
var i = 0;
while (i <= 2){
if(game.board[0][i] != null && game.board[0][i] == game.board[1][i] && game.board[0][i] == game.board[2][i]){
return api.game.determineWinningState(game, game.board[0][i]);
}
i++;
}
// diagonal win
if(
(game.board[1][1] != null && game.board[0][0] == game.board[1][1] && game.board[0][0] == game.board[2][2])
|| (game.board[1][1] != null && game.board[0][2] == game.board[1][1] && game.board[0][2] == game.board[2][0])
){
return api.game.determineWinningState(game, game.board[1][1]);
}
// tie
var tied = true;
game.board.forEach(function(row){
row.forEach(function(square){
if(square == null){
tied = false;
}
});
});
if(tied == true){
return "tied";
}else{
return "playing"
}
},
determineWinningState: function(game, symbol){
if(symbol == game.playerMarker){
return "you won"
}else{
return "you lost"
}
},
aiTurn: function(game){
var options = [
[], [], []
];
var bestMove = [null, null];
var bestScore = null;
var y = -1;
game.board.forEach(function(row){
y++;
x = -1;
row.forEach(function(square){
x++;
if(square != null){
options[y][x] = null
}else{
var proposedGameAi = api.utils.objClone(game);
var proposedGamePlayer = api.utils.objClone(game);
proposedGameAi.board = JSON.parse(JSON.stringify(game.board));
proposedGamePlayer.board = JSON.parse(JSON.stringify(game.board));
proposedGameAi.board[y][x] = game.computerMarker;
proposedGamePlayer.board[y][x] = game.playerMarker;
if(api.game.determineGameState(proposedGameAi) == "you lost"){
options[y][x] = 1 // I win
if(1 > bestScore || bestScore == null){
bestScore = 1;
bestMove = [y,x]
}
}else if(api.game.determineGameState(proposedGamePlayer) == "you won"){
options[y][x] = 0.5 // I blocked a player win
if(0.5 > bestScore || bestScore == null){
bestScore = 0.5;
bestMove = [y,x]
}
}else{
options[y][x] = 0
if(0 > bestScore || bestScore == null){
bestScore = 0;
bestMove = [y,x]
}
}
}
});
});
if(bestScore != null){
game.board[bestMove[0]][bestMove[1]] = game.computerMarker;
}
},
}
next();
}
@evantahler
Copy link
Author

And here is the game played by telnet:

> telnet localhost 5000
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"welcome":"Hello! Welcome to the actionHero api","room":"defaultRoom","context":"api"}
gameCreate
{"context":"response","messageCount":1}
gameView
{"game":{"board":[[null,null,null],[null,null,null],[null,null,null]],"turn":0,"state":"playing","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":2}
setParam y=1
{"error":"Error: setParam is not a known action or that is not a valid apiVersion.","context":"response","messageCount":3}
paramAdd y=1
{"status":"OK","context":"response","data":null,"messageCount":4}
paramAdd x=1
{"status":"OK","context":"response","data":null,"messageCount":5}
gameMove
{"game":{"board":[["O",null,null],[null,"X",null],[null,null,null]],"turn":1,"state":"playing","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":6}
paramAdd x=0
{"status":"OK","context":"response","data":null,"messageCount":7}
gameMove
{"game":{"board":[["O",null,null],["X","X","O"],[null,null,null]],"turn":2,"state":"playing","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":8}
paramAdd y=0
{"status":"OK","context":"response","data":null,"messageCount":9}
paramAdd x=1
{"status":"OK","context":"response","data":null,"messageCount":10}
gameMove
{"game":{"board":[["O","X",null],["X","X","O"],[null,"O",null]],"turn":3,"state":"playing","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":11}
paramAdd x=2
{"status":"OK","context":"response","data":null,"messageCount":12}
gameMove
{"game":{"board":[["O","X","X"],["X","X","O"],["O","O",null]],"turn":4,"state":"playing","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":13}
paramAdd y=2
{"status":"OK","context":"response","data":null,"messageCount":14}
paramAdd x=2
{"status":"OK","context":"response","data":null,"messageCount":15}
gameMove
{"game":{"board":[["O","X","X"],["X","X","O"],["O","O","X"]],"turn":5,"state":"tied","playerMarker":"X","computerMarker":"O"},"context":"response","messageCount":16}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment