SimpleGame:  A JRLE Game Explained

 

For questions about this document or this project contact Thomas Seufert at seufet@users.sourceforge.net.

 

Overview.. 1

Running SimpleGame. 1

“Playing” The Game. 2

Commands Summary. 3

Game Play. 3

Specifying Action / Reaction With The RLE. 4

Changing Constitution Changes Max Hp. 4

Updating The Cur Hp Display. 6

Stepping On A Trap Hurts The Player 7

When The Player Dies, We See A Grave. 8

Moving At a Monster Attacks It 8

Monster Death. 9

The Quest – Player Victory. 9

“Hot” Usage of the Variant Manager 10

Changing Languages. 10

 

 

Overview

 

The purpose of SimpleGame is to demonstrate use of the Roguelike Engine(RLE) with a simple, functional game.

 

Running SimpleGame

 

To run or build SimpleGame, you need to use the JDK 1.5 Beta.  To check your version, type:

 

java –version

 

If the version number is less than 1.5, download JDK 1.5 from java.sun.com.

 

The easiest way to start SimpleGame is to use Ant.  If you do not have Ant 1.6.1 installed, please download it from ant.apache.org.  Ant is a Java build and deployment tool.

 

Assuming that you have Ant installed, from the command line type:

 

ant simple

 

You should see a window open up entitled “SimpleGame, A Roguelike Engine Demonstration.”  If an error occurs, check that the ant /bin directory is in your path, and that you are running from the directory to which you installed the RLE.

 

 

“Playing” The Game

 

SimpleGame is not exactly stimulating entertainment, but does demonstrate several useful concepts and techniques. 

 

When the game starts, the “Welcome” perspective is shown.  This displays a Web Page found at variants/SimpleGame/Web/welcome.html.  To play the game, press space bar or enter.  You will be taken to the Game perspective.  The game window should now look like this:

 

 

Notice that it vaguely resembles a way-stripped-down version of Angband.  At left, we can see that the player has one stat, Con (Constitution), as well as Max Hp (Maximum Hit Points) and Cur Hp (Current Hit Points), a Melee Value (how much damage the player does in an attack) and Experience.  Finally, speed appears at bottom.

 

Commands Summary

 

Numeric Keypad – Moves the player around.  Move at a monster to attack it

i – display inventory

d – drop an item

g – get an item and place it in the inventory

t – take off/remove an equipped item

w – wear/wield an item

e – display the player’s equipment

 

Game Play

 

Floors are represented by periods, walls by #, and traps by ^.  Monsters are the alphabetic characters of varying sizes that move toward you and try to kill you.

 

Move the player around, and killing monsters until reaching the Experience goal (settable in the Variant Manager at root/plug-ins/quest/experience-quest) or getting killed.  You can tweak the balance using the Variant Manager to make the game easy, hard, or impossible.  There are items you can pick up and drop, some of which will help you, and some of which do nothing.    Fun, huh?

 

Note that the player can now regenerate simply by moving around.

 

Please feel free to post questions or comments on the online forum at http://sourceforge.net/forum/forum.php?forum_id=311539.

 

Specifying Action / Reaction With The RLE

 

There are a few actions one can perform in this game that bear notice.

 

Changing Constitution Changes Max Hp

 

Let’s open the Variant Manager and look at the SimpleGame project.  Type “ant vm” or run vm.bat, then click File -> Open, and select “SimpleGame”. 

 

In the Variant Manager, open the node “root/player/attributes”.  You should see several child nodes.  First click on “con”. 

 

 

Constitution’s type is specified as “Computed,” which means that its value must be computed from the values of other attributes, and it need not be saved to disk.  Clicking to “hp-max”, we see the same is true for that attribute.

 

Now click on “hp”.  Its type is listed as “Persistent,” indicating that its value needs to be explicitly set by variant code; it must be saved to disk because it cannot be computed from other attributes. 

 

How are Computed attributes computed?  Click back to “hp-max”, and notice the Editor called “Computer Plug-in”.

 

  The class name is rle.core.data.SimpleFormulaComputer, and it specifies a formula parameter:

 

            formula=20 + 5 * ${con-hp-bonus[${statIdx(${con})}]}

 

