Monkees and Bananas Sample Problem

This is an extended version of a rather common AI planning problem.

The point is for the monkee to find and eat some bananas.

Extracted from CLIPS Version 6.0 Example


In [1]:
from pyknow import *
import schema

In [2]:
class Monkey(Fact):
    location = Field(str, default="green-couch")
    on_top_of = Field(str, default="floor")
    holding = Field(str, default="blank")

    
class Thing(Fact):
    name = Field(str, mandatory=True)
    location = Field(str, mandatory=True)
    on_top_of = Field(str, default="floor")
    weight = Field(schema.Or("light", "heavy"), default="light")


class Chest(Fact):
    name = Field(str, mandatory=True)
    contents = Field(str, mandatory=True)
    unlocked_by = Field(str, mandatory=True)


class GoalIsTo(Fact):
    action = Field(schema.Or("hold", "unlock", "eat", "move", "on", "walk-to"),
                   mandatory=True)
    arguments = Field(schema.Or(str, [str]), mandatory=True)

In [3]:
class ChestUnlockingRules:
    """CHEST UNLOCKING RULES"""

    @Rule(
         GoalIsTo(action="unlock", arguments=MATCH.chest),
         Thing(name=MATCH.chest, on_top_of=NE("floor"), weight="light"),
         Monkey(holding=~MATCH.chest),
         NOT(GoalIsTo(action="hold", arguments=MATCH.chest)))
    def hold_chest_to_put_on_floor(self, chest):
        self.declare(GoalIsTo(action="hold", arguments=chest))

    @Rule(
        GoalIsTo(
            action="unlock",
            arguments=MATCH.chest),
        AS.monkey << Monkey(
                         location=MATCH.place,
                         on_top_of=MATCH.on,
                         holding=MATCH.chest),
        AS.thing  << Thing(
                         name=MATCH.chest))
    def put_chest_on_floor(self, monkey, thing, place, on, chest):
        print("Monkey throws the %s off the %s onto the floor." % (chest, on))
        self.modify(monkey, holding="blank")
        self.modify(thing, location=place, on_top_of="floor")

    @Rule(
        GoalIsTo(action="unlock", arguments=MATCH.obj),
        Thing(name=MATCH.obj, on_top_of="floor"),
        Chest(name=MATCH.obj, unlocked_by=MATCH.key),
        Monkey(holding=~MATCH.key),
        NOT(GoalIsTo(action="hold", arguments=MATCH.key)))
    def get_key_to_unlock(self, key):
        self.declare(GoalIsTo(action="hold", arguments=key))

    @Rule(
        GoalIsTo(
            action="unlock",
            arguments=MATCH.chest),
        Monkey(
            location=MATCH.mplace,
            holding=MATCH.key),
        Thing(
            name=MATCH.chest,
            location=MATCH.cplace & ~MATCH.mplace,
            on_top_of="floor"),
        Chest(
            name=MATCH.chest,
            unlocked_by=MATCH.key),
        NOT(
            GoalIsTo(
                action="walk-to",
                arguments=MATCH.cplace)))
    def move_to_chest_with_key(self, cplace):
        self.declare(GoalIsTo(action="walk-to", arguments=cplace))
    
    @Rule(
        AS.goal << GoalIsTo(action="unlock", arguments=MATCH.name),
        AS.chest << Chest(name=MATCH.name, contents=MATCH.contents, unlocked_by=MATCH.key),
        Thing(name=MATCH.name, location=MATCH.place, on_top_of=MATCH.on),
        Monkey(location=MATCH.place, on_top_of=MATCH.on, holding=MATCH.key))
    def unlock_chest_with_key(self, goal, name, key, contents, place, chest):
        print("Monkey opens the %s with the %s revealing the %s." % (name, key, contents))
        self.modify(chest, contents="nothing")
        self.declare(Thing(name=contents, location=place, on_top_of=name, weight="light"))
        self.retract(goal)

