Up to Main Index                          Up to Journal for February, 2016

                   JOURNAL FOR SATURDAY 20TH FEBRUARY, 2016
______________________________________________________________________________

SUBJECT: Return of the sneezes
   DATE:

Another day, another update to the public dev branch.

One of the commands I usually add to WolfMUD during development is SNEEZE.
This is a very simple command that exercises locking, messaging and location
traversal. It's also a good example for demonstrating a few techniques and
quite fun to boot.

In order to implement SNEEZE we first need to create two helpers on the Exits
attribute type. We will also be taking about locations a lot. To help us I've
reproduced the map from the Exits documentation for reference:


           ____________________________________________________________
          |1            |3              |5               |6            |
          |  Fireplace      Entrance      Between            Bakery    |
          |                               Tavern/Bakery                |
          |__                         __|__           __|______________|
          |2             4              |7              |8             |
          |  Common Room    Bar         | Street outside     Pawn Shop |
          |                             | Pawn Shop                    |
          |______________|______________|__           __|______________|
                         |10            |9              |
                         |   Outside      Fountain      |
                         |   Armourer     Square        |
                         |______________|_______________|



The first helper method we need is Exits.Surrounding(). This method returns a
slice of Inventories for each location accessible from the current location.
This includes locations diagonally, up and down - so it's even 3D!

So if we are at the Fireplace, location 1, the available exits are:


       East: Entrance (3)
  Southeast: Bar (4)
      South: Common room (2)


Calling Surround from location 1 would return a slice with three inventories,
one each for locations 3, 4 and 2.

If we were at location 5, Between Tavern/Bakery, calling Surround would return
a slice of three inventories again:


     East: Bakery (6)
    South: Street outside Pawn Shop (7)
     West: Entrance (3)


It does not return inventories for locations 8, Pawn Shop, or 4, Bar, because
there are no exits southeast or southwest from location 5.

The other helper method we need is Exits.Within(distance) which builds on
Exits.Surround(). It returns all inventories for locations within a given
distance, number of moves, from the current location. These inventories are
returned as a slice of Inventory slices:


  [][]has.Inventory


The first slice represents the distance from the current location, the
subslices contains all of the inventories reachable at a particular distance.
The first slice is always the current location and has just the one Inventory.

If we are at location 1, Fireplace, and called Within(2) it would return three
slices - one each for locations at distance 0, 1 and 2 from the current
location:


  [][]has.Inventory {
    []has.Inventory{ 1 }        // Locations reachable within 0 moves
    []has.Inventory{ 3, 4, 2 }  // Locations reachable within 1 move
    []has.Inventory{ 5 }        // Locations reachable within 2 moves
  }


In the above example I have used integers to represent the location references
from the map. In real code they would be interface references to has.Inventory
interface types.

Note that the locations returned are those within N number of moves. Looking
at the map you might be expecting locations 7, 9 and 10 to also be returned
for a distance of 2. However if you are only following available exits then
location 7 is actually 3 moves away, location 9 is 4 moves away and location
10 is 5 moves away.

Another example. Assume we are at location 5, Between Tavern/Bakery. Calling
Within(2) would return:


  [][]has.Inventory {
    []has.Inventory{ 5 }              // Locations reachable within 0 moves
    []has.Inventory{ 6, 7, 3 }        // Locations reachable within 1 move
    []has.Inventory{ 8, 9, 4, 2, 1 }  // Locations reachable within 2 moves
  }


With these helpers we can implement the SNEEZE command. If a player types
SNEEZE then other players at the same location will see the player sneeze.
Players in adjoining locations will hear a loud sneeze. Players a little
further out will hear a sneeze - but not a loud sneeze as they are further
away. Those still further away will not hear anything.

With the implementation of Exits.Within(distance) the actual command becomes
almost trivial to write:


  func Sneeze(s *state) {

    // Get all location inventories within 2 moves of current location
    locations := attr.FindExits(s.where.Parent()).Within(2)

    // Try locking all of the locations we found
    lockAdded := false
    for _, d := range locations {
      for _, i := range d {
        if !s.CanLock(i) {
          s.AddLock(i)
          lockAdded = true
        }
      }
    }

    // If we added any locks return to the parser so we can relock
    if lockAdded {
      return
    }

    // Notify actor
    s.msg.actor.WriteString("You sneeze. Aaahhhccchhhooo!")

    // Notify observers in same location
    who := attr.FindName(s.actor).Name("Someone")
    s.msg.observer.WriteJoin("You see ", who, " sneeze.")

    // Notify observers in near by locations
    for _, e := range locations[1] {
      s.msg.observers[e].WriteString("You hear a loud sneeze.")
    }

    // Notify observers in further out locations
    for _, e := range locations[2] {
      s.msg.observers[e].WriteString("You hear a sneeze.")
    }

    s.ok = true
  }


We start by calling Within(2) to get all of the location inventories within 2
moves of our current location. We then try and take the lock on each of those
locations, see footnote [1]. If we added any locks because we didn't already
hold them we return and the parser will call our Sneeze function again - this
time with all of the locks held. We then send a message to the actor and
observers at the current location. To observers within 1 move - the range
locations[1] - we send the message "You hear a loud sneeze". To observers
within 2 moves - the range locations[2] - we send the message "You hear a
sneeze".

Note that in the above paragraph [1] and [2] are slice references in the code
and I've specifically pointed out the footnote. Normally footnotes are written
[1] and [2]. It would be nice if I could use the Unicode superscripts ¹ ² etc.
for footnotes, but that causes issues for some people with certain browsers.

*sigh*

Maybe I need to look again at using a monospaced web font with good Unicode
support so I know which characters/glyphs are available? Personally I'd like a
font with a slashed zero instead of an empty or dotted one. A lot of fonts
only have superscript 1, 2 and 3 sometimes 4 from ISO-8859-1 (Latin 1). In
Unicode there is a specific superscripts and subscripts block, but a lot of
fonts don't have these :(

I work almost exclusively in XTerm windows, usually with Vim or Mutt open. The
font I use is the misc-fixed 6x13 font modified by a package called
xfonts-traditional[2] which modifies the look of some of the characters. It
has quite good Unicode coverage but unfortunately it has no slashed zero :(

--
Diddymus

  [1] This is the BRL or Big Room Lock we have to hold when doing something at
      a location.

  [2] xfonts-traditional Debian package:
      https://packages.debian.org/stretch/xfonts-traditional


  Up to Main Index                          Up to Journal for February, 2016