The “Computer” Plug-In specifies that the SimpleFormulaComputer class should be used to calculate the value of Hit Points Max when one is required.  In this formula, we can see that the Max Hp will be 20 plus 5 times a big, ugly expression which uses SimpleFormulaComputer’s scripting syntax.  Breaking it down a bit:

 

${con} – the value of the “con” attribute

${statIdx()} – special function that computes the index that should be used in a lookup table for a particular stat value

${con-hp-bonus[]} – accesses an entry in the 1D lookup table “con-hp-bonus”.

 

Putting that together, we can see that the Max Hp will depend on the value of “con” as specified by bonuses in the lookup table “con-hp-bonus”.

 

How does the game know that it should recompute hp-max each time con changes?  Note the “Dependencies” control for hp-max.  The fact that con is selected here causes the RuntimeData class which manages the player’s Runtime Attributes to dispose of the current value of hp-max when con changes.  This is accomplished via the RuntimeData.disposeComputed() method.  This disposal is detected by the rle.core.ui.RuntimeAttributeLabel instance which displays Max Hp, causing it to query the RuntimeData instance for a new value, by calling RuntimeData.get().

 

Updating The Cur Hp Display

 

If we look at the source file SimpleGame.java, we can see how the RuntimeAttributeLabel maxHpLabel was configured:

 

      

 

    // Build the info display

    JPanel left = new JPanel();

 

    left.setOpaque(false);

    left.setLayout(new SpringLayout());

 

    RuntimeData data = context.currentPlayer().getRuntimeData();

    left.add(createLabel("Con:"));

    left.add(createRTALabel("con",data));

 

    left.add(createLabel(" "));

    left.add(createLabel(" "));

 

    left.add(createLabel("Max Hp"));

    left.add(createRTALabel("hp-max",data));

 

    left.add(createLabel("Cur Hp"));

    left.add(createRTALabel("hp",data));

 

 

JLabel createLabel(String s){

    JLabel label = new JLabel(s);

    label.setForeground(Color.white);

    return label;

  }

 

  RuntimeAttributeLabel createRTALabel(String prop, RuntimeData data){

    RuntimeAttributeLabel label = new RuntimeAttributeLabel(prop,data);

    label.setForeground(Color.white);

    return label;

  }

 

The rle.core.ui.RuntimeAttributeLabel is a useful component which is assigned to update a display to reflect changes to a particular RuntimeData attribute automatically.  Therefore, when changes are made to hp, no call is required to ensure that the display is updated, simply call data.set(“hp”,newValue), and the Core infrastructure takes care of it.  This is a powerful example of the value an event-driven architecture can provide.

 

Stepping On A Trap Hurts The Player

 

To make traps hurt the player, the SimpleGame uses a Plug-In event handler.  In the Variant Manager, open the node “root /terrain”.  Click to the tab local -> listener.

 

For the Trap type, the rle.simple.action.TrapAction class is specified, with the parameter damage set to some value.  The class TrapAction implements rle.vm.PlugIn, allowing it to be “plugged in”, and rle.core.dungeon.event.DungeonListener, allowing it to be notified when creatures enter and leave a location. 

 

From Dungeon.java:

 

     /**

      * Place terrain t at point p.

      */

      public void place(Terrain t, Point p){

              

            

 

            // If t has a dungeon listener, we add it to listen at its location only

            DungeonListener terrainListener = t.dungeonListener();

            if (terrainListener != null){

                  addDungeonListener(p,terrainListener);

            }

      }

 

…and from Terrain.java:

 

      public void setConfig(InfoView view){

            super.setConfig(view);

 

            listener = (DungeonListener)view.createPlugIn("listener");

      }

 

      public DungeonListener dungeonListener(){

            return listener;

      }

 

Note that the value of “listener” is set from information coming in from the InfoView view, which in turn comes from the Variant Manager.

 

When The Player Dies, We See A Grave

 

Setting up an action to occur when the Player dies is handled in SimpleGame.java.  An inner class PropertyChangeListener “PlayerDeathListener” is added to the Player instance’s RuntimeData to receive notification of changes to the “hp” attribute only.  If it finds that the value is < 0, it brings up the grave scene with the call to SimpleGame.showDeathPerspective().

 

  class PlayerDeathListener implements PropertyChangeListener {

    public void propertyChange(PropertyChangeEvent e) {

      // We assume we were only added for "hp"

      Integer newValue = (Integer)e.getNewValue();

      if (newValue < 0) showDeathPerspective();

      System.out.println("Player hp went from " + e.getOldValue() + " to "+

                         e.getNewValue());

    }

  }

 

  Player createPlayer(){

      InfoView playerInfo = info.findInfo("Player/default");

        assert playerInfo != null;

      Player player = new Player();

      player.setConfig(playerInfo);

 

        RuntimeData data = player.getRuntimeData();

        data.addPropertyChangeListener("hp", new PlayerDeathListener());

 

      return player;

  }

 

