﻿////////////////////////////////////////////////////////////////////
/// reversi.js
/// © 2007 Carl Johansen
/// 
/// Routines for Reversi computer player.
////////////////////////////////////////////////////////////////////

//These are really constants
var BOARD_SIZE = 8;
var IllegalSquare = -1;
var EmptySquare = 0;
var WhitePlayer = 1;
var BlackPlayer = 2;

var theBoard = new Array();
var evalWeight;

//---------------------------------------------------------------------------------------

function GetOpponent(player)
{
    return (player == WhitePlayer) ? BlackPlayer : WhitePlayer;
}

//---------------------------------------------------------------------------------------

function GetInitializedBoard()
{
    var i, j;
    var theBoard = new Array(BOARD_SIZE + 2);
    for(i = 0; i <= (BOARD_SIZE + 1); i++)
    {
        theBoard[i] = new Array(BOARD_SIZE + 2);
        for(j = 0; j <= (BOARD_SIZE + 1); j++)
            if(i == 0 || i == (BOARD_SIZE + 1) || j == 0 || j == (BOARD_SIZE + 1))
                theBoard[i][j] = IllegalSquare;
            else
                theBoard[i][j] = EmptySquare;
    }
    theBoard[4][4] = WhitePlayer;
    theBoard[5][5] = WhitePlayer;
    theBoard[4][5] = BlackPlayer;
    theBoard[5][4] = BlackPlayer; 
    
    return theBoard;       
}

//---------------------------------------------------------------------------------------

function GetInitializedEvaluator()
{
    var currRow, currCol;

    var evalWeight = new Array(BOARD_SIZE + 1);

    for(currRow = 1; currRow <= BOARD_SIZE; currRow++)
    {
        evalWeight[currRow] = new Array(BOARD_SIZE + 1);
        for(currCol = 1; currCol <= BOARD_SIZE; currCol++)
            if(currRow == 1 || currRow == BOARD_SIZE || currCol == 1 || currCol == BOARD_SIZE)
                evalWeight[currRow][currCol] = BOARD_SIZE;
            else if(currRow == 2 || currRow == (BOARD_SIZE - 1) || currCol == 2 || currCol == (BOARD_SIZE - 1))
                evalWeight[currRow][currCol] = 1;
            else if(currRow == 3 || currRow == (BOARD_SIZE - 2) || currCol == 3 || currCol == (BOARD_SIZE - 2))
                evalWeight[currRow][currCol] = 5;
            else if(currRow == 4 || currRow == (BOARD_SIZE - 3) || currCol == 4 || currCol == (BOARD_SIZE - 3))
                evalWeight[currRow][currCol] = 3;
    }
    
    evalWeight[1][1] = 15;
    evalWeight[1][BOARD_SIZE] = 15;
    evalWeight[BOARD_SIZE][1] = 15;
    evalWeight[BOARD_SIZE][BOARD_SIZE] = 15;
    evalWeight[2][2] = -1;
    evalWeight[2][BOARD_SIZE - 1] = -1;
    evalWeight[BOARD_SIZE - 1][2] = -1;
    evalWeight[BOARD_SIZE - 1][BOARD_SIZE - 1] = -1;
    evalWeight[1][2] = 1;
    evalWeight[1][BOARD_SIZE - 1] = 1;
    evalWeight[BOARD_SIZE][2] = 1;
    evalWeight[BOARD_SIZE][BOARD_SIZE - 1] = 1;
    
    return evalWeight;
}

//---------------------------------------------------------------------------------------

function IsValidMove(theBoard, moveRow, moveCol, player)
{
    var opponent = GetOpponent(player);
    var offset, checkSquare, i, j;
    
    for(i = -1; i <= 1; i++)
        for(j = -1; j <= 1; j++)
            if(i != 0 || j != 0)
            {
                if(theBoard[moveRow + i][moveCol + j] == opponent)
                {
                    offset = 2;
                    do {
                        checkSquare = theBoard[moveRow + i * offset][moveCol + j * offset];
                        if(checkSquare == player)
                            return true;
                        offset++;
                    } while(checkSquare == opponent);
                }
            }
    return false;            
}
    
//---------------------------------------------------------------------------------------

function ApplyMove(theBoard, moveRow, moveCol, player)
{
    //ApplyMove assumes that the move is valid!
    //It returns the modified state of theBoard.
    
    var opponent = GetOpponent(player);
    var offset, applyOffset, checkSquare, i, j;
    
    theBoard[moveRow][moveCol] = player;

    for(i = -1; i <= 1; i++)
    for(j = -1; j <= 1; j++)
        if(i != 0 || j != 0)
        {
            if(theBoard[moveRow + i][moveCol + j] == opponent)
            {
                offset = 2;
                do {
                    checkSquare = theBoard[moveRow + i * offset][moveCol + j * offset];
                    if(checkSquare == player)
                    {
                        //Apply capture along vector [i, j]
                        for(applyOffset = 1; applyOffset < offset; applyOffset++)
                            theBoard[moveRow + i * applyOffset][moveCol + j * applyOffset] = player;
                        break;
                    }
                    offset++;
                } while(checkSquare == opponent);
            }
        }
    return theBoard;
}

//---------------------------------------------------------------------------------------

function GetValidMoveList(theBoard, player)
{
    var currRow, currCol, intNumValidMoves = 0;
    var arrValidMoves = new Array(2);
    arrValidMoves[0] = new Array(40);
    arrValidMoves[1] = new Array(40);
    
    for(currRow = 1; currRow <= BOARD_SIZE; currRow++)
        for(currCol = 1; currCol <= BOARD_SIZE; currCol++)
            if(theBoard[currRow][currCol] == EmptySquare)
                if(IsValidMove(theBoard, currRow, currCol, player))
                {
                    arrValidMoves[0][intNumValidMoves] = currRow;
                    arrValidMoves[1][intNumValidMoves] = currCol;
                    intNumValidMoves++;
                }
    
    var result = new Object();
    result.NumValidMoves = intNumValidMoves;
    result.ValidMoves = arrValidMoves;
    return result;
}

