/***************************************************************************
**                                                                        **
**                          Connect-4 Algorithm                           **
**                                                                        **
**                              Version 3.10                              **
**                                                                        **
**                            By Keith Pomakis                            **
**                          (pomakis@pobox.com)                           **
**                                                                        **
**                              April, 2005                               **
**                                                                        **
****************************************************************************
**                                                                        **
**                          Sample Implementation!                        **
**                                                                        **
**  This code is poorly written and contains no internal documentation.   **
**  Its sole purpose is to quickly demonstrate an actual implementation   **
**  of the functions contained in the file "c4.c".  It's a fully working  **
**  game which should work on any type of system, so give it a shot!      **
**                                                                        **
**  The computer is pretty brain-dead at level 3 or less, but at level 4  **
**  and up it provides quite a challenge!                                 **
**                                                                        **
****************************************************************************
**  $Id: game.c,v 3.10 2005/04/21 20:10:01 pomakis Exp pomakis $
***************************************************************************/

#include "../pre_emptive_os/api/osapi.h"
#include "../pre_emptive_os/api/general.h"
#include <printf_P.h>
#include <ea_init.h>
#include <stdlib.h>
#include "lcd.h"
#include "key.h"
#include "select.h"

#include <string.h>

const unsigned char _red[] = {0x0e,0x0e,0x01,0x00,
0x00,0x04,0x03,0xe0,0x00,0x05,0xe0,0x03,0x00,0x05,0x03,0xe0,0x00,0x09,0xe0,0x03,
0x03,0x03,0x00,0x0c,0xe0,0x03,0x03,0x00,0x0c,0xe0,0x03,0x00,0x54,0xe0,0x03,0x00,
0x0c,0xe0,0x03,0x03,0x00,0x0c,0xe0,0x03,0x03,0x03,0x00,0x0a,0xe0,0x03,0x00,0x05,
0x03,0xe0,0x00,0x05,0xe0,0x03,0x03,0x03,0x03
};
const unsigned char _yellow[] = {0x0e,0x0e,0x01,0x00,
0x00,0x04,0x03,0xfc,0x00,0x05,0xfc,0x03,0x00,0x05,0x03,0xfc,0x00,0x09,0xfc,0x03,
0x03,0x03,0x00,0x0c,0xfc,0x03,0x03,0x00,0x0c,0xfc,0x03,0x00,0x54,0xfc,0x03,0x00,
0x0c,0xfc,0x03,0x03,0x00,0x0c,0xfc,0x03,0x03,0x03,0x00,0x0a,0xfc,0x03,0x00,0x05,
0x03,0xfc,0x00,0x05,0xfc,0x03,0x03,0x03,0x03
};
const unsigned char _white[] = {0x0e,0x0e,0x01,0x00,
0x00,0x04,0x03,0xff,0x00,0x05,0xff,0x03,0x00,0x05,0x03,0xff,0x00,0x09,0xff,0x03,
0x03,0x03,0x00,0x0c,0xff,0x03,0x03,0x00,0x0c,0xff,0x03,0x00,0x54,0xff,0x03,0x00,
0x0c,0xff,0x03,0x03,0x00,0x0c,0xff,0x03,0x03,0x03,0x00,0x0a,0xff,0x03,0x00,0x05,
0x03,0xff,0x00,0x05,0xff,0x03,0x03,0x03,0x03
};


/******************************************************************************
 * Typedefs and defines
 *****************************************************************************/

#define GAME_NOT_STARTED 0
#define GAME_RUNNING     1
#define GAME_OVER        2
#define GAME_END         3

#define MODE_MOVING      0
#define MODE_SELECTING   1

#define SCREEN_WIDTH  ((tU8)130)
#define SCREEN_HEIGHT ((tU8)130)
#define NUM_COLS ((tU8)7)
#define NUM_ROWS ((tU8)6)

#define BORDER ((tU8)2)
#define BOARD_X_OFFSET  ((tU8)4)
#define BOARD_Y_OFFSET  ((tU8)4)

#define CELL_WIDTH   ((tU8)(130-(BOARD_X_OFFSET*2)-(NUM_COLS+1)*BORDER)/NUM_COLS)
#define CELL_HEIGHT  ((tU8)(130-(BOARD_Y_OFFSET*2)-(NUM_ROWS+1)*BORDER)/NUM_ROWS)

#define BOARD_WIDTH  ((tU8)((NUM_COLS+1)*BORDER+NUM_COLS*CELL_WIDTH))
#define BOARD_HEIGHT ((tU8)((NUM_ROWS+1)*BORDER+NUM_ROWS*CELL_HEIGHT))

