MC logo

Trains Schedule Puzzle

  Prolog Examples

<<Simple List Operations trains.pl Peg Jump 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.
<<Simple List Operations Peg Jump Puzzle>>