% noughts and crosses game

:- discontiguous( rule/3 ).
:- dynamic( sumvec/1 ).
:- dynamic( current/1 ).
:- dynamic( x_won/0 ).
:- dynamic( o_won/0 ).

blank( [b, b, b, b,  b, b, b, b,    b, b, b, b,    b, b, b, b] ).  

% horizontal
row( [o, o, o, o,    b, b, b, b,    b, b, b, b,    b, b, b, b] ).
row( [b, b, b, b,    o, o, o, o,    b, b, b, b,    b, b, b, b] ).
row( [b, b, b, b,    b, b, b, b,    o, o, o, o,    b, b, b, b] ).
row( [b, b, b, b,    b, b, b, b,    b, b, b, b,    o, o, o, o] ).

% vertical
row( [o, b, b, b,    o, b, b, b,    o, b, b, b,    o, b, b, b] ).
row( [b, o, b, b,    b, o, b, b,    b, o, b, b,    b, o, b, b] ).
row( [b, b, o, b,    b, b, o, b,    b, b, o, b,    b, b, o, b] ).
row( [b, b, b, o,    b, b, b, o,    b, b, b, o,    b, b, b, o] ).

% diagonal
row( [o, b, b, b,    b, o, b, b,    b, b, o, b,    b, b, b, o] ).
row( [b, b, b, o,    b, b, o, b,    b, o, b, b,    o, b, b, b] ).

% e.g.
current( [o, o, b, x, x, b, b, b, b] ).

%
pickoutrow( [], [], [] ) :- !.
pickoutrow( [o|Rt], [Ch|Ct], [Ch|A] ) :- !, pickoutrow( Rt, Ct, A ).
pickoutrow( [b|Rt], [Ch|Ct], A ) :- !, pickoutrow( Rt, Ct, A ).

count_symb_inner( [], A, A, S ).   
count_symb_inner( [S|T], A, R, S ) :- !, A1 is A + 1, count_symb_inner( T, A1, R, S ).
count_symb_inner( [_|T], A, R, S ) :- count_symb_inner( T, A, R, S ).

count_symb( L, R, S ) :- count_symb_inner( L, 0, R, S ).

add_in_blank( Sym, [], [], [] ).
add_in_blank( Sym, [o|Rt], [b|B], [Sym|B2] ) :- !, add_in_blank( Sym, Rt, B, B2 ).
add_in_blank( Sym, [_|Rt], [H|B], [H|B2] ) :- add_in_blank( Sym, Rt, B , B2 ).

