-module(jones). %The "homage a jones" treasure chamber puzzle. %Set up the puzzle -export([start/0]). %These are the actions. Call them with show:look() or show:take(statue). -export([look/0, take/1, lift/0, drop/0, place/0, leave/0]). %include record and macro definitions. -include("../include/records.hrl"). -include("../include/macros.hrl"). %Start sets up the simulation and clears out the existing data, if any is around. start() -> %Herodotus and Simulation are processes that can be messaged. herodotus:start(jones_hero), thucydides:start(jones_hero, jones_sim), %Reset empties out the Mnesia databases so that each playthrough is fresh. %it also sets the time to 0. This uses the process dictionary, a small bit of mutable state. reset(), %fill out the i_arcs here setup_iarc(look), setup_iarc(take), setup_iarc(lift), setup_iarc(drop), setup_iarc(place), setup_iarc(leave) . %Stop terminates the simulation and herodotus processes. stop() -> thucydides:stop(jones_sim), herodotus:stop(jones_hero) . %reset is a private function that resets the time to 0 and clears the histories, %filling them up with initial state. reset() -> herodotus:clear(jones_hero), thucydides:clear(jones_sim), put(jones_time, 0), RockOnFloor = ?FLUENT({inventory, floor}, 0, infinity, removed, [rock], _Val), StatueOnPlate = ?FLUENT({inventory, plate}, 0, infinity, removed, [statue], _Val), EmptyHands = ?FLUENT({inventory, player}, 0, infinity, removed, [], _Val), DoorOpen = ?FLUENT({open, [door, plate]}, 0, infinity, closed, open, _Val), Fluents = [RockOnFloor, StatueOnPlate, EmptyHands, DoorOpen], herodotus:trigger(incident:new(init, Fluents, [], 0), main, jones_hero), tick() . %Current_time is a private function that returns the time. current_time() -> get(jones_time) . %Tick is a private function that increments the time. tick() -> put(jones_time, current_time() + 1) . %setup_iarc is a function of arity 1 that sets up the archetype corresponding to the given label %we could just as easily write setup_look, setup_take, setup_lift, etc, but I wanted to do this %to show the matching feature of Erlang setup_iarc(look) -> %This macro creates a 'Fluent Archetype'. All of the arguments are in the context of %a function. The available vars are (_Time, _Bindings) for the first five code arguments, where %_Time is the time the Incident is triggered; and (Self, _Now, _Args, _Time, _EndTime) for %the sixth code argument. Self is the Fluent being evaluated. %This macro provides its own stack mode, so that definition isn't shown yet. FA = ?A_ADD({times_looked, %modify times_looked dict:fetch("Looker", _Bindings)}, %for the looker _Time, %at the given time infinity, %forever persist, %if this gets ended somehow, it won't change 1), %increment by one %This is primarily for documentation and discovery purposes, and may be removed later Vars = ["Looker"], %Look requires nothing in particular Reqs = [], %Look doesn't depend on any fluents ExtraInputs = [], %Look doesn't cancel anything Cancels = [], %Create the archetype Look = i_arc:new(look, main, [FA], Vars, Reqs, ExtraInputs, Cancels), %Add the archetype to the simulation thucydides:add_iarc(Look, jones_sim) ; setup_iarc(take) -> %A complete fluent archetype, including stack mode. TakerAddItem = ?A_FLUENT({inventory, %modify inventory dict:fetch("Taker", _Bindings)}, %of the taker _Time, %at the given time infinity, %forever removed, %if it gets canceled, it was 'removed' [dict:fetch("Item", _Bindings)], %This is the item involved fluent:fmerge(_Net, _Val)), %and it's merged into the net value SourceRemoveItem = ?A_FLUENT({inventory, %modify inventory dict:fetch("Source", _Bindings)}, %of the source _Time, %right now infinity, %until forever returned, %if it is canceled, it will be 'returned' [dict:fetch("Item", _Bindings)], %This is the item involved fluent:fsubtract(_Net, _Val)), %And it's subtracted from the net value Flus = [SourceRemoveItem, TakerAddItem], %these are the fluents for this archetype Vars = ["Taker", "Source", "Item"], %these are the variables SourceHasItem = %Now for the requirements %REQ is a macro which creates a Requirement, given three bits of selector information, arguments, %and a function to check the value resulting from querying that selector. ?REQ(main, {inventory, %Require that the value of the inventory dict:fetch("Source", _Bindings)}, %of the Source _Time, %at the time of application [], %with no extra args lists:member(dict:fetch("Item", _Bindings), _Val) %includes the item ) , TakerHandsFree = ?REQ(main, {inventory, %Require that the inventory dict:fetch("Taker", _Bindings)}, %of the taker _Time, %at the time of application [], %with no extra args _Val =:= [] %is empty. ) , Reqs = [SourceHasItem, TakerHandsFree], %gather the requirements Extras = [], %no extra dependencies Cancels = [], %no cancels Take = i_arc:new(take, main, Flus, Vars, Reqs, Extras, Cancels), %synthesize the archetype thucydides:add_iarc(Take, jones_sim) %add the archetype ; setup_iarc(lift) -> %A complete fluent archetype, including stack mode. TakerAddItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Taker", _Bindings)}, %of the taker _Time, %at the given time infinity, %forever removed, %if it gets canceled, it was 'removed' herodotus:value( %the item to be added is the result of a query Self, %when you make a query from within a fluent, you should provide this %this query is the inventory of the source. we can assume there's only one %note that we use _Time, the time the fluent was set, and not _Now, the time it is evaluated selector:new({inventory, dict:fetch("Source", _Bindings)}, _Time), %no extra args [], %the history is the main history of main, %the jones_hero herodotus process. jones_hero ), fluent:fmerge(_Net, _Val)), %and it's merged into the net value SourceRemoveItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Source", _Bindings)}, %of the source _Time, %right now infinity, %until forever returned, %if it is canceled, it will be 'returned' [hd(_Net)], %take whatever's on top fluent:fsubtract(_Net, _Val) ), %And it's subtracted from the net value Flus = [SourceRemoveItem, TakerAddItem], %these are the fluents for this archetype Vars = ["Taker", "Source", "Trigger", "TriggerFluent", "TriggeredState", "UntriggeredState"], %these are the variables SourceHasItem = %Now for the requirements %REQ is a macro which creates a Requirement, given three bits of selector information, arguments, %and a function to check the value resulting from querying that selector. ?REQ(main, {inventory, %Require that the value of the inventory dict:fetch("Source", _Bindings)}, %of the Source _Time, %at the time of application [], %with no extra args is_list(_Val) andalso length(_Val) > 0 %contains an item ) , TakerHandsFree = ?REQ(main, {inventory, %Require that the inventory dict:fetch("Taker", _Bindings)}, %of the taker _Time, %at the time of application [], %with no extra args _Val =:= [] %is empty. ) , Reqs = [SourceHasItem, TakerHandsFree], %gather the requirements Extras = [], %no extra dependencies %This finds the "door opened by plate" fluents and kills them. TriggerFinder = ?A_SEL({dict:fetch("TriggerFluent", _Bindings), [dict:fetch("Trigger", _Bindings), dict:fetch("Source", _Bindings)]}, _Time, infinity), Cancels = [TriggerFinder], Lift = i_arc:new(lift, main, Flus, Vars, Reqs, Extras, Cancels), %synthesize the archetype thucydides:add_iarc(Lift, jones_sim) %add the archetype ; setup_iarc(drop) -> %A complete fluent archetype, including stack mode. DestinationAddItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Destination", _Bindings)}, %of the destination _Time, %at the given time infinity, %forever removed, %if it gets canceled, it was 'removed' herodotus:value( %the item to be added is the result of a query Self,%when you make a query from a fluent, you should provide this %this query is the inventory of the dropper. selector:new({inventory, dict:fetch("Dropper", _Bindings)}, _Time), %no extra args [], %the history is the main history of main, %the jones_hero herodotus process. jones_hero ), fluent:fmerge(_Net, _Val)), %and it's merged into the net value DropperRemoveItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Dropper", _Bindings)}, %of the dropper _Time, %right now infinity, %until forever returned, %if it is canceled, it will be 'returned' [lists:last(_Net)], %drop the lowest thing fluent:fsubtract(_Net, _Val) ), %And it's subtracted from the net value Flus = [DropperRemoveItem, DestinationAddItem], %these are the fluents for this archetype Vars = ["Dropper", "Destination"], %these are the variables DropperHasItem = %Now for the requirements %REQ is a macro which creates a Requirement, given three bits of selector information, arguments, %and a function to check the value resulting from querying that selector. ?REQ(main, {inventory, %Require that the value of the inventory dict:fetch("Dropper", _Bindings)}, %of the Source _Time, %at the time of application [], %with no extra args _Val =/= [] %contains an item ) , Reqs = [DropperHasItem], %gather the requirements Extras = [], %no extra dependencies - inventory, dropper already handled Cancels = [], %no cancels Drop = i_arc:new(drop, main, Flus, Vars, Reqs, Extras, Cancels), %synthesize the archetype thucydides:add_iarc(Drop, jones_sim) %add the archetype ; setup_iarc(place) -> %A complete fluent archetype, including stack mode. DestinationAddItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Destination", _Bindings)}, %of the destination _Time, %at the given time infinity, %forever removed, %if it gets canceled, it was 'removed' herodotus:value( %the item to be added is the result of a query Self,%when you make a query from a fluent, you should provide this %this query is the inventory of the dropper. selector:new({inventory, dict:fetch("Dropper", _Bindings)}, _Time), %no extra args [], %the history is the main history of main, %the jones_hero herodotus process. jones_hero ), fluent:fmerge(_Net, _Val)), %and it's merged into the net value DropperRemoveItem = ?A_FLUENT( {inventory, %modify inventory dict:fetch("Dropper", _Bindings)}, %of the dropper _Time, %right now infinity, %until forever returned, %if it is canceled, it will be 'returned' [lists:last(_Net)], %drop the last thing in the list fluent:fsubtract(_Net, _Val) ), %And it's subtracted from the net value Trigger = ?A_FLUENT( %This opens the door, or whatever {dict:fetch("TriggerFluent", _Bindings), %we use both contexts, so that the cancel later can be specific [dict:fetch("Trigger", _Bindings), dict:fetch("Destination", _Bindings)]}, _Time, infinity, dict:fetch("UntriggeredState", _Bindings), dict:fetch("TriggeredState", _Bindings), _Val ), Flus = [DropperRemoveItem, DestinationAddItem, Trigger], %these are the fluents for this archetype Vars = ["Dropper", "Destination", "Trigger", "TriggerFluent", "TriggeredState", "UntriggeredState"], %these are the variables DropperHasItem = %Now for the requirements %REQ is a macro which creates a Requirement, given three bits of selector information, arguments, %and a function to check the value resulting from querying that selector. ?REQ(main, {inventory, %Require that the value of the inventory dict:fetch("Dropper", _Bindings)}, %of the Source _Time, %at the time of application [], %with no extra args length(_Val) > 0 %contains an item ) , DestFree = ?REQ(main, {inventory, %Require that the inventory dict:fetch("Destination", _Bindings)}, %of the taker _Time, %at the time of application [], %with no extra args _Val =:= [] %is empty. ) , Reqs = [DropperHasItem, DestFree], %gather the requirements Extras = [], %no extra dependencies - inventory, dropper already handled Cancels = [], %no cancels Place = i_arc:new(place, main, Flus, Vars, Reqs, Extras, Cancels), %synthesize the archetype thucydides:add_iarc(Place, jones_sim) %add the archetype ; setup_iarc(leave) -> FA = ?A_ADD({finished, %modify finished dict:fetch("Leaver", _Bindings)}, %for the leaver _Time, %at the given time infinity, %forever false, %if this gets ended somehow, it becomes false true), %set to true %This is primarily for documentation and discovery purposes, and may be removed later Vars = ["Leaver", "Portal", "PassableFluent", "PassableValue"], %Leave requires an open door DoorOpen = ?REQ(main, {dict:fetch("PassableFluent", _Bindings), %Require that the doo dict:fetch("Portal", _Bindings)}, %of the taker _Time, %at the time of application [], %with no extra args _Val =:= dict:fetch("PassableValue", _Bindings) %is empty. ) , Reqs = [DoorOpen], %Leave doesn't depend on any fluents ExtraInputs = [], %Leave doesn't cancel anything Cancels = [], %Create the archetype Leave = i_arc:new(leave, main, [FA], Vars, Reqs, ExtraInputs, Cancels), %Add the archetype to the simulation thucydides:add_iarc(Leave, jones_sim) . %Value is a private function that gets the "current value" of an {Ft, Ctx} tuple. value(Ft, Ctx) -> herodotus:value(selector:new({Ft, Ctx}, current_time()), main, jones_hero) . %Look invokes the Look action from the puzzle description. It reveals information about the room, including the %location of various objects and the state of the door. look() -> %This asks the simulation jones_sim to trigger the "look" incident in the given history(main) %at the given time with the given bindings. %We use special bindings for the player just to show a little more of the Archetype system. %We use an incident for this so that the system can fetch out where the player may have gotten lost. case thucydides:trigger(look, current_time(), dict:store("Looker", player, dict:new()), jones_sim) of ok -> tick(), %now we grab some fluent values to print them out. FloorItems = value(inventory, floor), PedestalItems = value(inventory, plate), PlayerItems = value(inventory, player), DoorState = value(open, door), io:format("You are in a glittery treasure room. A pedestal sits in the center of the room. There is one door.~nOn the floor, you see ~p.~nOn the pedestal, you see ~p.~nYou are carrying ~p.~nThe door is ~p.~nYou have been here for ~p minutes.~n", [FloorItems, PedestalItems, PlayerItems, DoorState, current_time()]) end . %Take invokes the Take action from the puzzle description. It takes an item as an argument, %and passes that on to the Take action. take(Item) -> FloorItems = value(inventory, floor), %fetch out the old floor items for a later consistency check NewItems = lists:delete(Item, FloorItems), case thucydides:trigger(take, current_time(), dict:store("Source", floor, dict:store("Item", Item, dict:store("Taker", player, dict:new()))), jones_sim) of not_met -> tick(), io:format("That's not there, or your hands are full.~n"); ok -> tick(), case {value(inventory, floor), value(inventory, player)} of %check floor contents and player inventory {NewItems, [Item]} -> %poor man's error checking io:format("You pick it up.~n") end end . lift() -> Bindings = dict:store("Source", plate, dict:store("Taker", player, dict:store("Trigger", door, dict:store("TriggerFluent", open, dict:store("TriggeredState", open, dict:store("UntriggeredState", closed, dict:new())))))), OldPlate = value(inventory, plate), io:format("~p~n",[Bindings]), case thucydides:trigger(lift, current_time(), Bindings, jones_sim) of not_met -> tick(), io:format("There's nothing there, or your hands are full.~n"); ok -> tick(), case {value(inventory, plate), %check plate contents value(inventory, player), %and player inventory value(open, door)} of %and door state {[], OldPlate, closed} -> %poor man's error checking io:format("You remove the item from the plate. The door closes.~n") end end . drop() -> PlayerItems = value(inventory, player), %fetch out the old inventories FloorItems = value(inventory, floor), NewFloorItems = lists:merge(PlayerItems, FloorItems), io:format("~p~n", [NewFloorItems]), case thucydides:trigger(drop, current_time(), dict:store("Destination", floor, dict:store("Dropper", player, dict:new())), jones_sim) of not_met -> tick(), io:format("Your hands are empty.~n"); ok -> tick(), case {value(inventory, floor), %check floor contents and player inventory value(inventory, player)} of {NewFloorItems, []} -> %poor man's error checking io:format("You drop it.~n") end end . place() -> PlayerItems = value(inventory, player), %fetch out the old inventories PlateItems = value(inventory, plate), Bindings = dict:store("Dropper", player, dict:store("Destination", plate, dict:store("Trigger", door, dict:store("TriggerFluent", open, dict:store("TriggeredState", open, dict:store("UntriggeredState", closed, dict:new())))))), case thucydides:trigger(place, current_time(), Bindings, jones_sim) of not_met -> tick(), io:format("Your hands are empty or the pedestal is full.~n"); ok -> tick(), case {value(inventory, plate), %check plate contents value(inventory, player), %check player inventory value(open, door)} of %check door state {PlayerItems, PlateItems, open} -> %poor man's error checking io:format("You place it. The door opens.~n") end end . leave() -> case thucydides:trigger(leave, current_time(), dict:store( "Portal", door, dict:store("PassableFluent", open, dict:store("PassableValue", open, dict:store("Leaver", player, dict:new())))), jones_sim) of not_met -> tick(), io:format("The door is closed!~n"); ok -> tick(), case value(inventory, player) of [statue] -> io:format("You leave, statue in hand! Hooray!~n"); [] -> io:format("You didn't take the statue! Start over!~n") end, stop() end .