:- op(700, xfx, l). :- op(700, xfx, r). From l To :- From to To-_. From r To :- From to _-To. answer(Answer) :- findall(Node, (Node to _, atom_chars(Node, [_, _, a])), Starts), routes(Starts, [Route1 | RestOfRoutes]), foldl(unify2, RestOfRoutes, Route1, _-(_-Answer-_)). % unify2 combines 2 routes into one with its own stride-offsets-dests unify2(Route1, Route2, NewRoute) :- writef('Combining %t + %t', [Route1, Route2]), once(findnsols(2, S, converge(Route1, Route2, S), SolutionPair)), SolutionPair = [NewA-LenA-NewZ, _-LenB-_], NewStride is LenB - LenA, NewRoute = NewA-(NewStride-LenA-[0-NewZ]), writef(' --> %t\n', [NewRoute]). % Len = Stride1*X1 + Offset1 + Dest1 = Stride2*X2 + Offset2 + Dest2 % For performance, Route1's Stride should =< Route2's Stride converge(Route1, Route2, NewA-Len-NewZ) :- Route1 = A1-(Stride1-Offset1-Dests1), Route2 = A2-(Stride2-Offset2-Dests2), natnum(X2), pick([Dests1, Dests2], [Dest1-Z1, Dest2-Z2]), 0 is (Stride2*X2 + Offset2 + Dest2 - Offset1 - Dest1) mod Stride1, Len is Stride2*X2 + Offset2 + Dest2, atom_concat(A1, A2, NewA), atom_concat(Z1, Z2, NewZ). routes(Starts, Routes) :- maplist([S, S-Route]>>(route_at(S, Route)), Starts, Routes). % route_at(N, Node, Dests, AllDests) means going N steps will arrive at Node % passing through Dests. route_at(Start, Route) :- route_at(0, Start, [], Route). route_at(N, FirstZ, [FirstZN-FirstZ | Zs], Stride-Offset-AllZs) :- Stride is N - FirstZN, direction_looped(Stride), Offset is FirstZN, maplist({Offset}/[N-X, M-X]>>(M is N-Offset), [FirstZN-FirstZ | Zs], AllZs), !. route_at(N, Node, Dests, AllDests) :- ( atom_chars(Node, [_, _, z]) -> append(Dests, [N-Node], NextDests) ; NextDests = Dests), Nplus1 is N + 1, step_at(N, Step), call(Step, Node, NextNode), route_at(Nplus1, NextNode, NextDests, AllDests). % Step is the N-th step (counting starts from 0). step_at(N, Step) :- direction_list(Dir), length(Dir, DirLen), Remainder is N mod DirLen, nth0(Remainder, Dir, Step). direction_looped(Len) :- direction_list(D), length(D, DLen), 0 is Len mod DLen. direction_list(Dir) :- direction(Str), atom_chars(Str, Dir). % pick one item from each sublist of ListOfLists & put them into Items in order. % [[1,2,3], [4], [5,6]] -> [1,4,5]; [1,4,6]; [2,4,5]; [2,4,6]; [3,4,5]; [3,4,6]. pick(ListOfLists, Items) :- maplist([SubList, X]>>(member(X, SubList)), ListOfLists, Items). natnum(0). natnum(N) :- natnum(N0), N is N0 + 1.