#include <string.h>
#include <stdlib.h>

#include "octi.h"

#define DATAFILENAME "octi.dat"

GAME *game=0,*undo_game;

#define BT BOARD_TILE
#define OB BOARD_OCTI_BACK
#define OF BOARD_OCTI_FRONT
const int board_tile[BOARD_H][BOARD_W]={{BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,OB,BT,OB,BT,OB,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,OF,BT,OF,BT,OF,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT},
                                        {BT,BT,BT,BT,BT,BT,BT,BT,BT}};
#undef OF
#undef OB
#undef BT

const XY prong_dir[N_PRONGS]={{ 0,-1},{ 1,-1},{ 1, 0},{ 1, 1},
                              { 0, 1},{-1, 1},{-1, 0},{-1,-1}};

#define GFX_W 640
#define GFX_H 480
#define BIG_OCTI_W 432
#define BIG_OCTI_H 144
#define BOARD_X 208
#define BOARD_Y 144
#define TILE_W 48
#define TILE_H 36
#define FRONT_H 12
#define POD_W 48
#define POD_H 44
#define POD_HEIGHT 8

DATAFILE *dat;
BITMAP *scrbuf;

#define CYAN 84
#define RED 131
#define YELLOW 180

#ifndef NO_AI
unsigned char player_computer[MAX_PLAYERS]={0,1};
#endif

#include "octitext.inc"

//This function uses recursion to draw the pods in the right order, while
//the linked list is stored in the opposite order for the convenience of
//processing moves.
void draw_pod(BOARD_POD *pod,RLE_SPRITE *tile,int xp,int *yp) {
 if (pod->under) draw_pod(pod->under,tile,xp,yp);
 *yp-=POD_HEIGHT;
 draw_rle_sprite(scrbuf,tile,xp,*yp);
 if (pod->state&ST_MARKED) draw_rle_sprite(scrbuf,dat[POD_MARK].dat,xp,*yp);
 {
  int p;
  for (p=0;p<N_PRONGS;p++) {
   if (pod->prong[p]) draw_rle_sprite(scrbuf,
                    dat[(pod->prong[p]==2?PRONG_MARK0:PRONG0)+p].dat,xp,*yp);
  }
 }
}

void draw_board(int xs,int ys) {
 blit(dat[BIG_OCTI].dat,scrbuf,0,0,BOARD_X,0,BIG_OCTI_W,BIG_OCTI_H);
 {
  int x,y;
  for (y=0;y<BOARD_H;y++) {
   for (x=0;x<BOARD_W;x++) {
    int xp=BOARD_X+x*TILE_W;
    int yp=BOARD_Y+y*TILE_H;
    blit(dat[board_tile[y][x]].dat,scrbuf,0,0,xp,yp,TILE_W,TILE_H);
    if (game) {
     if (xs==x && ys==y) {
      rect(scrbuf,xp,yp,xp+TILE_W-1,yp+TILE_H-1,RED);
      rect(scrbuf,xp+1,yp+1,xp+TILE_W-2,yp+TILE_H-2,RED);
     }
     if (game->board[y][x].pod) draw_pod(game->board[y][x].pod,
                              dat[POD0+game->board[y][x].player].dat,xp,&yp);
    }
   }
  }
 }
 {
  int x;
  for (x=0;x<BOARD_W;x++) {
   blit(dat[BOARD_FRONT].dat,scrbuf,
                 0,0,BOARD_X+x*TILE_W,BOARD_Y+BOARD_H*TILE_H,TILE_W,FRONT_H);
  }
 }
}

void draw_analysed_pod(BOARD_POD *pod,RLE_SPRITE *tile,int xp,int *yp,
                                                              int n,int ns) {
 if (pod->under) draw_analysed_pod(pod->under,tile,xp,yp,n+1,ns);
 *yp-=TILE_H-POD_HEIGHT;
 if (n==ns) {
  rect(scrbuf,xp,*yp,xp+TILE_W-1,*yp+TILE_H-1,RED);
  rect(scrbuf,xp+1,*yp+1,xp+TILE_W-2,*yp+TILE_H-2,RED);
 }
 *yp-=POD_HEIGHT;

 draw_rle_sprite(scrbuf,tile,xp,*yp);
 if (pod->state&ST_MARKED) draw_rle_sprite(scrbuf,dat[POD_MARK].dat,xp,*yp);
 {
  int p;
  for (p=0;p<N_PRONGS;p++) {
   if (pod->prong[p]) draw_rle_sprite(scrbuf,
                    dat[(pod->prong[p]==2?PRONG_MARK0:PRONG0)+p].dat,xp,*yp);
  }
 }
}

