/*********************************************************************/
/* Four in a Row    V1.0   8/14/87                                   */
/* Copyright (C) 1987 by Pery Pearson                                */
/*                                                                   */
/* Note the 'readme' file.                                           */
/* Feel free to modify the program if you like;  the computer player */
/* ( my_move() and related routines ) really could use some type of  */
/* strategy routine.                                                 */
/*********************************************************************/


#include <intuition/intuition.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include <graphics/gfxbase.h>

#include "struct.c"    /* structures and definitions */


int grid [MAXX] [MAXY];
int on_pole[8];
int polepick, turn, bead_count;
int player2, finished, newgame;
int first_turn, illegal;
int inrow[4], empty[4], maybe[4], possibles[4];

/**************************************************************************
 some variables used in making the computer's move(I hope they help):

* inrow[x]    : the number of beads in a row including the proposed bead;
                x is a direction (vertically, horizontally, & both
                diagonals).  Which direction depends upon how inrow was
                assigned.  i.e. inrow[0]=in_row(..., 1, 1,...) is in the
                positive slope diagonal.
* maybe[x]    : Unused, but it is the number of WHO's beads in the direction
                being used which are separated from WHO's beads that are
                in-a-row by spaces only, no intervening enemy beads.  x is
                a direction here too.  (is that clear ?!? - I thought this
                might be useful for a stategy.)
* empty[x]    : the number of spaces in the direction in question on both
                sides of WHO's in-a-row beads which are empty or contain
                other of WHO's beads.  This is bounded by the grid
                boundaries and enemy beads.  x is a direction.
* possibles[x]: the number of possible 4-in-a-rows with (x+1) beads in a row
                already (including the hypothetical move in question).
*************************************************************************/

/* make C happy by declaring functions */

void open_libraries(), done();
void handle_message(), setup(), put_text();
void handle_menu(), whose_move(), get_possibles();
void up_gadgets(), set_ball(), main();
void clear_grid(), draw_grid(), draw_base();
void handle_proj(), handle_game(), initialize();
void illegal_move(), my_move(), player_move();
void reset_menu_item(), clear_square();


void main()
{
   initialize();

   FOREVER {
      if (newgame)   setup();
      whose_move();
      if (!finished && turn == ME && player2 == ME)   {
         my_move();
         whose_move();
      }
      if ((message = (struct IntuiMessage *)GetMsg(window1->UserPort))==NULL)
      {
        Wait(1<<window1->UserPort->mp_SigBit);
        continue;
      }
      handle_message(message);
   }
}


void whose_move()
{
   int xoff, yoff;

   xoff = XOFF-2*8-2;
   yoff = YOFF+(MAXY+3)*ROWS+3;

   if (!finished) {
      if (player2 == ME) {
         if (turn == ME) {
            put_text(xoff-4, yoff, "      My move      ", 4);
         }
         else {
            put_text(xoff-4, yoff, "     Your move     ", 3);
         }
      }
      else {
         if (turn == YOU) {
            put_text(xoff, yoff, "   White's move   ", 3);
         }
         else {
            put_text(xoff, yoff, "   Brown's move   ", 4);
         }
      }
   }
   return;
}


void put_text(x, y, text, c)
int x, y, c;
char *text;
{
   SetAPen(window1->RPort, c);
   TEXT(window1, x, y, text);
   return;
}


void reset_menu_item(menuitem, newtext)
struct MenuItem *menuitem;
APTR newtext;
{
   ClearMenuStrip(window1);
   menuitem-> ItemFill = newtext;
   SetMenuStrip(window1, &menu1);

   return;
}


void handle_message(message)
struct IntuiMessage *message;
{
   ULONG class;
   USHORT code;

   class = message->Class;
   code = message->Code;

   if (illegal) {
      illegal = 0;
      put_text(XOFF-2*8-2, YOFF+(MAXY+4)*ROWS+3, "                  ", 0);
   }

   switch (class) {
      case GADGETUP     :  up_gadgets();
                           break;
      case MOUSEBUTTONS :  break;
      case CLOSEWINDOW  :  ReplyMsg(message);
                           done(NULL);
                           break;
      case MENUPICK     :
                           handle_menu(code, message);
                           break;
   }
   ReplyMsg(message);
   return;
}


