/*  PARIS.PL  */
 
 
/*
        ******************************************************
        *                   PARIS ARNOLD                     *
        *                St. John's College                  *
        *                                                    *
        *      Prolog program: completed 17th June 1992      *
        ******************************************************
 
 
This is a game written by Paris Arnold for this year's practical.
I helped with some of it, the use of findall for example, and level,
but otherwise, it's pretty much his own work.
*/
 
 
/*
Main predicate.
---------------
*/
 
 
run :-
    initialise,
    display_instructions,
    /*  These check are performed as the play may die or exit immediately
    */
    check_security,
    check_security,
    check_threats,
    check_exit,
 
    cycle.
 
 
/*  initialise sets up various variables in the game, such as the
    location of all the objects that take part in it, and the player's
    initial energy level.
*/
initialise :-
    retractall( at(_,_) ),
    retractall( at_player(_) ),
    retractall( energy(_) ),
    retractall( holding(_) ),
    retractall( name_of_player(_) ),
    get_name_of_player( Player ),
    random_square( P ),
    assert( at_player(P) ),
    random_square( T ),
    assert( at(tutor,T) ),
    random_square( H ),
    assert( at(hangover,H) ),
    random_square( A ),
    assert( at(aspirin,A) ),
    random_square( E ),
    assert( at(essay, E) ),
    random_square( X ),
    assert( at(exit, X) ),
    random_energy( NRG ),
    assert( energy(NRG) ).
 
 
cycle :-
    get_direction( Direction ),
    decrease_energy,
    update_square_if_possible( Direction ),
    output('STATUS:'~),
    name_of_player(Player),
    at_player(P),
    energy(NRG),
    output(Player<>' is at'...P~),
    output('Energy level is'...NRG~),
    move_monsters,
    check_security,
    check_security,
    check_threats,
    check_threats,
    check_exit,
    at(tutor,T),
    at(hangover,H),
    output('The tutor monster is at'...T~),
    output('The hangover monster is at'...H~),nl,
    cycle.
 
 
/*  random_square( S- ):
        Return a random square.
*/
random_square( S ) :-
    random( 25, N ),
    /*  Number of highest square is 25, and random(N) returns
        an integer between 0 and N-1.
    */
    S is N + 1.
 
 
/*  random_energy( E- ):
        Return a random energy between 50 and 75 inclusive.
*/
random_energy( E ) :-
    random( 25, N ),
    E is N + 51.
 
 
display_instructions:-
    output('                        **** THE GAME ****'~~),
    name_of_player(Player),
    output('Welcome to the real world,'...Player<>'.'~~),
    output('The rules are simple. You are in a maze with two monsters chasing after'~),
    output('you. They are the hangover monster and the tutor monster.'~),
    output('If they find you they will kill you unless of course you have the appropriate'~),
    output('security - obviously some aspirin or an essay, respectively!'~),
    output('You must move around the maze going Up, Down, Left or Right trying to find'~),
    output('the securities.  You have a limited amount of energy which is decreased'~),
    output('every time you attempt to move. You will die if you run out of energy.'~),
    output('There is an exit somewhere. Find that before dying and you have successfully'~),
    output('completed the game.'~~),
 
    at_player(P),
    at(tutor, T),
    at(hangover, H),
    output(Player<>', you are at square'...P~),
    output('The tutor monster is at'...T~),
    output('The hangover monster is at'...H~),nl,
 
    output('I\'m not going to say where the securities or the exit can be found.'~),
    output('You\'ll have to look for them by yourself!'~~),nl,
 
    pause.
 
 
/*
The map.
--------
*/
 
 
/*  joins( S1?, S2? ):
        Square S1 joins S2.
 
The map with connections marked by - or |
 
    1 -  2  - 3  - 4  - 5
    |         |         |
    6    7  - 8    9   10
    |              |    |
    11 -12 - 13 - 14 - 15
    |    |    |    |
    16   17  18   19 - 20
    |         |    |    |
    21   22 - 23  24   25
 
*/
joins( 1, 2  ).
joins( 2, 3  ).
joins( 3, 4  ).
joins( 4, 5  ).
joins( 1, 6  ).
joins( 3, 8  ).
joins( 5, 10 ).
joins( 7, 8  ).
joins( 6, 11 ).
joins( 7, 12 ).
joins( 9, 14 ).
joins( 10, 15 ).
joins( 11, 12 ).
joins( 12, 13 ).
joins( 13, 14 ).
joins( 14, 15 ).
joins( 11, 16 ).
joins( 12, 17 ).
joins( 13, 18 ).
joins( 14, 19 ).
joins( 19, 20 ).
joins( 16, 21 ).
joins( 18, 23 ).
joins( 19, 24 ).
joins( 20, 25 ).
joins( 22, 23 ).
 
 
/*  linked ( S1, S2 ).
        Square S1 is linked to S2 if
            S2 joins S1 or
            S1 joins S2.
*/
linked( S1, S2 ):-
    joins( S2 , S1).
