tic tac toe
TRANSCRIPT
maximizing('x'). minimizing('o').
%%% the player playing x is always trying to maximize the utility of the board position %%% the player playing o is always trying to minimize the utility of the board position
corner_square(1, 1). %%% map corner squares to board squares corner_square(2, 3). corner_square(3, 7). corner_square(4, 9). run :hello, play(1), goodbye. run :goodbye. hello :initialize, cls, nl, nl, nl, write('Welcome to Tic-Tac-Toe.'), read_players, output_players. initialize :random_seed, blank_mark(E), asserta( board([E,E,E, E,E,E, E,E,E]) ). %%% create a blank board %%% use current time to initialize random number generator %%% Display welcome message, initialize game %%% Play the game starting with player 1 %%% Display end of game message
goodbye :board(B), nl,
nl, write('Game over: '), output_winner(B), retract(board(_)), retract(player(_,_)), read_play_again(V), !, (V == 'Y' ; V == 'y'), !, run. 8 read_play_again(V) :nl, nl, write('Play again (Y/N)? '), read(V), (V == 'y' ; V == 'Y' ; V == 'n' ; V == 'N'), !. read_play_again(V) :nl, nl, write('Please enter Y or N.'), read_play_again(V). read_players :nl, nl, write('Number of human players? '), read(N), ayers(0) :asserta( player(1, computer) ), asserta( player(2, computer) ), !. set_players(1) :nl,
write('Is human playing X or O (X moves first)? '), read(M), human_playing(M), !. set_players(2) :asserta( player(1, human) ), asserta( player(2, human) ), !. set_players(N) :nl, write('Please enter 0, 1, or 2.'), read_players. human_playing(M) :(M == 'x' ; M == 'X'), asserta( player(1, human) ), asserta( player(2, computer) ), !. human_playing(M) :(M == 'o' ; M == 'O'), asserta( player(1, computer) ), asserta( player(2, human) ), !. human_playing(M) :nl, write('Please enter X or O.'), set_players(1). play(P) :board(B), !, output_board(B), !, not(game_over(P, B)), !, make_move(P, B), !, next_player(P, P2), !, play(P2), !.
215 %.......................................
216 % square
217 %.......................................
218 % The mark in a square(N) corresponds to an item in a list, as follows:
219
220 square([M,_,_,_,_,_,_,_,_],1,M).
221 square([_,M,_,_,_,_,_,_,_],2,M).
222 square([_,_,M,_,_,_,_,_,_],3,M).
223 square([_,_,_,M,_,_,_,_,_],4,M).
224 square([_,_,_,_,M,_,_,_,_],5,M).
225 square([_,_,_,_,_,M,_,_,_],6,M).
226 square([_,_,_,_,_,_,M,_,_],7,M).
227 square([_,_,_,_,_,_,_,M,_],8,M).
228 square([_,_,_,_,_,_,_,_,M],9,M).
231 %.......................................
232 % win
233 %.......................................
234 % Players win by having their mark in one of the following square configurations:
235 % win([M,M,M, _,_,_, _,_,_],M). win([_,_,_, M,M,M, _,_,_],M). win([_,_,_, _,_,_, M,M,M],M). win([M,_,_, M,_,_, M,_,_],M). win([_,M,_, _,M,_, _,M,_],M). win([_,_,M, _,_,M, _,_,M],M). win([M,_,_, _,M,_, _,_,M],M). win([_,_,M, _,M,_, M,_,_],M).
247 %.......................................
248 % move
249 %.......................................
250 % applies a move on the given board
251 % (put mark M in square S on board B and return the resulting board B2)
252 % move(B,S,M,B2) :set_item(B,S,M,B2).
259 %.......................................
260 % game_over
261 %.......................................
262 % determines when the game is over
263 % game_over(P, B) :game_over2(P, B). game_over2(P, B) :opponent_mark(P, M), %%% game is over if opponent wins win(B, M). game_over2(P, B) :blank_mark(E), not(square(B,S,E)). %%% game is over if opponent wins
279 %.......................................
280 % make_move
281 %.......................................
282 % requests next move from human/computer,
283 % then applies that move to the given board
284 %
make_move(P, B) :player(P, Type), make_move2(Type, P, B, B2),
retract( board(_) ), asserta( board(B2) ). make_move2(human, P, B, B2) :nl, nl, write('Player '), write(P), write(' move? '), read(S),
blank_mark(E), square(B, S, E), player_mark(P, M), move(B, S, M, B2), !.
make_move2(human, P, B, B2) :nl, nl, write('Please select a numbered square.'), make_move2(human,P,B,B2). make_move2(computer, P, B, B2) :nl, nl, write('Computer is thinking about next move...'), player_mark(P, M), minimax(0, B, M, S, U),
move(B,S,M,B2),
nl,
nl,
write('Computer places '),
write(M),
write(' in square '),
write(S),
write('.').
334 %.......................................
335 % moves
336 %.......................................
337 % retrieves a list of available moves (empty squares) on a board.
338 % moves(B,L) :not(win(B,x)), %%% if either player already won, then there are no available moves
not(win(B,o)), blank_mark(E), findall(N, square(B,N,E), L), L \= [].
349 %.......................................
350 % utility
351 %.......................................
352 % determines the value of a given board position
353 % utility(B,U) :win(B,'x'), U = 1,!. utility(B,U) :win(B,'o'), U = (-1), !. utility(B,U) :U = 0.
372 %.......................................
373 % minimax
374 %.......................................
375 % The minimax algorithm always assumes an optimal opponent.
376 % For tic-tac-toe, optimal play will always result in a tie, so the algorithm is effectively playing not-to-lose.
377
378 % For the opening move against an optimal player, the best minimax can ever hope for is a tie.
379 % So, technically speaking, any opening move is acceptable.
380 % Save the user the trouble of waiting for the computer to search the entire minimax tree
381 % by simply selecting a random square. minimax(D,[E,E,E, E,E,E, E,E,E],M,S,U) :blank_mark(E), random_int_1n(9,S), !. minimax(D,B,M,S,U) :D2 is D + 1, moves(B,L), !, %%% get the list of available moves
best(D2,B,M,L,S,U), %%% recursively determine the best available move !.
397 % if there are no more available moves,
398 % then the minimax value is the utility of the given board position minimax(D,B,M,S,U) :utility(B,U).
405 %.......................................
406 % best
407 %.......................................
408 % determines the best move in a given list of moves by recursively calling minimax
409 %
410
411 % if there is only one move left in the list... best(D,B,M,[S1],S,U) :move(B,S1,M,B2), inverse_mark(M,M2), !, minimax(D,B2,M2,_S,U), %%% then recursively search for the utility value of that move. S = S1, !, output_value(D,S,U), %%% apply that move to the board,
420
!.
423 % if there is more than one move in the list... best(D,B,M,[S1|T],S,U) :move(B,S1,M,B2), inverse_mark(M,M2), !, minimax(D,B2,M2,_S,U1), %%% recursively search for the utility value of that move, %%% apply the first move (in the list) to the board,
best(D,B,M,T,S2,U2), output_value(D,S1,U1),
%%% determine the best move of the remaining moves,
better(D,M,S1,U1,S2,U2,S,U) %%% and choose the better of the two moves (based on their respective utility values).
436 %.......................................
437 % better
438 %.......................................
439 % returns the better of two moves based on their respective utility values.
440 %
441 % if both moves have the same utility value, then one is chosen at random.
better(D,M,S1,U1,S2,U2, maximizing(M), U1 > U2, S = S1, U = U1, !. better(D,M,S1,U1,S2,U2, minimizing(M), U1 < U2, S = S1, U = U1, !. better(D,M,S1,U1,S2,U2, U1 == U2,
S,U) :%%% if the player is maximizing %%% then greater is better.
S,U) :%%% if the player is minimizing, %%% then lesser is better.
S,U) :%%% if moves have equal utility,
random_int_1n(10,R),
%%% then pick one of them at random
better2(D,R,M,S1,U1,S2,U2,S,U), !.
466 better(D,M,S1,U1,S2,U2, S = S2, U = U2, !.
S,U) :-
%%% otherwise, second move is better
473 %.......................................
474 % better2
475 %.......................................
476 % randomly selects two squares of the same utility value given a single probability
477 % 9 better2(D,R,M,S1,U1,S2,U2, S,U) :R < 6, S = S1, U = U1,
!. better2(D,R,M,S1,U1,S2,U2, S,U) :S = S2, U = U2, !.
494 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495 %%% OUTPUT
496 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
output_players :-
nl,
player(1, V1),
write('Player 1 is '), %%% either human or computer
write(V1), nl,
player(2, V2),
write('Player 2 is '), %%% either human or computer
write(V2),
!.
output_winner(B) :-
win(B,x),
write('X wins.'), !.
output_winner(B) :win(B,o),
write('O wins.'), !.
output_winner(B) :-
write('No winner.').
output_board(B) :-
nl,
nl,
output_square(B,1),
write('|'),
output_square(B,2),
write('|'),
output_square(B,3),
nl,
write('-----------'),
nl, output_square(B,4),
write('|'),
output_square(B,5),
write('|'),
output_square(B,6),
nl,
write('-----------'),
nl,
output_square(B,7),
write('|'),
output_square(B,8),
write('|'),
output_square(B,9), !.
output_board :-
board(B),
output_board(B), !.
output_square(B,S) :-
square(B,S,M),
write(' '),
output_square2(S,M),
write(' '), !.
output_square2(S, E) :-
blank_mark(E),
write(S), !.
%%% if square is empty, output the square number
output_square2(S, M) :-
write(M), !.
%%% if square is marked, output the mark
output_value(D,S,U) :-
D == 1,
nl,
write('Square '),
write(S),
write(', utility: '),
write(U), !.
585 output_value(D,S,U) :-
586
true.
591 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
592 %%% PSEUDO-RANDOM NUMBERS
593 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
594
595 %.......................................
596 % random_seed
597 %.......................................
598 % Initialize the random number generator...
599 % If no seed is provided, use the current time
600 %
random_seed :-
random_seed(_),
!.
random_seed(N) :-
nonvar(N),
% Do nothing, SWI-Prolog does not support seeding the random number generator
!.
random_seed(N) :-
var(N),
615 % Do nothing, SWI-Prolog does not support seeding the random number generator
616
!.
619 /*****************************************
620 OTHER COMPILER SUPPORT
621 ******************************************
622
623 arity_prolog___random_seed(N) :-
624
nonvar(N),
625
randomize(N),
626
!
627
.
628
629 arity_prolog___random_seed(N) :-
630
var(N),
631
time(time(Hour,Minute,Second,Tick)),
632
N is ( (Hour+1) * (Minute+1) * (Second+1) * (Tick+1)),
633
randomize(N),
634
!
635
.
636
637 ******************************************/
638
639
640
641 %.......................................
642 % random_int_1n
643 %.......................................
644 % returns a random integer from 1 to N
645 %
random_int_1n(N, V) :-
V is random(N) + 1,
!.
651 /*****************************************
652 OTHER COMPILER SUPPORT
653 ******************************************
654
655 arity_prolog___random_int_1n(N, V) :-
656
R is random,
657
V2 is (R * N) - 0.5,
658
float_text(V2,V3,fixed(0)),
659
int_text(V4,V3),
660
V is V4 + 1,
661
!.
664 ******************************************/
665
666
667 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
668 %%% LIST PROCESSING
669 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
member([V|T], V).
member([_|T], V) :- member(T,V).
append([], L, L).
append([H|T1], L2, [H|T3]) :- append(T1, L2, T3).
678 %.......................................
679 % set_item
680 %.......................................
681 % Given a list L, replace the item at position N with V
682 % return the new list in list L2
683 %
set_item(L, N, V, L2) :-
set_item2(L, N, V, 1, L2).
set_item2( [], N, V, A, L2) :-
N == -1,
L2 = []. set_item2( [_|T1], N, V, A, [V|T2] ) :-
A = N, A1 is N + 1, set_item2( T1, -1, V, A1, T2 ).
set_item2( [H|T1], N, V, A, [H|T2] ) :-
A1 is A + 1,
set_item2( T1, N, V, A1, T2 ).
get_item(L, N, V) :get_item2(L, N, 1, V).
get_item2( [], _N, _A, V) :-
V = [], !, fail. get_item2( [H|_T], N, A, V) :A = N, V = H.
get_item2( [_|T], N, A, V) :A1 is A + 1,
get_item2( T, N, A1, V).