home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / CLIPPER / MISC / AIE8907.ZIP / APPRENT.CDE next >
Encoding:
Text File  |  1988-05-04  |  19.5 KB  |  642 lines

  1.  ************* HEX2 CODE: ************************
  2.  
  3.  
  4. {HEX2 code copyright 1989 Mark Minasi.  All Rights Reserved.}
  5. {$B-}    {Boolean complete evaluation off}
  6. {$S+}    {Stack checking on}
  7. {$I+}    {I/O checking on}
  8. {$R+}
  9.  
  10. Program Hexapawn1;
  11. {This demonstrates a straightforward approach to computer game playing: an
  12.  N level search followed by minimax-based back pruning}
  13.  
  14.  
  15. Uses
  16.   Dos;
  17.  
  18. Const
  19. {$I tconh.ins}
  20.  
  21.   Black:byte = 2;
  22.   White:byte = 1;
  23.   MoveDirection:array[1..2] of integer=(-1,1);
  24.   Computer = 10;
  25.   Human = 11;
  26.   Neither:byte =3;
  27.   names:array[1..3] of string[9]=('White','Black','Neither');
  28.  
  29.   Expanded = 1;
  30.   Unexpanded = 2;
  31.   BigWin = 32000;
  32.   BigLoss = -32000;
  33.   UnEvaluated = -Maxint;
  34.   {Change the next three things to alter the dimension of the board}
  35.   BD = 4; {Board Dimension}
  36.   emptyctype='';
  37.  
  38. {Note on players and sides:
  39.  
  40.   sides are indicated by a byte, 1=white, 2=black 3=neither.
  41.   we know which are computer & which are human with player[].
  42.   player[1]=player[white]=computer, value 10, or human, value 11.
  43.   player[2] ditto.}
  44.  
  45. Type CType = array[1..BD,1..BD] of byte;
  46.      B2    = array[1..2] of byte;
  47.      {$i ttyph.ins}
  48.  
  49. Var
  50.  
  51. {$I tvarh.ins}
  52. Player:                                B2;
  53. WhoWon:                                byte;
  54. CurrentBoard:                          Ctype;
  55. Nlevels:                               Integer; {Number of levels to search}
  56. T1,T2:                                 Real; {for timekeeping}
  57. LastFound:                             Integer; {used to speed up search}
  58. Side:                                  Byte;
  59.  
  60. {$I tprch.ins}
  61. {$I time.ins}
  62.  
  63. Procedure Init(var CurrentBoard:Ctype);
  64. Var I,J:Integer;
  65. Begin
  66.   {Set up Board}
  67.   For I:=1 to BD Do For j:=1 to BD do CurrentBoard[I,J]:=Neither;
  68.   For J:=1 to BD Do
  69.   begin
  70.     CurrentBoard[1,J]:=Black;
  71.     CurrentBoard[BD,J]:=White;
  72.   end;
  73.   LastFound:=Root; {initialize for search}
  74.   Assign(outfile,'');
  75.   Rewrite(outfile);
  76. End;
  77.  
  78. Function Opposite(Side:byte):byte;
  79. Begin
  80.   If Side=White then Opposite:=Black
  81.   Else If Side=Black then Opposite:=White
  82.   Else Writeln('ERROR IN OPPOSITE.  ASKED FOR SIDE=',Side);
  83. End;
  84.  
  85. Procedure ShowBoard(Board:Ctype);
  86. Var I,J:Integer;
  87. x:byte;
  88. {Prints out the selected board string}
  89. Begin
  90.   For I:=1 to BD do
  91.   begin
  92.     for j:=1 to bd do
  93.     begin
  94.       x:=board[i,j];
  95.       if x=white then write('W')
  96.       else if x=black then write('B')
  97.       else if x=neither then write('[')
  98.       else write('?');
  99.     end;
  100.     writeln;
  101.   end;
  102. end;
  103.  
  104. Procedure ChooseColor(var player:b2);
  105. Var Answer:char;
  106. Begin
  107.   Write('Should the computer play white? ');
  108.   Readln(answer);answer:=upcase(answer);
  109.   if answer='Y' then player[white]:=computer else player[white]:=human;
  110.  
  111.   Write('Should the computer play black? ');
  112.   Readln(answer);answer:=upcase(answer);
  113.   if answer='Y' then player[black]:=computer else player[black]:=human;
  114. End;
  115.  
  116.  
  117. Procedure Convert(TestBoard:Ctype;Var B:Ctype;Var BSum,WSum:Integer);
  118. Var
  119.   I,J,L: Integer;
  120.  
  121. Begin;
  122.   {Set up board for ease of analysis}
  123.   BSum := 0;
  124.   WSum := 0;
  125.   B:=TestBoard;
  126.   For I:=1 to BD do
  127.   For J:=1 to BD do
  128.   begin
  129.     if b[i,j]=black then bsum:=bsum+1 else
  130.     if b[i,j]=white then wsum:=wsum+1;
  131.   end;
  132. End;
  133.  
  134. Function Evaluate1(Board:Ctype;Side:byte):Integer;
  135. {Basic evaluation function}
  136. {Returns the integer evaluation function value for a given board position
  137.  and a given side.}
  138. Var
  139.   Enemy:                 byte;
  140.   I,j,sum:               Integer;
  141. Begin
  142.   {Presumably, this position has already been checked for a win.
  143.    Therefore, this must be an intermediate position.}
  144.   Sum:=0;
  145.   Enemy := Opposite(Side);
  146.   for i:=1 to bd do for j:=1 to bd do
  147.   if board[i,j]=side then sum:=sum+1 else if board[i,j]=enemy then
  148.   sum:=sum-1;
  149.   Evaluate1 := sum;
  150. End;
  151.  
  152. Function Evaluate2(Board:Ctype;Side:byte):Integer;
  153. {Aggressive evaluation function}
  154. {Returns the integer evaluation function value for a given board position
  155.  and a given side.}
  156. Var
  157.   Enemy:                 byte;
  158.   I,j,sum,attack,i1:               Integer;
  159. Begin
  160.   {Presumably, this position has already been checked for a win.
  161.    Therefore, this must be an intermediate position.}
  162.   Sum:=0;
  163.   Attack:=0;
  164.   Enemy := Opposite(Side);
  165.   for i:=1 to bd do for j:=1 to bd do
  166.   begin
  167.   if board[i,j]=side then sum:=sum+1 else if board[i,j]=enemy then
  168.   sum:=sum-1;
  169.   if (board[i,j]=side) and (i+MoveDirection[Side]>0) and
  170.   (i+MoveDirection[Side]<=BD)  then
  171.   begin
  172.     i1:=i+MoveDirection[Side];
  173.     if (j+1<=BD) and (board[i1,j+1]=enemy) then attack:=attack+1;
  174.     if (j-1>0) and (board[i1,j-1]=enemy) then attack:=attack+1;
  175.   end;
  176.   end;
  177.   Evaluate2 := 4*sum+attack;
  178. End;
  179.  
  180. Function Won(B1:Ctype;WhosMove:byte):byte;
  181. {Examines board TestBoard to see, given that WhosMove just moved, whether or
  182.  not WhosMove just won.}
  183.  
  184. Var
  185.   CheckedColor,SoFar : byte;
  186.   B:                    Ctype;
  187.   I,J,L,BSum,WSum,DI: Integer;
  188.   Iplus, jright, jleft:integer;
  189.   istart,iend:integer;
  190.   AllBlocked, blackback, whiteback:               boolean;
  191. Begin
  192.   SoFar := Neither;
  193.  
  194.   {check back row}
  195.   j:=1;
  196.   repeat
  197.     if b1[1,j]=white then sofar:=white else if b1[bd,j]=black then sofar:=black;
  198.     j:=j+1;
  199.   until (J>BD) or (SoFar<>Neither);
  200.  
  201.    {check for blocked moves}
  202.    If SoFar = Neither Then
  203.    Begin
  204.       {Check for blocked pieces}
  205.       CheckedColor := Opposite(WhosMove);
  206.       if CheckedColor=white then
  207.       begin
  208.         istart:=2;iend:=4;
  209.       end else
  210.       begin
  211.         istart:=1;iend:=3;
  212.       end;
  213.       DI := MoveDirection[CheckedColor];
  214.       AllBlocked := True;
  215.       For I:=1 to BD Do
  216.         For J:=1 to BD Do
  217.         Begin     {check piece at i,j}
  218.           If b1[I,J] = CheckedColor Then
  219.           Begin  {Look to see if the piece cannot move}
  220.             Iplus := I+DI;
  221.             Jleft := J-1;
  222.             Jright := J+1;
  223.             If (b1[iplus,j]<>whosmove)
  224.                                           {white not in front}
  225.             Or ((jleft>0) and (jleft<=bd) and (b1[iplus,jleft]=whosmove))
  226.                                           {white to right and ahead}
  227.             Or ((jright>0) and (jright<=bd) and (b1[iplus,jright]=whosmove))
  228.                                           {white to left and ahead}
  229.             Then Allblocked:=False;
  230.           End;
  231.         End;     {Checking piece at i,j}
  232.  
  233.       If AllBlocked Then SoFar := WhosMove;
  234.     End; {Looking for blocked pieces}
  235.  
  236.  
  237.   if SoFar=Neither Then
  238.   begin
  239.     Convert(B1,B,BSum,WSum);
  240.     {Check to see if either side is depleted}
  241.     If BSum = 0 Then SoFar := White
  242.     Else If WSum = 0 Then SoFar := Black;
  243.   end;
  244.  
  245.   Won:=SoFar;
  246. End; {Procedure}
  247.  
  248. Procedure Mirror(Board:Ctype;var B:Ctype);
  249. {Given a board, it inverts it about the middle column}
  250. var
  251.     I,J,MJ,Bsum,Wsum:Integer;
  252.     NSwaps:Integer;
  253.     T: Byte;
  254.  
  255. Begin
  256.   NSwaps := BD div 2;
  257.   B:=Board;
  258.   For J:=1 To NSwaps Do
  259.   Begin
  260.     MJ := BD + 1 - J;
  261.     For I:=1 To BD Do
  262.     Begin
  263.       T:=B[I,J];
  264.       B[I,J]:=B[I,MJ];
  265.       B[I,MJ]:=T;
  266.     End;
  267.   End;
  268. End;
  269.  
  270.  
  271. Procedure CheckBeforeAdding(NewBoard:Ctype;Var OkayToAdd:Boolean;
  272.                             PreviousMove:Integer);
  273. {This is where any kind of control strategy would go.  In the particular
  274.  case of Hexapawn, you can never have an ancestor identical to a descendant
  275.  node, as no moves are irreversible.  However, to reduce the tree, no
  276.  siblings will be allowed that are mirror images of each other.
  277.  PreviousMove is the node number of the previous board position.}
  278.  
  279. Var
  280.   RBoard :Ctype;
  281.   I,J,N:      Integer;
  282.   temp:ctype;
  283.   match:boolean;
  284. Begin
  285.  
  286. OkayToAdd:=true;
  287.  
  288. (*  Mirror code seems useless, so it's commented out *)
  289.   Mirror(NewBoard,RBoard);
  290.   N := FirstOffspring(PreviousMove);
  291.   OkayToAdd:=True;
  292.   While (N<>Null) and (OkayToAdd) Do
  293.   Begin
  294.     Characteristic(N,Temp);
  295.     match:=true;
  296.     for i:=1 to bd do for j:=1 to bd do
  297.      if temp[i,j]<>rboard[i,j] then match:=false;
  298.     If match Then OkayToAdd:=False;
  299.     N:=RSibling(N);
  300.   End;
  301.  
  302.  
  303. End;
  304.  
  305.  
  306. Procedure ConsiderMove(I,J:Integer;MoveNum:integer;B:Ctype;ThisSide:byte;
  307.          Var FilledTree:Boolean;PreviousMove:Integer;OriginalSide:byte;
  308.          Nlevels:integer);
  309. {Consider move number MoveNum from location (I,J) on the board.
  310.  Other useful information is the side that is doing the moving (ThisSide),
  311.  whether or not this move fills the tree (FilledTree), and what was the node
  312.  number of the previous move (PreviousMove).  OriginalSide is the side that is
  313.  doing the analysis.}
  314.  
  315. Const
  316.   DJ: array[1..3] of integer = (0,1,-1);
  317. Var
  318.   A: Ctype;
  319.   CanDo,  OkayToAdd: boolean;
  320.   MD, IP,NN                : integer;
  321.   NewBoard        :  Ctype;
  322.   WinFlag:               byte;
  323. Begin
  324.   FilledTree:=False;
  325.   CanDo := False;
  326.   MD := MoveDirection[ThisSide];
  327.   IP := I + MD;
  328.   {Is the proposed move possible?  Is there a blank space to occupy or an
  329.    enemy to attack?}
  330.   If (MoveNum=1) and (B[IP,J]=Neither) Then CanDo := True;
  331.   If ((J+1)<=BD) and (MoveNum=2)
  332.                  and (B[IP,J+1]=Opposite(ThisSide)) Then CanDo := True;
  333.   If ((J-1)>0) and (MoveNum=3)
  334.                 and (B[IP,J-1]=Opposite(ThisSide)) Then CanDo := True;
  335.   If CanDo Then
  336.   Begin
  337.     A := B;                  {Set up A to receive new board position}
  338.     A[I,J] := Neither;
  339.     A[IP,J+DJ[MoveNum]] := ThisSide;
  340.     NewBoard:=A;
  341.     {Control Strategies}
  342.     CheckBeforeAdding(NewBoard,OkaytoAdd,PreviousMove);
  343.     If OkaytoAdd Then
  344.     Begin              {Adding node to tree}
  345.       CreateNode(PreviousMove,NewBoard,NN);
  346.       If NN=Null then FilledTree := True Else
  347.       Begin {Check to see if the new node is a terminal node.  This would be
  348.              either due to its being at maximum depth, or a "game over" node.}
  349.         WinFlag := Won(NewBoard,ThisSide);
  350.         If (Depth(NN) = Nlevels) or (WinFlag<>Neither) Then
  351.         Begin   {This is a terminal node.  Set its value}
  352.           SetValue(NN,Expanded);
  353.           If WinFlag=Neither Then SetGValue(NN,UnEvaluated) else
  354.           If WinFlag=OriginalSide Then SetGValue(NN,BigWin-Depth(NN)) else
  355.           {See Note 1, end of program}
  356.           If WinFlag=Opposite(OriginalSide) Then
  357.                    SetGvalue(NN,BigLoss+Depth(NN));
  358.         End Else
  359.         Begin {Non terminal node}
  360.           SetValue(NN,Unexpanded);
  361.           SetGValue(NN,UnEvaluated);
  362.         End
  363.       End {Tree not filled, marking nodes};
  364.     End {adding to tree} ;
  365.   End {"Can Do" clause};
  366. End;
  367.  
  368. Procedure Expand(N:Integer;OriginalSide:byte;var treefull:boolean;
  369.                  Nlevels:integer);
  370. label escape;
  371. Const
  372.   NMoves = 3;
  373. { This expands node N, producing all of its offspring nodes.  Any control
  374.   strategies are implemented here.  One such in Hexapawn would be to remove
  375.   mirror images among siblings.
  376. When done, sets node N to "expanded."
  377. Generated offspring are set to "unexpanded" unless they are terminal nodes or
  378. at max depth, in which case they are set to "expanded".
  379. }
  380. Var
  381.   ThisSide:                        byte;
  382.   B:                               Ctype;
  383.   Bsum,Wsum,I,J,K:                 Integer;
  384.   Board:                           Ctype;
  385.  
  386. Begin
  387.   If N=Null then writeln('Error in Expand.  Null node ',N,' passed.');
  388.   {First, we've got to know which side is doing the moving.}
  389.   If Odd(Depth(N)) Then ThisSide := Opposite(OriginalSide)
  390.                    Else ThisSide:=OriginalSide;
  391.   Characteristic(N,Board);
  392.   Convert(Board,B,Bsum,Wsum);
  393.   {Find the pieces, then see if they can move}
  394.   For I:=1 to BD do For J:=1 to BD Do
  395.   Begin
  396.     If B[I,J]=ThisSide Then For K:=1 to Nmoves Do
  397.     begin
  398.       ConsiderMove(I,J,K,B,ThisSide,TreeFull,N,OriginalSide,Nlevels);
  399.       If TreeFull Then Goto Escape;
  400.     end;
  401.     SetValue(N,Expanded);
  402.   End;
  403. escape:
  404. End;
  405.  
  406. Procedure FindFirst(Var N:integer);
  407. Var
  408.   M:         Integer;
  409.   Alldone:   Boolean;
  410. { Locates first unexpanded node.  This version uses a depth-first search.}
  411. {starts from last created node}
  412. Begin
  413.   M:=Lastfound;
  414.   Alldone:=False;
  415.   While not Alldone do
  416.   Begin
  417.     If M=Null then Alldone:=True else
  418.     Begin
  419.       If NodeValue(M) = Unexpanded then Alldone:=True else M:=NextNode(M);
  420.     End;
  421.   End;
  422.   N:=M;
  423.   LastFound:=N;
  424. End;
  425.  
  426. Procedure Gentree(var Nlevels:Integer;Side:byte);
  427. {Generates a game move tree Nlevels deep for side Side}
  428. Var
  429.   N:Integer;
  430.   AllDone:Boolean;
  431.   Treefull:boolean;
  432. Begin
  433. Repeat
  434.   Treefull:=False;
  435.   LastFound:=Root; {so search is initialized properly}
  436.   Inittree;
  437.   SetCharacteristic(Root,CurrentBoard);
  438.   SetValue(Root,Unexpanded);
  439.   SetGValue(Root,Unevaluated);
  440.   Alldone:=false;
  441.   While (not Alldone) and (not TreeFull) Do
  442.   Begin        {main loop}
  443.     {Find first unexpanded node.}
  444.     FindFirst(N);
  445.     IF N=Null then Alldone:=True Else
  446.     Begin      {expand node n}
  447.       Expand(N,Side,TreeFull,Nlevels);
  448.       If treefull then
  449.       begin    {reduce search depth}
  450.         Nlevels:=Nlevels-1;
  451.         writeln('GENTREE:Tree overflow.  Reducing plies temporarily to ',
  452.                nlevels);
  453.         LastFound:=Root; {reset this or the thing goes crazy}
  454.       end;     {reduce search depth}
  455.     end;       {expand node n}
  456.   End;         {main loop}
  457. Until Not Treefull;
  458. End;
  459.  
  460. Procedure GetInputMove(var Board:Ctype;Side:byte);
  461. {Gets latest move from human player.  Checks for validity of moves}
  462. Var Valid:boolean;
  463.     Row1,Row2,Column1,Column2:Integer;
  464.     EN:                       Integer;
  465. Begin
  466.   EN := 0; {No errors to start}
  467.   Repeat
  468.     Valid:=True;
  469.     If EN<>0 Then Writeln('Encountered error number ',EN);
  470.     EN := 0;
  471.     write(names[side]);
  472.     writeln(' moves.');
  473.     Writeln('Please enter the source and destination row & column for the',
  474.           ' piece to move.');
  475.     Write('(separate row/col with a space, like "3 3 2 3" ? ');
  476.     Readln(Row1,Column1,Row2,Column2);
  477.     {Check validity}
  478.     If (Row1<1) or (Row1>BD) or (Column1<1) or (Column1>BD) or (Row2<1) or
  479.        (Row2>BD) or (Column2<1) or (Column2>BD) Then
  480.                                      Begin Valid:=False;EN:=1;End;
  481.     If Valid Then If (board[Row1,Column1] <> Side)
  482.              Then Begin Valid:=False;En:=2;End;
  483.     {Check that the piece exists to move}
  484.     If Valid Then If MoveDirection[Side] <> (Row2-Row1)
  485.              Then Begin Valid := False; EN:=3;End;{Check Move direction}
  486.     If Valid Then If (Column1=Column2) And (board[Row2,Column2] <>
  487.               Neither) Then Begin Valid:=False; EN:=4;End;
  488.                {Must move forward to open square}
  489.     If Valid Then If (Column1<>Column2) and (board[Row2,Column2]
  490.               <> Opposite(Side)) Then Begin Valid:=False;EN:=5;End;
  491.                {Must attack enemy only}
  492.   Until Valid;
  493.   Writeln('Valid Move.');
  494.   {Register Change in Board}
  495.   Board[Row1,Column1] := Neither;
  496.   Board[Row2,Column2] := Side;
  497. End; {Procedure}
  498.  
  499. Procedure Minimax(Var Board:Ctype;Side:byte;BottomLevel:Integer);
  500. {Side is the side that is currently moving}
  501. Const
  502.   Maximum = 10;
  503.   Minimum = 11;
  504. Var
  505.   TempNV,N,Lev,F,NN:          Integer;
  506.   Temp:ctype;
  507.   MoveFound:boolean;
  508.  
  509.   Procedure FindOptOffspr(Node:Integer;Var BestValue,BestNode:Integer;
  510.                           Direction:Integer);
  511.   {Searches all offspring of node Node to find optimal (minimum or maximum)
  512.    G values.  Best G value and node where it was found are stored in BestValue
  513.    and BestNode.  Direction indicates whether to find minimum or maximum.}
  514.   Var
  515.     N,X,BestSoFar,BestNodeSoFar,Factor:            Integer;
  516.   Begin
  517.     N:=FirstOffspring(Node);
  518.     If Direction=Maximum Then BestSoFar :=-Maxint Else BestSoFar := Maxint;
  519.     BestNodeSoFar := Null;
  520.     While N<>Null Do    {Search all offspring}
  521.     Begin
  522.       X := GValue(N) ;
  523.       If ((X > BestSoFar) and (Direction=Maximum)) Or ((X < BestSoFar) and
  524.                                         (Direction=Minimum)) Then
  525.       Begin
  526.         BestSoFar := X;
  527.         BestNodeSoFar := N;
  528.       End;
  529.       N:=RSibling(N);
  530.     End; {Search all offspring}
  531.     BestNode := BestNodeSoFar;
  532.     BestValue := BestSoFar;
  533.     If BestNode = Null then Writeln('Warning: No moves found in',
  534.      ' FindOptOffspr');
  535.   End; {Procedure}
  536.  
  537. Begin
  538.   MoveFound:=False;
  539.   {First, evaluate the bottom level}
  540.   N := FirLevel(BottomLevel);
  541.   If N=Null Then Writeln('No bottom level (',bottomlevel,') found in ',
  542.                           'Minimax.');
  543.   While N<>Null Do
  544.   Begin
  545.     If GValue(N)=Unevaluated Then
  546.     begin
  547.       Characteristic(N,Temp);
  548.       if side=white then TempNV:=Evaluate1(Temp,Side)
  549.                     else TempNV:=Evaluate2(Temp,Side);
  550.       {black uses more aggressive approach}
  551.       SetGValue(N,TempNV);
  552.     end;
  553.     N := NNTL(N);
  554.   End;
  555.   {Now do the folding/pruning process}
  556.   Lev := Bottomlevel - 1;
  557.   While Lev >= 0 Do
  558.   Begin
  559.     {For each node on this level that is not already evaluated (nonterminal
  560.      nodes), fold the offspring values into the node.}
  561.     N := FirLevel(Lev);
  562.     While N<>Null Do
  563.     Begin
  564.       If Gvalue(N)=Unevaluated Then
  565.       Begin
  566.         {Alternate looking for maxima and minima}
  567.         If Odd(Lev) Then FindOptOffspr(N,F,NN,Minimum)
  568.                     Else FindOptOffspr(N,F,NN,Maximum);
  569.         {In case this isn't clear, recall (for instance) that the root is
  570.          at depth = 0.  Here, we examine the descendants of the root and
  571.          look for the maximum.}
  572.         SetGValue(N,F);
  573.         {If level = 0, this is the final pass.  Save the result.}
  574.         If Lev=0 Then
  575.         begin
  576.           Characteristic(NN,Board);
  577.           MoveFound:=true;
  578.         end;
  579.  
  580.       End;
  581.       N:=NNTL(N); {Get next node on this level}
  582.     End;          {Until finished with this level}
  583.     Lev := Lev - 1;
  584.   End; {Get next level}
  585.   If not movefound then writeln('No move found in Minimax.');
  586. End; {Procedure}
  587.  
  588.  
  589. Procedure ComputeAMove(var Board:Ctype;Side:byte;Nlevels:integer);
  590. Begin
  591.   writeln('Computing move for ',names[side],'.');
  592.   T1 := Time;
  593.   Gentree(Nlevels,Side); {Generates move tree}
  594.   Minimax(Board,Side,Nlevels); {Returns new board position}
  595.   T2 := Time;
  596.   Writeln('Computed value of move: ',GValue(Root),'.  ',NNodes,
  597.   ' examined.  Required ',(t2-t1):7:2,' seconds.');
  598. End;
  599.  
  600. Procedure MoveSide(Side:byte;var CurrentBoard:ctype);
  601. Var
  602.   NewBoard:                    Ctype;
  603.  
  604. {This routine moves the white pieces and checks for win}
  605. Begin
  606.   If Player[Side]=Human then
  607.     GetInputMove(CurrentBoard,Side)
  608.   Else
  609.     ComputeAMove(CurrentBoard,Side,Nlevels);
  610. End;
  611.  
  612.  
  613. Begin
  614.   debug[1]:=false;debug[2]:=true;debug[3]:=true;
  615.   Init(Currentboard);
  616.  
  617.   Write('How many plies to examine? ');Readln(Nlevels);
  618.   ChooseColor(player);  {which roles should computer play?}
  619.   ShowBoard(Currentboard);
  620.   Side:=White;
  621.   Repeat
  622.     MoveSide(Side,CurrentBoard);
  623.     ShowBoard(CurrentBoard);
  624.     WhoWon:=Won(CurrentBoard,Side);
  625.     Side:=Opposite(Side);
  626.   Until WhoWon<>Neither;
  627.  
  628.   Writeln(names[WhoWon],' has won.');
  629. End.
  630.  
  631. {Note 1: This is an interesting bug.  You have to do something to make the
  632.  program choose an earlier win rather than a later win.  If it has two paths
  633.  available, both of which guarantee win, it'll take whichever the first it
  634.  sees is.  There may be a one-move path to win, and a three-move path to win,
  635.  and it takes the three move path.  Note that it's not really a bug -- it will
  636.  win either way -- but it does look peculiar.  In fact, it looks a little
  637.  sadistic, choosing to let the victim "twist slowly in the wind."
  638.  
  639.  Similarly, once the program sees that it will lose for sure, it picks the
  640.  loss that is quickest.  This is dumb, so re-score losses to loss+depth}
  641.  
  642. ram sees that it will lose for sure, it picks t