%==============================================================================
% Representation of the Game State:
%==============================================================================

% The game state is represented as the term
%	game(Col1,...,Col7)
% where each column is represented as
%	col(Field1,...,Field6)
% where each field is one of
%	empty
%	yellow
%	red.

%------------------------------------------------------------------------------
% initial(GameState): This returns the initial/empty game state.
%------------------------------------------------------------------------------

initial(game(
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty),
	col(empty, empty, empty, empty, empty, empty))).

%------------------------------------------------------------------------------
% col_get(RowNo, Column, FieldColor):
%------------------------------------------------------------------------------

col_get(1, col(Field,_,_,_,_,_), Field).
col_get(2, col(_,Field,_,_,_,_), Field).
col_get(3, col(_,_,Field,_,_,_), Field).
col_get(4, col(_,_,_,Field,_,_), Field).
col_get(5, col(_,_,_,_,Field,_), Field).
col_get(6, col(_,_,_,_,_,Field), Field).

%------------------------------------------------------------------------------
% col_add(Old_Column, Field_Color, New_Column):
%------------------------------------------------------------------------------

col_add(col(empty,empty,empty,empty,empty,empty), F1,
	col(F1,empty,empty,empty,empty,empty)).
col_add(col(F1,empty,empty,empty,empty,empty), F2,
	col(F1,F2,empty,empty,empty,empty)) :-
	F1 \= empty.
col_add(col(F1,F2,empty,empty,empty,empty), F3,
	col(F1,F2,F3,empty,empty,empty)) :-
	F1 \= empty,
	F2 \= empty.
col_add(col(F1,F2,F3,empty,empty,empty), F4,
	col(F1,F2,F3,F4,empty,empty)) :-
	F1 \= empty,
	F2 \= empty,
	F3 \= empty.
col_add(col(F1,F2,F3,F4,empty,empty), F5,
	col(F1,F2,F3,F4,F5,empty)) :-
	F1 \= empty,
	F2 \= empty,
	F3 \= empty,
	F4 \= empty.
col_add(col(F1,F2,F3,F4,F5,empty), F6,
	col(F1,F2,F3,F4,F5,F6)) :-
	F1 \= empty,
	F2 \= empty,
	F3 \= empty,
	F4 \= empty,
	F5 \= empty.

%------------------------------------------------------------------------------
% player(Player, Field): Maps German player color name to internal enum value.
%------------------------------------------------------------------------------

player(gelb, yellow).
player(rot, red).

%------------------------------------------------------------------------------
% zug(OldState, Player, ColNo, NewState): Changes of the game state.
%------------------------------------------------------------------------------

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 1,
	game(New1,Col2,Col3,Col4,Col5,Col6,Col7)) :-
	player(Player, Field),
	col_add(Col1, Field, New1).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 2,
	game(Col1,New2,Col3,Col4,Col5,Col6,Col7)) :-
	player(Player, Field),
	col_add(Col2, Field, New2).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 3,
	game(Col1,Col2,New3,Col4,Col5,Col6,Col7)) :-
	player(Player, Field),
	col_add(Col3, Field, New3).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 4,
	game(Col1,Col2,Col3,New4,Col5,Col6,Col7)) :-
	player(Player, Field),
	col_add(Col4, Field, New4).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 5,
	game(Col1,Col2,Col3,Col4,New5,Col6,Col7)) :-
	player(Player, Field),
	col_add(Col5, Field, New5).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 6,
	game(Col1,Col2,Col3,Col4,Col5,New6,Col7)) :-
	player(Player, Field),
	col_add(Col6, Field, New6).

zug(game(Col1,Col2,Col3,Col4,Col5,Col6,Col7), Player, 7,
	game(Col1,Col2,Col3,Col4,Col5,Col6,New7)) :-
	player(Player, Field),
	col_add(Col7, Field, New7).

%------------------------------------------------------------------------------
% marke(State, RowNo, ColNo, Player):
%------------------------------------------------------------------------------

marke(game(Col1,_Col2,_Col3,_Col4,_Col5,_Col6,_Col7), RowNo, 1, Player) :-
	col_get(RowNo, Col1, Field),
	player(Player, Field).