void handle_menu(code, message)
USHORT code;
struct IntuiMessage *message;
{
   while (code != MENUNULL) {
      switch (MENUNUM(code)) {
         case PROJECT   :  handle_proj(code, message);
                           break;
         case GAME      :  handle_game(code);
      }
      code = ItemAddress(&menu1, code)->NextSelect;
   }
   return;
}


void handle_proj(code, message)
USHORT code;
struct IntuiMessage *message;
{
   switch (ITEMNUM(code)) {
      case ABOUT  :
                     (void)AutoRequest(window1 , &about1, &positive,
                                       &negative, 0, 0, 38*8, 20*ROWS);
                     break;
      case QUIT   :  ReplyMsg(message);
                     done(NULL);
   }
   return;
}


void handle_game(code)
USHORT code;
{
   int menu_num;

   switch (ITEMNUM(code)) {
      case NEWGAME      :  newgame = 1;
                           put_text(XOFF-7, YOFF, "              ", 0);
                           break;
      case PLAYER2      :  menu_num = SHIFTMENU(GAME) +
                                      SHIFTITEM(FIRSTPLAYER);
                           if (player2 == ME) {
                              player2 = YOU;
                              reset_menu_item(&m2_item2,(APTR)&m2_text2b);
                              OffMenu(window1, menu_num);
                           }
                           else {
                              player2 = ME;
                              reset_menu_item(&m2_item2, (APTR)&m2_text2a);
                              OnMenu(window1, menu_num);
                           }
                           break;
      case FIRSTPLAYER  :
                           if (first_turn == ME) {
                              first_turn = YOU;
                              reset_menu_item(&m2_item3, (APTR)&m2_text3b);
                           }
                           else {
                              first_turn = ME;
                              reset_menu_item(&m2_item3, (APTR)&m2_text3a);
                           }
                           break;
      case INSTRUCTIONS :
                           (void)AutoRequest(window1 , &instr1, &positive,
                                       &negative, 0, 0, 33*8, 10*ROWS);
   }
   return;
}


void up_gadgets()

{
   struct Gadget *igad;
   int   gadget_id;

   igad = (struct Gadget *)message -> IAddress;
   gadget_id = igad -> GadgetID;
   polepick = -1;
   switch(gadget_id) {
      case GAD1   :  polepick = 0;
                     break;
      case GAD2   :  polepick = 1;
                     break;
      case GAD3   :  polepick = 2;
                     break;
      case GAD4   :  polepick = 3;
                     break;
      case GAD5   :  polepick = 4;
                     break;
      case GAD6   :  polepick = 5;
                     break;
      case GAD7   :  polepick = 6;
                     break;
      case GAD8   :  polepick = 7;
   }

   if ( !finished && polepick >= 0 )   player_move();

   return;
}


int count_em(who, x, y, empty, maybe, pole, onpole)
int who, x, y, *empty, *maybe, pole, onpole;
{
   int count = 0,
       xn, yn;

   xn = pole;
   yn = on_pole[pole] - onpole;
   while ( grid[yn += y][xn += x] == who && yn < MAXY && xn < MAXX
           && yn >= 0 && xn >= 0) {
      count++;
   }
   while ( (grid[yn][xn] == NOONE || grid[yn][xn] == who)
           && yn < MAXY && xn < MAXX && yn >= 0 && xn >= 0) {
      (*empty)++;
      if (grid[yn][xn] == who)
         (*maybe)++;
      yn += y;
      xn += x;
   }

   return(count);
}


int in_row(who, x, y, empty, maybe, pole, onpole)
int who, x, y, *empty, *maybe, pole, onpole;
{
   *empty = 0;
   *maybe = 0;
   return( count_em(who, x, y, empty, maybe, pole, onpole)
           + count_em(who, -x, -y, empty, maybe, pole, onpole) + 1 );
}