#define BOARD_GRID_COLOR  ((tU8)0)    // black
#define BOARD_BKG_COLOR   ((tU8)0x02) // blue
#define GAME_BKG_COLOR_OK    ((tU8)0x02) // blue
#define GAME_BKG_COLOR_ERR   ((tU8)0xe0) // red

#define SELECTED_BKG_COLOR  ((tU8)0xfc) // yellow
#define FONT_FIXED_COLOR    ((tU8)0xe0) // black
#define FONT_UNFIXED_COLOR  ((tU8)0x02) // blue

#define CELL_X_OFFSET(col)  (BOARD_X_OFFSET + BORDER + (col*BORDER) + ((col)*CELL_WIDTH))
#define CELL_Y_OFFSET(row)  (BOARD_Y_OFFSET + BORDER + (row*BORDER) + ((row)*CELL_HEIGHT))

#define CELL_PAD_X  ((tU8)1)
#define CELL_PAD_Y  ((tU8)1)

#define CHAR_HEIGHT 14
#define CHAR_WIDTH   8  
#define CENTER_X(numchars) ((SCREEN_WIDTH - numchars*CHAR_WIDTH)/2)


#define IS_SELECTED(row,col) (row==currRow && col==currCol)

#include <time.h>

#ifndef Boolean
#define Boolean char
#endif

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#define C4_NONE      2
#define C4_MAX_LEVEL 12 //was 20

#define SIZE_X 7
#define SIZE_Y 6
#define TOTAL_SIZE SIZE_X*SIZE_Y
#define NUM_TO_CONNECT 4
#define WIN_PLACES  4*SIZE_X*SIZE_Y - 3*SIZE_X*NUM_TO_CONNECT - 3*SIZE_Y*NUM_TO_CONNECT + 3*SIZE_X + 3*SIZE_Y - 4*NUM_TO_CONNECT + 2*NUM_TO_CONNECT*NUM_TO_CONNECT + 2


/* Some macros for convenience. */

#define other(x)        ((x) ^ 1)
#define real_player(x)  ((x) & 1)

#define pop_state() \
        (current_state = &state_stack[--depth])

/* The "goodness" of the current state with respect to a player is the */
/* score of that player minus the score of the player's opponent.  A   */
/* positive value will result if the specified player is in a better   */
/* situation than his/her opponent.                                    */

#define goodness_of(player) \
        (current_state->score[player] - current_state->score[other(player)])

/* A local struct which defines the state of a game. */

typedef struct {

    tU8 board[SIZE_X][SIZE_Y];/* The board configuration of the game state.  */
                            /* board[x][y] specifies the position of the   */
                            /* xth column and the yth row of the board,    */
                            /* where column and row numbering starts at 0. */
                            /* (The 0th row is the bottom row.)            */
                            /* A value of 0 specifies that the position is */
                            /* occupied by a piece owned by player 0, a    */
                            /* value of 1 specifies that the position is   */
                            /* occupied by a piece owned by player 1, and  */
                            /* a value of C4_NONE specifies that the       */
                            /* position is unoccupied.                     */

    tS8 score_array[2][WIN_PLACES];  /* An array specifying statistics on both      */
                            /* players.  score_array[0] specifies the      */
                            /* statistics for player 0, while              */
                            /* score_array[1] specifies the statistics for */
                            /* player 1.                                   */

    tS8 score[2];           /* The actual scores of each player, deducible */
                            /* from score_array, but kept separately for   */
                            /* efficiency.  The score of player x is the   */
                            /* sum of score_array[x].  A score is          */
                            /* basically a function of how many winning    */
                            /* positions are still available to the        */
                            /* and how close he/she is to achieving each   */
                            /* of these positions.                         */

    tS8 winner;       /* The winner of the game - either 0, 1 or     */
                            /* C4_NONE.  Deducible from score_array, but   */
                            /* kept separately for efficiency.             */

    tS8 num_of_pieces;      /* The number of pieces currently occupying    */
                            /* board spaces.  Deducible from board, but    */
                            /* kept separately for efficiency.             */

} Game_state;

/* Static global variables. */


static tS8 map[SIZE_X][SIZE_Y][WIN_PLACES];  
                    /* map[x][y] is an array of win place indices, */
                    /* terminated by a -1.                         */

static tS8 magic_win_number;
static tBool game_in_progress = FALSE, move_in_progress = FALSE;
static tBool seed_chosen = FALSE;
static void (*poll_function)(void) = NULL;
static clock_t poll_interval, next_poll;
static Game_state state_stack[C4_MAX_LEVEL+1];
static Game_state *current_state;
static tS8 depth;
static tS8 states_allocated = 0;
static tS8 drop_order[SIZE_X];

/* A declaration of the local functions. */

static void update_score(tS8 player, tS8 x, tS8 y);
static tS8 drop_piece(tS8 player, tS8 column);
static void push_state(void);
static tS8 evaluate(tS8 player, tS8 level, tS8 alpha, tS8 beta);
static void drawGrid(void);
static void drawCell(tU8 row, tU8 col, tBool selected);
static void moveTo(tU8 newCol);