marke(game(_Col1,Col2,_Col3,_Col4,_Col5,_Col6,_Col7), RowNo, 2, Player) :-
	col_get(RowNo, Col2, Field),
	player(Player, Field).

marke(game(_Col1,_Col2,Col3,_Col4,_Col5,_Col6,_Col7), RowNo, 3, Player) :-
	col_get(RowNo, Col3, Field),
	player(Player, Field).

marke(game(_Col1,_Col2,_Col3,Col4,_Col5,_Col6,_Col7), RowNo, 4, Player) :-
	col_get(RowNo, Col4, Field),
	player(Player, Field).

marke(game(_Col1,_Col2,_Col3,_Col4,Col5,_Col6,_Col7), RowNo, 5, Player) :-
	col_get(RowNo, Col5, Field),
	player(Player, Field).

marke(game(_Col1,_Col2,_Col3,_Col4,_Col5,Col6,_Col7), RowNo, 6, Player) :-
	col_get(RowNo, Col6, Field),
	player(Player, Field).

marke(game(_Col1,_Col2,_Col3,_Col4,_Col5,_Col6,Col7), RowNo, 7, Player) :-
	col_get(RowNo, Col7, Field),
	player(Player, Field).

%==============================================================================
% Printing of the Game Board:
%==============================================================================

drucke(State) :-
	print_top,
	print_row(State, 6),
	print_row(State, 5),
	print_row(State, 4),
	print_row(State, 3),
	print_row(State, 2),
	print_row(State, 1),
	print_bottom.

%------------------------------------------------------------------------------
% get_sym(GameState, RowNo, ColNo, Symbol):
%------------------------------------------------------------------------------

get_sym(State, RowNo, ColNo, '*') :-
	marke(State, RowNo, ColNo, gelb).

get_sym(State, RowNo, ColNo, '+') :-
	marke(State, RowNo, ColNo, rot).

get_sym(State, RowNo, ColNo, ' ') :-
	\+ marke(State, RowNo, ColNo, _).

%------------------------------------------------------------------------------
% print_row(State, RowNo):
%------------------------------------------------------------------------------

print_row(State, RowNo) :-
	print_row(State, RowNo, 1).

print_row(_State, _RowNo, StartColNo) :-
	StartColNo > 7,
	write('|'),
	nl.

print_row(State, RowNo, StartColNo) :-
	StartColNo =< 7,
	get_sym(State, RowNo, StartColNo, Sym),
	write('|'),
	write(Sym),
	NextColNo is StartColNo + 1,
	print_row(State, RowNo, NextColNo).

%------------------------------------------------------------------------------
% print_top:
%------------------------------------------------------------------------------

print_top :-
	nl.

%------------------------------------------------------------------------------
% print_bottom:
%------------------------------------------------------------------------------

print_bottom :-
	write('---------------'),
	nl,
	write(' 1 2 3 4 5 6 7 '),
	nl,
	nl.

%==============================================================================
% Checking Whether a Player has Won the Game:
%==============================================================================

% Four in a row (deltaRow = 0, deltaCol = 1):
gewonnen(State, Player) :-
	col(ColNo),
	row(RowNo),
	ColNo =< 4,
	n_of_a_color(State, Player, RowNo, ColNo, 0, 1, 4).

% Four in a column (deltaRow = 1, deltaCol = 0):
gewonnen(State, Player) :-
	col(ColNo),
	row(RowNo),
	RowNo =< 3,
	n_of_a_color(State, Player, RowNo, ColNo, 1, 0, 4).

% Four in a diagonal up (deltaRow = 1, deltaCol = 1):
gewonnen(State, Player) :-
	col(ColNo),
	row(RowNo),
	ColNo =< 4,
	RowNo =< 3,
	n_of_a_color(State, Player, RowNo, ColNo, 1, 1, 4).

% Four in a diagonal down (deltaRow = -1, deltaCol = 1):
gewonnen(State, Player) :-
	col(ColNo),
	row(RowNo),
	ColNo =< 4,
	RowNo >= 4,
	n_of_a_color(State, Player, RowNo, ColNo, -1, 1, 4).