#ifdef DEBUG_MALLOCS
int get_n_mallocs(void);
#endif

void update_screen(void) {
#ifdef DEBUG_MALLOCS
 textprintf(scrbuf,dat[FONT_LARGE].dat,0,GFX_H-26,CYAN,"%d",get_n_mallocs());
#endif
 acquire_screen();
 blit(scrbuf,screen,0,0,0,0,GFX_W,GFX_H);
 release_screen();
}

//x=-1 indicates an error message.
void draw_screen(int x,int y,int n,unsigned char **text) {
 rectfill(scrbuf,0,0,BOARD_X-1,GFX_H-1,0);
 textprintf(scrbuf,dat[FONT_LARGE].dat,0,0,RED,
                                "It is %s's turn.",game->go?"black":"white");
 {
  int y;
  for (y=39;*text;text++,y+=13)
   textout(scrbuf,dat[FONT_SMALL].dat,*text,0,y,CYAN);
 }
 if (x>=0) {
  textprintf(scrbuf,dat[FONT_SMALL].dat,0,143,YELLOW,
                                    "You have %d pod%s and %d prong%s.",
                                    game->player[game->go].pods,
                                    game->player[game->go].pods==1?"":"s",
                                    game->player[game->go].prongs,
                                    game->player[game->go].prongs==1?"":"s");
  textprintf(scrbuf,dat[FONT_SMALL].dat,0,156,YELLOW,
                                "%d of your pods ha%s been captured.",
                                game->player[game->go].captured,
                                game->player[game->go].captured==1?"s":"ve");
  {
   int xp=(BOARD_X-TILE_W)>>1;
   int yp=GFX_H-FRONT_H;
   blit(dat[BOARD_FRONT].dat,scrbuf,0,0,xp,yp,TILE_W,FRONT_H);
   yp-=TILE_H;
   blit(dat[board_tile[y][x]].dat,scrbuf,0,0,xp,yp,TILE_W,TILE_H);
   if (game->board[y][x].pod) {
    yp-=POD_HEIGHT;
    draw_analysed_pod(game->board[y][x].pod,
                          dat[POD0+game->board[y][x].player].dat,xp,&yp,0,n);
   }
  }
 }
 draw_board(x,y);
 update_screen();
}

void initialise_game(void) {
 game=malloc(sizeof(GAME));
 game->board=malloc(sizeof(BOARD_CELL *)*BOARD_H);
 {
  int y;
  for (y=0;y<BOARD_H;y++) {
   game->board[y]=malloc(sizeof(BOARD_CELL)*BOARD_W);
   {
    int x;
    for (x=0;x<BOARD_W;x++) game->board[y][x].pod=0;
   }
  }
 }

 game->board[2][6].player=1;
 game->board[2][6].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[2][6].pod,0,sizeof(BOARD_POD));
 game->board[2][4].player=1;
 game->board[2][4].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[2][4].pod,0,sizeof(BOARD_POD));
 game->board[2][2].player=1;
 game->board[2][2].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[2][2].pod,0,sizeof(BOARD_POD));
 game->board[6][2].player=0;
 game->board[6][2].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[6][2].pod,0,sizeof(BOARD_POD));
 game->board[6][4].player=0;
 game->board[6][4].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[6][4].pod,0,sizeof(BOARD_POD));
 game->board[6][6].player=0;
 game->board[6][6].pod=malloc(sizeof(BOARD_POD));
 memset(game->board[6][6].pod,0,sizeof(BOARD_POD));

 game->player[1].prongs=game->player[0].prongs=25;
 game->player[1].pods=game->player[0].pods=4;
 game->player[1].captured=game->player[0].captured=0;

 game->go=0;

}

void destroy_game(GAME *game) {
 int y;
 for (y=0;y<BOARD_H;y++) {
  {
   int x;
   for (x=0;x<BOARD_W;x++) {
    register BOARD_POD *pod=game->board[y][x].pod;
    while (pod) {
     while (pod->jumped) {
      XY_LINK *prev=pod->jumped->prev;
      free(pod->jumped);
      pod->jumped=prev;
     }
     {
      register BOARD_POD *under=pod->under;
      free(pod);
      pod=under;
     }
    }
   }
  }
  free(game->board[y]);
 }
 free(game->board);
 free(game);
}

int calculate_n_pods(int x,int y) {
 int n=0;
 BOARD_POD *pod=game->board[y][x].pod;
 while (pod) {
  pod=pod->under;
  n++;
 }
 return n;
}