linked( S1, S2 ):-
    joins( S1, S2).
 
 
/*  level( S?, L? ):
        L is the level of square S.
        The levels are horizontal, one per line of the map.
*/
level( S, L ) :-
    nonvar( S ),
    !,
    L is ( S + 4 ) div 5.
 
level( S, L ) :-
    nonvar( L ),
    !,
    Lowest is L*5 - 4,
    Highest is L*5,
    between( Lowest, Highest, S ).
 
level( S, L ) :-
    between( 1, 5, L ),
    level( S, L ).
 
 
/*  joins_right ( S1, S2 ).
        S2 is to the right of square S1 and there is a link.
*/
 
joins_right( S1, S2 ):-
    level( S1, L1 ), level ( S2, L2 ), (L1 = L2),
    S2 > S1, linked( S2, S1 ).
 
 
/* joins_left ( S1, S2 ):
        Square S1 is to the left of square S2 and there is a link.
*/
 
joins_left( S1, S2 ):-
    joins_right( S2, S1 ).
 
 
/* joins_down( S1, S2 ):
        S1 joins down to S2 if S1 is linked to S2 and is on a higher level
*/
 
joins_down( S2, S1):-
    level( S1, L1), level( S2, L2 ), (L1 > L2),
    linked( S1, S2).
 
 
/* joins_up (S2, S1):
        S2 joins up to S1 if S2 is linked to S1 and S2 is on a lower level.
*/
 
joins_up( S1, S2):-
    joins_down( S2, S1).
 
 
/*
World updating.
---------------
*/
 
 
/*  check_security checks to see if Player is in the same sqaure as
    a security (ie aspirin or essay) and if he is then the security
    is picked up: Player is HOLDING(OBJECT)
*/
check_security :-
    at_player( X ),
    at( Obj, X ),
    is_a(Obj,security),
    !,
    assert( holding(Obj) ),
    retractall( at(Obj,X) ),
    output( 'You have picked up an'...Obj~ ).
 
check_security.
 
 
/*  check_exit checks if to see if the player is in the square with
    the exit. If he is, and this is only run once threats and securities
    have been checked (ie he can still die in this square), then he exits,
    the game is over and the program aborts.
*/
check_exit :-
    at_player( X ),
    at(exit, X ), 
    !,
    name_of_player( P ),
    energy( NRG ),
    output( 'Well done'... P<>'. You have found the exit!' ), nl,
    output( 'You have finished the game with'...NRG...'energy points left.' ),nl,nl,
    output('Now wasn\'t that the most exciting computer game you have EVER played?!'~~),
    abort.
 
check_exit.
 
 
/*  check_threats checks for fatal occurences as below. If any one of the
    criteria is met then the player dies:
 
    1)  Player's energy level =< 0
    2)  Player is in the same square as Hangover monster with no aspirin
    3)  Player is in the same sqaure as Tutor monster with no essay
 
    If a monster is encountered but the player is holding the relevant
    security then he survives, the security and the monster are randomly
    reassigned to new squares and the game continues.
*/
check_threats :-
    at_player( X ),
    energy( NRG ),
    NRG =< 0,
    !,
    output('You have run out of energy and can go no further.'~),
    die.
 
check_threats :-
    at_player( X ),
    at( tutor, X ),
    not( holding(essay) ),
    !,
    output('You have just encountered the tutor monster.'~),
    output('Unfortunately you didn\'t have an essay to placate him.'~),
    die.
 
check_threats :-
    at_player( X ),
    at( hangover , X ),
    not(holding(aspirin) ),
    !,
    output('You have just encountered the hangover monster.'~),
    output('Unfortunately you didn\'t have any aspirin to placate him.'~),
    die.
 
check_threats :-
    at_player ( X ),
    at(tutor, X ),
    holding( essay ), 
    !,
    output('You have encountered the Tutor monster, but have placated him '~),
    output('with an essay.'), nl,
    output('You will now have to find another essay because another tutor '~),
    output('could be along any minute!'~~),
 
    retractall( at (tutor, T ) ),
    retractall( at (essay, E ) ),
    random_square( T ),
    assert( at(tutor,T) ),
    random_square( E ),
    assert( at(essay, E) ).
 
