//
// Tests various properties of a tic-tac-toe gameboard
//

.type Mark <: symbol

.decl player(x:Mark)

player("X").
player("O").

.decl cell(x:Mark)
cell(" ").
cell("X").
cell("O").

.decl other(a:Mark,b:Mark)
other("X","O").
other("O","X").


// cross product of all possible states (not considering whether they actually can be reached)

.decl all_states(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark,

	p:Mark
)

all_states( a1,b1,c1,a2,b2,c2,a3,b3,c3, p ) :-
	cell(a1), cell(b1), cell(c1),
	cell(a2), cell(b2), cell(c2),
	cell(a3), cell(b3), cell(c3),
	player(p).



// list of states actually reachable through valid moves

.decl reachable_state(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark,

	p:Mark
)

.decl state_succ(
	aa1:Mark, aa2:Mark, aa3:Mark,
	ab1:Mark, ab2:Mark, ab3:Mark,
	ac1:Mark, ac2:Mark, ac3:Mark,
	a:Mark,

	ba1:Mark, ba2:Mark, ba3:Mark,
	bb1:Mark, bb2:Mark, bb3:Mark,
	bc1:Mark, bc2:Mark, bc3:Mark,
	b:Mark
)

// -- rules for all states --

// initial state (X or O starts)
reachable_state( " ", " ", " ",  " ", " ", " ",  " ", " ", " ",  "X").
reachable_state( " ", " ", " ",  " ", " ", " ",  " ", " ", " ",  "0").

// all reachable states
reachable_state( a1,b1,c1,a2,b2,c2,a3,b3,c3, p ) :- state_succ( _,_,_, _,_,_, _,_,_, _,   a1,b1,c1, a2,b2,c2, a3,b3,c3, p ).


// -- rules for successor states --

// some utilitiy definitions

.decl row1( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )
.decl row2( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )
.decl row3( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )

.decl col1( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )
.decl col2( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )
.decl col3( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )

.decl dig1( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )
.decl dig2( a1:Mark, a2:Mark, a3:Mark, b1:Mark, b2:Mark, b3:Mark, c1:Mark, c2:Mark, c3:Mark,    a:Mark, b:Mark, c:Mark )


row1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  a1,b1,c1 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).
row2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  a2,b2,c2 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).
row3( a1,b1,c1,a2,b2,c2,a3,b3,c3,  a3,b3,c3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).

col1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  a1,a2,a3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).
col2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  b1,b2,b3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).
col3( a1,b1,c1,a2,b2,c2,a3,b3,c3,  c1,c2,c3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).

dig1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  a1,b2,c3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).
dig2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  c1,b2,a3 ) :- all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_).


// ------------------------------------

.decl has_won(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark,

	x:Mark
)

has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- row1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).
has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- row2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).
has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- row3( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).

has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- col1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).
has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- col2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).
has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- col3( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).

has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- dig1( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).
has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, x ) :- dig2( a1,b1,c1,a2,b2,c2,a3,b3,c3,  x,x,x ).


// test whether a game is won (by eather side):

.decl won(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark
)

won( a1,b1,c1,a2,b2,c2,a3,b3,c3 ) :- has_won(a1,b1,c1,a2,b2,c2,a3,b3,c3,"X").
won( a1,b1,c1,a2,b2,c2,a3,b3,c3 ) :- has_won(a1,b1,c1,a2,b2,c2,a3,b3,c3,"O").


// test whether a game is a draw

.decl draw(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark
)

draw( a1,b1,c1,a2,b2,c2,a3,b3,c3 ) :-
	all_states(a1,b1,c1,a2,b2,c2,a3,b3,c3,_),
	!won(a1,b1,c1,a2,b2,c2,a3,b3,c3),
	a1 != " ", b1 != " ", c1 != " ",
	a2 != " ", b2 != " ", c2 != " ",
	a3 != " ", b3 != " ", c3 != " ".


// test whether a game is finished

.decl finished(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark
)

finished( a1,b1,c1,a2,b2,c2,a3,b3,c3 ) :- won(a1,b1,c1,a2,b2,c2,a3,b3,c3).
finished( a1,b1,c1,a2,b2,c2,a3,b3,c3 ) :- draw(a1,b1,c1,a2,b2,c2,a3,b3,c3).


// successor relation


.decl valid_move(
	aa1:Mark, aa2:Mark, aa3:Mark,
	ab1:Mark, ab2:Mark, ab3:Mark,
	ac1:Mark, ac2:Mark, ac3:Mark,
	a:Mark,

	ba1:Mark, ba2:Mark, ba3:Mark,
	bb1:Mark, bb2:Mark, bb3:Mark,
	bc1:Mark, bc2:Mark, bc3:Mark
)

