/*  MONOPOLY.PL  */
 
 
/*  square( Number?, Identification? ):
        Number is the square's position, origin at GO=1.
        Identification is one of
            { [ colour, Name ], [ station, Name ], [ utility, Name ],
              go, supertax, income_tax, chance, community_chest,
              jail, go_to_jail, free_parking
            }.
*/
square( 1, go ).
square( 2, [colour, old_kent_road] ).
square( 3, community_chest ).
square( 4, [colour, whitechapel] ).
square( 5, income_tax ).
square( 6, [station,kings_cross ] ).
square( 7, [colour,angel_islington] ).
square( 8, chance ).
square( 9, [colour,euston_road] ).
square( 10, [colour,pentonville] ).
square( 11, just_visiting ).
square( 12, [colour,pall_mall] ).
square( 13, [utility,electricity] ).
square( 14, [colour,whitehall] ).
square( 15, [colour,northumberland_avenue] ).
square( 16, [station,marylebone] ).
square( 17, [colour,bow_street] ).
square( 18, community_chest ).
square( 19, [colour,marlborough_street] ).
square( 20, [colour,vine_street] ).
square( 21, free_parking ).
square( 22, [colour,strand] ).
square( 23, chance ).
square( 24, [colour,fleet_street] ).
square( 25, [colour,trafalgar_square] ).
square( 26, [station,fenchurch_street] ).
square( 27, [colour,leicester_square] ).
square( 28, [colour,coventry_street] ).
square( 29, [utility,water] ).
square( 30, [colour,picadilly] ).
square( 31, go_to_jail ).
square( 32, [colour,regent_street] ).
square( 33, community_chest ).
square( 34, [colour,oxford_street] ).
square( 35, [colour,bond_street] ).
square( 36, [station,liverpool_street] ).
square( 37, chance ).
square( 38, [colour,park_lane] ).
square( 39, supertax ).
square( 40, [colour,mayfair] ).
 
 
/*  colour( Name?, Colour? ):
        There's one of these for each square on which houses can be
        built. Colour is one of
            { brown, light_blue, purple, orange, red, yellow,
              green, dark_blue
            }.
*/
colour( old_kent_road, brown ).
colour( whitechapel, brown ).
colour( angel_islington, light_blue ).
colour( pentonville, light_blue ).
colour( euston_road, light_blue ).
colour( pall_mall, purple ).
colour( whitehall, purple ).
colour( northumberland_avenue, purple ).
colour( bow_street, orange ).
colour( marlborough_street, orange ).
colour( vine_street, orange ).
colour( strand, red ).
colour( fleet_street, red ).
colour( trafalgar_square, red ).
colour( coventry_street, yellow ).
colour( leicester, yellow ).
colour( picadilly, yellow ).
colour( regent_street, green ).
colour( oxford_street, green ).
colour( bond_street, green ).
colour( mayfair, dark_blue ).
colour( park_lane, dark_blue ).
 
 
/*  move( Player+, There-, Here+ ):
        Update Player's position.
*/
move( Player, There, Here ) :-
    retract( at(Player,There) ),
    asserta( at(Player,Here) ).
 
 
/*  credit( Player+, C+ ):
        Add C to Player's balance.
*/
credit( Player, C ) :-
    retract( balance(Player,Old) ),
    New is Old + C,
    asserta( balance(Player,New) ).
 
 
/*  debit( Player+, D+ ):
        Subtract D from Player's balance, unless it would go to
        zero or below, state which case call
        bankruptcy_action.
*/
debit( Player, D ) :-
    retract( balance(Player,Old) ),
    New is Old - D,
    (
        New > 0
    ->
        asserta( balance(Player,New) )
    ;
        bankruptcy_action( Player, Old, D, Action ),
        perform( Player, Action ) 
    ).
 
 
/*  start( Players+ ):
        Initialise the game. Players is a list of players.
*/
start( Players ) :-
    retractall( balance(_,_) ),
    retractall( owns(_,_) ),
    retractall( at(_,_) ),
    retractall( state(_,_) ),
    asserta( balance(bank,100000) ),
    forall( square(_,[colour,Property]), asserta(owns(bank,Property)) ),
    forall( square(_,[station,Property]), asserta(owns(bank,Property)) ),
    forall( square(_,[utility,Property]), asserta(owns(bank,Property)) ),
    start_players( Players ).
 
 
start_players( [] ) :- !.
 
start_players( [Player|Others] ) :-
    assert( at(Player,go) ),
    assert( balance(Player,1500) ).
 
 
throw( IsDouble, Total ) :-
    random( 1, 6, Die1 ),
    random( 1, 6, Die2 ),
    Total is Die1 + Die2,
    ( Die1=Die2 -> IsDouble=is_double ; IsDouble=no_double ).
 
 
step( Player ) :-
    throw( IsDouble, Total ),
    at( Player, Old ),
    New is (Old + Total) mod 41,
    update( Player, IsDouble, Old, New ).
 
 
