# Tic Tac Toe AI - library based on Minimax algorithm

I recently struggled with implementing AI in Tic Tac Toe game. For better understanding of minimax algorithm I've decided to separate AI's logic. When I finished I thought that some may find it useful, so I packed AI in easy-to-use API and described its usage in README file on GitHub. I am wondering if my code uses any bad practices or something can be written cleaner and more efficient. I will be more than grateful for any feedback.

https://github.com/pietrzakacper/tic-tac-toe-ai

Code:

ai.js

import init from './init';
import minimax from './minimax';
import {isTerminated,getStateOfGame} from './game';

function getAIAction(data) {
const checkedData = {};

init(data, checkedData);

return minimax(checkedData.board, checkedData.aiCharacter, checkedData.aiCharacter, 0);
}
export default {
getAIMove: function(data) {
return (getAIAction(data)).move;
},
getBoardAfterAIMove: function(data) {
return (getAIAction(data)).board;
},
isTerminated: isTerminated,
getStateOfGame: getStateOfGame
};


init.js

import valid from './validation';

export default function initialization(data, target) {
for (let key in valid) {
if (data.hasOwnProperty(key)) {
target[key] = valid[key](data);
} else {
throw new Error('AI ERROR: Missing property : "' + key + '" in passed data object');
}
}
}


minimax.js

import Action from './action';
import * as GameTools from './game';

export default function calculateValue(board, character, aiCharacter, depth) {

const gameState = GameTools.getStateOfGame(board);
//if simulated game is terminated
if (gameState != 'not-end') {
const tmpAction = new Action(-1);
//evaluate game
tmpAction.score = GameTools.getGameScore(gameState, aiCharacter, depth);
return tmpAction;
}

const actions = [];

GameTools.getAllMoves(board).forEach(move => {
const action = new Action(move);
action.board = GameTools.getBoardAfterSimulatedMove(board, move, character);
action.score = calculateValue(action.board, character === 'x' ? 'o' : 'x', aiCharacter, depth + 1).score;
actions.push(action);
});

//sort actions depending on which player (MAX or MIN) is making move
actions.sort(character === aiCharacter ? ACTION_ASCENDING : ACTION_DESCENDING);

//return best action
return actions[0];
}

const ACTION_ASCENDING = (a, b) => b.score - a.score;

const ACTION_DESCENDING = (a, b) => a.score - b.score;


game.js

import {
whichIfAnyPlayerWon,
isDraw
} from './win';

function isTerminated(board) {
return getStateOfGame(board) === 'not-end' ? false : true;
}

function getStateOfGame(board) {

const supposedChampion = whichIfAnyPlayerWon(board);

if (supposedChampion != 'none') {
return supposedChampion + '-won';
}

if (isDraw(board)) {
return 'draw';
}

return 'not-end';
}

function getGameScore(msg, aiCharacter, depth) {
if (msg === aiCharacter + '-won') {
return 10 - depth;
} else if (msg === 'draw') {
return 0;
}
return depth - 10;
}

function getAllMoves(board) {
const possibleMoves = [];

board.forEach(function(value, index) {
if (value === 'e') {
possibleMoves.push(index);
}
});

return possibleMoves;
}

function getBoardAfterSimulatedMove(board, pos, char) {
const newBoard = board.slice();
newBoard[pos] = char;
return newBoard;
}

export {
getStateOfGame,
isTerminated,
getGameScore,
getAllMoves,
getBoardAfterSimulatedMove
};


win.js

function whichIfAnyPlayerWon(board) {
const characters = ['x', 'o'];

for (let i = 0; i < characters.length; ++i) {
if (hasWon(characters[i], board)) {
return characters[i];
}
}
return 'none';
}

function isDraw(board) {
for (let i = 0; i < board.length; ++i) {
if (board[i] === 'e') {
return false;
}
}
return true;
}

function hasWon(char, board) {

if (isColumnWin(char, board) || isRowWin(char, board) || isDiagonalWin(char, board)) {
return true;
}

return false;
}

function isColumnWin(char, board) {
for (let i = 0; i < 3; ++i) {
if (char === board[i] && char === board[i + 3] && char === board[i + 6]) {
return true;
}
}
return false;
}

function isRowWin(char, board) {
for (let i = 0; i <= 6; i += 3) {
if (char === board[i] && char === board[i + 1] && char === board[i + 2]) {
return true;
}
}
return false;
}

function isDiagonalWin(char, board) {
return char === board[0] && char === board[4] && char == board[8] ||
char === board[2] && char === board[4] && char == board[6];
}

export {
whichIfAnyPlayerWon,
isDraw
};