%------------------------------------------------------------------------------
% row(RowNo): Possible Row Numbers.
%------------------------------------------------------------------------------

row(1).
row(2).
row(3).
row(4).
row(5).
row(6).

%------------------------------------------------------------------------------
% col(ColNo): Possible Column Numbers.
%------------------------------------------------------------------------------

col(1).
col(2).
col(3).
col(4).
col(5).
col(6).
col(7).

%------------------------------------------------------------------------------
% n_of_a_color(State, Player, StartRow, StartCol, DeltaRow, DeltaCol, N):
%------------------------------------------------------------------------------

n_of_a_color(_State, _Player, _RowNo, _ColNo, _DeltaRow, _DeltaCol, 0).

n_of_a_color(State, Player, RowNo, ColNo, DeltaRow, DeltaCol, N) :-
	N > 0,
	marke(State, RowNo, ColNo, Player),
	NextRowNo is RowNo + DeltaRow,
	NextColNo is ColNo + DeltaCol,
	NextN is N - 1,
	n_of_a_color(State, Player, NextRowNo, NextColNo, DeltaRow, DeltaCol,
			NextN).


%==============================================================================
% Checking Whether the Game Board is Full:
%==============================================================================

voll(State) :-
	marke(State, 6, 1, _),
	marke(State, 6, 2, _),
	marke(State, 6, 3, _),
	marke(State, 6, 4, _),
	marke(State, 6, 5, _),
	marke(State, 6, 6, _),
	marke(State, 6, 7, _).


%==============================================================================
% Choose a Move in the Game:
%==============================================================================

waehle_zug_sb(State, Player, Col) :-
	possible_moves(State, Player, Moves),
	choose_best_move(Moves, Player, Col),
	!.

choose_best_move([move(ColNo,State)|MoreMoves], Player, BestColNo) :-
	max_depth(Depth),
	opponent(Player, Opponent),
	minmax(State, Opponent, Depth, OpponentValue),
	Value is -OpponentValue,
	choose_best_move(MoreMoves, Player, ColNo, Value, BestColNo).

choose_best_move([], _Player, ColNo, _Value, ColNo).

choose_best_move([move(ColNo,State)|MoreMoves], Player, ColNoSoFar, MaxSoFar,
			BestColNo) :-
	max_depth(Depth),
	opponent(Player, Opponent),
	minmax(State, Opponent, Depth, OpponentValue),
	Value is -OpponentValue,
	Value > MaxSoFar ->
		choose_best_move(MoreMoves, Player, ColNo, Value, BestColNo);
	choose_best_move(MoreMoves, Player, ColNoSoFar, MaxSoFar, BestColNo).

%------------------------------------------------------------------------------
% max_depth(Depth): Maximal Look-Ahead.
%------------------------------------------------------------------------------

max_depth(2).

%------------------------------------------------------------------------------
% minmax(State, Player, Depth, Value):
%------------------------------------------------------------------------------

minmax(State, Player, _Depth, 1) :-
	gewonnen(State, Player),
	!.

minmax(State, Player, _Depth, -1) :-
	opponent(Player, Opponent),
	gewonnen(State, Opponent),
	!.

minmax(_State, _Player, 0, 0) :-
	!.

minmax(State, _Player, _Depth, 0) :-
	voll(State),
	!.

minmax(State, Player, Depth, Value) :-
	Depth > 0,
	NextDepth is Depth - 1,
	possible_moves(State, Player, Moves),
	opponent(Player, Opponent),
	minmax_list(Moves, Opponent, NextDepth, ValueList),
	max_neg(ValueList, Value).

minmax_list([], _Opponent, _NextDepth, []).

minmax_list([move(_ColNo,State)|MoreMoves], Opponent, NextDepth,
		[Value|MoreValues]) :-
	minmax(State, Opponent, NextDepth, Value),
	minmax_list(MoreMoves, Opponent, NextDepth, MoreValues).

%------------------------------------------------------------------------------
% possible_moves(State, Player, Moves):
%------------------------------------------------------------------------------