static tU8 gameStatus;
static tU8 gameMode;

static tU8 currRow;
static tU8 currCol;

static tU8 selectedNum;

#define BOARD_SIZE   7

/* See the file "c4.c" for documentation on the following functions. */

extern void    c4_poll(void (*poll_func)(void), clock_t interval);
extern void    c4_new_game();
extern tBool   c4_make_move(tS8 player, tS8 column, tS8 *row);
extern tBool   c4_auto_move(tS8 player, tS8 level, tS8 *column, tS8 *row);
extern tU8  ** c4_board(void);
extern tS8     c4_score_of_player(tS8 player);
extern tBool   c4_is_winner(tS8 player);
extern tBool   c4_is_tie(void);
extern void    c4_win_coords(tS8 *x1, tS8 *y1, tS8 *x2, tS8 *y2);
extern void    c4_end_game(void);
extern void    c4_reset(void);
extern void    c4_print_board(void);


enum {HUMAN = 0, COMPUTER = 1};

static tU8 piece[2] = { 'X', 'O' };

/*****************************************************************************
 *
 * Description:
 *    Draw game background and game board, initialize all variables
 *
 ****************************************************************************/
static void
setupGame ()
{
  tU8 row,col;
  c4_new_game();
 
  currCol = 0;

  gameMode = MODE_MOVING;
  
  drawGrid();
  
  for (row=0;row<SIZE_Y;row++)
    for (col=0;col<SIZE_X;col++)
      drawCell(row,col,IS_SELECTED(row,col));
}

/*****************************************************************************
 *
 * Description:
 *    Show Startup Screen
 *
 ****************************************************************************/
static void
showPreStartupScreen(void)
{
  // clear screen
  lcdColor(GAME_BKG_COLOR_OK,BOARD_GRID_COLOR);
  lcdClrscr();
  lcdRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GAME_BKG_COLOR_OK); 


  // print message
  lcdGotoxy(CENTER_X(9), SCREEN_HEIGHT/2 - 2*CHAR_HEIGHT-2);
  lcdPuts("Connect-4"); 
  lcdGotoxy(CENTER_X(12), SCREEN_HEIGHT/2 - CHAR_HEIGHT-2);
  lcdPuts("Press button");  
  lcdGotoxy(CENTER_X(8), SCREEN_HEIGHT/2 + 2);
  lcdPuts("to start");  

 }

void
playConnect4()
{
  //tS8 x1,y1,x2,y2;
  showPreStartupScreen();
  gameStatus = GAME_NOT_STARTED;
  
  while(gameStatus != GAME_END)
    {
      tU8 anyKey;
      
      osSleep(1);
      
      anyKey = checkKey();
      switch(gameStatus)
	{
	case GAME_NOT_STARTED:
	  if (anyKey != KEY_NOTHING)
	    {
	      gameStatus = GAME_RUNNING;
	      setupGame();
	      //c4_print_board();
	    }
	  break;
	  
	case GAME_RUNNING:
	  if (anyKey != KEY_NOTHING)
	    {
	      if (anyKey == KEY_RIGHT)
		{
		  moveTo((currCol+1) % SIZE_X);
		}
	      else if (anyKey == KEY_LEFT)
		{
		  moveTo((currCol+(SIZE_X-1)) % SIZE_X);
		}
	      else if (anyKey == KEY_CENTER)
		{
		  if (c4_make_move(HUMAN, currCol, NULL))
		    {
		      //Human move was valid, so update board & get computer move
		      //c4_print_board();
		      //refresh the grid
		      drawGrid();
		      c4_auto_move(COMPUTER, 4, NULL, NULL);
		      //c4_print_board();
		      //refresh the grid
		      drawGrid();
		      if (c4_is_winner(HUMAN) || c4_is_winner(COMPUTER) || c4_is_tie())
			{
			  gameStatus = GAME_OVER;
			  break;
			}
		    }
		}
	      
	      osSleep(1);
	    }
	  else
	    {
	      
	      osSleep(1);
	    }
	  break;
	case GAME_OVER:
	  {
	    // c4_win_coords(&x1, &y1, &x2, &y2);
	    //printf("  (%d,%d) to (%d,%d)\n\n", x1+1, y1+1, x2+1, y2+1);
	  tMenu menu;
	  
	  menu.xPos = 10;
	  menu.yPos = 40;
	  menu.xLen = 6+(12*8);
	  menu.yLen = 4*14;
	  menu.noOfChoices = 2;
	  menu.initialChoice = 0;
	  if(c4_is_winner(0)) menu.pHeaderText = "You won!";
	  else if(c4_is_winner(1)) menu.pHeaderText = "I won!!!";
	  else if(c4_is_tie()) menu.pHeaderText = "Draw!";
	  menu.headerTextXpos = 20;
	  menu.pChoice[0] = "Restart game";
	  menu.pChoice[1] = "End game";
	  menu.bgColor       = 0;
	  menu.borderColor   = 0x6d;
	  menu.headerColor   = 0;
	  menu.choicesColor  = 0xfd;
	  menu.selectedColor = 0xe0;
	  
	  switch(drawMenu(menu))
	    {
	    case 0: gameStatus = GAME_RUNNING;setupGame(); break;  //Restart game
	    case 1: gameStatus = GAME_END; c4_end_game();break    //End game
	    default: break;
	    }
	  }
	  break;
        
	default:
	  gameStatus = GAME_END;
	  break;
	  
	 
	}
    }
  
  
  return;  

}

