/* $Id: mancala.c,v 1.2 2007-06-13 18:29:52 sverrehu Exp $ */
/**************************************************************************
 *
 *  FILE            mancala.c
 *  MODULE OF       The board game Mancala.
 *
 *  DESCRIPTION     An implementation of the simple game called Mancala.
 *                  This is the backend.
 *
 *  WRITTEN BY      Sverre H. Huseby
 *
 **************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "minimax.h"
#include "mancala.h"

/**************************************************************************
 *                                                                        *
 *                       P R I V A T E    D A T A                         *
 *                                                                        *
 **************************************************************************/

/*
 *  Define this if you want a more timeconsuming evaluation function.
 *  We're not sure it makes it any better -- it may even make it worse.
 */
#define FULL_EVAL

/*
 *  The board state stack, and the current stacklevel.
 */
static Board boardStack[STACK_LEVELS];
static int   idx = 0;

/*
 *  The game logic function requests an array of pointers to possible
 *  moves for a level of it's recursion. We need one array for each
 *  possible recursion level, so we don't mess things up. possibleMoveTable
 *  contains the real move variables, while possibleMove contains the
 *  pointers into the variable table.
 */
static Move  possibleMoveTable[STACK_LEVELS][MAX_HOLES];
static PMove possibleMove[STACK_LEVELS][MAX_HOLES];



/**************************************************************************
 *                                                                        *
 *                   P R I V A T E    F U N C T I O N S                   *
 *                                                                        *
 **************************************************************************/

/*------------------------------------------------------------------------*
 |                 Callbacks for the game logic function                  |
 *------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------
 *
 *  NAME          pushBoard
 *
 *  FUNCTION      Save the current board state on a stack.
 *
 *  DESCRIPTION   This callback is called at the start of each recursion
 *                level. We use it to save the current board setup, and
 *                to make sure requests for new moves (getMoves()) don't
 *                mess up the array of moves currently being explored.
 */
static void pushBoard(void)
{
    ++idx;
    memcpy(&boardStack[idx], &boardStack[idx - 1], sizeof(Board));
}



/*-------------------------------------------------------------------------
 *
 *  NAME          popBoard
 *
 *  FUNCTION      Restore the current board stat from the stack.
 *
 *  DESCRIPTION   This callback is called at the end of each recursion
 *                level. We restore the board state (remove the
 *                `experimental' moves), and open for reuse of the array
 *                of possible moves.
 */
static void popBoard(void)
{
    --idx;
}



/*-------------------------------------------------------------------------
 *
 *  NAME          getMoves
 *
 *  FUNCTION      Make list of possible moves for the given player.
 *
 *  INPUT         player  the player to make movelist for.
 *
 *  OUTPUT        numMoves
 *                        number of moves in the returned array.
 *
 *  RETURNS       An array of pointers to legal moves, or NULL if no
 *                legal moves for this player.
 *
 *  DESCRIPTION   This callback is called for each recursion level to
 *                get a list of possible following moves. A legal move is
 *                the number of a non-empty hole for the player in question.
 *
 *                The current stacklevel is used to find a `free' array
 *                that we can fill with legal moves. We couldn't use the
 *                same array all the time, since the array we returned in
 *                the previous call is not fully examined (yeah, recursion
 *                gives some interresting problems...).
 */
static PMove *getMoves(Player player, int *numMoves)
{
    int        q, n = 0;
    StoneCount *hole;
    PMove      *m, *ret;

    m = ret = possibleMove[idx];
    hole = boardStack[idx].hole[player];
    for (q = 0; q < MAX_HOLES; q++) {
	if (*hole)
	    m[n++]->hole = q;
	++hole;
    }
    if ((*numMoves = n) == 0)
	ret = NULL;
    return ret;
}



/*-------------------------------------------------------------------------
 *
 *  NAME          undoMove
 *
 *  FUNCTION      Update the board to the state before the given move.
 *
 *  INPUT         player  the player that did the move.
 *                move    pointer to the move to undo.
 *
 *  DESCRIPTION   This callback is called at the end of each recursion
 *                level. Since we really change the board in popBoard(),
 *                we don't need to do anything here.
 */