//this returns a pointer to the pointer to the pod, rather than just the
//pointer to the pod. That way, the pointer to the pod can be changed when
//moving pods on the board.
BOARD_POD **get_pod(GAME *game,int x,int y,int n) {
 BOARD_POD **pod=&game->board[y][x].pod;
 while (n) {
  pod=&(*pod)->under;
  n--;
 }
 return pod;
}

//returns 1 on success
unsigned char add_prong(int x,int y,int n,unsigned char move_prong) {
 if (move_prong==0) {//If prong is being moved, checks have already been made
  if (game->player[game->go].prongs==0) {
   draw_screen(-1,-1,-1,text_prong_none_left);
   readkey();
   return 0;
  }
  if (game->board[y][x].pod==0) {
   draw_screen(-1,-1,-1,text_prong_no_pod);
   readkey();
   return 0;
  }
  if (game->board[y][x].player!=game->go) {
   draw_screen(-1,-1,-1,text_prong_unfriendly);
   readkey();
   return 0;
  }
 }
 {
  BOARD_POD *pod=*get_pod(game,x,y,n);
  int p;
  for (p=0;p<N_PRONGS;p++) if (pod->prong[p]==0) goto start_prong;
  draw_screen(-1,-1,-1,text_prong_already_full);
  readkey();
  return 0;
start_prong:
  pod->prong[p]=2;
  draw_screen(x,y,n,move_prong?text_move_prong:text_add_prong);
  for (;;) {
   switch (readkey()>>8) {
    case KEY_4_PAD:
    case KEY_LEFT:
     pod->prong[p]=0;
     do p=(p-1)&(N_PRONGS-1); while (pod->prong[p]);
     goto start_prong;
    case KEY_6_PAD:
    case KEY_RIGHT:
     pod->prong[p]=0;
     do p=(p+1)&(N_PRONGS-1); while (pod->prong[p]);
     goto start_prong;
    case KEY_ENTER:
    case KEY_SPACE:
     pod->prong[p]=1;
     if (move_prong==0) game->player[game->go].prongs--;
     return 1;
    case KEY_ESC:
     pod->prong[p]=0;
     return 0;
   }
  }
 }
}

unsigned char reposition_prong(int x,int y,int n) {
 if (game->board[y][x].pod==0) {
  draw_screen(-1,-1,-1,text_reprong_no_pod);
  readkey();
  return 0;
 }
 if (game->board[y][x].player!=game->go) {
  draw_screen(-1,-1,-1,text_reprong_unfriendly);
  readkey();
  return 0;
 }
 {
  BOARD_POD *pod=*get_pod(game,x,y,n);
  int p;
  for (p=0;p<N_PRONGS;p++) if (pod->prong[p]==0) goto prong_gap;
  draw_screen(-1,-1,-1,text_reprong_full);
  readkey();
  return 0;
prong_gap:
  for (p=0;p<N_PRONGS;p++) if (pod->prong[p]) goto start_prong;
  draw_screen(-1,-1,-1,text_reprong_no_prongs);
  readkey();
  return 0;
start_prong:
  pod->prong[p]=2;
  draw_screen(x,y,n,text_select_prong);
  for (;;) {
   switch (readkey()>>8) {
    case KEY_4_PAD:
    case KEY_LEFT:
     pod->prong[p]=1;
     do p=(p-1)&(N_PRONGS-1); while (pod->prong[p]==0);
     goto start_prong;
    case KEY_6_PAD:
    case KEY_RIGHT:
     pod->prong[p]=1;
     do p=(p+1)&(N_PRONGS-1); while (pod->prong[p]==0);
     goto start_prong;
    case KEY_ENTER:
    case KEY_SPACE:
     {
      unsigned char rv=add_prong(x,y,n,1);
      pod->prong[p]=(rv==0);
      return rv;
     }
    case KEY_ESC:
     pod->prong[p]=1;
     return 0;
   }
  }
 }
}