/*****************************************************************************
 *
 * Description:
 *    Draws the board and background, but no numbers
 *
 ****************************************************************************/
static void
drawCell(tU8 row, tU8 col, tBool selected)
{
  tU8 bkgColor;
   
  // calculate cell offsets
  tU8 xOff = CELL_X_OFFSET(col);
  tU8 yOff = CELL_Y_OFFSET(NUM_ROWS-1-row)+5;
  
  if (selected)
    bkgColor = SELECTED_BKG_COLOR;
  else
    bkgColor = BOARD_BKG_COLOR;

  // clear cell
  lcdRect(xOff,yOff,CELL_WIDTH+1,CELL_HEIGHT-2,bkgColor);
    
  //draw symbol
  //lcdGotoxy(xOff+CELL_PAD_X, yOff+CELL_PAD_Y);
  if (current_state->board[col][row]==0)
     lcdIcon(xOff+CELL_PAD_X,yOff+CELL_PAD_Y,_red[0],_red[1],_red[2],_red[3],&_red[4]);
  else if (current_state->board[col][row]==1)
     lcdIcon(xOff+CELL_PAD_X,yOff+CELL_PAD_Y,_yellow[0],_yellow[1],_yellow[2],_yellow[3],&_yellow[4]);
  else if (current_state->board[col][row]==C4_NONE)
     lcdIcon(xOff+CELL_PAD_X,yOff+CELL_PAD_Y,_white[0],_white[1],_white[2],_white[3],&_white[4]);
 
}

/*****************************************************************************
 *
 * Description:
 *    Changes current position to the new position
 *
 ****************************************************************************/
static void
moveTo(tU8 newCol)
{
  // leave old cell
  drawCell(0,currCol,FALSE);
  
  // enter new cell
  drawCell(0,newCol,TRUE);
  
  //currRow = newRow;
  currCol = newCol;
}

/*****************************************************************************
 *
 * Description:
 *    Draws the board and background, but no numbers
 *
 ****************************************************************************/
static void
drawGrid(void)
{
  tU8 i,j,x,y;
  
  // clear screen
  lcdColor(GAME_BKG_COLOR_OK,BOARD_GRID_COLOR);
  lcdClrscr();
  
  // draw background
  lcdRect(BOARD_X_OFFSET, BOARD_Y_OFFSET, BOARD_WIDTH, BOARD_HEIGHT, BOARD_BKG_COLOR); 
  
  //clear all cells
  for (i=0;i<SIZE_Y;i++)
    for (j=0;j<SIZE_X;j++)
      drawCell(i, j, FALSE);
  //show currently selected cell
  drawCell(currRow,currCol,TRUE);


}

/*

Connect 4 for LPC Game Board, based on Keith Pmakis' code.
Changes have been made to reduce the code size, by using a fixed size
game board of 7 by 6, with a connect of 4 to win.

 */
/***************************************************************************
**                                                                        **
**                          Connect-4 Algorithm                           **
**                                                                        **
**                              Version 3.10                              **
**                                                                        **
**                            By Keith Pomakis                            **
**                          (pomakis@pobox.com)                           **
**                                                                        **
**                              April, 2005                               **
**                                                                        **
****************************************************************************

/****************************************************************************/
/**                                                                        **/
/**  This function sets up a new game.  This must be called exactly once   **/
/**  before each game is started.  Before it can be called a second time,  **/
/**  end_game() must be called to destroy the previous game.               **/
/**                                                                        **/
/**  width and height are the desired dimensions of the game board, while  **/
/**  num is the number of pieces required to connect in a row in order to  **/
/**  win the game.                                                         **/
/**                                                                        **/
/****************************************************************************/

