## Drools does Pacman

In the interests of finding a fun and more complex problem with multiple things happening, I decided to start writting a Pacman implementation. The basics are now in place, in that I can load a grid and guide a Pacman around it with a Monster (Ghost) tracking it.

The grid is loaded from a text file that uses symbols to map the layout, currently I use a very simple layout that looks like this:

* * * * * * * * * * ** # . . . _ . . . # ** . * * * * * * * . ** . * * * * * * * . ** . . . . # . . . . ** . * * * * * * * . ** . * * * * * * * . ** . . . . # . . . . ** . * * * * * * * . ** . * * * * * * * . ** # . . . _ . . . # ** * * * * * * * * * *

* Wall
. Food
# Power Pill
_ Empty

When the game starts off Pacman is in the lower empty cell and the Monster in the top empty cell. The arrow keys move Pacman around and the Monster tracks the Pacman. The rules are split into four drl files; base, key-handlers, Pacman and Monster.

A KeyListener implementation is hooked up to a WorkingMemory EntryPoint and feeds in key presses. From the KeyEvent it creates a derived (not in WorkingMemory) possible Direction and validates that Direction. If the new Direction is valid the old Direction is retracted and the new one inserted. The exitpoint is used to send print information to a channel, which is appended to the GUI.

rule KeyListenerRule dialect "mvel" when   $keyEvent : KeyEvent() from entry-point "KeyListener"$char     : Character( name == "Pacman" )   $l : Location( character ==$char )   $newD : Direction() from createDirection($l.character, $keyEvent )$target   : Cell( row == ($l.row +$newD.vertical), col == ($l.col +$newD.horizontal) )               CellContents( cell == $target, cellType != CellType.WALL )$oldD     : Direction( character == $l.character )then exitPoints["ConsoleExitPoint" ].insert( "insert " +$newD + "n" );   retract( $keyEvent ); retract($oldD );   insert( $newD );end As the Tick, simulated time, increases we attempt to change a Character’s Location based on the given Direction. The rule makes sure the new Location is valid, and if so schedules the move to the new Location, in time with the Tick. rule validDirection dialect "mvel" when$l : Location( )  $d : Direction( character ==$l.character )  $target : Cell( row == ($l.row + $d.vertical), col == ($l.col +$d.horizontal) ) CellContents( cell ==$target, cellType != CellType.WALL )  not ScheduledLocationUpdate( location == $l )$t : Tick()then  insert( new ScheduledLocationUpdate($l,$l.row += $d.vertical,$l.col += $d.horizontal,$t.tock + 1) );endrule setNewDirection dialect "mvel" when  $s : ScheduledLocationUpdate()$l : Location( this == $s.location ) Tick( tock ==$s.tock )then exitPoints["ConsoleExitPoint"].insert( "set new Location " + $l + "n" ); modify($l ) { row = $s.row, col =$s.col }; retract( $s );end As the pacman moves around it detects the CellContents, if it’s Food it’ll increase the score by 1. rule EatFood dialect "mvel" no-loop when$char : Character( name == "Pacman" )   $l : Location( character ==$char )   $target : Cell( row ==$l.row, col == $l.col)$contents : CellContents( cell == $target, cellType == CellType.FOOD )$s : Score()  then   modify( $contents ) { cellType = CellType.EMPTY }; modify($s ) { score += 1 };   end

Among other things it’s also looking out for Monster collisions.

rule MonsterCollision dialect "mvel" no-loop when   $pac : Character( name == "Pacman" )$pacLoc : Location( character == $pac )$mon    : Character( name == "Monster" )   $monLoc : Location( character ==$mon, col == $pacLoc.col, row ==$pacLoc.row )   $t : Tick()then retract($t );endrule FinishedKilled  dialect "mvel" when   $pac : Character( name == "Pacman" )$pacLoc : Location( character == $pac )$mon    : Character( name == "Monster" )   $monLoc : Location( character ==$mon, col == $pacLoc.col, row ==$pacLoc.row )   not Tick()      $s : Score()then exitPoints["ConsoleExitPoint"].insert( "Killed!!!! score = " +$s.score + " n" );   kcontext.knowledgeRuntime.halt();end

The implementation currently uses a simple distance diff from the Monster to the Pacman to determine the Monster direction. The direction must be valid and if both a horizontal and a vertical direction is valid it uses dynamic salience to pick the one with the highest difference. This is a simplistic approach, just to get the ball rolling, ideally we would implement the logic as in the original arcade game.

rule GoRight dialect "mvel"  salience (Math.abs( $df.colDiff )) when$df   : DirectionDiff(colDiff > 0 )   $target : Cell( row ==$df.row, col == ($df.col + 1) ) CellContents( cell ==$target, cellType != CellType.WALL )      $d : Direction( character ==$df.fromChar, horizontal != Direction.RIGHT)   then   retract( $d ); retract($df );   insert( new Direction($df.fromChar, Direction.RIGHT, 0 ) ); endrule GoDown dialect "mvel" salience (Math.abs($df.rowDiff ))  when    $df : DirectionDiff(rowDiff < 0 )$target : Cell(  col == $df.col, row == ($df.row - 1))    CellContents( cell == $target, cellType != CellType.WALL )$d : Direction( character == $df.fromChar, vertical != Direction.DOWN) then retract($d );    retract( $df ); insert( new Direction($df.fromChar, 0,  Direction.DOWN ) );end

Running the game and pressing the left arrow gives the following output. Notice Pacman moves to the left and stops when he reaches the wall, the Monster tracks him to the left and then comes down for the kill.

insert Direction Pacman speed = 5 LEFTset new Location Location Monster speed = 3 10:4set new Location Location Pacman speed = 5 1:4set new Location Location Monster speed = 3 10:3set new Location Location Pacman speed = 5 1:3set new Location Location Monster speed = 3 10:4set new Location Location Monster speed = 3 10:3set new Location Location Pacman speed = 5 1:2set new Location Location Monster speed = 3 10:2retract Direction Monster speed = 3 LEFTset new Location Location Monster speed = 3 10:1set new Location Location Pacman speed = 5 1:1retract Direction Pacman speed = 5 LEFTset new Location Location Monster speed = 3 9:1set new Location Location Monster speed = 3 8:1set new Location Location Monster speed = 3 7:1set new Location Location Monster speed = 3 6:1set new Location Location Monster speed = 3 5:1set new Location Location Monster speed = 3 4:1set new Location Location Monster speed = 3 3:1set new Location Location Monster speed = 3 2:1set new Location Location Monster speed = 3 1:1Killed!!!! score = 8

I’ve committed everything to drools-examples, you’ll need drools trunk, as there are a few fixes necessary for this to work:
trunk/drools-examples/drools-examples-drl/src/main/java/org/drools/examples/pacman/
trunk/drools-examples/drools-examples-drl/src/main/resources/org/drools/examples/pacman/
trunk/drools-examples/drools-examples-drl/src/main/rules/org/drools/examples/pacman/

It’s still very basic. Next it needs to be hooked up to a GUI, such as SwtPacman, the source code of which is provided here on a wiki page where you can also add notes:
http://www.jboss.org/community/docs/DOC-14378

Then it should be updated to real Pacman grid layouts and all the monsters added, each with it’s own custom logic. There is also additional logic like Ghosts slowing down when they turn corners and Pacman slowing down when he eats food. You can find out all the details at Wikipedia, here.

Ghost ColorOriginal Pac Man[13]American Pac-Man
Character (Personality)TranslationNicknameTranslationAlternate
character
Alternate
nickname
Character (Personality)Nickname