update( Player, Is_double, jail, New ) :-
    state( Player, [jail,Times] ),
    Times < 2,
    !,
    get_out_of_jail_alert( Player, Is_double, Act ),
    (
        Act = accept_double
        %  Only allowed if Player had a double!
    ->
        retract( state( Player, [jail,_] ) )
    ;
        Act = play_get_out_of_jail_card
    ->
        retract( has_card( Player, get_out_of_jail ) ),
        retract( state( Player, [jail,_] ) )
    ;
        Act = [pay,50]
    ->
        debit( Player, 50 ),
        retract( state( Player, [jail,_] ) )
    ;
        TimesPlus1 is Times + 1,
        retract( state(Player,[jail,_]) ),
        asserta( state(Player,[jail,TimesPlus1]) )
    ).
 
update( Player, Is_double, jail, New ) :-
    state( Player, [jail,_] ),
    !,
    must_get_out_of_jail_alert( Player, Is_double, Act ),
    (
        Act = accept_double
        %  Only allowed if Player had a double!
    ->
        retract( state( Player, [jail,_] ) )
    ;
        Act = play_get_out_of_jail_card
    ->
        retract( has_card( Player, get_out_of_jail ) ),
        retract( state( Player, [jail,_] ) )
    ;
        Act = [pay,50]
    ->
        debit( Player, 50 ),
        %  This one may cause trouble.
        retract( state( Player, [jail,_] ) )
    ;
        true % should never happen
    ).
 
update( Player, _, Old, New ) :-
    check_go( Player, Old, New ),
    move( Player, Old, New ),
    deal_with_square( Player, New ).
 
 
/*  check_go( Player+, Old+, New+ ):
        If Player has passed GO in going from Old to New,
        credit him #200.
*/
check_go( Player, Old, New ) :-
    passed_go( Player, Old, New ),
    !,
    credit( Player, 200 ).
 
 
/*  passed_go( Player+, Old+, New+ ):
        True if Player has passed GO from Old to New.
*/
passed_go( Player, Old, New ) :-
    true.
 
/*  deal_with_square( Player+, Old+, New+ ):
*/
 
 
start :-
    get_names_of_players( Players ),
    choose_order( Players, Players_ ),
    start_game( Players_ ),
    asserta( order(Players_) ),
    next_throw.
 
 
next_throw :-
    get_next_player( Player ),
    throw( Is_double, Total ),
    new_square_should_be( Player, New ),
    (
        state( Player,[jail] )
    ->
        already_in_jail(Player,Is_double,Total)
    ;
        New = go_to_jail
    ->
        go_to_jail(Player,Is_double,Total)
    ;
        move( Player, New )
    ).
 
 
already_in_jail( Player, Is_double, Total ) :-
    state( Player, [jailed,Times] ).
 
go_to_jail( Player, Is_double, Total ) :-
    move( Player, _, jail ),
    asserta( state(Player,[jailed,0]) ),
    get_out_of_jail_alert( Player, 0, Act ),
    get_out_of_jail( Player, Act ).
 
 
get_out_of_jail( Player, play_get_out_of_jail_card ) :-
    retract( has(Player,get_out_of_jail_card) ),
    retract( state(Player,[jailed,_]) ),
    next_throw.
 
get_out_of_jail( Player, [pay,50] ) :-
    debit( Player, 50 ),
    retract( state(Player,[jailed,_]) ),
    next_throw.
 
get_out_of_jail( Player, accept_doubles ) :-
    next_throw.
 
 
move( Player, New ) :-
    check_go( Player ),
    new_square( Player, New ).
 
 
new_square( Player, supertax ) :-
    debit( Player, 100 ),
    next_throw.
 
new_square( Player, income_tax ) :-
    debit( Player, 200 ),
    next_throw.
 
new_square( Player, free_parking ) :-
    next_throw.
 
new_square( Player, jail ) :-
    next_throw.
 
new_square( Player, chance ) :-
    pick_chance_card( Player, Card ),
    do_card( Player, Card ).
 
new_square( Player, community_chest ) :-
    pick_community_chest_card( Player, Card ),
    do_card( Player, Card ).
 
new_square( Player, [USC,Which] ) :-
    (
        owns( Owner, [USC,Which] )
    ->
        rent_alert( Player, Owner, [USC,Which] )
    ;
        buy_alert( Player, [USC,Which] )
    ).
 
 
rent_alert( Player, Owner, [USC,Which] ) :-
    rent_alert( Owner, Player, [USC,Which], Action ),
    (
        Action = nowt
    ->
        next_throw
    ;
        Action = [pay,Rent]
    ->
        pay_rent( Player, Owner, Rent )
    ;
        true
    ).
 
 
pay_rent( Player, Owner, Rent ) :-
    debit( Player, Rent ),
    credit( Owner, Rent ),
    (
        Action = nowt
    ->
        next_throw
    ;
        true
    ).
 
 
buy_alert( Player, [USC,Which] ) :-
    buy_alert( Player, [USC,Which], Action ).
