Finding things in the world

Scripts constantly ask "who is here?", "what am I carrying?", "is there a player nearby?". The answer always comes as an iterable — an on-demand sequence of things. You get one from a producer built-in and then walk it with a consumer. A collection of creatures or objects is always an iterable, so this pattern is everywhere.

Producers: getting a collection

These hand you an iterable. The common ones:

Producer Yields
creatures / people living creatures in a room (or a creature's room)
objects objects in a room, carried, or contained
contents the contents of a container or room
inventory a creature's carried objects
equipment a creature's worn gear
implants a creature's implants
tattoos a creature's tattoos
fighting a creature's living combatants
alias a creature's or object's name words
words the whitespace-split words of a string
list an iterable over its arguments

A bare string is not iterable — to walk its words you must convert it with words first. To write a sequence literally, use the ( … ) list form:

let dirs (north south east west)

(The list built-in is the command-form equivalent, handy when the elements are computed rather than written out; see Numbers, text, and logic.)

Consumers: walking a collection

Do something per element — each

each runs a block once per element, binding each to the block's parameter:

after command (roll-call) {
  each [/> $self room people] { <person>
    do "say I see [name $person]."
  }
}

How many, the first, the nth — count, first, nth

after command (headcount) {
  let here [/> $self room creatures]
  do "say There are [count $here] of us; the first is [name [first $here]]."
}

count gives the size, first the first element (or null if empty), and nth an element by 0-based index (null if out of range):

let second [nth [words $arg] 1]    # the second word the player typed

Is it empty? — empty

empty tells you whether an iterable yields nothing — cleaner than comparing a count to zero:

after command (look-around) {
  if [empty [/> $self room objects]] {
    do "say The floor is bare."
  }
}

Filtering — select

select keeps only the elements whose predicate returns true, producing a new (lazy) iterable. When the predicate is a single built-in, pass it by name with & (see Structuring scripts):

after command (census) {
  let players [select [/> $self room creatures] &isplayer]
  do "say There are [count $players] players here."
}

When the test is more than one built-in, write a block — a parameter declared inside its own braces, bound to each element, yielding a bool:

after command (veterans) {
  let vets [select [/> $self room creatures] { <c> [ge [level $c] 5] }]
  do "say There are [count $vets] veterans here."
}

Any? all? — some and every

some is true if the predicate holds for at least one element; every is true if it holds for all (and is vacuously true on an empty iterable):

after command (check) {
  require [some [/> $self room creatures] &isplayer]
  do "say A player is present."
}

after command (allmobs) {
  if [every [/> $self room creatures] &ismob] {
    do "say No players here — just us monsters."
  }
}

Membership without a block — ismember, hasabbrev

When you just need "is this value in the collection?", reach for ismember (exact equality) or hasabbrev (string-prefix match) instead of writing a some predicate:

after command (door) {
  let dirs (north south east west)
  if [ismember "north" $dirs] {
    do "say North is a valid direction."
  }
}

after command (gesture) {
  if [hasabbrev "wav" [alias $actor]] {
    do "say Your name starts like 'wave'."
  }
}

A range — slice

slice keeps a positional range: a start only takes everything from there on; a start and end take start up to (but not including) end:

after command (top3) {
  each [slice [/> $self room people] 0 3] { <p>
    do "say Front rank: [name $p]."
  }
}

Pick one at random — choose

choose returns a uniformly-random element, or null over an empty iterable:

after enter {
  let target [choose [select [creatures $self] &isplayer]]
  unless [isnull $target]
  do "wave $target"
}

The unless guard skips the wave when no player was present and choose returned null.

Lazy filtering pays off

select and slice are lazy: they compute each element only as a consumer pulls it. So [first [select … ]] does only as much work as it takes to find the first match — handy when you want "the first player in the room" without materializing the whole filtered set:

after command (greet) {
  let p [first [select [creatures $self] &isplayer]]
  unless [isnull $p]
  do "bow $p"
}

Visibility — cansee

Producers list what is present; they do not account for what the owner can perceive. To respect darkness, invisibility, and hiding, filter with cansee:

after command (peer) {
  let visible [select [creatures $self] { <c> [cansee $self $c] }]
  do "say I can make out [count $visible] figure(s) in here."
}

Gear, implants, and container contents

Beyond a creature's carried inventory, three producers reach the things attached to it: equipment (worn slots), implants (implanted devices), and tattoos (tattoos). They consume the same way as any other iterable:

after command (frisk) {
  echo $actor "Worn: [count [equipment $actor]], implants: [count [implants $actor]], tattoos: [count [tattoos $actor]]."
  each [equipment $actor] { <piece>
    echo $actor "You are wearing [name $piece]."
  }
}

For what is inside a container — an object or a room — contents gives the items it holds:

after command (rummage) {
  let chest [first [/> $self room objects]]
  unless [isnull $chest]
  do "say The [name $chest] holds [count [contents $chest]] thing(s)."
}

Built-in word matching — keyword and abbrev

A handler's $args is a word iterable, so two predicates read it directly: keyword matches whole words (case-insensitive) and abbrev matches prefixes. These are the idiomatic way to branch on what a player typed:

after command (say) {
  require [keyword $args help assist aid]
  echo $actor "$self listens attentively."
}

See also