This is an excellent example of how the JRLE allows developers flexibility in determining how to implement functionality.  PlayerDeathListener is an inner class whose use in this example is determined by code alone; it could just as easily have been a Plug-In specified as an attribute to Player or Dungeon.  For this example, however, pluggable behavior is not required, so the simpler approach of tighter coupling was chosen.

 

Moving At a Monster Attacks It

 

It may not be obvious at first, but attempting to move at a Monster attacks and will eventually kill it.  This behavior is implemented in the rle.simple.action.Mover class.

 

Creature victim = dungeon.creature(newLoc);

      if (victim != null){

        meleeAttack(player,victim);

      }

      else{

        dungeon.remove(player);

        dungeon.place(player, newLoc);

      }

 

 

void meleeAttack(Creature attacker, Creature victim){

    // Access the attacker data

    RuntimeData attackerData = attacker.getRuntimeData();

 

    // Compute the damage

    int damage = attackerData.getInt("melee");

 

    // Access the victim data

    RuntimeData victimData = victim.getRuntimeData();

 

    // Apply the armor

    // AC of 0 gives full damage, AC of 25 gives 75% damage, AC of 100 gives no damage

    int armorValue = victim.getRuntimeData().getInt("ac");

    int modDamage = damage * (100 - armorValue) / 100;

 

    // Ensure no negative damage, or increased damage for negative AC

    modDamage = max(0,min(damage,modDamage));

 

    // Hurt me!

    info("You hit the " + victim.config().getTranslation("displayname"));

    info("Melee Value '" + damage + "' resulted in " + modDamage + " Damage.");

    victimData.set("hp",victimData.getInt("hp")-modDamage);

  }

Monster Death

 

How do monsters know they’ve died?  See the inner class rle.simple.dungeon.DungeonBuilder.MonsterDeathListener.  From DungeonBuilder.java:

 

    Monster m = new Monster();

    m.setConfig(creatureInfo);

 

    RuntimeData data = m.getRuntimeData();

    data.addPropertyChangeListener("hp", new MonsterDeathListener(m));

 

The Quest – Player Victory

 

The SimpleGame quest is very similar to the Monster and Player DeathListeners—it watches a single Runtime Attribute, looking for a particular condition to react to.  The only difference is that this time we’re watching for good news!  From rle.simple.ExperienceQuest:

public void propertyChange(PropertyChangeEvent e) {

    // We assume we were only added for "experience"

    Integer newValue = (Integer)e.getNewValue();

 

    // Access the victory condition

 

 

    if (newValue >= victoryAmt) {

      SimpleGame game = (SimpleGame)getContext();

      game.showVictoryPerspective();

    }

    System.out.println("Player experience went from " + e.getOldValue() + " to "+

                       e.getNewValue());

  }

 

Of course, the ExperienceQuest is a PlugIn, and is linked from the Variant Manager into SimpleGame by the lines:

 

RuntimeData data = player.getRuntimeData();

 

 

        // make sure we can win

        InfoView questInfo = infoRoot.findInfo("quest/experience-quest");

        PropertyChangeListener expQuest = (PropertyChangeListener)questInfo.createPlugIn("plugindata");

        data.addPropertyChangeListener("experience", expQuest);

 

“Hot” Usage of the Variant Manager

 

SimpleGame can launch the Variant Manager to modify much (but not all) game data while playing.  Use the menu option Variant -> Launch Variant Manager.

 

Changing Languages

 

SimpleGame currently supports play in English or Spanish.  Switch languages during the game using the menu option Variant -> Language.

 

 

 

Revision Date

User

Comments

5/17/2004

Thomas Seufert

Created

5/30/2004

Thomas Seufert

Update for 0.0.7

6/15/2004

Thomas Seufert

Update for 0.0.8

7/8/2004

Thomas Seufert

Update for 0.0.9