Friday, September 30, 2011

roguelike tutorial 13: aggressive monsters

Now that we have all these cool weapons and armor and food, the bat's and fungi aren't as troublesome as they used to be. We need something that charges straight for us, something that peruses us relentlessly, a simple minded foe that we don't want to run into. For that we need a way to find a path to the player.

The monsters are only going to pathfind to the player if they see him so we could do the simpleist thing and move east if the player is east, north if the player is north, etc. That would almost always work well enough but let's go ahead and add real path finding. Entire tutorials are written about path finding but for this we can use the following code that implements the A Star algorithm and is specialized for our creatures:

package rltut;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

public class PathFinder {
       private ArrayList<Point> open;
       private ArrayList<Point> closed;
       private HashMap<Point, Point> parents;
       private HashMap<Point,Integer> totalCost;
     
       public PathFinder() {
             this.open = new ArrayList<Point>();
             this.closed = new ArrayList<Point>();
             this.parents = new HashMap<Point, Point>();
             this.totalCost = new HashMap<Point, Integer>();
       }
     
       private int heuristicCost(Point from, Point to) {
             return Math.max(Math.abs(from.x - to.x), Math.abs(from.y - to.y));
       }

       private int costToGetTo(Point from) {
             return parents.get(from) == null ? 0 : (1 + costToGetTo(parents.get(from)));
       }
     
       private int totalCost(Point from, Point to) {
             if (totalCost.containsKey(from))
                 return totalCost.get(from);
           
             int cost = costToGetTo(from) + heuristicCost(from, to);
             totalCost.put(from, cost);
             return cost;
       }

       private void reParent(Point child, Point parent){
             parents.put(child, parent);
             totalCost.remove(child);
       }

       public ArrayList<Point> findPath(Creature creature, Point start, Point end, int maxTries) {
             open.clear();
             closed.clear();
             parents.clear();
             totalCost.clear();
       
             open.add(start);
           
             for (int tries = 0; tries < maxTries && open.size() > 0; tries++){
                   Point closest = getClosestPoint(end);
                 
                   open.remove(closest);
                   closed.add(closest);

                   if (closest.equals(end))
                         return createPath(start, closest);
                   else
                         checkNeighbors(creature, end, closest);
             }
             return null;
       }

        private Point getClosestPoint(Point end) {
            Point closest = open.get(0);
            for (Point other : open){
                if (totalCost(other, end) < totalCost(closest, end))
                    closest = other;
            }
            return closest;
        }

        private void checkNeighbors(Creature creature, Point end, Point closest) {
            for (Point neighbor : closest.neighbors8()) {
                if (closed.contains(neighbor)
                 || !creature.canEnter(neighbor.x, neighbor.y, creature.z)
                 && !neighbor.equals(end))
                     continue;
    
                if (open.contains(neighbor))
                    reParentNeighborIfNecessary(closest, neighbor);
                else
                    reParentNeighbor(closest, neighbor);
            }
        }

        private void reParentNeighbor(Point closest, Point neighbor) {
            reParent(neighbor, closest);
            open.add(neighbor);
        }

        private void reParentNeighborIfNecessary(Point closest, Point neighbor) {
            Point originalParent = parents.get(neighbor);
            double currentCost = costToGetTo(neighbor);
            reParent(neighbor, closest);
            double reparentCost = costToGetTo(neighbor);
  
            if (reparentCost < currentCost)
                open.remove(neighbor);
            else
                reParent(neighbor, originalParent);
        }

        private ArrayList<Point> createPath(Point start, Point end) {
            ArrayList<Point> path = new ArrayList<Point>();

            while (!end.equals(start)) {
                path.add(end);
                end = parents.get(end);
            }

            Collections.reverse(path);
            return path;
        }
    }

So far I've liked having Points and Lines where all the work is done in the constructor and would like to extend this idea to Paths. So let's create a Path class that hides the details from us.

package rltut;

import java.util.List;

public class Path {

  private static PathFinder pf = new PathFinder();

  private List<Point> points;
  public List<Point> points() { return points; }

  public Path(Creature creature, int x, int y){
      points = pf.findPath(creature, 
                           new Point(creature.x, creature.y, creature.z), 
                           new Point(x, y, creature.z), 
                           300);
  }
}

If having our Line path do all that work in the constructor was questionable then this is far more questionable. I may end up regretting this and making sure future employers never see this but for now I'll try it and we'll see if it becomes a problem.


Like with our other creatures we need a CreatureAi. I'll take the easy and uncreative way out and pick Zombies for our new monster. The ZombieAi will be a bit different than the others since it needs a reference to the player so it knows who to look for.

package rltut;

import java.util.List;

public class ZombieAi extends CreatureAi {
  private Creature player;

  public ZombieAi(Creature creature, Creature player) {
    super(creature);
    this.player = player;
  }
}

During the zombie's turn it will move to the player if it can see him, otherwise it will wander around. Since zombies are a little slow, I gave them a chance of doing nothing during their turn for just a little bit of interest.

public void onUpdate(){
      if (Math.random() < 0.2)
          return;
  
      if (creature.canSee(player.x, player.y, player.z))
          hunt(player);
      else
          wander();
  }

Creating a new path each turn may not be the best idea but we'll only have a few zombies and rogulikes are turn based so it shouldn't be too much of a problem. If it does be come a performance problem we can fix it.

The hunt method finds a path to the target and moves to it.
public void hunt(Creature target){
      List<Point> points = new Path(creature, target.x, target.y).points();
  
      int mx = points.get(0).x - creature.x;
      int my = points.get(0).y - creature.y;
  
      creature.moveBy(mx, my, 0);
  }

Now we can add zombies to our factory. Since the Ai needs a reference to the player, we have to pass that in.
public Creature newZombie(int depth, Creature player){
      Creature zombie = new Creature(world, 'z', AsciiPanel.white, "zombie", 50, 10, 10);
      world.addAtEmptyLocation(zombie, depth);
      new ZombieAi(zombie, player);
      return zombie;
  }

To add zombies to our world we need to update createCreatures in the PlayScreen.

for (int i = 0; i < z + 3; i++){
         factory.newZombie(z, player);
     }

Adding pathfinding to a game is a big deal. The PathFinder we're using for now is good enough but has some major inefficiencies. I'm using a HashMap of points rather than an array so we don't have to worry about the world size or anything like that. This will take up less memory and handle aarbitrarily large maps but it will be much much slower.

download the code

Tuesday, September 27, 2011

roguelike tutorial 12: weapons and armor

Time for some weapons and armor.

Since we have a very simple Attack value and Defense value for creatures, let's use that for our weapons and armor. Go ahead and add that to the Item class.

private int attackValue;
  public int attackValue() { return attackValue; }
  public void modifyAttackValue(int amount) { attackValue += amount; }

  private int defenseValue;
  public int defenseValue() { return defenseValue; }
  public void modifyDefenseValue(int amount) { defenseValue += amount; }


And create some new items in our factory class.