void
c4_new_game()
{
    register tS8 i, j, k, x;
    tS8 win_index, column;
    tS8 *win_indices;

   
    magic_win_number = 1 << 4;

    /* Set up a random seed for making random decisions when there is */
    /* equal goodness between two moves.                              */

    //if (!seed_chosen) {
    //    srand((tU8) time((time_t *) 0));
    //    seed_chosen = TRUE;
    //}

    /* Set up the board */

    depth = 0;
    current_state = &state_stack[0];
 
    for (i=0; i<SIZE_X; i++) {
        for (j=0; j<SIZE_Y; j++)
            current_state->board[i][j] = C4_NONE;
    }
    
    /* Set up the score array */

    for (i=0; i<WIN_PLACES; i++) {
        current_state->score_array[0][i] = 1;
        current_state->score_array[1][i] = 1;
    }

    current_state->score[0] = current_state->score[1] = WIN_PLACES;
    current_state->winner = C4_NONE;
    current_state->num_of_pieces = 0;

    states_allocated = 1;

    /* Set up the map */

    for (i=0; i<SIZE_X; i++) {
      for (j=0; j<SIZE_Y; j++) {
	map[i][j][0] = -1;
      }
    }
    
    win_index = 0;

    /* Fill in the horizontal win positions */
    for (i=0; i<SIZE_Y; i++)
        for (j=0; j<SIZE_X-NUM_TO_CONNECT+1; j++) {
            for (k=0; k<NUM_TO_CONNECT; k++) {
                win_indices = map[j+k][i];
                for (x=0; win_indices[x] != -1; x++)
                    ;
                win_indices[x++] = win_index;
                win_indices[x] = -1;
            }
            win_index++;
        }

    /* Fill in the vertical win positions */
    for (i=0; i<SIZE_X; i++)
        for (j=0; j<SIZE_Y-NUM_TO_CONNECT+1; j++) {
            for (k=0; k<NUM_TO_CONNECT; k++) {
                win_indices = map[i][j+k];
                for (x=0; win_indices[x] != -1; x++)
                    ;
                win_indices[x++] = win_index;
                win_indices[x] = -1;
            }
            win_index++;
        }

    /* Fill in the forward diagonal win positions */
    for (i=0; i<SIZE_Y-NUM_TO_CONNECT+1; i++)
        for (j=0; j<SIZE_X-NUM_TO_CONNECT+1; j++) {
            for (k=0; k<NUM_TO_CONNECT; k++) {
                win_indices = map[j+k][i+k];
                for (x=0; win_indices[x] != -1; x++)
                    ;
                win_indices[x++] = win_index;
                win_indices[x] = -1;
            }
            win_index++;
        }

    /* Fill in the backward diagonal win positions */
    for (i=0; i<SIZE_Y-NUM_TO_CONNECT+1; i++)
        for (j=SIZE_X-1; j>=NUM_TO_CONNECT-1; j--) {
            for (k=0; k<NUM_TO_CONNECT; k++) {
                win_indices = map[j-k][i+k];
                for (x=0; win_indices[x] != -1; x++)
                    ;
                win_indices[x++] = win_index;
                win_indices[x] = -1;
            }
            win_index++;
        }

    /* Set up the order in which automatic moves should be tried. */
    /* The columns nearer to the center of the board are usually  */
    /* better tactically and are more likely to lead to a win.    */
    /* By ordering the search such that the central columns are   */
    /* tried first, alpha-beta cutoff is much more effective.     */

    column = (SIZE_X-1) / 2;
    for (i=1; i<=SIZE_X; i++) {
        drop_order[i-1] = column;
        column += ((i%2)? i : -i);
    }

    game_in_progress = TRUE;
}


/****************************************************************************/
/**                                                                        **/
/**  This function drops a piece of the specified player into the          **/
/**  specified column.  A value of TRUE is returned if the drop is         **/
/**  successful, or FALSE otherwise.  A drop is unsuccessful if the        **/
/**  specified column number is invalid or full.  If the drop is           **/
/**  successful and row is a non-NULL pointer, the row where the piece     **/
/**  ended up is returned through the row pointer.  Note that column and   **/
/**  row numbering start at 0.                                             **/
/**                                                                        **/
/****************************************************************************/

tBool
c4_make_move(tS8 player, tS8 column, tS8 *row)
{
    tS8 result; 

    if (column >= SIZE_X || column < 0)
        return FALSE;

    result = drop_piece(real_player(player), column);
    if (row && result >= 0)
        *row = result;
    return (result >= 0);
}

extern void
c4_print_board()
{
    tS8 x, y;
    //tU8 **board;

    static tU8 piece[2] = { 'X', 'O' };

    printf("\n");
    for (y=SIZE_Y-1; y>=0; y--) {

      printf("|");
      for (x=0; x<SIZE_X; x++) {
	if (current_state->board[x][y] == C4_NONE)
	  printf(" |");
	else
	  // printf("x|");
	  printf("%c|", piece[current_state->board[x][y]]);
      }
      printf("\n");
      
      printf("+");
      for (x=0; x<SIZE_X; x++)
	printf("-+");
      printf("\n");
    }
    
    printf(" ");
    for (x=0; x<SIZE_X; x++)
      printf("%d ", (x>8)?(x+1)/10:x+1);

    printf("\n\n");
}