int check_win()
{
   int tempret, win, who,
       dum;   /* dummy var */

   dum = 0;   /* just to stop C's warning */
   tempret = 0;
   who = turn;
   if (in_row(who, 1, 1, &dum, &dum, polepick, 1) >= 4)  win = 1;
   else if (in_row(who, -1, 1, &dum, &dum, polepick, 1) >= 4)  win = 1;
       else if (in_row(who, 1, 0, &dum, &dum, polepick, 1) >= 4)  win = 1;
           else if(in_row(who, 0, 1, &dum, &dum, polepick, 1) >= 4)  win = 1;
               else  win = 0;

   if (win) {
      tempret = 1;
      if (player2 == ME) {
         if (turn == ME) {
            put_text(XOFF+4*8-3, YOFF, "I win", 4);
         }
         else {
            put_text(XOFF+3*8-3, YOFF, "You win", 3);
         }
      }
      else {
         if (turn == YOU) {
            put_text(XOFF+1*8+1, YOFF, "White wins", 3);
         }
         else {
            put_text(XOFF+1*8+1, YOFF, "Brown wins", 4);
         }
      }
   }
   else {
      if (bead_count == MAXX*MAXY) {
         tempret = 1;
         put_text(XOFF-7, YOFF, "It's a tie !!!", 14);
      }
   }

   if (tempret) {
      put_text(XOFF-2*8-6, YOFF+(MAXY+3)*ROWS+3, "                   ", 0);
   }

   return(tempret);
}


void illegal_move()
{
   put_text(XOFF-2*8-2, YOFF+(MAXY+4)*ROWS+3, "That column's full", 15);
   DisplayBeep(screen1);
   illegal = 1;

   return;
}


void get_possibles(who, pole, onpole)
int who, pole, onpole;
{
   int dir;

   inrow[0] = in_row(who, 1, 1, &empty[0], &maybe[0], pole, onpole);
   inrow[1] = in_row(who, -1, 1, &empty[1], &maybe[1], pole, onpole);
   inrow[2] = in_row(who, 0, 1, &empty[2], &maybe[2], pole, onpole);
   inrow[3] = in_row(who, 1, 0, &empty[3], &maybe[3], pole, onpole);
   for (dir = 0; dir < 4; dir++) {
      possibles[dir] = 0;
   }
   for (dir = 0; dir < 4; dir++) {
      if (inrow[dir] >3) {
         possibles[3] = 5;
      }
      else {
         if (empty[dir] + inrow[dir] >= 4) {
            possibles[inrow[dir]-1]++;
         }
      }
   }
   return;
}


int best_move(who, pole, otherhigh, bad)
int who, pole, otherhigh, *bad;
{
   int inrow, temp_high, mult;

   get_possibles(who, pole, 0);
   inrow = 3;
   temp_high = possibles[inrow];
   while (temp_high < 1 && inrow > 0) {
      inrow--;
      temp_high = possibles[inrow];
   }
   if (inrow == 0) mult = 1;
   else if (inrow == 1) mult = 4;
   else if (inrow == 2) mult = 13;
   else mult = 40;
   temp_high *= mult;

/* try not to set up the player for a win */

   if (who == YOU && temp_high < 100
       && otherhigh < 100 && on_pole[pole] < MAXY-1) {
      get_possibles(YOU, pole, -1);
      if (possibles[3] > 4) {
         *bad = 1;
         temp_high = 0;
      }
   }

   return(temp_high);
}


int random_move()
{
   int pole;

   while (on_pole[(pole = rand()&7)] >= MAXY)  ;
   return(pole);
}


void my_move()
{
   int high_move, polenum, move, block, badmoves, bad, winmove;

   high_move = 0;
   badmoves = 1;
   winmove = 0;
   for (polenum = 0; polenum < MAXX; polenum++) {
      if (on_pole[polenum] < MAXY) {
         bad = 0;
         if ((move = best_move(ME, polenum, 0, &bad)) >100) {
            if (!winmove || (winmove && rand() < rand() )) {
               winmove = 1;
               high_move = move;
               badmoves = 0;
               polepick = polenum;
            }
         }
         else if (!winmove) {
            block = best_move(YOU, polenum, move, &bad);
            if (block > move || bad)
               move = block;
            if (move > high_move || ( move == high_move && move > 0 &&
                rand() < rand() )) {
               high_move = move;
               badmoves = 0;
               polepick = polenum;
            }
         }
      }
   }
   if (badmoves) {
      polepick = random_move();
   }
   set_ball(ME);
   if (check_win()) finished = 1;
   else  turn = YOU;

   return;
}


void set_ball(who)
int who;
{
   struct Image *image_ptr;
   int i, j;

   j = polepick;
   i = on_pole[j];

   grid[i][j] = who;
   switch (who) {
            case YOU     : if (i)   image_ptr = &ball_1;
                           else     image_ptr = &base_ball_1;
                           break;
            case ME      : if (i)   image_ptr = &ball_2;
                           else     image_ptr = &base_ball_2;
         }
   DrawImage(window1->RPort, image_ptr,
             XOFF+j*COLS, YOFF+(MAXY-i-1)*(ROWS-1)+MAXY-1);
   on_pole[polepick] += 1;
   bead_count += 1;

   return;
}


