
Task

Play a game of tic-tac-toe.

Ensure that legal moves are played and that a winning position is notified.


Tic-tac-toe   is also known as:

   naughts and crosses 
   tic tac toe 
   tick tack toe 
   three in a row 
   tres en rayo       and
   Xs  and  Os


See also
   MathWorld™, Tic-Tac-Toe game.
   Wikipedia tic-tac-toe.


:- use_module('min-max.pl').
 
:-dynamic box/2.
:- dynamic tic_tac_toe_window/1.
 
% Computer begins.
tic-tac-toe(computer) :-
	V is random(9),
	TTT = [_,_,_,_,_,_ ,_,_,_],
	nth0(V, TTT, o),
	display_tic_tac_toe(TTT).
 
% Player begins
tic-tac-toe(me) :-
	TTT = [_,_,_,_,_,_ ,_,_,_],
	display_tic_tac_toe(TTT).
 
 
display_tic_tac_toe(TTT) :-
	retractall(box(_,_)),
	retractall(tic_tac_toe_window(_)),
	new(D, window('Tic-tac-Toe')),
	send(D, size, size(170,170)),
	X = 10, Y = 10,
	display(D, X, Y, 0, TTT),
	assert(tic_tac_toe_window(D)),
	send(D, open).
 
display(_, _, _, _, []).
 
display(D, X, Y, N, [A,B,C|R]) :-
	display_line(D, X, Y, N, [A,B,C]),
	Y1 is Y+50,
	N3 is N+3,
	display(D, X, Y1, N3, R).
 
 
display_line(_, _, _, _, []).
display_line(D, X, Y, N, [C|R]) :-
	(   nonvar(C)-> C1 = C; C1 = ' '),
	new(B, tic_tac_toe_box(C1)),
	assertz(box(N, B)),
	send(D, display, B, point(X, Y)),
	X1 is X + 50,
	N1 is N+1,
	display_line(D, X1, Y, N1, R).
 
 
 
% class tic_tac_toe_box
% display an 'x' when the player clicks
% display an 'o' when the computer plays
:- pce_begin_class(tic_tac_toe_box, box, "Graphical window with text").
 
variable(mess, any, both, "text to display").
 
initialise(P, Lbl) :->
	send(P, send_super, initialise),
	send(P, slot, mess, Lbl),
	WS = 50, HS = 50,
	send(P, size, size(WS,HS)),
	send(P, recogniser,
	     handler_group(new(click_gesture(left,
					     '',
					     single,
					     message(@receiver, my_click))))).
 
% the box is clicked
my_click(B) :->
	send(B, set_val, x),
	send(@prolog, play).
 
% only works when the box is "free"
set_val(B, Val) :->
	get(B, slot, mess, ' '),
	send(B, slot, mess, Val),
	send(B, redraw),
	send(B, flush).
 
 
%  redefined method to display custom graphical objects.
'_redraw_area'(P, A:area) :->
	send(P, send_super, '_redraw_area', A),
	%we display the text
	get(P, slot, mess, Lbl),
	new(Str1, string(Lbl)),
	get_object(P, area, area(X,Y,W,H)),
	send(P, draw_box, X, Y, W, H),
	send(P, draw_text, Str1,
		font(times, normal, 30),
		X, Y, W, H, center, center).
 
:- pce_end_class.
 
play :-
	numlist(0, 8, L),
	maplist(init, L, TTT),
	finished(x, TTT, Val),
	(   Val = 2 -> send(@display, inform,'You win !'),
	               tic_tac_toe_window(D),
		       send(D, destroy)
	;   (	Val = 1 -> send(@display, inform,'Draw !'),
	                   tic_tac_toe_window(D),
		           send(D, destroy)
	    ;	next_move(TTT, TT1),
		maplist(display, L, TT1),
		finished(o, TT1, V),
		(   V = 2 -> send(@display, inform,'I win !'),
			     tic_tac_toe_window(D),
		             send(D, destroy)
		;   (	V = 1 -> send(@display, inform,'Draw !'),
		                 tic_tac_toe_window(D),
			         send(D, destroy)
		    ;	true)))).
 
 
% use minmax to compute the next move
next_move(TTT, TT1) :-
	minimax(o, 0, 1024, TTT, _V1- TT1).
 
 
% we display the new board
display(I, V) :-
	nonvar(V),
	box(I, V1),
	send(V1, set_val, V).
 
display(_I, _V).
 
% we create the board for minmax
init(I, V) :-
	box(I, V1),
	get(V1, slot, mess, V),
	V \= ' '.
 
init(_I, _V).
 
% winning position for the player P ?
winned(P, [A1, A2, A3, A4, A5, A6, A7, A8, A9]) :-
       (is_winning_line(P, [A1, A2, A3]);
	is_winning_line(P, [A4, A5, A6]);
	is_winning_line(P, [A7, A8, A9]);
	is_winning_line(P, [A1, A4, A7]);
	is_winning_line(P, [A2 ,A5, A8]);
	is_winning_line(P, [A3, A6, A9]);
	is_winning_line(P, [A1, A5, A9]);
	is_winning_line(P, [A3, A5, A7])).
 
 
is_winning_line(P, [A, B, C]) :-
	nonvar(A), A = P,
	nonvar(B), B = P,
	nonvar(C), C = P.
 
% Winning position for the player
eval(Player, Deep, TTT, V) :-
	winned(Player, TTT),
	(   Player = o -> V is 1000 - 50 * Deep; V is -1000+ 50 * Deep).
 
% Loosing position for the player
eval(Player, Deep, TTT, V) :-
	select(Player, [o,x], [Player1]),
	winned(Player1, TTT),
	(   Player = x -> V is 1000 - 50 * Deep; V is -1000+ 50 * Deep).
 
% Draw position
eval(_Player, _Deep, TTT, 0) :-
	include(var, TTT, []).
 
 
% we fetch the free positions of the board
possible_move(TTT, LMove) :-
	new(C, chain),
	forall(between(0,8, I),
	       (   nth0(I, TTT, X),
		   (   var(X) -> send(C, append, I); true))),
	chain_list(C, LMove).
 
% we create the new position when the player P clicks
% the box "N"
assign_move(P, TTT, N, TT1) :-
	copy_term(TTT, TT1),
	nth0(N, TT1, P).
 
% We fetch all the possible boards obtained from board TTT
% for the player P
get_next(Player, Deep, TTT, Player1, Deep1, L):-
	possible_move(TTT, LMove),
	select(Player, [o,x], [Player1]),
	Deep1 is Deep + 1,
	maplist(assign_move(Player, TTT), LMove, L).
 
 
% The game is over ?
% Player P wins
finished(P, TTT, 2) :-
	winned(P, TTT).
 
% Draw
finished(_P, TTT, 1) :-
	include(var, TTT, []).
 
% the game is not over
finished(_P, _TTT, 0) .
 
% minmax must knows when the computer plays
% (o for ordinateur in French)
computer(o).
 
 