Prolog Chess AI, Alpha Beta Pruning - prolog

I am relatively new to Prolog. Currently I am trying to do an AI for a Minichess Game, named Diana chess or Ladies chess (https://en.wikipedia.org/wiki/Minichess). It's basically chess but with a 6x6 Board where you don't have a queen and only one knight.
For my AI, I am mainly looking at the book Prolog programming for artificial intelligence by Ivan Bratko (3rd Edition). There he shows an implementation of the alpha-beta algorithm.I am also looking at the Prolog-Code someone posted on GitHub, where he implemented the same code for his Checkers game. (https://github.com/migafgarcia/prolog-checkers/blob/master/checkers.pl)
The code should think a certain number of turns ahead and choose the best move out of them. Before I started programming in Prolog I did a lot oh Java programming, that's why "Zug" in the first line of my Code is the return of NextMove. I hope this all makes some sense.
My Problem is that when I run my code and, for example, I let the AI run against a human (me), when I start as white and do my first move (b2b3), the AI just doesn't work, the backtracking kinda stops right before NextMove and that way I can't get the best move back. Furthermore when the AI thinks for the minimizing player and needs to choose his move, he always takes the move with the highest value, but, when my understanding from the algorithm is not wrong, shouldn't it take the lowest Value?
I am grateful for every comment on this or for any feedback at all.
Here is the code: https://pastebin.com/sc4gLdQe
alphabeta(Player,MaxiPlayer,Alpha,Beta,Board,NextMove,Eval,Depth) :-
Depth<2,
NewDepth is Depth+1,
findall(
(Player,Figur,VonL,VonR,NachL,NachR,Gegner,Board),
zugbewegung(Player,Figur,VonL,VonR,NachL,NachR,Gegner,Board),
Moves
),
write('All Moves:'),
sysoutMoves(Moves),nl,
bounded_best(Player,MaxiPlayer,Alpha,Beta,Board,Moves,NextMove,Eval,NewDepth),
write('NextMove: '),
write(NextMove),nl,
!.
alphabeta(Player,_,_,_,Board,_,Eval,_) :-
value(Board,Eval,Player),
!.
bounded_best(Player,MaxiPlayer,Alpha,Beta,Board,
[(Spieler,Figur,VonL,VonR,NachL,NachR,Gegner,_)|Moves],
BestMove,BestEval,Depth) :-
sysoutMove((Spieler,Figur,VonL,VonR,NachL,NachR,Gegner,_)),
sysoutMoves(Moves),
ziehen(Board,(Spieler,Figur,VonL,VonR,NachL,NachR,Gegner,_),NewBoard),
gegner(Player,NextPlayer),
alphabeta(NextPlayer,MaxiPlayer,Alpha,Beta,NewBoard,_,Eval,Depth),
good_enough(Player,MaxiPlayer,Moves,Alpha,Beta,Board,
(Spieler,Figur,VonL,VonR,NachL,NachR,Gegner,_),
Eval,BestMove,BestEval,Depth).
good_enough(_,_,[],_,_,_,_,_,_,_) :- !.
good_enough(Player,MaxiPlayer,_,_,Beta,_,Move,Eval,Move,Eval,_) :-
minimizing(Player,MaxiPlayer),
Eval > Beta,
!.
good_enough(Player,MaxiPlayer,_,Alpha,_,_,Move,Eval,Move,Eval,_) :-
maximizing(Player,MaxiPlayer),
Eval < Alpha,
!.
good_enough(Player,MaxiPlayer,Moves,Alpha,Beta,Board,Move,Eval,BestMove,BestEval,Depth) :-
new_bounds(Player,MaxiPlayer,Alpha,Beta,Eval,NewAlpha,NewBeta),
bounded_best(Player,MaxiPlayer,NewAlpha,NewBeta,Board,Moves,Move1,Eval1,Depth),
better_of(Player,MaxiPlayer,Move,Eval,Move1,Eval1,BestMove,BestEval).
new_bounds(Player,MaxiPlayer,Alpha,Beta,Eval,Eval,Beta) :-
minimizing(Player,MaxiPlayer),
Eval > Alpha,
!.
new_bounds(Player,MaxiPlayer,Alpha,Beta,Eval,Alpha,Eval) :-
maximizing(Player,MaxiPlayer),
Eval < Beta,
!.
new_bounds(_,_,Alpha,Beta,_,Alpha,Beta).
better_of(Player,MaxiPlayer,Move1,Eval1,_,Eval2,Move1,Eval1) :-
maximizing(Player,MaxiPlayer),
Eval1 >= Eval2,
!.
better_of(Player,MaxiPlayer,_,Eval1,Move2,Eval2,Move2,Eval2) :-
maximizing(Player,MaxiPlayer),
Eval2 >= Eval1,
!.
better_of(Player,MaxiPlayer,Move1,Eval1,_,Eval2,Move1,Eval1) :-
minimizing(Player,MaxiPlayer),
Eval1 =< Eval2,
!.
better_of(Player,MaxiPlayer,_,Eval1,Move2,Eval2,Move2,Eval2) :-
minimizing(Player,MaxiPlayer),
Eval2 =< Eval1,
!.
maximizing(Player,MaxiPlayer) :-
Player == MaxiPlayer.
minimizing(Player,MaxiPlayer) :-
Player \== MaxiPlayer.

Related

Prolog Domino Solution

I need an algorithm that given a set of domino pieces, returns every possible end to the game.
I have already found this one, Prolog domino game, but it only adds pieces to the beggining of the set, so it doesn't give you every possible solution.
I replaced this [5-4, 4-3, 3-2, 2-1], with this [[5,4], [4,3], [3,2], [2,1]], and tried adding this line domino_order(In, X, [Out|[X,Y]]) :- select(Piece, In, Remaining), swap_or_not(Piece, [X,Y]), domino_order(Remaining, Y, Out)., but it doesn't work.
writing down the detailed logic would lead to somewhat complex code.
I suggest instead to have a quick check for validity, and let Prolog work out the insertion points.
domino :-
Spare = [4-7,3-4], Curr = [1-2,2-3],
domino_row_add_spare(Curr, Spare, R),
writeln(R).
domino_row_add_spare(C, [], C).
domino_row_add_spare(C, Sps, U) :-
append(L, R, C),
select(X-Y, Sps, Rest),
(append(L, [X-Y|R], C1) ; append(L, [Y-X|R], C1)),
valid(C1),
domino_row_add_spare(C1, Rest, U).
valid([_]).
valid([_-X,X-Y|R]) :- valid([X-Y|R]).

Prolog: Tick-tack-toe game

I have basic Tick-tack-toe game, where 2 players both make moves on a grid of 9 cells. The problem is that after the frist player makes the last and winnig move, the game doesnt stop and player 2 still can play. And if second player, somehow, makes winning move too, he will be the winner, despite player 1 actually getting the win first. It doesnt make same error if second player wins frist. Draw works fine. Here`s the code:
:- dynamic o/1.
:- dynamic x/1.
/* the various combinations of a successful horizontal, vertical
or diagonal line */
ordered_line(1,2,3).
ordered_line(4,5,6).
ordered_line(7,8,9).
ordered_line(1,4,7).
ordered_line(2,5,8).
ordered_line(3,6,9).
ordered_line(1,5,9).
ordered_line(3,5,7).
/*line predicate to complete lines
line(A,B,C) :- ordered_line(A,B,C).
line(A,B,C) :- ordered_line(A,C,B).
line(A,B,C) :- ordered_line(B,A,C).
line(A,B,C) :- ordered_line(B,C,A).
line(A,B,C) :- ordered_line(C,A,B).
line(A,B,C) :- ordered_line(C,B,A).
full(A) :- x(A).
full(A) :- o(A).
empty(A) :- not(full(A)).
all_full :- full(1),full(2),full(3),full(4),full(5),
full(6),full(7),full(8),full(9).
done :- ordered_line(A,B,C), x(A), x(B), x(C), write('Player 2 win.'),nl.
done :- ordered_line(A,B,C), o(A), o(B), o(C), write('Player 1 win.'),nl.
done :- all_full, write('Draw.'), nl.
move1 :- write('Player 1 (o) enter a move: '), read(X), between(1,9,X),
empty(X), assert(o(X)).
move1:-all_full.
move2 :- write('Player 2 (x) enter a move: '), read(X), between(1,9,X),
empty(X),assert(x(X)).
move2:- all_full.
printsquare(N) :- o(N), write(' o ').
printsquare(N) :- x(N), write(' x ').
printsquare(N) :- empty(N), write(' ').
printboard :- printsquare(1),printsquare(2),printsquare(3),nl,
printsquare(4),printsquare(5),printsquare(6),nl,
printsquare(7),printsquare(8),printsquare(9),nl.
clear :- x(A), retract(x(A)), fail.
clear :- o(A), retract(o(A)), fail.
play :- not(clear), repeat, move1, printboard, move2,printboard, done.
And that`s the error I get:
Game doesnt stop when player 1 wins
Hope you can help me :) Thanks in advance.
Edit: The "Player 2 wins" shows in "done" predicate. After successfull finishing line of 3 'o' or 'x' game should end with either Player 1 wins or Player 2 wins. I`ll include original code, which might help with understanding the problem I get Original code with comments
The problem is in the main predicate of the game:
play :- not(clear), repeat, move1, printboard, move2,printboard, done.
You do not check for done after player's 1 move.
In Prolog's syntax, logical "or" is written as ;. So one way to express the play predicate is:
play :- not(clear), repeat, move1, printboard, (done; move2, printboard, done).
Which says "after move1, check if the game is done (i.e., finished); if not, player 2 makes the move, and then the check is repeated.

Easy prolog queries

I am very new to prolog and although I’ve read some books I can definitely tell that my programming brain can’t think the Prolog way. The problem I would like to solve is pretty simple (I believe). I will describe it via an example.
Let’s say that I have a graph that contains 4 “types” of nodes and 3 edges that connect the nodes. The types can be A, B, C or D and as you can see from the image below (see Figure 1), A can be connected with B and C (A_To_B and A_To_C edges respectively), while C can be connected to D (C_To_D edge). There’s also an additional rule not shown on the picture: A can be connected to at most 1 C.
I would like to express these simple rules in Prolog to solve the problem shown in the second picture. There are 3 nodes which type is missing (labeled X?, Y? and Z?). By applying the above rules in my mind I can easily find that X? and Z? are of B type (as A can connect to no more than 1 Cs) and Y? is of type D as C can only connect to D.
Could please provide me any help on that? I am not writing just to pick the solution. I would like to learn Prolog as well so any suggestion on a book that explains Prolog to people who have never worked on such concepts before like me would be very welcome.
EDIT: Example that fails
I came up with the following two examples:
For example 1, the rules are
can_connect(a,b,_).
can_connect(a,c,1).
link(1,2).
type(1,a).
type(2,_).
The possible solutions returned are [b,c] which is correct as we request at most 1 link from A to C meaning that 0 links is also acceptable.
In example 2 the rules change to the following:
can_connect(a,b,_).
can_connect(a,c,**2**).
link(1,2).
link(1,3).
type(1,a).
type(2,_).
type(3,c).
Running the code here returns [c] which is wrong. b is also an acceptable solution as we require again at most 2 A to C links which means that having only 1 is OK.
I spent this weekend trying to figure out the solution. First of all, I believe that it works as intended in Example 1 simply because there's no link from A to C instantiated in the proposed solution (where checking if 2 can be b), so the can_connect(a,c,1) is not checked so the proposed solution is getting accepted. In Example 2, there's one A to C link already there so the can_connect(a,c,2) is checked and the solution where node 2 has type b is rejected as the rule checks if there are exactly 2 and not at most 2 links from A to C.
I find a solution which works at these scenarios but fails at some others. Here it is:
% value #3 is the lower bound and #4 is the upper bound.
can_connect(a,b,0,500).
% A C node can be connected by 0, 1 or 2 A nodes
can_connect(a,c,0,2).
can_connect(d,c,1,1).
can_connect(c,e,0,1).
%The same as previous solution
link(1,2).
link(1,3).
% No change here
type(1,a).
type(2,_).
type(3,c).
% No change here
node_type(N, NT) :-
type(N, NT),
nonvar(NT),
!. % assume a node has only one type
% No change here
node_type(N, NT) :-
assoc_types(Typed),
maplist(check_connections(Typed), Typed),
memberchk(N:NT, Typed).
% No change here
assoc_types(Typed) :-
findall(N, type(N, _), L),
maplist(typed, L, Typed).
% No change here
typed(N, N:T) :-
type(N, T),
member(T, [a,b,c]).
% Changes here
check_connections(Graph, N:NT) :-
forall(link(N, M), (
memberchk(M:MT, Graph),
can_connect(NT, MT, L, U),
findall(X, (link(N, X), memberchk(X:MT, Graph)), Ts),
mybetween(L, U, Ts),
forall(can_connect(NT, Y, LM, UM), (
findall(P, (link(N,P),memberchk(P:Y, Graph)), Ss),
length(Ss, SsSize ),
SsSize>=LM,
SsSize=<UM
))
)).
% It is used to find if the length of a list is between two limits.
mybetween(Lower, Upper, MyList) :-
length(MyList, MySize),
MySize=<Upper,
MySize>=Lower.
This solution fails in this example
In this example, X? must be always b, Y? must always be C and Z? must always be D. It finds X? and Y? correctly but not Z?. I believe after some debugging that this is due the fact that in the current implementation I only check the can_connect rules that are related with links that start from a node and not that end to a node. However, I am not sure at all about that.
Any help is appreciated.
the representation of the problem needs to disambiguate nodes names, so we can express the links appropriately
now we can write
can_connect(a,b,_).
can_connect(a,c,1).
can_connect(c,d,_).
link(1,2).
link(1,3).
link(1,4).
link(4,5).
link(4,6).
link(7,4).
link(7,8).
type(1,a).
type(2,b).
type(3,_).
type(4,c).
type(5,d).
type(6,_).
type(7,a).
type(8,_).
The underscore (anonymous variable) in Prolog plays a role similar to NULL in SQL, it can assume any value.
So, a first snippet
node_type(N, NT) :- type(N, NT), nonvar(NT), !. % assume a node has only one type
can be used to express what we know about the problem.
Facts can_connect/3 then can be read like
a can connect to any number of b
a can connect to just 1 c
etc
Where we don't know the node type, a complex rule is needed, that infers the type of source node from the type of target node, and accounts for the counting constraint, something like
node_type(N, NT) :-
link(M, N),
type(M, MT),
can_connect(MT, NT, C),
aggregate(count, Y^(link(M, Y), type(Y, NT)), C).
?- forall(between(1,8,N), (node_type(N,T),writeln(N:T))).
1:a
2:b
3:b
4:c
5:d
6:d
7:a
8:b
true.
edit if your Prolog doesn't have library(aggregate), from where aggregate/3 has been loaded, you can try
node_type(N, NT) :-
link(M, N),
type(M, MT),
can_connect(MT, NT, C),
findall(t, (link(M, Y), type(Y, NT)), Ts), length(Ts, C).
edit first of all, the updated graph, marked with types where known:
my previous code worked only under very restricted assumptions. Here is something more general, that checks the constraints over the full graph (as was suggested by #false comment), with a 'generate and test' approach.
node_type(N, NT) :-
assoc_types(Typed),
maplist(check_connections(Typed), Typed),
memberchk(N:NT, Typed).
assoc_types(Typed) :-
findall(N, type(N, _), L),
maplist(typed, L, Typed).
typed(N, N:T) :- type(N, T), member(T, [a,b,c,d]).
check_connections(Graph, N:NT) :-
forall(link(N, M), (
memberchk(M:MT, Graph),
can_connect(NT, MT, C),
aggregate(count, X^(link(N, X), memberchk(X:MT, Graph)), C)
)).
now ?- node_type(4,X). fails...

Cryptarithmetic puzzle (Prolog)

I was asked to write a Prolog code to solve the cryptarithmetic puzzle, using "generate and test". For example I get solve([R,O,B],[B,E,R,T],[N,O,R,E,S]) and I need to find an assign for the letters.
So I wrote this code:
sum(List1,List2,SumList) :-
append(List1,List2,List3),
append(List3,SumList,AllList),
assign([0,1,2,3,4,5,6,7,8,9],AllList),
add_zero(List1,List1Z),
add_zero(List2,List2Z),
add_zero(SumList,SumListZ),
name(Num1,List1Z),
name(Num2,List2Z),
name(SumNum,SumListZ),
SumNum is Num1+Num2,
!.
remove(X,[X|Xs],Xs).
remove(X,[_|Ys],Res) :-
remove(X,Ys,Res).
assign(Digits,[X|Tail]) :-
nonvar(X),
!,
assign(Digits,Tail).
assign(Digits,[X|Tail]) :-
remove(X,Digits,D1),
assign(D1,Tail).
assign(_,[]) :-
!.
add_zero([X|Tail1],[Y|Tail2]) :-
!,
Y is X+48,
add_zero(Tail1,Tail2).
add_zero([],[]) :-
!.
But I have a bug and I can't find it... can you help me?
The problem with your code is that in the second clause of remove/3 you are not keeping the item which is not removed.
It should read:
remove(X,[Y|Ys],[Y|Res]):-
remove(X,Ys,Res).
I tried your code with SEND + MORE = MONEY and it worked fine after fixing that procedure.
However it did not find a solution for ROB + BERT = NORES... According to this site, which has many solvers, your equation has no solution.

studying for prolog/haskell programming exam

I starting to study for my upcoming exam and I'm stuck on a trivial prolog practice question which is not a good sign lol.
It should be really easy, but for some reason I cant figure it out right now.
The task is to simply count the number of odd numbers in a list of Int in prolog.
I did it easily in haskell, but my prolog is terrible. Could someone show me an easy way to do this, and briefly explain what you did?
So far I have:
odd(X):- 1 is X mod 2.
countOdds([],0).
countOdds(X|Xs],Y):-
?????
Your definition of odd/1 is fine.
The fact for the empty list is also fine.
IN the recursive clause you need to distinguish between odd numbers and even numbers. If the number is odd, the counter should be increased:
countOdds([X|Xs],Y1) :- odd(X), countOdds(Xs,Y), Y1 is Y+1.
If the number is not odd (=even) the counter should not be increased.
countOdds([X|Xs],Y) :- \+ odd(X), countOdds(Xs,Y).
where \+ denotes negation as failure.
Alternatively, you can use ! in the first recursive clause and drop the condition in the second one:
countOdds([X|Xs],Y1) :- odd(X), !, countOdds(Xs,Y), Y1 is Y+1.
countOdds([X|Xs],Y) :- countOdds(Xs,Y).
In Prolog you use recursion to inspect elements of recursive data structs, as lists are.
Pattern matching allows selecting the right rule to apply.
The trivial way to do your task:
You have a list = [X|Xs], for each each element X, if is odd(X) return countOdds(Xs)+1 else return countOdds(Xs).
countOdds([], 0).
countOdds([X|Xs], C) :-
odd(X),
!, % this cut is required, as rightly evidenced by Alexander Serebrenik
countOdds(Xs, Cs),
C is Cs + 1.
countOdds([_|Xs], Cs) :-
countOdds(Xs, Cs).
Note the if, is handled with a different rule with same pattern: when Prolog find a non odd element, it backtracks to the last rule.
ISO Prolog has syntax sugar for If Then Else, with that you can write
countOdds([], 0).
countOdds([X|Xs], C) :-
countOdds(Xs, Cs),
( odd(X)
-> C is Cs + 1
; C is Cs
).
In the first version, the recursive call follows the test odd(X), to avoid an useless visit of list'tail that should be repeated on backtracking.
edit Without the cut, we get multiple execution path, and so possibly incorrect results under 'all solution' predicates (findall, setof, etc...)
This last version put in evidence that the procedure isn't tail recursive. To get a tail recursive procedure add an accumulator:
countOdds(L, C) :- countOdds(L, 0, C).
countOdds([], A, A).
countOdds([X|Xs], A, Cs) :-
( odd(X)
-> A1 is A + 1
; A1 is A
),
countOdds(Xs, A1, Cs).

Resources