valid_move( " ",ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3,  a,   a ,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3 ) :- reachable_state(" ",ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, a).
valid_move( aa1," ",ac1,aa2,ab2,ac2,aa3,ab3,ac3,  a,  aa1, a ,ac1,aa2,ab2,ac2,aa3,ab3,ac3 ) :- reachable_state(aa1," ",ac1,aa2,ab2,ac2,aa3,ab3,ac3, a).
valid_move( aa1,ab1," ",aa2,ab2,ac2,aa3,ab3,ac3,  a,  aa1,ab1, a ,aa2,ab2,ac2,aa3,ab3,ac3 ) :- reachable_state(aa1,ab1," ",aa2,ab2,ac2,aa3,ab3,ac3, a).
valid_move( aa1,ab1,ac1," ",ab2,ac2,aa3,ab3,ac3,  a,  aa1,ab1,ac1, a ,ab2,ac2,aa3,ab3,ac3 ) :- reachable_state(aa1,ab1,ac1," ",ab2,ac2,aa3,ab3,ac3, a).
valid_move( aa1,ab1,ac1,aa2," ",ac2,aa3,ab3,ac3,  a,  aa1,ab1,ac1,aa2, a ,ac2,aa3,ab3,ac3 ) :- reachable_state(aa1,ab1,ac1,aa2," ",ac2,aa3,ab3,ac3, a).
valid_move( aa1,ab1,ac1,aa2,ab2," ",aa3,ab3,ac3,  a,  aa1,ab1,ac1,aa2,ab2, a ,aa3,ab3,ac3 ) :- reachable_state(aa1,ab1,ac1,aa2,ab2," ",aa3,ab3,ac3, a).
valid_move( aa1,ab1,ac1,aa2,ab2,ac2," ",ab3,ac3,  a,  aa1,ab1,ac1,aa2,ab2,ac2, a ,ab3,ac3 ) :- reachable_state(aa1,ab1,ac1,aa2,ab2,ac2," ",ab3,ac3, a).
valid_move( aa1,ab1,ac1,aa2,ab2,ac2,aa3," ",ac3,  a,  aa1,ab1,ac1,aa2,ab2,ac2,aa3, a ,ac3 ) :- reachable_state(aa1,ab1,ac1,aa2,ab2,ac2,aa3," ",ac3, a).
valid_move( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3," ",  a,  aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3, a  ) :- reachable_state(aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3," ", a).


//
// Note: the finished clause is ignored due to the recursive evaluation order.
//	already finished games are not filtered properly
//
state_succ(  aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, a,  ba1,bb1,bc1,ba2,bb2,bc2,ba3,bb3,bc3, b )
	:- reachable_state( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, a ), other(a,b), !finished( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3 ),
	   valid_move( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, a,  ba1,bb1,bc1,ba2,bb2,bc2,ba3,bb3,bc3 ).


// now search for states that are not finished games but in any way one of the players has already won

// mapping a state to a potential winner
.decl possible_results(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark,

	p:Mark,

	w:Mark
)


possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, p, "X") :- reachable_state(a1,b1,c1,a2,b2,c2,a3,b3,c3, p), has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, "X" ).
possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, p, "O") :- reachable_state(a1,b1,c1,a2,b2,c2,a3,b3,c3, p), has_won( a1,b1,c1,a2,b2,c2,a3,b3,c3, "O" ).


possible_results( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, ap, x) :- state_succ( aa1,ab1,ac1,aa2,ab2,ac2,aa3,ab3,ac3, ap, ba1,bb1,bc1,ba2,bb2,bc2,ba3,bb3,bc3, bp ),
							possible_results( ba1,bb1,bc1,ba2,bb2,bc2,ba3,bb3,bc3, bp, x).


// search for game states where only one player can win any more but not within a single step

.decl decided_games(
	a1:Mark, a2:Mark, a3:Mark,
	b1:Mark, b2:Mark, b3:Mark,
	c1:Mark, c2:Mark, c3:Mark,

	p:Mark,

	w:Mark
)
.output decided_games()

decided_games( a1,b1,c1,a2,b2,c2,a3,b3,c3, "O", "X" ) :-
	!won(a1,b1,c1,a2,b2,c2,a3,b3,c3),
	possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, "O", "X" ),
	!possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, "O", "O" ).


decided_games( a1,b1,c1,a2,b2,c2,a3,b3,c3, "X", "O" ) :-
	!won(a1,b1,c1,a2,b2,c2,a3,b3,c3),
	possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, "X", "O" ),
	!possible_results( a1,b1,c1,a2,b2,c2,a3,b3,c3, "X", "X" ).