public Item newDagger(int depth){
    Item item = new Item(')', AsciiPanel.white, "dagger");
    item.modifyAttackValue(5);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newSword(int depth){
    Item item = new Item(')', AsciiPanel.brightWhite, "sword");
    item.modifyAttackValue(10);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newStaff(int depth){
    Item item = new Item(')', AsciiPanel.yellow, "staff");
    item.modifyAttackValue(5);
    item.modifyDefenseValue(3);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newLightArmor(int depth){
    Item item = new Item('[', AsciiPanel.green, "tunic");
    item.modifyDefenseValue(2);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newMediumArmor(int depth){
    Item item = new Item('[', AsciiPanel.white, "chainmail");
    item.modifyDefenseValue(4);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newHeavyArmor(int depth){
    Item item = new Item('[', AsciiPanel.brightWhite, "platemail");
    item.modifyDefenseValue(6);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item randomWeapon(int depth){
    switch ((int)(Math.random() * 3)){
    case 0: return newDagger(depth);
    case 1: return newSword(depth);
    default: return newStaff(depth);
    }
  }

  public Item randomArmor(int depth){
    switch ((int)(Math.random() * 3)){
    case 0: return newLightArmor(depth);
    case 1: return newMediumArmor(depth);
    default: return newHeavyArmor(depth);
    }
  }

Don't forget to add them to the newly created game in the PlayScreen createItems method.

If you play you should be able to see them and carry them around.


If we want to use them then we need to add some methods to the creature class to equip and unequip weapons and armor. For now, creatures can wield one weapon and wear one pice of armor at a time. If you want separate armor slots for helmet, rings, shoes, etc, you can do that too. I'm also going to use the same methods to deal with armor or weapons.

private Item weapon;
  public Item weapon() { return weapon; }

  private Item armor;
  public Item armor() { return armor; }
public void unequip(Item item){
      if (item == null)
         return;
  
      if (item == armor){
          doAction("remove a " + item.name());
          armor = null;
      } else if (item == weapon) {
          doAction("put away a " + item.name());
          weapon = null;
      }
  }
public void equip(Item item){
      if (item.attackValue() == 0 && item.defenseValue() == 0)
          return;
  
      if (item.attackValue() >= item.defenseValue()){
          unequip(weapon);
          doAction("wield a " + item.name());
          weapon = item;
      } else {
          unequip(armor);
          doAction("put on a " + item.name());
          armor = item;
      }
  }

And make sure that we unequip anything we eat or drop.

public void eat(Item item){
      if (item.foodValue() < 0)
         notify("Gross!");
  
      modifyFood(item.foodValue());
      inventory.remove(item);
      unequip(item);
  }

public void drop(Item item){
    if (world.addAtEmptySpace(item, x, y, z)){
        doAction("drop a " + item.name());
        inventory.remove(item);
        unequip(item);
    } else {
        notify("There's nowhere to drop the %s.", item.name());
    }
}

The easiest way to use our new equipment when calculating our overall attack and defense values is just to add them to the creature's getters.
public int attackValue() {
    return attackValue
     + (weapon == null ? 0 : weapon.attackValue())
     + (armor == null ? 0 : armor.attackValue());
  }

  public int defenseValue() {
    return defenseValue
     + (weapon == null ? 0 : weapon.defenseValue())
     + (armor == null ? 0 : armor.defenseValue());
  }


And now create an EquipScreen.
package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class EquipScreen extends InventoryBasedScreen {

  public EquipScreen(Creature player) {
    super(player);
  }

  protected String getVerb() {
    return "wear or wield";
  }

  protected boolean isAcceptable(Item item) {
    return item.attackValue() > 0 || item.defenseValue() > 0;
  }

  protected Screen use(Item item) {
    player.equip(item);
    return null;
  }
}

Wasn't that easy?

All that's left is making the 'w' wear or wield items in the PlayScreen. I prefer having one key for both rather than one for armor and another for weapons. If you'd rather have different keys then you can do that.
case KeyEvent.VK_W: subscreen = new EquipScreen(player); break;

And now you can find and use weapons and armor. Play around with different attackValues, defenseValues, and hit points. You can have 3 or 4 weapons or 300 weapons. Try changing how abundant weapons and armor are or maybe have some more common than others.


One advantage of having all our items be the same class but have different values is that an item can be more than one thing, e.g. you could make an edible weapon and the player would be able to eat or wield it with no extra code or you could have have a weapon that increases attack and defense.
public Item newEdibleWeapon(int depth){
    Item item = new Item(')', AsciiPanel.yellow, "baguette");
    item.modifyAttackValue(3);
    item.modifyFoodValue(50);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

You can't do that with Weapon, Food, and Armor subclasses.


Wouldn't it also be nice if the inventory screens told us what we have equipped so we don't eat the armor we're wearing or try to wear something we're already wearing? Here's one possible update to the InventoryBasedScreen:
String line = letters.charAt(i) + " - " + item.glyph() + " " + item.name();
    
  if(item == player.weapon() || item == player.armor())
      line += " (equipped)";
    
  lines.add(line);
Maybe the EquipScreen shouldn't let us equip what we're already using. Or maybe wearing or wielding what's already equipped should un-wear or un-weild it? That way the 'w' key can equip or unequip. It's your game so it's up to you. Implementing those is left as an exercise.

download the code

Friday, September 23, 2011

roguelike tutorial 11: hunger and food

Now that we've got monsters to kill and the ability to pick up and use things, how about we add some corpses and the ability to eat them?

We first need to update our Item class to support some nutritional value.
private int foodValue;
public int foodValue() { return foodValue; }
public void modifyFoodValue(int amount) { foodValue += amount; }

And update our creature to leave corpses.

public void modifyHp(int amount) {
    hp += amount;
    if (hp < 1) {
        doAction("die");
        leaveCorpse();
        world.remove(this);
    }
}

private void leaveCorpse(){
    Item corpse = new Item('%', color, name + " corpse");
    corpse.modifyFoodValue(maxHp * 3);
    world.addAtEmptySpace(corpse, x, y, z);
}

Update creatures to also have hunger.

private int maxFood;
public int maxFood() { return maxFood; }

private int food;
public int food() { return food; }

public void modifyFood(int amount) {
    food += amount;
    if (food > maxFood) {
        food = maxFood;
    } else if (food < 1 && glyph == '@') {
        modifyHp(-1000);
    }
}

Do you see the terrible hack there? We only want the player to be able to die of starvation since it would be boring if every monster dropped dead of starvation and if they need to eat they'd have to go around killing each other. We could have an entire ecosystem of bats farming fungus, that would introduce some neat gameplay options, but that's quite a bit more complicated than I'd like to do right now. Anyway dying only if you look like a @ is still an ugly hack — a hack so ugly our children's children will feel the shame. Let's fix it right now:

public void modifyFood(int amount) {
    food += amount;
    if (food > maxFood) {
        food = maxFood;
    } else if (food < 1 && isPlayer()) {
        modifyHp(-1000);
    }
}

public boolean isPlayer(){
    return glyph == '@';
}

The hack is still there but it's isolated for now. Later if we have other creatures with an @ glyph or if the player can assume other forms, we can update this one isolated place. One thing I've learned from real life software is that although ugly hacks are inevitable, you can always isolate them so the callers don't need to deal with it.

But enough preaching, our Creatures also need a method to eat.

public void eat(Item item){
    modifyFood(item.foodValue());
    inventory.remove(item);
}

Don't forget that creatures should start with decent belly full. Add this to the creature constructor:

this.maxFood = 1000;
this.food = maxFood / 3 * 2;

Now add an EatScreen so we can eat something in our inventory.

package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class EatScreen extends InventoryBasedScreen {

    public EatScreen(Creature player) {
        super(player);
    }

    protected String getVerb() {
        return "eat";
    }

    protected boolean isAcceptable(Item item) {
        return item.foodValue() != 0;
    }

    protected Screen use(Item item) {
        player.eat(item);
        return null;
    }
}

Wow, that was easy. InventoryBasedScreen is paying off already.


For the PlayScreen we need to map the 'e' key to the EatScreen.
case KeyEvent.VK_E: subscreen = new EatScreen(player); break;
We should also let the player know how hungry he is. Change the stats in displayOutput to this:
String stats = String.format(" %3d/%3d hp %8s", player.hp(), player.maxHp(), hunger());
And add a helper method. You can use whatever text and amounts you want.
private String hunger(){
    if (player.food() < player.maxFood() * 0.1)
        return "Starving";
    else if (player.food() < player.maxFood() * 0.2)
        return "Hungry";
    else if (player.food() > player.maxFood() * 0.9)
        return "Stuffed";
    else if (player.food() > player.maxFood() * 0.8)
        return "Full";
    else
        return "";
}

Of course none of this will do anything if we don't use up the food we've eaten. Go ahead and add a call to modifyFood in the relevant creature methods. Here's a couple examples:
public void dig(int wx, int wy, int wz) {
    modifyFood(-10);
    world.dig(wx, wy, wz);
    doAction("dig");
}
public void update(){
    modifyFood(-1);
    ai.onUpdate();
}

Go ahead and use the food values you want. You should play around with it for a while to decide what feels right. Maybe you want starvation to be a serious problem and hunting bats is the only way to stay alive or maybe you want starvation to hardly ever happen. Maybe heroes start with an inventory full of supplies or maybe they start with an empty and growling belly — as the designer it's up to you.

While looking at the modifyFood method I noticed we don't prevent hp from going higher than maxHp. Even though we don't have a way to do that yet you should add a check for that.


If we eat more than the maxFood shouldn't our stomach stretch and increase our maxFood? Or maybe the user should explode from overeating? Here's my implementation:
public void modifyFood(int amount) {
    food += amount;

    if (food > maxFood) {
        maxFood = maxFood + food / 2;
        food = maxFood;
        notify("You can't believe your stomach can hold that much!");
        modifyHp(-1);
    } else if (food < 1 && isPlayer()) {
        modifyHp(-1000);
    }
}

It's a subtle effect but it gives the player a decision to make when full and carrying a lot of food and under the right circumstances overeating may become a useful strategy.

Now go ahead and add some food to your world. Bread, meat, apples, whatever.

download the code

Tuesday, September 20, 2011

roguelike tutorial 10: items, inventory, inventory screens

Before we add potions, spellbooks, treasures, armor, weapons, food, and other roguelike goodies we need to think about inventory and start small. We'll need a new class for items, we'll need to update the world to track, add, and remove items, the creature class will need to be updated to pickup, use, and drop items, and the PlayScreen needs to be updated to display items and accept keystrokes to let the player actually pickup or drop items. Let's start with a new Item class.

package rltut;

import java.awt.Color;

public class Item {

    private char glyph;
    public char glyph() { return glyph; }

    private Color color;
    public Color color() { return color; }

    private String name;
    public String name() { return name; }

    public Item(char glyph, Color color, String name){
        this.glyph = glyph;
        this.color = color;
        this.name = name;
    }
}

Pretty simple so far. I didn't give it an x, y, or z coordinate because items don't need to know where they are; it kind of makes since to have them when laying on the ground but what about when they're in a container or being carried around by a creature?
I guess you could also make a Location interface. Then Point, Creature, and Item could implement it. That way an item's location could be a point in the world, a creature that's carrying it (or it's point in the world), or a container it's in. That would also be useful because an item would have a reference to wherever it is and whoever is carrying it. I'll have to try that on my next roguelike.
I guess you could have the owner update their location and add another flag indicating if the item is on the floor, in a container, or being carried. Sounds cumbersome and unnecessary; best to do without for now.

I'm happy with having a CreatureFactory to handle the details of creating a new creature so let's do the same for items. We could create an ItemFactory but I'd like to try something different: add items to the CreatureFactory. I haven't tried this before so I'm not sure if it's better to keep the two separate or not. I guess I'm going to find out.

The first step is the most powerful refactoring of all, Rename. We'll rename the CreatureFactory to something more general. I'm going to just call it StuffFactory. That's an atrociously non-descriptive name but I can rename it when I think of something better — of course temporary things often stay that way so this will probably remain a StuffFactory for a while.

And now we can add our first item.

public Item newRock(int depth){
        Item rock = new Item(',', AsciiPanel.yellow, "rock");
        world.addAtEmptyLocation(rock, depth);
        return rock;
    }

Now that we have an item to put in the world, we need to extend the world class to handle that. Instead of a list of all items I'm going to try something different — I'm only going to allow one item per tile. Good idea or bad, let's go ahead with that for now.

Our world needs one item per tile.

private Item[][][] items;

This should get initialized in the constructor.

this.items = new Item[width][height][depth];

We need a way to determine what item is in a location.

public Item item(int x, int y, int z){
    return items[x][y][z];
}

And a way to add an item to a random spot similar to how we add creatures.

public void addAtEmptyLocation(Item item, int depth) {
    int x;
    int y;
    
    do {
        x = (int)(Math.random() * width);
        y = (int)(Math.random() * height);
    }
    while (!tile(x,y,depth).isGround() || item(x,y,depth) != null);
    
    items[x][y][depth] = item;
}

And lastly, we need to update our methods that are used for displaying the world to also display items.

public char glyph(int x, int y, int z){
    Creature creature = creature(x, y, z);
    if (creature != null)
        return creature.glyph();
    
    if (item(x,y,z) != null)
        return item(x,y,z).glyph();
    
    return tile(x, y, z).glyph();
}
public Color color(int x, int y, int z){
    Creature creature = creature(x, y, z);
    if (creature != null)
        return creature.color();
    
    if (item(x,y,z) != null)
        return item(x,y,z).color();
    
    return tile(x, y, z).color();
}

And the only change to the PlayScreen is to add our new rocks.

private void createItems(StuffFactory factory) {
    for (int z = 0; z < world.depth(); z++){
        for (int i = 0; i < world.width() * world.height() / 20; i++){
            factory.newRock(z);
        }
    }
}

Just call that during setup of a new game and play it.


Now that we've got a world with items in it, we need to be able to pick them up and do stuff with them.

A lot can happen with a creature's inventory so let's create another class for that. Instead of using a list I'm going to use an array so the items index doesn't change when we lose something before it. E.g. if we quaff the potion in our 'd' slot, whatever was in the 'e' slot should remain there and not slide into the 'd' slot. If you want that kind of behavior then you could use a List — it's your choice.

package rltut;

public class Inventory {

    private Item[] items;
    public Item[] getItems() { return items; }
    public Item get(int i) { return items[i]; }

    public Inventory(int max){
        items = new Item[max];
    }
}

We need a method to add an item to the first open slot in our inventory.

public void add(Item item){
    for (int i = 0; i < items.length; i++){
        if (items[i] == null){
             items[i] = item;
             break;
        }
    }
}

And a way to remove an item from our inventory.

public void remove(Item item){
    for (int i = 0; i < items.length; i++){
        if (items[i] == item){
             items[i] = null;
             return;
        }
    }
}

We also need to know if the inventory is full and we can't carry any more.

public boolean isFull(){
    int size = 0;
    for (int i = 0; i < items.length; i++){
        if (items[i] != null)
             size++;
    }
    return size == items.length;
}

Now that we've got something to represent an inventory, we can add one to our Creature class. This means that potentially any creature can have an inventory (Spoiler alert!)

private Inventory inventory;
    public Inventory inventory() { return inventory; }

We need to initialize it in the constructor. I prefer smaller inventories since that means the player can't carry half the world with them; having to chose which two swords to bring with you is more interesting than just carrying them all. I also tend to forget what I've got once it goes beyond a screenfull.

this.inventory = new Inventory(20);

And our creatures need to be able to pickup and drop stuff, moving it from the world to the creatures inventory or back.

public void pickup(){
        Item item = world.item(x, y, z);
    
        if (inventory.isFull() || item == null){
            doAction("grab at the ground");
        } else {
            doAction("pickup a %s", item.name());
            world.remove(x, y, z);
            inventory.add(item);
        }
    }

public void drop(Item item){
        doAction("drop a " + item.name());
        inventory.remove(item);
        world.addAtEmptySpace(item, x, y, z);
    }


Your IDE has probably warned you that the world class doesn't support removing and adding items so let's take care of that. Removing an item is easy:

public void remove(int x, int y, int z) {
    items[x][y][z] = null;
}

Adding an item to a specific place is more complicated since we only allow one item per tile. Because of that, we need to check adjacent tiles for an open space and repeat until we find one or run out of open spaces.

public void addAtEmptySpace(Item item, int x, int y, int z){
    if (item == null)
        return;
    
    List<Point> points = new ArrayList<Point>();
    List<Point> checked = new ArrayList<Point>();
    
    points.add(new Point(x, y, z));
    
    while (!points.isEmpty()){
        Point p = points.remove(0);
        checked.add(p);
        
        if (!tile(p.x, p.y, p.z).isGround())
            continue;
         
        if (items[p.x][p.y][p.z] == null){
            items[p.x][p.y][p.z] = item;
            Creature c = this.creature(p.x, p.y, p.z);
            if (c != null)
                c.notify("A %s lands between your feet.", item.name());
            return;
        } else {
            List<Point> neighbors = p.neighbors8();
            neighbors.removeAll(checked);
            points.addAll(neighbors);
        }
    }
}

A funky side effect of this is that if there are no open spaces then the item won't be added but will no longer be in the creature's inventory - it will vanish from the game. You can either let that happen or somehow let the caller know that it hasn't been added and shouldn't be removed from the inventory. Or you could notify everyone in viewing distance that it has vanished. I'll leave that up to you. If you leave it as it is then there's no indication that the item vanished and that may be interpreted as a bug. If you tell users it happens they probably won't consider it a bug - just part of the game. This could also make a funny scenario: imagine being trapped in a room where the floor is covered in treasure but you can't pick any up since your inventory is full and there's no room to drop your useless rusty sword.

Here's one possibility:
public void drop(Item item){
    if (world.addAtEmptySpace(item, x, y, z)){
         doAction("drop a " + item.name());
         inventory.remove(item);
    } else {
         notify("There's nowhere to drop the %s.", item.name());
    }
}


The final step is to update the PlayScreen's respondToUserInput method so the user can actually pickup things. Some roguelikes use the 'g' key to get things, some use the ',' key, and some use either one.

switch (key.getKeyChar()){
         case 'g':
         case ',': player.pickup(); break;
         case '<': player.moveBy( 0, 0, -1); break;
         case '>': player.moveBy( 0, 0, 1); break;
         }

Try it out.


We can pick up some rocks and the code is there to drop them, but we don't have a way to specify what to drop. Code that isn't being used gives me a bad feeling so let's wire up the GUI to that drop method soon. Ideally the user will press the 'd' key, the GUI will ask what to drop, the user types the letter of the thing to drop, the player drops it, and we go back to the game. Remember all that time we spent creating Screen interface and thinking about cases with different rules for user input and output? Time to make a new Screen.

Actually, if we think about what we want to do with inventory, we can do better. Here's a few scenarios off the top of my head:
press 'd', ask what to drop, the user selects something that can be dropped, drop it
press 'q', ask what to quaff, the user selects something that can be quaffed, quaff it
press 'r', ask what to read, the user selects something that can be read, read it
press 't', ask what to throw, the user selects something that can be thrown, throw it
press 'e', ask what to eat, the user selects something that can be eaten, eat it
Notice a pattern? There's a key that get's pressed, some verb (drop, quaff, read), some check against the items (droppable, quaffable, readable), and some action (drop, quaff, read). The common behavior can be put in one class called InventoryBasedScreen and the specific details can be in subclasses. That way we can have a DropScreen, QuaffScreen, ReadScreen and others that all subclass the InventoryBasedScreen and just provide a few simple details.

Let's start with a basic InventoryBasedScreen:

package rltut.screens;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import rltut.Creature;
import rltut.Item;
import asciiPanel.AsciiPanel;

public abstract class InventoryBasedScreen implements Screen {

    protected Creature player;
    private String letters;

    protected abstract String getVerb();
    protected abstract boolean isAcceptable(Item item);
    protected abstract Screen use(Item item);

    public InventoryBasedScreen(Creature player){
        this.player = player;
        this.letters = "abcdefghijklmnopqrstuvwxyz";
    }
}

We need the reference to the player because that's the one who's going to do the work of dropping, quaffing, eating, etc. It's protected so that the subclasses can use it. The letters are so we can assign a letter to each inventory slot (If you allow the inventory to be larger then you need to add more characters). Maybe this should be part of the inventory class but I think this is the only place where we will use it so I'll put it here for now. We've also got abstract methods so our subclasses can specify the verb, what items are acceptable for the action, and a method to actually perform the action. Using an item returns a Screen since it may lead to a different screen, e.g. if we're going to throw something then we can transition into some sort of targeting screen.

Since this is a screen it needs to actually display some output. We not only ask what they want to use but go ahead and show a list of acceptable items.

public void displayOutput(AsciiPanel terminal) {
        ArrayList<String> lines = getList();
    
        int y = 23 - lines.size();
        int x = 4;

        if (lines.size() > 0)
            terminal.clear(' ', x, y, 20, lines.size());
    
        for (String line : lines){
            terminal.write(line, x, y++);
        }
    
        terminal.clear(' ', 0, 23, 80, 1);
        terminal.write("What would you like to " + getVerb() + "?", 2, 23);
    
        terminal.repaint();
    }

That should be pretty clear: write the list in the lower left hand corner and ask the user what to do. If you allow a larger inventory then you'll have to show two columns or scroll the list or something.

The getList method will make a list of all the acceptable items and the letter for each corresponding inventory slot.

private ArrayList<String> getList() {
        ArrayList<String> lines = new ArrayList<String>();
        Item[] inventory = player.inventory().getItems();
    
        for (int i = 0; i < inventory.length; i++){
            Item item = inventory[i];
        
            if (item == null || !isAcceptable(item))
                continue;
        
            String line = letters.charAt(i) + " - " + item.glyph() + " " + item.name();
        
            lines.add(line);
        }
        return lines;
    }

Now that we've got some output we need to respond to user input. The user can press escape to go back to playing the game, select a valid character to use, or some invalid key that will do nothing and keep them on the current screen.

public Screen respondToUserInput(KeyEvent key) {
        char c = key.getKeyChar();

        Item[] items = player.inventory().getItems();
    
        if (letters.indexOf(c) > -1
             && items.length > letters.indexOf(c)
             && items[letters.indexOf(c)] != null
             && isAcceptable(items[letters.indexOf(c)]))
            return use(items[letters.indexOf(c)]);
        else if (key.getKeyCode() == KeyEvent.VK_ESCAPE)
            return null;
        else
            return this;
    }

I hope that little mess makes sense. Use it, exit, or ask again.

This doesn't do anything yet, in fact it's an abstract class so it can't do anything until we create a subclass and use that. The first subclass will be a DropScreen.

package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class DropScreen extends InventoryBasedScreen {

    public DropScreen(Creature player) {
        super(player);
    }
}

We just need to supply the methods that were abstract in the original. We're asking the use what they want to drop so the getVerb should return that.

protected String getVerb() {
        return "drop";
    }

Since anything can be dropped, all items are acceptable.

protected boolean isAcceptable(Item item) {
        return true;
    }

Once the user selects what to drop we tell the player to do the work and return null since we are done with the DropScreen.

protected Screen use(Item item) {
        player.drop(item);
        return null;
    }

Now we can update the PlayScreen to use our fancy new DropScreen. The DropScreen is a little different that the start, play, win, and lose screens since it needs to return to the PlayScreen once it's done. We could pass the current play screen into newly created DropScreen and have it return the PlayScreen when it's done, but I've tried that before and it became kind of messy. This time I'll try something different. We can have the PlayScreen know if we're working with another sub screen and delegate input and output to that screen screen. Once the subscreen is done, it get's set to null and the PlayScreen works as normal.

First the PlayScreen needs to know what the subscreen is. If it's null then everything should work as it did before. There's no need to initialize this since we check for nulls when we use it.

private Screen subscreen;

After we displayOutput the subscreen should get a chance to display. This way the current game world will be a background to whatever the subscreen wants to show.

if (subscreen != null)
    subscreen.displayOutput(terminal);

And any user input needs to be sent to the subscreen if it exists. The subscreen will also tell the PlayScreen what the new subscreen is. We also need to handle the users pressing the 'd' key to drop items from inventory. Lastly, if we should update the world only if we don't have a subscreen.

public Screen respondToUserInput(KeyEvent key) {
     if (subscreen != null) {
         subscreen = subscreen.respondToUserInput(key);
     } else {
         switch (key.getKeyCode()){
         case KeyEvent.VK_ESCAPE: return new LoseScreen();
         case KeyEvent.VK_ENTER: return new WinScreen();
         case KeyEvent.VK_LEFT:
         case KeyEvent.VK_H: player.moveBy(-1, 0, 0); break;
         case KeyEvent.VK_RIGHT:
         case KeyEvent.VK_L: player.moveBy( 1, 0, 0); break;
         case KeyEvent.VK_UP:
         case KeyEvent.VK_K: player.moveBy( 0,-1, 0); break;
         case KeyEvent.VK_DOWN:
         case KeyEvent.VK_J: player.moveBy( 0, 1, 0); break;
         case KeyEvent.VK_Y: player.moveBy(-1,-1, 0); break;
         case KeyEvent.VK_U: player.moveBy( 1,-1, 0); break;
         case KeyEvent.VK_B: player.moveBy(-1, 1, 0); break;
         case KeyEvent.VK_N: player.moveBy( 1, 1, 0); break;
         case KeyEvent.VK_D: subscreen = new DropScreen(player); break;
         }
        
         switch (key.getKeyChar()){
         case 'g':
         case ',': player.pickup(); break;
         case '<': player.moveBy( 0, 0, -1); break;
         case '>': player.moveBy( 0, 0, 1); break;
         }
     }
    
     if (subscreen == null)
         world.update();
    
     if (player.hp() < 1)
         return new LoseScreen();
    
     return this;
 }

That was a lot of little changes to a few different places but if the DropScreen is any indication, the InventoryBasedScreen should be a major win in terms of being able to implement new features with little effort. The PlayScreen is getting a little out of hand now that it creates a new world, displays the world, handles user commands and deals with subscreens. Maybe the part about setting up a new game should be moved somewhere else.


Let's make this a proper roguelike with a special object to retrieve and return to the surface with. This will also give the player a legitimate victory condition other than pressing enter.

Add our new item to the SuffFactory:

public Item newVictoryItem(int depth){
        Item item = new Item('*', AsciiPanel.brightWhite, "teddy bear");
        world.addAtEmptyLocation(item, depth);
        return item;
    }

And make sure it get's created when we start a new game:

private void createItems(StuffFactory factory) {
    for (int z = 0; z < world.depth(); z++){
        for (int i = 0; i < world.width() * world.height() / 20; i++){
            factory.newRock(z);
        }
    }
    factory.newVictoryItem(world.depth() - 1);
}

And update the WorldBuilder to include some exist stairs.

private WorldBuilder addExitStairs() {
        int x = -1;
        int y = -1;
    
        do {
            x = (int)(Math.random() * width);
            y = (int)(Math.random() * height);
        }
        while (tiles[x][y][0] != Tile.FLOOR);
    
        tiles[x][y][0] = Tile.STAIRS_UP;
        return this;
    }

And make that part of creating caves.

public WorldBuilder makeCaves() {
        return randomizeTiles()
             .smooth(8)
             .createRegions()
             .connectRegions()
             .addExitStairs();
    }


Our normal stair handling won't work with up stairs on the uppermost layer of the world so let's handle that in the PlayScreen.

switch (key.getKeyChar()){
    case 'g':
    case ',': player.pickup(); break;
    case '<':
        if (userIsTryingToExit())
         return userExits();
        else
         player.moveBy( 0, 0, -1); 
        break;
    case '>': player.moveBy( 0, 0, 1); break;
    }

private boolean userIsTryingToExit(){
    return player.z == 0 && world.tile(player.x, player.y, player.z) == Tile.STAIRS_UP;
}

private Screen userExits(){
    for (Item item : player.inventory().getItems()){
        if (item != null && item.name().equals("teddy bear"))
            return new WinScreen();
    }
    return new LoseScreen();
}

Now you can remove the cases for VK_ESCAPE and VK_ENTER. You can also remove the message about pressing escape or enter. It took half the tutorials but we finally have a victory condition.

download the code

Friday, September 16, 2011

roguelike tutorial 09: wandering monsters

We need some monsters to wander around our caves. How about some fast moving bats?

First let's add some default movement behavior to the CreatureAi.

public void onEnter(int x, int y, int z, Tile tile){
    if (tile.isGround()){
         creature.x = x;
         creature.y = y;
         creature.z = z;
    } else {
         creature.doAction("bump into a wall");
    }
}

Let's also give it a method of moving randomly. This common behavior can be called by any subclass.

public void wander(){
    int mx = (int)(Math.random() * 3) - 1;
    int my = (int)(Math.random() * 3) - 1;
    creature.moveBy(mx, my, 0);
}

Now the ai for our bats.

package rltut;

public class BatAi extends CreatureAi {

    public BatAi(Creature creature) {
        super(creature);
    }

    public void onUpdate(){
        wander();
        wander();
    }
}

We could set up a system for dealing with different monster speeds but this is simple enough: bats move twice for every one of your moves. Easy to implement, easy to understand.

Now we add bats to our CreatureFactory. I picked some low hp and attack so they could nibble on you a bit but shouldn't be too much of a problem.

public Creature newBat(int depth){
    Creature bat = new Creature(world, 'b', AsciiPanel.yellow, 15, 5, 0);
    world.addAtEmptyLocation(bat, depth);
    new BatAi(bat);
    return bat;
}

And in our PlayScreen we need to update createCreatures. How about 20 bats per level?

for (int i = 0; i < 20; i++){
    creatureFactory.newBat(z);
}

And now that our player is in some danger of being killed, let's add a check after the world get's updated.

if (player.hp() < 1)
    return new LoseScreen();

Try it out. You should see bats being flying about and being batty.


If you run this you'll soon notice that the bats drop dead from attacking each other or even attacking themselves. Suicide bats are funny but they quickly go extinct.

Let's add a check to the first line of the moveBy method in the Creature class. It should bail out early if we're not actually moving. This will take care of creatures killing themselves when all they want to do is stand in one place.

if (mx==0 && my==0 && mz==0)
    return;

Creatures should also be able to see what other creatures are in the world so the CreatureAi can know what's going on.

public Creature creature(int wx, int wy, int wz) {
    return world.creature(wx, wy, wz);
}

We can now improve the CreatureAi wander method to make sure creatures don't fight other's like them.

public void wander(){
    int mx = (int)(Math.random() * 3) - 1;
    int my = (int)(Math.random() * 3) - 1;
    
    Creature other = creature.creature(creature.x + mx, creature.y + my, creature.z);
    
    if (other != null && other.glyph() == creature.glyph())
        return;
    else
        creature.moveBy(mx, my, 0);
}

You could also make it keep trying until mx != 0 && my != 0, that way it would never stand in the same spot. You may want to make sure it doesn't try to move into a wall or make it able to go up or down stairs.

And there you go. You now have some deep and bat-filled caves to explore.



Using the glyph in messages is lame; Creatures should have a name.

private String name;
    public String name() { return name; }

Using constructor injection, update the creatureFactory to pass in the appropriate name. Finally, update any messages that used the creature's glyph to now use the creature's name.

Much better.

download the code

Tuesday, September 13, 2011

roguelike tutorial 08: vision, line of sight, and field of view

It doesn't feel like we're exploring much since we see the whole level from the beginning. Ideally we can only see our immediate surroundings and remember what we've already seen. I think we can do that in one session.

The first thing we need is a way to determine if something is in our line of sight. To do this we get all the points in between us and what we want to look at and see if any of them block our vision. For this, we can create a new Line class that uses Bresenham's line algorithm to find all the points along the line.

package rltut;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Line {
    private List<Point> points;
    public List<Point> getPoints() { return points; }

    public Line(int x0, int y0, int x1, int y1) {
        points = new ArrayList<Point>();
    
        int dx = Math.abs(x1-x0);
        int dy = Math.abs(y1-y0);
    
        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;
        int err = dx-dy;
    
        while (true){
            points.add(new Point(x0, y0, 0));
        
            if (x0==x1 && y0==y1)
                break;
        
            int e2 = err * 2;
            if (e2 > -dx) {
                err -= dy;
                x0 += sx;
            }
            if (e2 < dx){
                err += dx;
                y0 += sy;
            }
        }
    }
}

If you look this all the work is done in the constructor - that's a bad sign. So says Misko Hevery of Google fame, Martian Feathers of Working Effectively With Legacy Code, and anyone who's had to deal with this before. On the other hand, it doesn't do that much work; it just creates a list of points. The points are value objects and the line itself could be a value object. I'm certainly no fan of constructors that initialize their collaborators but this seems like a special case. Since it's just a personal project and no one's life or money are on the line, I'll try it and see if it becomes a problem.

To make things a tiny bit more convenient to loop through the points in a line, we can make the class implement Iterable<Point>. All we have to do is declare that the Line implements Iterable<Point> and add the following method:

public Iterator<Point> iterator() {
        return points.iterator();
    }

I have a feeling that we will use this Line class a lot as we add more features.


Since creatures are the ones who are doing the seeing, it makes since to give creatures a new stat to say how far they can see and a couple methods for looking at the world.

private int visionRadius;
    public int visionRadius() { return visionRadius; }

    public boolean canSee(int wx, int wy, int wz){
        return ai.canSee(wx, wy, wz);
    }

    public Tile tile(int wx, int wy, int wz) {
        return world.tile(wx, wy, wz);
    }

I set the vision radius to 9 in the creature's constructor but you should use whatever value you prefer or even have it passed in from the creatureFactory. Since we delegate the task to seeing to the CreatureAi, that's where the work is done and what we'll add to next.

public boolean canSee(int wx, int wy, int wz) {
        if (creature.z != wz)
            return false;
    
        if ((creature.x-wx)*(creature.x-wx) + (creature.y-wy)*(creature.y-wy) > creature.visionRadius()*creature.visionRadius())
            return false;
    
        for (Point p : new Line(creature.x, creature.y, wx, wy)){
            if (creature.tile(p.x, p.y, wz).isGround() || p.x == wx && p.y == wy)
                continue;
        
            return false;
        }
    
        return true;
    }

Now that our player can see his immediate surroundings, we should update the PlayScreen to only show the monsters and tiles that can be seen. Tiles outside the line of sight are shown in dark grey.

private void displayTiles(AsciiPanel terminal, int left, int top) {
     for (int x = 0; x < screenWidth; x++){
         for (int y = 0; y < screenHeight; y++){
             int wx = x + left;
             int wy = y + top;

             if (player.canSee(wx, wy, player.z)){
                 Creature creature = world.creature(wx, wy, player.z);
                 if (creature != null)
                     terminal.write(creature.glyph(), creature.x - left, creature.y - top, creature.color());
                 else
                     terminal.write(world.glyph(wx, wy, player.z), x, y, world.color(wx, wy, player.z));
             } else {
                 terminal.write(world.glyph(wx, wy, player.z), x, y, Color.darkGray);
             }
         }
     }
 }

Try it. You should see tiles outside of the player's range in a dark grey color.


We need a place to store what tiles the user has seen but who tracks what's been seen? It's part of the GUI so maybe the PlayScreen? But it's heavely based on the map so maybe the World class should flag tiles that have been seen — many games do this so maybe we should too? But the Creature is the one seeing so maybe it should. But we want the player to record this, the other monsters don't need to so maybe the CreatureAi should track what the creature has seen? That last one seems right to me; the PlayerAi should track what the player has seen and the PlayScreen should use that info to determines what get's displayed. This also means that we can store what tile the player saw at each location so what the player remembers may be different than what is in the real world. So if there's a cave in, or tunnels are dug, or some other change in the world then the player will remember what was last seen and be quite surprised when returning to that area. Neat posiblities.

We need a new tile to indicate a place that has not been seen. This is similar to the out of bounds tile we have since it's not really part of the world but it makes things much easier.
UNKNOWN(' ', AsciiPanel.white)

There are several different ways of determining what is in the player's field of view this but the simplest, and therefore what I prefer, is called raycasting. It's exactly what we're already doing: draw a line from the viewer to the tile in question to see if anything is blocking the vision. Raycasting is probably the slowest way, but it's quick enough and I think has the best overall look. Other methods perform differently when columns and doorways are involved.

Let's create a new class for our field of view. We can slightly extend the common definition of ours to not only determine what is in view but to remember what has already been seen too. What's visible now and what was seen earlier are technically two different things and possibly should be implemented by two different classes, but they're close enough and we can change it later if necessary.
package rltut;

public class FieldOfView {
    private World world;
    private int depth;

    private boolean[][] visible;
    public boolean isVisible(int x, int y, int z){
        return z == depth && x >= 0 && y >= 0 && x < visible.length && y < visible[0].length && visible[x][y];
    }

    private Tile[][][] tiles;
    public Tile tile(int x, int y, int z){
        return tiles[x][y][z];
    }

    public FieldOfView(World world){
        this.world = world;
        this.visible = new boolean[world.width()][world.height()];
        this.tiles = new Tile[world.width()][world.height()][world.depth()];
    
        for (int x = 0; x < world.width(); x++){
            for (int y = 0; y < world.height(); y++){
                for (int z = 0; z < world.depth(); z++){
                    tiles[x][y][z] = Tile.UNKNOWN;
                }
            }
        }
    }
}
That seems like a good interface. We can ask if a tile is visible and we can ask what tile was last seen somewhere. We just need to add the method to update what's visible and has been seen.

public void update(int wx, int wy, int wz, int r){
        depth = wz;
        visible = new boolean[world.width()][world.height()];
    
        for (int x = -r; x < r; x++){
            for (int y = -r; y < r; y++){
                if (x*x + y*y > r*r)
                    continue;
         
                if (wx + x < 0 || wx + x >= world.width() 
                 || wy + y < 0 || wy + y >= world.height())
                    continue;
         
                for (Point p : new Line(wx, wy, wx + x, wy + y)){
                    Tile tile = world.tile(p.x, p.y, wz);
                    visible[p.x][p.y] = true;
                    tiles[p.x][p.y][wz] = tile;
             
                    if (!tile.isGround())
                        break;
                }
            }
        }
    }


Only the player is going to use this advanced field of view, all other creatures can use the default line of sight code. Add a FieldOfView variable to the PlayerAi and override the canSee method.
public boolean canSee(int wx, int wy, int wz) {
    return fov.isVisible(wx, wy, wz);
}

Since the FieldOfView requires a world to be passed in the constructor and we don't want the ai's to know about the world, we can build the FieldOfView elseware and rely on constructor injection to give it to the PlayerAi. This means it will have to be passed into the CreatureFactory from the PlayScreen too.

The PlayScreen should construct a new field of view once the world has been made and pass it to the CreatureFactory. Since the PlayScreen is also responsible for displaying the world to the user, we should keep a reference to the field of view. Then we can update it before displaying the world and rely on it for tiles outside of the player's view. After that, we just need to modify the displayTiles method.
private void displayTiles(AsciiPanel terminal, int left, int top) {
        fov.update(player.x, player.y, player.z, player.visionRadius());
    
        for (int x = 0; x < screenWidth; x++){
         for (int y = 0; y < screenHeight; y++){
             int wx = x + left;
             int wy = y + top;

             if (player.canSee(wx, wy, player.z)){
                 Creature creature = world.creature(wx, wy, player.z);
                 if (creature != null)
                     terminal.write(creature.glyph(), creature.x - left, creature.y - top, creature.color());
                 else
                     terminal.write(world.glyph(wx, wy, player.z), x, y, world.color(wx, wy, player.z));
                 else
                     terminal.write(fov.tile(wx, wy, player.z).glyph(), x, y, Color.darkGray);
             }
        }
    }
}
And there you go, line of sight and field of view. These caves are starting to feel like real caves. If only they had some more monsters....



I just remembered something I wanted to do with the world once we added creatures. Replace the glyph and color methods with these:
public char glyph(int x, int y, int z){
    Creature creature = creature(x, y, z);
    return creature != null ? creature.glyph() : tile(x, y, z).glyph();
}
public Color color(int x, int y, int z){
    Creature creature = creature(x, y, z);
    return creature != null ? creature.color() : tile(x, y, z).color();
}
Since the world takes care of that for us, the PlayScreen becomes simpler.
private void displayTiles(AsciiPanel terminal, int left, int top) {
    fov.update(player.x, player.y, player.z, player.visionRadius());
    
    for (int x = 0; x < screenWidth; x++){
        for (int y = 0; y < screenHeight; y++){
            int wx = x + left;
            int wy = y + top;

            if (player.canSee(wx, wy, player.z))
                terminal.write(world.glyph(wx, wy, player.z), x, y, world.color(wx, wy, player.z));
            else
                terminal.write(fov.tile(wx, wy, player.z).glyph(), x, y, Color.darkGray);
        }
    }
}



Instead of having doAction notify everyone nearby, it should only notify them if they can see the one doing the action.

public void doAction(String message, Object ... params){
    int r = 9;
    for (int ox = -r; ox < r+1; ox++){
        for (int oy = -r; oy < r+1; oy++){
            if (ox*ox + oy*oy > r*r)
                continue;
         
            Creature other = world.creature(x+ox, y+oy, z);
         
            if (other == null)
                continue;
         
            if (other == this)
                other.notify("You " + message + ".", params);
            else if (other.canSee(x, y, z))
                other.notify(String.format("The %s %s.", name, makeSecondPerson(message)), params);
         }
    }
}

Now you have to actually see something happen in order to be notified about it.

download the code

Friday, September 9, 2011

roguelike tutorial 07: z levels and deeper caves

Our simple little roguelike is so flat and two dimensional. Let's change that. I'll try to write use code thats easy to read and understand, even if it's woefully inefficient. It's a good thing world gen only happens once.

Most roguelikes have a 2d level and just simulate the current one. When you leave the current level either a new one is created or an old one is loaded. If loading an old level then the program usually simulates the passage of time and adds some items or creatures and makes it look like things happened even though you weren't there. I find it easier just to create the entire world at once and simulate everything. This also makes world gen easier since you can make decisions based on the entire world, not just the thin slices that already exist. Our world is small enough for now that the extra time to create it and memory it takes up it shouldn't be a problem.

In order to build better caves we're going to have to work with coordinates a lot. We should make a class to represent a point in space.

package rltut;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Point {
    public int x;
    public int y;
    public int z;

    public Point(int x, int y, int z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
Two points that represent the same location should be treated as equal. These are known as value objects as opposed to reference objects. We can tell Java that by overriding the hashCode and equals methods. Here's what Eclipse generated:
@Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        result = prime * result + z;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
         return true;
        if (obj == null)
         return false;
        if (!(obj instanceof Point))
         return false;
        Point other = (Point) obj;
        if (x != other.x)
         return false;
        if (y != other.y)
         return false;
        if (z != other.z)
         return false;
        return true;
    }

We're also going to spend a lot of time working with points that are adjacent to something. This will be much easier if we can just ask a point for a list of it's eight neighbors.

public List<point> neighbors8(){
    List<point> points = new ArrayList<point>();
  
    for (int ox = -1; ox < 2; ox++){
        for (int oy = -1; oy < 2; oy++){
            if (ox == 0 && oy == 0)
                continue;
    
            points.add(new Point(x+ox, y+oy, z));
        }
    }

    Collections.shuffle(points);
    return points;
}

We shuffle the list before returning it so we don't introduce bias. Otherwise the upper left neighbor would always be checked first and the lower right would be last which may lead to some odd things. Now that we have a Point class, let's use it to make some better caves. First we need to add two new tile types, stairs up and stairs down.

STAIRS_DOWN('>', AsciiPanel.white),
    STAIRS_UP('<', AsciiPanel.white);

Then, in the WorldBuilder class, we create a region map. Each location has a number that identifies what region of contiguous open space it belongs to; i.e. if two locations have the same region number, then you can walk from one to the other without digging through walls.

private WorldBuilder createRegions(){
        regions = new int[width][height][depth];
    
        for (int z = 0; z < depth; z++){
            for (int x = 0; x < width; x++){
                for (int y = 0; y < height; y++){
                   if (tiles[x][y][z] != Tile.WALL && regions[x][y][z] == 0){
                       int size = fillRegion(nextRegion++, x, y, z);
              
                       if (size < 25)
                           removeRegion(nextRegion - 1, z);
                   }
                }
            }
        }
        return this;
    }

This will look at every space in the world. If it is not a wall and it does not have a region assigned then that empty space, and all empty spaces it's connected to, will be given a new region number. If the region is to small it gets removed. When this method is done, all open tiles will have a region assigned to it and we can use the regions array to see if two tiles are part of the same open space.

The removeRegion method does what it sounds like. It just zero's out the region number and fills in the cave so it's solid wall. I prefer caves where the smaller areas have been filled in but this step isn't necessary.

private void removeRegion(int region, int z){
    for (int x = 0; x < width; x++){
        for (int y = 0; y < height; y++){
            if (regions[x][y][z] == region){
                regions[x][y][z] = 0;
                tiles[x][y][z] = Tile.WALL;
            }
        }
    }
}

The fillRegion method does a flood-fill starting with an open tile. It, and any open tile it's connected to, gets assigned the same region number. This is repeated until there are no unassigned empty neighboring tiles.

private int fillRegion(int region, int x, int y, int z) {
        int size = 1;
        ArrayList<Point> open = new ArrayList<Point>();
        open.add(new Point(x,y,z));
        regions[x][y][z] = region;
    
        while (!open.isEmpty()){
            Point p = open.remove(0);

            for (Point neighbor : p.neighbors8()){
                if (regions[neighbor.x][neighbor.y][neighbor.z] > 0
                  || tiles[neighbor.x][neighbor.y][neighbor.z] == Tile.WALL)
                    continue;

                size++;
                regions[neighbor.x][neighbor.y][neighbor.z] = region;
                open.add(neighbor);
            }
        }
        return size;
    }

To connect all the regions with stairs we just start at the top and connect them one layer at a time.

public WorldBuilder connectRegions(){
        for (int z = 0; z < depth-1; z++){
            connectRegionsDown(z);
        }
        return this;
    }

To connect two adjacent layers we look at each region that sits above another region. If they haven't been connected then we connect them.
private void connectRegionsDown(int z){
    List<String> connected = new ArrayList<String>();
  
    for (int x = 0; x < width; x++){
        for (int y = 0; y < height; y++){
            String region = regions[x][y][z] + "," + regions[x][y][z+1];
            if (tiles[x][y][z] == Tile.FLOOR
              && tiles[x][y][z+1] == Tile.FLOOR
              && !connected.contains(region)){
                connected.add(region);
                connectRegionsDown(z, regions[x][y][z], regions[x][y][z+1]);
            }
        }
    }
}

The region variable is just a way to uniquely combine two numbers into one. This way we just need a list of the region strings instead of some list of pairs or something. If java had tuples then we could use that instead of this way.

To connect two regions, we get a list of all the locations where one is directly above the other. Then, based on how much area overlaps, we connect them with stairs going up and stairs going down.

private void connectRegionsDown(int z, int r1, int r2){
        List<Point> candidates = findRegionOverlaps(z, r1, r2);
    
        int stairs = 0;
        do{
            Point p = candidates.remove(0);
            tiles[p.x][p.y][z] = Tile.STAIRS_DOWN;
            tiles[p.x][p.y][z+1] = Tile.STAIRS_UP;
            stairs++;
        }
        while (candidates.size() / stairs > 250);
    }

Finding which locations of two regions overlap is pretty straight forward.

public List<Point> findRegionOverlaps(int z, int r1, int r2) {
        ArrayList<Point> candidates = new ArrayList<Point>();
    
        for (int x = 0; x < width; x++){
         for (int y = 0; y < height; y++){
             if (tiles[x][y][z] == Tile.FLOOR
                  && tiles[x][y][z+1] == Tile.FLOOR
                  && regions[x][y][z] == r1
                  && regions[x][y][z+1] == r2){
              candidates.add(new Point(x,y,z));
             }
         }
        }
    
        Collections.shuffle(candidates);
        return candidates;
    }

After all that, the method for making caves needs to be amended.

public WorldBuilder makeCaves() {
        return randomizeTiles()
             .smooth(8)
             .createRegions()
             .connectRegions();
    }

The world class obviously needs to be changed. Not only does a third layer need to be added to the tiles array, but most methods need to accept a new z parameter. I'm suer you can find all the changes that need to be made. Here's an example of an updated method:
public void addAtEmptyLocation(Creature creature, int z){
        int x;
        int y;
    
        do {
         x = (int)(Math.random() * width);
         y = (int)(Math.random() * height);
        }
        while (!tile(x,y,z).isGround() || creature(x,y,z) != null);
    
        creature.x = x;
        creature.y = y;
        creature.z = z;
        creatures.add(creature);
    }

The Creature class also needs a new z coordinate and many of it's methods need to be updated too. I'll leave that to you as well. Here's my new version of the moveBy method.
public void moveBy(int mx, int my, int mz){
        Tile tile = world.tile(x+mx, y+my, z+mz);
    
        if (mz == -1){
            if (tile == Tile.STAIRS_DOWN) {
                doAction("walk up the stairs to level %d", z+mz+1);
            } else {
                doAction("try to go up but are stopped by the cave ceiling");
                return;
            }
        } else if (mz == 1){
            if (tile == Tile.STAIRS_UP) {
                doAction("walk down the stairs to level %d", z+mz+1);
            } else {
                doAction("try to go down but are stopped by the cave floor");
                return;
            }
        }
    
        Creature other = world.creature(x+mx, y+my, z+mz);
    
        if (other == null)
            ai.onEnter(x+mx, y+my, z+mz, tile);
        else
            attack(other);
    }

The CreatureAi classes, PlayScreen class, and a few others need to be updated. Your IDE should tell you which ones are broken by the new thrid dimension. You should be able to fix all of these on your own. One thing is that the greater than and less than keys used to move up and down stairs don't work with the keyCode() method so that needs to be handled differently. Here's how I did it:

switch (key.getKeyChar()){
        case '<': player.moveBy( 0, 0, -1); break;
        case '>': player.moveBy( 0, 0, 1); break;
        }


This one was a lot of work. The algorithm for creating 3d caves that are connected takes a while to explain even though each step isn't too difficult on it's own. The real bummer was having to update all the places where we were using coordinates. I suppose that if we were using a Point class from the beginning then adding a z coordinate to the Point would have required far fewer changes. Or we could have stuck to a list of 2d arrays and just tracked the current one instead of simulating a 3d world all at once. Oh well. Maybe you wont make the same mistake with the next roguelike you make.

download the code

Tuesday, September 6, 2011

roguelike tutorial 06: hitpoints, combat, and messages

Currently our hero just gets rid of any foe it bumps into. I think it's time our creatures had a real attack.

public void attack(Creature other){
        int amount = Math.max(0, attackValue() - other.defenseValue());
    
        amount = (int)(Math.random() * amount) + 1;
    
        other.modifyHp(-amount);
    }

    public void modifyHp(int amount) {
        hp += amount;
    
        if (hp < 1)
         world.remove(this);
    }
There's many many different ways to handle how much damage is done but I'll stick with something simple: the damage amount is a random number from 1 to the attackers attack value minus the defenders defense value. It's easy to code, easy to understand, and using only two variables worked fine for Catlevania: Symphony Of The Night. The IDE tells us what else we need to add to our creature class to support this.
    private int maxHp;
    public int maxHp() { return maxHp; }

    private int hp;
    public int hp() { return hp; }

    private int attackValue;
    public int attackValue() { return attackValue; }

    private int defenseValue;
    public int defenseValue() { return defenseValue; }

We can rely on constructor injection to set the values.

public Creature(World world, char glyph, Color color, int maxHp, int attack, int defense){
    this.world = world;
    this.glyph = glyph;
    this.color = color;
    this.maxHp = maxHp;
    this.hp = maxHp;
    this.attackValue = attack;
    this.defenseValue = defense;
}

Then update our CreatureFactory.

public Creature newPlayer(){
    Creature player = new Creature(world, '@', AsciiPanel.brightWhite, 100, 20, 5);
    world.addAtEmptyLocation(player);
    new PlayerAi(player);
    return player;
}

public Creature newFungus(){
    Creature fungus = new Creature(world, 'f', AsciiPanel.green, 10, 0, 0);
    world.addAtEmptyLocation(fungus);
    new FungusAi(fungus, this);
    return fungus;
}

As always, play around with the numbers and find something you like.


Now that we have some stats, let's display them on the PlayScreen. Here's what I added to the end of displayOutput:

    String stats = String.format(" %3d/%3d hp", player.hp(), player.maxHp());
    terminal.write(stats, 1, 23);

That went well. Adding new functionality was kept to a few small and isolated changes. The earlier decision to move all the creature creation to a factory also reduced the number of places we had to update. Now go fight some fungi!



Since this post is so short let's add another feature: messages. There are so many ways to do this but we're going to think of a way that avoids globals. A global message queue seems like the easiest way to do messaging - and it probably is - but sometimes it's a good idea to stick to a guideline like "don't use global variables" to see if it works in all scenarios and find where the guideline works against you and should be abandoned. I've found that small roguelikes are a perfect place for this kind of experimentation.

Since we're not using a global message queue to hold messages, where should we put the messages? Messages are just extra text for the GUI so maybe they should be part of the PlayScreen? But they're created by things in the world and everything has access to the world so maybe there? That doesn't seem right though. Messages are meant for the player so maybe the PlayerAi should be the receiver of the messages? That kind of make sense because it already gets called by the creature class and creatures are probably going to be the source of most messages. We can pass messages to the ai and any non-player ai can just ignore the messages.

Add a notify method to the creature class. To make it easier for the callers to build messages you can take the string and any parameters the caller passes and format the string for them. A nice convenience.
public void notify(String message, Object ... params){
    ai.onNotify(String.format(message, params));
}

Which means you need the corresponding empty method for the CreatureAi class. We'll make the PlayerAi class add the messages to a list. Other CreatureAi's will just ignore it.

package rltut;

import java.util.List;

public class PlayerAi extends CreatureAi {

    private List<String> messages;

    public PlayerAi(Creature creature, List<String> messages) {
        super(creature);
        this.messages = messages;
    }

    public void onEnter(int x, int y, Tile tile){
        if (tile.isGround()){
         creature.x = x;
         creature.y = y;
        } else if (tile.isDiggable()) {
         creature.dig(x, y);
        }
    }

    public void onNotify(String message){
        messages.add(message);
    }
}

Instead of creating a getter for the message list we rely on constructor injection. That means the list comes from somewhere else that may already have a reference to it. We can create the list in the PlayScreen and pass it to the creature factory which passes it to the new PlayerAi. Since the PlayScreen already has the list, it can easily display any messages that show up and clear the list afterwards.

Here's the update to the CreatureFactory:

public Creature newPlayer(List<String> messages){
    Creature player = new Creature(world, '@', AsciiPanel.brightWhite, 100, 20, 5);
    world.addAtEmptyLocation(player);
    new PlayerAi(player, messages);
    return player;
}

Here's the update to the PlayScreen:

private List<String> messages;

public PlayScreen(){
    screenWidth = 80;
    screenHeight = 21;
    messages = new ArrayList<String>();
    createWorld();
    
    CreatureFactory creatureFactory = new CreatureFactory(world);
    createCreatures(creatureFactory);
}

private void createCreatures(CreatureFactory creatureFactory){
    player = creatureFactory.newPlayer(messages);
    
    for (int i = 0; i < 8; i++){
        creatureFactory.newFungus();
    }
}
Displaying messages can also be done many different ways. If you haven't guessed by now I like to start simple. The simplest way I can think of is to just list them all on the screen at once.
private void displayMessages(AsciiPanel terminal, List<String> messages) {
    int top = screenHeight - messages.size();
    for (int i = 0; i < messages.size(); i++){
        terminal.writeCenter(messages.get(i), top + i);
    }
    messages.clear();
}
Just call that from the displayOutput method. Before clearing the message list, the messages could be copied into a separate list (or list of lists) so the history is preserved.

All we need now is some actual messages. Go ahead and notify the creature wherever it does something interesting or has something happen to it. Here's a sample of what I added to the attack method:
notify("You attack the '%s' for %d damage.", other.glyph, amount);
other.notify("The '%s' attacks you for %d damage.", glyph, amount);

And now the player can receive notices of what's going on.



What about notifying nearby creatures when something happens? Here's one way to do that:
public void doAction(String message, Object ... params){
        int r = 9;
        for (int ox = -r; ox < r+1; ox++){
         for (int oy = -r; oy < r+1; oy++){
             if (ox*ox + oy*oy > r*r)
                 continue;
         
             Creature other = world.creature(x+ox, y+oy);
         
             if (other == null)
                 continue;
         
             if (other == this)
                 other.notify("You " + message + ".", params);
             else
                 other.notify(String.format("The '%s' %s.", glyph, makeSecondPerson(message)), params);
         }
    }
}
The method makeSecondPerson does a small bit of string manipulation to make it grammatically correct. It assumes the first word is the verb, but that's easy enough to do as long as you don't plan on supporting other languages. It's best to avoid implicit rules like this since the only way to know about it is to already know it or watch it fail when you don't follow the implicit rule. It feels dirty to have gramer rules in with the Creature code so remember to move it somewhere better.
private String makeSecondPerson(String text){
    String[] words = text.split(" ");
    words[0] = words[0] + "s";
    
    StringBuilder builder = new StringBuilder();
    for (String word : words){
        builder.append(" ");
        builder.append(word);
    }
    
    return builder.toString().trim();
}

Then you can call doAction in your creature code and anyone nearby will be notified. Here's some examples:

when the FungusAi spawns a child:
creature.doAction("spawn a child");
while attacking:
doAction("attack the '%s' for %d damage", other.glyph, amount);
or when dying:
public void modifyHp(int amount) {
    hp += amount;
    
    if (hp < 1) {
        doAction("die");
        world.remove(this);
    }
}

Now when playing you can get messages about all these details going on in the world. The more you add the more detailed and deep the world will be.

download the code

Friday, September 2, 2011

roguelike tutorial 05: stationary monsters

Time for some monsters! I want to keep things simple so our first monster will be a stationary fungus. Sounds too boring? Well then I'll throw in a little something to make things interesting.

If our world's going to have a bunch of creatures then we should start there. Add a list for them to the World class and initialize it in the World constructor.

private List<creature> creatures;

We need a way to get the creature at a specific location.

public Creature creature(int x, int y){
    for (Creature c : creatures){
        if (c.x == x && c.y == y)
            return c;
    }
    return null;
}

And we'll update the addAtEmptyLocation method to make sure we don't add a creature where one already is. This will also be the place to add new creatures to our list.

public void addAtEmptyLocation(Creature creature){
    int x;
    int y;
  
    do {
        x = (int)(Math.random() * width);
        y = (int)(Math.random() * height);
    } 
    while (!tile(x,y).isGround() || creature(x,y) != null);
  
    creature.x = x;
    creature.y = y;
    creatures.add(creature);
}

If we want multiple creatures then we have to change how we display the world. Currently we just display the player. Get rid of that line and let's change the displayTiles method to show any creatures in the section of the world we're interested in.

private void displayTiles(AsciiPanel terminal, int left, int top) {
    for (int x = 0; x < screenWidth; x++){
        for (int y = 0; y < screenHeight; y++){
            int wx = x + left;
            int wy = y + top;

            Creature creature = world.creature(wx, wy);
            if (creature != null)
                terminal.write(creature.glyph(), creature.x - left, creature.y - top, creature.color());
            else
                terminal.write(world.glyph(wx, wy), x, y, world.color(wx, wy));
        }
    }
}
This is actually a very inefficient way to do this. It would be far better to draw all the tiles and then, for each creature, draw it if it is in the viewable region of left to left+screenWidth and top to top+screenHeight. That way we loop through screenWidth * screenHeight tiles + the number of creatures. The way I wrote we loop through screenWidth * screenHeight * the number of creatures. That's much worse. I don't know why I didn't realize this when I first wrote this since I've always drawn the creatures after the tiles. Consider this an example of one way to not do it.

You should be able to run it. It will look the same as before but now we can throw in some more creatures and they'll show up too. Our fungus first needs it's own ai:

package rltut;

public class FungusAi extends CreatureAi {
 
    public FungusAi(Creature creature) {
        super(creature);
    }
}

And add it to our CreatureFactory so we can make them.

public Creature newFungus(){
    Creature fungus = new Creature(world, 'f', AsciiPanel.green);
    world.addAtEmptyLocation(fungus);
    new FungusAi(fungus);
    return fungus;
}

Now modify the PlayScreen to populate the world with fungus.

public PlayScreen(){
    screenWidth = 80;
    screenHeight = 21;
    createWorld();
  
    CreatureFactory creatureFactory = new CreatureFactory(world);
    createCreatures(creatureFactory);
}

private void createCreatures(CreatureFactory creatureFactory){
    player = creatureFactory.newPlayer();
  
    for (int i = 0; i < 8; i++){
        creatureFactory.newFungus();
    }
}

If you run it now you should see some green f's standing about.


We've got some monsters but to way to slay them. Let's work on that now; starting with the Creature class.

public void moveBy(int mx, int my){
    Creature other = world.creature(x+mx, y+my);
  
    if (other == null)
        ai.onEnter(x+mx, y+my, world.tile(x+mx, y+my));
    else
        attack(other);
}

public void attack(Creature other){
    world.remove(other);
}

For now an attack will be an instant kill - just tell the world to get rid of the creature. Later on we can add hitpoints and whatnot.

Here's how the world class can remove a killed creature:

public void remove(Creature other) {
    creatures.remove(other);
}

There you go. Stationary monsters waiting to be slain.



And now time to throw in a little something to make things interesting. What if the fungi were able to reproduce and spread? I really like games where each creature has something special instead of all being the same things with slightly different stats so this could be interesting. We first need to let each creature know when it's time to update itself and whatever else it wants to do for it's turn. First and add a method to the World class that lets each creature know it's time to take a turn.

public void update(){
    for (Creature creature : creatures){
        creature.update();
    }
}

Your IDE is probably letting you know that the Creature class doesn't have an update method. Let's add one that delegates to the ai.

public void update(){   
    ai.onUpdate();  
}

Now your IDE is probably letting you know that the CreatureAi class doesn't have an onUpdate method so go ahead and add an empty one. We want the fungi to spread to a nearby open space every once in a while as part of it's behavior during updating. Here's what I came up with:

package rltut;

public class FungusAi extends CreatureAi {
    private CreatureFactory factory;
    private int spreadcount;
 
    public FungusAi(Creature creature, CreatureFactory factory) {
        super(creature);
        this.factory = factory;
    }

    public void onUpdate(){
        if (spreadcount < 5 && Math.random() < 0.02)
            spread();
    }
 
    private void spread(){
        int x = creature.x + (int)(Math.random() * 11) - 5;
        int y = creature.y + (int)(Math.random() * 11) - 5;
  
        if (!creature.canEnter(x, y))
            return;
  
        Creature child = factory.newFungus();
        child.x = x;
        child.y = y;
        spreadcount++;
    }
}

You can play around with how far it spreads, how often it spreads, and how many times it can spread. Don't forget to modify the newFungus method to pass itself into the FungusAi constructor. The last thing you need to do is tell the world to let everyone take a turn by calling world.update() in the PlayScreen after handling user input. The user's input makes the player move and each creature can move after that. Since we're relying on Java's event handling it would be very cumbersome to make our code pause and wait for user input during the player's onUpdate like with many other roguelikes. Just one tiny detail left. If you run this then you will probably see some exceptions happen because we're adding new creatures to our list while looping through the same list — which you can't do. There are different ways of dealing with this but the easiest I know of is to just make a copy of the list and loop through that instead.

public void update(){
    List<creature> toUpdate = new ArrayList<creature>(creatures);
    for (Creature creature : toUpdate){
        creature.update();
    }
}

download the code
So now you've got some underground caves with a hero and some fungi. You can attack and they can spread. We're a bit closer to being an actual game. Next up, actual combat.