/****************************************************************************/
/**                                                                        **/
/**  This function instructs the computer to make a move for the specified **/
/**  player.  level specifies the number of levels deep the computer       **/
/**  should search the game tree in order to make its decision.  This      **/
/**  corresponds to the number of "moves" in the game, where each player's **/
/**  turn is considered a move.  A value of TRUE is returned if a move was **/
/**  made, or FALSE otherwise (i.e. if the board is full).  If a move was  **/
/**  made, the column and row where the piece ended up is returned through **/
/**  the column and row pointers (unless a pointer is NULL, in which case  **/
/**  it won't be used to return any information).  Note that column and    **/
/**  row numbering start at 0.  Also note that for a standard 7x6 game of  **/
/**  Connect-4, the computer is brain-dead at levels of three or less,     **/
/**  while at levels of four or more the computer provides a challenge.    **/
/**                                                                        **/
/****************************************************************************/

tBool
c4_auto_move(tS8 player, tS8 level, tS8 *column, tS8 *row)
{
    tS8 i, best_column = -1, goodness = 0, best_worst = -127;
    tS8 num_of_equal = 0, real_player, current_column, result;

     real_player = real_player(player);
 
    /* It has been proven that the best first move for a standard 7x6 game  */
    /* of connect-4 is the center column.  See Victor Allis' masters thesis */
    /* ("ftp://ftp.cs.vu.nl/pub/victor/connect4.ps") for this proof.        */

    if (current_state->num_of_pieces < 2 &&
                        SIZE_X == 7 && SIZE_Y == 6 && NUM_TO_CONNECT == 4 &&
                        (current_state->num_of_pieces == 0 ||
                         current_state->board[3][0] != C4_NONE)) {
        if (column)
            *column = 3;
        if (row)
            *row = current_state->num_of_pieces;
        drop_piece(real_player, 3);
        return TRUE;
    }
 
    move_in_progress = TRUE;

    /* Simulate a drop in each of the columns and see what the results are. */

    for (i=0; i<SIZE_X; i++) {

      push_state();
      current_column = drop_order[i];
      result = drop_piece(real_player, current_column);
      
      /* If this column is full, ignore it as a possibility. */
      if (result < 0) {
	pop_state();
	continue;
      }
      
        /* If this drop wins the game, take it! */
      else if (current_state->winner == real_player) {
	best_column = current_column;
	pop_state();
	break;
      }
      
      /* Otherwise, look ahead to see how good this move may turn out */
      /* to be (assuming the opponent makes the best moves possible). */
      else {
	//next_poll = clock() + poll_interval;
	goodness = evaluate(real_player, level, -127, -best_worst);
      }
      
      /* If this move looks better than the ones previously considered, */
      /* remember it.                                                   */
      if (goodness > best_worst) {
	best_worst = goodness;
	best_column = current_column;
	num_of_equal = 1;
      }
      
      /* If two moves are equally as good, make a random decision. */
      else if (goodness == best_worst) {
	num_of_equal++;
	if ((rand()>>4) % num_of_equal == 0)
	  best_column = current_column;
      }
      
      pop_state();
    }
    
    move_in_progress = FALSE;
    
    /* Drop the piece in the column decided upon. */
    
    if (best_column >= 0) {
      result = drop_piece(real_player, best_column);
      if (column)
	*column = best_column;
      if (row)
	*row = result;
      return TRUE;
    }
    else
      return FALSE;
}



/****************************************************************************/
/**                                                                        **/
/**  This function returns a two-dimensional array containing the state of **/
/**  the game board.  Do not modify this array.  It is assumed that a game **/
/**  is in progress.  The value of this array is dynamic and will change   **/
/**  to reflect the state of the game as the game progresses.  It becomes  **/
/**  and stays undefined when the game ends.                               **/
/**                                                                        **/
/**  The first dimension specifies the column number and the second        **/
/**  dimension specifies the row number, where column and row numbering    **/
/**  start at 0 and the bottow row is considered the 0th row.  A value of  **/
/**  0 specifies that the position is occupied by a piece owned by player  **/
/**  0, a value of 1 specifies that the position is occupied by a piece    **/
/**  owned by player 1, and a value of C4_NONE specifies that the position **/
/**  is unoccupied.                                                        **/
/**                                                                        **/
/****************************************************************************/

tU8 **
c4_board(void)
{
  return current_state->board;
}


