Add Reversi for the Z-Machine by John Menichelli
[reverzi.git] / reverzi.inf
1 !***************************************************************************
2 !**
3 !** Reverzi: Yet Another Abuse of the Z-Machine
4 !** 
5 !** Reversi for the Z-Machine by John Menichelli
6 !**
7 !***************************************************************************
8
9 !***************************************************************************
10 !**
11 !** Reverzi can be inserted into other games, possibly as an Easter egg.
12 !** To start the game, call PlayRev();
13 !**
14 !** I've given all of the identifiers in this code a "rev_" prefix, to 
15 !** separate them from identifiers in the main game.
16 !** 
17 !** The code uses 8 global variables and 192 bytes of array space. 
18 !**
19 !** This source code is in the public domain.
20 !**
21 !***************************************************************************
22
23 !***************************************************************************
24 !** 
25 !** The board in internally represented as follows:
26 !** 
27 !**   0  1  2  3  4  5  6  7
28 !**   8  9 10 11 12 13 14 15
29 !**  16 17 18 19 20 21 22 23
30 !**  24 25 26 27 28 29 30 31
31 !**  32 33 34 35 36 37 38 39
32 !**  40 41 42 43 44 45 46 47
33 !**  48 49 50 51 52 53 54 55
34 !**  56 57 58 59 60 61 62 63
35 !** 
36 !***************************************************************************
37
38 !***************************************************************************
39 !**
40 !** Arrays and Variables
41 !**
42 !***************************************************************************
43
44 Array rev_b --> 64;
45 Array rev_c --> 64;
46 Array rev_d --> 64;
47
48 Global rev_black;
49 Global rev_white;
50 Global rev_play;
51 Global rev_playside;
52 Global rev_compside;
53 Global rev_skipmove;
54 Global rev_mode;
55 Global rev_passes;
56
57 !***************************************************************************
58 !**
59 !** Routines
60 !**
61 !***************************************************************************
62
63 [ OtherSide side;
64    if (side == 1) return 2; return 1;
65 ];
66
67 [ PosAdjoins pos side retval x y;
68    y = (pos/8);
69    x = (pos-(y*8)-1);
70    retval=0;
71
72    if (x > 0) {
73       if (rev_b->(pos-1) == side) retval = 1;
74    }
75
76    if (x < 7) {
77       if (rev_b->(pos+1) == side) retval = 1;
78    }
79
80    if (y > 0) {
81       if (rev_b->(pos-8) == side) retval = 1;
82    }
83
84    if (y < 7) {
85       if (rev_b->(pos+8) == side) retval = 1;
86    }
87
88    if ((x > 0) && (y > 0)) {
89       if (rev_b->(pos-9) == side) retval = 1;
90    }
91
92    if ((x < 7) && (y > 0)) {
93       if (rev_b->(pos-7) == side) retval = 1;
94    }
95
96    if ((x > 0) && (y < 7)) {
97       if (rev_b->(pos+7) == side) retval = 1;
98    }
99
100    if ((x < 7) && (y < 7)) {
101       if (rev_b->(pos+9) == side) retval = 1;
102    }
103
104    return retval;
105 ];
106
107 [ CountPieces loop;
108    rev_black=0;
109    rev_white=0;
110
111    for(loop=0: loop<64: loop++)
112    {
113       if (rev_b->loop == 1) rev_black++;
114       if (rev_b->loop == 2) rev_white++;
115    }
116 ];
117
118 [ DrawBoard i ;
119    @set_cursor 3 1;
120    for (i=0: i<64: i++)
121    {
122       if (i%8 == 0)
123          print "    "; 
124
125       switch (rev_b->i)
126       { 
127          0: print ". ";
128          1: print "X ";
129          2: print "O ";
130       }
131
132       if (i%8 == 7)
133          print "^";
134    }
135
136     CountPieces();
137
138     @set_cursor 3 22;
139     print "Black:   ";
140     @set_cursor 3 29;
141     print rev_black;
142
143     @set_cursor 5 22;
144     print "White:   ";
145     @set_cursor 5 29;
146     print rev_white;
147
148     @set_cursor 7 22;
149     print "Player: ";
150     if (rev_playside == 1)
151        print "X";
152     else
153        print "O";
154 ];
155
156 [ InitGame i ;
157    for(i = 0: i < 64: i++)
158       rev_b->i = 0;
159
160    rev_b->27 = 1; ! Black
161    rev_b->28 = 2; ! White
162    rev_b->35 = 2; ! White
163    rev_b->36 = 1; ! Black
164
165    CountPieces();
166
167    @split_window 13;
168    @set_window 1;
169    @set_cursor -1 0; ! Turn off cursor
170    for (i = 1 : i <= 13 : ++i) {
171       @set_cursor i 1;
172       spaces (0->33)-1; }
173
174    @set_cursor 1 6;
175    print "R E V E R Z I";
176
177    DrawBoard();
178    
179    @set_cursor 1 34;
180    print "W";
181    @set_cursor 2 33;
182    print "A S";
183    @set_cursor 3 34;
184    print "Z";
185    @set_cursor 5 33;
186    print "[Spc] Enter move";
187    @set_cursor 6 33;
188    print "[C]hange sides";
189    @set_cursor 7 33;
190    print "[P]ass";
191    @set_cursor 8 33;
192    print "[M]ode = ";
193    if (rev_mode == 0)
194       print "Slow";
195    else
196       print "Fast";
197    @set_cursor 9 33;
198    print "[Q]uit";
199    @set_cursor 10 33;
200    print "[H]elp";
201 ];
202
203 [ LineCaptured side pos offset stop capture cont
204                count lpos loop other retval;
205
206    retval = 0;
207    other = OtherSide(side);
208    cont = 1;
209    count = 0;
210  
211    for (lpos=pos+offset: (lpos >= 0 && lpos <= 63 && cont == 1): lpos=lpos+offset)
212    {
213       if (rev_b->lpos == 0) cont = 0;
214
215       if (rev_b->lpos == other) count++;
216
217       if (rev_b->lpos == side && cont ~= 0)
218       {
219          cont = 0;
220          if (capture == 1 && count > 0)
221          {
222             for (loop = pos: loop ~= lpos: loop = loop + offset)
223                rev_b->loop = side;
224          }
225          retval = count;
226       }
227       if (lpos == stop) cont = 0;
228    }
229    return retval;
230 ];
231
232 [ PiecesCaptured side pos capture x y captured minnw minne minsw minse;
233
234    captured = 0;
235    y = (pos/8);
236    x = (pos - (y*8));
237
238    if (x < y)
239       minnw = x;
240    else
241       minnw = y;
242
243    if (x < (7 - y))
244       minsw = x;
245    else
246       minsw = (7 - y); 
247
248    if ((7 - x) < y)
249       minne = (7 - x);
250    else
251       minne = y;
252
253    if ((7 - x) < (7 - y))
254       minse = (7 - x);
255    else
256       minse = (7 - y);
257
258    if (x > 0)
259    {
260       captured = captured + 
261                  linecaptured (side, pos, -1, pos-x, capture);
262    }
263
264    if (x < 7)
265    {
266       captured = captured +
267                  linecaptured (side, pos, 1, pos+7-x, capture);
268    }
269
270    if (y > 0)
271    {
272       captured = captured +
273                  linecaptured(side, pos, -8, pos-(y*8), capture);
274    }
275
276    if (y < 7)
277    {
278       captured = captured +
279                  linecaptured(side, pos, 8, pos+((7-y)*8), capture);
280    }
281
282    if ((x > 0) && (y > 0))
283    {
284       captured = captured +
285                  linecaptured(side, pos, -9, pos-(minnw*9), capture);
286    }
287
288    if ((x > 0) && (y < 7))
289    {
290       captured = captured +
291                  linecaptured(side, pos, 7, pos+(minsw*7), capture);
292    }
293
294    if ((x < 7) && (y > 0))
295    {
296       captured = captured +
297                  linecaptured(side, pos, -7, pos-(minne*7), capture);
298    }
299
300    if ((x < 7) && (y < 7))
301    {
302       captured = captured +
303                  linecaptured(side, pos, 9, pos+(minse*9), capture);
304    }
305    return captured;
306 ];
307
308 [ IsValid side pos other retval adjoins;
309    retval= 0;
310    if (pos > -1 && pos < 64)
311    {
312       other = OtherSide(side);
313       if(rev_b->pos == 0)
314       {
315          adjoins = PosAdjoins(pos, other);
316
317          if (adjoins == 1)
318             if (PiecesCaptured(side, pos, 0) > 0) retval = 1;
319       }
320    }
321    return retval;
322 ];
323
324 ! The "guts" of the program - selects moves for the computer.
325 ! Also checks to see if the player has any valid moves
326
327 [ GetMove side loop max move;
328    max = -1;
329    move = -1;
330
331    ! If a corner is empty, avoid adjacent squares unless there is no other
332    ! choice. Refer to diagram at the beginning of this file for details.
333
334    for (loop = 0: loop < 64: loop++)
335       rev_d->loop = 0;
336
337    if (rev_b->0 == 0)
338    {
339       if (IsValid(side, 1)) rev_d->1 = 1;
340       if (IsValid(side, 8)) rev_d->8 = 1;
341       if (IsValid(side, 9)) rev_d->9 = 1;
342    }
343
344    if (rev_b->7 == 0)
345    {
346       if (IsValid(side, 6))  rev_d->6 = 1;
347       if (IsValid(side, 14)) rev_d->14 = 1;
348       if (IsValid(side, 15)) rev_d->15 = 1;
349    }
350
351    if (rev_b->56 == 0)
352    {
353       if (IsValid(side, 48)) rev_d->48 = 1;
354       if (IsValid(side, 49)) rev_d->49 = 1;
355       if (IsValid(side, 57)) rev_d->57 = 1;
356    }
357
358    if (rev_b->63 == 0)
359    {
360       if (IsValid(side, 54)) rev_d->54 = 1;
361       if (IsValid(side, 55)) rev_d->55 = 1;
362       if (IsValid(side, 62)) rev_d->62 = 1;
363    }
364
365    ! Corner moves have priority
366
367    if(IsValid(side, 0)) move = 0;
368
369    if(IsValid(side, 7)) move = 7;
370
371    if(IsValid(side, 56)) move = 56;
372
373    if(IsValid(side, 63)) move = 63;
374
375    if (move > -1)
376       return move;
377
378    else
379    {
380       for (loop = 0: loop < 64: loop++)
381       {
382          if (IsValid(side, loop))
383          {
384             rev_c->loop = PiecesCaptured(side, loop, 0);
385             if (rev_c->loop > max)
386             {
387                max = rev_c->loop;
388                move = loop;
389             }
390             if (rev_c->loop == max && random(10) > 5)
391             {
392                max = rev_c->loop;
393                move = loop;
394             }
395          }
396          else
397             rev_c->loop = 0;
398       }
399
400       ! Now we have two arrays: rev_c contains the number of pieces that would 
401       ! be captured if that move was played, while rev_d contains those moves 
402       ! that should be avoided.
403
404       ! First, eliminate moves adjacent to the corners
405
406       for (loop = 0: loop < 64: loop++)
407          if (rev_d->loop == 1 && rev_c->loop > 0) rev_c->loop = 0;
408
409       ! Next, check to see if any valid moves are left
410    
411       max = 0; ! Re-use max
412       for (loop = 0: loop < 64: loop++)
413          if (rev_c->loop > 0) max++;
414
415       ! If a not-adajcent-to-the-corner move exists, make it
416
417       if (max > 0)
418       {
419          max = 0;
420          for (loop = 0: loop < 64: loop++)
421          {
422             if (rev_c->loop > max)
423             {
424                max = rev_c->loop;
425                move = loop;
426             }
427             if (rev_c->loop == max && random(10) > 5)
428             {
429                max = rev_c->loop;
430                move = loop;
431             }
432          }
433       }
434    }
435    return move;
436 ];
437
438 [ GetCompMove move ;
439    move = GetMove(rev_compside);
440    if (move > -1)
441    {
442       rev_b->move = rev_compside;
443       PiecesCaptured(rev_compside, move, 1);
444       CountPieces();
445    }
446    else
447    {
448       rev_passes++;
449       CountPieces();
450       if (rev_black + rev_white == 64) return;
451       @set_cursor 12 1;
452       spaces (0->33) - 1;
453       @set_cursor 12 5;
454       print "[No allowable moves - skipping turn]";
455       Pause();
456       @set_cursor 12 1;
457       spaces (0->33) - 1;
458    }
459 ];
460
461 [ GetPlayerMove pos valid k cur_x cur_y a move;
462    rev_passes = 0;
463    move = GetMove(rev_playside);
464    if (move == -1)
465    {
466       rev_passes++;
467       @set_cursor 12 1;
468       spaces (0->33) - 1;
469       @set_cursor 12 5;
470       print "[You have no allowable moves - skipping turn]";
471       Pause();
472       @set_cursor 12 1;
473       spaces (0->33) - 1;
474       rtrue;
475    }
476
477    rev_skipmove=0;
478    valid=0;
479    cur_y = 3;
480    cur_x = 5;
481    @set_cursor cur_y cur_x;
482    print "?";
483    pos = 0;
484
485    while (valid == 0 && rev_skipmove == 0)
486    {
487       for (::)
488       {
489          @read_char 1 -> k;
490          @set_cursor 12 1;
491          spaces (0->33) - 1;
492
493          ! Space (enter move)
494          if (k == ' ')
495             break;
496
497          ! Change sides
498          if (k == 'c' || k == 'C')
499          {
500             rev_compside = rev_playside;
501             rev_playside = OtherSide(rev_playside);
502             InitGame();
503             if (rev_compside == 1) ! Computer = black
504             {
505                GetCompMove();
506                DrawBoard();
507             }
508          }
509
510          ! Help
511          if (k == 'h' || k == 'H')
512          {
513             @set_window 0;
514
515             print "Rules:^^";
516
517             print "Black (X) moves first.^^
518             
519                    To make a legal move, your piece must be placed next to a 
520                    piece of the opposite color. The move is legal if 
521                    somewhere on the column/row/diagonal in the direction of 
522                    the opposite piece is one of your own pieces. All opposite 
523                    pieces in this direction are then captured by you and will 
524                    change to your color.^^
525                    
526                    If your piece is placed next to several pieces of the 
527                    opposite color then you capture those pieces also.^^
528                    
529                    If you have no possible moves you will be forced to pass 
530                    your turn. To manually pass simply press ~P~. Two 
531                    consecutive passes (one by you and one by the computer 
532                    will end the game.^^
533
534                    If the computer cannot make a move it will display a 
535                    message. You continue to make your moves as normal.^^
536                    
537                    The game is over when all fields are occupied or when no 
538                    side can make a legal move. The winner of the game is the 
539                    one with the most pieces when the game is over.^^";
540
541             
542             print "Controls: Use the arrow keys to move the cursor (the ~?~) 
543                    around the board. When the ~?~ is where you want to place 
544                    your piece, press the space bar. You can also use the ~W~,
545                    ~A~, ~S~ and ~Z~ keys to move the cursor.^^
546                    
547                    Most of the menu choices should be self-explanatory. ~C~ 
548                    allows you to change sides. Press ~P~ to pass, ~Q~ to
549                    quit and ~H~ to diplay this information. ~M~ changes the 
550                    game's mode between fast and slow. In ~slow~ mode, the 
551                    game will pause after you make a move, so that you can 
552                    see the results of your move. In ~fast~ mode, the computer 
553                    will make it's move immediately after you make yours, then 
554                    display the results.^^";
555
556             @set_window 1;
557          }
558
559          ! Pass
560          if (k == 'p' || k == 'P')
561          {
562             rev_passes++;
563             rev_skipmove = 1;
564             break;
565          }
566
567          ! Mode change
568          if (k == 'm' || k == 'M')
569          {
570             if (rev_mode == 0)
571                rev_mode = 1;
572             else
573                rev_mode = 0;
574
575             @set_cursor 8 33;
576             print "[M]ode = ";
577             if (rev_mode == 0)
578                print "Slow";
579             else
580                print "Fast";
581          }
582
583          ! Quit
584          if (k == 'q' || k == 'Q')
585          {
586             @set_cursor 12 1;
587             spaces (0->33) - 1;
588             @set_cursor 12 5;
589             print "Play again? (y/n) ";
590
591             @read_char 1 -> a;
592             if (a == 'n' || a == 'N') {
593                rev_play = 0;
594                break; }
595             else
596             {
597                @set_cursor 12 1;
598                spaces (0->33) - 1;
599                rev_playside = 1;
600                rev_compside = 2;
601                InitGame();
602             }
603          }
604
605          ! Left
606          if (k == 'a' || k == 'A' || k == 131)
607          {
608             if (pos%8 == 0)
609                pos = pos + 7;
610             else
611                pos--;
612             cur_x = cur_x - 2;
613             if (cur_x < 5) cur_x = 19;
614             DrawBoard();
615          }
616
617          ! Right
618          if (k == 's' || k == 'S' || k == 132)
619          {
620             if ((pos+1)%8 == 0)
621                pos = pos - 7;
622             else
623                pos++;
624             cur_x = cur_x + 2;
625             if (cur_x > 19) cur_x = 5;
626             DrawBoard();
627          }
628
629          ! Up
630          if (k == 'w' || k == 'W' || k == 129)
631          {
632             if (pos <= 7)
633                pos = pos + 56;
634             else
635                pos = pos - 8;
636             cur_y--;
637             if (cur_y < 3) cur_y = 10;
638             DrawBoard();
639          }
640
641          ! Down
642          if (k == 'z' || k == 'Z' || k == 0 || k == 130) 
643          {
644             if (pos >= 56)
645                pos = pos - 56;
646             else
647                pos = pos + 8;
648             cur_y++;
649             if (cur_y > 10) cur_y = 3;
650             DrawBoard();
651          }
652          @set_cursor cur_y cur_x;
653          print "?";
654       }
655       if (rev_play == 0) return;
656       if (rev_skipmove == 1) return;
657       if (IsValid(rev_playside, pos) == 1)
658          valid = 1;
659       else
660       {
661          @set_cursor 12 1;
662          spaces (0->33) - 1;
663          @set_cursor 12 5;
664          print "[Invalid move]";
665       }
666    }
667
668    if (rev_skipmove == 0) {
669       rev_b->pos = rev_playside;
670       PiecesCaptured(rev_playside, pos, 1);
671       DrawBoard();
672       CountPieces();
673    }
674 ];
675
676 [ PlayRev a;
677    rev_mode = 1; ! Fast (default)
678
679 .Start;
680    rev_playside = 1;
681    rev_compside = 2;
682    rev_play = 1;
683    while (rev_play)
684    {
685       InitGame();
686       while ((rev_black+rev_white < 64) &&
687              (rev_black > 0)            &&
688              (rev_white > 0)            &&
689              (rev_passes < 2))
690       {
691          GetPlayerMove();
692
693          if (rev_play == 0) break;
694          if (rev_mode == 0)
695          {
696             @set_cursor 12 1;
697             spaces (0->33) - 1;
698             @set_cursor 12 5;
699             print "[Press any key to continue]";
700             Pause();
701             @set_cursor 12 1;
702             spaces (0->33) - 1;
703          }
704          GetCompMove();
705          DrawBoard();
706       }
707
708       if (rev_play == 0) break;
709
710       @set_cursor 12 1;
711       spaces (0->33) - 1;
712       @set_cursor 12 5;
713
714       if (rev_passes == 2) {
715          print "Two passes - game ends.";
716          jump PlayAgain; }
717
718       if (rev_black > rev_white)
719       {
720          if (rev_playside == 1)
721             print "You win.";
722          else
723             print "I win.";
724       }
725
726       if (rev_white > rev_black)
727       {
728          if (rev_playside == 1) 
729             print "I win.";
730          else
731             print "You win.";
732       }
733    
734       if (rev_white == rev_black)
735          print "Drawn game.";
736
737 .PlayAgain;         
738       print " Play again? (y/n) ";
739
740       @read_char 1 -> a;
741       if (a == 'y' || a == 'Y') {
742          @set_cursor 12 1;
743          spaces (0->33) - 1;
744          jump Start; }
745       else
746          break;
747    }
748    @set_cursor -2 0; ! Turn cursor on again
749    @set_cursor 1 1;
750    @split_window 0;
751    @erase_window $ffff;
752 ];
753
754 [ Main ;
755
756    PlayRev();
757
758    print "Reverzi, Version 1.0^
759           Reverzi is a port of Reversi to the Z-Machine^
760           Ported by John Menichelli, December 1999^^";
761
762    print "The original source code was written by Dave Derrick for handheld
763           PCs. I downloaded it from the OrbWorks web site 
764           (www.orbworks.com/ceres.html) and converted it to Inform. The 
765           original source was written in PocketC and was very easy to convert
766           to Inform; the most difficult part of the conversion was creating
767           the UI. ^^";
768
769    print "Special thanks to Andrew Plotkin - some of the code for this
770           game - along with the idea of abusing the Z-Machine - was borrowed 
771           from his game ~Freefall.~^^";
772 ];
773
774 [ Pause dummy;
775         @read_char 1 dummy;
776         return dummy;
777 ];