212 lines
5.7 KiB
ObjectPascal
212 lines
5.7 KiB
ObjectPascal
program Tic(Input, Output);
|
|
|
|
type
|
|
Contents = (Unassigned, Human, Computer);
|
|
var
|
|
BestI, BestJ: integer; { best solution a depth of zero in the search }
|
|
B: array[0..2, 0..2] of Contents; {zero based so modulus works later}
|
|
Player: Contents;
|
|
|
|
procedure DisplayBoard;
|
|
var
|
|
I, J: integer;
|
|
T: array [Contents] of char;
|
|
begin
|
|
T[Unassigned] := ' ';
|
|
T[Human] := 'O';
|
|
T[Computer] := 'X';
|
|
for I := 0 to 2 do
|
|
begin
|
|
for J := 0 to 2 do
|
|
begin
|
|
Write(T[B[I, J]]);
|
|
if J <> 2 then
|
|
Write(' | ');
|
|
end;
|
|
WriteLn;
|
|
if I < 2 then
|
|
WriteLn('---------');
|
|
end;
|
|
WriteLn;
|
|
WriteLn;
|
|
end;
|
|
|
|
function SwapPlayer(Player: Contents): Contents;
|
|
begin
|
|
if Player = Computer then
|
|
SwapPlayer := Human
|
|
else
|
|
SwapPlayer := Computer;
|
|
end;
|
|
|
|
function CheckWinner: Contents;
|
|
var
|
|
I: integer;
|
|
begin
|
|
CheckWinner := Unassigned; { no winner yet }
|
|
for I := 0 to 2 do
|
|
begin
|
|
{ first horizontal solution }
|
|
if (CheckWinner = Unassigned) and (B[I, 0] <> Unassigned) and
|
|
(B[I, 1] = B[I, 0]) and (B[I, 2] = B[I, 0]) then
|
|
CheckWinner := B[I, 0]
|
|
else
|
|
{ now vertical solution }
|
|
if (CheckWinner = Unassigned) and (B[0, I] <> Unassigned) and
|
|
(B[1, I] = B[0, I]) and (B[2, I] = B[0, I]) then
|
|
CheckWinner := B[0, I];
|
|
end;
|
|
{ now check the paths of the two cross line slants that share the middle position }
|
|
if (CheckWinner = Unassigned) and (B[1, 1] <> Unassigned) then
|
|
begin
|
|
if (B[1, 1] = B[0, 0]) and (B[2, 2] = B[0, 0]) then
|
|
CheckWinner := B[0, 0]
|
|
else if (B[1, 1] = B[2, 0]) and (B[0, 2] = B[1, 1]) then
|
|
CheckWinner := B[1, 1];
|
|
end;
|
|
end;
|
|
|
|
{ Basic strategy test - is this te best solution we have seen }
|
|
function SaveBest(CurScore, CurBest: Contents): boolean;
|
|
begin
|
|
if CurScore = CurBest then
|
|
SaveBest := False
|
|
else if (CurScore = Unassigned) and (CurBest = Human) then
|
|
SaveBest := False
|
|
else if (CurScore = Computer) and ((CurBest = Unassigned) or
|
|
(CurBest = Human)) then
|
|
SaveBest := False
|
|
else
|
|
SaveBest := True;
|
|
end;
|
|
|
|
|
|
{ Basic strategy - recursive depth first search of possible moves
|
|
if computer can win save it, otherwise block if need be, else do deeper.
|
|
At each level modify the board for the next call, but clean up as go back up,
|
|
by remembering the modified position on the call stack. }
|
|
function TestMove(Val: Contents; Depth: integer): Contents;
|
|
var
|
|
I, J: integer;
|
|
Score, Best, Changed: Contents;
|
|
begin
|
|
Best := Computer;
|
|
Changed := Unassigned;
|
|
Score := CheckWinner;
|
|
if Score <> Unassigned then
|
|
begin
|
|
if Score = Val then
|
|
TestMove := Human
|
|
else
|
|
TestMove := Computer;
|
|
end
|
|
else
|
|
begin
|
|
for I := 0 to 2 do
|
|
for J := 0 to 2 do
|
|
begin
|
|
if B[I, J] = Unassigned then
|
|
begin
|
|
Changed := Val;
|
|
B[I, J] := Val;
|
|
{ the value for now and try wioth the other player }
|
|
Score := TestMove(SwapPlayer(Val), Depth + 1);
|
|
if Score <> Unassigned then
|
|
Score := SwapPlayer(Score);
|
|
B[I, J] := Unassigned;
|
|
if SaveBest(Score, Best) then
|
|
begin
|
|
if Depth = 0 then
|
|
begin { top level, so remember actual position }
|
|
BestI := I;
|
|
BestJ := J;
|
|
end;
|
|
Best := Score;
|
|
end;
|
|
end;
|
|
end;
|
|
if Changed <> Unassigned then
|
|
TestMove := Best
|
|
else
|
|
TestMove := Unassigned;
|
|
end;
|
|
end;
|
|
|
|
function PlayGame(Whom: Contents): string;
|
|
var
|
|
I, J, K, Move: integer;
|
|
Win: Contents;
|
|
begin
|
|
Win := Unassigned;
|
|
for I := 0 to 2 do
|
|
for J := 0 to 2 do
|
|
B[I, J] := Unassigned;
|
|
WriteLn('The board positions are numbered as follows:');
|
|
WriteLn('1 | 2 | 3');
|
|
WriteLn('---------');
|
|
WriteLn('4 | 5 | 6');
|
|
WriteLn('---------');
|
|
WriteLn('7 | 8 | 9');
|
|
WriteLn('You have O, I have X.');
|
|
WriteLn;
|
|
K := 1;
|
|
repeat {rather a for loop but can not have two actions or early termination in Pascal}
|
|
if Whom = Human then
|
|
begin
|
|
repeat
|
|
Write('Your move: ');
|
|
ReadLn(Move);
|
|
if (Move < 1) or (Move > 9) then
|
|
WriteLn('Opps: enter a number between 1 - 9.');
|
|
Dec(Move);
|
|
{humans do 1 -9, but the computer wants 0-8 for modulus to work}
|
|
I := Move div 3; { convert from range to corridinated of the array }
|
|
J := Move mod 3;
|
|
if B[I, J] <> Unassigned then
|
|
WriteLn('Opps: move ', Move + 1, ' was already done.')
|
|
until (Move >= 0) and (Move <= 8) and (B[I, J] = Unassigned);
|
|
B[I, J] := Human;
|
|
end;
|
|
if Whom = Computer then
|
|
begin
|
|
{ randomize if computer opens, so its not always the same game }
|
|
if K = 1 then
|
|
begin
|
|
BestI := Random(3);
|
|
BestJ := Random(3);
|
|
end
|
|
else
|
|
Win := TestMove(Computer, 0);
|
|
B[BestI, BestJ] := Computer;
|
|
WriteLn('My move: ', BestI * 3 + BestJ + 1);
|
|
end;
|
|
DisplayBoard;
|
|
Win := CheckWinner;
|
|
if Win <> Unassigned then
|
|
begin
|
|
if Win = Human then
|
|
PlayGame := 'You win.'
|
|
else
|
|
PlayGame := 'I win.';
|
|
end
|
|
else
|
|
begin
|
|
Inc(K); { "for" loop counter actions }
|
|
Whom := SwapPlayer(Whom);
|
|
end;
|
|
until (Win <> Unassigned) or (K > 9);
|
|
if Win = Unassigned then
|
|
PlayGame := 'A draw.';
|
|
end;
|
|
|
|
begin
|
|
Randomize;
|
|
Player := Human;
|
|
while True do
|
|
begin
|
|
WriteLn(PlayGame(Player));
|
|
WriteLn;
|
|
Player := SwapPlayer(Player);
|
|
end
|
|
end.
|