Principles of Kodu Computation
David S. Touretzky, Carnegie Mellon University
Perception
- Predicates bind to the closest matching object.
"WHEN see apple DO boom it" will first blow up the apple closest to
the character and then work its way outward. (Unlike "eat" or
"launch", the "boom" action has an unlimited range.)
- Filters applied to a predicate are intersected to restrict the match.
- "WHEN see" matches any object except the character itself.
- "WHEN see apple" matches the nearest apple.
- "WHEN see red" matches the nearest red thing.
- "WHEN see red apple" matches the nearest red apple.
- "WHEN see red apple LOS" matches the nearest red apple in the character's line of sight.
- Characters don't see themselves.
Characters won't pick themselves as a match for a predicate unless a
"me" modifier is used. In that case they can only match themselves.
Examples:
- "WHEN see red DO color it blue" will sequentially match all the
red things (except perhaps for the character itself) and turn them
blue.
- "WHEN see me red DO color it blue" will turn the character itself
blue if it is presently red.
- "Not" applies to the predicate, not the filters..
- The conditions "WHEN see not red" and "WHEN see red not" have the
same meaning. They both mean "see nothing that is red". They
do not mean "see something that is not red". At present there
is no way to express the latter in Kodu.
- Objects have either one or two colors.
Body color and glow color are independent, and either will satisfy a
color filter. "WHEN see red DO move toward" will move toward the
closest object that either has a red body or is glowing red. There is
no way to distinguish between these two conditions.
The "glow" action with no color argument uses the object's current
body color to set the glow color.
- Bump is transitory.
When a pursuing object bumps its target they will bounce off each
other in a manner determined by their mass and relative speed. This
can terminate the bump relation after as little as one rule cycle; the
duration of contact is indeterminate. A lost contact might be
re-established, either deliberately through continued pursuit or
accidentally via rebounding off other objects. Inertia and the
elasticity of collisions mean the persistence of a bump is not
guaranteed even if the pursuer halts its motion the instant the bump
occurs. Due to the transitory nature of "bump", rules employing this
predicate do not offer the "once" modifier.
Rule Evaluation
- Indented rules are only considered when their parent's
condition is true.
Since the parent is only considered if its own parent's condition is
true, an indented rule depends on all its ancestors' conditions being
simultaneously true.
- Kodu first evaluates all the WHENs, and then performs the selected DOs.
On each rule cycle, all the predicates on the current page are
evaluated before any actions are run. This matters when a rule's
action changes some state that a later rule's condition refers to.
Even if the later rule's condition will be made false by the earlier
rule's action, the later rule will still run because its condition was
true when all the conditions were being evaluated.
This convention is sometimes useful and other times a source of
obscure bugs. An example of the latter is:
[1] WHEN DO set score 5 points once
[2] WHEN timer 1 second DO subtract 1 point
[3] WHEN scored 0 points DO win
This code never subtracts anything from the score. It wins
immediately, because all scores start out at 0. So on the first rule
cycle the conditions of rules 1 and 3 both evaluate to true. Then
rule 1's action initializes the red score to 5, rule 2 starts its
timer, and rule 3's action immediately ends the program. This is
obviously not what the programmer intended.
One solution is to initialize the score on page 1 but put the loop on
page 2 so that the exit test cannot run until after the score has been
properly initialized. Another solution is to use a short time delay
to prevent the exit test from running on the very first rule cycle:
[1] WHEN DO set score 5 points once
[2] WHEN timer 1 second DO subtract 1 point
[3] WHEN timer .25 seconds DO
  ↳ [4] WHEN scored 0 points DO win
A third solution is to set a flag to indicate that initialization has
happened, and include that flag in the termination test, which again prevents
the test from running on the very first rule cycle:
[1] WHEN DO set score red 5 points once
[2] WHEN DO set score "F" 1 point once
[3] WHEN timer 1 second DO subtract red 1 point
[4] WHEN scored "F" 1 point DO
  ↳ [5] WHEN scored red 0 points DO win
