Eran Kampf
Eran Kampf
13 min read

"Four in a row" game in Prolog

thumbnail for this post

I decided to publish a prolog project I wrote about 4 years ago for an Introduction to AI course at the university. The project is an implementation of the “four in a row” game in Prolog language (using AMZI prolog). The paper itself is in hebrew but the code is universal ;)

FourRow.pdf (hebrew) (64.68 KB)
fourrow.pro (21.8 KB)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section 1 : Game's Facts
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% These two numbers set how clever the AI is
% (how many levels will it scan)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
win_deep(2).		% Attack rating
nolose_deep(2).		% Defence rating
random_factor(3).	% AI random factor. chance of success is 1/(X-1)

player_human(h).
player_ai(c).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% board_sign facts are used to determin the game sign
% of the guman and computer:
% h - Human, c - Computer, f - Empty.
% This rules are created dynamically at the setup
% phase of the game.
%
% Example:
%  board_sign(h,x).
%  board_sign(c,o).
%  board_sign(f,f).
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Game board description
% ----------------------
% The game's board is represented by dynamic facts:
% game(Board).
% Board representation:	f - empty spot
%			h - human's spot
%			c - computer's spot
% Row = [lowers,...,highest], lowest var index number is 1
% Each sublist represents 1 column.
% The board size if represented by board_size(N).
%
% Example:
% board_size(8).
% game([[f,f,f,f],
%    	[f,f,f,f],
%	[f,f,f,f],
%	[f,f,f,f]]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section 2 : Game's Execution statement.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
main:- write_help,game_start.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% game_start is divided to 3 rules:
% 1. Setup game - create board, assign which player starts
% 2. Id human starts get input from human
% 3. If computer starts have the computer make his move
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
game_start:-setup, nl, fail.
game_start:-board_sign(h,x),game(Board), show_board(Board), get_input(Board),!. % Player starts, Cut prevents backtracing
game_start:-board_sign(h,o),game(Board),think(Board),!. % Computer starts, Cut prevents backtracing

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section 3 : Game's Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% go(X).
% go performs the player's move and call the computer.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% first check the move is legal (Column not full)
go(X):-  game(Pos),
	 not(get_y(X,Y)),	% continue if column is full, else fail
	 say(Pos, $Move Illegal! You cant move to the specified column. Choose again...$, yes).

go(X):-  get_y(X,Y),			% Get Y value for X
	 game(Current_Pos),		% Get current board position
	 member(Current_Pos, L, X),	% Get the X list - L
	 member(L, f, Y),
	 !, % This cut is used to prevent prolog from backtracing into previous turns
	 player_human(HSign), 	 	% Get the sign of human player
       	 replace(L2, L, HSign, Y),	% Put human's sign at its place in the X column
       	 replace(Current_Pos2, Current_Pos, L2, X), % Update the board
       	 think(Current_Pos2). 		% Call AI with updated board

% think is the AI rule. the AI tries to do certain actions
% by their priority.
% 1 - Check if it has been defeated.
% 2 - Check if there's a move that will make him win
% 3,4 - Prevent player from winning. Block player if he's about to win.
% 5 - Make a move without allowing the player to make a winning move afterwards
% 6 - Make any move
%
% The cuts in each rule's end are used to prevent prolog from backtracing to
% previous, already played, turns.
think(Pos) :-
  	player_human(H), victory(H, Pos), !,
  	say(Pos, $Unbelievable! You are the winner.$, no).
think(Pos) :-
  	try_to_win(Pos), !.
think(Pos) :-
  	retractall(last_good_move(_)),
	try_nolose(Pos).
think(_)   :-
  	last_good_move(Pos2), !, % prevents backtracking that will cause the
				 % computer to change a move he had
				 % already done and look for a new one.
  	say(Pos2, $It's your move.$, yes).
think(Pos) :-
  	player_ai(AI), player_human(H),
	move(AI, Pos, Pos2),
  	nolose(H, Pos2, 0), !, % Tries to advance toward winning
  	say(Pos2, $Go on, you have some chance in this game.$, yes).
think(Pos) :-
  	player_ai(AI),
  	move(AI, Pos, Pos2),
  	say(Pos2, $Go on, you have some chance in this game.$, yes).

try_nolose(Pos) :- player_ai(AI),
		   move(AI, Pos, Pos2),
                   nolose_deep(Deep),
		   player_human(H),
		   nolose(H, Pos2, Deep),
                   retractall(last_good_move(_)),assert(last_good_move(Pos2)),
		   random_factor(F), random_number(X,F), X == 1, !, fail. % Make AI's move more inetersting

try_to_win(Pos) :- player_ai(AI),
		   move(AI, Pos, Pos2),
                   is_it_win(Pos2).


is_it_win(Pos2) :-
  player_ai(AI),
  victory(AI, Pos2),
  say(Pos2, $Sorry, you cannot win against the AI.$, no).

is_it_win(Pos2) :-
  win_deep(Deep),
  player_human(H),
  win(H, Pos2, Deep),
  say(Pos2, $Give up. Don't lose my time.$, yes).


% AI scanning rules
% -----------------------------
% AI is trying to win
win(c, Pos, Deep) 	:- move(c, Pos, Pos2), win(h, Pos2, Deep).
win(h, Pos, _) 		:- victory(c, Pos). % AI Wins
win(h, Pos, 0) 		:- !, fail. % Cant go any deeper
win(h, Pos, Deep) 	:- Deep1 is Deep - 1, not(nowin(h, Pos, Deep1)). % find a move that will not cause X (Player) to win

nowin(h, Pos, _) 	:- not(move(Pos)).
nowin(h, Pos, Deep) 	:- move(h, Pos, Pos2), nowin(c, Pos2, Deep).% Check if after this move X (Player) will be able to make a winning move
nowin(c, Pos, _) 	:- victory(h, Pos). % move caused X (Player) to win
nowin(c, Pos, Deep) 	:- not(win(c, Pos, Deep)).

%%%

lose(h, Pos, Deep) :- move(h, Pos, Pos2), lose(c, Pos2, Deep).
lose(c, Pos, _)    :- victory(h, Pos). % Check if AI lost bcz of Player's move
lose(c, Pos, 0)    :- !, fail.         % Cant go any deeper
lose(c, Pos, Deep) :- Deep1 is Deep - 1, not(nolose(c, Pos, Deep1)). % AI's move

% nolose(o, Pos, Deep) - finds a move that will not cause AI to loose.
nolose(c, Pos, Deep) :- move(c, Pos, Pos2), nolose(h, Pos2, Deep).
nolose(h, Pos, _)    :- victory(c, Pos).   % Check if this board will cause O (AI) to win
nolose(h, Pos, _)    :- not(move(Pos)).    % The last move belongs to O
nolose(h, Pos, Deep) :- not(lose(h, Pos, Deep)). % check that a move by Player wont cause AI to lose

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% say(Board_Position, Message, Continue).
% This is the computer's play result, it shows the
% board position and a nasty remark from the AI :)
% Continue signals if the game is over or not.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
show_board(Pos):-
	write_board(Pos).

say(Pos, Message, Continue) :-
  write(Message),
  nl,
  show_board(Pos),
  say_continue(Pos, Continue).

say_continue(Pos, yes) :-
  get_input(Pos).

say_continue(Pos, no) :-quit,!. % The cut is used in order to stop the program's running

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% member determines Element's position in the list
% member(List, Element, N).
% first var is marked as N=1
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
member([Element|_],Element,1):-!.
member([_|Tail],Element,N):-
		M is (N-1),
		member(Tail,Element,M).

% same as member 1 but without the cut
% example of use: member2([1,2,3,4],X,N).
member2([Element|_],Element,1).
member2([_|Tail],Element,N):-
		member2(Tail,Element,N1),
		N is (N1+1).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% replace(NewList, List, Element, N).
% replaces the N item of List (leftest=1) with Element
% and results NewList
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
replace([Element|Tail],[_|Tail],Element,1).
replace([Head|TailNew],[Head|Tail],Element,N):-
			M is (N-1),
			replace(TailNew,Tail,Element,M).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% check_four(Who, Board).
%% Checks Board to see if player Who won using
%% one of the states that requires 4 colums:
%% Horizonal, Right\Left slash - -,/,\
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
horiz_checkup(Who, Col1, Col2, Col3, Col4):-
	member2(Col1, Who, N),
	member2(Col2, Who, N),
	member2(Col3, Who, N),
	member2(Col4, Who, N).

rightslash_checkup(Who, Col1, Col2, Col3, Col4):-
	member2(Col1, Who, N1),
	N2 is N1+1,
	N3 is N2+1,
	N4 is N3+1,
	member2(Col2, Who, N2),
	member2(Col3, Who, N3),
	member2(Col4, Who, N4).

leftslash_checkup(Who, Col1, Col2, Col3, Col4):-
	member2(Col1, Who, N4),
	N3 is N4-1,
	N2 is N3-1,
	N1 is N2-1,
	member2(Col2, Who, N3),
	member2(Col3, Who, N2),
	member2(Col4, Who, N1).

four_checkup(Who,Col1,Col2,Col3,Col4) :- horiz_checkup(Who,Col1,Col2,Col3,Col4),!.
four_checkup(Who,Col1,Col2,Col3,Col4) :- leftslash_checkup(Who,Col1,Col2,Col3,Col4),!.
four_checkup(Who,Col1,Col2,Col3,Col4) :- rightslash_checkup(Who,Col1,Col2,Col3,Col4).

% collect the board's columns to facts
check_four_process(Who, Board, N1):-
	N2 is N1+1,
	N3 is N2+1,
	N4 is N3+1,
	member2(Board, Col1, N1),
	member2(Board, Col2, N2),
	member2(Board, Col3, N3),
	member2(Board, Col4, N4),
	four_checkup(Who,Col1,Col2,Col3,Col4),
	assert(game_won(Who)).

check_four(Who, Board):-
	board_size(BSize),
	Top is BSize-3,
	for(X,1,Top,1),
	check_four_process(Who,Board,X),
	fail.
check_four(Who, Board):-
	game_won(Who),
	retractall(game_won(Who)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% check_vert(Who, Board).
%% Checks Board to see if player Who won vertically.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
check_vert(Who,Board):-
	member2(Board, Col, N),
	sublist([Who,Who,Who,Who],Col).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% victory(Who, Board).
%% Checks Board to see if player Who won.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
victory( Who, Board ):-check_four(Who,Board).
victory( Who, Board ):-check_vert(Who,Board).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% get_y(X)
% gets Y value for given X. Y is the lowest available
% item in the X column (available=marked with f)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_y(X,Y) :- game(Board),
		member(Board, XList, X), % Get the list for X
		find_first_free(XList,Y).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% find_first_free(List, N).
% Find the first free pos in List (marked by f) and
% return its position to N
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
find_first_free([f|Tail],1):-!.  % the ! is meant so there will be only 1 result
find_first_free([Head|Tail],N):-find_first_free(Tail, M),
   			     		 N1 is M+1,
			     		 N is N1.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% move puts P on a place on an available place on the board
% (an available place is a place marked with f)
% P - value (o\x) , Pos - original board , Pos2 - new board
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
move(P, Pos, Pos2) :-
  member2(Pos, L, X),		% gets column
  find_first_free(L,Y),
  replace(L2, L, P, Y),
  replace(Pos2, Pos, L2, X).

% checks that row isnt full (still have spots marked with f)
move(Pos) :-
  member2(Pos, L, _),
  member2(L,f,_).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% write_board(Pos) - outputs the board Pos.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
write_item(Item):-
	board_sign(Item, Out),
	write(Out).

write_col(Col,N):-
	member(Col, Item, N),
	write(' '),write_item(Item).

write_board_process(Pos,N):-
	board_size(BSize),
	for(X,1,BSize,1),
	member(Pos, Col, X), % Get the X column
	write_col(Col, N),
	fail.

write_board(Pos):-
	board_size(BSize),
	BSize1 is BSize+1,
	for(X,1,BSize,1),
	X1 is BSize1-X,
	not(write_board_process(Pos,X1)),
	nl,
	fail.
write_board(Pos):-
	board_size(BSize),
	for(X,1,BSize,1),
	write(' '),write(X), % write the column's numbers
	fail.
write_board(Pos):-nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section 4 : Interface Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% get_input(Pos) gets input gets the input from
% the human player
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_input(Pos):-
	nl,
	assert(current_position(Pos)), % save current position
	get_command(X),
	do(X).

% The cuts in the do() rules prevent prolog from backtracing back to the command prompt
do(makemove(X)):-makemove(X),!.
do(set(GameVar,Value)):-set(GameVar,Value),!.
do(show_gamevars):-show_gamevars,!.
do(help):-help,!.
do(show):-show,!.
do(quit):-quit,!.
do(reset):-game_start.

% makemove(X) makes the player's move
makemove(X):-
	string_atom(String, X),
	string_integer(String, N),
	current_position(Pos),
	retractall(game(_)),retractall(current_position(_)),
	assert(game(Pos)),
	go(N).

% help shows the help screen
help:-  write_help, show.

% show output the current board
show:-  current_position(Pos), nl,show_board(Pos),
	retractall(current_position(_)),
	get_input(Pos).

write_help:-
	writeln('FOUR IN A ROW - Prolog final project'),
	writeln('Made By Eran Kampf'),
	writeln(''),
	writeln('GAME DESCRIPTION:'),
	writeln('The game board is at size N*N which represent N columns on which each'),
	writeln('player can slide his sign. Each player, at his turn, puts his sign on'),
	writeln('one of the columns at it "slides" down the colums to the lowest place possible.'),
	writeln('In order to win the game a play must have 4 signs placed together horizonally,'),
	writeln('vertically, or slash shape (/ or \).'),
	writeln(''),
	writeln('GAME CONTROL:'),
	writeln('At the beginning of the game you will be ask to enter the board size.'),
	writeln('The game itself is controlled using simple English commands.'),
	writeln(''),
	writeln('- Type ''go'',''goto'',''move'',etc and the column number to make your move.'),
	writeln('  You can also just type a number. Example: ''go 2'', ''2'',''goto 2''.'),
	writeln('- Type ''help'' to read these instructions again.'),
	writeln('- Type ''quit'' in order to finish the game.'),
	writeln('- Type ''show'',''board'',''pos'' to review the current Board position'),
	writeln('- Type ''reset'' to reset the game.'),
	writeln('- Type ''set <game var> <value>'' to change the value of one of the game vars.'),
	writeln('  The game vars are : random_factor, win_deep, nolose_deep.'),
	writeln('  Example: ''set random_factor 3'' '),
	writeln('- Type ''gamevars'' in order to see the current values of the game vars'),
	writeln(''),
  	writeln('Hit any key to continue.'),get0(_).

% Sets the random_factor value
set(random_factor, Value):-
	atom_number(Value, IntValue),
	IntValue>2,   % factor of 2 means 1/1 chance to miss the move - computer wont move
	retractall(random_factor(_)),assert(random_factor(IntValue)),
	respond(['random_factor value set to ',IntValue]),
	show.

% Sets the win_deep value
set(win_deep, Value):-
	atom_number(Value, IntValue),
	retractall(win_deep(_)), assert(win_deep(IntValue)),!,
	respond(['win_deep value set to ',IntValue]),
	show.

% Sets the nolose_deep value
set(nolose_deep, Value):-
	atom_number(Value, IntValue),
	retractall(nolose_deep(_)),assert(nolose_deep(IntValue)),
	respond(['nolose_deep value set to ',IntValue]),
	show.

% Outputs the values of the game vars
show_gamevars:-
	random_factor(RandFac),
	win_deep(Win),
	nolose_deep(Nolose),
	respond(['win_deep :',Win]),
	respond(['nolose_deep :',Nolose]),
	respond(['random_factor :',RandFac]),show.

% quits game
quit:-
	nl,
	writeln('Quiting program.'),
	retractall(current_position(_)),
	retractall(game(_)).

%%%%%%%%%%%%%%%%%%%

get_command(C):-
	read_list(L),	% reads sentence and put in list.
  	command(X,L,[]),    % call the grammar for command
  	C =.. X,!.          % make the command list a structure
get_command(C):-
  respond(['I don''t understand, try again or type help']),get_command(C).

% The grammar doesn't have to be real English.
% There are two types of commands , those with and without a
% single argument.
% A special case is also made for the command makemove which can be
% activated simply by typing a number.
command([Pred,Arg]) --> verb(Type,Pred),
			nounphrase(Type,Arg).   % command + argument
command([Pred,Arg1,Arg2]) -->
			verb(Type,Pred),
			nounphrase(Type,Arg1,Arg2).  % command + 2 arguments

command([Pred])     --> verb(misc,Pred).	% command only (no arguments)
command([makemove,Arg]) --> noun(make_move,Arg).% special case - makemove command

nounphrase(Type,Noun) --> noun(Type,Noun).
nounphrase(Type,Noun1,Noun2) --> noun(Type,Noun1,Noun2).

verb(make_move,makemove) --> go_verb.
verb(misc,V) --> misc_verb(V).
verb(set, V) --> set_verb(V).

% define the verbs that will cause execusion of the makemove
go_verb --> [go].
go_verb --> [go,to].
go_verb --> [move].
go_verb --> [move,to].
go_verb --> [g].

% other commands
misc_verb(help) --> [help].	% Show Help
misc_verb(show) --> [show].	% Show Board
misc_verb(show) --> [show,board].% Show Board
misc_verb(show) --> [board].	% Show Board
misc_verb(show) --> [pos].	% Show Board
misc_verb(reset) --> [reset].	% Reset game
misc_verb(reset) --> [startover].% Reset game
misc_verb(quit) --> [quit].	% Quit Game
misc_verb(quit) --> [exit].	% Quit Game
misc_verb(quit) --> [end].	% Quit Game
misc_verb(quit) --> [bye].	% Quit Game

misc_verb(show_gamevars) --> [gamevars]. % show the game vars
set_verb(set)	--> [set].
set_verb(set)	--> [config].

noun(make_move,N) --> [N].
noun(set, Var, Value) --> [Var,Value].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% read_list(L) - reads a normal sentence from the user and puts
%% it into a list.  It is composed of two parts:
%% 1. Reads a line of ASCII characters from the user, using the
%%    built-in predicate get0/1, which reads a single ASCII character.
%%    The line should be terminated by one of the lastword(X) keys.
%% 2. Part uses DCG to parse the list of characters into a list
%%    of words, using another built-in predicate name/2, which
%%    converts a list of ASCII characters into an atom.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_list(L) :-
  write('Enter Command: '),
  read_line(CL),
  wordlist(L,CL,[]), !.

read_line(L) :-
  get0(C),
  buildlist(C,L).

buildlist(C,[]) :- lastword(C),!. % Cut is used to stop when reached to a terminal char
buildlist(C,[C|X]) :-
  get0(C2),
  buildlist(C2,X).

wordlist([X|Y]) --> word(X), whitespace, wordlist(Y).
wordlist([X]) --> whitespace, wordlist(X).
wordlist([X]) --> word(X).
wordlist([X]) --> word(X), whitespace.

word(W) --> charlist(X), {name(W,X)}.

charlist([X|Y]) --> chr(X), charlist(Y).
charlist([X]) --> chr(X).

chr(X) --> [X],{X>=48}.

whitespace --> whsp, whitespace.
whitespace --> whsp.

whsp --> [X], {X<48}.
lastword(10).   % end if new line entered
lastword(`.).
lastword(`!).
lastword(`?).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section 4 : Program Setup rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Reads an integer input
read_int(X):-read(X),integer(X),!. % Cut used to stop when an integer is read
read_int(X):-writeln('Error'),read_int(X).

% read a y\n input
yesno(`y).
yesno(`n).
read_yn(X):-get(X), yesno(X),!.   % Cut is used to stop when a yesno(X) char
				  % is read
read_yn(X):-writeln('Error'), read_yn(X).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% setup rule is the rule that setups the game.
% It is divided into 2 parts:
% 1. Create the board according to user input
% 2. Assign the player's signs where the player
%    who opens the game is X and the other is O
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
setup:- write('Enter Board Size: '),
	read_int(BSize),
	create_board(BSize,Board),
	nl, fail.

setup:-	retractall(board_sign(_,_)),
	write('Do you want to start? (y / n) - '), read_yn(Chr),
	set_players(Chr).

% set_players set the players signs according to who opens the game.
set_players(`y):- % Human opens - X
		assert(board_sign(h,x)),
		assert(board_sign(c,o)),
		assert(board_sign(f,f)),!.
set_players(`n):- % Computer opens - X
		assert(board_sign(h,o)),
		assert(board_sign(c,x)),
		assert(board_sign(f,f)),!.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% create_board(N,Board)
% This rule creates an N*N sizes board.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
create_col(N,Col):-
	for(X,1,N,1),
	assert(col_item(X,f)),
	fail.
create_col(N,Col):-
	findall(Item, col_item(_,Item), Col),
	retractall(col_item(_,_)).

create_board_process(N,Board,Col):-
	for(X,1,N,1),
	assert(board_item(X,Col)),
	fail.

create_board(N,Board):-
	retractall(board_size(_)),assert(board_size(N)),
	create_col(N,Col),
	not(create_board_process(N,Board,Col)),
	findall(Item, board_item(_,Item), Board),
	retractall(board_item(_,_)),
	retractall(game(_)),assert(game(Board)).

%%%%%%%%%%%%%%%%%%%%%%%%
%% Misc Utility Rules
%%%%%%%%%%%%%%%%%%%%%%%%

% random_number(X,Lim) gets a limit Lim and gives a random
% number X which is from 1 to Lim-1
random_number(X,Lim):-X is integer(random*(Lim-1))+1.

% writeln(X) outputs X and then does nl.
writeln(X):-write(X),nl.

% respond simplifies writing a mixture of literals and variables
% Example: respond(['The value of X is: ',X])
respond([]):-
  write('.'),nl,nl.
respond([H|T]):-
  write(H),
  respond(T).

% Concates 2 lists
conc([],L,L).
conc([X|L1], L2, [X|L3]):-
	conc(L1,L2,L3).

% Finds sublist S in the list L
sublist(S,L):-
	conc(L1,L2,L),
	conc(S,L3,L2).

% add(X,L) adds X to the list L
add(X, L, [X,L]).

% atom_number(Atom,N) convers between an atom of an integer
% Atom to an integer N.
atom_number(Atom, N):- 	string_atom(String, Atom),
			string_integer(String, N),
			not(Integer==0).