!*************************************************************************** !** !** 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; ];