- Actions are performed sequentially.
Actions can build on the effects of earlier actions. This matters
mainly for score operations, where the actions "set score 5" and
"score 1 point" will leave the score at 6 points when the rules are
ordered as stated. If the order is reversed, the score will first be
incremented and then that value will be discarded as the score is set
to 5.
- When actions conflict, the earlier rule prevails.
This rule arbitration mechanism is the heart of the Default Value
idiom. In order for two actions to conflict, the verb must be the
same, and the parameter values must be incompatible. For example,
"color me red" and "color me blue" conflict, so the earlier action
will prevail. Similarly, "open me" and "close me" conflict, as do
"move toward" and "move wander".
There are a few cases where actions combine rather than conflict. For
example, "move avoid" can merge with other move actions such as "move
toward". Also, if a "WHEN gamepad L-stick DO move" rule is ordered
after a "move toward" action, the two motions will combine. But if
the gamepad rule appears before the "move toward" action it will
override it.
- Page switch short-circuits action execution.
When a "switch to page" action is executed, the actions of any
remaining (i.e., higher numbered) rules are skipped. The following
actions can therefore act as the "else" part of a conditional.
The "win" and "end" primitives also prevent any following actions from
executing, because they immediately end the game.
- The "once" modifier is not satisfied until the action executes
successfully; it then remains satisfied until the rule's predicate
becomes false.
For example: in a world with multiple apples, "WHEN see apple DO eat
it once" will run until the first apple is successfully eaten. It will
not run again until no apples are visible for at least one rule cycle, or
the page is re-entered via a "switch to page" action.
- "Once" flags are cleared on page entry.
Upon switching into a page, all rules have their "once" flag cleared,
making them eligible to run. This is true even when the page being
entered is the one that was just left.
- Conflicting goals call for separate pages.
If a program's goals can't be approached simultaneously, such as
traveling to different locations or performing actions on different
objects, the most straightforward solution is to assign each goal to a
separate page. For example, to fetch an apple from a tree that
launches an apple when bumped, and to give that apple to an octopus,
we must pursue multiple objects. Pursuing the apple takes priority
over pursuing the tree, but both can go on the same page because the
apple doesn't become visible until the tree has been bumped. Pursuing
the octopus should go on a separate page because the octopus is
visible all the time; it will either override or be overridden by
pursuit of the tree, neither of which is desirable.
PAGE 1:
[1] WHEN see apple DO move toward
[2] WHEN bump apple DO grab it
  ↳ [3] WHEN DO switch to page 2
[4] WHEN see tree DO move toward
PAGE 2:
[1] WHEN see octopus DO move toward
[2] WHEN bump octopus DO give
It is often possible to avoid page switches by using some other type
of state to control which goal is being pursued, but at a cost of
increased rule complexity, which results from adding dependency
relationships. The example above could be coded as a single page by
using the character's holding state to control which rules are active:
[1] WHEN got apple DO
  ↳ [2] WHEN see octopus DO move toward
  ↳ [3] WHEN bump octopus DO give
