Up to Main Index                               Up to Journal for May, 2021

                      JOURNAL FOR SUNDAY 23RD MAY, 2021
______________________________________________________________________________

SUBJECT: I’ve been thinking…
   DATE: Sun 23 May 18:45:51 BST 2021

It’s been a while since my last visit here. In the intervening time I’ve been
doing a lot of thinking and started to do a little experimenting…

I’ve recently been talking to some of my more advanced users and it’s got me
thinking a lot about WolfMUD’s implementation, design decisions I’ve made and
a whole lot of other related stuff. I’ve also been talking to users that are
frustrated with WolfMUD’s slow progress :(

When writing something like a MUD, or a simpler text adventure game, there are
mainly two schools of thought. The first is to try and model the game world
using objects and building things in the game world from parts. This is the
approach the Java and Go versions of WolfMUD have adopted. The second school
of thought represents the game world by have a general object and setting or
clearing lots of flags and bits to signify the capabilities of an object.

Of the two approaches I prefer the modelling approach. Although it is more
complex and you end up with a lot more code. Saying that, early pre-Java
versions of WolfMUD used flags and bits due to the memory limitations of early
computers.

In the flags and bits school you might have an object represented as an
unsigned integer. If bit 0 is set you can take and carry the item, if it is
cleared it’s fixed in place. If bit 1 is set the item might be edible. If bit
2 is set the item might be drinkable. And so on, assigning bits to properties
as development progresses. The number of bits in an integer limits the number
of properties available, unless you use multiple integers and use sets of
bits. In Go the largest integer is a uint64 allowing for 64 properties in a
single integer.

Using the bitwise operators and (&), or (|) for setting, clearing and testing
bits can lead to some very dense, lightweight code.

To illustrate, lets define some property bit masks. Here I’m using a byte to
keep the values small enough to easily show their values:


  const (
    Unset byte = 1 << iota // 0b00000000
    Gettable               // 0b00000001
    Edible                 // 0b00000010
    Drinkable              // 0b00000100
  )


We can now define an apple which will be gettable and edible:


  apple := Gettable|Edible   // 0b00000011 = 0b00000001 | 0b00000010


We can check if the apple is edible in two different ways:


  if apple&edible != Unset {  // 0b00000011 != 0b00000000 ?
    // apple edible
  }

  if apple&Edible == Edible { // 0b00000011 & 0b00000010 == 0b00000010 ?
    // apple edible
  }


We can check multiple properties, for example is the apple gettable and edible?:


  if apple&(Gettable|Edible) == Gettable|Edible {
    // apple edible and gettable
  }


We can check if the apple is gettable or edible:


  if apple&(Gettable|Edible) != Unset {
    // apple edible and gettable
  }


However, there is now the issue of non-binary properties. For example how do
you represent the fact that an apple is edible — and gives your character +10
health back when eaten? Are you going to store a health value for everything
and have it set to zero for most items? You could store the value in a
map[byte]int. Then if the health bit is set you can check for a value for it.
Something like:


  type item struct{
    prop byte
    attr map[byte]int
  }

  apple := item{Gettable|Edible, map[byte]int{Edible:10}}

  health := apple.attr[Edible] // Get the value (10) for the Edible property


That’s okay for integer properties. What about string or float properties? I
guess you could use multiple maps. Maybe even a map[uint64][]byte and use byte
packing to store integers, floats, strings and other types in bytes? You could
use interface{} and enter a totally different kind of hell…

If you add too many maps the savings from using bit flags become a moot point.

What about something that has multiple values? Like a door? Currently a door
in WolfMUD has an automatic open/close frequency, whether it is currently open
or closed, the direction it is blocking and where it leads to.

At the end of all this I’ve been left wondering if there is a compromise
between the two styles? A lightweight approach that takes maximum advantage of
Go’s features — I need to zero values more, especially when doing lookups for
attributes and properties. To that end I’ve been working on a few experiments,
simple single player style text adventure prototypes — some of which even look
quite promising. What I am look for specifically is a lightweight, performant,
memory efficient solution for storing and manipulating the game world and most
importantly the code must be easy to work with.

So far I have everything as a simple Thing which is defined as:


  type Thing struct {
    Name        string
    Description string
    Is          byte
    As          map[byte]string
    In          []*Thing
  }


Everything in the world has a name and description, for now those are explicit
strings but could be pushed into ‘As’ values. I can check what a Thing is,
check string values and manipulate inventories:


  if what.Is&Narrative == Narrative {
    // what is a narrative
  }
  if what.As[Alias] == "APPLE" {
    // what has an alias of APPLE
  }
  where.In = append(what.In, what) // Put what into where


I think using ‘Is’, ‘As’ and ‘In’ work and read quite well:


  what.Is&Narrative                 // what IS a narrative?
  what.As[Alias] == "APPLE"         // what AS an alias is "APPLE"?
  where.In = append(where.In, what) // put what IN where's inventory


I’m thinking of adding ‘Of’ or ‘By’ as a field to Thing which would be a
map[byte]Int, then you can get the value of a property as ‘what.Of[Health]’ or
‘what.By[Health]’. I’ve been deliberately trying to pick short but meaningful
names that are readable as they are used extensively throughout the code. I’m
also wondering if ‘As’ would be more suitable as a map[byte][]string.

This is all experimental and so will likely change. So far I have WolfMUD’s
dragon breath tavern, items, containers and narratives. I can walk around and
manipulate items. Commands implemented are: movement (N, NE, E, SE, S, SW, W,
NW, UP, DOWN), L/LOOK, EXAM/EXAMINE, INV, DROP, GET, TAKE, PUT and QUIT.


    Welcome to the WolfMini experimental environment!

  [Fireplace]
  You are in the corner of the common room in the dragon's breath tavern. A
  fire burns merrily in an ornate fireplace, giving comfort to weary
  travellers. The fire causes shadows to flicker and dance around the room,
  changing darkness to light and back again. To the south the common room
  continues and east the common room leads to the tavern entrance.

  You see a chest here.

  Exits: East Southeast South
  >inv
  You are not carrying anything.
  >exam chest
  You examine a chest.
  This is a large iron bound wooden chest. It contains: a green ball, an
  apple.
  >take apple chest
  You take an apple from a chest.
  >inv
  You are carrying:
    an apple
  >drop apple
  You drop an apple.
  >l
  [Fireplace]
  You are in the corner of the common room in the dragon's breath tavern. A
  fire burns merrily in an ornate fireplace, giving comfort to weary
  travellers. The fire causes shadows to flicker and dance around the room,
  changing darkness to light and back again. To the south the common room
  continues and east the common room leads to the tavern entrance.

  You see a chest here.
  You see an apple here.

  Exits: East Southeast South
  >s
  [Common room]
  You are in a small, cosy common room in the dragon's breath tavern. Looking
  around you see a few chairs and tables for patrons. In one corner there is a
  very old grandfather clock. To the east you see a bar and to the north there
  is the glow of a fire.

  You see the tavern cat here.

  Exits: Northeast East North
  >e
  [Tavern bar]
  You are at the tavern's very sturdy bar. Behind the bar are shelves stacked
  with many bottles in a dizzying array of sizes, shapes and colours. There
  are also regular casks of beer, ale, mead, cider and wine behind the bar.

  Exits: West Northwest North
  >n
  [Tavern entrance]
  You are in the entryway to the dragon's breath tavern. To the west you see
  an inviting fireplace and south an even more inviting bar. Eastward a door
  leads out into the street.

  You see a red ball here.

  Exits: East South Southwest West
  >w
  [Fireplace]
  You are in the corner of the common room in the dragon's breath tavern. A
  fire burns merrily in an ornate fireplace, giving comfort to weary
  travellers. The fire causes shadows to flicker and dance around the room,
  changing darkness to light and back again. To the south the common room
  continues and east the common room leads to the tavern entrance.

  You see a chest here.
  You see an apple here.

  Exits: South East Southeast
  >quit
  Bye bye!


There is no text formatting, lines just wrap — I’ve added wrapping here to
make the session easier to read. The parser is a simple word parser. No colour
output, no multiplayer and the world is currently hard coded. However,
everything was coded up in only a few hours — and I had a blast doing it :)

I’ve not decided yet if it’s going to be worth making this code available as I
work on it. At some point I’ll have to share it to get feedback. If you are
interested in getting the code to play with yourself drop me an email, if
there is enough interest I’ll publish: diddymus@wolfmud.org

What I do know is that WolfMUD is becoming a bit of an unwieldy, heavyweight
burden. I’m not planning another rewrite, although some of the current code is
over a decade old — and I’ve learnt a lot about Go since those first lines
were written. Depending on how my experiments go, what I have planned is more
of a surgical gutting ;)

--
Diddymus


  Up to Main Index                               Up to Journal for May, 2021