//returns 1 on success
unsigned char add_pod(int x,int y) {
 int liberate=(game->board[2][2].pod && game->board[2][2].player==game->go) ||
              (game->board[2][4].pod && game->board[2][4].player==game->go) ||
              (game->board[2][6].pod && game->board[2][6].player==game->go);
 if (x==2 || x==4 || x==6) {
  if (y==2) {
   if (liberate) {
    if (game->board[y][x].pod && game->board[y][x].player==game->go) {
     if (game->player[game->go].captured) {
      game->player[game->go].captured--;
      goto addpod_valid;
     }
     if (game->player[game->go].pods) {
      game->player[game->go].pods--;
      goto addpod_valid;
     }
     draw_screen(-1,-1,-1,text_addpod_none_left);
     readkey();
     return 0;
    }
    draw_screen(-1,-1,-1,text_addpod_liberate_unoccupied);
    readkey();
    return 0;
   }
   draw_screen(-1,-1,-1,text_addpod_cannot_liberate);
   readkey();
   return 0;
  }
  if (y==6) {
   if (game->board[y][x].pod==0) {
    if (game->player[game->go].pods) {
     game->player[game->go].pods--;
     goto addpod_valid;
    }
    draw_screen(-1,-1,-1,text_addpod_none_reserve);
    readkey();
    return 0;
   }
   if (game->board[y][x].player==game->go) {
    if (liberate && game->player[game->go].captured) {
     game->player[game->go].captured--;
     goto addpod_valid;
    }
    if (game->player[game->go].pods) {
     game->player[game->go].pods--;
     goto addpod_valid;
    }
    draw_screen(-1,-1,-1,
                    liberate?text_addpod_none_left:text_addpod_none_reserve);
    readkey();
    return 0;
   }
   draw_screen(-1,-1,-1,text_addpod_unfriendly);
   readkey();
   return 0;
  }
 }
 draw_screen(-1,-1,-1,text_addpod_not_octi);
 readkey();
 return 0;
addpod_valid:
 {
  BOARD_POD *pod=malloc(sizeof(BOARD_POD));
  pod->under=game->board[y][x].pod;
  pod->state=0;
  pod->jumped=0;
  {int p; for (p=0;p<N_PRONGS;p++) pod->prong[p]=0;}
  game->board[y][x].pod=pod;
  game->board[y][x].player=game->go;
 }
 return 1;
}