static void undoMove(Player player, PMove move)
{
    /* memcpy(&boardStack[idx], &boardStack[idx - 1], sizeof(Board)); */
}



/*-------------------------------------------------------------------------
 *
 *  NAME          evalBoard
 *
 *  FUNCTION      Evaluate the board after a player has moved.
 *
 *  INPUT         player  the player to evaluate the board for.
 *
 *  RETURNS       A measure indicating how good the current board is for
 *                the given player. Larger values are better. A positive
 *                value indicates that the given player is in the lead,
 *                while a negative value indicates the opposite.
 *
 *  DESCRIPTION   We do it simple; just see which player has captured
 *                most stones.
 */
static Score evalBoard(Player player)
{
    Board  *b;

#ifdef FULL_EVAL
    Player winner;
    /*
     *  The next thing that happens after the evaluation, is that the
     *  previous board is popped from the stack, so we can let the
     *  evaluation function mess the board up like this:
     */
    checkAndFixWin(&winner);
#endif
    b = &boardStack[idx];
    return b->mancala[player] - b->mancala[player ^ 1];
}



/**************************************************************************
 *                                                                        *
 *                    P U B L I C    F U N C T I O N S                    *
 *                                                                        *
 **************************************************************************/

/*-------------------------------------------------------------------------
 *
 *  NAME          initGame
 *
 *  FUNCTION      Set up variables for a new game.
 */
void initGame(int stones_pr_hole)
{
    int    q, w;
    Board  *b;
    Player player;
    
    /*
     *  The game logic function uses random numbers to choose between
     *  moves that gives equal score, so we initiate the generator.
     */
    srand(time(NULL));

    /*
     *  An important step: Set up the possible move -pointers that the
     *  caller of getMoves() want. We make pointers into the table
     *  containing the real values.
     */
    for (q = 0; q < STACK_LEVELS; q++)
	for (w = 0; w < MAX_HOLES; w++)
	    possibleMove[q][w] = &possibleMoveTable[q][w];

    /*
     *  Clear the game stack, and set up the board to initial state.
     *  The `real' board is actually at position 0 of the stack array.
     */
    idx = 0;
    b = &boardStack[idx];
    for (player = 0; player < 2; player++) {
	b->mancala[player] = 0;
	for (w = 0; w < MAX_HOLES; w++)
	    b->hole[player][w] = stones_pr_hole;
    }
}



/*-------------------------------------------------------------------------
 *
 *  NAME          getHole
 *
 *  FUNCTION      Get number of stones in a hole on the board.
 *
 *  INPUT         player  the player to get number of stones for.
 *                hole    the hole number. 0 is the hole closest to
 *                        the mancala.
 *
 *  RETURNS       The number of stones in the given hole.
 */
StoneCount getHole(Player player, int hole)
{
    return boardStack[0].hole[player][hole];
}



/*-------------------------------------------------------------------------
 *
 *  NAME          getMancala
 *
 *  FUNCTION      Get number of stones in the mancala of a player.
 *
 *  INPUT         player  the player to get number of stones for.
 *
 *  RETURNS       The number of stones in the given player's mancala.
 */
StoneCount getMancala(Player player)
{
    return boardStack[0].mancala[player];
}



/*-------------------------------------------------------------------------
 *
 *  NAME          legalMove
 *
 *  FUNCTION      Check if a human's move is legal.
 *
 *  INPUT         player  the player that wants to move.
 *                move    pointer to the move to check.
 *
 *  RETURNS       1 if legal move, 0 if illegal.
 *
 *  DESCRIPTION   This is used to check if a human player has chosen to
 *                do a legal move. The computer player will only do moves
 *                returned by getMoves(), and those are (or should) all be
 *                legal.
 */
int legalMove(Player player, PMove move)
{
    int   q;
    PMove *legalmove;     /* array of pointerts to legal moves */
    int   numMoves;       /* number of moves in this array */

    legalmove = getMoves(player, &numMoves);
    for (q = 0; q < numMoves; q++) {
	if ((*legalmove)->hole == move->hole)
	    return 1;
	++legalmove;
    }
    return 0;
}