/****************************************************************************/
/**                                                                        **/
/**  This function returns the "score" of the specified player.  This      **/
/**  score is a function of how many winning positions are still available **/
/**  to the player and how close he/she is to achieving each of these      **/
/**  positions.  The scores of both players can be compared to observe how **/
/**  well they are doing relative to each other.                           **/
/**                                                                        **/
/****************************************************************************/

tS8
c4_score_of_player(tS8 player)
{
  return current_state->score[real_player(player)];
}


/****************************************************************************/
/**                                                                        **/
/**  This function returns TRUE if the specified player has won the game,  **/
/**  and FALSE otherwise.                                                  **/
/**                                                                        **/
/****************************************************************************/

tBool
c4_is_winner(tS8 player)
{
  return (current_state->winner == real_player(player));
}


/****************************************************************************/
/**                                                                        **/
/**  This function returns TRUE if the board is completely full without a  **/
/**  winner, and FALSE otherwise.                                          **/
/**                                                                        **/
/****************************************************************************/

tBool
c4_is_tie(void)
{
  return (current_state->num_of_pieces == TOTAL_SIZE &&
            current_state->winner == C4_NONE);
}


/****************************************************************************/
/**                                                                        **/
/**  This function returns the coordinates of the winning connections of   **/
/**  the winning player.  It is assumed that a player has indeed won the   **/
/**  game.  The coordinates are returned in x1, y1, x2, y2, where (x1, y1) **/
/**  specifies the lower-left piece of the winning connection, and         **/
/**  (x2, y2) specifies the upper-right piece of the winning connection.   **/
/**  If more than one winning connection exists, only one will be          **/
/**  returned.                                                             **/
/**                                                                        **/
/****************************************************************************/

void
c4_win_coords(tS8 *x1, tS8 *y1, tS8 *x2, tS8 *y2)
{
    register tS8 i, j, k;
    tS8 winner, win_pos = 0;
    tBool found;

 
    winner = current_state->winner;
 
    while (current_state->score_array[winner][win_pos] != magic_win_number)
        win_pos++;

    /* Find the lower-left piece of the winning connection. */

    found = FALSE;
    for (j=0; j<SIZE_Y && !found; j++)
        for (i=0; i<SIZE_X && !found; i++)
            for (k=0; map[i][j][k] != -1; k++)
                if (map[i][j][k] == win_pos) {
                    *x1 = i;
                    *y1 = j;
                    found = TRUE;
                    break;
                }

    /* Find the upper-right piece of the winning connection. */

    found = FALSE;
    for (j=SIZE_Y-1; j>=0 && !found; j--)
        for (i=SIZE_X-1; i>=0 && !found; i--)
            for (k=0; map[i][j][k] != -1; k++)
                if (map[i][j][k] == win_pos) {
                    *x2 = i;
                    *y2 = j;
                    found = TRUE;
                    break;
                }
}


/****************************************************************************/
/**                                                                        **/
/**  This function ends the current game.  It is assumed that a game is    **/
/**  in progress.  It is illegal to call any other game function           **/
/**  immediately after this one except for c4_new_game(), c4_poll() and    **/
/**  c4_reset().                                                           **/
/**                                                                        **/
/****************************************************************************/

void
c4_end_game(void)
{
  states_allocated = 0;
  game_in_progress = FALSE;
}


/****************************************************************************/
/**                                                                        **/
/**  This function resets the state of the algorithm to the starting state **/
/**  (i.e., no game in progress and a NULL poll function).  There should   **/
/**  no reason to call this function unless for some reason the calling    **/
/**  algorithm loses track of the game state.  It is illegal to call any   **/
/**  other game function immediately after this one except for             **/
/**  c4_new_game(), c4_poll() and c4_reset().                              **/
/**                                                                        **/
/****************************************************************************/

void
c4_reset(void)
{
  
    if (game_in_progress)
        c4_end_game();
    poll_function = NULL;
}

/****************************************************************************/
/****************************************************************************/
/**                                                                        **/
/**  The following functions are local to this file and should not be      **/
/**  called externally.                                                    **/
/**                                                                        **/
/****************************************************************************/
/****************************************************************************/

/****************************************************************************/
/**                                                                        **/
/**  This function updates the score of the specified player in the        **/
/**  context of the current state,  given that the player has just placed  **/
/**  a game piece in column x, row y.                                      **/
/**                                                                        **/
/****************************************************************************/

static void
update_score(tS8 player, tS8 x, tS8 y)
{
    register tS8 i;
    tS8 win_index;
    tS8 this_difference = 0, other_difference = 0;
    //tS8 **current_score_array = current_state->score_array;
    tS8 other_player = other(player);


    for (i=0; map[x][y][i] != -1; i++) {
        win_index = map[x][y][i];
        this_difference += current_state->score_array[player][win_index];
        other_difference += current_state->score_array[other_player][win_index];

        current_state->score_array[player][win_index] <<= 1;
        current_state->score_array[other_player][win_index] = 0;

        if (current_state->score_array[player][win_index] == magic_win_number)
	  if (current_state->winner == C4_NONE)
	      current_state->winner = player;
    }

    current_state->score[player] += this_difference;
    current_state->score[other_player] -= other_difference;
}


