// Allows bot to play and learn to not lose at TicTacToe. state TicTacToe { pattern "tictactoe" answer start(); pattern "tic tac toe" answer start(); pattern "[stop exit quit end]" topic "TicTacToe" answer end(); pattern "*" topic "TicTacToe" answer play(); pattern "*" topic "TicTacToe whoStarts" answer whoStarts(); pattern "tictactoe autoplay" answer autoPlay(); pattern "tictactoe autoplay *" answer autoPlayMany(); pattern "[yes yep yeah y ok okay]" topic "TicTacToe playAgain" answer start(); pattern "*" topic "TicTacToe playAgain" answer end(); function end() { conversation.topic = null; conversation.board = null; conversation.playerBoard = null; return "Ok, thanks for playing."; } function start() { conversation.topic = "TicTacToe whoStarts"; conversation.board = null; conversation.playerBoard = null; return "Do you want to be or ?"; } // Responds in accordance to whether the bot is X or O by giving the player a blank board or making a move and returning the board. function whoStarts() { if (star != "X" && star != "O") { return "Type X or O."; } conversation.topic = "TicTacToe"; conversation.board = "_________"; conversation.foo = "_________"; var newBoard = conversation.board; if (star == "X") { conversation.player = "X"; conversation.bot = "O"; Avatar.setCommand({ type: "game", start:"TicTacToe", board: newBoard }); return "Your Move."; } conversation.bot = "X"; conversation.player = "O"; var bot = conversation.bot; var player = conversation.player; makeMove(); Avatar.setCommand({ type: "game", start:"TicTacToe", board: newBoard }); return "Your move."; } // Has the bot play another bot a number of times. function autoPlayMany() { var games = star.toNumber(); if (games == null) { return null; } var tallyWins = 0; var tallyLosses = 0; var tallyTies = 0; for (var game = 0; game < games; game++) { autoPlay(); } var lossPercentage = tallyLosses / games * 100; tallyWins + " Wins " + tallyLosses + " Losses " + tallyTies + " Ties. " + lossPercentage + "% loss percentage."; } // Has the bot play another bot once. function autoPlay() { conversation.board = "_________"; var newBoard = conversation.board; var result = null; conversation.bot = "O"; conversation.player = "X"; while (true) { var player = conversation.bot; var bot = conversation.player; var move = randomMove(); if (move == null) { break; } if (Math.random() >= 0.5) { move = findGoodMove(); } newBoard = newBoard.substring(0, move) + "X" + newBoard.substring(move + 1, 9); conversation.board = newBoard; conversation.playerBoard = newBoard; conversation.append(#playerBoards, newBoard); var gameOver = checkGameOver(); if (gameOver == false) { countLosses(); tallyLosses = tallyLosses + 1; result = "I lost. "; break; } bot = conversation.bot; player = conversation.player; move = findGoodMove(); if (move == null) { break; } newBoard = newBoard.substring(0, move) + "O" + newBoard.substring(move + 1, 9); conversation.board = newBoard; conversation.append(#boards, newBoard); var gameOver = checkGameOver(); if (gameOver == true) { countWins(); tallyWins = tallyWins + 1; result = "I win. "; break; } } if (gameOver == null) { countWins(); countLosses(); tallyTies = tallyTies + 1; result = "Tie Game."; } endGame(); Avatar.setCommand({ type: "game", start:"TicTacToe", board: newBoard }); return result; } // Plays TicTacToe. 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; if (star == "A1") { if (newBoard.substring(0,1) != "_") { return "Space is taken." } newBoard = player + board.substring(1,9); } if (star == "A2") { if (newBoard.substring(1,2) != "_") { return "Space is taken." } newBoard = board.substring(0,1) + player + board.substring(2,9); } if (star == "A3") { if (newBoard.substring(2,3) != "_") { return "Space is taken." } newBoard = board.substring(0,2) + player + board.substring(3,9); } if (star == "B1") { if (newBoard.substring(3,4) != "_") { return "Space is taken." } newBoard = board.substring(0,3) + player + board.substring(4,9); } if (star == "B2") { if (newBoard.substring(4,5) != "_") { return "Space is taken." } newBoard = board.substring(0,4) + player + board.substring(5,9); } if (star == "B3") { if (newBoard.substring(5,6) != "_") { return "Space is taken." } newBoard = board.substring(0,5) + player + board.substring(6,9); } if (star == "C1") { if (newBoard.substring(6,7) != "_") { return "Space is taken." } newBoard = board.substring(0,6) + player + board.substring(7,9); } if (star == "C2") { if (newBoard.substring(7,8) != "_") { return "Space is taken." } newBoard = board.substring(0,7) + player + board.substring(8,9); } if (star == "C3") { if (newBoard.substring(8,9) != "_") { return "Space is taken." } newBoard = board.substring(0,8) + player; } if (newBoard.length() != 9 ) { return "Invalid board."; } var moves = 0; for (var count = 0; count < 9; count++) { var newValue = newBoard.substring(count, count + 1); var oldValue = board.substring(count, count + 1); if (newValue != oldValue) { if (newValue != player) { return "Invalid move, you are " + player; } moves = moves + 1; } } if (moves == 0) { return "Make a move." } if (moves > 1) { return "Too many moves." } conversation.board = newBoard; conversation.playerBoard = newBoard; conversation.append(#playerBoards, newBoard); if (checkGameOver() == false) { countLosses(); endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "You win. Want to play again? "; } makeMove(); } // Bot makes a move. function makeMove() { lookAhead(); if (move == null) { findGoodMove(); } if (move != null) { // Move newBoard = newBoard.substring(0, move) + bot + newBoard.substring(move + 1, 9); conversation.board = newBoard; conversation.append(#boards, newBoard); if (checkGameOver() == true) { countWins(); endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return " I win. Want to play again? "; } var openMoves = 0; for (var count = 0; count < 9; count++) { if (newBoard.substring(count, count + 1) == "_") { openMoves = openMoves + 1; } } if (openMoves == 0) { if (checkGameOver() == null) { countLosses(); countWins(); endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "Tie game. Want to play again? "; } } Avatar.setCommand({ type: "game", board: newBoard }); return "Your move."; } countWins(); countLosses(); endGame(); Avatar.setCommand({ type: "game", board: newBoard }); return "Tie game. Want to play again? "; } // Bot looks to see if it can win in one move, lose in one move, or have a guaranteed win in two moves. function lookAhead() { var validMoves = new Array(); for (var count = 0; count < 9; count++) { if (newBoard.substring(count, count + 1) == "_") { validMoves.add(count); } } var move = null; for (validMove in validMoves) { board = newBoard.substring(0, validMove) + bot + newBoard.substring(validMove + 1, 9); newBoard = board; if (checkGameOver() == true) { move = validMove; break; } newBoard = conversation.board; var possibleBoard1 = board; for (playerValidMove in validMoves) { if (playerValidMove == validMove) { continue; } possibleBoard1 = board.substring(0, playerValidMove) + player + board.substring(playerValidMove + 1, 9); newBoard = possibleBoard1; if (checkGameOver() == false) { move = playerValidMove; newBoard = conversation.board; break; } newBoard = conversation.board; var possibleBoard2 = possibleBoard1; var winMoves = 0; for (validMove2 in validMoves) { if (validMove2 == validMove) { continue; } if (validMove2 == playerValidMove) { continue; } possibleBoard2 = possibleBoard1.substring(0, validMove2) + bot + possibleBoard1.substring(validMove2 + 1, 9); newBoard = possibleBoard2; if (checkGameOver() == true) { winMoves = winMoves + 1; } if (winMoves > 1) { move = validMove; break; } newBoard = conversation.board; } } } newBoard = conversation.board; return move; } // Finds a good move by analysing the value of each possible move, then does the same with every possible move the player can make. // Finds the move where the bot has the highest value and the player has the lowest. function findGoodMove() { var validMoves = new Array(); for (var count = 0; count < 9; count++) { if (newBoard.substring(count, count + 1) == "_") { validMoves.add(count); } } var move = null; var bestDifference = null; for (validMove in validMoves) { board = newBoard.substring(0, validMove) + bot + newBoard.substring(validMove + 1, 9); var boardValue = board.value; if (boardValue == null) { boardValue = 0; } var possibleBoard = board; for (playerValidMove in validMoves) { if (playerValidMove == validMove) { continue; } possibleBoard = board.substring(0, playerValidMove) + player + board.substring(playerValidMove + 1, 9); var playerBoardValue = playerBoard.value; if (playerBoardValue == null) { playerBoardValue = 0; } var worstPlayerValue = null; if (worstPlayerValue == null || playerBoardValue < worstPlayerValue) { worstPlayerValue = playerBoardValue; } } var boardDifference = boardValue - worstPlayerValue; if (bestDifference == null || boardDifference > bestDifference) { move = validMove; bestDifference = boardDifference; } } return move; } function randomMove() { var validMoves = new Array(); for (var count = 0; count < 9; count++) { if (newBoard.substring(count, count + 1) == "_") { validMoves.add(count); } } return validMoves.random(#element); } // Checks if someone has won. function checkGameOver() { var win = conversation.bot == "X"; if (newBoard.substring(0, 3) == "OOO") { return !win; } if (newBoard.substring(3, 6) == "OOO") { return !win; } if (newBoard.substring(6, 9) == "OOO") { return !win; } if (newBoard.substring(0, 3) == "XXX") { return win; } if (newBoard.substring(3, 6) == "XXX") { return win; } if (newBoard.substring(6, 9) == "XXX") { return win; } var a1 = newBoard.substring(0, 1); var a2 = newBoard.substring(1, 2); var a3 = newBoard.substring(2, 3); var b1 = newBoard.substring(3, 4); var b2 = newBoard.substring(4, 5); var b3 = newBoard.substring(5, 6); var c1 = newBoard.substring(6, 7); var c2 = newBoard.substring(7, 8); var c3 = newBoard.substring(8, 9); if (a1 == "O" && b1 == "O" && c1 == "O") { return !win; } if (a2 == "O" && b2 == "O" && c2 == "O") { return !win; } if (a3 == "O" && b3 == "O" && c3 == "O") { return !win; } if (a1 == "O" && b2 == "O" && c3 == "O") { return !win; } if (a3 == "O" && b2 == "O" && c1 == "O") { return !win; } if (a1 == "X" && b1 == "X" && c1 == "X") { return win; } if (a2 == "X" && b2 == "X" && c2 == "X") { return win; } if (a3 == "X" && b3 == "X" && c3 == "X") { return win; } if (a1 == "X" && b2 == "X" && c3 == "X") { return win; } if (a3 == "X" && b2 == "X" && c1 == "X") { return win; } return null; } // Assigns a value to each board in the game to show that it lead to the bot's win and the player's loss. function countWins() { var increment = 1 / conversation.size(#boards); var value = increment; for (board in conversation.boards) { if (board.value == null) { board.value = 0; } board.value = board.value / 2 + value / 2; value = value + increment; } value = increment; for (playerBoard in conversation.playerBoards) { if (playerBoard.value == null) { playerBoard.value = 0; } playerBoard.value = playerBoard.value / 2 - value / 2; value = value + increment; } } // Assigns a value to each board in the game to show that it lead to the bot's loss and the player's win. function countLosses() { var increment = 1 / conversation.size(#boards); var value = increment; for (board in conversation.boards) { if (board.value == null) { board.value = 0; } board.value = board.value / 2 - value / 2; value = value + increment; } value = increment; for (playerBoard in conversation.playerBoards) { if (playerBoard.value == null) { playerBoard.value = 0; } playerBoard.value = playerBoard.value / 2 + value / 2; value = value + increment; } } function endGame() { conversation.topic = "TicTacToe playAgain"; conversation.boards = null; conversation.playerBoards = null; } function drawBoard() { return newBoard; var html = ""; for (var count = 0; count < 9; count++) { var character = newBoard.substring(count, count + 1); var cordinate = "A" + (count + 1); if (count > 2) { cordinate = "B" + (count - 2); } if (count > 5) { cordinate = "C" + (count - 5); } html = html + ""; if (count == 2 || count == 5) { html = html + ""; } } html = html + "
" + character + "
"; return html; } }