Trains Schedule Puzzle
% Add times in the form [hours, minutes past]. % tadd(A, B, A+B). % A and B must be bound at call. tadd([Ah, Am], [Bh, Bm], [Sh, Sm]) :- Sm is Am + Bm, Sm < 60, Sh is Ah + Bh. tadd([Ah, Am], [Bh, Bm], [Sh, Sm]) :- X is Am + Bm, X >= 60, Sm is X - 60, Sh is Ah + Bh + 1. % Tell if a time in the form [hours, minutes past] is <= another. % tleq(A, B) % succeeds if A is earlier or the same as B. tleq([Ah, Am], [Ah, Bm]) :- Am =< Bm. tleq([Ah, _], [Bm, _]) :- Ah < Bm. % Encode the train schedule. The fact at(station, time, train) means that % the indicated train is stopped at the station at the indicated time. at(alphaville, [7,0], e1). at(fortuna, [8,10], e1). at(alphaville, [7,10], e2). at(betaville, [7,40], e2). at(clarksville, [8,30], e2). at(discovery, [9,10], e2). at(ephemeral, [10,0], e2). at(fortuna, [10,30], e2). at(gamesport, [11,20], e2). at(hope_springs, [12,0], e2). at(betaville, [7,20], e3). at(clarksville, [7,50], e3). at(discovery, [8,10], e3). at(fortuna, [8,40], e3). at(alphaville, [7,20], e4). at(discovery, [7,50], e4). at(fortuna, [8,30], e4). at(gamesport, [9,00], e4). at(hope_springs, [9,30], e4). at(betaville, [7,30], e5). at(clarksville, [8,20], e5). at(gamesport, [8,50], e5). at(hope_springs, [9,0], e5). at(alphaville, [7,40], e6). at(betaville, [8,10], e6). at(discovery, [8,50], e6). at(fortuna, [9,20], e6). at(gamesport, [9,40], e6). at(hope_springs, [10,0], e6). at(betaville, [7,50], e7). at(discovery, [8,10], e7). at(fortuna, [8,40], e7). at(alphaville, [7,50], e8). at(ephemeral, [8,30], e8). at(fortuna, [9,0], e8). at(gamesport, [9,20], e8). at(hope_springs, [9,40], e8). at(alphaville, [8,30], e9). at(betaville, [9,0], e9). at(ephemeral, [9,30], e9). at(fortuna, [9,50], e9). at(gamesport, [10,20], e9). at(hope_springs, [10,50], e9). at(clarksville, [8,40], e10). at(gamesport, [9,10], e10). at(alphaville, [9,30], e11). at(betaville, [10,0], e11). at(discovery, [10,30], e11). at(fortuna, [11,0], e11). at(hope_springs, [11,30], e11). at(alphaville, [10,10], e12). at(betaville, [10,50], e12). at(clarksville, [11,30], e12). at(ephemeral, [12,0], e12). at(alphaville, [9,0], w1). at(clarksville, [8,10], w1). at(discovery, [8,0], w1). at(ephemeral, [7,40], w1). at(fortuna, [7,20], w1). at(hope_springs, [7,0], w1). at(alphaville, [10,50], w2). at(betaville, [10,10], w2). at(clarksville, [9,30], w2). at(discovery, [9,0], w2). at(ephemeral, [8,20], w2). at(fortuna, [8,0], w2). at(gamesport, [7,30], w2). at(hope_springs, [7,10], w2). at(alphaville, [10,20], w3). at(clarksville, [9,40], w3). at(ephemeral, [9,10], w3). at(fortuna, [8,50], w3). at(gamesport, [8,20], w3). at(hope_springs, [7,50], w3). at(clarksville, [10,0], w4). at(gamesport, [8,40], w4). at(hope_springs, [8,20], w4). at(alphaville, [11,10], w5). at(discovery, [9,40], w5). at(hope_springs, [8,50], w5). at(discovery, [9,50], w6). at(hope_springs, [9,10], w6). at(discovery, [10,5], w7). at(hope_springs, [9,15], w7). at(betaville, [10,30], w8). at(discovery, [10,0], w8). at(ephemeral, [9,40], w8). at(hope_springs, [9,20], w8). at(betaville, [11,20], w9). at(discovery, [10,50], w9). at(ephemeral, [10,30], w9). at(gamesport, [10,10], w9). at(hope_springs, [9,50], w9). at(betaville, [10,40], w10). at(ephemeral, [10,10], w10). at(alphaville, [12,5], w11). at(betaville, [11,30], w11). at(discovery, [11,10], w11). at(fortuna, [10,50], w11). at(hope_springs, [10,20], w11). at(alphaville, [11,50], w12). at(clarksville, [11,20], w12). at(fortuna, [10,40], w12). % The predicate ride meaans that if you are available for boarding at % station Leave at time Now, and take train Train, you will arrive at % station Arrive at time Then. Now must be bound when used, and Leave % is sort of assumed to be, though I don't think the rule requires that. ride(Leave, Now, Arrive, Then, Train) :- at(Leave, LeaveTime, Train), tleq(Now, LeaveTime), at(Arrive, Then, Train), tleq(LeaveTime, Then). % The predicate trip(Leave, Now, Arrive, Then, Path, Trains) is true % if, given you are available to board a train at station Leave at time Now, % you can reach station Arrive at time Then, passing through a series of % stations given by the list Path, taking a series of trains given by the % list Trains. The list Path is a list of stations beginning with Leave % and ending with Arrive. The Predicate is requires at least 4 minutes % at each intermediate station for transfer time. I assume that the rule % is used with Leave and Now already bound. % This is the basis rule for trip. It represents a trip consisting of one % train ride, and is essentially a use of ride. trip(Leave, Now, Arrive, Then, [Leave, Arrive], [Train]) :- ride(Leave, Now, Arrive, Then, Train). % This is the recursive rule for Trip. It is satisfied if you can find a % ride from station Leave to some intermediate station XferSta on train Train, % arriving at time XWhen. After arriving, you must wait four minutes until % time WhenAvail, when you are available to board another train. You must % then be able to take a trip from XferSta to Arrive, arriving there at % time Then. The list of stations is Leave plus the stations traversed % in your trip from XferSta to Arrive. The list of trains is the one you % use to get to XferSta plus the ones you use from XferSta to Arrive. trip(Leave, Now, Arrive, Then, [Leave, XferSta | Route], [Train | Trains]) :- ride(Leave, Now, XferSta, XWhen, Train), Leave \= XferSta, Arrive \= XferSta, tadd(XWhen, [0,4], WhenAvail), trip(XferSta, WhenAvail, Arrive, Then, [XferSta | Route], Trains). % The clauses % Leave \= XferSta, Arrive \= XferSta, % just make sure that the system does not waste time padding trips with rides % that never leave the station. The station list is given as % [Leave, XferSta | Route] % in the rule just to give individual access to the first two members of % the list when writing the rule. % This is the top-level call. It is satisfied if you can, starting at 7:00 % at alphaville, take a trip to hope_springs, arrive at HopeTime, wait one % at least 1 minute until HopeLeaveTime, then travel back to alphaville, % arriving at PlaneTime, but not later than noon. The list Trains is just % the entire list of trains there and back, taken from each call to % trip and appended. The list Route is the list of stations likewise % created, except that hope_springs is not repeated. to_flight(PlaneTime, Route, Trains) :- trip(alphaville, [7,0], hope_springs, HopeTime, RouteTo, TrainsTo), tadd(HopeTime, [0,1], HopeLeaveTime), trip(hope_springs, HopeLeaveTime, alphaville, PlaneTime, [hope_springs | RouteFrom], TrainsFrom), tleq(PlaneTime, [12,0]), append(RouteTo, RouteFrom, Route), append(TrainsTo, TrainsFrom, Trains). % I use % [hope_springs | RouteFrom] % instead of the simle alternative % RouteFrom % to set RouteFrom to the route for the return trip _less_ the starting % station, hope_springs. This avoids repeating hope_springs in the final % list after the concatination. This works because I know the route from % the second use of trip must begin at hope_springs, so by forcing the % the route to resolve with [hope_springs | RouteFrom], I force the % variable to become the route less its first member.