void player_move()
{
   if (on_pole[polepick] >= MAXY)   illegal_move();
   else {
      if ( turn == YOU )
         set_ball(YOU);
      else
         set_ball(ME);     /* ME is player2 in two player mode */
      if (check_win() ) finished = 1;
      else  if ( (turn += 1) > 1) turn = 0;
   }
   return;
}


void open_libraries()
{
   if ((IntuitionBase = (struct IntuitionBase *)
      OpenLibrary("intuition.library",I_REV)) == NULL) done(2);
   if ((GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",G_REV))
      == NULL) done(3);
   oldview = GfxBase -> ActiView;
   return;
}


void done(s)
SHORT s;
{
   if (window1)   {
      ClearMenuStrip(window1);
      CloseWindow(window1);
   }
   if (screen1)   {
      CloseScreen(screen1);
      LoadView(oldview);
   }
   if (colormap != NULL) FreeColorMap(colormap);
   if (GfxBase) CloseLibrary(GfxBase);
   if (IntuitionBase) CloseLibrary(IntuitionBase);

   if (s) {
      printf("Error #%d",s);
   }

   exit(s);
}


void clear_grid()
{
   int i, j;
   for (i=0; i<MAXX; i++) {
      for (j=0; j<MAXY; j++) {
         grid [i][j] = NOONE;
      }
   }
   return;
}


void clear_square()
{
   DrawImage(window1->RPort, &clear_block, XOFF, YOFF);
   return;
}


void draw_grid()
{
   int i, j;
   struct Image *image_ptr;

   for (i=0 ; i<MAXY; i++) {
      for (j=0; j<MAXX; j++) {
         switch (grid [i][j]) {
            case NOONE   : image_ptr = &pole;
                           break;
            case YOU     : if (i)   image_ptr = &ball_1;
                           else     image_ptr = &base_ball_1;
                           break;
            case ME      : if (i)   image_ptr = &ball_2;
                           else     image_ptr = &base_ball_2;
         }
         DrawImage(window1->RPort, image_ptr,
                   XOFF+j*COLS, YOFF+(MAXY-i-1)*(ROWS-1)+MAXY-1);
      }
   }
   return;
}


void draw_base()
{
   DrawImage(window1->RPort, &base_top, XOFF-5, YOFF+MAXY*ROWS-5);
   DrawImage(window1->RPort, &base_bot, XOFF-15, YOFF+MAXY*ROWS+5);
   DrawImage(window1->RPort, &top_left, XOFF-15, YOFF+MAXY*ROWS-5);
   DrawImage(window1->RPort, &end_rect, XOFF-15, YOFF+MAXY*ROWS+5);
   DrawImage(window1->RPort, &bot_left, XOFF-15, YOFF+MAXY*ROWS+15);
   DrawImage(window1->RPort, &top_right,XOFF+MAXX*COLS+5, YOFF+MAXY*ROWS-5);

   DrawBorder(window1->RPort, &line1, 0*COLS, 0);
   DrawBorder(window1->RPort, &line1, 1*COLS, 0);
   DrawBorder(window1->RPort, &line1, 2*COLS, 0);
   DrawBorder(window1->RPort, &line1, 3*COLS, 0);
   DrawBorder(window1->RPort, &line1, 4*COLS, 0);
   DrawBorder(window1->RPort, &line1, 5*COLS, 0);
   DrawBorder(window1->RPort, &line1, 6*COLS, 0);
   DrawBorder(window1->RPort, &line1, 7*COLS, 0);

   RefreshGadgets(&Gadget1, window1, NULL);

   return;
}


void initialize()
{
   open_libraries();

   font = &Font80;

   init_images();
   init_menus();
   init_gadgets();
   init_display(80*W, 20*H, 160*W, 148*H, 4, "Four in a Row");

   player2 = 0;
   first_turn = YOU;
   newgame = 1;

   return;
}


void setup()
{
   int i;

   newgame = 0;
   clear_grid();
   clear_square();
   draw_base();
   draw_grid();
   turn = first_turn;
   bead_count = 0;
   finished = 0;
   for (i=0; i<MAXY; i++) {
      on_pole[i] = 0;
   }
   illegal = 0;

   return;
}
