!***************************************************************************
!**
!** Reverzi: Yet Another Abuse of the Z-Machine
!**
!** Reversi for the Z-Machine by John Menichelli
!**
!***************************************************************************

!***************************************************************************
!**
!** Reverzi can be inserted into other games, possibly as an Easter egg.
!** To start the game, call PlayRev();
!**
!** I've given all of the identifiers in this code a "rev_" prefix, to
!** separate them from identifiers in the main game.
!**
!** The code uses 8 global variables and 192 bytes of array space.
!**
!** This source code is in the public domain.
!**
!***************************************************************************

!***************************************************************************
!**
!** The board in internally represented as follows:
!**
!**   0  1  2  3  4  5  6  7
!**   8  9 10 11 12 13 14 15
!**  16 17 18 19 20 21 22 23
!**  24 25 26 27 28 29 30 31
!**  32 33 34 35 36 37 38 39
!**  40 41 42 43 44 45 46 47
!**  48 49 50 51 52 53 54 55
!**  56 57 58 59 60 61 62 63
!**
!***************************************************************************

!***************************************************************************
!**
!** Arrays and Variables
!**
!***************************************************************************

Array rev_b --> 64;
Array rev_c --> 64;
Array rev_d --> 64;

Global rev_black;
Global rev_white;
Global rev_play;
Global rev_playside;
Global rev_compside;
Global rev_skipmove;
Global rev_mode;
Global rev_passes;

!***************************************************************************
!**
!** Routines
!**
!***************************************************************************

[ OtherSide side;
  if (side == 1) return 2; return 1;
];

[ PosAdjoins pos side retval x y;
  y = (pos/8);
  x = (pos-(y*8)-1);
  retval=0;

  if (x > 0) {
     if (rev_b->(pos-1) == side) retval = 1;
  }

  if (x < 7) {
     if (rev_b->(pos+1) == side) retval = 1;
  }

  if (y > 0) {
     if (rev_b->(pos-8) == side) retval = 1;
  }

  if (y < 7) {
     if (rev_b->(pos+8) == side) retval = 1;
  }

  if ((x > 0) && (y > 0)) {
     if (rev_b->(pos-9) == side) retval = 1;
  }

  if ((x < 7) && (y > 0)) {
     if (rev_b->(pos-7) == side) retval = 1;
  }

  if ((x > 0) && (y < 7)) {
     if (rev_b->(pos+7) == side) retval = 1;
  }

  if ((x < 7) && (y < 7)) {
     if (rev_b->(pos+9) == side) retval = 1;
  }

  return retval;
];

[ CountPieces loop;
  rev_black=0;
  rev_white=0;

  for(loop=0: loop<64: loop++)
  {
     if (rev_b->loop == 1) rev_black++;
     if (rev_b->loop == 2) rev_white++;
  }
];

[ DrawBoard i ;
  @set_cursor 3 1;
  for (i=0: i<64: i++)
  {
     if (i%8 == 0)
        print "    ";

     switch (rev_b->i)
     {
        0: print ". ";
        1: print "X ";
        2: print "O ";
     }

     if (i%8 == 7)
        print "^";
  }

   CountPieces();

   @set_cursor 3 22;
   print "Black:   ";
   @set_cursor 3 29;
   print rev_black;

   @set_cursor 5 22;
   print "White:   ";
   @set_cursor 5 29;
   print rev_white;

   @set_cursor 7 22;
   print "Player: ";
   if (rev_playside == 1)
      print "X";
   else
      print "O";
];

[ InitGame i ;
  for(i = 0: i < 64: i++)
     rev_b->i = 0;

  rev_b->27 = 1; ! Black
  rev_b->28 = 2; ! White
  rev_b->35 = 2; ! White
  rev_b->36 = 1; ! Black

  CountPieces();

  @split_window 13;
  @set_window 1;
  @set_cursor -1 0; ! Turn off cursor
  for (i = 1 : i <= 13 : ++i) {
     @set_cursor i 1;
     spaces (0->33)-1; }

  @set_cursor 1 6;
  print "R E V E R Z I";

  DrawBoard();

  @set_cursor 1 34;
  print "W";
  @set_cursor 2 33;
  print "A S";
  @set_cursor 3 34;
  print "Z";
  @set_cursor 5 33;
  print "[Spc] Enter move";
  @set_cursor 6 33;
  print "[C]hange sides";
  @set_cursor 7 33;
  print "[P]ass";
  @set_cursor 8 33;
  print "[M]ode = ";
  if (rev_mode == 0)
     print "Slow";
  else
     print "Fast";
  @set_cursor 9 33;
  print "[Q]uit";
  @set_cursor 10 33;
  print "[H]elp";
];

