← MegaMOO Guide

Matching

Turning the 2nd blue sword into an object.

What a matcher is for

Your verb is handed strings. dobj, iobj and args are all text — whatever the player typed, split around the preposition, nothing more. Turning "the 2nd blue sword" into the actual object in the room is a separate step, and it is yours to make.

That step is a matcher. You give it the string and a list of candidates; it gives you back one object, or None.

target = pmatch(dobj, pobj, list(pobj.location.contents) + list(pobj.contents)) if not target or not target.existent: pobj.msg("Examine what?") return

The candidate list is the important half and it is never chosen for you. A verb that should only reach what you are carrying passes pobj.contents; one that reaches inside a chest passes chest.contents; the pair above is the ordinary "in the room or in my hands" search. Matching cannot find what you did not offer it, which is the whole of its access control.

The two you will call

There are two entry points, and choosing between them is a permissions decision rather than a stylistic one.

pmatchbmatch
Forplayer commandsstaff verbs
Used bythe room parents #12 and #13, and #9, #11, #20#3, the character base
me, hereyesyes
my swordyes, filtered from your candidatesyes, straight from pobj.contents
#4711noyes
$namenoyes
Signaturepmatch(inp, pobj, candidates)bmatch(inp, pobj, candidates, db)

The difference that matters is the third row. bmatch resolves #4711 by going to the database directly — it never consults your candidate list, so the object need not be in the room, in your hands, or anywhere near you. That is exactly right for @tel and exactly wrong for get.

Use pmatch in anything a player can type

A player command written with bmatch hands every player a working #N syntax against the whole database, whatever candidate list you carefully assembled. The candidate list stops being a boundary the moment # is legal.

The my handling differs too, in a way that is easy to miss. bmatch("my key", pobj, []) searches pobj.contents and ignores the candidates entirely. pmatch instead filters the candidates down to those whose location is the player. If you curated a list for a reason — only what is visible, only what is not worn — pmatch respects it and bmatch does not.

How the input is read

Both matchers eventually reach the same parser, which takes the string apart in a fixed order:

StepDoes whatExample
1. Articlea leading the, a, an or some is droppedthe big red ball → big red ball
2. Ordinalif the first token left is an ordinal, it is consumed2nd blue sword → index 1
3. Nounthe last tokensword
4. Adjectiveseverything between["big", "red"]

The candidates are then walked in the order you gave them, keeping every object whose noun matches and whose adjectives match, and the ordinal-th survivor is returned. Candidate order is your ordering — the list you passed — which is why 2 door means "the second door as the room lists them".

Matching the noun

The noun is tested against three things, in this order: each non-article word of obj.name, then every entry in obj.aliases, then obj.noun. A hit on any of them is a match.

Matching is by prefix, case-insensitively, down to a single character. Against "a silver sword", swo, sw, sil and plain s all match.

One letter matches broadly

There is no minimum length. s matches every object with a word starting in "s" — the sword, the sack, the stranger — and the first in your candidate list wins, silently. That is worth knowing before you write a verb whose argument is often one character.

name_match is a verb on $match_utils now, not a function in match_utils.py, and its docstring used to claim a single character needed an exact whole-word match. It never did — the guard it described could only ever reject the empty string — and the docstring was corrected when the verb was ported. The behaviour is unchanged; it is the description that was wrong.

Name words, not the whole name

The name is split on spaces and each word tried separately, so silver reaches "a silver sword" without a or sword being involved. Articles inside the name are skipped while doing it, which is why a does not match every object in the game.

Adjectives

Every adjective must appear in the order typed, and at a word boundary. big blue ball matches "a big blue ball"; blue big ball does not.

Only the leading boundary is checked, so berry does not reach "a blueberry tart" — but blue does. An adjective has to start a word; it does not have to finish one.

If scanning the name fails, there is a second pass: an adjectives property, a list of strings, is matched by prefix and also in order. That is how you make an object answer to a word that does not appear in its name.

