Related
Suppose, we have the following game:
There is a pair of numbers (x, y), 2 players are making moves. During the move a player can increase any number by 1 or multiply it by 2.
The player, who makes a move after which (x + y) >= 77 wins.
The initial position is (8, x), find the minimal x such as the second player wins in minimal number of turns.
This problem can be easily solved analytically: both players multiply x by 2 and we get the following inequality:
8 + 2*2*x >= 77 => 4*x >= 69 => x >= (69 / 4) => x >= 17,25
x = ceil(17,25)
x = 18
Now we tried to solve it using Prolog:
:- use_module(library(clpfd)).
top(77).
% possible moves for player
next_state(X1, X2, Y1, Y2) :- Y1 #= X1 + 1,
Y2 #= X2.
next_state(X1, X2, Y1, Y2) :- Y1 #= X1,
Y2 #= X2 + 1.
next_state(X1, X2, Y1, Y2) :- Y1 #= 2*X1,
Y2 #= X2.
next_state(X1, X2, Y1, Y2) :- Y1 #= X1,
Y2 #= 2*X2.
% winning pair
win(X1, X2) :- top(X),
X1 + X2 #>= X.
% we have a sequence of states
sequence_correct([[X1, X2]]) :- win(X1, X2).
sequence_correct([[X1, X2], [Y1, Y2] | T]) :- next_state(X1, X2, Y1, Y2),
sequence_correct([[Y1, Y2] | T]).
% find X such as there is a sequence of 3 states, and there is no Y such as
% Y < X => X is minimum
min(X) :- sequence_correct([[8, X], _, _]), \+ (sequence_correct([[8, Y], _, _]), Y #< X).
But unfortunately when we try to find minimal X, it fails:
?- min(X).
false.
?- min(18). % <- this is good
true.
?- min(17).
false.
?- min(19).
false.
What is wrong?
How to fix?
You are using (\+)/1 which explains:
?- min(X).
false.
No position is negative [X0,Y0] ins 0..sup. Assuming the game doesn't start in the winning position (X0+Y0 #< 77), only the last move is winning (X+Y #>= 77).
move_(s(X,Y), s(X0,Y0), s(X,Y)) :-
[X0,Y0] ins 0..sup,
X0+Y0 #< 77,
next_state(X0, Y0, X, Y).
moves([S0|Ss]) :-
foldl(move_, Ss, S0, s(X,Y)),
X+Y #>= 77.
min(Y) :-
Y0 in 0..77,
labeling([min], [Y0]),
moves([s(8,Y0),_,_]),
!, % commit to the minimum.
Y = Y0.
The search for the minimum is done with labeling([min], [Y0]).
Improved solution for any depth:
move_(s(P,X,Y), s(P0,X0,Y0), s(P,X,Y)) :-
P #= 1-P0,
X0+Y0 #< 77,
next_state(X0, Y0, X, Y).
min(Depth, s(P0,X0,Y0), s(P,X,Y)) :-
[X0,Y0] ins 0..sup,
X0+Y0 #< 77,
length(Ss, Depth),
foldl(move_, Ss, s(P0,X0,Y0), s(P,X,Y)),
X+Y #>= 77.
min(Y) :-
length(_, Depth),
Y0 in 0..77,
labeling([min], [Y0]),
min(Depth, s(0,8,Y0), s(P,_,_)), % Start with player 0. Player 1-P wins.
P = 0,
!, % commit to the minimum.
Y = Y0.
Without clpfd:
move(A, B, A1, B1) :-
( move_num(A, A1), B1 = B
; move_num(B, B1), A1 = A
).
move_num(N, N1) :-
( N1 is N + 1
; N1 is N * 2
).
won(A, B) :-
Tot is A + B,
% Fast integer comparison
Tot #>= 77.
turns(v(A, B), []) :-
% Second player has won
won(A, B).
turns(v(A, B), [state(first(A1,B1),second(A2,B2))|T]) :-
% First player
move(A, B, A1, B1),
\+ won(A1, B1),
% Second player
move(A1, B1, A2, B2),
turns(v(A2, B2), T).
?- time(findall(v(N, Len), (between(0, 20, N), once(( length(T, Len), turns(v(8, N), T) )) ), Vs)).
% 9,201 inferences, 0.001 CPU in 0.001 seconds (99% CPU, 17290920 Lips)
Vs = [v(0,2),v(1,2),v(2,2),v(3,2),v(4,2),v(5,2),v(6,2),v(7,2),v(8,2),v(9,2),v(10,2),v(11,2),v(12,2),v(13,2),v(14,2),v(15,2),v(16,2),v(17,2),v(18,1),v(19,1),v(20,1)].
... which shows that N=18 is the first to have length 1.
Could then use e.g. https://www.swi-prolog.org/pldoc/man?predicate=aggregate_all/3
Can improve efficiency by restricting the length of the turns to be best-so-far:
under_best_length(Len) :-
nb_getval(best_turns, Best),
( integer(Best) ->
Len is Best - 1
; Len = inf
).
best_length_update(Len, N) :-
nb_getval(best_turns, Best),
once(Best == undefined ; Len < Best),
nb_setval(best_turns, Len),
% Potentially useful
nb_setval(best_n, N).
Result in swi-prolog, annotated:
?- nb_setval(best_turns, undefined), between(-80, 80, N),
under_best_length(Best),
once(( between(1, Best, Len), length(T, Len), turns(v(8, N), T) )),
best_length_update(Len, N).
% The first solution becomes best-so-far
N = -80,
Best = inf,
Len = 3,
T = [state(first(9,-80),second(10,-80)),state(first(20,-80),second(40,-80)),state(first(80,-80),second(160,-80))] ;
% Narrowing down to length 2
N = -51,
Best = Len, Len = 2,
T = [state(first(16,-51),second(32,-51)),state(first(64,-51),second(128,-51))] ;
% Length 1 is first seen with N=18
N = 18,
Best = Len, Len = 1,
T = [state(first(8,36),second(8,72))] ;
% There is no solution with a length lower than 1
false.
Here is a one-liner to show the desired 18 answer:
?- time(( nb_setval(best_turns, undefined), between(0, 78, N),
under_best_length(Best),
once(( between(1, Best, Len), length(T, Len), turns(v(8, N), T) )),
best_length_update(Len, N), false ; nb_getval(best_n, BestN) )).
% 3,789 inferences, 0.001 CPU in 0.001 seconds (99% CPU, 5688933 Lips)
BestN = 18.
So I recently started to learn Prolog and I had a question regarding making a predicate which gives u all the possible solutions (next moves) as the current spot is given. The best example is a maze. So this is my data which tells me 'w = white' and 'b = black' within a 5x5 maze:
grid([ [w, w, w, b, w],
[b ,b, w, w, w],
[w, w, w, b, w],
[w, b, b, b, b],
[w, w, w, w, w] ]).
I also implemented a predicated called white/1, which tells me if the given spot in the maze is white:
white(X/Y) :-
grid(M),
nth1(X, M, Line),
nth1(Y, Line, w).
What I now want to do is make a predicate that gives me all the possible moves within the maze. For example if I query:
?- move(5/2, NextState).
NextState = 4/2 ;
NextState = 5/1 ;
NextState = 5/3 ;
No
This is my code, but it gives false and I know it's completely wrong, but I don't know how to implement such a predicate:
move(5/5, _).
move(X/Y, NextState) :-
white(X/Y),
X1 is X + 1,
X1 =< 5,
move(X1/Y, NextState),
Y1 is Y + 1,
Y1 =< 5,
move(X/Y1, NextState),
X2 is X - 1,
X2 >= 5,
move(X2/Y, NextState),
Y2 is Y - 1,
Y2 >= 0,
move(X/Y2, NextState).
If someone could help me, I would really appreciate it! :)
EDIT:
move(X/Y, _) :-
white(X/Y).
move(X/Y, X/Y) :-
X1 is X + 1,
move(X/Y, X1/Y);
X2 is X - 1,
move(X/Y, X2/Y);
Y1 is Y + 1,
move(X/Y, X/Y1);
Y2 is Y - 1,
move(X/Y, X/Y2).
If I query it gives me:
?- move(3/3, NextState).
true ;
NextState = 3/3 ;
NextState = 3/3 ;
NextState = 3/3 ;
NextState = 3/3 ;
move(X/Y, X1/Y1, Xm/Ym) :-
X1 is X + 1, Y1 = Y, X1 =< Xm;
X1 is X - 1, Y1 = Y, X1 > 0;
X1 = X, Y1 is Y + 1, Y1 =< Ym;
X1 = X, Y1 is Y - 1, Y1 > 0.
For each line in the above predicate we have a new direction. The Xm/Ym are bounds of the maze.
| ?- move(5/2, X, 5/5).
X = 4/2 ? ;
X = 5/3 ? ;
X = 5/1
yes
You can remove Xm/Ym as an argument and just put 5 in the body. Or you can define a new predicate
move1(Current, Next) :- move(Current, Next, 5/5)
Instead of disjunctions, we can also write multiple clauses.
move(X/Y, X1/Y) :- X1 is X + 1, X1 =< 5.
move(X/Y, X1/Y) :- X1 is X - 1, X1 > 0.
move(X/Y, X/Y1) :- Y1 is Y + 1, Y1 =< 5.
move(X/Y, X/Y1) :- Y1 is Y - 1, Y1 > 0.
Both are equivalent and this is even clearer.
I've written prolog program which takes initial, pickup and final (x,y,z) coordinates. The agent should reach the final coordinates through the pickup coordinates. When I run the query, the program runs indefinitely. I'm assuming this is because there are a huge number of combinations to search through. So i decreased my constraints in in_range(X,Y,Z). I'm new to prolog, any help would be appreciated. Thanks
in_range(X,Y,Z):-
X > -5,
X < 5,
Y >= 0,
Y < 10,
Z > -5,
Z < 5.
deliver(Ix,Iy,Iz,Px,Py,Pz,Fx,Fy,Fz):-
in_range(Ix,Iy,Iz),
in_range(Px,Py,Pz),
in_range(Fx,Fy,Fz),
move(Ix,Iy,Iz,Px,Py,Pz),
move(Px,Py,Pz,Fx,Fy,Fz).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1-1,
Y is Y1,
Z is Z1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1-1,
Y is Y1,
Z is Z1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1,
Y is Y1-1,
Z is Z1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1,
Y is Y1-1,
Z is Z1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1,
Y is Y1,
Z is Z1-1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1,
Y is Y1,
Z is Z1-1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1+1,
Y is Y1,
Z is Z1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1+1,
Y is Y1,
Z is Z1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1,
Y is Y1+1,
Z is Z1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1,
Y is Y1+1,
Z is Z1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(X1,Y1,Z1,X2,Y2,Z2):-
X is X1,
Y is Y1,
Z is Z1+1,
in_range(X,Y,Z),
X =:= X2,
Y =:= Y2,
Z =:= Z2;
X is X1,
Y is Y1,
Z is Z1+1,
in_range(X,Y,Z),
move(X,Y,Z,X2,Y2,Z2).
move(6,_,_,_,_,_).
move(-6,_,_,_,_,_).
move(_,11,_,_,_,_).
move(_,-1,_,_,_,_).
move(_,_,6,_,_,_).
move(_,_,-6,_,_,_).
The query I'm running is
?-deliver(0,0,0,1,1,1,4,4,4).
You're hitting an infinite recursion because you're doing a depth-first search without tracking visited nodes, by tracing it you'll see for the example that your search gets lost in a (0, 0, 4) <> (0, 0, 5) loop, based on the first move predicate.
Tabling will work for SWI-Prolog, but let's take this opportunity to explore portable solutions as they'll aid your Prolog (and broader CS) learning.
First, let's look at that in_range/3 predicate and turn our locations into terms (if you bump into the word "reification", this is doing that). We want to consider our locations as terms so we can pass them around as whole entities. It also helps us think! We can define what a location is in your world like so:
% loc(X, Y, Z) is a location in 3D integer co-ordinate space
loc(X, Y, Z) :-
between(-5, 5, X),
between(0, 10, Y),
between(-5, 5, Z).
Thus in_range/3 becomes in_range/1:
in_range(Loc) :- call(Loc).
As a bonus, you can generate locations: ?- loc(X, Y, Z).
Now those move predicates can be tidied up to make them much easier to read, trace and think about. To that end, they only define a single move, best to keep journeys
to their own predicate so we can use these individually when we only want a single step. (?- move(loc(1, 1, 2), Step).)
%! move(Loc1, Loc2)
move(loc(X1, Y, Z), loc(X2, Y, Z)) :- X2 is X1 + 1, in_range(loc(X2, Y, Z)).
move(loc(X1, Y, Z), loc(X2, Y, Z)) :- X2 is X1 - 1, in_range(loc(X2, Y, Z)).
move(loc(X, Y1, Z), loc(X, Y2, Z)) :- Y2 is Y1 + 1, in_range(loc(X, Y2, Z)).
move(loc(X, Y1, Z), loc(X, Y2, Z)) :- Y2 is Y1 - 1, in_range(loc(X, Y2, Z)).
move(loc(X, Y, Z1), loc(X, Y, Z2)) :- Z2 is Z1 + 1, in_range(loc(X, Y, Z2)).
move(loc(X, Y, Z1), loc(X, Y, Z2)) :- Z2 is Z1 - 1, in_range(loc(X, Y, Z2)).
Now let's define our delivery predicate. in_range/1 can be used to check the From is valid, whereas Pickup and Dest are expected to take care of that themselves:
deliver(From, Pickup, Dest) :-
in_range(From),
go_to(From, Pickup),
go_to(Pickup, Dest).
So far I've only refactored your code to break predicates down into smaller definitions for more versatility and easier readability. The big change to prevent the infinite recursion is in go_to/2, which is not yet defined. Given that you're doing a search in integer 3D coordinate space, the most suitable search algorithm is A*, which will not only exclude search locations already visited, but will first search locations closest to the intended goal.
go_to(Origin, Destination) :-
a_star(Origin, Destination).
% A* for SWI-Prolog
:- use_module(library(heaps)).
% Use to order search, 3D euclidean distance squared
heuristic_distance(loc(X1, Y1, Z1), loc(X2, Y2, Z2), Distance) :-
Distance is (X1 - X2)^2 + (Y1 - Y2)^2 + (Z1 - Z2)^2.
% Add a list of nodes to the heap
open_add_nodes(Heap, [], _, Heap).
open_add_nodes(Heap, [ToAdd|Tail], Dest, Out) :-
heuristic_distance(ToAdd, Dest, Dist),
add_to_heap(Heap, Dist, ToAdd, Heap1),
open_add_nodes(Heap1, Tail, Dest, Out).
% Get an ordered list of reachable locations from the origin
get_reachable(Loc, Locations) :-
setof(L, move(Loc, L), Locations).
% A* search setup
a_star(Origin, Dest) :-
% Create heap of open search nodes
heuristic_distance(Dest, Origin, Dist),
singleton_heap(Open, Dist, Origin),
% Do the search
a_star(Open, Dest, [Origin]).
% Do the A* search
a_star(Open, Dest, Closed) :-
% Get the most promising Node
get_from_heap(Open, _, Loc, RemainingSearch),
% If we've reached the goal, return the Answer
( Dest = Loc
% Otherwise keep searching
; get_reachable(Loc, ReachableLocations),
% Exclude visited nodes
ord_union(Closed, ReachableLocations, Closed1, ToSearch),
% Add new open nodes to search heap
open_add_nodes(RemainingSearch, ToSearch, Dest, Open1),
% Carry on searching
a_star(Open1, Dest, Closed1)
).
Now A* might take a bit to read and understand, but it'd be much more difficult if we were having to deal with the X, Y, Z coordinates than with locations: every Loc, Dest, Origin and ToAdd is a location. It's also only possible to code because our move/2 predicate only takes a single step, so we can choose not to use Prolog's implicit Depth-First Search.
All this code is in a SWISH Notebook so you can explore it. To learn more about search algorithms I'd recommend the MIT AI lectures on YouTube, 4 and 5 cover search. For Prolog implementations, "The Craft of Prolog", also out of MIT, is excellent.
How can I express the following conjunction more succinctly?
condition(X1, X2, X3, X4, X5) :-
X1 \= X2,
X1 \= X3,
X1 \= X4,
X1 \= X5,
X2 \= X3,
X2 \= X4,
X2 \= X5,
X3 \= X4,
X3 \= X5,
X4 \= X5.
Ideally, I want to use a single goal of a built-in / library predicate.
You could also opt to define a predicate uniques/1 with maplist/2 that succeeds if the list consists of unique elements. Then your predicate condition/5 would act as calling predicate:
:- use_module(library(apply)). % for maplist/2
condition(X1, X2, X3, X4, X5) :-
uniques([X1,X2,X3,X4,X5]).
uniques([]).
uniques([X|Xs]) :-
maplist(dif(X),Xs),
uniques(Xs).
?- condition(1,2,3,4,5).
true.
?- condition(1,2,3,4,1).
false.
And uniques/1 can be used for arbitrary lists:
?- uniques([]).
true.
?- uniques([1,a,6,f(X)]).
true.
?- uniques([A,B,C]).
dif(A, C),
dif(A, B),
dif(B, C).
?- uniques([A,B,A]).
false.
?- uniques(U).
U = [] ;
U = [_G265] ;
U = [_G392, _G395],
dif(_G392, _G395) ;
U = [_G489, _G492, _G495],
dif(_G489, _G495),
dif(_G489, _G492),
dif(_G492, _G495) ;
.
.
.
It depends...
If all Xi are integers and your Prolog supports finite-domain constraints (clpfd), just write:
:- use_module(library(clpfd)).
condition(X1, X2, X3, X4, X5) :-
all_distinct([X1,X2,X3,X4,X5]). % use library predicate
I am writing a program to find all of the squares that a Knight can move to in a game of chess.
For example: validKnightMove(X1/Y1, X2/Y2). where each argument is a co-ordinate pair.
What I've done:
Written the predicate.
Made it deterministic using cuts.
Unit-tested it with PL-Unit.
The predicate works, but I cannot query it in a desirable way from the Prolog shell.
What I'd like to do:
I would like to make a query that will find all of the valid squares I can move to from a given location. For example, ?- validKnightMove(4/4, X/Y), then the shell would search for X and Y values which satisfy the predicate.
However, when I make the query, it simply returns false., despite having valid solutions.
Here is some output from the shell to demonstrate the issue:
1 ?- validKnightMove(4/4, 6/3).
true.
2 ?- validKnightMove(4/4, X/Y).
false.
Here is my code:
This code is admittedly verbose, but should be easy to read.
validKnightMove(X1/Y1, X2/Y2) :- % Right 1, Down 2
onBoard(X2/Y2),
X2 =:= X1 + 1,
Y2 =:= Y1 + 2,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Right 2, Down 1
onBoard(X2/Y2),
X2 =:= X1 + 2,
Y2 =:= Y1 + 1,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Left 2, Down 1
onBoard(X2/Y2),
X2 =:= X1 - 2,
Y2 =:= Y1 + 1,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Left 1, Down 2
onBoard(X2/Y2),
X2 =:= X1 - 1,
Y2 =:= Y1 + 2,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Right 1, Up 2
onBoard(X2/Y2),
X2 =:= X1 + 1,
Y2 =:= Y1 - 2,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Right 2, Up 1
onBoard(X2/Y2),
X2 =:= X1 + 2,
Y2 =:= Y1 - 1,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Left 2, Up 1
onBoard(X2/Y2),
X2 =:= X1 - 2,
Y2 =:= Y1 - 1,
!.
validKnightMove(X1/Y1, X2/Y2) :- % Left 1, Up 2
onBoard(X2/Y2),
X2 =:= X1 - 1,
Y2 =:= Y1 - 2,
!.
onBoard(X/Y) :-
between(1, 8, X),
between(1, 8, Y),
!.
My question is: Why can't prolog find all the solutions for a deterministic predicate?
Note: My prolog version is SWI-Prolog (Multi-threaded, 32 bits, Version 6.2.2)