Retrocomputing

# Hexapawn program from BASIC Computer Games

The hexapawn program is one of the games in the famous BASIC Computer Games published in 1978. While converting it to Pascal I found a couple of bugs in the code and corrected them:

• The game only has board patterns for plays where the computer has a move. It is therefore possible to get to a state the computer thinks is an "ILLEGAL BOARD PATTERN". This just means the computer can't move and loses the game.
• Pattern 2 had a move missing. It is also possible to move from cell 3 to 5. Pattern 18 had a bad code (28) for a move. It meant the computer could jump from cell 2 to cell 8. The correct code is 24.

For more analysis of the game, see OS X Games from the Ground Up: Hexapawn.

## Pascal edition

```{
\$TITLE Hexapawn
HEXAPAWN:  INTERPRETATION OF HEXAPAWN GAME AS PRESENTED IN
MARTIN GARDNER'S "THE UNEXPECTED HANGING AND OTHER MATHEMATICAL
DIVERSIONS", CHAPTER EIGHT:  A MATCHBOX GAME-LEARNING MACHINE
ORIGINAL VERSION FOR H-P TIMESHARE SYSTEM BY R.A. KAAPKE 5/5/76
INSTRUCTIONS BY JEFF DALTON
CONVERSION TO MITS BASIC BY STEVE NORTH
CONVERSION TO PASCAL BY SOREN ROUG
}

PROGRAM hexapawn(input, output);

CONST
maxpatterns = 19;
TYPE
cellvals = (computer, empty, human);
VAR
pattern : ARRAY [1..maxpatterns, 1..9] OF cellvals;
moves : ARRAY [1..maxpatterns, 1..4] OF integer;
s,t : ARRAY [1..9] OF cellvals;
p : ARRAY [cellvals] OF char;
m1,m2,i,j,k,won,lost,x,y : integer;
a : char;
seed : real;
reversed,gameres : boolean;

FUNCTION inputerr(r: integer):integer;
BEGIN
writeln('** Input error - reenter **');
inputerr := 1;
readln(input) { skip beyond carriage return }
END;

{ Random generator. Needs 32 bit integer or real as seed }
FUNCTION rnd(VAR seed : real):integer;
CONST
multiplier = 25173.0;
increment = 13849.0;
modulus = 32767.0;
BEGIN
rnd := round(seed);
seed := afrac((multiplier * seed + increment) / modulus) * modulus
END;

PROCEDURE randomize(VAR seed:real);
CONST
modulus = 32767.0;
BEGIN
seed := afrac(seed / modulus) * modulus
END;

{ Read integer values separated by a comma }
VAR
r : integer;
comma: char;
BEGIN
prompt;  { flush output }
REPEAT
ioabort(input, false);
r := ioresult(input);
ioabort(input, true);
IF r <> 0 THEN
r := inputerr(r);
IF comma <> ',' THEN
r := inputerr(20);
UNTIL r = 0;
END;

FUNCTION fnr(x:integer):integer;
BEGIN
CASE x OF
1: fnr := 3;
2: fnr := 2;
3: fnr := 1;
4: fnr := 6;
5: fnr := 5;
6: fnr := 4;
7: fnr := 9;
8: fnr := 8;
9: fnr := 7
END
END;

FUNCTION highdigit(y:integer):integer;
BEGIN
highdigit := y DIV 10
END;

FUNCTION lowdigit(y:integer):integer;
BEGIN
lowdigit := y MOD 10
END;

VAR
yesno:char;
BEGIN
prompt;  { flush output }
if (yesno >= 'a') and (yesno <= 'z') then
yesno := chr(ord(yesno) - ord('a') + ord('A'));
END;

{ Initialise the patterns. Only the patterns that have moves are listed. }
PROCEDURE init;
VAR
c : ARRAY [1..maxpatterns,1..9] OF char;
m : ARRAY [1..maxpatterns,1..11] OF char;
i,j : integer;

BEGIN
c[ 1] := 'XXXO...OO';
c[ 2] := 'XXX.O.O.O';
c[ 3] := 'X.XXO...O';
c[ 4] := '.XXOX...O';
c[ 5] := 'X.XOO..O.';
c[ 6] := 'XX.O.O..O';
c[ 7] := '.XX.XOO..';
c[ 8] := '.XXXOOO..';
c[ 9] := 'X.XX.O.O.';
c[10] := '.XX.O...O';
c[11] := '.XX.O.O..';
c[12] := 'X.XO....O';
c[13] := '..XXXO...';
c[14] := 'X..OOO...';
c[15] := '.X.XOO...';
c[16] := 'X..XXO...';
c[17] := '..XXO....';
c[18] := '.X.OX....';
c[19] := 'X..XO....';

FOR i := 1 TO maxpatterns DO
FOR j := 1 TO 9 DO
BEGIN
CASE c[i,j] OF
'X': pattern[i,j] := computer;
'.': pattern[i,j] := empty;
'O': pattern[i,j] := human;
END
END;

{ Possible moves for each pattern }
m[ 1] := '24,25,36,00';
m[ 2] := '14,15,35,36';
m[ 3] := '15,35,36,47';
m[ 4] := '36,58,59,00';
m[ 5] := '15,35,36,00';
m[ 6] := '24,25,26,00';
m[ 7] := '26,57,58,00';
m[ 8] := '26,35,00,00';
m[ 9] := '47,48,00,00';
m[10] := '35,36,00,00';
m[11] := '35,36,00,00';
m[12] := '36,00,00,00';
m[13] := '47,58,00,00';
m[14] := '15,00,00,00';
m[15] := '26,47,00,00';
m[16] := '47,58,00,00';
m[17] := '35,36,47,00';
m[18] := '24,58,00,00';
m[19] := '15,47,00,00';

FOR i := 1 TO maxpatterns DO
FOR j := 1 TO 4 DO
moves[i,j] := (ord(m[i,j * 3 - 2]) - ord('0')) * 10
+ ord(m[i,j * 3 - 1]) - ord('0');
END;

PROCEDURE printboard;
VAR
i,j : integer;
BEGIN
FOR i := 1 TO 3 DO
BEGIN
write(' ':10);
FOR j := 1 TO 3 DO
write(p[s[(i-1)*3+j]]);
writeln
END;
writeln
END;

PROCEDURE instructions;
BEGIN
writeln;
writeln('THIS PROGRAM PLAYS THE GAME OF HEXAPAWN.');
writeln('HEXAPAWN IS PLAYED WITH CHESS PAWNS ON A 3 BY 3 BOARD.');
writeln('THE PAWNS ARE MOVED AS IN CHESS - ONE SPACE FORWARD TO');
writeln('AN EMPTY SPACE OR ONE SPACE FORWARD AND DIAGONALLY TO');
writeln('CAPTURE AN OPPOSING MAN.  ON THE BOARD, YOUR PAWNS');
writeln('ARE ''O'', THE COMPUTER''S PAWNS ARE ''X'', AND EMPTY ');
writeln('SQUARES ARE ''.''.  TO ENTER A MOVE, TYPE THE NUMBER OF');
writeln('THE SQUARE YOU ARE MOVING FROM, FOLLOWED BY THE NUMBER');
writeln('OF THE SQUARE YOU WILL MOVE TO.  THE NUMBERS MUST BE');
writeln('SEPERATED BY A COMMA.');
writeln;
writeln('THE COMPUTER STARTS A SERIES OF GAMES KNOWING ONLY WHEN');
writeln('THE GAME IS WON (A DRAW IS IMPOSSIBLE) AND HOW TO MOVE.');
writeln('IT HAS NO STRATEGY AT FIRST AND JUST MOVES RANDOMLY.');
writeln('HOWEVER, IT LEARNS FROM EACH GAME.  THUS, WINNING BECOMES');
writeln('MORE AND MORE DIFFICULT.  ALSO, TO HELP OFFSET YOUR');
writeln('INITIAL ADVANTAGE, YOU WILL NOT BE TOLD HOW TO WIN THE');
writeln('GAME BUT MUST LEARN THIS BY PLAYING.');
writeln;
writeln('THE NUMBERING OF THE BOARD IS AS FOLLOWS:');
writeln(' ':10, '123');
writeln(' ':10, '456');
writeln(' ':10, '789');
writeln;
writeln('FOR EXAMPLE, TO MOVE YOUR RIGHTMOST PAWN FORWARD,');
writeln('YOU WOULD TYPE 9,6 IN RESPONSE TO THE QUESTION');
writeln('''YOUR MOVE ?''.  SINCE I''M A GOOD SPORT, YOU''LL ALWAYS');
writeln('GO FIRST.');
writeln
END;

{ Get move from player and check boundaries }
PROCEDURE getmove;
VAR
valid : boolean;
BEGIN
valid := false;
REPEAT
if (m1 > 0) AND (m1 < 10) AND (m2 > 0) AND (m2 < 10) then
valid := true
else
writeln('ILLEGAL CO-ORDINATES.');
UNTIL valid = true;
END;

PROCEDURE usermove;
VAR
valid : boolean;
BEGIN
REPEAT
valid := true;
getmove;
IF s[m1] <> human THEN valid := false;
IF valid AND (s[m2] = human) THEN valid := false;
IF valid AND (m2-m1 <> -3) AND (s[m2] <> computer) THEN valid := false;
IF valid AND (m2 > m1) THEN valid := false;
IF valid AND (m2-m1 = -3) AND (s[m2]<> empty) THEN valid := false;
IF valid AND (m2-m1 < -4) THEN valid := false;
IF valid AND (m1 = 7) AND (m2 = 3) THEN valid := false;

IF NOT valid THEN
writeln('ILLEGAL MOVE');
UNTIL valid = true;
END;

PROCEDURE initboard;
BEGIN
s[4] := empty;
s[5] := empty;
s[6] := empty;
s[1] := computer;
s[2] := computer;
s[3] := computer;
s[7] := human;
s[8] := human;
s[9] := human;
END;

FUNCTION endreached(player:cellvals; p:integer):boolean;
BEGIN
IF (s[p] = player) OR (s[p+1] = player) OR (s[p+2] = player) THEN
endreached := true
ELSE
endreached := false;
END;

FUNCTION hasnopawns(player:cellvals):boolean;
VAR
i : integer;
BEGIN
hasnopawns := true;
FOR i := 1 TO 9 DO
IF s[i] = player THEN hasnopawns := false;
END;

{ Find a move out of four possible.
Return false if no move available.
}
FUNCTION findmove(x:integer; VAR y:integer):boolean;
VAR
i : integer;
available : boolean;
BEGIN
available := false;
FOR i := 1 TO 4 DO
IF moves[x,i] <> 0 THEN available := true;
IF available THEN
REPEAT
y := rnd(seed) MOD 4 + 1;
UNTIL moves[x,y] <> 0;
findmove := available
END;

{ Play a game of hexapawn. Returns true if computer won,
false if player won.
}
FUNCTION playgame:boolean;
LABEL 120,330,350,460,510,540,790,800;

VAR
i,j,k : integer;

BEGIN
x := 0;
y := 0;
initboard;
printboard;

120:
usermove;
s[m1] := empty;
s[m2] := human;
printboard;
IF endreached(human,1) OR hasnopawns(computer) THEN
BEGIN
playgame := false;
GOTO 800
END;

FOR i := 1 TO 9 DO
BEGIN
IF s[i] <> computer THEN GOTO 330; { continue }
IF s[i+3] = empty THEN GOTO 350; { break }
IF (fnr(i) = i) THEN
IF((s[i+2] = human) OR (s[i+4] = human)) THEN GOTO 350  { break }
ELSE GOTO 330;
IF i > 3 THEN
IF s[8] = human THEN GOTO 350; { break }
330:  END;
350:
FOR i := 1 TO maxpatterns DO
BEGIN
FOR j := 1 TO 3 DO
FOR k := 3 DOWNTO 1 DO
t[(j-1)*3+k] := pattern[i,(j-1)*3+4-k];
FOR j := 1 TO 9 DO
IF s[j] <> pattern[i,j] THEN GOTO 460;
reversed := false;
GOTO 540;
460:     FOR j := 1 TO 9 DO
IF s[j] <> t[j] THEN GOTO 510; { continue }
reversed := true;
GOTO 540;
510:   END;
{ No pattern with a possible move was found }
write('I CAN''T MOVE, SO ');
playgame := false;
GOTO 800;

540:
x := i;
if NOT findmove(x,y) THEN
BEGIN
write('I CAN''T MOVE, SO ');
playgame := false;
GOTO 800;
END;

IF reversed THEN
BEGIN
writeln('I MOVE FROM ', fnr(highdigit(moves[x,y])):1, ' TO ',
fnr(lowdigit(moves[x,y])):1);
s[fnr(highdigit(moves[x,y]))] := empty;
s[fnr(lowdigit(moves[x,y]))] := computer
END
ELSE
BEGIN
writeln('I MOVE FROM ', highdigit(moves[x,y]):1, ' TO ',
lowdigit(moves[x,y]):1);
s[highdigit(moves[x,y])] := empty;
s[lowdigit(moves[x,y])] := computer
END;
printboard;
IF endreached(computer,7) OR hasnopawns(human) THEN
BEGIN
playgame := true;
GOTO 800
END;

{ Can human move? }
FOR i := 1 TO 9 DO
BEGIN
IF s[i] <> human THEN GOTO 790; { continue }
IF s[i-3] = empty THEN GOTO 120;
IF (fnr(i) = i) THEN
IF ((s[i-2] = computer) OR (s[i-4] = computer)) THEN GOTO 120
ELSE GOTO 790; { continue }
IF (i < 7) THEN
IF (s[2] = computer) THEN GOTO 120
ELSE GOTO 790; { continue }
IF s[5] = computer THEN GOTO 120;
790: END;
write('YOU CAN''T MOVE, SO ');
playgame := true;
800:
END;
\$Page
\$Subtitle MAIN
BEGIN
randomize(seed);
writeln(' ':32, 'HEXAPAWN');
writeln(' ':15, 'CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY');
writeln;
writeln;
p := 'X.O';
won := 0;
lost := 0;
init;
REPEAT
write('INSTRUCTIONS (Y-N)? ');
UNTIL (a = 'Y') OR (a = 'N');
IF a = 'Y' THEN instructions;

WHILE true DO
BEGIN
gameres := playgame;
IF gameres THEN
BEGIN
writeln('I WIN.');
won := won + 1;
END
ELSE
BEGIN
writeln('YOU WIN.');
moves[x,y] := 0;
lost := lost + 1;
END;
writeln('I HAVE WON ',won:1,' AND YOU ',lost:1,' OUT OF ',won+lost:1,' GAMES.');
writeln;
END
END.

```

The statements starting with '\$' are compiler instructions and can safely be removed.