@adprop drape.adjectives = ["blue", "velvet", "heavy"]

@adprop, not @set: no object ships with an adjectives property, and @set refuses a property that does not already exist locally or by inheritance.

With that set, blue drape finds it even though the name says "a drape". The two passes are independent: the name is tried whole first, and only a complete failure falls through to the property.

Ordinals

Three spellings are accepted, and all of them are converted to a 0-based index:

FormExamplesIndex
Wordsfirst … twentiethfirst → 0
Suffixed1st, 2nd, 3rd, 21st2nd → 1
Bare integer1, 2, 52 → 1
A bare number is always an ordinal

parse_ordinal claims any all-digit token, so an object whose name is a number cannot be matched by typing that number — get 7 means "get the seventh thing", not "get the 7". Give such an object a word alias.

An ordinal with nothing after it — a bare 2nd — matches nothing rather than defaulting to a noun, and an ordinal past the end of the matches returns None rather than the last one.

Keywords and references

Before any name matching happens, four special forms are tried. Which ones are honoured depends on the matcher, per the table above.

InputResolves topmatchbmatch
methe acting playeryesyes
herepobj.locationyesyes
my <x>your own possessionsyesyes
#4711that object numbernoyes
$namea property of #0noyes

$name reads the property name off object #0 and resolves it — so $trash_bin follows whatever #0.trash_bin currently holds. It is the way to name a fixed object without writing its number into a verb, and a shipped world defines a couple of dozen of them: $item, $chair, $wearable, $trainer and so on.

What comes back

One object, or None. Matchers do not raise, and there is no ambiguity error: two equally good candidates means the earlier one in your list wins, silently. If you need to know that the input was ambiguous, ask match_all and count.

None is not the only failure. An object mid-destruction is still in the room and still matches its own name, so every unhidden player verb must check existent after a match:

target = pmatch(dobj, pobj, candidates) # An object that has stopped existing is not a match, however well its # name fits: pmatch searches what is in the room, and something mid- # destruction is still there to be found. if not target or not target.existent: pobj.msg("Examine what?") return

Staff verbs on #3 deliberately skip the check — reaching a half-destroyed object is sometimes the point of a staff command.

The rest of the toolkit

pmatch and bmatch are assembled out of smaller pieces, all of which are available to verb code in their own right.

FunctionDoes
match(inp, candidates, ordinal=0)the name/adjective/ordinal engine, with no keywords at all
match_all(inp, candidates)every match as a list; the ordinal is ignored
omatch(inp, pobj, db)only the keywords: me, here, #N, $name
name_match(obj, token)would this one token match this one object
adj_match(adjectives, obj)would these adjectives match this object
parse_ordinal(word)a 0-based index, or None if it is not an ordinal
strip_articles(text)text without a leading article
smatch(target, query, minlen=0)plain prefix comparison of two strings; no objects involved
prep_match(word)a word to its canonical preposition: onto → on
split_on_prep(text)text → (before, prep, after); before keeps the command word

match_all is what you want for "get all the coins" and for detecting ambiguity. prep_match is what a verb uses to check that the preposition it got is the one it wanted — prep_match(prep) != 'from' accepts from however the player abbreviated it.

Traps

match is not the regex match

In a verb namespace, match is the matcher function above. If your verb has a custom parser and you want the regular expression's match object, the name for it is regex_match. Both names are set, and the builtins are injected afterwards, so match ends up as the matcher whatever the parser did.

Matching is not permission

Everything a matcher can reach is decided by the list you pass, and nothing else — no visibility test, no reachability test, no ownership test. If a verb should not touch worn items, filter them out of the candidates; do not expect the matcher to.

The order you pass is the order it searches

pobj.contents + pobj.location.contents and pobj.location.contents + pobj.contents answer differently when both hold a sword. Neither is wrong; pick the one that suits the verb, and be aware that ordinals count in that order.

Aliases and noun are native attributes

They are read straight off the object, not through add_property. Adding a property with either name shadows nothing and does not change matching.