possible_moves(State, Player, Moves) :-
	findall(Move,
		possible_move(State, Player, Move),
		Moves).

%------------------------------------------------------------------------------
% possible_move(State, Player, Move): Column must not be full.
%------------------------------------------------------------------------------

possible_move(State, Player, move(ColNo,NextState)) :-
	col(ColNo),
	\+ marke(State, 6, ColNo, _Player),
	zug(State, Player, ColNo, NextState).

%------------------------------------------------------------------------------
% opponent(Player, Opponent):
%------------------------------------------------------------------------------

opponent(rot, gelb).
opponent(gelb, rot).

%------------------------------------------------------------------------------
% max_neg(ValueList, MaxNeg): MaxNeg is the maximum of the negated values.
%------------------------------------------------------------------------------

max_neg([Value|MoreValues], MaxNeg) :-
	NegValue is -Value,
	max_neg(MoreValues, NegValue, MaxNeg).

% max_neg(ValueList, MaxNegIn, MaxNegOut):
max_neg([], MaxNeg, MaxNeg).

max_neg([Value|MoreValues], MaxNegIn, MaxNegOut) :-
	NegValue is -Value,
	NegValue > MaxNegIn -> max_neg(MoreValues, NegValue, MaxNegOut);
	max_neg(MoreValues, MaxNegIn, MaxNegOut).


%==============================================================================
% Main Program to Play the Game:
%==============================================================================

play :-
	initial(State),
	player_move(State, gelb).

% Note:
% player_move and computer_move both first check whether the other player
% has won or whether the board is full.
% They also print the game board before selecting a move.
% I.e. after selecting a move, these tasks do not have to be done
% because one can rely on the fact that the other predicate will do it.

%------------------------------------------------------------------------------
% player_move(State, Color):
%------------------------------------------------------------------------------

player_move(State, Color) :-
	opponent(Color, OpponentColor),
	gewonnen(State, OpponentColor),
	!,
	drucke(State),
	write('Der Computer hat gewonnen.'),
	nl.

player_move(State, _Color) :-
	voll(State),
	!,
	drucke(State),
	write('Das Spiel ist unentschieden ausgegangen.'),
	nl.

player_move(State, Color) :-
	drucke(State),
	write('Ihr Zug [1-7.]? '),
	flush,
	read(Move),
	check_move(State, Move, CorrectedMove),
	zug(State, Color, CorrectedMove, NextState),
	opponent(Color, OpponentColor),
	computer_move(NextState, OpponentColor).

%------------------------------------------------------------------------------
% check_move(State, Move, CorrectedMove):
%------------------------------------------------------------------------------

check_move(State, Move, Move) :-
	col(Move),
	\+ marke(State, 6, Move, _),
	!.

check_move(State, Move, CorrectedMove) :-
	col(Move),
	marke(State, 6, Move, _),
	!,
	write('Die Spalte ist schon voll. Waehlen Sie eine andere: '),
	flush,
	read(NewMove),
	check_move(State, NewMove, CorrectedMove).

check_move(State, Move, CorrectedMove) :-
	\+ col(Move),
	write('Das ist keine gueltige Spalte.'),
	nl,
	write('Bitte nur 1 bis 7 eingeben und mit \'.\' abschliessen: '),
	flush,
	read(NewMove),
	check_move(State, NewMove, CorrectedMove).

%------------------------------------------------------------------------------
% computer_move(State, Color):
%------------------------------------------------------------------------------

computer_move(State, Color) :-
	opponent(Color, OpponentColor),
	gewonnen(State, OpponentColor),
	!,
	drucke(State),
	write('Sie haben gewonnen.'),
	nl.

computer_move(State, _Color) :-
	voll(State),
	!,
	drucke(State),
	write('Das Spiel ist unentschieden ausgegangen.'),
	nl.

computer_move(State, Color) :-
	drucke(State),
	waehle_zug_sb(State, Color, Move),
	zug(State, Color, Move, NextState),
	write('Computer zieht in Spalte '),
	write(Move),
	write('.'),
	nl,
	opponent(Color, OpponentColor),
	player_move(NextState, OpponentColor).

