// Allows bot to play and learn to not lose at Checkers. state Checkers { pattern "checkers" answer start(); pattern "checkers" answer start(); pattern "[stop exit quit end]" topic "Checkers" answer end(); pattern "* *" topic "Checkers" answer play(); pattern "*" topic "Checkers whoStarts" answer whoStarts(); pattern "checkers autoplay" answer autoPlay(); pattern "checkers autoplay *" answer autoPlayMany(); pattern "[yes yep yeah y ok okay]" topic "Checkers playAgain" answer start(); pattern "*" topic "Checkers playAgain" answer end(); pattern "deep test" answer deepLearningMove(); function end() { conversation.topic = null; conversation.board = null; conversation.playerBoard = null; return "Ok, thanks for playing."; } function start() { conversation.topic = "Checkers whoStarts"; conversation.board = null; conversation.playerBoard = null; return "Do you want to be or ?"; } // Responds in accordance to whether the bot is R or B by giving the player a blank board or making a move and returning the board. function whoStarts() { if (star != "R" && star != "B") { return "Type R or B."; } conversation.topic = "Checkers"; conversation.board = "/r/r/r/rr/r/r/r//r/r/r/r_/_/_/_//_/_/_/_b/b/b/b//b/b/b/bb/b/b/b/"; conversation.foo = "/r/r/r/rr/r/r/r//r/r/r/r_/_/_/_//_/_/_/_b/b/b/b//b/b/b/bb/b/b/b/"; var newBoard = conversation.board; if (star == "R") { conversation.player = "R"; conversation.bot = "B"; Avatar.setCommand({ type: "game", start:"Checkers", board: newBoard }); return "Your Move."; } conversation.bot = "R"; conversation.player = "B"; var bot = conversation.bot; var player = conversation.player; makeMove(); Avatar.setCommand({ type: "game", start:"Checkers", board: newBoard }); return "Your move."; } // Plays Checkers. Ensures the player has made a valid move. function play() { var board = conversation.board; var newBoard = board; var bot = conversation.bot; var player = conversation.player; var turn = player; var moves = 0; if (newBoard.length() != 64 ) { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid board."; } var coordinateFrom = star[0].toNumber(); var coordinateTo = star[1].toNumber(); var move = coordinateTo - coordinateFrom; var checkJump = checkJump(); var doubleJump = false; if (board.substring(coordinateTo -1, coordinateTo) != "_") { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid Move." } if (player == "R") { if (board.substring(coordinateFrom - 1, coordinateFrom) != "R") { Avatar.setCommand({ type: "game", board: newBoard }); return "You are R." } if (checkJump == false) { if (board.substring(coordinateFrom - 1, coordinateFrom).toSymbol() == "r".toSymbol()) { if (move != 7 && move != 9) { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid Move."; } } if (board.substring(coordinateFrom - 1, coordinateFrom) == "R") { if (Math.abs(move) != 7 && Math.abs(move) != 9) { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid move." } } } if (checkJump == true) { if (Math.abs(move) != 14 && Math.abs(move) != 18) { Avatar.setCommand({ type: "game", board: newBoard }); return "You must make jump."; } } } if (player == "B") { if (board.substring(coordinateFrom - 1, coordinateFrom) != "B") { Avatar.setCommand({ type: "game", board: newBoard }); return "You are B." } if (checkJump == false) { if (board.substring(coordinateFrom - 1, coordinateFrom).toSymbol() == "b".toSymbol()) { if (move != -7 && move != -9) { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid Move."; } } if (board.substring(coordinateFrom - 1, coordinateFrom) == "B") { if (Math.abs(move) != 7 && Math.abs(move) != 9) { Avatar.setCommand({ type: "game", board: newBoard }); return "Invalid move." } } } if (checkJump == true) { if (Math.abs(move) != 14 && Math.abs(move) != 18) { Avatar.setCommand({ type: "game", board: newBoard }); return "You must make jump."; } } } newBoard = board.substring(0,coordinateTo - 1) + board.substring(coordinateFrom - 1, coordinateFrom) + board.substring(coordinateTo, 64); newBoard = newBoard.substring(0,coordinateFrom - 1) + "_" + newBoard.substring(coordinateFrom, 64); if (checkJump == true) { var jumpedPiece = (coordinateTo + coordinateFrom) / 2; newBoard = newBoard.substring(0,jumpedPiece - 1) + "_" + newBoard.substring(jumpedPiece, 64); doubleJump = true; checkJump = checkJump(); } if (checkQueen() == true) { //newBoard = newBoard.substring(0,coordinateTo - 1) + player + newBoard.substring(coordinateTo, 64); if (player == "R") { newBoard = newBoard.substring(0,coordinateTo - 1) + "R" + newBoard.substring(coordinateTo, 64); } if (player == "B") { newBoard = newBoard.substring(0,coordinateTo - 1) + "B" + newBoard.substring(coordinateTo, 64); } } conversation.board = newBoard; conversation.playerBoard = newBoard; conversation.append(#playerBoards, newBoard); if (checkGameOver() == false) { endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "You win. Want to play again? "; } if (checkJump == true) { Avatar.setCommand({ type: "game", board: newBoard }); return "You can jump again." } makeMove(); } // Bot makes a move. function makeMove() { turn = bot; var moveFrom = null; var moveTo = null; board = conversation.board; doubleJump = false; checkJump = checkJump(); if (checkJump == false) { // *** You can switch the script from using a random move to using a deep learning neural network. randomMove(); //deepLearningMove(); } if (moveTo == null) { endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "You win. Want to play again? "; } if (moveTo != null) { // Move newBoard = board.substring(0,moveTo) + board.substring(moveFrom, moveFrom + 1) + board.substring(moveTo + 1, 64); newBoard = newBoard.substring(0,moveFrom) + "_" + newBoard.substring(moveFrom + 1, 64); if (checkJump == true) { var jumpedPiece = (moveTo + moveFrom) / 2; newBoard = newBoard.substring(0,jumpedPiece) + "_" + newBoard.substring(jumpedPiece + 1, 64); doubleJump = true; checkJump = checkJump(); } if (checkQueen() == true) { newBoard = newBoard.substring(0,moveTo) + bot + newBoard.substring(moveTo + 1, 64); } conversation.board = newBoard; conversation.append(#boards, newBoard); if (checkGameOver() == true) { endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "I win. Want to play again? "; } if (checkJump == true) { makeMove(); } Avatar.setCommand({ type: "game", board: newBoard }); return "Your move."; } } function movesFrom() { var validPieces = new Array(); var bot = conversation.bot; for (var count = 0; count < 64; count++) { var piece = newBoard.substring(count, count + 1); if (bot == "B" && piece == "b") { if ((count - 9 > 0 && newBoard.substring(count - 9, count - 8) == "_") || (count - 7 > 0 && newBoard.substring(count - 7, count - 6) == "_")) { validPieces.add(count); } if (piece.toSymbol() == "B".toSymbol()) { if ((count + 9 < 63 && newBoard.substring(count + 9, count + 10) == "_") || (count + 7 < 63 && newBoard.substring(count + 7, count + 8) == "_")) { validPieces.add(count); } } } if (bot == "R" && piece == "r") { if ((count + 9 < 63 && newBoard.substring(count + 9, count + 10) == "_") || (count + 7 < 63 && newBoard.substring(count + 7, count + 8) == "_")) { validPieces.add(count); } if (piece.toSymbol() == "R".toSymbol()) { if ((count - 9 > 0 && newBoard.substring(count - 9, count - 8) == "_") || (count - 7 > 0 && newBoard.substring(count - 7, count - 6) == "_")) { validPieces.add(count); } } } } } function movesTo() { var validMoves = new Array(); var bot = conversation.bot; if (bot == "B") { if (moveFrom - 9 > 0 && newBoard.substring(moveFrom - 9, moveFrom - 8) == "_" ) { validMoves.add(moveFrom - 9); } if (moveFrom - 7 > 0 && newBoard.substring(moveFrom - 7, moveFrom - 6) == "_") { validMoves.add(moveFrom - 7); } if (newBoard.substring(moveFrom, moveFrom + 1).toSymbol() == "B".toSymbol()) { if (moveFrom + 9 < 63 && newBoard.substring(moveFrom + 9, moveFrom + 10) == "_") { validMoves.add(moveFrom + 9); } if (moveFrom + 7 < 63 && newBoard.substring(moveFrom + 7, moveFrom + 8) == "_") { validMoves.add(moveFrom + 7); } } } if (bot == "R") { if (moveFrom + 9 < 63 && newBoard.substring(moveFrom + 9, moveFrom + 10) == "_") { validMoves.add(moveFrom + 9); } if (moveFrom + 7 < 63 && newBoard.substring(moveFrom + 7, moveFrom + 8) == "_") { validMoves.add(moveFrom + 7); } if (newBoard.substring(moveFrom, moveFrom + 1).toSymbol() == "R".toSymbol()) { if (moveFrom - 9 > 0 && newBoard.substring(moveFrom - 9, moveFrom - 8) == "_" ) { validMoves.add(moveFrom - 9); } if (moveFrom - 7 > 0 && newBoard.substring(moveFrom - 7, moveFrom - 6) == "_") { validMoves.add(moveFrom - 7); } } } } // Checks if someone has won. function checkGameOver() { var win = conversation.bot == "R"; var over = true; for (var count = 0; count < 64; count++) { if (newBoard.substring(count, count + 1) == "R") { over = false; } } if (over == true) { return !win; } over = true; for (var count = 0; count < 64; count++) { if (newBoard.substring(count, count + 1) == "B") { over = false; } } if (over == true) { return win; } return null; } function checkJump() { if (turn == player) { for (var count = 0; count < 64; count++) { if (doubleJump == true) { count = coordinateTo - 1; } if (player == "R" && newBoard.substring(count, count + 1) == "r") { if (count + 14 < 63 && newBoard.substring(count + 7, count + 8) == "b" && newBoard.substring(count + 14, count + 15) == "_") { return true; } if (count + 18 < 63 && newBoard.substring(count + 9, count + 10) == "b" && newBoard.substring(count + 18, count + 19) == "_") { return true; } if (newBoard.substring(count, count + 1).toSymbol() == "R".toSymbol()) { if (count - 14 > 0 && newBoard.substring(count - 7, count - 6) == "b" && newBoard.substring(count - 14, count - 13) == "_") { return true; } if (count - 18 > 0 && newBoard.substring(count - 9, count - 8) == "b" && newBoard.substring(count - 18, count - 17) == "_") { return true; } } } if (player == "B" && newBoard.substring(count, count + 1) == "b") { if (count - 14 > 0 && newBoard.substring(count - 7, count - 6) == "r" && newBoard.substring(count - 14, count - 13) == "_") { return true; } if (count - 18 > 0 && newBoard.substring(count - 9, count - 8) == "r" && newBoard.substring(count - 18, count - 17) == "_") { return true; } if (newBoard.substring(count, count + 1).toSymbol() == "B".toSymbol()) { if (count + 14 < 63 && newBoard.substring(count + 7, count + 8) == "r" && newBoard.substring(count + 14, count + 15) == "_") { return true; } if (count + 18 < 63 && newBoard.substring(count + 9, count + 10) == "r" && newBoard.substring(count + 18, count + 19) == "_") { return true; } } } if (doubleJump == true) { return false; } } } if (turn == bot) { for (var count = 0; count < 64; count++) { if (doubleJump == true) { count = moveTo; } if (bot == "B" && newBoard.substring(count, count + 1) == "b") { if (count - 14 > 0 && newBoard.substring(count - 7, count - 6) == "r" && newBoard.substring(count - 14, count - 13) == "_") { moveFrom = count; moveTo = count - 14; return true; } if (count - 18 > 0 && newBoard.substring(count - 9, count - 8) == "r" && newBoard.substring(count - 18, count - 17) == "_") { moveFrom = count; moveTo = count - 18; return true; } if (newBoard.substring(count, count + 1).toSymbol() == "B".toSymbol()) { if (count + 14 < 63 && newBoard.substring(count + 7, count + 8) == "r" && newBoard.substring(count + 14, count + 15) == "_") { moveFrom = count; moveTo = count + 14; return true; } if (count + 18 < 63 && newBoard.substring(count + 9, count + 10) == "r" && newBoard.substring(count + 18, count + 19) == "_") { moveFrom = count; moveTo = count + 18; return true; } } } if (bot == "R" && newBoard.substring(count, count + 1) == "r") { if (count + 14 < 63 && newBoard.substring(count + 7, count + 8) == "b" && newBoard.substring(count + 14, count + 15) == "_") { moveFrom = count; moveTo = count + 14; return true; } if (count + 18 < 63 && newBoard.substring(count + 9, count + 10) == "b" && newBoard.substring(count + 18, count + 19) == "_") { moveFrom = count; moveTo = count + 18; return true; } if (newBoard.substring(count, count + 1).toSymbol() == "R".toSymbol()) { if (count - 14 > 0 && newBoard.substring(count - 7, count - 6) == "b" && newBoard.substring(count - 14, count - 13) == "_") { moveFrom = count; moveTo = count - 14; return true; } if (count - 18 > 0 && newBoard.substring(count - 9, count - 8) == "b" && newBoard.substring(count - 18, count - 17) == "_") { moveFrom = count; moveTo = count - 18; return true; } } } if (doubleJump == true) { return false; } } } return false; } function checkQueen() { for (var count = 0; count < 8; count++) { if (turn == "B") { if (newBoard.substring(count, count + 1).toSymbol() == "b".toSymbol()) { return true; } } } for (var count = 56; count < 64; count++) { if (turn == "R") { if (newBoard.substring(count, count + 1).toSymbol() == "r".toSymbol()) { return true; } } } return newBoard; } function endGame() { conversation.topic = "Checkers playAgain"; conversation.boards = null; conversation.playerBoards = null; } function randomMove() { movesFrom(); var moveFrom = validPieces.random(#element); movesTo(); var moveTo = validMoves.random(#element); return moveFrom + moveTo; } function deepLearningMove() { debug("Doing a move..."); // rotate the board such that our pieces are moving "up" the board var rotatedBoard = ""; if (bot == "R") { var boardToRotate = newBoard; rotateBoard(); } else { rotatedBoard = newBoard; } // *** Enter your application id from your user page, and optionally your own deep learning Analytic id. var data = " "; // Format the board for the network. // eliminate the unreachable squares, and set input values. for (var count = 0; count < 64; count++) { var piece = rotatedBoard.substring(count, count+1); if (piece != "/") { var input = "0.0"; if (piece == "_") { input = "0.0"; } else if (piece.toSymbol() == "R".toSymbol()) { input = "-1.0"; } else if (piece.toSymbol() == "B".toSymbol()) { input = "1.0"; } else if (piece == "r") { input = "-0.5"; } else if (piece == "b") { input = "0.5"; } else { debug("Unknown piece: " + piece); } data = data + input; if (count != 62) { // 62 is the last one, so omit comma data = data + ", "; } } } data = data + " "; debug("" + data + ""); var result = Http.postXML("https://www.botlibre.com/rest/api/test-data-analytic", data); var outputs = result.output; // convert the output to an array var position = 0; var output = "0"; var outputArray = new Array(); while(outputs.indexOf(",") != -1) { position = outputs.indexOf(","); output = outputs.substring(0,position); output.toNumber(); outputArray.add(output); outputs = outputs.substring(position + 1,outputs.length()); } outputArray.add(outputs); // Determine from list of legal moves which output has highest score. var best = "-1.0"; var bestMoveFrom = null; var bestMoveTo = null; var moveFrom = null; var moveTo = null; movesFrom(); initNetworkOutputConversionMap(); // an array to help convert the output for (var i = 0; i < validPieces.length(); i++) { moveFrom = validPieces[i]; var convertedMoveFrom = null; if (bot == "B") { convertedMoveFrom = validPieces[i].toNumber(); } else if (bot == "R") { // Flip the move around if playing as red. convertedMoveFrom = (63 - validPieces[i]).toNumber(); } movesTo(); for (var j = 0; j < validMoves.length(); j++) { moveTo = validMoves[j]; var convertedMoveTo = null; if (bot == "B") { convertedMoveTo = validMoves[j].toNumber(); } else if (bot == "R") { // Flip the move around if playing as red. convertedMoveTo = (63 - validMoves[j]).toNumber(); } // The output of the network has directionality: // Values from 0-31 mean moving towards the bottom right // from 32-63 mean moving towards the bottom left // from 64-95 mean moving towards the top right // from 96-127 mean moving towards the top left // Convert the legal move from our format to the network's format var index = -1; if (convertedMoveTo == convertedMoveFrom + 7 || convertedMoveTo == convertedMoveFrom + 14) { index = networkOutputConversionMap[convertedMoveFrom]; } else if (convertedMoveTo == convertedMoveFrom + 9 || convertedMoveTo == convertedMoveFrom + 18) { index = networkOutputConversionMap[convertedMoveFrom] + 32; } else if (convertedMoveTo == convertedMoveFrom - 7 || convertedMoveTo == convertedMoveFrom - 14) { index = networkOutputConversionMap[convertedMoveFrom] + 64; } else if (convertedMoveTo == convertedMoveFrom - 9 || convertedMoveTo == convertedMoveFrom - 18) { index = networkOutputConversionMap[convertedMoveFrom] + 96; } // Update our best move if (outputArray[index].toNumber() > best.toNumber()) { best = outputArray[index]; bestMoveFrom = moveFrom; bestMoveTo = moveTo; } } } moveFrom = bestMoveFrom; moveTo = bestMoveTo; debug("Chosen move: " + moveFrom + " " + moveTo); debug("Score: " + best); return moveFrom + moveTo; } // Rotate the board 180 degrees, and flip the pieces. // This is to allow the network to be able to play from both sides. function rotateBoard() { rotatedBoard = ""; for (var i = 0; i < 64; i++) { var originalPiece = boardToRotate.substring(63 - i, 64 - i); var flippedPiece; if (originalPiece == "/" || originalPiece == "_") { flippedPiece = originalPiece; } else if (originalPiece.toSymbol() == "B".toSymbol()) { flippedPiece = "R"; } else if (originalPiece.toSymbol() == "R".toSymbol()) { flippedPiece = "B"; } else if (originalPiece == "b") { flippedPiece = "r"; } else if (originalPiece == "r") { flippedPiece = "b"; } rotatedBoard = rotatedBoard + flippedPiece; } debug(rotatedBoard); return rotatedBoard; } // An array that helps to map every position on the board from the format // used in the rest of the code to the format outputted by the network. // We represent a position as one of 64 possible values. // However, to reduce output size, 32 of these are eliminated since those // squares are unreachable. // This array provides the conversion. function initNetworkOutputConversionMap() { var networkOutputConversionMap = new Array(); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(0); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(1); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(2); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(3); networkOutputConversionMap.add(4); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(5); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(6); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(7); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(8); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(9); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(10); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(11); networkOutputConversionMap.add(12); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(13); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(14); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(15); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(16); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(17); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(18); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(19); networkOutputConversionMap.add(20); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(21); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(22); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(23); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(24); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(25); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(26); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(27); networkOutputConversionMap.add(28); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(29); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(30); networkOutputConversionMap.add(-1); networkOutputConversionMap.add(31); networkOutputConversionMap.add(-1); return networkOutputConversionMap; } }