//---------------------------------------------------------------------------------------

function CloneBoard(theBoard)
{
    var boardCopy  = new Array(10);
    var i;
    for(i = 0; i < 10; i++)
        boardCopy[i] = [].concat(theBoard[i]);
    return boardCopy;
}
//---------------------------------------------------------------------------------------

function ChooseMove(theBoard, player)
{
    var boardCopy = CloneBoard(theBoard);
        
    var result = new Object();
    var intNumMoves, arrMoveList, intBestMove, intBestScore, i, intCurrScore, intNumEmptySquares
	var blnUseSimpleCount, intSearchDepth, dummy1, dummy2
	
	var squareCount = GetSquareCount(boardCopy);
	var blnUseSimpleCount = ( squareCount.NumEmpty < 8 );
	
	var validMoveList = GetValidMoveList(boardCopy, player);
	intNumMoves = validMoveList.NumValidMoves;
	if(intNumMoves == 0)
	{
	    //Forfeit move.
	    result.Row = -1;
	    result.Score = EvaluatePosition(boardCopy, player, blnUseSimpleCount);
	    return result;
	}
	
	intSearchDepth = (squareCount.NumEmpty > 8) ? 1 : 7;
	intBestScore = -9999;
	
	for(i = 0; i < intNumMoves; i++)
	{
	    intCurrScore = EvaluateMove(boardCopy, player, validMoveList.ValidMoves[0][i], validMoveList.ValidMoves[1][i],
	                                    intSearchDepth, blnUseSimpleCount);
        if(intCurrScore > intBestScore || (intCurrScore == intBestScore && Math.random() < 0.5))
        {
            intBestMove = i;
            intBestScore = intCurrScore;
        }            
	}
	result.Row = validMoveList.ValidMoves[0][intBestMove];
	result.Col = validMoveList.ValidMoves[1][intBestMove];
	result.Score = intBestScore;
	return result;	
}

//---------------------------------------------------------------------------------------

function EvaluateMove(theBoard, player, moveRow, moveCol, searchDepth, blnUseSimpleCount)
{
    var boardCopy = CloneBoard(theBoard);
        
    var opponent = GetOpponent(player);
    boardCopy = ApplyMove(boardCopy, moveRow, moveCol, player);
    
    if(searchDepth > 0)
    {
    	var validReplyList = GetValidMoveList(boardCopy, player);
        var intNumReplies = validReplyList.NumValidMoves;
        if(intNumReplies == 0)        
            //Opponent would have no reply.
            return ChooseMove(boardCopy, player).Score;
        else
        {
            var intBestOpponentScore = -9999;
            var i;
            for(i = 0; i < intNumReplies; i++)
            {
                var intOpponentScore = EvaluateMove(boardCopy, opponent, validReplyList.ValidMoves[0][i], validReplyList.ValidMoves[1][i],
                                                searchDepth - 1, blnUseSimpleCount);
                if(intOpponentScore > intBestOpponentScore)
                    intBestOpponentScore = intOpponentScore;
            }
            return (-1 * intBestOpponentScore);
        }           
    }
    else
        return EvaluatePosition(boardCopy, player, blnUseSimpleCount);
}

//---------------------------------------------------------------------------------------
function EvaluatePosition(theBoard, player, blnUseSimpleCount)
{
	//Returns a score for the position from player's point of view.  High score is better for player, low score is better for opponent.
	var opponent = GetOpponent(player);
    var intScore = 0;
    var intSquareWeight, currRow, currCol, numPlayerSquares = 0, numOpponentSquares = 0;
    if(blnUseSimpleCount)
        intSquareWeight = 1;
        
    for(currRow = 1; currRow <= BOARD_SIZE; currRow++)
        for(currCol = 1; currCol <= BOARD_SIZE; currCol++)
            if(theBoard[currRow][currCol] === player)
            {
                if(!blnUseSimpleCount)
                    intSquareWeight = evalWeight[currRow][currCol];
                intScore += intSquareWeight;
                numPlayerSquares++;
            }
            else if(theBoard[currRow][currCol] == opponent)
            {
                if(!blnUseSimpleCount)
                    intSquareWeight = evalWeight[currRow][currCol];
                intScore -= intSquareWeight;  
                numOpponentSquares++;                  
            }
    if(numOpponentSquares == 0)
       intScore = 9998; //victory 
    else if(numPlayerSquares == 0)
       intScore = -9998; //defeat            
    return intScore;            
}

//---------------------------------------------------------------------------------------

function GetSquareCount(theBoard)
{
    var result = new Object();
    var intNumEmpty = 0, intNumWhite = 0, intNumBlack = 0;
    var currRow, currCol;
    
    for(currRow = 1; currRow <= BOARD_SIZE; currRow++)
        for(currCol = 1; currCol <= BOARD_SIZE; currCol++)       
            switch(theBoard[currRow][currCol])
            {
                case EmptySquare:
                    intNumEmpty++;
                    break;                    
                case WhitePlayer:
                    intNumWhite++;
                    break;                    
                case BlackPlayer:
                    intNumBlack++;
                    break;                    
            }
    
    result.NumWhite = intNumWhite;   
    result.NumBlack = intNumBlack;   
    result.NumEmpty = intNumEmpty;   
    return result;
}