% GETTING THE WINNING LINE (always gives o tho')
% ----------------------------------------------

winning_line( Board, Line ) :-
  row( R ), pickoutrow( R, Board, PR ),
  ( count_symb( PR, 4, o ) ; count_symb( PR, 4, x ) ), !,
  Line = R.

% RULE 1 (computer is o).
% ------

opposer( o, x ).
opposer( x, o ).

rule( Player, Board, NewBoard ) :- 
  row( R ), pickoutrow( R, Board, PR ), 
  count_symb( PR, 3, Player ), count_symb( PR, 1, b ), !,
  add_in_blank( Player, R, Board, NewBoard ),
  ( Player = x -> asserta( x_won ) ; asserta( o_won ) ).

% RULE 2
% ------

rule( Player, Board, NewBoard ) :-
  row( R ), pickoutrow( R, Board, PR ), 
  opposer( Player, Opposer ),
  count_symb( PR, 3, Opposer ), count_symb( PR, 1, b ), !, 
  add_in_blank( Player, R, Board, NewBoard ).

% RULE 3
% ------

add_lists( [], [], [] ).
add_lists( [Ah|At], [Bh|Bt], [Sh|St] ) :- Sh is Ah + Bh, add_lists( At, Bt, St ).

blank_at_pos_inner( [], P ) :- fail.
blank_at_pos_inner( [b|Rt], P, P ) :- !.
blank_at_pos_inner( [_|Rt], P, P ) :- fail.
blank_at_pos_inner( [_|Rt], P, C ) :- C1 is C + 1, blank_at_pos_inner( Rt, P, C1 ).
blank_at_pos( R, P ) :- blank_at_pos_inner( R, P, 1 ).


row_contains_inner( [Rh|Rt], P, P ) :- !, Rh = o.
row_contains_inner( [Rh|Rt], P, Q ) :- Q1 is Q + 1, row_contains_inner( Rt, P, Q1 ).
row_contains( R, P ) :- row_contains_inner( R, P, 1 ).

construct_scores_inner( Player, R, B, [], 17 ) :- !.
construct_scores_inner( Player, R, B, [1|St], P ) :- 
  blank_at_pos( B, P ), row_contains( R, P ), pickoutrow( R, B, PR ), count_symb( PR, 4, b ), !,
  P1 is P + 1, construct_scores_inner( Player, R, B, St, P1 ).
construct_scores_inner( Player, R, B, [2|St], P ) :-
  blank_at_pos( B, P ), row_contains( R, P ), pickoutrow( R, B, PR ),
  count_symb( PR, 3, b ), count_symb( PR, 1, Player ), !,
  P1 is P + 1, construct_scores_inner( Player, R, B, St, P1 ).
construct_scores_inner( Player, R, B, [8|St], P ) :-
  blank_at_pos( B, P ), row_contains( R, P ), pickoutrow( R, B, PR ),
  count_symb( PR, 2, b ), count_symb( PR, 2, Player ), !,
  P1 is P + 1, construct_scores_inner( Player, R, B, St, P1 ).
construct_scores_inner( Player, R, B, [0|St], P ) :-
  P1 is P + 1, construct_scores_inner( Player, R, B, St, P1 ).

construct_scores_2( P, B ) :- row( R ), construct_scores_inner( P, R, B, S, 1 ),
  sumvec( SUM ), add_lists( SUM, S, New ), abolish( sumvec/1 ), asserta( sumvec( New ) ), fail.
construct_scores_2( P, B ) :- true.

construct_scores( P, B, S ) :- abolish( sumvec/1 ), 
  asserta( sumvec( [0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0] ) ), !,
  construct_scores_2( P, B ), sumvec( S ).
  	
max_list_inner( [], M, M ).
max_list_inner( [H|T], N, M ) :- H > N, !, max_list_inner( T, H, M ).
max_list_inner( [H|T], N, M ) :- max_list_inner( T, N, M ).
max_list( [H|T], M ) :- max_list_inner( [H|T], H, M ).

play_in_first_max( P, [], [], M, [] ) :- !, M = -1. 
play_in_first_max( P, [b|Bt], [M|St], M, [P|NBt] ) :- !, play_in_first_max( P, Bt, St, -1, NBt ).
play_in_first_max( P, [Bh|Bt], [_|St], M, [Bh|NBt] ) :- play_in_first_max( P, Bt, St, M, NBt ).
  
rule( Player, Board, NewBoard ) :-
  construct_scores( Player, Board, Scores ), max_list( Scores, Max ), 
  play_in_first_max( Player, Board, Scores, Max, NewBoard ).

play_in_given_place_inner( P, [], [], Pos, A ) :- !, Pos = -1. 
play_in_given_place_inner( P, [b|Bt], [P|St], Pos, Pos ) :- 
  !, A1 is Pos + 1, play_in_given_place_inner( P, Bt, St, -1, A1 ).
play_in_given_place_inner( P, [Bh|Bt], [Bh|St], Pos, A ) :- 
  A1 is A + 1, play_in_given_place_inner( P, Bt, St, Pos, A1 ).


play_in_given_place( Player, Board, NewBoard, Position ) :-
   play_in_given_place_inner( Player, Board, NewBoard, Position, 1 ).

play_me_inner :-
  current( B ), 
  rule( o, B, B2 ),
  write( B2 ), nl,
  write( 'Play where? ' ), read( Pos ),
  play_in_given_place( x, B2, B3, Pos ),
  write( B3 ), nl,
  abolish( current/1 ),
  asserta( current( B3 ) ),
  play_me_inner.


play_me :-
  abolish( current/1 ), blank( B ), asserta( current( B ) ),
  play_me_inner.

play_self_inner :-
  current( B ), 
  rule( o, B, B2 ),
  write( B2 ), nl,
  rule( x, B2, B3 ),
  write( B3 ), nl,
  abolish( current/1 ),
  asserta( current( B3 ) ),
  play_self_inner.
	

play_self :- 
  abolish( current/1 ), blank( B ), asserta( current( B ) ),
  play_self_inner.


% state remembering interface
% ---------------------------
  
reset_board( B ) :- abolish( current/1 ), blank( B ), asserta( current( B ) ), abolish( x_won/0 ), abolish( o_won/0 ).

human_x_move( Position, NewBoard ) :- current( B ), play_in_given_place( x, B, NewBoard, Position ),
  abolish( current/1 ), asserta( current( NewBoard ) ).

computer_move( NewBoard ) :- current( B ), rule( o, B, NewBoard ),
  abolish( current/1 ), asserta( current( NewBoard ) ).

% COMPUTER'S MOVE
% ---------------

% Rule 1: if you can win in this move do so.


% Rule 2: if the other player can win in the next move stop him.


% Rule 3: now for each possible choice of square,
%         count 1 for a potential win not yet started.
%         count 2 for a potential win with one sq. already used.


