% 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.