void move_pod(int *x,int *y,int *n,int *max_n,int *sx,int *sy) {
 if (game->board[*y][*x].pod==0) {
  draw_screen(-1,-1,-1,text_move_no_pod);
  readkey();
  return;
 }
 if (game->board[*y][*x].player!=game->go) {
  draw_screen(-1,-1,-1,text_move_unfriendly);
  readkey();
  return;
 }
 {
  BOARD_POD **spod=get_pod(game,*x,*y,*n);
  {
   int p;
   for (p=0;p<N_PRONGS;p++) if ((*spod)->prong[p]) goto piece_has_prongs;
  }
  draw_screen(-1,-1,-1,text_move_no_prongs);
  readkey();
  return;
piece_has_prongs:
  if (*sx>=0) {//a pod has already moved
   if (*x!=*sx || *y!=*sy) {//moving a pod from a different square
    if (((*spod)->state&ST_JUMPED)==0) {//this pod has not jumped
     draw_screen(-1,-1,-1,((*spod)->state&ST_MOVED)?
                        text_move_already_moved:text_move_already_elsewhere);
     readkey();
     return;
    }
   }
  }
  {
   int sxm=*x,sym=*y;
   (*spod)->state|=ST_MARKED;
   for (;;) {
move_redraw:
    draw_screen(*x,*y,-1,text_move);
    for (;;) {
     switch (readkey()>>8) {
      case KEY_4_PAD:
      case KEY_LEFT:  if (*x) {(*x)--; goto move_redraw;} break;
      case KEY_6_PAD:
      case KEY_RIGHT: if (*x<BOARD_W-1) {(*x)++; goto move_redraw;} break;
      case KEY_2_PAD:
      case KEY_UP:    if (*y) {(*y)--; goto move_redraw;} break;
      case KEY_8_PAD:
      case KEY_DOWN:  if (*y<BOARD_H-1) {(*y)++; goto move_redraw;} break;
      case KEY_ENTER:
      case KEY_SPACE:
       if (*x==sxm && *y==sym) {
        (*spod)->state&=~ST_MARKED;
        return;
       }
       {
        int xd=*x-sxm,yd=*y-sym;
        int p;
        for (p=0;p<N_PRONGS;p++) {
         if ((*spod)->prong[p]) {
          if (prong_dir[p].x==xd && prong_dir[p].y==yd) {
           if ((*spod)->state&ST_JUMPED) {
            (*spod)->state&=~ST_MARKED;
            *n=0;
            *max_n=calculate_n_pods(*x,*y);
            draw_screen(-1,-1,-1,text_move_already_jumped);
            readkey();
            return;
           }
           if (game->board[*y][*x].pod) {
            if (game->board[*y][*x].player!=game->go) {
             (*spod)->state&=~ST_MARKED;
             *n=0;
             *max_n=calculate_n_pods(*x,*y);
             draw_screen(-1,-1,-1,text_move_to_unfriendly);
             readkey();
             return;
            }
           }
           //This is a valid move! Proceed.
           (*spod)->state|=ST_MOVED;
           (*spod)->state&=~ST_MARKED;
           {
            BOARD_POD *temp=(*spod)->under;
            (*spod)->under=game->board[*y][*x].pod;
            game->board[*y][*x].pod=*spod;
            game->board[*y][*x].player=game->go;
            *spod=temp;
           }
           *sx=sxm;
           *sy=sym;
           *n=0;
           *max_n=calculate_n_pods(*x,*y);
           return;
          }
         }
        }
        for (p=0;p<N_PRONGS;p++) {
         if ((*spod)->prong[p]) {
          if (prong_dir[p].x<<1==xd && prong_dir[p].y<<1==yd) {
           int xm=sxm+(xd>>1),ym=sym+(yd>>1);//middle square
           {
            BOARD_POD *mpod=game->board[ym][xm].pod;
            if (mpod==0) {
             (*spod)->state&=~ST_MARKED;
             *n=0;
             *max_n=calculate_n_pods(*x,*y);
             draw_screen(-1,-1,-1,text_move_invalid);
             readkey();
             return;
            }
            while (mpod->under) mpod=mpod->under;//get the bottom pod
            if (mpod->state&(ST_MOVED|ST_JUMPED)) {
             (*spod)->state&=~ST_MARKED;
             *n=0;
             *max_n=calculate_n_pods(*x,*y);
             draw_screen(-1,-1,-1,text_jump_moving_pod);
             readkey();
             return;
            }
           }
           if (game->board[*y][*x].pod) {
            //This square is occupied - unless the occupying pod has jumped
            //Note: if any of the pods has jumped here, all will have done.
            if ((game->board[*y][*x].pod->state&ST_JUMPED)==0) {
             (*spod)->state&=~ST_MARKED;
             *n=0;
             *max_n=calculate_n_pods(*x,*y);
             draw_screen(-1,-1,-1,text_jump_occupied);
             readkey();
             return;
            }
           }
           {
            XY_LINK *xy=(*spod)->jumped;
            while (xy) {
             if (xy->x==xm && xy->y==ym) {
              (*spod)->state&=~ST_MARKED;
              *n=0;
              *max_n=0;//we know there are no pods on this square.
              draw_screen(-1,-1,-1,text_jump_square_twice);
              readkey();
              return;
             }
             xy=xy->prev;
            }
           }
           //This is a valid move! Proceed.
           (*spod)->state|=ST_JUMPED;
           (*spod)->state&=~ST_MARKED;
           {
            XY_LINK *prev=(*spod)->jumped;
            (*spod)->jumped=malloc(sizeof(XY_LINK));
            (*spod)->jumped->prev=prev;
            (*spod)->jumped->x=xm;
            (*spod)->jumped->y=ym;
           }
           {
            BOARD_POD *temp=(*spod)->under;
            (*spod)->under=game->board[*y][*x].pod;
            game->board[*y][*x].pod=*spod;
            game->board[*y][*x].player=game->go;
            *spod=temp;
           }
           if (*sx<0) {//Set as starting square only if the first move.
            *sx=sxm;
            *sy=sym;
           }
           *n=0;
           *max_n=calculate_n_pods(*x,*y);
           return;
          }
         }
        }
       }
       (*spod)->state&=~ST_MARKED;
       *n=0;
       *max_n=calculate_n_pods(*x,*y);
       draw_screen(-1,-1,-1,text_move_invalid);
       readkey();
       return;
      case KEY_ESC:
       (*spod)->state&=~ST_MARKED;
       *n=0;
       *max_n=calculate_n_pods(*x,*y);
       return;
     }
    }
   }
  }
 }
}

