From 72f29376dad2995cb0d353f6f88d5910ae5ba895 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Thu, 4 Feb 2016 21:20:51 -0500 Subject: [PATCH 01/14] adding skeleton for AI elements --- src/checkers/checkers_game.js | 27 +++++++++++++++++++++++++++ tabletop/core/game.js | 28 +++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/checkers/checkers_game.js b/src/checkers/checkers_game.js index 4e0ed07..247b920 100644 --- a/src/checkers/checkers_game.js +++ b/src/checkers/checkers_game.js @@ -117,4 +117,31 @@ CheckersGame.prototype.playerDidWin = function(player) { }; + +// takes in a player +// returns the score for the board based on the player passed in (ie. if the player +// has won it should return 10, if he loses should return -10) +CheckersGame.prototype.scoreBoard = function(player) { + + // (optional: probably do this after you've done everything below) + // award 2 points for a board with 2 x's or o's in same row/column/diag + + // needed: + // award 10 points for win + // subtract 10 points for loss + // you could call "playerDidWin" to evaluate this +}; + + + + + + + + + + + + + module.exports = CheckersGame; diff --git a/tabletop/core/game.js b/tabletop/core/game.js index 04ac482..03a7e4b 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -290,4 +290,30 @@ Game.prototype.destroyToken = function(token) { this.board.destroyToken(token); }; -module.exports = Game; \ No newline at end of file + +Game.prototype.pickAImove = function(player, board, game) { + + var moves = this.getListOfMoves(); + + // make the move, recurse this function using a copy of the board + // for each move + // if (isValidMove) call executeMove on the copy of board/game + + + // evaluate based on difficulty + // novice: choose random move + // amateur: choose best move with 50%, 2nd best 25%, random 25% + // master: always choose best +}; + +Game.prototype.getListOfMoves = function(player) { + + if (this.gameType == gameTypePlaceToken) { + // simulate every valid move for this gametype + // and then return the list + } + +} + + +module.exports = Game; From 6f17892992c97a0aeb93b097fa65a6d3df50a4b2 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 22 Feb 2016 14:25:33 -0500 Subject: [PATCH 02/14] first commit w/ ai components. jenky fixes to errors i was getting, should fix later --- src/checkers/checkers_game.js | 29 ++------- src/checkers/style.css | 50 ---------------- tabletop/core/component.js | 8 +-- tabletop/core/game.js | 110 ++++++++++++++++++++++++++++------ tabletop/core/index.js | 1 - tabletop/core/manual_turn.js | 13 +++- webpack.config.js | 2 +- 7 files changed, 113 insertions(+), 100 deletions(-) delete mode 100644 src/checkers/style.css diff --git a/src/checkers/checkers_game.js b/src/checkers/checkers_game.js index 247b920..4ee3eff 100644 --- a/src/checkers/checkers_game.js +++ b/src/checkers/checkers_game.js @@ -9,7 +9,9 @@ function CheckersGame(board) { this.moveEvaluationType = TableTop.Constants.moveEvalationTypeGameEvaluator; this.possibleNumPlayers = [2]; this.showNextPlayerScreen = false; + this.enableAI = true; }; + inherits(CheckersGame, TableTop.Game); CheckersGame.prototype.setPlayers = function(players) { @@ -38,7 +40,7 @@ CheckersGame.prototype.executeMove = function() { if (jumpedToken) { this.destroyToken(jumpedToken); } - console.log(JSON.stringify(this.board)); + // move the token to the new tile and clear proposedMove this.board.moveTokenToTile(token, destination); this.proposedMove = {}; @@ -65,7 +67,6 @@ CheckersGame.prototype.isValidMove = function(token, oldTile, newTile) { var player = this.getCurrentPlayer(); - console.log("checking valid move", player, oldPos, newPos); /* If we don't own the piece or the destination is a red tile or @@ -74,15 +75,12 @@ CheckersGame.prototype.isValidMove = function(token, oldTile, newTile) { */ var p = this.getPlayerForToken(token); - console.log("players", p != player, p, player); if (this.getPlayerForToken(token) != player || newTile.color == TableTop.Constants.redColor || newTile.tokens[0]) return false; - console.log("getting here"); - return this.validNormalMove(token, oldPos, newPos, 1) || this.validJumpMove(token, oldPos, newPos); }; @@ -122,26 +120,9 @@ CheckersGame.prototype.playerDidWin = function(player) { // returns the score for the board based on the player passed in (ie. if the player // has won it should return 10, if he loses should return -10) CheckersGame.prototype.scoreBoard = function(player) { - - // (optional: probably do this after you've done everything below) - // award 2 points for a board with 2 x's or o's in same row/column/diag - - // needed: - // award 10 points for win - // subtract 10 points for loss - // you could call "playerDidWin" to evaluate this + var otherPlayer = this.players[this.getNextPlayer()]; + return 12 - otherPlayer.tokens.length; }; - - - - - - - - - - - module.exports = CheckersGame; diff --git a/src/checkers/style.css b/src/checkers/style.css deleted file mode 100644 index 712e58d..0000000 --- a/src/checkers/style.css +++ /dev/null @@ -1,50 +0,0 @@ -/*body { - background-color: #FFCC00; -} - -h1 { - color: black; - margin-left: 200px; - font-family: Futura, "Trebuchet MS", Arial, sans-serif; - font-size: 20vw; - text-align: center; -} - -form { - text-align: center; - font-size: 5vw; - font-family: Futura, "Trebuchet MS", Arial, sans-serif; -} - -select { - background: #FFE064; - width: 5%; - padding: 20px; - font-size: 4vw; - line-height: 1; - border: 0; - border-radius: 0; - height: 15%; - text-align: center; -} - -input { - background: #FFE064; - width: 15%; - padding: 10px; - font-size: 3vw; - line-height: 1; - border: 0; - border-radius: 0; - height: 15%; - text-align: center; - border-radius: 20px; -} - -canvas { - padding-left: 0; - padding-right: 0; - margin-left: auto; - margin-right: auto; - display: block; -}*/ \ No newline at end of file diff --git a/tabletop/core/component.js b/tabletop/core/component.js index 5e466e9..c6ddf4d 100644 --- a/tabletop/core/component.js +++ b/tabletop/core/component.js @@ -48,9 +48,9 @@ Component.prototype.subscribe = function(callback) { */ Component.prototype.propagate = function(child) { var context = this; - child.subscribe(function(messageObj) { +/* child.subscribe(function(messageObj) { context.sendMessage(messageObj.text, messageObj.type, messageObj.sender); - }); -} + }); */ +}; -module.exports = Component; \ No newline at end of file +module.exports = Component; diff --git a/tabletop/core/game.js b/tabletop/core/game.js index 94eff60..72c4d37 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -3,6 +3,7 @@ var ManualTurn = require("./manual_turn.js"); var Component = require("./component.js"); var inherits = require('util').inherits; var _ = require('lodash'); +var $ = require('jquery'); /** * The Game class @@ -23,6 +24,7 @@ function Game(board) { this.showNextPlayerScreen = true; this.playerColors = [0xFF0000, 0x000000, 0x00FF00, 0x0000FF, 0xFF00FF]; this.currentPlayer = 0; + this.enableAI = false; }; inherits(Game, Component); @@ -160,6 +162,15 @@ Game.prototype.nextPlayer = function() { this.currentPlayer = (this.currentPlayer + 1) % this.players.length; }; +/** + * Returns the next player, but does not switch + * Override to provide more logic on determining the next player + * @returns {void} +*/ +Game.prototype.getNextPlayer = function() { + return (this.currentPlayer + 1) % this.players.length; +}; + /** * Set the destination for a proposed move * @param {Tile} tile - the tile to move to @@ -294,29 +305,90 @@ Game.prototype.destroyToken = function(token) { }; -Game.prototype.pickAImove = function(player, board, game) { +Game.prototype.pickAImove = function(player, game) { - var moves = this.getListOfMoves(); - - // make the move, recurse this function using a copy of the board - // for each move - // if (isValidMove) call executeMove on the copy of board/game - + var moves = this.getValidMoves(player); + return {token: moves.tokens[0], tile: moves.tiles[0], destination: moves.destinationTiles[0][0]}; - // evaluate based on difficulty - // novice: choose random move - // amateur: choose best move with 50%, 2nd best 25%, random 25% - // master: always choose best + var results = []; + for (var i = 0; i < moves.tokens.length; i++) { + var token = moves.tokens[i]; + var tile = moves.tiles[i]; + moves.destinationTiles[i].forEach(function(dest) { + var gameCopy = this.smartClone(this); + gameCopy.proposedMove = {token: token, tile: tile, destination: dest}; + gameCopy.executeMove(); + var score = gameCopy.scoreBoard(player); + results.push({score: score, token: token, tile: tile, destination: dest}); + }, this); + } + + var bestResult = results[0]; + var bestScore = 0; + + results.forEach(function(result) { + if (result.score > bestScore) + bestResult = result; + }); + + return bestResult; }; -Game.prototype.getListOfMoves = function(player) { - - if (this.gameType == gameTypePlaceToken) { - // simulate every valid move for this gametype - // and then return the list +Game.prototype.getValidMoves = function(player) { + + var legalMoves = {}; + legalMoves.tokens = []; + legalMoves.tiles = []; + legalMoves.destinationTiles = []; + if (this.moveType == c.moveTypeManual) { + + player.tokens.forEach(function(token) { + var tile = this.board.findTileForToken(token); + var destinationTiles = []; + // since this.board is a double array we need to double loop + this.board.tiles.forEach(function(destinationRow) { + destinationRow.forEach(function(destination) { + if (this.isValidMove(token, tile, destination)) + destinationTiles.push(destination); + }, this); + }, this); + + if (destinationTiles.length > 0) { + legalMoves.tokens.push(token); + legalMoves.tiles.push(tile); + legalMoves.destinationTiles.push(destinationTiles); + } + }, this); + } - -} - + + return legalMoves; +}; +/* +Game.prototype.smartClone = function(object) +{ + + console.log("yes!"); + if (Object.prototype.toString.call(object) === '[object Array]') + { + var clone = []; + for (var i=0; i Date: Mon, 22 Feb 2016 19:46:40 -0500 Subject: [PATCH 03/14] functioning AI --- tabletop/core/game.js | 163 ++++++++++++++++++++++++++++------- tabletop/core/grid_board.js | 3 +- tabletop/core/manual_turn.js | 11 +-- tabletop/core/tile.js | 2 +- 4 files changed, 140 insertions(+), 39 deletions(-) diff --git a/tabletop/core/game.js b/tabletop/core/game.js index 72c4d37..bef1fc8 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -4,6 +4,10 @@ var Component = require("./component.js"); var inherits = require('util').inherits; var _ = require('lodash'); var $ = require('jquery'); +var Tile = require('./tile.js'); +var Token = require('./token.js'); +var Player = require('./player.js'); +var Gridboard = require('./grid_board.js'); /** * The Game class @@ -308,18 +312,50 @@ Game.prototype.destroyToken = function(token) { Game.prototype.pickAImove = function(player, game) { var moves = this.getValidMoves(player); - return {token: moves.tokens[0], tile: moves.tiles[0], destination: moves.destinationTiles[0][0]}; - var results = []; + + // for each token, grab the appropriate objects from the game copy object + // (since getValidMoves will return actual game objects and we want + // the copies). Then execute the move and score it. for (var i = 0; i < moves.tokens.length; i++) { - var token = moves.tokens[i]; - var tile = moves.tiles[i]; + + var tokenIdx = this.board.tokens.indexOf(moves.tokens[i]); + var tilePos = this.board.getTilePosition(moves.tiles[i]); + moves.destinationTiles[i].forEach(function(dest) { - var gameCopy = this.smartClone(this); - gameCopy.proposedMove = {token: token, tile: tile, destination: dest}; + var destPos = this.board.getTilePosition(dest); + + var gameCopy = this.copyGameStatus(this); + + var tokenCp = gameCopy.board.tokens[tokenIdx]; + var tileCp, destCp; + if (game.board instanceof Gridboard) { + tileCp = gameCopy.board.tiles[tilePos.x][tilePos.y]; + destCp = gameCopy.board.tiles[destPos.x][destPos.y]; + } else { + tileCp = gameCopy.board.tiles[tilePos]; + destCp = gameCopy.board.tiles[destPos]; + } + + gameCopy.proposedMove = { + token: tokenCp, + tile: tileCp, + destination: destCp, + tokenIdx: tokenIdx, + tilePos: tilePos, + destPos: destPos + }; + gameCopy.executeMove(); - var score = gameCopy.scoreBoard(player); - results.push({score: score, token: token, tile: tile, destination: dest}); + results.push({ + score: gameCopy.scoreBoard(player), + token: tokenCp, + tile: tileCp, + destination: destCp, + tokenIdx: tokenIdx, + tilePos: tilePos, + destPos: destPos + }); }, this); } @@ -327,11 +363,33 @@ Game.prototype.pickAImove = function(player, game) { var bestScore = 0; results.forEach(function(result) { - if (result.score > bestScore) + if (result.score > bestScore) { bestResult = result; + bestScore = result.score; + } }); + + + var actualToken = this.board.tokens[bestResult.tokenIdx]; + // actualTile should be same as this.board.tile[tilePos], but we + // use this cause it generalizes to all board types + var actualTile = this.board.findTileForToken(actualToken); + + var actualDest; + if (this.board instanceof Gridboard) { + actualDest = this.board.getTile(bestResult.destPos.x, bestResult.destPos.y); + } else { + actualDest = this.board.getTile(bestResult.destPos); + } - return bestResult; + var actualBestMove = + { + token: actualToken, + tile: actualTile, + destination: actualDest + }; + + return actualBestMove; }; Game.prototype.getValidMoves = function(player) { @@ -364,31 +422,72 @@ Game.prototype.getValidMoves = function(player) { return legalMoves; }; -/* -Game.prototype.smartClone = function(object) -{ + +// Create copy of game and manually copy over +// tiles/tokens to remove references +Game.prototype.copyGameStatus = function(game) { + + //var newGame = $.extend(true, {}, game); + var newGame = Object.create(this); + newGame.board = Object.create(this.board); + newGame.board.tokens = []; + newGame.board.tiles = []; + newGame.players = []; + var i, j = 0; - console.log("yes!"); - if (Object.prototype.toString.call(object) === '[object Array]') - { - var clone = []; - for (var i=0; i= 0) { + newGame.players[j].tokens.push(newToken); + } + } + + // if a token is on a tile, add it to the tile's list of tokens + var tile = game.board.findTileForToken(token); + var tilePos = game.board.getTilePosition(tile); + if (!tile || !tilePos) continue; + + if (game.board instanceof Gridboard) + newGame.board.tiles[tilePos.x][tilePos.y].addToken(newGame.board.tokens[i]); + else + newGame.board.tiles[tilePos].addToken(newGame.board.tokens[i]); + } - else - return object; + + return newGame; }; -*/ + module.exports = Game; diff --git a/tabletop/core/grid_board.js b/tabletop/core/grid_board.js index d293f3a..da9e69b 100644 --- a/tabletop/core/grid_board.js +++ b/tabletop/core/grid_board.js @@ -13,7 +13,7 @@ function GridBoard(width, height) { Board.call(this); for (var i = 0; i < width; i++) { this.tiles[i] = Array(this.height); - } + } this.width = width; this.height = height; @@ -69,6 +69,7 @@ GridBoard.prototype.moveTokenToTile = function(token, tile) { GridBoard.prototype.destroyToken = function(token) { var tile = this.findTileForToken(token); tile.removeToken(token); + token.isDead = true; }; module.exports = GridBoard; diff --git a/tabletop/core/manual_turn.js b/tabletop/core/manual_turn.js index 8a95868..e61020f 100644 --- a/tabletop/core/manual_turn.js +++ b/tabletop/core/manual_turn.js @@ -69,12 +69,13 @@ function ManualTurn(game, startView, view, gameOverView, nextPlayerView) { makeMove : function() { if (game.hasValidMove()) { - game.executeMove(); -/* if (this.game.getCurrentPlayer().name == "AI") { + if (this.game.getCurrentPlayer().name == "AI") { var turnMap = this; - setTimeout(function() { turnMap.transition("postTurn"); }, 50000); - } else { */ - this.transition("postTurn"); + setTimeout(function() { game.executeMove(); turnMap.transition("postTurn"); }, 500); + } else { + game.executeMove(); + this.transition("postTurn"); + } } else { console.log("Invalid move. Try again."); diff --git a/tabletop/core/tile.js b/tabletop/core/tile.js index 6e23071..5c64a93 100644 --- a/tabletop/core/tile.js +++ b/tabletop/core/tile.js @@ -23,7 +23,7 @@ inherits(Tile, Component); * @returns {void} */ Tile.prototype.clearTokens = function() { - this.tokens = null; + this.tokens = []; }; /** From c6a49eada684b9d030aaa181b51c51c35c90442a Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 22 Feb 2016 19:51:52 -0500 Subject: [PATCH 04/14] adding css back in --- src/checkers/style.css | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/checkers/style.css diff --git a/src/checkers/style.css b/src/checkers/style.css new file mode 100644 index 0000000..d2c2c1c --- /dev/null +++ b/src/checkers/style.css @@ -0,0 +1,50 @@ +/*body { + background-color: #FFCC00; +} + +h1 { + color: black; + margin-left: 200px; + font-family: Futura, "Trebuchet MS", Arial, sans-serif; + font-size: 20vw; + text-align: center; +} + +form { + text-align: center; + font-size: 5vw; + font-family: Futura, "Trebuchet MS", Arial, sans-serif; +} + +select { + background: #FFE064; + width: 5%; + padding: 20px; + font-size: 4vw; + line-height: 1; + border: 0; + border-radius: 0; + height: 15%; + text-align: center; +} + +input { + background: #FFE064; + width: 15%; + padding: 10px; + font-size: 3vw; + line-height: 1; + border: 0; + border-radius: 0; + height: 15%; + text-align: center; + border-radius: 20px; +} + +canvas { + padding-left: 0; + padding-right: 0; + margin-left: auto; + margin-right: auto; + display: block; +}*/ \ No newline at end of file From 8ea9da5c58c8e118e823eb013ea64dd91e769150 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 22 Feb 2016 19:52:06 -0500 Subject: [PATCH 05/14] commenting rather than deleting stylesheets requirement --- tabletop/core/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tabletop/core/index.js b/tabletop/core/index.js index e1b63a5..919d9b1 100644 --- a/tabletop/core/index.js +++ b/tabletop/core/index.js @@ -14,6 +14,7 @@ var core = Object.assign({ NextPlayerView: require('./next_player_view'), Player: require('./player'), StartView: require('./start_view'), +// StyleSheets: require('../../src/checkers/style.css'), Tile: require('./tile'), Token: require('./token'), Trade: require('./trade'), From 22c9f25928a20c47a4c67aceb09cfa8d4897a4a6 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 22 Feb 2016 19:54:15 -0500 Subject: [PATCH 06/14] removing enableAI flag --- src/checkers/checkers_game.js | 1 - tabletop/core/game.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/checkers/checkers_game.js b/src/checkers/checkers_game.js index 4ee3eff..874ce6b 100644 --- a/src/checkers/checkers_game.js +++ b/src/checkers/checkers_game.js @@ -9,7 +9,6 @@ function CheckersGame(board) { this.moveEvaluationType = TableTop.Constants.moveEvalationTypeGameEvaluator; this.possibleNumPlayers = [2]; this.showNextPlayerScreen = false; - this.enableAI = true; }; inherits(CheckersGame, TableTop.Game); diff --git a/tabletop/core/game.js b/tabletop/core/game.js index bef1fc8..c0ad813 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -28,7 +28,6 @@ function Game(board) { this.showNextPlayerScreen = true; this.playerColors = [0xFF0000, 0x000000, 0x00FF00, 0x0000FF, 0xFF00FF]; this.currentPlayer = 0; - this.enableAI = false; }; inherits(Game, Component); From 5089d8f8736f2dc7421a3e268aefe3dac814772c Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 22 Feb 2016 19:55:15 -0500 Subject: [PATCH 07/14] fixing css indent --- src/checkers/style.css | 46 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/checkers/style.css b/src/checkers/style.css index d2c2c1c..7823bc5 100644 --- a/src/checkers/style.css +++ b/src/checkers/style.css @@ -17,34 +17,34 @@ form { } select { - background: #FFE064; - width: 5%; - padding: 20px; - font-size: 4vw; - line-height: 1; - border: 0; - border-radius: 0; - height: 15%; - text-align: center; + background: #FFE064; + width: 5%; + padding: 20px; + font-size: 4vw; + line-height: 1; + border: 0; + border-radius: 0; + height: 15%; + text-align: center; } input { - background: #FFE064; - width: 15%; - padding: 10px; - font-size: 3vw; - line-height: 1; - border: 0; - border-radius: 0; - height: 15%; - text-align: center; - border-radius: 20px; + background: #FFE064; + width: 15%; + padding: 10px; + font-size: 3vw; + line-height: 1; + border: 0; + border-radius: 0; + height: 15%; + text-align: center; + border-radius: 20px; } canvas { padding-left: 0; - padding-right: 0; - margin-left: auto; - margin-right: auto; - display: block; + padding-right: 0; + margin-left: auto; + margin-right: auto; + display: block; }*/ \ No newline at end of file From 95ac50e86896da16367e8ace77714d3a0f5da70b Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Mon, 29 Feb 2016 18:40:12 -0500 Subject: [PATCH 08/14] refactoring, cr changes --- tabletop/core/aiplayer.js | 142 +++++++++++++++++++++++++++++++++++ tabletop/core/game.js | 6 +- tabletop/core/manual_turn.js | 4 +- tabletop/core/player.js | 4 + 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 tabletop/core/aiplayer.js diff --git a/tabletop/core/aiplayer.js b/tabletop/core/aiplayer.js new file mode 100644 index 0000000..a8485f3 --- /dev/null +++ b/tabletop/core/aiplayer.js @@ -0,0 +1,142 @@ +var inherits = require('util').inherits; +var Player = require("./Player.js"); +var Gridboard = require("./Gridboard.js"); +var c = require("./ttConstants"); + +/** + * AI Player + * @constructor + * @extends {Player} + * @param {string} difficulty - difficulty of AI +*/ + +function AIPlayer(difficulty) { + Player.call(this); + this.difficulty = difficulty; +} + +inherits(AIPlayer, Player); + + +AIPlayer.prototype.isAI = function() { + return true; +}; + + +AIPlayer.prototype.pickMove = function(game) { + + var moves = this.getValidMoves(); + var results = []; + + // for each token, grab the appropriate objects from the game copy object + // (since getValidMoves will return actual game objects and we want + // the copies). Then execute the move and score it. + for (var i = 0; i < moves.tokens.length; i++) { + + var tokenIdx = this.board.tokens.indexOf(moves.tokens[i]); + var tilePos = game.board.getTilePosition(moves.tiles[i]); + + moves.destinationTiles[i].forEach(function(dest) { + var destPos = game.board.getTilePosition(dest); + + var gameCopy = game.copyGameStatus(game); + + var tokenCp = gameCopy.board.tokens[tokenIdx]; + var tileCp, destCp; + if (game.board instanceof Gridboard) { + tileCp = gameCopy.board.tiles[tilePos.x][tilePos.y]; + destCp = gameCopy.board.tiles[destPos.x][destPos.y]; + } else { + tileCp = gameCopy.board.tiles[tilePos]; + destCp = gameCopy.board.tiles[destPos]; + } + + gameCopy.proposedMove = { + token: tokenCp, + tile: tileCp, + destination: destCp, + tokenIdx: tokenIdx, + tilePos: tilePos, + destPos: destPos + }; + + gameCopy.executeMove(); + results.push({ + score: gameCopy.scoreBoard(game), + token: tokenCp, + tile: tileCp, + destination: destCp, + tokenIdx: tokenIdx, + tilePos: tilePos, + destPos: destPos + }); + }); + } + + var bestResult = results[0]; + var bestScore = 0; + + + results.forEach(function(result) { + if (result.score > bestScore) { + bestResult = result; + bestScore = result.score; + } + }); + + + var actualToken = game.board.tokens[bestResult.tokenIdx]; + // actualTile should be same as game.board.tile[tilePos], but we + // use this cause it generalizes to all board types + var actualTile = game.board.findTileForToken(actualToken); + + var actualDest; + if (game.board instanceof Gridboard) { + actualDest = game.board.getTile(bestResult.destPos.x, bestResult.destPos.y); + } else { + actualDest = game.board.getTile(bestResult.destPos); + } + + var actualBestMove = + { + token: actualToken, + tile: actualTile, + destination: actualDest + }; + + return actualBestMove; +}; + +AIPlayer.prototype.getValidMoves = function(game) { + + var legalMoves = {}; + legalMoves.tokens = []; + legalMoves.tiles = []; + legalMoves.destinationTiles = []; + if (game.moveType == c.moveTypeManual) { + + game.tokens.forEach(function(token) { + var tile = game.board.findTileForToken(token); + var destinationTiles = []; + // since this.board is a double array we need to double loop + game.board.tiles.forEach(function(destinationRow) { + destinationRow.forEach(function(destination) { + if (game.isValidMove(token, tile, destination)) + destinationTiles.push(destination); + }, this); + }); + + if (destinationTiles.length > 0) { + legalMoves.tokens.push(token); + legalMoves.tiles.push(tile); + legalMoves.destinationTiles.push(destinationTiles); + } + }); + + } + + return legalMoves; +}; + + +module.exports = AIPlayer; diff --git a/tabletop/core/game.js b/tabletop/core/game.js index c0ad813..517a44b 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -3,7 +3,6 @@ var ManualTurn = require("./manual_turn.js"); var Component = require("./component.js"); var inherits = require('util').inherits; var _ = require('lodash'); -var $ = require('jquery'); var Tile = require('./tile.js'); var Token = require('./token.js'); var Player = require('./player.js'); @@ -166,9 +165,9 @@ Game.prototype.nextPlayer = function() { }; /** - * Returns the next player, but does not switch + * Returns the next player, but does not switch like nextPlayer does * Override to provide more logic on determining the next player - * @returns {void} + * @returns {int} The index of the next player. */ Game.prototype.getNextPlayer = function() { return (this.currentPlayer + 1) % this.players.length; @@ -360,6 +359,7 @@ Game.prototype.pickAImove = function(player, game) { var bestResult = results[0]; var bestScore = 0; + results.forEach(function(result) { if (result.score > bestScore) { diff --git a/tabletop/core/manual_turn.js b/tabletop/core/manual_turn.js index e61020f..eeda537 100644 --- a/tabletop/core/manual_turn.js +++ b/tabletop/core/manual_turn.js @@ -58,8 +58,8 @@ function ManualTurn(game, startView, view, gameOverView, nextPlayerView) { waitingForMove: { _onEnter: function() { view.drawView(); - if (this.game.getCurrentPlayer().name == "AI") { - var AIMove = this.game.pickAImove(this.game.getCurrentPlayer(), this.game); + if (this.game.getCurrentPlayer().isAI()) { + var AIMove = this.getCurrentPlayer().pickAImove(this.game); game.proposedMove = AIMove; this.handle("makeMove"); } else { diff --git a/tabletop/core/player.js b/tabletop/core/player.js index 7af9f6a..fe1d001 100644 --- a/tabletop/core/player.js +++ b/tabletop/core/player.js @@ -37,4 +37,8 @@ Player.prototype.destroyToken = function(token) { token.isDead = true; }; +Player.prototype.isAI = function() { + return false; +}; + module.exports = Player; From 4c5b9035726b6de8894ee23c35b75ad09e453887 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 16:26:12 -0500 Subject: [PATCH 09/14] improving ai, refactoring --- src/checkers/checkers_game.js | 30 +++++++ tabletop/core/aiplayer.js | 144 +++++++++------------------------ tabletop/core/game.js | 145 +++++++--------------------------- tabletop/core/manual_turn.js | 5 +- tabletop/core/start_view.js | 11 ++- tabletop/core/ttConstants.js | 12 +++ 6 files changed, 118 insertions(+), 229 deletions(-) diff --git a/src/checkers/checkers_game.js b/src/checkers/checkers_game.js index 874ce6b..f1eb4dd 100644 --- a/src/checkers/checkers_game.js +++ b/src/checkers/checkers_game.js @@ -9,6 +9,7 @@ function CheckersGame(board) { this.moveEvaluationType = TableTop.Constants.moveEvalationTypeGameEvaluator; this.possibleNumPlayers = [2]; this.showNextPlayerScreen = false; + this.AIDifficulty = TableTop.Constants.AIDifficultyHard; }; inherits(CheckersGame, TableTop.Game); @@ -115,6 +116,35 @@ CheckersGame.prototype.playerDidWin = function(player) { +CheckersGame.prototype.getValidMoves = function() { + + var validMoves = []; + + this.board.tokens.forEach(function(token) { + + var tile = this.board.findTileForToken(token); + + // for each possible destination tile... + this.board.tiles.forEach(function(destinationRow) { + destinationRow.forEach(function(destination) { + + if (this.isValidMove(token, tile, destination)) + validMoves.push({ + token: token, + tile: tile, + destination: destination + }); + + + }, this); + }, this); + }, this); + + + return validMoves; +}; + + // takes in a player // returns the score for the board based on the player passed in (ie. if the player // has won it should return 10, if he loses should return -10) diff --git a/tabletop/core/aiplayer.js b/tabletop/core/aiplayer.js index a8485f3..b169fe6 100644 --- a/tabletop/core/aiplayer.js +++ b/tabletop/core/aiplayer.js @@ -1,17 +1,22 @@ var inherits = require('util').inherits; var Player = require("./Player.js"); -var Gridboard = require("./Gridboard.js"); +var Gridboard = require("./grid_board.js"); var c = require("./ttConstants"); /** * AI Player * @constructor * @extends {Player} - * @param {string} difficulty - difficulty of AI + * @param {string} difficulty - difficulty of AI. Should be one of the values of TableTop.Constants.validAIDifficulties. */ -function AIPlayer(difficulty) { - Player.call(this); +function AIPlayer(name, color, difficulty) { + Player.call(this, name, color); + + // safety check + if (c.validAIDifficulties.indexOf(difficulty) == -1) + difficulty = c.AIDifficultyEasy; + this.difficulty = difficulty; } @@ -23,120 +28,43 @@ AIPlayer.prototype.isAI = function() { }; -AIPlayer.prototype.pickMove = function(game) { +AIPlayer.prototype.generateMove = function(game) { - var moves = this.getValidMoves(); + var moves = game.getValidMoves(); var results = []; - - // for each token, grab the appropriate objects from the game copy object - // (since getValidMoves will return actual game objects and we want - // the copies). Then execute the move and score it. - for (var i = 0; i < moves.tokens.length; i++) { - - var tokenIdx = this.board.tokens.indexOf(moves.tokens[i]); - var tilePos = game.board.getTilePosition(moves.tiles[i]); + + moves.forEach(function(move) { - moves.destinationTiles[i].forEach(function(dest) { - var destPos = game.board.getTilePosition(dest); - - var gameCopy = game.copyGameStatus(game); - - var tokenCp = gameCopy.board.tokens[tokenIdx]; - var tileCp, destCp; - if (game.board instanceof Gridboard) { - tileCp = gameCopy.board.tiles[tilePos.x][tilePos.y]; - destCp = gameCopy.board.tiles[destPos.x][destPos.y]; - } else { - tileCp = gameCopy.board.tiles[tilePos]; - destCp = gameCopy.board.tiles[destPos]; - } - - gameCopy.proposedMove = { - token: tokenCp, - tile: tileCp, - destination: destCp, - tokenIdx: tokenIdx, - tilePos: tilePos, - destPos: destPos - }; - - gameCopy.executeMove(); - results.push({ - score: gameCopy.scoreBoard(game), - token: tokenCp, - tile: tileCp, - destination: destCp, - tokenIdx: tokenIdx, - tilePos: tilePos, - destPos: destPos - }); + var gameCopy = game.copyGameStatus(game); + gameCopy.proposedMove = game.copyMoveForGame(move, gameCopy); + gameCopy.executeMove(); + results.push({ + move: move, + score: gameCopy.scoreBoard(game) }); - } - - var bestResult = results[0]; - var bestScore = 0; - - - results.forEach(function(result) { - if (result.score > bestScore) { - bestResult = result; - bestScore = result.score; - } }); - - var actualToken = game.board.tokens[bestResult.tokenIdx]; - // actualTile should be same as game.board.tile[tilePos], but we - // use this cause it generalizes to all board types - var actualTile = game.board.findTileForToken(actualToken); + var result = this.pickMove(results); + return result.move; +}; - var actualDest; - if (game.board instanceof Gridboard) { - actualDest = game.board.getTile(bestResult.destPos.x, bestResult.destPos.y); - } else { - actualDest = game.board.getTile(bestResult.destPos); - } +AIPlayer.prototype.pickMove = function(results) { - var actualBestMove = - { - token: actualToken, - tile: actualTile, - destination: actualDest - }; + // sort descending based on score + var resultsSorted = results.sort(function(a, b) { + return b.score - a.score; + }); - return actualBestMove; -}; - -AIPlayer.prototype.getValidMoves = function(game) { - - var legalMoves = {}; - legalMoves.tokens = []; - legalMoves.tiles = []; - legalMoves.destinationTiles = []; - if (game.moveType == c.moveTypeManual) { - - game.tokens.forEach(function(token) { - var tile = game.board.findTileForToken(token); - var destinationTiles = []; - // since this.board is a double array we need to double loop - game.board.tiles.forEach(function(destinationRow) { - destinationRow.forEach(function(destination) { - if (game.isValidMove(token, tile, destination)) - destinationTiles.push(destination); - }, this); - }); - - if (destinationTiles.length > 0) { - legalMoves.tokens.push(token); - legalMoves.tiles.push(tile); - legalMoves.destinationTiles.push(destinationTiles); - } - }); - + var range; + if (this.difficulty == c.AIDifficultyHard) { + range = 1; + } else if (this.difficulty == c.AIDifficultyMedium) { + range = Math.min(results.length, 3); + } else if (this.difficulty == c.AIDifficultyHard) { + range = Math.min(results.length, 5); } - - return legalMoves; -}; + return resultsSorted[Math.floor(Math.random()*range)]; +}; module.exports = AIPlayer; diff --git a/tabletop/core/game.js b/tabletop/core/game.js index 517a44b..5d58b96 100644 --- a/tabletop/core/game.js +++ b/tabletop/core/game.js @@ -27,6 +27,7 @@ function Game(board) { this.showNextPlayerScreen = true; this.playerColors = [0xFF0000, 0x000000, 0x00FF00, 0x0000FF, 0xFF00FF]; this.currentPlayer = 0; + this.AIDifficulty = c.AIDifficultyEasy; }; inherits(Game, Component); @@ -307,121 +308,6 @@ Game.prototype.destroyToken = function(token) { }; -Game.prototype.pickAImove = function(player, game) { - - var moves = this.getValidMoves(player); - var results = []; - - // for each token, grab the appropriate objects from the game copy object - // (since getValidMoves will return actual game objects and we want - // the copies). Then execute the move and score it. - for (var i = 0; i < moves.tokens.length; i++) { - - var tokenIdx = this.board.tokens.indexOf(moves.tokens[i]); - var tilePos = this.board.getTilePosition(moves.tiles[i]); - - moves.destinationTiles[i].forEach(function(dest) { - var destPos = this.board.getTilePosition(dest); - - var gameCopy = this.copyGameStatus(this); - - var tokenCp = gameCopy.board.tokens[tokenIdx]; - var tileCp, destCp; - if (game.board instanceof Gridboard) { - tileCp = gameCopy.board.tiles[tilePos.x][tilePos.y]; - destCp = gameCopy.board.tiles[destPos.x][destPos.y]; - } else { - tileCp = gameCopy.board.tiles[tilePos]; - destCp = gameCopy.board.tiles[destPos]; - } - - gameCopy.proposedMove = { - token: tokenCp, - tile: tileCp, - destination: destCp, - tokenIdx: tokenIdx, - tilePos: tilePos, - destPos: destPos - }; - - gameCopy.executeMove(); - results.push({ - score: gameCopy.scoreBoard(player), - token: tokenCp, - tile: tileCp, - destination: destCp, - tokenIdx: tokenIdx, - tilePos: tilePos, - destPos: destPos - }); - }, this); - } - - var bestResult = results[0]; - var bestScore = 0; - - - results.forEach(function(result) { - if (result.score > bestScore) { - bestResult = result; - bestScore = result.score; - } - }); - - - var actualToken = this.board.tokens[bestResult.tokenIdx]; - // actualTile should be same as this.board.tile[tilePos], but we - // use this cause it generalizes to all board types - var actualTile = this.board.findTileForToken(actualToken); - - var actualDest; - if (this.board instanceof Gridboard) { - actualDest = this.board.getTile(bestResult.destPos.x, bestResult.destPos.y); - } else { - actualDest = this.board.getTile(bestResult.destPos); - } - - var actualBestMove = - { - token: actualToken, - tile: actualTile, - destination: actualDest - }; - - return actualBestMove; -}; - -Game.prototype.getValidMoves = function(player) { - - var legalMoves = {}; - legalMoves.tokens = []; - legalMoves.tiles = []; - legalMoves.destinationTiles = []; - if (this.moveType == c.moveTypeManual) { - - player.tokens.forEach(function(token) { - var tile = this.board.findTileForToken(token); - var destinationTiles = []; - // since this.board is a double array we need to double loop - this.board.tiles.forEach(function(destinationRow) { - destinationRow.forEach(function(destination) { - if (this.isValidMove(token, tile, destination)) - destinationTiles.push(destination); - }, this); - }, this); - - if (destinationTiles.length > 0) { - legalMoves.tokens.push(token); - legalMoves.tiles.push(tile); - legalMoves.destinationTiles.push(destinationTiles); - } - }, this); - - } - - return legalMoves; -}; - // Create copy of game and manually copy over // tiles/tokens to remove references Game.prototype.copyGameStatus = function(game) { @@ -488,5 +374,34 @@ Game.prototype.copyGameStatus = function(game) { return newGame; }; +Game.prototype.copyMoveForGame = function(move, gameCopy) { + + var newMove = {}; + Object.keys(move).forEach(function(key) { + + var obj = move[key]; + if (obj instanceof Token) { + var tokenIdx = this.board.tokens.indexOf(obj); + var tokenCp = gameCopy.board.tokens[tokenIdx]; + newMove[key] = tokenCp; + } else if (obj instanceof Tile) { + var tilePos = this.board.getTilePosition(obj); + var tileCp; + if (this.board instanceof Gridboard) + tileCp = gameCopy.board.tiles[tilePos.x][tilePos.y]; + else + tileCp = gameCopy.board.tiles[tilePos]; + + newMove[key] = tileCp; + } + }, this); + + return newMove; +}; + +Game.prototype.getValidMoves = function() { + return []; +}; + module.exports = Game; diff --git a/tabletop/core/manual_turn.js b/tabletop/core/manual_turn.js index eeda537..1af532f 100644 --- a/tabletop/core/manual_turn.js +++ b/tabletop/core/manual_turn.js @@ -23,8 +23,7 @@ function ManualTurn(game, startView, view, gameOverView, nextPlayerView) { // 1 uninitialized: { start : function() { - this.transition("startScreen"); - + this.transition("startScreen"); } }, @@ -59,7 +58,7 @@ function ManualTurn(game, startView, view, gameOverView, nextPlayerView) { _onEnter: function() { view.drawView(); if (this.game.getCurrentPlayer().isAI()) { - var AIMove = this.getCurrentPlayer().pickAImove(this.game); + var AIMove = this.game.getCurrentPlayer().generateMove(this.game); game.proposedMove = AIMove; this.handle("makeMove"); } else { diff --git a/tabletop/core/start_view.js b/tabletop/core/start_view.js index 4f35f2f..ad57bba 100644 --- a/tabletop/core/start_view.js +++ b/tabletop/core/start_view.js @@ -1,5 +1,6 @@ var c = require("./ttConstants.js"); var Player = require("./player.js"); +var AIPlayer = require("./aiplayer.js"); var Component = require("./component"); var inherits = require('util').inherits; @@ -118,9 +119,13 @@ StartView.prototype.handleButtonClick = function() { } else { playerName = document.getElementById('player' + (i+1) + 'Name').value; } - players[i] = new Player(playerName, this.game.playerColors[i]); + + if (playerName.toUpperCase() === "AI") + players[i] = new AIPlayer(playerName, this.game.playerColors[i], this.game.AIDifficulty); + else + players[i] = new Player(playerName, this.game.playerColors[i]); } - + this.game.setPlayers(players); this.game.updateState("play"); }; @@ -137,4 +142,4 @@ StartView.prototype.drawMessage = function() { }; -module.exports = StartView; \ No newline at end of file +module.exports = StartView; diff --git a/tabletop/core/ttConstants.js b/tabletop/core/ttConstants.js index ac64b28..b431d0d 100644 --- a/tabletop/core/ttConstants.js +++ b/tabletop/core/ttConstants.js @@ -50,5 +50,17 @@ ttConstants.moveEvaluationTypeLandingAction = 1; // after game.isValidMove() verfies move is legal ttConstants.moveEvaluationTypeGameEvaluator = 2; +// ai difficulties +ttConstants.AIDifficultyEasy = 1; +ttConstants.AIDifficultyMedium = 2; +ttConstants.AIDifficultyHard = 3; +ttConstants.validAIDifficulties = + [ + ttConstants.AIDifficultyEasy, + ttConstants.AIDifficultyMedium, + ttConstants.AIDifficultyHard + ]; + + module.exports = ttConstants; From fc37ac52bff6fbfcab372bea9488b4abf7fb885e Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 16:40:45 -0500 Subject: [PATCH 10/14] basic ai tutorial --- tutorials/markdown/ai.md | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tutorials/markdown/ai.md diff --git a/tutorials/markdown/ai.md b/tutorials/markdown/ai.md new file mode 100644 index 0000000..ce8ecab --- /dev/null +++ b/tutorials/markdown/ai.md @@ -0,0 +1,65 @@ +# Using AI + +## How to use AI Player + +Simply give one or more of the players the name "ai" (case insensitive) + +## How to implement AI in your game + +### Optional + +Set the difficulty using this.AIDifficulty. Defaults to AIDifficultyEasy. + +### Required + +You need to override two methods. + +First, override ```getValidMoves()```. This function should return all the possible moves in the same format used for game.proposedMove. For moveTypeManual games, such as Checkers, the proposed move takes the format +``` +{ + token: token, + tile: tile, + destination: destination +} +``` + +Here's our implemetation for Checkers: + +``` +CheckersGame.prototype.getValidMoves = function() { + + var validMoves = []; + + this.board.tokens.forEach(function(token) { + + var tile = this.board.findTileForToken(token); + + // for each possible destination tile... + this.board.tiles.forEach(function(destinationRow) { + destinationRow.forEach(function(destination) { + + if (this.isValidMove(token, tile, destination)) + validMoves.push({ + token: token, + tile: tile, + destination: destination + }); + + + }, this); + }, this); + }, this); + + + return validMoves; +}; +``` + +Second, you need to override scoreBoard. This can be as simple or as advanced as you'd like. For a game like Checkers, you could value the board based on how many tokens are remaining (shown below). If you wanted more advanced AI, you could play higher value on defending pieces. + +``` +CheckersGame.prototype.scoreBoard = function(player) { + var otherPlayer = this.players[this.getNextPlayer()]; + return 12 - otherPlayer.tokens.length; +}; +``` \ No newline at end of file From 62b4fee952baf9a9599c5c434c25dc5e137e5c29 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 16:45:21 -0500 Subject: [PATCH 11/14] adding advanced section --- tutorials/markdown/ai.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tutorials/markdown/ai.md b/tutorials/markdown/ai.md index ce8ecab..4571e29 100644 --- a/tutorials/markdown/ai.md +++ b/tutorials/markdown/ai.md @@ -62,4 +62,10 @@ CheckersGame.prototype.scoreBoard = function(player) { var otherPlayer = this.players[this.getNextPlayer()]; return 12 - otherPlayer.tokens.length; }; -``` \ No newline at end of file +``` + + +### Advanced + +For the AI to properly work, it needs to be able to understand the objects in your proposedMove object in order to properly convert into objects that the game copies can understand. By default, the AI system copies all tokens and tiles over using their appropriate keys. If your proposed move contains objects that aren't tiles or tokens, then you'll need to override ```game.copyMoveForGame(move, gameCopy)``` + From 959539a8caa34d1d6014baedb0a8f4657ae819e6 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 17:02:37 -0500 Subject: [PATCH 12/14] child.subscribe still not working for me - adding error handling --- tabletop/core/component.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tabletop/core/component.js b/tabletop/core/component.js index b69ff04..8967c59 100644 --- a/tabletop/core/component.js +++ b/tabletop/core/component.js @@ -54,9 +54,13 @@ Component.prototype.subscribe = function(callback) { */ Component.prototype.propagate = function(child) { var context = this; - child.subscribe(function(messageObj) { + if (typeof child.subscribe == 'function') { + child.subscribe(function(messageObj) { context.sendMessage(messageObj.text, messageObj.type, messageObj.sender, messageObj.clientID); - }); + }); + } else { + console.warn("child.subscribe is not a function"); + } }; module.exports = Component; From 27ee68c673404ebb3b2f05b04a88f9c4b45b5928 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 17:02:43 -0500 Subject: [PATCH 13/14] bug fixes --- tabletop/core/aiplayer.js | 2 +- tabletop/core/start_view.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tabletop/core/aiplayer.js b/tabletop/core/aiplayer.js index 8e26e8e..d91c34b 100644 --- a/tabletop/core/aiplayer.js +++ b/tabletop/core/aiplayer.js @@ -60,7 +60,7 @@ AIPlayer.prototype.pickMove = function(results) { range = 1; } else if (this.difficulty == c.AIDifficultyMedium) { range = Math.min(results.length, 3); - } else if (this.difficulty == c.AIDifficultyHard) { + } else if (this.difficulty == c.AIDifficultyEasy) { range = Math.min(results.length, 5); } diff --git a/tabletop/core/start_view.js b/tabletop/core/start_view.js index ad57bba..bae00a2 100644 --- a/tabletop/core/start_view.js +++ b/tabletop/core/start_view.js @@ -121,7 +121,7 @@ StartView.prototype.handleButtonClick = function() { } if (playerName.toUpperCase() === "AI") - players[i] = new AIPlayer(playerName, this.game.playerColors[i], this.game.AIDifficulty); + players[i] = new AIPlayer(playerName, this.game.playerColors[i], null, this.game.AIDifficulty); else players[i] = new Player(playerName, this.game.playerColors[i]); } From 0dbc656b5c48ccef465184e99d1c9ab18164cf61 Mon Sep 17 00:00:00 2001 From: konnorbeard Date: Tue, 1 Mar 2016 19:15:07 -0500 Subject: [PATCH 14/14] cr changes --- src/checkers/checkers_game.js | 8 ++++---- tabletop/core/aiplayer.js | 2 +- tabletop/core/component.js | 12 ++++-------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/checkers/checkers_game.js b/src/checkers/checkers_game.js index f1eb4dd..7015996 100644 --- a/src/checkers/checkers_game.js +++ b/src/checkers/checkers_game.js @@ -115,7 +115,7 @@ CheckersGame.prototype.playerDidWin = function(player) { }; - +// returns all possible valid moves CheckersGame.prototype.getValidMoves = function() { var validMoves = []; @@ -128,13 +128,13 @@ CheckersGame.prototype.getValidMoves = function() { this.board.tiles.forEach(function(destinationRow) { destinationRow.forEach(function(destination) { - if (this.isValidMove(token, tile, destination)) + if (this.isValidMove(token, tile, destination)) { validMoves.push({ token: token, tile: tile, destination: destination }); - + } }, this); }, this); @@ -148,7 +148,7 @@ CheckersGame.prototype.getValidMoves = function() { // takes in a player // returns the score for the board based on the player passed in (ie. if the player // has won it should return 10, if he loses should return -10) -CheckersGame.prototype.scoreBoard = function(player) { +CheckersGame.prototype.scoreBoard = function() { var otherPlayer = this.players[this.getNextPlayer()]; return 12 - otherPlayer.tokens.length; }; diff --git a/tabletop/core/aiplayer.js b/tabletop/core/aiplayer.js index d91c34b..efe2a21 100644 --- a/tabletop/core/aiplayer.js +++ b/tabletop/core/aiplayer.js @@ -40,7 +40,7 @@ AIPlayer.prototype.generateMove = function(game) { gameCopy.executeMove(); results.push({ move: move, - score: gameCopy.scoreBoard(game) + score: gameCopy.scoreBoard() }); }); diff --git a/tabletop/core/component.js b/tabletop/core/component.js index 8967c59..c67a495 100644 --- a/tabletop/core/component.js +++ b/tabletop/core/component.js @@ -53,14 +53,10 @@ Component.prototype.subscribe = function(callback) { * @return {void} */ Component.prototype.propagate = function(child) { - var context = this; - if (typeof child.subscribe == 'function') { - child.subscribe(function(messageObj) { - context.sendMessage(messageObj.text, messageObj.type, messageObj.sender, messageObj.clientID); - }); - } else { - console.warn("child.subscribe is not a function"); - } + var context = this; + child.subscribe(function(messageObj) { + context.sendMessage(messageObj.text, messageObj.type, messageObj.sender, messageObj.clientID); + }); }; module.exports = Component;