In [4]:
class HoldObjectRules:
    """HOLD OBJECT RULES"""

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Chest(name=MATCH.chest, contents=MATCH.obj),
        NOT(GoalIsTo(action="unlock", arguments=MATCH.chest)))
    def unlock_chest_to_hold_object(self, chest):
        self.declare(GoalIsTo(action="unlock", arguments=chest))

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of="ceiling", weight="light"),
        NOT(Thing(name="ladder", location=MATCH.place)),
        NOT(GoalIsTo(action="move", arguments__0="ladder", arguments__1=MATCH.place)))
    def use_ladder_to_hold(self, place):
        self.declare(GoalIsTo(action="move", arguments=["ladder", place]))

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of="ceiling", weight="light"),
        Thing(name="ladder", location=MATCH.place, on_top_of="floor"),
        Monkey(on_top_of=NE("ladder")),
        NOT(GoalIsTo(action="on", arguments="ladder")))
    def climb_ladder_to_hold(self):
        self.declare(GoalIsTo(action="on", arguments="ladder"))

    @Rule(
        AS.goal << GoalIsTo(action="hold", arguments=MATCH.name),
        AS.thing << Thing(name=MATCH.name, location=MATCH.place, on_top_of="ceiling", weight="light"),
        Thing(name="ladder", location=MATCH.place),
        AS.monkey << Monkey(location=MATCH.place, on_top_of="ladder", holding="blank"))
    def grab_object_from_ladder(self, thing, monkey, goal, name):
        print("Monkey grabs the %s." % name)
        self.modify(thing, location="held", on_top_of="held")
        self.modify(monkey, holding=name)
        self.retract(goal)

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of=MATCH.on & NE("ceiling"), weight="light"),
        Monkey(location=MATCH.place, on_top_of=~MATCH.on),
        NOT(GoalIsTo(action="on", arguments=MATCH.on)))
    def climb_to_hold(self, on):
        self.declare(GoalIsTo(action="on", arguments=on))

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of=NE("ceiling"), weight="light"),
        Monkey(location=~MATCH.place),
        NOT(GoalIsTo(action="walk-to", arguments=MATCH.place)))
    def walk_to_hold(self, place):
        self.declare(GoalIsTo(action="walk-to", arguments=place))

    @Rule(
        GoalIsTo(action="hold", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of=MATCH.on, weight="light"),
        Monkey(location=MATCH.place, on_top_of=MATCH.on, holding=NE("blank")),
        NOT(GoalIsTo(action="hold", arguments="blank")))
    def drop_to_hold(self):
        self.declare(GoalIsTo(action="hold", arguments="blank"))

    @Rule(
      AS.goal << GoalIsTo(action="hold", arguments=MATCH.name),
      AS.thing << Thing(name=MATCH.name, location=MATCH.place, on_top_of=MATCH.on, weight="light"),
      AS.monkey << Monkey(location=MATCH.place, on_top_of=MATCH.on, holding="blank"))
    def grab_object(self, name, monkey, thing, goal):
        print("Monkey grabs the %s." % name)
        self.modify(thing, location="held", on_top_of="held")
        self.modify(monkey, holding=name)
        self.retract(goal)

    @Rule(
        AS.goal << GoalIsTo(action="hold", arguments="blank"),
        AS.monkey << Monkey(location=MATCH.place, on_top_of=MATCH.on, holding=MATCH.name & NE("blank")),
        AS.thing << Thing(name=MATCH.name))
    def drop_object(self, monkey, thing, place, name, on, goal):
        print("Monkey drops the %s." % name)
        self.modify(monkey, holding="blank")
        self.modify(thing, location=place, on_top_of=on)
        self.retract(goal)

In [5]:
class MoveObjectRules:
    """MOVE OBJECT RULES"""

    @Rule(
        GoalIsTo(action="move", arguments__0=MATCH.obj),
        Chest(name=MATCH.chest, contents=MATCH.obj),
        NOT(GoalIsTo(action="unlock", arguments=MATCH.chest)))
    def unlock_chest_to_move_object(self, chest):
        self.declare(GoalIsTo(action="unlock", arguments=chest))

    @Rule(
        GoalIsTo(action="move", arguments__0=MATCH.obj, arguments__1=MATCH.place),
        Thing(name=MATCH.obj, location=~MATCH.place, weight="light"),
        Monkey(holding=~MATCH.obj),
        NOT(GoalIsTo(action="hold", arguments=MATCH.obj)))
    def hold_object_to_move(self, obj):
        self.declare(GoalIsTo(action="hold", arguments=obj))

    @Rule(
        GoalIsTo(action="move", arguments__0=MATCH.obj, arguments__1=MATCH.place),
        Monkey(location=~MATCH.place, holding=MATCH.obj),
        NOT(GoalIsTo(action="walk-to", arguments=MATCH.place)))
    def move_object_to_place(self, place):
        self.declare(GoalIsTo(action="walk-to", arguments=place))

    @Rule(
        AS.goal << GoalIsTo(action="move", arguments__0=MATCH.name, arguments__1=MATCH.place),
        AS.monkey << Monkey(location=MATCH.place, holding=MATCH.obj),
        AS.thing << Thing(name=MATCH.name, weight="light"))
    def drop_object_once_moved(self, name, monkey, thing, goal, place):
        print("Monkey drops the %s." % name)
        self.modify(monkey, holding="blank")
        self.modify(thing, location=place, on_top_of="floor")
        self.retract(goal)

    @Rule(
        AS.goal << GoalIsTo(action="move", arguments__0=MATCH.obj, arguments__1=MATCH.place),
        Thing(name=MATCH.obj, location=MATCH.place))
    def already_moved_object(self, goal):
        self.retract(goal)