unsigned char finalise_move(int *x,int *y,int *n,int *max_n) {
 unsigned char jumped=0;
 {
  int ys;
  for (ys=0;ys<BOARD_H;ys++) {
   int xs;
   for (xs=0;xs<BOARD_W;xs++) {
    BOARD_POD *pod=game->board[ys][xs].pod;
    while (pod) {//For each pod...
     pod->state&=ST_MARKED;//Keep ST_MARKED; set others to 0
     if (pod->jumped) {
      jumped=1;//This pod must have jumped.
      do {//Scan squares it has jumped.
       BOARD_POD *podm=game->board[pod->jumped->y][pod->jumped->x].pod;
       while (podm) {//Scan pods on this square
        podm->state|=ST_MARKED;//Mark them
        podm=podm->under;
       }
       {
        XY_LINK *xy=pod->jumped;
        pod->jumped=pod->jumped->prev;
        free(xy);//Next jumped square, freeing memory on the way
       }
      } while (pod->jumped);
     }
     pod=pod->under;
    }
   }
  }
 }
 if (jumped==0) return 1;
 //Process jumps
 for (;;) {
capture_redraw:
  draw_screen(*x,*y,*n,text_capture);
  for (;;) {
   switch (readkey()>>8) {
    case KEY_4_PAD:
    case KEY_LEFT:  if (*x) {(*x)--; goto changed_xy;} break;
    case KEY_6_PAD:
    case KEY_RIGHT: if (*x<BOARD_W-1) {(*x)++; goto changed_xy;} break;
    case KEY_2_PAD:
    case KEY_UP:    if (*y) {(*y)--; goto changed_xy;} break;
    case KEY_8_PAD:
    case KEY_DOWN:  if (*y<BOARD_H-1) {(*y)++; goto changed_xy;} break;
    case KEY_9_PAD:
    case KEY_PGUP:  if (*n) {(*n)--; goto capture_redraw;} break;
    case KEY_3_PAD:
    case KEY_PGDN:  if (*n<*max_n-1) {(*n)++; goto capture_redraw;} break;
    case KEY_DEL:
     if (game->board[*y][*x].pod) {
      BOARD_POD **pod=get_pod(game,*x,*y,*n);
      if ((*pod)->state) {//No flags besides ST_MARKED will be set.
       BOARD_POD *captured=*pod;
       {
        int p;
        for (p=0;p<N_PRONGS;p++)
         game->player[game->go].prongs+=captured->prong[p];
       }
       game->player[game->board[*y][*x].player].captured++;
       (*pod)=(*pod)->under;
       free(captured);
       (*max_n)--;
       if (*n) (*n)--;
       goto capture_redraw;
      }
      draw_screen(-1,-1,-1,text_capture_not_jumped);
      readkey();
      goto capture_redraw;
     }
     draw_screen(-1,-1,-1,text_capture_no_pod);
     readkey();
     goto capture_redraw;
    case KEY_ENTER:
    case KEY_SPACE:
     {
      int ys;
      for (ys=0;ys<BOARD_H;ys++) {
       int xs;
       for (xs=0;xs<BOARD_W;xs++) {
        BOARD_POD *pod=game->board[ys][xs].pod;
        while (pod) {
         pod->state=0;
         pod=pod->under;
        }
       }
      }
     }
     return 1;
    case KEY_ESC:
     return 0;
   }
  }
changed_xy:
  *n=0;
  *max_n=calculate_n_pods(*x,*y);
 }
}

//This function does not duplicate pod states and jumped lists. The function
//is only called when pod states are 0 and jumped lists are empty anyway.
GAME *duplicate_game(GAME *g1) {
 GAME *g2=malloc(sizeof(GAME));
 g2->board=malloc(sizeof(BOARD_CELL *)*BOARD_H);
 {
  int y;
  for (y=0;y<BOARD_H;y++) {
   g2->board[y]=malloc(sizeof(BOARD_CELL)*BOARD_W);
   {
    int x;
    for (x=0;x<BOARD_W;x++) {
     g2->board[y][x].player=g1->board[y][x].player;
     {
      BOARD_POD *spod=g1->board[y][x].pod;
      BOARD_POD **dpod=&g2->board[y][x].pod;
      while (spod) {
       *dpod=malloc(sizeof(BOARD_POD));
       (*dpod)->state=0;
       (*dpod)->jumped=0;
       {
        int p;
        for (p=0;p<N_PRONGS;p++) (*dpod)->prong[p]=spod->prong[p];
       }
       spod=spod->under;
       dpod=&(*dpod)->under;
      }
      *dpod=0;
     }
    }
   }
  }
 }
 memcpy(g2->player,g1->player,sizeof(PLAYER)*MAX_PLAYERS);
 g2->go=g1->go;
 return g2;
}