[4] WHEN see apple DO move toward
[5] WHEN bump apple DO grab it
[6] WHEN see tree DO move toward
The earlier two-page solution uses a single indented rule with an
empty WHEN part: an instance of the Do Two Things idiom. The one-page
solution uses two indented rules with non-empty WHEN parts, so these
rules implement more complex conjunctive conditions: the If This And
Also That idiom.
Even more complex state machines can be implemented on a single page
by using scores, but the result is more baroque and less transparent
than using separate pages.
- Timer predicates are only true for an instant.
A rule such as "WHEN timer 1 second DO ..." will run for one just rule
cycle every time the timer expires. Thus, their actions should be of
the type that take effect instantaneously, such as updating a score,
creating or destroying an object, playing a sound, or switching pages.
Move actions would be ineffective in this context because they require
multiple rule cycles for the character to accelerate.
To use a timer to trigger an extended duration action, the timer must
either set a flag or cause a page switch.
- Timers don't nest.
Because timer predicates are only true for an instant, any dependent
(indented) rule can only run for an instant. Thus, if the indented
rule contains its own timer, the nested timer will start but will be
cancelled on the very next rule cycle when the parent rule is no
longer active. Thus, nested timers conditions can never be
satisfied.
Scored
- Tiles in score expressions, whether point tiles or score
references, combine additively.
Score expressions can occur in "WHEN scored score-color"
conditions or in "DO score score-color", "DO set score
score-color", or "DO subtract score-color" actions. A
score expression consists of one or more tiles, in any order, that are
either point tiles or score color tiles. The values of all the tiles
that make up a score expression are added together.
A "WHEN scored score-color" condition or a "DO score
score-color", "DO set score score-color", or "DO
subtract score-color" action can also take a random expression
as an argument. This consists of a "random" tile optionally preceded
and/or followed by a score expression.
- A "WHEN scored score-color" condition with no score expression
responds to any change in the score.
The condition will be true if the score changed during the preceding
rule cycle.
Holding
- Any object can grab or give.
Even inanimate objects like trees and rocks can grab, give, or
receive objects. For clarity in the points that follow, we will use
the term "character" for the holder and "object" for the thing being
held, but any object can play either role.
- A character can only hold one thing at a time.
If a character is holding an object when it grabs another object, the
first object is dropped. Similarly, if a character is holding an
object when another character gives it something, the first object is
dropped.
- Held characters can't hold or grab anything.
If a character is grabbed while holding an object, the held object is
dropped. If a held character attempts to grab an object, it fails:
the action has no effect.
- Held objects are visible to other characters, but not the holder.
The holder can detect the held object with "got", but not with "see".
- Held objects can't be grabbed by another character.
A character can see and bump a held object, but cannot steal it from
the holder. However, the holder can use "give" to transfer the
object.
- Held objects can't move, but otherwise behave normally.
For example: held objects can perceive the world, speak, create or
launch other objects, and even boom the object holding them. But they cannot move or,
as mentioned above, grab anything.
Speech
- Speech actions are sequential and last between 1.25 and 1.5 seconds.
A character can only say one thing at a time. (But characters can
play multiple sounds at once.) The minimum duration for a speech
action is somewhere between 1.25 and 1.5 seconds.
- Speech actions don't queue.
If a character is already saying something when a new speech action is attempted, the
new speech is discarded.
- Speech can he heard either at onset or offset (the default).
When a spoken text is heard is controlled by an option of the "said"
tile in "WHEN hear said". By default, a character will not "hear"
(hence, react to) an utterance until the speaker has finished. This
allows for natural pacing of a conversation between characters.
However, if a character should react immediately to an utterance, the
setting can be changed to respond to the onset rather than the offset
of speech.
- Continuously repeated speech has no offset, and the
speech bubble never goes away.
With the default setting of "WHEN hear said", a character will never
hear an utterance that is being repeated continuously because there is
no speech offset event. The "once" modifier or some transient WHEN
condition can be used to ensure that speech is uttered only once.
Paths
(Coming soon.)
Paths are not objects and cannot be seen, bumped, or programmed.
"end of path" is like "see" in that a character does not sense itself
unless a "me" tile is used.
Death
- Between dying and nonexistence there is exactly one rule cycle.
Dying, which can result from being eaten or shot, is detected by "WHEN
health 0 points". The condition is true for exactly one rule cycle,
so actions that require more time (such as motion) will not execute
successfully. Switching to a new page can be used for short-circuit
effect, but any rules on the new page will never run because this is
the character's last rule cycle. However, many other actions can be
performed in the dying state, such as manipulating scores, creating
objects, even launching a missile.
- A dying character cannot see itself.
A dying character that wishes to check its body color or glow color
cannot do so, because it is no longer perceptible in the world.
- Dying characters can't hold anything.
During its last rule cycle, a dying character has already dropped
whatever it was holding.
Knockout
(Coming soon.)
Camera Control
(Coming soon.)
Dave Touretzky
Last modified: Sat Aug 22 23:22:26 EDT 2015
|