check_threats :-
    at_player( X ),
    at(hangover, X ),
    holding( aspirin ),
    !,
    output('You have just had a lucky escape from a hangover.'~),
    output('Fortunately you had some aspirin on you to protect yourself.'~),
    output('You\'ve used them all up now - better find some more.'~),
    output('Who knows when the next hangover may strike?!'~~),
 
    retractall( at (hangover, H ) ),
    retractall( at (aspirin, A ) ),
    random_square( H ),
    assert( at(hangover,H) ),
    random_square( A ),
    assert( at(aspirin, A) ).
 
check_threats.
 
 
/*  If check_threats produces are valid fatal meeting or incident then
    DIE is run and the player is told he's dead. Program execution aborts
*/
die :-
    name_of_player( P ),
    output( P<>', what can I say? You\'re dead!'~~),
    output('Now wasn\'t that the most exciting computer game you have EVER played?!'~~),
    abort.
 
 
/*  move_monsters moves a monster one square using the same valid
    connections as the player.
*/
move_monsters:-
    move_monster( tutor ),
    move_monster( hangover ).
 
 
move_monster( M ) :-
    at( M, X ),
    findall( Next, linked(X,Next), Squares ),
    random_element( Squares, S, _ ),
    retractall( at(M,X) ),
    assert( at(M,S) ).
 
 
/*  Every time player attempts to or succeeds in moving
    his energy level is reduced by 3.
*/
decrease_energy :-
    energy( NRG ),
    NewNRG is NRG-3,
    retractall( energy(_) ),
    assert( energy(NewNRG) ).
 
 
/*  update_square_if_possible takes the direction the player has input
    and attempts to move him in that direction. If there is a link
    between squares in that directin then the player is moved. If not,
    he stays put and is told as much.
*/
update_square_if_possible( D ):-
    at_player( P ),
    update_square_if_possible_1( D, P ).
 
 
update_square_if_possible_1( l, P ):-
    joins_left(P,N),
    !,
    retractall(at_player(P)),
    assert( at_player(N) ).
 
update_square_if_possible_1( r, P ):-
    joins_right(P,N),
    !,
    retractall(at_player(P)),
    assert( at_player(N) ).
 
update_square_if_possible_1( u, P ):-
    joins_up(P,N),
    !,
    retractall(at_player(P)),
    assert( at_player(N) ).
 
update_square_if_possible_1( d, P ):-
    joins_down(P,N),
    !,
    retractall(at_player(P)),
    assert( at_player(N) ).
 
update_square_if_possible_1( D, P):-
    output('You cannot move in that direction. There is no connection that way.'), nl,
    output('You will be pleased to know that you have still lost some energy for'~),
    output('trying to move and the monsters are still after you!'~),
    output('You are still at square'...P),nl,nl.
 
 
/*  is_a defines which objects are monsters and which securities for use in
    the check procedures above.
*/
is_a( tutor, monster ).
is_a( hangover, monster ).
is_a( essay, security ).
is_a( aspirin, security ).
 
 
/*
User interface.
---------------
*/
 
 
/*  get_direction asked the player which way he wants to move next,
    and checks that this is a valid direction
*/
get_direction( Direction ) :-
    ask_atom( 'Direction (u,d,r,l)?',
              a( X, (X=u;X=d;X=l;X=r), 'u,d,l, or r' ),
              Direction
    ).
 
 
pause :-
    ask_atom('Please hit RETURN to continue', _ ).
 
 
/*  get_name_of_player( P- ):
        Ask for player's name, as atom.
*/
get_name_of_player( P_Uc ) :-
    ask_atom( 'Please type the player\'s name?', a, P ), nl,
    name( P, [C1|Cn] ),
    chars_to_uppercase( [C1], [UC1] ),
    name( P_Uc, [UC1|Cn] ),
    assert(name_of_player(P_Uc) ).
 
 
/*
Miscellaneous.
--------------
*/
 
 
/*  show_state:
        Display the state of the game, for debugging.
*/
show_state :-
    listing(name_of_player),
    listing(at_player),
    listing(at),
    listing(energy),
    listing(holding).
 
 
/*  between( Lo+, Hi+, X- ):
        Successively sets X to all integers between Lo and Hi.
*/
between( Lo, Hi, _ ) :-
    Lo > Hi,
    !,
    fail.
 
between( Lo, Hi, Lo ).
 
between( Lo, Hi, X ) :-
    Next is Lo + 1,
    between( Next, Hi, X ). 