In [6]:
class WalkToPlaceRules:
    """WALK TO PLACE RULES"""

    @Rule(
        AS.goal << GoalIsTo(action="walk-to", arguments=MATCH.place),
        Monkey(location=MATCH.place))
    def already_at_place(self, goal):
        self.retract(goal)

    @Rule(
        GoalIsTo(action="walk-to", arguments=MATCH.place),
        Monkey(location=~MATCH.place, on_top_of=NE("floor")),
        NOT(GoalIsTo(action="on", arguments="floor")))
    def get_on_floor_to_walk(self):
        self.declare(GoalIsTo(action="on", arguments="floor"))

    @Rule(
        AS.goal << GoalIsTo(action="walk-to", arguments=MATCH.place),
        AS.monkey << Monkey(location=~MATCH.place, on_top_of="floor", holding="blank"))
    def walk_holding_nothing(self, place, monkey, goal):
        print("Monkey walks to %s." % place)
        self.modify(monkey, location=place)
        self.retract(goal)

    @Rule(
        AS.goal << GoalIsTo(action="walk-to", arguments=MATCH.place),
        AS.monkey << Monkey(location=~MATCH.place, on_top_of="floor", holding=MATCH.obj & NE("blank")))
    def walk_holding_object(self, place, obj, goal, monkey):
        print("Monkey walks to %s holding the %s." % (place, obj))
        self.modify(monkey, location=place)
        self.retract(goal)

In [7]:
class GetOnObjectRules:
    """GET ON OBJECT RULES"""

    @Rule(
        AS.goal << GoalIsTo(action="on", arguments="floor"),
        AS.monkey << Monkey(on_top_of=MATCH.on & NE("floor")))
    def jump_onto_floor(self, on, monkey, goal):
        print("Monkey jumps off the %s onto the floor." % on)
        self.modify(monkey, on_top_of="floor")
        self.retract(goal)

    @Rule(
        GoalIsTo(action="on", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place),
        Monkey(location=~MATCH.place),
        NOT(GoalIsTo(action="walk-to", arguments=MATCH.place)))
    def walk_to_place_to_climb(self, place):
        self.declare(GoalIsTo(action="walk-to", arguments=place))

    @Rule(
        GoalIsTo(action="on", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place),
        Monkey(location=MATCH.place, holding=NE("blank")),
        NOT(GoalIsTo(action="hold", arguments="blank")))
    def drop_to_climb(self):
        self.declare(GoalIsTo(action="hold", arguments="blank"))

    @Rule(
        GoalIsTo(action="on", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of=MATCH.on),
        Monkey(location=MATCH.place, on_top_of=~MATCH.on & ~MATCH.obj, holding="blank"),
        NOT(GoalIsTo(action="on", arguments=MATCH.on)))
    def climb_indirectly(self, on):
        self.declare(GoalIsTo(action="on", arguments=on))

    @Rule(
        AS.goal << GoalIsTo(action="on", arguments=MATCH.obj),
        Thing(name=MATCH.obj, location=MATCH.place, on_top_of=MATCH.on),
        AS.monkey << Monkey(location=MATCH.place, on_top_of=MATCH.on, holding="blank"))
    def climb_directly(self, obj, monkey, goal):
        print("Monkey climbs onto the %s." % obj)
        self.modify(monkey, on_top_of=obj)
        self.retract(goal)

    @Rule(
        AS.goal << GoalIsTo(action="on", arguments=MATCH.obj),
        Monkey(on_top_of=MATCH.obj))
    def already_on_object(self, goal):
        self.retract(goal)