unsigned char do_human_turn(void) {
 int sx,sy;
 unsigned char moved;

//if 'moved' is set, then a prong has been added or moved, or a pod has been
//added or liberated. This means that the player can only confirm the move.

//if a player has moved a pod, 'moved' will not be set. The player can move
//a pod again under certain circumstances.

//if a pod has moved, sx and sy are the source square. If not, sx == -1.

 int x=4,y=6,n,max_n;
 undo_game=duplicate_game(game);
restart_turn:
 sx=-1;
 moved=0;
reset_n:
 n=0;
 max_n=calculate_n_pods(x,y);
turn_redraw:
 draw_screen(x,y,n,moved?text_confirm:(sx>=0?text_move_again:text_turn));
 for (;;) {
  switch (readkey()>>8) {
   case KEY_4_PAD:
   case KEY_LEFT:  if (x) {x--; goto reset_n;} break;
   case KEY_6_PAD:
   case KEY_RIGHT: if (x<BOARD_W-1) {x++; goto reset_n;} break;
   case KEY_2_PAD:
   case KEY_UP:    if (y) {y--; goto reset_n;} break;
   case KEY_8_PAD:
   case KEY_DOWN:  if (y<BOARD_H-1) {y++; goto reset_n;} break;
   case KEY_9_PAD:
   case KEY_PGUP:  if (n) {n--; goto turn_redraw;} break;
   case KEY_3_PAD:
   case KEY_PGDN:  if (n<max_n-1) {n++; goto turn_redraw;} break;
   case KEY_M:     if (moved) break;
                   move_pod(&x,&y,&n,&max_n,&sx,&sy);
                   goto turn_redraw;
   case KEY_P:     if (moved || sx>=0) break;
                   moved=add_prong(x,y,n,0);
                   goto turn_redraw;
   case KEY_R:     if (moved || sx>=0) break;
                   moved=reposition_prong(x,y,n);
                   goto turn_redraw;
   case KEY_A:     if (moved || sx>=0) break;
                   moved=add_pod(x,y);
                   if (moved) goto reset_n;
                   goto turn_redraw;
   case KEY_ENTER:
   case KEY_SPACE: if (moved==0) {//did not move any prongs or anything
                    if (sx<0) break;//did not move a pod
                    //moved a pod - finalise
                    if (finalise_move(&x,&y,&n,&max_n)==0) {
                     destroy_game(game);
                     game=duplicate_game(undo_game);
                     goto restart_turn;
                    }
                   }//did something - we can return
                   destroy_game(undo_game);
                   return 0;
   case KEY_ESC:   if (moved || sx>=0) {
                    destroy_game(game);
                    game=duplicate_game(undo_game);
                    goto restart_turn;
                   }
                   draw_screen(-1,-1,-1,text_confirm_abort);
                   for (;;) {
                    switch (readkey()>>8) {
                     case KEY_Y:
                      destroy_game(undo_game);
                      return 1;
                     case KEY_N:
                      goto turn_redraw;
                    }
                   }
  }
 }
}

void switch_players(GAME *game) {
 game->go=!game->go;
 {
  int y;
  for (y=0;y<BOARD_H;y++) {
   int x;
   for (x=0;x<BOARD_W;x++) {
    BOARD_POD *pod=game->board[y][x].pod;
    while (pod) {
     int p,q;
     for (p=0,q=N_PRONGS>>1;p<N_PRONGS>>1;p++,q++) {
      int tmp=pod->prong[p];
      pod->prong[p]=pod->prong[q];
      pod->prong[q]=tmp;
     }
     pod=pod->under;
    }
   }
  }
 }
#if (BOARD_W != 9 || BOARD_H != 9)
#error Rotation code must be reprogrammed for new board size
#else
 {
  int y1,y2;
  for (y1=0,y2=8;y1<4;y1++,y2--) {
   int x1,x2;
   for (x1=0,x2=8;x1<9;x1++,x2--) {
    BOARD_CELL tmp=game->board[y1][x1];
    game->board[y1][x1]=game->board[y2][x2];
    game->board[y2][x2]=tmp;
   }
  }
 }
 {
  int x1,x2;
  for (x1=0,x2=8;x1<4;x1++,x2--) {
   BOARD_CELL tmp=game->board[4][x1];
   game->board[4][x1]=game->board[4][x2];
   game->board[4][x2]=tmp;
  }
 }
#endif
}

