// A simple set of PRODUCTION RULES for the Agent World. // They illustrate the idea of using "stages" to group rules // into sets that are used sequentially. You might want to // make use of more than the two stages used below. // Recall that your assignment is to add at least twelve (12) // rules to these rules (discarding any or all of these rules // is fine). Don't forget to clearly document the rules // you write. // Note: these sample rules do not "know about" MINERAL's // (so the first thing you might to add are some rules // for dealing with them - in that case, also be sure to // edit RunAgentWorld so that minerals appear in the AW). // Here is the structure of a production rule. // // ruleName // { // precondtions // -> // effects // } // // Preconditions are of the form: // // P(term1, ..., termN) // NOT (P(term1, ..., termN)) // // where a TERM is either a constant or a (implicitly universally quantified) // variable. VARIABLES are indicated by having '?' as their first character. // There is an IMPLICIT "and" connecting all the preconditions. // // An unnegated precondition is matched if it unifies with an item // in either short-term or long-term working memory. NOT(P(...)) is matched // if P(...) does NOT unify with something in either short-term or // long-term memory. // // ***** Be sure to remember the extra parens around the predicate when using // ***** a NOT (as well as the ASSERTs and RETRACTs listed below). Our rule // ***** parser, which reads the file of production rules, requires them. // // The AW's production system automatically adds all the SENSOR READINGS // to short-term memory at the beginning of each turn. These are added // as predicates of the form: // // Sensor(type, direction, distance) // // where type is one of {ANIMAL, MINERAL, VEGETABLE, WALL, NOTHING}, // direction is an integer in [0,35], and // distance is the distance to the sensed object. // // It also adds the following to short-term memory: // // LastReward(reward) // // Several "procedurally defined" predicates are built into the system: // // greaterThan(?x, ?y) lessThan(?x, ?y) // greaterThanOrEqual(?x, ?y) lessThanOrEqual(?x, ?y) // // You must order the preconditions of your rules so that the variables // in these predicates are bound to numbers by earlier preconditions. // // NOTE: in our implementation you cannot put procedurally defined predicates // inside NOTs (fortunately there the above four predicates cover all the // possibilities so this isn't needed). Also, our implementation requires // that all the variables inside a NOT be bound before the NOT is evaluated. // (Neither of these are fundamental limitations, rather than are merely // simplications used in the AW's production system.) // // // Effects must be one of the following forms: // // Assert (P(term1, ..., termN)) // Add to SHORT-TERM memory. // AssertLT (P(term1, ..., termN)) // Add to LONG-TERM memory. // Retract (P(term1, ..., termN)) // Remove from SHORT-TERM memory. // RetractLT (P(term1, ..., termN)) // Remove from LONG-TERM memory. // Move(direction) // Move in this direction; -1 means move randomly, // // a number in [0,35] means move along this sensor // // direction, and a number < -1 means "stand still." // // The CONFLICT RESOLUTION STRATEGY built into the AW's production // system is to apply the FIRST (looking top-down in this file) // rule whose preconditions are matched. However, within one reasoning // cycle, the same rule will not be reused WITH THE SAME VARIABLE BINDINGS // (it CAN be used with DIFFERENT bindings, though). This will prevent // infinite looping. // // A "reasoning cycle" ends when (a) the chosen rule has a MOVE in its effects // or (b) no more rules match with new bindings. When this happens, // all the facts in SHORT-TERM memory are erased (as is the record of which // rules were applied with which variable bindings). Also, if no rule // suggests a move, then your player will stand still. // If the Agent World is in "single stepping" mode, the system will // describe which rules are being fired and what is being added to // and retracted from working memory. So be sure to use this capability // to understand and debug your rule set. // Case doesn't matter, ie assertLT, AssertLT, ASSERTLT, etc are all // treated the same. //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // State some general facts about the world. // Only need to do this once, since these facts are stored in long-term memory. defineOpposites { NOT (Initialized()) // See if these facts have not yet been asserted -> // into LONG-TERM (LT) MEMORY. AssertLT (Opposite( 0, 18)) AssertLT (Opposite(18, 0)) AssertLT (Opposite( 1, 19)) AssertLT (Opposite(19, 1)) AssertLT (Opposite( 2, 20)) AssertLT (Opposite(20, 2)) AssertLT (Opposite( 3, 21)) AssertLT (Opposite(21, 3)) AssertLT (Opposite( 4, 22)) AssertLT (Opposite(22, 4)) AssertLT (Opposite( 5, 23)) AssertLT (Opposite(23, 5)) AssertLT (Opposite( 6, 24)) AssertLT (Opposite(24, 6)) AssertLT (Opposite( 7, 25)) AssertLT (Opposite(25, 7)) AssertLT (Opposite( 8, 26)) AssertLT (Opposite(26, 8)) AssertLT (Opposite( 9, 27)) AssertLT (Opposite(27, 9)) AssertLT (Opposite(10, 28)) AssertLT (Opposite(28, 10)) AssertLT (Opposite(11, 29)) AssertLT (Opposite(29, 11)) AssertLT (Opposite(12, 30)) AssertLT (Opposite(30, 12)) AssertLT (Opposite(13, 31)) AssertLT (Opposite(31, 13)) AssertLT (Opposite(14, 32)) AssertLT (Opposite(32, 14)) AssertLT (Opposite(15, 33)) AssertLT (Opposite(33, 15)) AssertLT (Opposite(16, 34)) AssertLT (Opposite(34, 16)) AssertLT (Opposite(17, 35)) AssertLT (Opposite(35, 17)) // You might also wish to assert things like // "RightOf(0, 1)" and "LeftOf(0, 35)" // This way you can make decision based on nearby-sensor readings. AssertLT (Initialized()) } // Get the "ball rolling" during each turn by specifying the // initial reasoning stage. startReasoning { Initialized() -> Assert (Stage(EvaluateScene)) // In the first stage, we'll assess the scene. } //////////////////////// Stage EvaluateScene /////////////////////////////// // Need to be slightly tricky here. Hold on to the // last move in short-term memory and clear it // from long-term memory (so that when the current move // is chosen we can store it in long-term memory and not have // confusion between the two; also notice the need for slightly // different predicate names). noteLastMoveMade { Stage(EvaluateScene) HoldLastMove(?direction) -> RetractLT (holdLastMove(?direction)) Assert (LastMove(?direction)) } // All directions in which food is sensed are good. lookForFood { Stage(EvaluateScene) Sensor(VEGETABLE, ?dir, ?dis) -> Assert (GOODdirection(?dir)) } // A given direction is a BADdirection if a wall can be seen nearby. lookForWalls { Stage(EvaluateScene) Sensor(WALL, ?dir, ?dis) // The following number (50) may seem a bit high; note, though, that // if the wall is nearby to the east (sensor 0), we also don't // want to move along sensor 1, 2, 35, 34, etc. lessThan(?dis, 50) // A "procedurally defined" predicate. -> Assert (BADdirection(?dir)) } // A given direction is a BADdirection if an opponent can be seen. lookForOpponents { Stage(EvaluateScene) Sensor(ANIMAL, ?dir, ?dis) lessThan(?dis, 100) // Note: the max sensor range is 110. -> Assert (BADdirection(?dir)) } // Notice we have to be aware of our conflict-resolution strategy // here so that this rule isn't used until all the rules that look // for good and bad directions have done their job. When that is // the case, we switch to the next stage of reasoning. switchStages { Stage(EvaluateScene) -> Retract (Stage(EvaluateScene)) Assert (Stage(ChooseMove)) } //////////////////////// Stage ChooseMove /////////////////////////////// // If the previous direction still looks good and we didn't get penalized // on the last step, continue in that direction. continueIfDirectionIsStillGood { Stage(ChooseMove) LastMove(?dir) GOODdirection(?dir) LastReward(?reward) // Need this predicate to "read" the prev step's reward. greaterThanOrEqual(?reward, 0) // Check if we got penalized for the last step. -> AssertLT (holdLastMove(?dir)) // Remember last dir Moved in LONG-TERM memory. Move(?dir) // Done reasoning for this step when Move chosen. } // Otherwise, choose a good direction to move. moveIfDirectionIsGood { Stage(ChooseMove) GOODdirection(?dir) -> AssertLT (holdLastMove(?dir)) Move(?dir) } // If no good direction, move in the opposite direction of BADdirections // (assuming the opposite direction isn't also bad). moveAwayFromBADdirection { Stage(ChooseMove) BADdirection(?dir) Opposite(?dir, ?oppDir) NOT (BADdirection(?oppDir)) -> AssertLT (holdLastMove(?oppDir)) Move(?oppDir) } // As a default, continue in the same direction unless it is bad // or a negative reward received for the previous action. haveInertia { Stage(ChooseMove) LastMove(?direction) NOT (BADdirection(?direction)) LastReward(?reward) greaterThanOrEqual(?reward, 0) -> AssertLT (holdLastMove(?direction)) Move(?direction) } // If no move is chosen by the above rules, request that the system choose // a random move. This is indicated by specifying "Move(-1)"; note, though, // that the last move made is not recorded in this case. Also, be aware that // this might choose a bad direction. moveRandomly { Stage(ChooseMove) -> Move(-1) // Tell the system to do a random move. }