[ LineCaptured side pos offset stop capture cont
              count lpos loop other retval;

  retval = 0;
  other = OtherSide(side);
  cont = 1;
  count = 0;

  for (lpos=pos+offset: (lpos >= 0 && lpos <= 63 && cont == 1): lpos=lpos+offset)
  {
     if (rev_b->lpos == 0) cont = 0;

     if (rev_b->lpos == other) count++;

     if (rev_b->lpos == side && cont ~= 0)
     {
        cont = 0;
        if (capture == 1 && count > 0)
        {
           for (loop = pos: loop ~= lpos: loop = loop + offset)
              rev_b->loop = side;
        }
        retval = count;
     }
     if (lpos == stop) cont = 0;
  }
  return retval;
];

[ PiecesCaptured side pos capture x y captured minnw minne minsw minse;

  captured = 0;
  y = (pos/8);
  x = (pos - (y*8));

  if (x < y)
     minnw = x;
  else
     minnw = y;

  if (x < (7 - y))
     minsw = x;
  else
     minsw = (7 - y);

  if ((7 - x) < y)
     minne = (7 - x);
  else
     minne = y;

  if ((7 - x) < (7 - y))
     minse = (7 - x);
  else
     minse = (7 - y);

  if (x > 0)
  {
     captured = captured +
                linecaptured (side, pos, -1, pos-x, capture);
  }

  if (x < 7)
  {
     captured = captured +
                linecaptured (side, pos, 1, pos+7-x, capture);
  }

  if (y > 0)
  {
     captured = captured +
                linecaptured(side, pos, -8, pos-(y*8), capture);
  }

  if (y < 7)
  {
     captured = captured +
                linecaptured(side, pos, 8, pos+((7-y)*8), capture);
  }

  if ((x > 0) && (y > 0))
  {
     captured = captured +
                linecaptured(side, pos, -9, pos-(minnw*9), capture);
  }

  if ((x > 0) && (y < 7))
  {
     captured = captured +
                linecaptured(side, pos, 7, pos+(minsw*7), capture);
  }

  if ((x < 7) && (y > 0))
  {
     captured = captured +
                linecaptured(side, pos, -7, pos-(minne*7), capture);
  }

  if ((x < 7) && (y < 7))
  {
     captured = captured +
                linecaptured(side, pos, 9, pos+(minse*9), capture);
  }
  return captured;
];

[ IsValid side pos other retval adjoins;
  retval= 0;
  if (pos > -1 && pos < 64)
  {
     other = OtherSide(side);
     if(rev_b->pos == 0)
     {
        adjoins = PosAdjoins(pos, other);

        if (adjoins == 1)
           if (PiecesCaptured(side, pos, 0) > 0) retval = 1;
     }
  }
  return retval;
];

! The "guts" of the program - selects moves for the computer.
! Also checks to see if the player has any valid moves

[ GetMove side loop max move;
  max = -1;
  move = -1;

  ! If a corner is empty, avoid adjacent squares unless there is no other
  ! choice. Refer to diagram at the beginning of this file for details.

  for (loop = 0: loop < 64: loop++)
     rev_d->loop = 0;

  if (rev_b->0 == 0)
  {
     if (IsValid(side, 1)) rev_d->1 = 1;
     if (IsValid(side, 8)) rev_d->8 = 1;
     if (IsValid(side, 9)) rev_d->9 = 1;
  }

  if (rev_b->7 == 0)
  {
     if (IsValid(side, 6))  rev_d->6 = 1;
     if (IsValid(side, 14)) rev_d->14 = 1;
     if (IsValid(side, 15)) rev_d->15 = 1;
  }

  if (rev_b->56 == 0)
  {
     if (IsValid(side, 48)) rev_d->48 = 1;
     if (IsValid(side, 49)) rev_d->49 = 1;
     if (IsValid(side, 57)) rev_d->57 = 1;
  }

  if (rev_b->63 == 0)
  {
     if (IsValid(side, 54)) rev_d->54 = 1;
     if (IsValid(side, 55)) rev_d->55 = 1;
     if (IsValid(side, 62)) rev_d->62 = 1;
  }

  ! Corner moves have priority

  if(IsValid(side, 0)) move = 0;

  if(IsValid(side, 7)) move = 7;

  if(IsValid(side, 56)) move = 56;

  if(IsValid(side, 63)) move = 63;

  if (move > -1)
     return move;

  else
  {
     for (loop = 0: loop < 64: loop++)
     {
        if (IsValid(side, loop))
        {
           rev_c->loop = PiecesCaptured(side, loop, 0);
           if (rev_c->loop > max)
           {
              max = rev_c->loop;
              move = loop;
           }
           if (rev_c->loop == max && random(10) > 5)
           {
              max = rev_c->loop;
              move = loop;
           }
        }
        else
           rev_c->loop = 0;
     }

     ! Now we have two arrays: rev_c contains the number of pieces that would
     ! be captured if that move was played, while rev_d contains those moves
     ! that should be avoided.

     ! First, eliminate moves adjacent to the corners

     for (loop = 0: loop < 64: loop++)
        if (rev_d->loop == 1 && rev_c->loop > 0) rev_c->loop = 0;

     ! Next, check to see if any valid moves are left

     max = 0; ! Re-use max
     for (loop = 0: loop < 64: loop++)
        if (rev_c->loop > 0) max++;

     ! If a not-adajcent-to-the-corner move exists, make it

     if (max > 0)
     {
        max = 0;
        for (loop = 0: loop < 64: loop++)
        {
           if (rev_c->loop > max)
           {
              max = rev_c->loop;
              move = loop;
           }
           if (rev_c->loop == max && random(10) > 5)
           {
              max = rev_c->loop;
              move = loop;
           }
        }
     }
  }
  return move;
];

