%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % 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