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