In [8]:
class EatObjectRules:
    """EAT OBJECT RULES"""

    @Rule(
        GoalIsTo(action="eat", arguments=MATCH.obj),
        Monkey(holding=~MATCH.obj),
        NOT(GoalIsTo(action="hold", arguments=MATCH.obj)))
    def hold_to_eat(self, obj):
        self.declare(GoalIsTo(action="hold", arguments=obj))
    
    @Rule(
        AS.goal << GoalIsTo(action="eat", arguments=MATCH.name),
        AS.monkey << Monkey(holding=MATCH.name),
        AS.thing << Thing(name=MATCH.name))
    def satisfy_hunger(self, goal, thing, monkey, name):
        print("Monkey eats the %s." % name)
        self.modify(monkey, holding="blank")
        self.retract(goal)
        self.retract(thing)

In [9]:
class MonkeesAndBananas(ChestUnlockingRules,
                        HoldObjectRules,
                        MoveObjectRules,
                        WalkToPlaceRules,
                        GetOnObjectRules,
                        EatObjectRules,
                        KnowledgeEngine):

    @DefFacts()
    def startup(self):
        """INITIAL STATE"""
        yield Monkey(location="t5-7", on_top_of="green-couch", holding="blank")
        yield Thing(name="green-couch", location="t5-7", on_top_of="floor", weight="heavy")
        yield Thing(name="red-couch", location="t2-2", on_top_of="floor", weight="heavy")
        yield Thing(name="big-pillow", location="t2-2", on_top_of="red-couch", weight="light")
        yield Thing(name="red-chest", location="t2-2", on_top_of="big-pillow", weight="light")
        yield Chest(name="red-chest", contents="ladder", unlocked_by="red-key")
        yield Thing(name="blue-chest", location="t7-7", on_top_of="ceiling", weight="light")
        yield Chest(name="blue-chest", contents="bananas", unlocked_by="blue-key")
        yield Thing(name="blue-couch", location="t8-8", on_top_of="floor", weight="heavy")
        yield Thing(name="green-chest", location="t8-8", on_top_of="ceiling", weight="light")
        yield Chest(name="green-chest", contents="blue-key", unlocked_by="red-key")
        yield Thing(name="red-key", location="t1-3", on_top_of="floor", weight="light")
        yield GoalIsTo(action="eat", arguments="bananas")

In [10]:
mab = MonkeesAndBananas()
mab.reset()
mab.run()


Monkey jumps off the green-couch onto the floor.
Monkey walks to t2-2.
Monkey climbs onto the red-couch.
Monkey climbs onto the big-pillow.
Monkey grabs the red-chest.
Monkey throws the red-chest off the big-pillow onto the floor.
Monkey jumps off the big-pillow onto the floor.
Monkey walks to t1-3.
Monkey grabs the red-key.
Monkey walks to t2-2 holding the red-key.
Monkey opens the red-chest with the red-key revealing the ladder.
Monkey drops the red-key.
Monkey climbs onto the red-chest.
Monkey grabs the ladder.
Monkey jumps off the red-chest onto the floor.
Monkey walks to t7-7 holding the ladder.
Monkey drops the ladder.
Monkey climbs onto the ladder.
Monkey grabs the blue-chest.
Monkey throws the blue-chest off the ladder onto the floor.
Monkey jumps off the ladder onto the floor.
Monkey grabs the ladder.
Monkey walks to t8-8 holding the ladder.
Monkey drops the ladder.
Monkey climbs onto the ladder.
Monkey grabs the green-chest.
Monkey throws the green-chest off the ladder onto the floor.
Monkey jumps off the ladder onto the floor.
Monkey walks to t2-2.
Monkey grabs the red-key.
Monkey walks to t8-8 holding the red-key.
Monkey opens the green-chest with the red-key revealing the blue-key.
Monkey drops the red-key.
Monkey climbs onto the green-chest.
Monkey grabs the blue-key.
Monkey jumps off the green-chest onto the floor.
Monkey walks to t7-7 holding the blue-key.
Monkey opens the blue-chest with the blue-key revealing the bananas.
Monkey drops the blue-key.
Monkey climbs onto the blue-chest.
Monkey grabs the bananas.
Monkey eats the bananas.