[ GetCompMove move ;
  move = GetMove(rev_compside);
  if (move > -1)
  {
     rev_b->move = rev_compside;
     PiecesCaptured(rev_compside, move, 1);
     CountPieces();
  }
  else
  {
     rev_passes++;
     CountPieces();
     if (rev_black + rev_white == 64) return;
     @set_cursor 12 1;
     spaces (0->33) - 1;
     @set_cursor 12 5;
     print "[No allowable moves - skipping turn]";
     Pause();
     @set_cursor 12 1;
     spaces (0->33) - 1;
  }
];

[ GetPlayerMove pos valid k cur_x cur_y a move;
  rev_passes = 0;
  move = GetMove(rev_playside);
  if (move == -1)
  {
     rev_passes++;
     @set_cursor 12 1;
     spaces (0->33) - 1;
     @set_cursor 12 5;
     print "[You have no allowable moves - skipping turn]";
     Pause();
     @set_cursor 12 1;
     spaces (0->33) - 1;
     rtrue;
  }

  rev_skipmove=0;
  valid=0;
  cur_y = 3;
  cur_x = 5;
  @set_cursor cur_y cur_x;
  print "?";
  pos = 0;

  while (valid == 0 && rev_skipmove == 0)
  {
     for (::)
     {
        @read_char 1 -> k;
        @set_cursor 12 1;
        spaces (0->33) - 1;

        ! Space (enter move)
        if (k == ' ')
           break;

        ! Change sides
        if (k == 'c' || k == 'C')
        {
           rev_compside = rev_playside;
           rev_playside = OtherSide(rev_playside);
           InitGame();
           if (rev_compside == 1) ! Computer = black
           {
              GetCompMove();
              DrawBoard();
           }
        }

        ! Help
        if (k == 'h' || k == 'H')
        {
           @set_window 0;

           print "Rules:^^";

           print "Black (X) moves first.^^

                  To make a legal move, your piece must be placed next to a
                  piece of the opposite color. The move is legal if
                  somewhere on the column/row/diagonal in the direction of
                  the opposite piece is one of your own pieces. All opposite
                  pieces in this direction are then captured by you and will
                  change to your color.^^

                  If your piece is placed next to several pieces of the
                  opposite color then you capture those pieces also.^^

                  If you have no possible moves you will be forced to pass
                  your turn. To manually pass simply press ~P~. Two
                  consecutive passes (one by you and one by the computer
                  will end the game.^^

                  If the computer cannot make a move it will display a
                  message. You continue to make your moves as normal.^^

                  The game is over when all fields are occupied or when no
                  side can make a legal move. The winner of the game is the
                  one with the most pieces when the game is over.^^";


           print "Controls: Use the arrow keys to move the cursor (the ~?~)
                  around the board. When the ~?~ is where you want to place
                  your piece, press the space bar. You can also use the ~W~,
                  ~A~, ~S~ and ~Z~ keys to move the cursor.^^

                  Most of the menu choices should be self-explanatory. ~C~
                  allows you to change sides. Press ~P~ to pass, ~Q~ to
                  quit and ~H~ to diplay this information. ~M~ changes the
                  game's mode between fast and slow. In ~slow~ mode, the
                  game will pause after you make a move, so that you can
                  see the results of your move. In ~fast~ mode, the computer
                  will make it's move immediately after you make yours, then
                  display the results.^^";

           @set_window 1;
        }

        ! Pass
        if (k == 'p' || k == 'P')
        {
           rev_passes++;
           rev_skipmove = 1;
           break;
        }

        ! Mode change
        if (k == 'm' || k == 'M')
        {
           if (rev_mode == 0)
              rev_mode = 1;
           else
              rev_mode = 0;

           @set_cursor 8 33;
           print "[M]ode = ";
           if (rev_mode == 0)
              print "Slow";
           else
              print "Fast";
        }

        ! Quit
        if (k == 'q' || k == 'Q')
        {
           @set_cursor 12 1;
           spaces (0->33) - 1;
           @set_cursor 12 5;
           print "Play again? (y/n) ";

           @read_char 1 -> a;
           if (a == 'n' || a == 'N') {
              rev_play = 0;
              break; }
           else
           {
              @set_cursor 12 1;
              spaces (0->33) - 1;
              rev_playside = 1;
              rev_compside = 2;
              InitGame();
           }
        }

        ! Left
        if (k == 'a' || k == 'A' || k == 131)
        {
           if (pos%8 == 0)
              pos = pos + 7;
           else
              pos--;
           cur_x = cur_x - 2;
           if (cur_x < 5) cur_x = 19;
           DrawBoard();
        }

        ! Right
        if (k == 's' || k == 'S' || k == 132)
        {
           if ((pos+1)%8 == 0)
              pos = pos - 7;
           else
              pos++;
           cur_x = cur_x + 2;
           if (cur_x > 19) cur_x = 5;
           DrawBoard();
        }

        ! Up
        if (k == 'w' || k == 'W' || k == 129)
        {
           if (pos <= 7)
              pos = pos + 56;
           else
              pos = pos - 8;
           cur_y--;
           if (cur_y < 3) cur_y = 10;
           DrawBoard();
        }

        ! Down
        if (k == 'z' || k == 'Z' || k == 0 || k == 130)
        {
           if (pos >= 56)
              pos = pos - 56;
           else
              pos = pos + 8;
           cur_y++;
           if (cur_y > 10) cur_y = 3;
           DrawBoard();
        }
        @set_cursor cur_y cur_x;
        print "?";
     }
     if (rev_play == 0) return;
     if (rev_skipmove == 1) return;
     if (IsValid(rev_playside, pos) == 1)
        valid = 1;
     else
     {
        @set_cursor 12 1;
        spaces (0->33) - 1;
        @set_cursor 12 5;
        print "[Invalid move]";
     }
  }

  if (rev_skipmove == 0) {
     rev_b->pos = rev_playside;
     PiecesCaptured(rev_playside, pos, 1);
     DrawBoard();
     CountPieces();
  }
];

[ PlayRev a;
  rev_mode = 1; ! Fast (default)

Start;
  rev_playside = 1;
  rev_compside = 2;
  rev_play = 1;
  while (rev_play)
  {
     InitGame();
     while ((rev_black+rev_white < 64) &&
            (rev_black > 0)            &&
            (rev_white > 0)            &&
            (rev_passes < 2))
     {
        GetPlayerMove();

        if (rev_play == 0) break;
        if (rev_mode == 0)
        {
           @set_cursor 12 1;
           spaces (0->33) - 1;
           @set_cursor 12 5;
           print "[Press any key to continue]";
           Pause();
           @set_cursor 12 1;
           spaces (0->33) - 1;
        }
        GetCompMove();
        DrawBoard();
     }

     if (rev_play == 0) break;

     @set_cursor 12 1;
     spaces (0->33) - 1;
     @set_cursor 12 5;

     if (rev_passes == 2) {
        print "Two passes - game ends.";
        jump PlayAgain; }

     if (rev_black > rev_white)
     {
        if (rev_playside == 1)
           print "You win.";
        else
           print "I win.";
     }

     if (rev_white > rev_black)
     {
        if (rev_playside == 1)
           print "I win.";
        else
           print "You win.";
     }

     if (rev_white == rev_black)
        print "Drawn game.";

PlayAgain;
     print " Play again? (y/n) ";

     @read_char 1 -> a;
     if (a == 'y' || a == 'Y') {
        @set_cursor 12 1;
        spaces (0->33) - 1;
        jump Start; }
     else
        break;
  }
  @set_cursor -2 0; ! Turn cursor on again
  @set_cursor 1 1;
  @split_window 0;
  @erase_window $ffff;
];

[ Main ;

  PlayRev();

  print "Reverzi, Version 1.0^
         Reverzi is a port of Reversi to the Z-Machine^
         Ported by John Menichelli, December 1999^^";

  print "The original source code was written by Dave Derrick for handheld
         PCs. I downloaded it from the OrbWorks web site
         (www.orbworks.com/ceres.html) and converted it to Inform. The
         original source was written in PocketC and was very easy to convert
         to Inform; the most difficult part of the conversion was creating
         the UI. ^^";

  print "Special thanks to Andrew Plotkin - some of the code for this
         game - along with the idea of abusing the Z-Machine - was borrowed
         from his game ~Freefall.~^^";
];

[ Pause dummy;
       @read_char 1 dummy;
       return dummy;
];