%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Situation records
%
% situation(
% prev: !<previous situation record>
% poss: [
% <action1>: <action possibility var1>
% <actionN>: <action possibility varN>
% ]
% <fluent1>: <fluent variable>
% ...
% <fluentN>: <fluent variable>
% )
%
% This arrangement allows easy reference to the fluents by name. For instance:
%
% {Planner mkSitRec(SPrev S)}
% S.temperature =: S.prev.temperature + 10
%
% Situation records use constraints to represent uncertainty.
%The fluent variables are all finite domain variables. WRONG
%It's common for
% some fluents to be constrained but not determined, so care must be
% taken in referencing fluent variables so as not to inadvertently
% block on an undetermined (and quite likely undeterminable)
% value. The fully determined fluent values are *not* the variables
% for which our system is being solved. In the original situation
% record of a plan, the domains of the fluent variables are the inputs
% to the system. The domains of intermediate and final situations
% computed by the system are ancillary to the decision outputs.
%
% The poss entry contains a list with one pair per primitive
% action. The first element of each pair atom is the action. The
% second element is a 0/1 integer indicating if that action is
% possible
%
% During the search for desirable consequent actions, we first try
% various actions which are definitely possibly (their entry in poss
% is determined and non-zero) If the search fails to find attractive
% alternatives, actions are explored whose entries are not
% determined. Primitive actions whose preconditions resolve to
% determinate false values are not explored.
%
% Note on possible modeling of time:
%
% Time is represented as a fluent. Each primitive player action has an
% associated duration ration which is used to add a constraint to this
% this fluent to indicate how long they will take. Consequently, each
% situation is stamped with a (possibly constrained but indeterminate)
% time which indicates when the situation is expected to pertain.
%
% Note on possible addition of exogenous actions:
%
% Exogenous actions can be modelled as constraints that, unlike
% the axioms of a character action, can reference the hypothetical
% new situation record as well as the current situation. Thus the
% constraints can take into account the character action's expected effects.
% This means that exogenous action constraints can be applied to a current
% situation before exploring decision alternatives.
%
% This arrangement reflects the fact that the character knows or
% assumes that certain things can happen of their own accord, but
% expects occurence and effect to be influenced by his actions and by
% the passage of time.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Decision records
%
% A decision record is the
% decision(
% situation: <previous situation record>
% consequent: <resulting situation record>
% action: <chosen action>
% inputs: var(
% <choice1>: <choice variable>
% ...
% <choiceN>: <choice variable>
% )
% )
%
% Decision records are the root variables of the computation spaces
% used to explore possible actions in a situation. Given an input
% situation and a selected action to explore, a decision record is
% built from the current situation, a freshly created consequent
% situation, the action, and the definition of the action's input
% variables. If there are no input variables, the action's time
% constraint and effect axiom are immediately applied. Otherwise, we
% distribute further on the various choice variables before exploring
% the action's effects. When an action is finally selected and
% actually executed, the determinate values of the input variables
% are available as input parameters to the action's effector.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
local
[Plan] = {Module.link ["~/SpookyDistance/Plan.ozf"]}
%% Fluent definitions
%
% Each fluent represents a variable of interest about the current
% situation. Note that the fluents model the character's mind; they
% may represent what the character thinks he knows about his
% environment, or something about his emotional state, or they might
% represent a variable of sSome fluents represent internal states of
% the character's mind. (
Fluents =
[
fluent(
name: temperature
desc: "ambient temperature"
init: 0#130
infer: proc {$ S ?T}
% Assume that the temperature can't change too quickly.
T =<: S.temperature + 2
T >=: S.temperature - 2
end
)
fluent(
name: thermostat
desc: "thermostat setting"
init: 32#90
infer: true % Thermostat setting should be stable.
)
fluent(
name: alive
init: 0#1
infer: fun {$ S} S.temperature <: 120 end
)
% location 0,0 == at thermostat
fluent(
name: locationX
init: 0#9
infer: true % Our location shouldn't change on its own!
)
fluent(
name: locationY
init: 0#9
infer: true
)
]
% Note that the effectors assume their inputs are determined, since
% we're simulating the model for this test.
Actions =
[
action(
name: walk
pre: fun {$ S} S.alive end
effect: proc {$ S I}
S.locationX =: S.prev.locationX + I.dirX - 1
S.locationY =: S.prev.locationY + I.dirY - 1
end
effector: proc {$ S I}
% An effector proc would normally queue up a message
% for the game engine. We cheat here for testing.
S.locationX = S.prev.locationX + I.dirX - 1
S.locationY = S.prev.locationY + I.dirY - 1
end
inputs: proc {$ S ?I}
I = var(
dirX: {FD.int 0#2} % 0=W, 1=none, 2=E
dirY: {FD.int 0#2} % 0=S, 1=none, 2=N
)
% Prevent noop moves
{FD.impl I.dirX =: 1 I.dirY \=: 1} = 1
end
)
action(
name: turnTempUp
pre: fun {$ S}
{FD.conj
{FD.conj S.alive =: 1 S.locationX =: 0}
S.locationY =: 0}
end
effect: proc {$ S _} S.thermostat = {FD.max S.prev.thermostat + 5 90}
end
effector: proc {$ S _} S.thermostat = {Min S.prev.thermostat + 5 90} end
)
action(
name: turnTempDown
pre: fun {$ S}
{FD.conj
{FD.conj S.alive =: 1 S.locationX =: 0}
S.locationY =: 0}
end
effect: proc {$ S _} S.thermostat = {FD.min S.prev.thermostat - 5 32}
end
effector: proc {$ S _} S.thermostat = {Min S.prev.thermostat - 5 32} end
)
]
Planner={New Plan.planner init(Fluents Actions)}
%%
% Get input from the game engine and merge it into the current
% situtation record.
%
% TODO: Because of the (eventual) asynchronous model of arriving
% sensory data, we will have to code this so that values that
% were already determined can be replaced by newer values.
%
proc {GetInput Tick S ?Continue}
proc {DeferredActions}
D
in
% Testing hack. Apply effects of previous decision to this new state.
{Exchange ActionLoopback D nil}
case D
of nil then skip
[] decision(...) then
{Show [deferred D.action.name D.inputs]}
{D.action.effector S D.inputs}
end
end
in
{Show [tick Tick]}
if Tick >= 2 then % Stop running when this is true
Continue = false
elseif Tick == 0 then
% Set up initial state.
S.locationX = {OS.rand} mod 10
S.locationY = {OS.rand} mod 10
S.alive = 1
S.temperature = 110
S.thermostat = 90
Continue = true
else
{DeferredActions}
Continue = true
end
end
%%
% Send actions from our plan to the game engine.
%
proc {ExecuteActions D}
% For now, we cheat and assume that the input in the next
% tick will be exactly those effects that we predicted
% during planning.
{Assign ActionLoopback D}
end
proc {Main SituationPrev Tick}
Situation = {Planner mkSitRec(SituationPrev $)}
Decision
in
if {GetInput Tick Situation} then
{Planner exploreActions(Situation)} % For debugging the search tree.
{Planner chooseAction(Situation Decision)}
if Decision == nil then
raise paralyzed(debug:Situation) end
else
{ExecuteActions Decision}
{Main Situation Tick+1}
end
end
end
SDefault
ActionLoopback = {NewCell nil} % For testing
in
{Browser.object clear}
{System.printInfo "\n**** Test1 ****\n"}
SDefault={Planner mkSitRec(nil $)}
{Main SDefault 0}
{Show done}
end