/*-------------------------------------------------------------------------
 *
 *  NAME          checkAndFixWin
 *
 *  FUNCTION      Check if the game is ended, and in that case fix scores.
 *
 *  OUTPUT        winner  the player that won.
 *
 *  RETURNS       0 if game not finished, 1 if one winner, 2 if draw.
 *
 *  DESCRIPTION   The game is over if any of the players have no stones
 *                left to move. In that case, all remaining stones for the
 *                opposite player is moved to that player's mancala, and
 *                the winner is the one with more stones.
 */
int checkAndFixWin(Player *winner)
{
    int        q, ret = 0, sum[2];
    Board      *b;
    StoneCount *hole;
    Player     player;

    b = &boardStack[idx];
    for (player = 0; player < 2; player++) {
	hole = b->hole[player];
	sum[player] = 0;
	for (q = 0; q < MAX_HOLES; q++)
	    sum[player] += hole[q];
    }
    if (sum[0] == 0 || sum[1] == 0) {
	ret = 1;
	for (player = 0; player < 2; player++) {
	    b->mancala[player] += sum[player];
	    hole = b->hole[player];
	    for (q = 0; q < MAX_HOLES; q++)
		hole[q] = 0;
	}
	if (b->mancala[0] > b->mancala[1])
	    *winner = 0;
	else if (b->mancala[0] < b->mancala[1])
	    *winner = 1;
	else
	    ret = 2;
    }
    return ret;
}



/*-------------------------------------------------------------------------
 *
 *  NAME          getBestMove
 *
 *  FUNCTION      Calculate best move for player.
 *
 *  INPUT         player  the player to evaluate.
 *                maxPly  max recursion depth.
 *
 *  RETURNS       Pointer to the best move to make, or NULL if no move
 *                available.
 *
 *  DESCRIPTION   This is just a frontend to the real game logic function,
 *                hiding the static functions found in this file.
 */
PMove getBestMove(Player player, int maxPly)
{
    return miniMax(pushBoard, popBoard,
		   getMoves, doMove, undoMove,
		   evalBoard, player, maxPly);
}



/*------------------------------------------------------------------------*
 |   The following function is a callback, but it's used for moving too.  |
 *------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------
 *
 *  NAME          doMove
 *
 *  FUNCTION      Update the board with the given move.
 *
 *  INPUT         player  the player that does the move.
 *                move    pointer to the move to make.
 *
 *  RETURNS       The next player to move. For Mancala, this is the same
 *                player once again if the last stone ends in the mancala.
 *
 *  DESCRIPTION   This function is used both as a callback for finding the
 *                best moves, and as a `normal' function to update the
 *                board when a move is chosen.
 */
Player doMove(Player player, PMove move)
{
    int        seeds, holeNum, oppositeHoleNum;
    StoneCount *hole, *oppositeHole;
    Board      *b;
    Player     nextPlayer, holeOwner;

    nextPlayer = player ^ 1;
    b = &boardStack[idx];
    holeOwner = player;
    hole = b->hole[holeOwner];
    holeNum = move->hole;
    seeds = hole[holeNum];
    hole[holeNum] = 0;
    for (;;) {
	if (--holeNum < 0) {
	    if (holeOwner == player) {
		++b->mancala[player];
		if (!--seeds) {
		    nextPlayer = player;
		    break;
		}
	    }
	    holeOwner ^= 1;
	    hole = b->hole[holeOwner];
	    holeNum = MAX_HOLES - 1;
	} 
	++hole[holeNum];
	if (!--seeds) {
	    if (holeOwner == player) {
		oppositeHoleNum = MAX_HOLES - 1 - holeNum;
		oppositeHole = b->hole[player ^ 1];
		if (hole[holeNum] == 1 && oppositeHole[oppositeHoleNum]) {
		    b->mancala[player] += 1 + oppositeHole[oppositeHoleNum];
		    oppositeHole[oppositeHoleNum] = 0;
		    hole[holeNum] = 0;
		}
	    }
	    break;
	}
    }
    return nextPlayer;
}