/****************************************************************************/
/**                                                                        **/
/**  This function drops a piece of the specified player into the          **/
/**  specified column.  The row where the piece ended up is returned, or   **/
/**  -1 if the drop was unsuccessful (i.e., the specified column is full). **/
/**                                                                        **/
/****************************************************************************/

static tS8
drop_piece(tS8 player, tS8 column)
{
    tS8 y = 0;

    while (current_state->board[column][y] != C4_NONE && ++y < SIZE_Y)
        ;

    if (y == SIZE_Y)
        return -1;

    current_state->board[column][y] = player;
    current_state->num_of_pieces++;
    update_score(player, column, y);

    return y;
}


/****************************************************************************/
/**                                                                        **/
/**  This function pushes the current state onto a stack.  pop_state()     **/
/**  is used to pop from this stack.                                       **/
/**                                                                        **/
/**  Technically what it does, since the current state is considered to    **/
/**  be the top of the stack, is push a copy of the current state onto     **/
/**  the stack right above it.  The stack pointer (depth) is then          **/
/**  incremented so that the new copy is considered to be the current      **/
/**  state.  That way, all pop_state() has to do is decrement the stack    **/
/**  pointer.                                                              **/
/**                                                                        **/
/**  For efficiency, memory for each stack state used is only allocated    **/
/**  once per game, and reused for the remainder of the game.              **/
/**                                                                        **/
/****************************************************************************/

static void
push_state(void)
{
    register tS8 i, win_places_array_size;
    Game_state *old_state, *new_state;

    win_places_array_size = WIN_PLACES * sizeof(tS8);
    old_state = &state_stack[depth++];
    new_state = &state_stack[depth];

    if (depth == states_allocated) {

      states_allocated++;
    }

    /* Copy the board */

    for (i=0; i<SIZE_X; i++)
        memcpy(new_state->board[i], old_state->board[i], SIZE_Y);

    /* Copy the score array */

    memcpy(new_state->score_array[0], old_state->score_array[0],
           win_places_array_size);
    memcpy(new_state->score_array[1], old_state->score_array[1],
           win_places_array_size);

    new_state->score[0] = old_state->score[0];
    new_state->score[1] = old_state->score[1];
    new_state->winner = old_state->winner;
    new_state->num_of_pieces = old_state->num_of_pieces;

    current_state = new_state;
}


/****************************************************************************/
/**                                                                        **/
/**  This recursive function determines how good the current state may     **/
/**  turn out to be for the specified player.  It does this by looking     **/
/**  ahead level moves.  It is assumed that both the specified player and  **/
/**  the opponent may make the best move possible.  alpha and beta are     **/
/**  used for alpha-beta cutoff so that the game tree can be pruned to     **/
/**  avoid searching unneccessary paths.                                   **/
/**                                                                        **/
/**  The specified poll function (if any) is called at the appropriate     **/
/**  intervals.                                                            **/
/**                                                                        **/
/**  The worst goodness that the current state can produce in the number   **/
/**  of moves (levels) searched is returned.  This is the best the         **/
/**  specified player can hope to achieve with this state (since it is     **/
/**  assumed that the opponent will make the best moves possible).         **/
/**                                                                        **/
/****************************************************************************/

static tS8
evaluate(tS8 player, tS8 level, tS8 alpha, tS8 beta)
{
    tS8 i, goodness, best, maxab;

    if (poll_function != NULL && next_poll <= clock()) {
        next_poll += poll_interval;
        (*poll_function)();
    }

    if (current_state->winner == player)
        return 127 - depth;
    else if (current_state->winner == other(player))
        return -(127 - depth);
    else if (current_state->num_of_pieces == TOTAL_SIZE)
        return 0; /* a tie */
    else if (level == depth)
        return goodness_of(player);
    else {
        /* Assume it is the other player's turn. */
        best = -127;
        maxab = alpha;
        for(i=0; i<SIZE_X; i++) {
            if (current_state->board[drop_order[i]][SIZE_Y-1] != C4_NONE)
                continue; /* The column is full. */
            push_state();
            drop_piece(other(player), drop_order[i]);
            goodness = evaluate(other(player), level, -beta, -maxab);
            if (goodness > best) {
                best = goodness;
                if (best > maxab)
                    maxab = best;
            }
            pop_state();
            if (best > beta)
                break;
        }

        /* What's good for the other player is bad for this one. */
        return -best;
    }
}