unsigned char choose_players(void) {
 draw_board(-1,-1);
 acquire_screen();
 blit(scrbuf,screen,BOARD_X,0,BOARD_X,0,TILE_W*BOARD_W,GFX_H);
 release_screen();

#ifndef NO_AI
restart_choice:
#endif
 rectfill(scrbuf,0,0,BOARD_X-1,GFX_H-1,0);
 textout(scrbuf,dat[FONT_LARGE].dat,"Welcome to OCTI!",0,0,YELLOW);
 textout(scrbuf,dat[FONT_SMALL].dat,"Game designed by Don Green",0,26,RED);
 textout(scrbuf,dat[FONT_SMALL].dat,"Programmed by Ben Davis",0,39,RED);
 textout(scrbuf,dat[FONT_SMALL].dat,"OCTI Website: www.octi.net",0,52,RED);

#ifdef NO_AI
 textout(scrbuf,dat[FONT_SMALL].dat,"Press Enter or Space to play.",0,78,CYAN);
 textout(scrbuf,dat[FONT_SMALL].dat,"Press Esc to exit.",0,91,CYAN);
#else
 textout(scrbuf,dat[FONT_LARGE].dat,"Choose Players",0,78,CYAN);
 textprintf(scrbuf,dat[FONT_SMALL].dat,0,104,YELLOW,"White is %s (press W to change).",player_computer[0]?"electronic":"human");
 textprintf(scrbuf,dat[FONT_SMALL].dat,0,117,YELLOW,"Black is %s (press B to change).",player_computer[1]?"electronic":"human");
 textout(scrbuf,dat[FONT_SMALL].dat,"Press Enter or Space to play.",0,143,CYAN);
 textout(scrbuf,dat[FONT_SMALL].dat,"Press Esc to exit.",0,156,CYAN);
#endif

 acquire_screen();
 blit(scrbuf,screen,0,0,0,0,BOARD_X,GFX_H);
 release_screen();

 for (;;) {
  switch (readkey()>>8) {
#ifndef NO_AI
   case KEY_W: player_computer[0]=!player_computer[0]; goto restart_choice;
   case KEY_B: player_computer[1]=!player_computer[1]; goto restart_choice;
#endif
   case KEY_ENTER:
   case KEY_SPACE: return 1;
   case KEY_ESC: return 0;
  }
 }

}

//This is called just after a player has moved, before rotating the board.
//If the top OCTI squares are occupied by the player who has just moved,
//that player has won so we return 1. Otherwise we return 0.
unsigned char player_has_won(void) {
 //if current player occupies all enemy OCTI squares, he/she/it has won.
 if (game->board[2][2].pod && game->board[2][2].player==game->go &&
     game->board[2][4].pod && game->board[2][4].player==game->go &&
     game->board[2][6].pod && game->board[2][6].player==game->go) return 1;
 //if other player has reserve pods, the game can continue.
 if (game->player[!game->go].pods) return 0;
 //if not, check whether player has pieces on the board.
 {
  int y;
  for (y=0;y<BOARD_H;y++) {
   int x;
   for (x=0;x<BOARD_W;x++) {
    //if player has pieces on the board, the game can continue.
    if (game->board[y][x].pod && game->board[y][x].player!=game->go) return 0;
   }
  }
 }
 //player has no pieces on the board, so the current player wins.
 return 1;
}

int main(void) {
 allegro_init();
 install_keyboard();
 set_color_depth(8);
 dat=load_datafile(DATAFILENAME);
 if (dat==0) {
  allegro_message("Unable to load octi.dat.\n\n");
  return 1;
 }
 if (set_gfx_mode(GFX_AUTODETECT,GFX_W,GFX_H,0,0)) {
  allegro_message("Unable to set graphics mode.\n%s\n\n",allegro_error);
  return 1;
 }

 set_palette(dat[OCTI_PAL].dat);
 scrbuf=create_bitmap(GFX_W,GFX_H);
 if (scrbuf==0) {
  allegro_exit();
  allegro_message("Out of memory.\n\n");
  return 1;
 }

#ifdef ALLEGRO_WINDOWS
 set_display_switch_callback(SWITCH_IN,update_screen);
#endif

 while (choose_players()) {

  initialise_game();

  for (;;) {
   if (
#ifndef NO_AI
       player_computer[game->go]?do_computer_turn():
#endif
                                 do_human_turn()) goto abort_game;
   if (player_has_won()) break;
   switch_players(game);
  }

  rectfill(scrbuf,0,0,BOARD_X-1,GFX_H-1,0);
  textprintf(scrbuf,dat[FONT_LARGE].dat,0,0,RED,
                               "%s is the winner!",game->go?"Black":"White");
  textout(scrbuf,dat[FONT_SMALL].dat,"Press any key to finish.",0,39,CYAN);

  draw_board(-1,-1);
  update_screen();
  readkey();

abort_game:

  destroy_game(game);
  game=0;
 }

 destroy_bitmap(scrbuf);
 unload_datafile(dat);
 return 0;
} END_OF_MAIN();
