Up to Main Index Up to Journal for June, 2021 JOURNAL FOR SUNDAY 13TH JUNE, 2021 ______________________________________________________________________________ SUBJECT: New command matcher for WolfMini DATE: Sun 13 Jun 17:53:38 BST 2021 Another jolly week of working late into the night hacking away on WolfMini. I finished the world loader which can now load the original zone files from WolfMUD proper and link the zones together. I have basic vetoes working after implementing the Thing.Any field I discussed last time. Although, after using vetoes for a while this became part of Thing.As :/ However, the Thing.Any field is now used for aliases and alias qualifiers. I also tweaked, a major refactoring, the inventories to be map[string]*Thing where the key is the unique identifier of the item. My current thing now looks like: // Thing is used to represent any and all items in the game world. type Thing struct { Is isKey // Bit flags for capabilities/state As map[asKey]string // Single value for a key Any map[anyKey][]string // One or more values for a key In map[string]*Thing // Item's in a Thing (inventory) } One of the issues I had/have with WolfMUD is the item matcher that tries to work out which items a player’s command is referring to. In WolfMUD, adopting the matcher and switching out the simple parser was a pain, it made a mess of the code for all commands and slowed down the command handling a lot. A lot of this week has been spent on a new command matcher for WolfMini. I had some specific goals in mind: it had to be simple, fast, and integrate nicely with other code. The new matcher has all of the features of the current WolfMUD matcher, except it can’t handle ranges of items. It can handle aliases, qualifiers, bound qualifiers, ‘All’ and a specific instance such as 2 (second) or 3 (third) item. For experimenting with the matcher I again implemented the WHICH command that tells you which item would have been picked by the matcher. Here is a quick demo, I’ve outdented the command lines to make it easier to read. The duration on the prompt is the time to take input, process it, display results and return to the prompt for the next command: 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 large, round blue ball here. You see a small green ball here. You see a small red ball here. You see a small blue ball here. You see exits: East Southeast South 33.839µs>which ball You see a large, round blue ball. 41.576µs>which all ball You see a large, round blue ball. You see a small green ball. You see a small red ball. You see a small blue ball. 43.255µs>which blue ball You see a large, round blue ball. 34.285µs>which 3 ball You see a small red ball. 36.736µs>get ball You get a large, round blue ball. 30.749µs>which all blue ball You see a small blue ball. You have a large, round blue ball. 49.266µs> which all ball You see a small green ball. You see a small red ball. You see a small blue ball. You have a large, round blue ball. 46.91µs>which red ball green ball You see a small red ball. You see a small green ball. 47.107µs>which 2 ball You see a small red ball. 35.663µs>which 2 blue ball You have a large, round blue ball. 48.131µs>which red ball green frog green ball You see a small red ball. You see no 'GREEN FROG'. You see a small green ball. 40.254µs> My next job is to roll the new matcher into all of the commands I have implemented so far. The matcher is about 88 lines of code. I won’t show that much code here, but I will show the code for the WHICH command: func (s *state) Which() { if len(s.word) == 0 { s.Msg("Which what?") return } action := []string{"see", "have"} actIdx := 0 for _, uid := range MatchUID(s.word, World[s.actor.As[Where]], s.actor) { what := World[s.actor.As[Where]].In[uid] if what == nil { what = s.actor.In[uid] actIdx = 1 } switch { case what == nil: s.Msg("You see no '", uid, "'.\n") default: s.Msg("You ", action[actIdx], " ", what.As[Name], ".\n") } } } The magic is in the MatchUID function which takes a list of words and the items whose inventories should be looked into for items. Here we look at items in the location where the actor is first: World[s.actor.As[Where]] and then the actor’s inventory: s.actor for matching items. The match results are returned as a string slice containing either unique identifiers or unmatched words. For example the last command “which red ball green frog green ball” returns: []string{"#UID-106", "GREEN FROG", "#UID-10A"} We can then get the item with a simple lookup that returns nil if no item matches: World[s.actor.As[Where]].In[uid] and s.actor.In[uid], if we try to get the “GREEN FROG” we will get nil indicating not found. However, we can also test if the string has a prefix of “#UID-” for matches, otherwise it’s a non-matching result. I need to tidy up the new matcher code a bit, then roll it into all of the existing commands and see how well everything works. After that? I’m still trying to hit the hard nails on the head early to see how far this little experiment can go and if it’s worth pursuing. I’d like to bring life to the world next with mobs doing things and items resetting. For that I’ll need to tackle the prickly source of many previous problems — concurrency and locking. Will the BRL (Big Room Lock) survive into WolfMini? Not sure yet… -- Diddymus Up to Main Index Up to Journal for June, 2021