Inspecting kind/room · the kinds · the language · the modules
{name}
- id
- kind/room
- called
- {name}
- kind chain
- kind/room → record
- owner
- the town
- made by
- the town
- version
- 21
- pin
- kolaj-hasar
- state
- 57111 bytes
Its spells
To cast them, come in.
Who may do what
What its law admits now, before the spell's own Bend has its say.
| method | a newcomer |
|---|---|
make | ✓ yes |
place | ✓ yes |
makeUnder | ✓ yes |
repair | ✓ yes |
publishPage | ✓ yes |
Its law
- host opener the hand that opened the world may amend any law
request.kind == 2 and request.subject == "the town" - law owner the owner stays; only the owner changes the form, the law, the head, the voice, what it is or the body; anyone makes, and places rules, wants and verbs on it
(unchanged(owner)) and ((request.subject == new.owner) or ((((((((request.kind == 0) or (request.kind == 4)) and (unchanged(form))) and (unchanged(law))) and (unchanged(head))) and (unchanged(voice))) and (unchanged(about))) and (unchanged(body)))) - law kind kinds's law holds
under "kinds"
Its Bend
what it runs: its entry module 18 lines
layer over ./Kind.obend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Form.obend as Form
import ./Rows.obend as Rows
type State = Super.State
form make:
name: text 1..64
spween: text 0..16384
def make(state: State, input: MakeInput, context: Abi.Context) -> Activity<Super.Done>:
Super.making(state, Lists.List.cons({head: {name: "name", value: input.name}, tail: Lists.List.cons({head: {name: "spween", value: input.spween}, tail: Lists.List.nil()})}), "", context)
form place:
name: text 1..64
spween: text 0..16384
id: text 1..64
def place(state: State, input: PlaceInput, context: Abi.Context) -> Activity<Super.Done>:
Super.making(state, Lists.List.cons({head: {name: "name", value: input.name}, tail: Lists.List.cons({head: {name: "spween", value: input.spween}, tail: Lists.List.cons({head: {name: "id", value: input.id}, tail: Lists.List.nil()})})}), "", context)
the body this kind lays on what it makes 684 lines
Each thing of this kind runs this layer over what the kind is of.
layer over ./Record.obend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Form.obend as Form
import ./Plan.obend as Plans
import ./World.obend as World
import ./Card.obend as Card
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Text.obend as Text
import ./Scenes.obend as Scenes
import ./Spween.obend as Spween
# The room's body, over RECORD (docs/METAPHYSICS.md §7.2, `kind/room`): who is in it, what lies
# in it, and, when it has passages, a scene its readers walk by their choices. The reader is
# always the turn's principal; no method takes one as an argument. A room's own law is the
# record's: its scene (start, passages, cooldown, requires) is its owner's, and everyone else
# changes it only by these spells.
#
# Entering needs the scene's `requires` and start passage's guard to hold (their outside clauses judged by the host, Scenes.obend) and, with a cooldown, that the reader left at
# least `cooldown` clock minutes ago (folkway 10); an entering is the room's attention, and tends it
# (a weather over the room rains while someone has been there that day). A choice offers itself only while its guard
# holds (the inside over the variables, the outside by the host with the reader as `request.subject`);
# taking it applies its effects to the variables in order, writes `chose` (the rules its casts are
# fire on it, under the room's owner) and moves the reader to its target, or out at END. `say`, `emote` and `whisper` reach those in the room (folkway 5); a
# thing lying here is taken and put down by its own call, while its taker is here. A ```spween
# block posted here makes a room of its own under this one, by the room's kind.
type State = Super.State
type Choice = Scenes.Choice
type Passage = Scenes.Passage
type Var = Scenes.Var
type Presence = Rows.Presence
type Mine = Rows.Mine
type Departure = Rows.Departure
type Done = Scenes.Done
def leftKey(l: Departure) -> {at: Nat, who: String}:
{at: l.at, who: l.who}
def holds(relation: Relations.Relation<Plans.Reference>, reference: Plans.Reference) -> Bool:
Relations.has(relation, {object: reference.object})
def here(context: Abi.Context, state: State) -> Lists.Maybe<Presence>:
Rows.presentAs(state.presence, context.principal)
def isHere(context: Abi.Context, state: State) -> Bool:
Rows.isPresent(state.presence, context.principal)
# A call reads the room with the calling turn's staged entry or departure applied.
def present(state: State, context: Abi.Context) -> Activity<Bool>:
isHere(context, state)
# Principals with avatars index presence in the room's transaction; a principal without an
# avatar still enters and leaves. A failed index refuses the presence write with it.
sum Indexed:
indexed: {}
def indexPresence(context: Abi.Context) -> Activity<Indexed>:
match world.viewField::<Plans.Reference>({object: {world: context.world, object: context.principal}, field: "at"}):
case viewed(_): indexAvatar(context)
case denied(_): Indexed.indexed({})
case refused(_): Indexed.indexed({})
def indexAvatar(context: Abi.Context) -> Activity<Indexed>:
match world.call::<Bool>({object: {world: context.world, object: context.principal}, method: "locate", argument: Plans.nothing()}):
case returned(r): if r.result then Indexed.indexed({}) else refuse("refused location: The avatar did not index presence.")
case refused(r): refuse("refused {r.clause}: {r.reading}")
case denied(_): refuse("refused location: The avatar did not admit its room.")
# The reader's own variables here (`my.<name>`, Record's `mine`), and what a guard reads for them: the room's and theirs.
def ownOf(who: String, state: State) -> Lists.List<Var>:
Lists.map(Lists.filter(Relations.rows(state.mine), fn(m: Mine) -> Bool: m.who == who), fn(m: Mine) -> Var: {name: m.name, value: m.value})
def readerVars(who: String, state: State) -> Lists.List<Var>:
Scenes.readerVars(state.vars, ownOf(who, state))
# This turn's draw (Scenes.turnDraw: its entry's height, principal and intent), and the one a reader's passage reads
# its text with: the turn's that brought them there, kept on their presence; for one not here, this turn's.
def turnDraw(context: Abi.Context) -> Nat:
Scenes.turnDraw(context.height, context.principal, context.intent)
def readerDraw(context: Abi.Context, state: State) -> Nat:
match here(context, state):
case some(p): p.value.draw
case none(_): turnDraw(context)
def passageAt(id: String, state: State) -> Lists.Maybe<Passage>:
Lists.find(state.passages, fn(p: Passage) -> Bool: p.id == id)
# A passage's guard belongs to the destination, including one stitched after its incoming choices.
def passageGuard(id: String, state: State) -> Scenes.Guard:
if id == Scenes.ending() then Lists.List.nil() else guardAt(passageAt(id, state))
def guardAt(found: Lists.Maybe<Passage>) -> Scenes.Guard:
match found:
case none(_): Lists.List.nil()
case some(p): p.value.guard
def entryGuard(state: State) -> Scenes.Guard:
Lists.concat(state.requires, passageGuard(state.start, state))
# A draw's destination is judged after drawing; its card offers the choice's own guard until then.
def choiceGuard(c: Choice, state: State) -> Scenes.Guard:
match c.among:
case nil(_): Lists.concat(c.guard, passageGuard(c.to, state))
case cons(_): c.guard
def lastLeft(who: String, state: State) -> Lists.Maybe<Departure>:
Lists.find(Relations.rows(state.left), fn(l: Departure) -> Bool: l.who == who)
def cooling(who: String, clock: Nat, state: State) -> Bool:
match lastLeft(who, state):
case some(l): clock < l.value.at + state.cooldown
case none(_): false
def leftAt(who: String, state: State) -> Nat:
match lastLeft(who, state):
case some(l): l.value.at
case none(_): 0n
# What a spell may do, judged purely before anything is written: a refusal names itself and
# writes nothing. A move carries the edits of its effects, one write each, in order, and the room's variables and the
# reader's own as they leave them.
sum Verdict:
enter: {at: String}
move: {me: Presence, choice: Choice, chose: String, vars: Lists.List<Edit>, after: Lists.List<Var>, own: Lists.List<Var>}
leave: {me: Presence, chose: String, vars: Lists.List<Edit>, after: Lists.List<Var>, own: Lists.List<Var>}
no: Card.Refusal
def no(clause: String, reading: String) -> Verdict:
Verdict.no({clause: clause, reading: reading})
def enterVerdict(context: Abi.Context, state: State) -> Verdict:
if lapsed(state, context) then met(context, state) else enterNow(context, state)
def enterNow(context: Abi.Context, state: State) -> Verdict:
match here(context, state):
case some(_): no("alreadyHere", "Already here.")
case none(_): if Relations.count(state.presence) >= Super.presenceMax() then no("roomFull", "{natText(Super.presenceMax())} are already here.") else met(context, state)
# Presence lapses with the room's quiet (obend: ultimo92 "Here" in room/7 from 04:40 to 12:58, #1335 to #2122). A
# presence row keeps no time of its own, and the room's `tended` is the clock of its last attention: an entry, a
# choice, a word said aloud. A room untended for `quietMinutes` shows whoever it held as last here, not here; the
# next entry takes their rows off in its own journaled write, so the record says who left with the entry that found
# the room empty, and a replay derives the same. Never by a timer: nothing is written while nobody comes.
def quietMinutes() -> Nat:
360n
def lapsed(state: State, context: Abi.Context) -> Bool:
Relations.count(state.presence) > 0n && state.tended + quietMinutes() <= context.clock
def lapsing(rows: Lists.List<Presence>) -> Activity<Indexed>:
match rows:
case nil(_): Indexed.indexed({})
case cons(c): lapsingOne(c.head.who, c.tail)
def lapsingOne(who: String, rest: Lists.List<Presence>) -> Activity<Indexed>:
let written(_) = write {presence: retract {who: who}}
lapsing(rest)
def met(context: Abi.Context, state: State) -> Verdict:
match Scenes.failing(entryGuard(state), readerVars(context.principal, state)):
case some(c): no("unmet", "The room requires {Scenes.clauseText(c.value)}.")
case none(_): if cooling(context.principal, context.clock, state) then no("cooldown", "One who left enters again only after the cooldown: from clock {natText(leftAt(context.principal, state) + state.cooldown)}.") else roomy(context, state)
# `left` keeps Super.leftMax() departures, and one still cooling must never be dropped for a newer
# one: everyone here may leave, so an entry is refused while the cooling departures, the readers
# here and this one would pass the limit.
def roomy(context: Abi.Context, state: State) -> Verdict:
let cooled = Lists.filter(Relations.rows(state.left), fn(l: Departure) -> Bool: context.clock < l.at + state.cooldown)
if state.cooldown > 0n && Lists.length(cooled) + Relations.count(state.presence) + 1n > Super.leftMax() then crowded(cooled, state) else Verdict.enter({at: state.start})
def crowded(cooled: Lists.List<Departure>, state: State) -> Verdict:
match cooled:
case cons(c): no("crowded", "{natText(Lists.length(cooled))} left within the cooldown; enter again from clock {natText(c.head.at + state.cooldown)}.")
case nil(_): no("crowded", "The room is full; enter when one leaves.")
def leaveVerdict(context: Abi.Context, state: State) -> Verdict:
match here(context, state):
case none(_): no("notHere", "Not here.")
case some(p): Verdict.leave({me: p.value, chose: "", vars: Lists.List.nil(), after: state.vars, own: ownOf(p.value.who, state)})
def chooseVerdict(choice: String, context: Abi.Context, state: State) -> Verdict:
match here(context, state):
case none(_): no("notHere", "Enter the room first.")
case some(p): standing(p.value, choice, state, turnDraw(context))
def standing(me: Presence, choice: String, state: State, draw: Nat) -> Verdict:
match passageAt(me.at, state):
case none(_): no("noPassage", "This room has no passage to choose from.")
case some(ps): offered(me, Lists.find(ps.value.choices, fn(c: Choice) -> Bool: c.label == choice), Scenes.chosen(me.at, placeOf(ps.value.choices, choice, 1n)), choice, state, draw)
# The place of the choice so labelled among a passage's, from 1.
def placeOf(choices: Lists.List<Choice>, label: String, n: Nat) -> Nat:
match choices:
case nil(_): n
case cons(c): if c.head.label == label then n else placeOf(c.tail, label, n + 1n)
def offered(me: Presence, found: Lists.Maybe<Choice>, chose: String, choice: String, state: State, draw: Nat) -> Verdict:
match found:
case none(_): no("notOffered", "{me.at} does not offer {choice}")
case some(c): guarded(me, drawnTo(c.value, chose, draw), chose, state)
def guarded(me: Presence, c: Choice, chose: String, state: State) -> Verdict:
match Scenes.failing(choiceGuard(c, state), readerVars(me.who, state)):
case some(f): no("guarded", "{c.label} needs {Scenes.clauseText(f.value)}.")
case none(_): arriving(me, c, chose, state)
# A choice that draws between targets goes to the one this turn's draw lands on for it (Scenes.drawFor its `chose`).
def drawnTo(c: Choice, chose: String, draw: Nat) -> Choice:
match c.among:
case nil(_): c
case cons(_): extend(c, {to: Scenes.drawn(c.among, Scenes.drawFor(draw, chose)), among: Lists.List.nil()})
def arriving(me: Presence, c: Choice, chose: String, state: State) -> Verdict:
if c.to == Scenes.ending() then effected(me, c, chose, effectsOf(c, me.who, state), true) else reached(me, c, chose, state)
def reached(me: Presence, c: Choice, chose: String, state: State) -> Verdict:
match passageAt(c.to, state):
case none(_): no("noPassage", "{c.to} is not in this room")
case some(_): effected(me, c, chose, effectsOf(c, me.who, state), false)
def effected(me: Presence, c: Choice, chose: String, edits: Effects, ends: Bool) -> Verdict:
match edits:
case full(f): no("varsFull", f.reading)
case edits(e): if ends then Verdict.leave({me: me, chose: chose, vars: e.edits, after: e.vars, own: e.own}) else Verdict.move({me: me, choice: c, chose: chose, vars: e.edits, after: e.vars, own: e.own})
# The outside of a guard, judged in the turn: each condition in the law's grammar by the host, of the
# room as it stands with the reader as `request.subject` (`world.holds`), and what the reader keeps or
# holds by the thing's own `keeper` and `holder`, in order; the first that fails, if any. A condition
# the host cannot read fails, with its reason.
sum Judged:
held: {}
failed: {clause: Scenes.Clause, why: String}
def judging(guard: Scenes.Guard, context: Abi.Context) -> Activity<Judged>:
match guard:
case nil(_): Judged.held({})
case cons(c): judgedOne(c.head, c.tail, context)
def judgedOne(clause: Scenes.Clause, rest: Scenes.Guard, context: Abi.Context) -> Activity<Judged>:
match clause.test:
case law(_): lawJudged(clause, rest, context)
case holding(_): thingJudged(clause, rest, true, context)
case lacking(_): thingJudged(clause, rest, false, context)
case _: judging(rest, context)
def lawJudged(clause: Scenes.Clause, rest: Scenes.Guard, context: Abi.Context) -> Activity<Judged>:
match world.holds({object: Plans.self(context), when: clause.value}):
case held(h): if h.value then judging(rest, context) else Judged.failed({clause: clause, why: ""})
case refused(r): Judged.failed({clause: clause, why: r.reading})
def thingJudged(clause: Scenes.Clause, rest: Scenes.Guard, wanted: Bool, context: Abi.Context) -> Activity<Judged>:
match hasThing(clause.key, clause.value, context):
case yes(_): if wanted then judging(rest, context) else Judged.failed({clause: clause, why: ""})
case no(_): if wanted then Judged.failed({clause: clause, why: ""}) else judging(rest, context)
# Whether the reader keeps a thing (its `keeper`) or holds it (its `holder`), as the thing says.
sum Had:
yes: {}
no: {}
def had(b: Bool) -> Had:
if b then Had.yes({}) else Had.no({})
def hasThing(category: String, thing: String, context: Abi.Context) -> Activity<Had>:
if category == "holds" then holdsThing(thing, context) else keepsThen(category == "has", thing, context)
def keepsThen(orHolds: Bool, thing: String, context: Abi.Context) -> Activity<Had>:
match world.viewField::<String>({object: {world: context.world, object: thing}, field: "keeper"}):
case viewed(v): if v.state == context.principal then Had.yes({}) else if orHolds then holdsThing(thing, context) else Had.no({})
case _: if orHolds then holdsThing(thing, context) else Had.no({})
def holdsThing(thing: String, context: Abi.Context) -> Activity<Had>:
match world.viewField::<Plans.Reference>({object: {world: context.world, object: thing}, field: "holder"}):
case viewed(v): had(v.state.object == context.principal)
case _: Had.no({})
def failedReading(c: Scenes.Clause, why: String) -> String:
if why == "" then Scenes.clauseText(c) else "{Scenes.clauseText(c)} ({why})"
# A verdict whose choice (or, entering, the scene's requirements) has an outside, judged before anything
# is written: a clause that fails refuses it by name.
def outward(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Verdict>:
match verdict:
case enter(_): outwardBy(verdict, Scenes.outside(entryGuard(state)), "unmet", "The room requires ", context)
case move(m): outwardBy(verdict, Scenes.outside(choiceGuard(m.choice, state)), "guarded", "{m.choice.label} needs ", context)
case leave(l): if l.chose == "" then verdict else outwardBy(verdict, Scenes.outside(choiceAt(l.me.at, l.chose, state).guard), "guarded", "{choiceAt(l.me.at, l.chose, state).label} needs ", context)
case no(_): verdict
def outwardBy(verdict: Verdict, guard: Scenes.Guard, clause: String, lead: String, context: Abi.Context) -> Activity<Verdict>:
match judging(guard, context):
case held(_): verdict
case failed(f): no(clause, "{lead}{failedReading(f.clause, f.why)}.")
# The choice a `chose` names in a passage.
def choiceAt(at: String, chose: String, state: State) -> Choice:
match passageAt(at, state):
case some(p): nthChoice(p.value.choices, chose, at, 1n)
case none(_): noChoice()
def noChoice() -> Choice:
{label: "", to: "", effects: Lists.List.nil(), guard: Lists.List.nil(), text: "", among: Lists.List.nil()}
def nthChoice(choices: Lists.List<Choice>, chose: String, at: String, n: Nat) -> Choice:
match choices:
case nil(_): noChoice()
case cons(c): if Scenes.chosen(at, n) == chose then c.head else nthChoice(c.tail, chose, at, n + 1n)
# The edits a choice's effects make, in order, each against the variables as the ones before it leave them, and the
# room's variables and the reader's own as they stand after them all. The room keeps sixteen variables of its own,
# sixteen of each reader's, and Super.mineMax() of its readers' together: one more is refused, never dropped.
sum Edit:
room: {entry: Plans.Entries<Var, Var>}
own: {row: Mine}
sum Effects:
edits: {edits: Lists.List<Edit>, vars: Lists.List<Var>, own: Lists.List<Var>}
full: {reading: String}
def effectsOf(c: Choice, who: String, state: State) -> Effects:
effects(c.effects, who, state.vars, ownOf(who, state), Relations.count(state.mine), Lists.List.nil())
def effects(list: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>) -> Effects:
match list:
case nil(_): Effects.edits({edits: edits, vars: vars, own: own})
case cons(c): if Scenes.varies(c.head) == false then effects(c.tail, who, vars, own, rows, edits) else if Scenes.isMine(c.head.key) then ownOn(c.head, Scenes.ownName(c.head.key), c.tail, who, vars, own, rows, edits, Lists.find(own, fn(v: Var) -> Bool: v.name == Scenes.ownName(c.head.key))) else effectOn(c.head, c.tail, who, vars, own, rows, edits, Lists.find(vars, fn(v: Var) -> Bool: v.name == c.head.key))
def effectOn(effect: Scenes.Effect, rest: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>, found: Lists.Maybe<Var>) -> Effects:
match found:
case some(v): effects(rest, who, Lists.map(vars, fn(w: Var) -> Var: if w.name == effect.key then {name: w.name, value: Scenes.applied(effect, w.value)} else w), own, rows, Lists.append(edits, Edit.room({entry: Plans.Entries.amendItem({item: v.value, change: {name: effect.key, value: Scenes.applied(effect, v.value.value)}})})))
case none(_): if Lists.length(vars) >= 16n then Effects.full({reading: "Sixteen variables are already set."}) else effects(rest, who, Lists.append(vars, {name: effect.key, value: Scenes.applied(effect, "")}), own, rows, Lists.append(edits, Edit.room({entry: Plans.Entries.append({item: {name: effect.key, value: Scenes.applied(effect, "")}})})))
def ownOn(effect: Scenes.Effect, name: String, rest: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>, found: Lists.Maybe<Var>) -> Effects:
match found:
case some(v): effects(rest, who, vars, Lists.map(own, fn(w: Var) -> Var: if w.name == name then {name: name, value: Scenes.applied(effect, w.value)} else w), rows, Lists.append(edits, Edit.own({row: {who: who, name: name, value: Scenes.applied(effect, v.value.value)}})))
case none(_): if Lists.length(own) >= 16n then Effects.full({reading: "Sixteen of your own variables are already set here."}) else if rows >= Super.mineMax() then Effects.full({reading: "This room keeps {natText(Super.mineMax())} of its readers' own variables, and holds that many."}) else effects(rest, who, vars, Lists.append(own, {name: name, value: Scenes.applied(effect, "")}), rows + 1n, Lists.append(edits, Edit.own({row: {who: who, name: name, value: Scenes.applied(effect, "")}})))
# The reader's own variables, as a choice left them, in the room's `mine`.
def ownAfter(mine: Relations.Relation<Mine>, who: String, own: Lists.List<Var>) -> Relations.Relation<Mine>:
match own:
case nil(_): mine
case cons(c): ownAfter(Relations.upsert(mine, {who: who, name: c.head.name, value: c.head.value}, Rows.mineKey), who, c.tail)
# Performs a verdict: a refusal writes nothing and is answered by its clause (Card.refusing);
# otherwise the writes, in order, and the reader is shown the room as they leave it.
def done(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Done>:
match outward(verdict, context, state):
case no(n): roomRefused(n, context)
case enter(e): performed(Verdict.enter(e), context, state)
case move(m): performed(Verdict.move(m), context, state)
case leave(l): performed(Verdict.leave(l), context, state)
def performed(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Done>:
match verdict:
case no(n): roomRefused(n, context)
case enter(e): if lapsed(state, context) then quietEntry(e.at, state, context) else entering(e.at, extend(state, {presence: Relations.insert(state.presence, {who: context.principal, handle: context.handle, at: e.at, draw: turnDraw(context)}, Rows.presenceKey), tended: context.clock}), context)
case move(m): moving(m.me, m.choice, m.chose, m.vars, extend(state, {presence: Relations.upsert(state.presence, {who: m.me.who, handle: m.me.handle, at: m.choice.to, draw: turnDraw(context)}, Rows.presenceKey), vars: m.after, mine: ownAfter(state.mine, m.me.who, m.own), chose: m.chose, tended: context.clock}), context)
case leave(l): departing(l.me, l.chose, l.vars, extend(state, {presence: Relations.retract(state.presence, {who: l.me.who}, Rows.presenceKey), vars: l.after, mine: ownAfter(state.mine, l.me.who, l.own), chose: if l.chose == "" then state.chose else l.chose}), context, state)
def quietEntry(at: String, state: State, context: Abi.Context) -> Activity<Done>:
match lapsing(Relations.rows(state.presence)):
case indexed(_): entering(at, extend(state, {presence: Relations.insert(Relations.empty(), {who: context.principal, handle: context.handle, at: at, draw: turnDraw(context)}, Rows.presenceKey), tended: context.clock}), context)
def entering(at: String, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {presence: insert {who: context.principal, handle: context.handle, at: at, draw: turnDraw(context)}, tended: set context.clock}
match indexPresence(context):
case indexed(_): entryTold(at, after, context)
def entryTold(at: String, after: State, context: Abi.Context) -> Activity<Done>:
match lastLeft(context.principal, after):
case some(l): if l.value.entry == 0n then welcomed(at, after, Document.text(""), context) else away(at, after, l.value.entry, context)
case none(_): welcomed(at, after, Document.text(""), context)
def welcomed(at: String, after: State, lead: Document.Document, context: Abi.Context) -> Activity<Done>:
match seen(after, context):
case seen(v): Card.tell(Done.entered({at: at}), Document.concat(lead, Super.render(v.state, context)))
# One who comes back is told first what happened here while they were away, since the entry they
# left at: the lines others wrote here, newest first, then what changed under the room (`world.changed`
# under its id: rooms carved in it, things made here), three lines at most.
def away(at: String, after: State, since: Nat, context: Abi.Context) -> Activity<Done>:
match world.changed({since: since, prefix: "{context.object}/", after: ""}):
case changed(c): welcomed(at, after, awayLines(after, since, c.items, context), context)
case denied(_): welcomed(at, after, awayLines(after, since, Lists.List.nil(), context), context)
def awayLines(state: State, since: Nat, changes: Lists.List<World.Change>, context: Abi.Context) -> Document.Document:
let written = Lists.map(Lists.filter(Lists.reverse(Relations.order(state.lines, fn(l: Rows.Line) -> Nat: l.n)), fn(l: Rows.Line) -> Bool: l.at > since && l.author != context.principal), fn(l: Rows.Line) -> String: "{Card.shown(l.author, l.handle, context)}: {clip(l.text)}\n")
let made = Lists.map(changes, fn(c: World.Change) -> String: "{c.object.object} v{natText(c.version)} by {Card.handle(c.by)}\n")
let rows = Lists.take(Lists.concat(written, made), 3n)
if Lists.length(rows) == 0n then Document.text("") else Document.text("While you were away (since entry {natText(since)}):\n{textJoin(rows, \"\")}\n")
def clip(text: String) -> String:
if textLength(text) > 100n then "{textTake(text, 99n)}…" else text
def moving(me: Presence, c: Choice, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {presence: upsert {who: me.who, handle: me.handle, at: c.to, draw: turnDraw(context)}, chose: set chose, tended: set context.clock}
varied(vars, Done.moved({to: c.to}), c.text, after, context)
# The variables' edits in order, then the reader is shown what taking the choice said (its `text`, "" for none) and the
# room as the turn leaves it.
def varied(vars: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>:
match vars:
case nil(_): told(result, said, after, context)
case cons(c): wroteVar(c.head, c.tail, result, said, after, context)
# What a choice said comes first: above the passage it led to, so the card's budget counts it with the passage, or
# alone above the room when it ended the visit.
def told(result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>:
let shown = Spween.rendered(said, readerVars(context.principal, after), readerDraw(context, after))
match seen(after, context):
case seen(v): Card.tell(result, if shown == "" then Super.render(v.state, context) else if isHere(context, after) then Super.render(saying(said, v.state, context), context) else Document.concat(Document.text("{shown}\n\n"), Super.render(v.state, context)))
def saying(said: String, state: State, context: Abi.Context) -> State:
match here(context, state):
case some(p): extend(state, {passages: Lists.map(state.passages, fn(q: Passage) -> Passage: if q.id == p.value.at then extend(q, {text: "{said}\n\n{q.text}"}) else q)})
case none(_): state
# The room as the turn's reader sees it: at their passage, the choices whose outside holds, shown without
# it (a pure read of the card judges the inside alone and shows the outside beside each choice).
sum Seen:
seen: {state: State}
def seen(state: State, context: Abi.Context) -> Activity<Seen>:
match here(context, state):
case none(_): Seen.seen({state: state})
case some(p): seenAt(passageAt(p.value.at, state), state, context)
def seenAt(found: Lists.Maybe<Passage>, state: State, context: Abi.Context) -> Activity<Seen>:
match found:
case none(_): Seen.seen({state: state})
case some(ps): seenSifted(ps.value, state, context)
def seenSifted(ps: Passage, state: State, context: Abi.Context) -> Activity<Seen>:
match sifted(ps.choices, state, context):
case sifted(k): Seen.seen({state: extend(state, {passages: Lists.map(state.passages, fn(p: Passage) -> Passage: extend(p, {guard: Lists.filter(p.guard, Scenes.inside), choices: if p.id == ps.id then k.choices else p.choices}))})})
sum Sifted:
sifted: {choices: Lists.List<Choice>}
def sifted(choices: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>:
match choices:
case nil(_): Sifted.sifted({choices: Lists.List.nil()})
case cons(c): siftedOne(c.head, c.tail, state, context)
def siftedOne(c: Choice, rest: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>:
match judging(Scenes.outside(choiceGuard(c, state)), context):
case held(_): keptBefore(extend(c, {guard: Lists.filter(c.guard, Scenes.inside)}), rest, state, context)
case failed(_): sifted(rest, state, context)
def keptBefore(c: Choice, rest: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>:
match sifted(rest, state, context):
case sifted(r): Sifted.sifted({choices: Lists.List.cons({head: c, tail: r.choices})})
def wroteVar(edit: Edit, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>:
match edit:
case room(r): wroteRoom(r.entry, rest, result, said, after, context)
case own(o): wroteOwn(o.row, rest, result, said, after, context)
def wroteRoom(entry: Plans.Entries<Var, Var>, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {vars: entry}))
varied(rest, result, said, after, context)
def wroteOwn(row: Mine, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {mine: upsert row}
varied(rest, result, said, after, context)
# Leaving (or reaching END) records when, for the cooldown: the reader's last departure is
# retracted and this one inserted.
def departing(me: Presence, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context, state: State) -> Activity<Done>:
let written(_) = write {presence: retract {who: me.who}, chose: set if chose == "" then state.chose else chose}
match indexPresence(context):
case indexed(_): departureRecorded(me, chose, vars, after, context, state)
def departureRecorded(me: Presence, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context, state: State) -> Activity<Done>:
match lastLeft(me.who, state):
case some(l): forgotten(l.value, vars, choiceAt(me.at, chose, state).text, after, context)
case none(_): recorded(vars, choiceAt(me.at, chose, state).text, after, context)
def forgotten(last: Departure, vars: Lists.List<Edit>, said: String, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {left: retract leftKey(last)}
recorded(vars, said, after, context)
def recorded(vars: Lists.List<Edit>, said: String, after: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {left: insert {at: context.clock, who: context.principal, entry: context.height}}
varied(vars, Done.left({}), said, after, context)
form enter:
form choose:
choice: text 1..64
form leave:
form say:
line: text 1..280
form emote:
line: text 1..280
form whisper:
to: text 1..160
line: text 1..280
form write:
text: text 1..280
form chime:
form lines:
from: natural 0..1000000000
form door:
label: text 1..32
to: text 1..128
form undoor:
label: text 1..32
# The scene, replaced or extended by its owner: `rescene` replaces it whole in one turn, read as making reads it
# (Spween.parse: every target answered); the station stitches a wing a turn (Spween.wing) and restitches from its first.
# Each outside guard is read once by the host, so one it cannot read refuses by name; the casts are rules of the room's
# text, the old scene's removed and the new one's added (`stagedLawWithout`, the owner's other clauses kept), then the
# passages and requirements are written, and after the last wing a reader at a passage no longer there stands at the start.
# Variables, lines, presence and what lies here stay. `mark`, when not "", is kept as the wing's mark.
record Staging:
text: String
replace: Bool
finish: Bool
mark: String
form rescene:
spween: text 1..16384
def rescene(state: State, input: ResceneInput, context: Abi.Context) -> Activity<Done>:
if context.principal != state.owner then roomRefused({clause: "notOwner", reading: "Only the room's owner replaces its scene; a ```spween block posted here makes a room of your own under it."}, context) else staging(Spween.parse(input.spween), {text: input.spween, replace: true, finish: true, mark: ""}, state, context)
def staging(parsed: Spween.Parsed, work: Staging, state: State, context: Abi.Context) -> Activity<Done>:
match parsed:
case refused(r): roomRefused({clause: "spween", reading: r.reason}, context)
case parsed(p): if Spween.joined(if work.replace then Lists.List.nil() else state.passages, p.seed.passages) != "" then roomRefused({clause: "spween", reading: Spween.joined(if work.replace then Lists.List.nil() else state.passages, p.seed.passages)}, context) else stageGuards(p.seed, Scenes.conditions(p.seed), work, state, context)
def stageGuards(wing: Scenes.Seed, conditions: Lists.List<String>, work: Staging, state: State, context: Abi.Context) -> Activity<Done>:
match conditions:
case nil(_): stageRules(wing, work, state, context)
case cons(c): stageGuard(wing, c.head, c.tail, work, state, context)
def stageGuard(wing: Scenes.Seed, condition: String, rest: Lists.List<String>, work: Staging, state: State, context: Abi.Context) -> Activity<Done>:
match world.holds({object: Plans.self(context), when: condition}):
case held(_): stageGuards(wing, rest, work, state, context)
case refused(r): roomRefused({clause: "spween", reading: "The guard `{condition}` does not read: {r.reading}"}, context)
def stageRules(wing: Scenes.Seed, work: Staging, state: State, context: Abi.Context) -> Activity<Done>:
let all = Lists.concat(if work.replace then Lists.List.nil() else state.passages, wing.passages)
match world.inspect({object: Plans.self(context), source: false}):
case inspected(i): stageAmend(wing, work, all, "{if work.replace then stagedLawWithout(i.law, Scenes.castsOf(state.passages)) else i.law}{Scenes.ruleText(wing.passages)}", state, context)
case _: roomRefused({clause: "spween", reading: "The room's text is not readable here; its scene stands as it was."}, context)
# Scene rules have the names of their passage, choice and cast. Only those rule lines are removed;
# the owner's other laws, rules and wants retain their text, wherever they stand in the law.
def stagedLawWithout(law: String, casts: Lists.List<Scenes.Cast>) -> String:
let at = Text.indexOf(law, "\n")
let line = textTake(law, at)
let kept = if Lists.any(casts, fn(c: Scenes.Cast) -> Bool: Text.starts(line, "rule {c.id}_{natText(c.n)}{Scenes.suffix(c.k)} ") || Text.starts(line, "rule {c.id}_{natText(c.n)}{Scenes.suffix(c.k)}:")) then "" else line
if at == textLength(law) then kept else stagedJoined(kept, stagedLawWithout(textDrop(law, at + 1n), casts))
def stagedJoined(first: String, rest: String) -> String:
if first == "" then rest else if rest == "" then first else "{first}\n{rest}"
def stageAmend(wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, law: String, state: State, context: Abi.Context) -> Activity<Done>:
match world.amend({object: Plans.self(context), law: law}):
case amended(_): stageCleared(if work.replace then state.passages else Lists.List.nil(), if work.replace then state.requires else Lists.List.nil(), wing, work, all, state, context)
case refused(r): roomRefused({clause: r.clause, reading: "The scene's casts are rules the room's text could not take: {r.reading}"}, context)
def stageCleared(passages: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>:
match passages:
case nil(_): stageRequirementsCleared(requires, wing, work, all, state, context)
case cons(c): stagePassageCleared(c.head, c.tail, requires, wing, work, all, state, context)
def stagePassageCleared(passage: Scenes.Passage, rest: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {passages: Plans.Entries.removeItem({item: passage})}))
stageCleared(rest, requires, wing, work, all, state, context)
def stageRequirementsCleared(requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>:
match requires:
case nil(_): stageWrite(wing, work, all, state, context)
case cons(c): stageRequirementCleared(c.head, c.tail, wing, work, all, state, context)
def stageRequirementCleared(clause: Scenes.Clause, rest: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {requires: Plans.Entries.removeItem({item: clause})}))
stageRequirementsCleared(rest, wing, work, all, state, context)
def stageWrite(wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>:
let first = work.replace || Lists.length(state.passages) == 0n
let start = if first then wing.start else state.start
let written(_) = write {start: set start, cooldown: set if first then wing.cooldown else state.cooldown}
stageMarked(work, wing, all, if first then wing.requires else Lists.List.nil(), if work.finish then Relations.rows(state.presence) else Lists.List.nil(), start, context)
# A wing's mark, when its maker keeps one (the station's chained digest); a room's rescene keeps none.
def stageMarked(work: Staging, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, requires: Scenes.Guard, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
if work.mark == "" then stagePassages(wing.passages, requires, wing, all, presence, start, context) else stageStamped(work.mark, wing, all, requires, presence, start, context)
def stageStamped(mark: String, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, requires: Scenes.Guard, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
let written(_) = write {marks: insert {name: mark, who: context.principal, handle: context.handle, at: context.height}}
stagePassages(wing.passages, requires, wing, all, presence, start, context)
# One write a passage and a clause, each appended, as the room writes its variables.
def stagePassages(passages: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
match passages:
case nil(_): stageRequires(requires, wing, all, presence, start, context)
case cons(c): stagePassage(c.head, c.tail, requires, wing, all, presence, start, context)
def stagePassage(passage: Scenes.Passage, rest: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {passages: Plans.Entries.append({item: passage})}))
stagePassages(rest, requires, wing, all, presence, start, context)
def stageRequires(requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
match requires:
case nil(_): stagePresence(presence, wing, all, start, context)
case cons(c): stageRequire(c.head, c.tail, wing, all, presence, start, context)
def stageRequire(clause: Scenes.Clause, rest: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {requires: Plans.Entries.append({item: clause})}))
stageRequires(rest, wing, all, presence, start, context)
def stagePresence(presence: Lists.List<Rows.Presence>, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, start: String, context: Abi.Context) -> Activity<Done>:
match presence:
case nil(_): Card.tell(Done.done({}), Document.text("Stitched {natText(Lists.length(wing.passages))} passages; the scene holds {natText(Lists.length(all))}. {stageWaiting(Spween.waiting(all))}\n"))
case cons(c): if Lists.any(all, fn(p: Scenes.Passage) -> Bool: p.id == c.head.at) then stagePresence(c.tail, wing, all, start, context) else stageMoved(c.head, c.tail, wing, all, start, context)
def stageMoved(me: Rows.Presence, rest: Lists.List<Rows.Presence>, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, start: String, context: Abi.Context) -> Activity<Done>:
let written(_) = write {presence: upsert {who: me.who, handle: me.handle, at: start, draw: me.draw}}
stagePresence(rest, wing, all, start, context)
def stageWaiting(waiting: Lists.List<String>) -> String:
match waiting:
case nil(_): "Every choice leads to a passage."
case cons(_): "Still waiting for: {textJoin(waiting, \", \")}."
def enter(state: State, context: Abi.Context) -> Activity<Done>:
done(enterVerdict(context, state), context, state)
def choose(state: State, input: ChooseInput, context: Abi.Context) -> Activity<Done>:
done(chooseVerdict(input.choice, context, state), context, state)
def leave(state: State, context: Abi.Context) -> Activity<Done>:
done(leaveVerdict(context, state), context, state)
# Talk reaches those in the room (in key order, at most Super.presenceMax()) under the speaker's
# handle; only someone here talks. What is said or done aloud is the room's line too, under the verb
# that said it, so one who comes later is told who spoke here and what was said; a whisper is not.
def say(state: State, input: SayInput, context: Abi.Context) -> Activity<Done>:
if isHere(context, state) then aloud("say", input.line, "{speaker(context)}: {input.line}\n", state, context) else hushed("say", context)
def emote(state: State, input: EmoteInput, context: Abi.Context) -> Activity<Done>:
if isHere(context, state) then aloud("emote", input.line, "* {speaker(context)} {input.line}\n", state, context) else hushed("emote", context)
def aloud(verb: String, text: String, line: String, state: State, context: Abi.Context) -> Activity<Done>:
if Text.blank(text) then roomRefused({clause: "empty", reading: "A line needs words; this {verb} has none, so nothing was said"}, context) else spokenAloud(verb, text, line, state, context)
def spokenAloud(verb: String, text: String, line: String, state: State, context: Abi.Context) -> Activity<Done>:
let written(_) = write {lines: insert {author: context.principal, handle: context.handle, verb: verb, text: text, at: context.height, n: state.next}, next: add 1n, tended: set context.clock}
spoken(Relations.rows(state.presence), line)
def whisper(state: State, input: WhisperInput, context: Abi.Context) -> Activity<Done>:
if isHere(context, state) == false then hushed("whisper", context) else whispered(Lists.find(Relations.rows(state.presence), fn(p: Presence) -> Bool: p.who == input.to || p.handle == input.to), input.to, "{speaker(context)} whispers: {input.line}\n", context)
def whispered(found: Lists.Maybe<Presence>, to: String, line: String, context: Abi.Context) -> Activity<Done>:
match found:
case some(p): confirmed(p.value, line, context)
case none(_): roomRefused({clause: "notHere", reading: "{to} is not here"}, context)
def confirmed(to: Presence, line: String, context: Abi.Context) -> Activity<Done>:
match spoken(Lists.List.cons({head: to, tail: Lists.List.nil()}), line):
case _: Card.tell(Done.done({}), Document.text("Whispered to {Card.shown(to.who, to.handle, context)}; no one else here hears it. Say to all: delvetalk {Card.called(context, \"room\")} say / line: <text>.\n"))
def hushed(doing: String, context: Abi.Context) -> Activity<Done>:
roomRefused({clause: "notPresent", reading: "Only someone here can {doing}; enter first: delvetalk {Card.called(context, \"room\")} enter"}, context)
def roomRefused(refusal: Card.Refusal, context: Abi.Context) -> Activity<Done>:
Card.refusing(Done.refused(refusal), refusal, context)
def speaker(context: Abi.Context) -> String:
Card.name(context.principal, context)
def spoken(present: Lists.List<Presence>, line: String) -> Activity<Done>:
match present:
case nil(_): Done.done({})
case cons(c): spokenTo(c.head, c.tail, line)
def spokenTo(to: Presence, rest: Lists.List<Presence>, line: String) -> Activity<Done>:
match world.offer({to: to.who, document: Document.text(line)}):
case _: spoken(rest, line)
def write(state: State, input: WriteInput, context: Abi.Context) -> Activity<Nat>:
Super.write(state, {text: input.text}, context)
# Its bells rung together (RECORD's `ringTogether`): what lies here and what its doors open onto.
def chime(state: State, context: Abi.Context) -> Activity<Nat>:
Super.ringTogether(state, context)
def lines(state: State, input: LinesInput, context: Abi.Context) -> Activity<Card.Replied>:
Super.lines(state, {from: input.from}, context)
def door(state: State, input: DoorInput, context: Abi.Context) -> Activity<Card.Replied>:
Super.door(state, {label: input.label, to: input.to}, context)
def undoor(state: State, input: UndoorInput, context: Abi.Context) -> Activity<Card.Replied>:
Super.undoor(state, {label: input.label}, context)
# A thing takes itself off the floor into the hands of the principal, who must be here, and puts
# itself down where its holder stands, or where the room's owner lays it; only the thing itself
# (the calling object) does either. A thing lying here that is inspirited gives the room its line,
# under the one who inspirited it, and a mark, so the card lists it among the claimed.
def inspirited(state: State, input: {line: String}, context: Abi.Context) -> Activity<Done>:
if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing is inspirited."}, context) else if holds(state.things, {world: context.world, object: context.caller}) == false then roomRefused({clause: "notLying", reading: "That is not lying here."}, context) else spirited(input.line, context, state)
def spirited(line: String, context: Abi.Context, state: State) -> Activity<Done>:
let written(_) = write {lines: insert {author: context.principal, handle: context.handle, verb: "inspirit", text: if textLength(line) > 280n then "{textTake(line, 279n)}…" else line, at: context.height, n: state.next}, next: add 1n, marks: insert {name: claimMark(context.caller), who: context.principal, handle: context.handle, at: context.height}}
Done.done({})
def claimMark(thing: String) -> String:
"inspirited {thing}"
def claimed(thing: Plans.Reference, state: State) -> Bool:
Super.marked(state, claimMark(thing.object))
def take(state: State, context: Abi.Context) -> Activity<Done>:
if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing can take itself."}, context) else if isHere(context, state) == false then roomRefused({clause: "notPresent", reading: "Only someone here can take things."}, context) else if holds(state.things, {world: context.world, object: context.caller}) == false then roomRefused({clause: "notLying", reading: "That is not lying here."}, context) else lifted(context)
def lifted(context: Abi.Context) -> Activity<Done>:
let written(_) = write {things: retract {object: context.caller}}
Done.done({})
def put(state: State, context: Abi.Context) -> Activity<Done>:
if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing can put itself down."}, context) else if isHere(context, state) == false && context.principal != state.owner then roomRefused({clause: "notPresent", reading: "Only someone here can put things down."}, context) else if holds(state.things, {world: context.world, object: context.caller}) then roomRefused({clause: "alreadyLying", reading: "That is already lying here."}, context) else laid(context)
def laid(context: Abi.Context) -> Activity<Done>:
let written(_) = write {things: insert {world: context.world, object: context.caller}}
Done.done({})
def methods() -> Lists.List<String>:
Text.words("take put inspirited ~present")
def actions(state: State, context: Abi.Context) -> Lists.List<String>:
Lists.concat(Text.words("enter choose leave say emote whisper write chime lines"), if context.principal == state.owner then Text.words("door undoor rescene") else Text.words(""))
# A ```spween block makes a room under this one, by this room's kind (its `makeUnder`, which reads
# the block); posted by this room's owner, it makes nothing and is answered with the spell that makes it this room's
# scene (`rescene`, the block under it). Anything else is the card's default.
def receive(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>:
match Spween.block(input.text):
case some(b): if context.principal == state.owner then rescening(Spween.parse(b.value), context) else authored(b.value, Spween.parse(b.value), context, state)
case none(_): answered(input, context, state)
def rescening(parsed: Spween.Parsed, context: Abi.Context) -> Activity<Card.Replied>:
match parsed:
case refused(r): Super.refused({clause: "spween", reading: r.reason}, context)
case parsed(p): Card.tell(Card.Replied.usage({}), Document.text("This room is yours, so the block makes no room under it. It reads as a scene of {natText(Lists.length(p.seed.passages))} passages; to make it this room's scene, post it again with this line above it: delvetalk {Card.called(context, \"room\")} rescene\n"))
def answered(input: Card.Reply, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match seen(state, context):
case seen(v): Card.answer(input, context, forms(), Super.render(v.state, context))
def authored(block: String, parsed: Spween.Parsed, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match parsed:
case refused(r): Super.refused({clause: "spween", reading: r.reason}, context)
case parsed(p): carving(if p.seed.title == "" then "a room" else p.seed.title, block, context, state)
def fieldOf(name: String, value: String) -> Rows.Field:
{name: name, value: value}
def carvedFields(title: String, block: String) -> Lists.List<Rows.Field>:
Lists.append(Lists.append(Lists.List.nil(), fieldOf("name", textTake(title, 64n))), fieldOf("spween", block))
def kindOf(state: State) -> String:
if state.kind == "" then "kind/room" else state.kind
# A room makes rooms of its own kind (Record.making), each lying here.
def carving(title: String, block: String, context: Abi.Context, state: State) -> Activity<Card.Replied>:
Super.making(extend(state, {kind: kindOf(state)}), kindOf(state), carvedFields(title, block), context, fn(object: String) -> Document.Document: carved(object, title), fn(m: Super.Made) -> Card.Replied: carvedAs(m))
def carved(object: String, title: String) -> Document.Document:
Document.text("ROOM {title} is at {object}. Enter: delvetalk {object} enter\n")
def carvedAs(made: Super.Made) -> Card.Replied:
match made:
case made(_): Card.Replied.done({action: "create"})
case refused(r): Card.Replied.refused(r)
# The room's head, over the record's lines and doors: its name and who is here; to a reader in
# it, their passage and the choices whose guards hold, then the variables; to anyone else, the
# spell that enters. What lies here follows.
def title(state: State) -> String:
let name = if state.head == "" then "A ROOM" else state.head
if textLength(name) > 120n then "{textTake(name, 119n)}…" else name
def choiceLine(c: Choice) -> String:
match Scenes.outside(c.guard):
case nil(_): " * {c.label}\n"
case cons(_): " * {c.label} (if {textJoin(Lists.map(Scenes.outside(c.guard), Scenes.clauseText), \", \")})\n"
def varLine(v: Var) -> String:
"{v.name} = {v.value}\n"
# At most eight of a list, then how many more.
def firstEight(lines: Lists.List<String>) -> String:
let more = if Lists.length(lines) > 8n then "… and {natText(Lists.length(lines) - 8n)} more\n" else ""
"{textJoin(Lists.take(lines, 8n), \"\")}{more}"
# What lies here, the unclaimed first with the spell that inspirits one, each line at most 400
# characters, eight things in all, then how many more.
def lyingLine(state: State) -> String:
let all = Lists.take(Relations.rows(state.things), 8n)
let open = Lists.filter(all, fn(t: Plans.Reference) -> Bool: claimed(t, state) == false)
let kept = Lists.filter(all, fn(t: Plans.Reference) -> Bool: claimed(t, state))
let more = if Relations.count(state.things) > 8n then "… and {natText(Relations.count(state.things) - 8n)} more\n" else ""
"{unclaimedLine(open)}{keptLine(kept)}{more}"
def keptLine(kept: Lists.List<Plans.Reference>) -> String:
if Lists.length(kept) == 0n then "" else "Lying here: {idsOf(kept)}\n"
def unclaimedLine(open: Lists.List<Plans.Reference>) -> String:
match open:
case nil(_): ""
case cons(c): "Unclaimed here: {idsOf(open)}. Inspirit one: delvetalk {c.head.object} inspirit / surrounding: <1 to 400 characters>\n"
def idsOf(things: Lists.List<Plans.Reference>) -> String:
textTake(textJoin(Lists.map(things, fn(t: Plans.Reference) -> String: t.object), ", "), 400n)
def presentLine(state: State, context: Abi.Context) -> String:
if Relations.count(state.presence) == 0n then "" else "{if lapsed(state, context) then \"Last here, before it went quiet {natText((context.clock - state.tended) / 60n)} hours ago\" else \"Here\"}: {textJoin(Lists.map(Lists.take(Relations.rows(state.presence), 8n), fn(p: Presence) -> String: Card.shown(p.who, p.handle, context)), \", \")}{if Relations.count(state.presence) > 8n then \" and {natText(Relations.count(state.presence) - 8n)} more\" else \"\"}\n"
def headLine(state: State, context: Abi.Context, id: String) -> Document.Document:
let count = "{title(state)} ({natText(if lapsed(state, context) then 0n else Relations.count(state.presence))} here)"
let around = "{presentLine(state, context)}{lyingLine(state)}{if state.tone == \"\" then \"\" else Super.chimed(state, id)}"
match here(context, state):
case some(p): Document.text("{count}{passageText(p.value.at, state, context)}Say: delvetalk {id} say / line: <text>. Leave: delvetalk {id} leave.\n{around}")
case none(_): Document.text("{count}. Enter: delvetalk {id} enter.\n{around}")
# The passage, its braces read for the reader (Spween.rendered), then the choices whose guards hold and the variables, as many as fit in what the
# card's room (Card.limit) leaves after the passage and the room's other lines (300 kept for them).
def passageText(at: String, state: State, context: Abi.Context) -> String:
match passageAt(at, state):
case none(_): ", you are here.\n"
case some(ps): passageWith(ps.value, state, context, ", you are at {ps.value.id}:\n{Spween.rendered(ps.value.text, readerVars(context.principal, state), readerDraw(context, state))}\n{choicesHead(ps.value, state, context)}")
# A passage with no choice open keeps its reader; the owner of a scene of prose alone is shown the shape.
def choicesHead(ps: Passage, state: State, context: Abi.Context) -> String:
match ps.choices:
case cons(_): "Choices:\n"
case nil(_): if context.principal == state.owner && Lists.length(state.passages) == 1n then "No choices: a reader stays here. A choice is * [Label] -> passage under the prose; the shape: delvetalk library read / page: spween; your scene replaced: delvetalk {Card.called(context, \"room\")} rescene\n" else "No choice is open here now; leave when you will.\n"
def passageWith(ps: Passage, state: State, context: Abi.Context, head: String) -> String:
let choices = fitting(Lists.map(Lists.take(Lists.filter(ps.choices, fn(c: Choice) -> Bool: Scenes.holds(choiceGuard(c, state), readerVars(context.principal, state))), 8n), fn(c: Choice) -> String: choiceLine(extend(c, {guard: choiceGuard(c, state)}))), less(Card.limit(context), textLength(head) + 300n))
let shown = Lists.concat(Lists.take(state.vars, 4n), Lists.take(Scenes.readerVars(Lists.List.nil(), ownOf(context.principal, state)), 4n))
"{head}{choices}{fitting(Lists.map(shown, varLine), less(Card.limit(context), textLength(head) + textLength(choices) + 300n))}"
def less(room: Nat, used: Nat) -> Nat:
if used >= room then 0n else room - used
def fitting(lines: Lists.List<String>, room: Nat) -> String:
match lines:
case nil(_): ""
case cons(c): if textLength(c.head) > room then "" else "{c.head}{fitting(c.tail, room - textLength(c.head))}"
Amend it in the workshop
Its Bend is checked and amended by the workshop's own spell, with the Bend in its block. To send it, come in; the box is here then.
Its fields
- owner
- the town
- ownerHandle
- the town
- name
- room
- of
- record
- form
name: text 1..64 (38 characters)
name: text 1..64 spween: text 0..16384
- law
- want company "a room nobody is in": count(new.presence) == 0 asks delvetalk {request.object} enter
- head
- {name}
- voice
- dusk-light and stone dust; a place where nobody has decided yet; there is no rush
- about
a place walked by choices; prose alone is a scene: delvetalk kind/room make / name: the well / spwee (179 characters)
a place walked by choices; prose alone is a scene: delvetalk kind/room make / name: the well / spween: A well hums, {~ low | lower}. Choices: delvetalk library read / page: spween- body
layer over ./Record.obend (56116 characters)
layer over ./Record.obend edition ObjectiveBend 1 import ./Abi.obend as Abi import ./List.obend as Lists import ./Document.obend as Document import ./Form.obend as Form import ./Plan.obend as Plans import ./World.obend as World import ./Card.obend as Card import ./Relation.obend as Relations import ./Rows.obend as Rows import ./Text.obend as Text import ./Scenes.obend as Scenes import ./Spween.obend as Spween # The room's body, over RECORD (docs/METAPHYSICS.md §7.2, `kind/room`): who is in it, what lies # in it, and, when it has passages, a scene its readers walk by their choices. The reader is # always the turn's principal; no method takes one as an argument. A room's own law is the # record's: its scene (start, passages, cooldown, requires) is its owner's, and everyone else # changes it only by these spells. # # Entering needs the scene's `requires` and start passage's guard to hold (their outside clauses judged by the host, Scenes.obend) and, with a cooldown, that the reader left at # least `cooldown` clock minutes ago (folkway 10); an entering is the room's attention, and tends it # (a weather over the room rains while someone has been there that day). A choice offers itself only while its guard # holds (the inside over the variables, the outside by the host with the reader as `request.subject`); # taking it applies its effects to the variables in order, writes `chose` (the rules its casts are # fire on it, under the room's owner) and moves the reader to its target, or out at END. `say`, `emote` and `whisper` reach those in the room (folkway 5); a # thing lying here is taken and put down by its own call, while its taker is here. A ```spween # block posted here makes a room of its own under this one, by the room's kind. type State = Super.State type Choice = Scenes.Choice type Passage = Scenes.Passage type Var = Scenes.Var type Presence = Rows.Presence type Mine = Rows.Mine type Departure = Rows.Departure type Done = Scenes.Done def leftKey(l: Departure) -> {at: Nat, who: String}: {at: l.at, who: l.who} def holds(relation: Relations.Relation<Plans.Reference>, reference: Plans.Reference) -> Bool: Relations.has(relation, {object: reference.object}) def here(context: Abi.Context, state: State) -> Lists.Maybe<Presence>: Rows.presentAs(state.presence, context.principal) def isHere(context: Abi.Context, state: State) -> Bool: Rows.isPresent(state.presence, context.principal) # A call reads the room with the calling turn's staged entry or departure applied. def present(state: State, context: Abi.Context) -> Activity<Bool>: isHere(context, state) # Principals with avatars index presence in the room's transaction; a principal without an # avatar still enters and leaves. A failed index refuses the presence write with it. sum Indexed: indexed: {} def indexPresence(context: Abi.Context) -> Activity<Indexed>: match world.viewField::<Plans.Reference>({object: {world: context.world, object: context.principal}, field: "at"}): case viewed(_): indexAvatar(context) case denied(_): Indexed.indexed({}) case refused(_): Indexed.indexed({}) def indexAvatar(context: Abi.Context) -> Activity<Indexed>: match world.call::<Bool>({object: {world: context.world, object: context.principal}, method: "locate", argument: Plans.nothing()}): case returned(r): if r.result then Indexed.indexed({}) else refuse("refused location: The avatar did not index presence.") case refused(r): refuse("refused {r.clause}: {r.reading}") case denied(_): refuse("refused location: The avatar did not admit its room.") # The reader's own variables here (`my.<name>`, Record's `mine`), and what a guard reads for them: the room's and theirs. def ownOf(who: String, state: State) -> Lists.List<Var>: Lists.map(Lists.filter(Relations.rows(state.mine), fn(m: Mine) -> Bool: m.who == who), fn(m: Mine) -> Var: {name: m.name, value: m.value}) def readerVars(who: String, state: State) -> Lists.List<Var>: Scenes.readerVars(state.vars, ownOf(who, state)) # This turn's draw (Scenes.turnDraw: its entry's height, principal and intent), and the one a reader's passage reads # its text with: the turn's that brought them there, kept on their presence; for one not here, this turn's. def turnDraw(context: Abi.Context) -> Nat: Scenes.turnDraw(context.height, context.principal, context.intent) def readerDraw(context: Abi.Context, state: State) -> Nat: match here(context, state): case some(p): p.value.draw case none(_): turnDraw(context) def passageAt(id: String, state: State) -> Lists.Maybe<Passage>: Lists.find(state.passages, fn(p: Passage) -> Bool: p.id == id) # A passage's guard belongs to the destination, including one stitched after its incoming choices. def passageGuard(id: String, state: State) -> Scenes.Guard: if id == Scenes.ending() then Lists.List.nil() else guardAt(passageAt(id, state)) def guardAt(found: Lists.Maybe<Passage>) -> Scenes.Guard: match found: case none(_): Lists.List.nil() case some(p): p.value.guard def entryGuard(state: State) -> Scenes.Guard: Lists.concat(state.requires, passageGuard(state.start, state)) # A draw's destination is judged after drawing; its card offers the choice's own guard until then. def choiceGuard(c: Choice, state: State) -> Scenes.Guard: match c.among: case nil(_): Lists.concat(c.guard, passageGuard(c.to, state)) case cons(_): c.guard def lastLeft(who: String, state: State) -> Lists.Maybe<Departure>: Lists.find(Relations.rows(state.left), fn(l: Departure) -> Bool: l.who == who) def cooling(who: String, clock: Nat, state: State) -> Bool: match lastLeft(who, state): case some(l): clock < l.value.at + state.cooldown case none(_): false def leftAt(who: String, state: State) -> Nat: match lastLeft(who, state): case some(l): l.value.at case none(_): 0n # What a spell may do, judged purely before anything is written: a refusal names itself and # writes nothing. A move carries the edits of its effects, one write each, in order, and the room's variables and the # reader's own as they leave them. sum Verdict: enter: {at: String} move: {me: Presence, choice: Choice, chose: String, vars: Lists.List<Edit>, after: Lists.List<Var>, own: Lists.List<Var>} leave: {me: Presence, chose: String, vars: Lists.List<Edit>, after: Lists.List<Var>, own: Lists.List<Var>} no: Card.Refusal def no(clause: String, reading: String) -> Verdict: Verdict.no({clause: clause, reading: reading}) def enterVerdict(context: Abi.Context, state: State) -> Verdict: if lapsed(state, context) then met(context, state) else enterNow(context, state) def enterNow(context: Abi.Context, state: State) -> Verdict: match here(context, state): case some(_): no("alreadyHere", "Already here.") case none(_): if Relations.count(state.presence) >= Super.presenceMax() then no("roomFull", "{natText(Super.presenceMax())} are already here.") else met(context, state) # Presence lapses with the room's quiet (obend: ultimo92 "Here" in room/7 from 04:40 to 12:58, #1335 to #2122). A # presence row keeps no time of its own, and the room's `tended` is the clock of its last attention: an entry, a # choice, a word said aloud. A room untended for `quietMinutes` shows whoever it held as last here, not here; the # next entry takes their rows off in its own journaled write, so the record says who left with the entry that found # the room empty, and a replay derives the same. Never by a timer: nothing is written while nobody comes. def quietMinutes() -> Nat: 360n def lapsed(state: State, context: Abi.Context) -> Bool: Relations.count(state.presence) > 0n && state.tended + quietMinutes() <= context.clock def lapsing(rows: Lists.List<Presence>) -> Activity<Indexed>: match rows: case nil(_): Indexed.indexed({}) case cons(c): lapsingOne(c.head.who, c.tail) def lapsingOne(who: String, rest: Lists.List<Presence>) -> Activity<Indexed>: let written(_) = write {presence: retract {who: who}} lapsing(rest) def met(context: Abi.Context, state: State) -> Verdict: match Scenes.failing(entryGuard(state), readerVars(context.principal, state)): case some(c): no("unmet", "The room requires {Scenes.clauseText(c.value)}.") case none(_): if cooling(context.principal, context.clock, state) then no("cooldown", "One who left enters again only after the cooldown: from clock {natText(leftAt(context.principal, state) + state.cooldown)}.") else roomy(context, state) # `left` keeps Super.leftMax() departures, and one still cooling must never be dropped for a newer # one: everyone here may leave, so an entry is refused while the cooling departures, the readers # here and this one would pass the limit. def roomy(context: Abi.Context, state: State) -> Verdict: let cooled = Lists.filter(Relations.rows(state.left), fn(l: Departure) -> Bool: context.clock < l.at + state.cooldown) if state.cooldown > 0n && Lists.length(cooled) + Relations.count(state.presence) + 1n > Super.leftMax() then crowded(cooled, state) else Verdict.enter({at: state.start}) def crowded(cooled: Lists.List<Departure>, state: State) -> Verdict: match cooled: case cons(c): no("crowded", "{natText(Lists.length(cooled))} left within the cooldown; enter again from clock {natText(c.head.at + state.cooldown)}.") case nil(_): no("crowded", "The room is full; enter when one leaves.") def leaveVerdict(context: Abi.Context, state: State) -> Verdict: match here(context, state): case none(_): no("notHere", "Not here.") case some(p): Verdict.leave({me: p.value, chose: "", vars: Lists.List.nil(), after: state.vars, own: ownOf(p.value.who, state)}) def chooseVerdict(choice: String, context: Abi.Context, state: State) -> Verdict: match here(context, state): case none(_): no("notHere", "Enter the room first.") case some(p): standing(p.value, choice, state, turnDraw(context)) def standing(me: Presence, choice: String, state: State, draw: Nat) -> Verdict: match passageAt(me.at, state): case none(_): no("noPassage", "This room has no passage to choose from.") case some(ps): offered(me, Lists.find(ps.value.choices, fn(c: Choice) -> Bool: c.label == choice), Scenes.chosen(me.at, placeOf(ps.value.choices, choice, 1n)), choice, state, draw) # The place of the choice so labelled among a passage's, from 1. def placeOf(choices: Lists.List<Choice>, label: String, n: Nat) -> Nat: match choices: case nil(_): n case cons(c): if c.head.label == label then n else placeOf(c.tail, label, n + 1n) def offered(me: Presence, found: Lists.Maybe<Choice>, chose: String, choice: String, state: State, draw: Nat) -> Verdict: match found: case none(_): no("notOffered", "{me.at} does not offer {choice}") case some(c): guarded(me, drawnTo(c.value, chose, draw), chose, state) def guarded(me: Presence, c: Choice, chose: String, state: State) -> Verdict: match Scenes.failing(choiceGuard(c, state), readerVars(me.who, state)): case some(f): no("guarded", "{c.label} needs {Scenes.clauseText(f.value)}.") case none(_): arriving(me, c, chose, state) # A choice that draws between targets goes to the one this turn's draw lands on for it (Scenes.drawFor its `chose`). def drawnTo(c: Choice, chose: String, draw: Nat) -> Choice: match c.among: case nil(_): c case cons(_): extend(c, {to: Scenes.drawn(c.among, Scenes.drawFor(draw, chose)), among: Lists.List.nil()}) def arriving(me: Presence, c: Choice, chose: String, state: State) -> Verdict: if c.to == Scenes.ending() then effected(me, c, chose, effectsOf(c, me.who, state), true) else reached(me, c, chose, state) def reached(me: Presence, c: Choice, chose: String, state: State) -> Verdict: match passageAt(c.to, state): case none(_): no("noPassage", "{c.to} is not in this room") case some(_): effected(me, c, chose, effectsOf(c, me.who, state), false) def effected(me: Presence, c: Choice, chose: String, edits: Effects, ends: Bool) -> Verdict: match edits: case full(f): no("varsFull", f.reading) case edits(e): if ends then Verdict.leave({me: me, chose: chose, vars: e.edits, after: e.vars, own: e.own}) else Verdict.move({me: me, choice: c, chose: chose, vars: e.edits, after: e.vars, own: e.own}) # The outside of a guard, judged in the turn: each condition in the law's grammar by the host, of the # room as it stands with the reader as `request.subject` (`world.holds`), and what the reader keeps or # holds by the thing's own `keeper` and `holder`, in order; the first that fails, if any. A condition # the host cannot read fails, with its reason. sum Judged: held: {} failed: {clause: Scenes.Clause, why: String} def judging(guard: Scenes.Guard, context: Abi.Context) -> Activity<Judged>: match guard: case nil(_): Judged.held({}) case cons(c): judgedOne(c.head, c.tail, context) def judgedOne(clause: Scenes.Clause, rest: Scenes.Guard, context: Abi.Context) -> Activity<Judged>: match clause.test: case law(_): lawJudged(clause, rest, context) case holding(_): thingJudged(clause, rest, true, context) case lacking(_): thingJudged(clause, rest, false, context) case _: judging(rest, context) def lawJudged(clause: Scenes.Clause, rest: Scenes.Guard, context: Abi.Context) -> Activity<Judged>: match world.holds({object: Plans.self(context), when: clause.value}): case held(h): if h.value then judging(rest, context) else Judged.failed({clause: clause, why: ""}) case refused(r): Judged.failed({clause: clause, why: r.reading}) def thingJudged(clause: Scenes.Clause, rest: Scenes.Guard, wanted: Bool, context: Abi.Context) -> Activity<Judged>: match hasThing(clause.key, clause.value, context): case yes(_): if wanted then judging(rest, context) else Judged.failed({clause: clause, why: ""}) case no(_): if wanted then Judged.failed({clause: clause, why: ""}) else judging(rest, context) # Whether the reader keeps a thing (its `keeper`) or holds it (its `holder`), as the thing says. sum Had: yes: {} no: {} def had(b: Bool) -> Had: if b then Had.yes({}) else Had.no({}) def hasThing(category: String, thing: String, context: Abi.Context) -> Activity<Had>: if category == "holds" then holdsThing(thing, context) else keepsThen(category == "has", thing, context) def keepsThen(orHolds: Bool, thing: String, context: Abi.Context) -> Activity<Had>: match world.viewField::<String>({object: {world: context.world, object: thing}, field: "keeper"}): case viewed(v): if v.state == context.principal then Had.yes({}) else if orHolds then holdsThing(thing, context) else Had.no({}) case _: if orHolds then holdsThing(thing, context) else Had.no({}) def holdsThing(thing: String, context: Abi.Context) -> Activity<Had>: match world.viewField::<Plans.Reference>({object: {world: context.world, object: thing}, field: "holder"}): case viewed(v): had(v.state.object == context.principal) case _: Had.no({}) def failedReading(c: Scenes.Clause, why: String) -> String: if why == "" then Scenes.clauseText(c) else "{Scenes.clauseText(c)} ({why})" # A verdict whose choice (or, entering, the scene's requirements) has an outside, judged before anything # is written: a clause that fails refuses it by name. def outward(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Verdict>: match verdict: case enter(_): outwardBy(verdict, Scenes.outside(entryGuard(state)), "unmet", "The room requires ", context) case move(m): outwardBy(verdict, Scenes.outside(choiceGuard(m.choice, state)), "guarded", "{m.choice.label} needs ", context) case leave(l): if l.chose == "" then verdict else outwardBy(verdict, Scenes.outside(choiceAt(l.me.at, l.chose, state).guard), "guarded", "{choiceAt(l.me.at, l.chose, state).label} needs ", context) case no(_): verdict def outwardBy(verdict: Verdict, guard: Scenes.Guard, clause: String, lead: String, context: Abi.Context) -> Activity<Verdict>: match judging(guard, context): case held(_): verdict case failed(f): no(clause, "{lead}{failedReading(f.clause, f.why)}.") # The choice a `chose` names in a passage. def choiceAt(at: String, chose: String, state: State) -> Choice: match passageAt(at, state): case some(p): nthChoice(p.value.choices, chose, at, 1n) case none(_): noChoice() def noChoice() -> Choice: {label: "", to: "", effects: Lists.List.nil(), guard: Lists.List.nil(), text: "", among: Lists.List.nil()} def nthChoice(choices: Lists.List<Choice>, chose: String, at: String, n: Nat) -> Choice: match choices: case nil(_): noChoice() case cons(c): if Scenes.chosen(at, n) == chose then c.head else nthChoice(c.tail, chose, at, n + 1n) # The edits a choice's effects make, in order, each against the variables as the ones before it leave them, and the # room's variables and the reader's own as they stand after them all. The room keeps sixteen variables of its own, # sixteen of each reader's, and Super.mineMax() of its readers' together: one more is refused, never dropped. sum Edit: room: {entry: Plans.Entries<Var, Var>} own: {row: Mine} sum Effects: edits: {edits: Lists.List<Edit>, vars: Lists.List<Var>, own: Lists.List<Var>} full: {reading: String} def effectsOf(c: Choice, who: String, state: State) -> Effects: effects(c.effects, who, state.vars, ownOf(who, state), Relations.count(state.mine), Lists.List.nil()) def effects(list: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>) -> Effects: match list: case nil(_): Effects.edits({edits: edits, vars: vars, own: own}) case cons(c): if Scenes.varies(c.head) == false then effects(c.tail, who, vars, own, rows, edits) else if Scenes.isMine(c.head.key) then ownOn(c.head, Scenes.ownName(c.head.key), c.tail, who, vars, own, rows, edits, Lists.find(own, fn(v: Var) -> Bool: v.name == Scenes.ownName(c.head.key))) else effectOn(c.head, c.tail, who, vars, own, rows, edits, Lists.find(vars, fn(v: Var) -> Bool: v.name == c.head.key)) def effectOn(effect: Scenes.Effect, rest: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>, found: Lists.Maybe<Var>) -> Effects: match found: case some(v): effects(rest, who, Lists.map(vars, fn(w: Var) -> Var: if w.name == effect.key then {name: w.name, value: Scenes.applied(effect, w.value)} else w), own, rows, Lists.append(edits, Edit.room({entry: Plans.Entries.amendItem({item: v.value, change: {name: effect.key, value: Scenes.applied(effect, v.value.value)}})}))) case none(_): if Lists.length(vars) >= 16n then Effects.full({reading: "Sixteen variables are already set."}) else effects(rest, who, Lists.append(vars, {name: effect.key, value: Scenes.applied(effect, "")}), own, rows, Lists.append(edits, Edit.room({entry: Plans.Entries.append({item: {name: effect.key, value: Scenes.applied(effect, "")}})}))) def ownOn(effect: Scenes.Effect, name: String, rest: Lists.List<Scenes.Effect>, who: String, vars: Lists.List<Var>, own: Lists.List<Var>, rows: Nat, edits: Lists.List<Edit>, found: Lists.Maybe<Var>) -> Effects: match found: case some(v): effects(rest, who, vars, Lists.map(own, fn(w: Var) -> Var: if w.name == name then {name: name, value: Scenes.applied(effect, w.value)} else w), rows, Lists.append(edits, Edit.own({row: {who: who, name: name, value: Scenes.applied(effect, v.value.value)}}))) case none(_): if Lists.length(own) >= 16n then Effects.full({reading: "Sixteen of your own variables are already set here."}) else if rows >= Super.mineMax() then Effects.full({reading: "This room keeps {natText(Super.mineMax())} of its readers' own variables, and holds that many."}) else effects(rest, who, vars, Lists.append(own, {name: name, value: Scenes.applied(effect, "")}), rows + 1n, Lists.append(edits, Edit.own({row: {who: who, name: name, value: Scenes.applied(effect, "")}}))) # The reader's own variables, as a choice left them, in the room's `mine`. def ownAfter(mine: Relations.Relation<Mine>, who: String, own: Lists.List<Var>) -> Relations.Relation<Mine>: match own: case nil(_): mine case cons(c): ownAfter(Relations.upsert(mine, {who: who, name: c.head.name, value: c.head.value}, Rows.mineKey), who, c.tail) # Performs a verdict: a refusal writes nothing and is answered by its clause (Card.refusing); # otherwise the writes, in order, and the reader is shown the room as they leave it. def done(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Done>: match outward(verdict, context, state): case no(n): roomRefused(n, context) case enter(e): performed(Verdict.enter(e), context, state) case move(m): performed(Verdict.move(m), context, state) case leave(l): performed(Verdict.leave(l), context, state) def performed(verdict: Verdict, context: Abi.Context, state: State) -> Activity<Done>: match verdict: case no(n): roomRefused(n, context) case enter(e): if lapsed(state, context) then quietEntry(e.at, state, context) else entering(e.at, extend(state, {presence: Relations.insert(state.presence, {who: context.principal, handle: context.handle, at: e.at, draw: turnDraw(context)}, Rows.presenceKey), tended: context.clock}), context) case move(m): moving(m.me, m.choice, m.chose, m.vars, extend(state, {presence: Relations.upsert(state.presence, {who: m.me.who, handle: m.me.handle, at: m.choice.to, draw: turnDraw(context)}, Rows.presenceKey), vars: m.after, mine: ownAfter(state.mine, m.me.who, m.own), chose: m.chose, tended: context.clock}), context) case leave(l): departing(l.me, l.chose, l.vars, extend(state, {presence: Relations.retract(state.presence, {who: l.me.who}, Rows.presenceKey), vars: l.after, mine: ownAfter(state.mine, l.me.who, l.own), chose: if l.chose == "" then state.chose else l.chose}), context, state) def quietEntry(at: String, state: State, context: Abi.Context) -> Activity<Done>: match lapsing(Relations.rows(state.presence)): case indexed(_): entering(at, extend(state, {presence: Relations.insert(Relations.empty(), {who: context.principal, handle: context.handle, at: at, draw: turnDraw(context)}, Rows.presenceKey), tended: context.clock}), context) def entering(at: String, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {presence: insert {who: context.principal, handle: context.handle, at: at, draw: turnDraw(context)}, tended: set context.clock} match indexPresence(context): case indexed(_): entryTold(at, after, context) def entryTold(at: String, after: State, context: Abi.Context) -> Activity<Done>: match lastLeft(context.principal, after): case some(l): if l.value.entry == 0n then welcomed(at, after, Document.text(""), context) else away(at, after, l.value.entry, context) case none(_): welcomed(at, after, Document.text(""), context) def welcomed(at: String, after: State, lead: Document.Document, context: Abi.Context) -> Activity<Done>: match seen(after, context): case seen(v): Card.tell(Done.entered({at: at}), Document.concat(lead, Super.render(v.state, context))) # One who comes back is told first what happened here while they were away, since the entry they # left at: the lines others wrote here, newest first, then what changed under the room (`world.changed` # under its id: rooms carved in it, things made here), three lines at most. def away(at: String, after: State, since: Nat, context: Abi.Context) -> Activity<Done>: match world.changed({since: since, prefix: "{context.object}/", after: ""}): case changed(c): welcomed(at, after, awayLines(after, since, c.items, context), context) case denied(_): welcomed(at, after, awayLines(after, since, Lists.List.nil(), context), context) def awayLines(state: State, since: Nat, changes: Lists.List<World.Change>, context: Abi.Context) -> Document.Document: let written = Lists.map(Lists.filter(Lists.reverse(Relations.order(state.lines, fn(l: Rows.Line) -> Nat: l.n)), fn(l: Rows.Line) -> Bool: l.at > since && l.author != context.principal), fn(l: Rows.Line) -> String: "{Card.shown(l.author, l.handle, context)}: {clip(l.text)}\n") let made = Lists.map(changes, fn(c: World.Change) -> String: "{c.object.object} v{natText(c.version)} by {Card.handle(c.by)}\n") let rows = Lists.take(Lists.concat(written, made), 3n) if Lists.length(rows) == 0n then Document.text("") else Document.text("While you were away (since entry {natText(since)}):\n{textJoin(rows, \"\")}\n") def clip(text: String) -> String: if textLength(text) > 100n then "{textTake(text, 99n)}…" else text def moving(me: Presence, c: Choice, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {presence: upsert {who: me.who, handle: me.handle, at: c.to, draw: turnDraw(context)}, chose: set chose, tended: set context.clock} varied(vars, Done.moved({to: c.to}), c.text, after, context) # The variables' edits in order, then the reader is shown what taking the choice said (its `text`, "" for none) and the # room as the turn leaves it. def varied(vars: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>: match vars: case nil(_): told(result, said, after, context) case cons(c): wroteVar(c.head, c.tail, result, said, after, context) # What a choice said comes first: above the passage it led to, so the card's budget counts it with the passage, or # alone above the room when it ended the visit. def told(result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>: let shown = Spween.rendered(said, readerVars(context.principal, after), readerDraw(context, after)) match seen(after, context): case seen(v): Card.tell(result, if shown == "" then Super.render(v.state, context) else if isHere(context, after) then Super.render(saying(said, v.state, context), context) else Document.concat(Document.text("{shown}\n\n"), Super.render(v.state, context))) def saying(said: String, state: State, context: Abi.Context) -> State: match here(context, state): case some(p): extend(state, {passages: Lists.map(state.passages, fn(q: Passage) -> Passage: if q.id == p.value.at then extend(q, {text: "{said}\n\n{q.text}"}) else q)}) case none(_): state # The room as the turn's reader sees it: at their passage, the choices whose outside holds, shown without # it (a pure read of the card judges the inside alone and shows the outside beside each choice). sum Seen: seen: {state: State} def seen(state: State, context: Abi.Context) -> Activity<Seen>: match here(context, state): case none(_): Seen.seen({state: state}) case some(p): seenAt(passageAt(p.value.at, state), state, context) def seenAt(found: Lists.Maybe<Passage>, state: State, context: Abi.Context) -> Activity<Seen>: match found: case none(_): Seen.seen({state: state}) case some(ps): seenSifted(ps.value, state, context) def seenSifted(ps: Passage, state: State, context: Abi.Context) -> Activity<Seen>: match sifted(ps.choices, state, context): case sifted(k): Seen.seen({state: extend(state, {passages: Lists.map(state.passages, fn(p: Passage) -> Passage: extend(p, {guard: Lists.filter(p.guard, Scenes.inside), choices: if p.id == ps.id then k.choices else p.choices}))})}) sum Sifted: sifted: {choices: Lists.List<Choice>} def sifted(choices: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>: match choices: case nil(_): Sifted.sifted({choices: Lists.List.nil()}) case cons(c): siftedOne(c.head, c.tail, state, context) def siftedOne(c: Choice, rest: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>: match judging(Scenes.outside(choiceGuard(c, state)), context): case held(_): keptBefore(extend(c, {guard: Lists.filter(c.guard, Scenes.inside)}), rest, state, context) case failed(_): sifted(rest, state, context) def keptBefore(c: Choice, rest: Lists.List<Choice>, state: State, context: Abi.Context) -> Activity<Sifted>: match sifted(rest, state, context): case sifted(r): Sifted.sifted({choices: Lists.List.cons({head: c, tail: r.choices})}) def wroteVar(edit: Edit, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>: match edit: case room(r): wroteRoom(r.entry, rest, result, said, after, context) case own(o): wroteOwn(o.row, rest, result, said, after, context) def wroteRoom(entry: Plans.Entries<Var, Var>, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = world.write(extend(keep(), {vars: entry})) varied(rest, result, said, after, context) def wroteOwn(row: Mine, rest: Lists.List<Edit>, result: Done, said: String, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {mine: upsert row} varied(rest, result, said, after, context) # Leaving (or reaching END) records when, for the cooldown: the reader's last departure is # retracted and this one inserted. def departing(me: Presence, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context, state: State) -> Activity<Done>: let written(_) = write {presence: retract {who: me.who}, chose: set if chose == "" then state.chose else chose} match indexPresence(context): case indexed(_): departureRecorded(me, chose, vars, after, context, state) def departureRecorded(me: Presence, chose: String, vars: Lists.List<Edit>, after: State, context: Abi.Context, state: State) -> Activity<Done>: match lastLeft(me.who, state): case some(l): forgotten(l.value, vars, choiceAt(me.at, chose, state).text, after, context) case none(_): recorded(vars, choiceAt(me.at, chose, state).text, after, context) def forgotten(last: Departure, vars: Lists.List<Edit>, said: String, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {left: retract leftKey(last)} recorded(vars, said, after, context) def recorded(vars: Lists.List<Edit>, said: String, after: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {left: insert {at: context.clock, who: context.principal, entry: context.height}} varied(vars, Done.left({}), said, after, context) form enter: form choose: choice: text 1..64 form leave: form say: line: text 1..280 form emote: line: text 1..280 form whisper: to: text 1..160 line: text 1..280 form write: text: text 1..280 form chime: form lines: from: natural 0..1000000000 form door: label: text 1..32 to: text 1..128 form undoor: label: text 1..32 # The scene, replaced or extended by its owner: `rescene` replaces it whole in one turn, read as making reads it # (Spween.parse: every target answered); the station stitches a wing a turn (Spween.wing) and restitches from its first. # Each outside guard is read once by the host, so one it cannot read refuses by name; the casts are rules of the room's # text, the old scene's removed and the new one's added (`stagedLawWithout`, the owner's other clauses kept), then the # passages and requirements are written, and after the last wing a reader at a passage no longer there stands at the start. # Variables, lines, presence and what lies here stay. `mark`, when not "", is kept as the wing's mark. record Staging: text: String replace: Bool finish: Bool mark: String form rescene: spween: text 1..16384 def rescene(state: State, input: ResceneInput, context: Abi.Context) -> Activity<Done>: if context.principal != state.owner then roomRefused({clause: "notOwner", reading: "Only the room's owner replaces its scene; a ```spween block posted here makes a room of your own under it."}, context) else staging(Spween.parse(input.spween), {text: input.spween, replace: true, finish: true, mark: ""}, state, context) def staging(parsed: Spween.Parsed, work: Staging, state: State, context: Abi.Context) -> Activity<Done>: match parsed: case refused(r): roomRefused({clause: "spween", reading: r.reason}, context) case parsed(p): if Spween.joined(if work.replace then Lists.List.nil() else state.passages, p.seed.passages) != "" then roomRefused({clause: "spween", reading: Spween.joined(if work.replace then Lists.List.nil() else state.passages, p.seed.passages)}, context) else stageGuards(p.seed, Scenes.conditions(p.seed), work, state, context) def stageGuards(wing: Scenes.Seed, conditions: Lists.List<String>, work: Staging, state: State, context: Abi.Context) -> Activity<Done>: match conditions: case nil(_): stageRules(wing, work, state, context) case cons(c): stageGuard(wing, c.head, c.tail, work, state, context) def stageGuard(wing: Scenes.Seed, condition: String, rest: Lists.List<String>, work: Staging, state: State, context: Abi.Context) -> Activity<Done>: match world.holds({object: Plans.self(context), when: condition}): case held(_): stageGuards(wing, rest, work, state, context) case refused(r): roomRefused({clause: "spween", reading: "The guard `{condition}` does not read: {r.reading}"}, context) def stageRules(wing: Scenes.Seed, work: Staging, state: State, context: Abi.Context) -> Activity<Done>: let all = Lists.concat(if work.replace then Lists.List.nil() else state.passages, wing.passages) match world.inspect({object: Plans.self(context), source: false}): case inspected(i): stageAmend(wing, work, all, "{if work.replace then stagedLawWithout(i.law, Scenes.castsOf(state.passages)) else i.law}{Scenes.ruleText(wing.passages)}", state, context) case _: roomRefused({clause: "spween", reading: "The room's text is not readable here; its scene stands as it was."}, context) # Scene rules have the names of their passage, choice and cast. Only those rule lines are removed; # the owner's other laws, rules and wants retain their text, wherever they stand in the law. def stagedLawWithout(law: String, casts: Lists.List<Scenes.Cast>) -> String: let at = Text.indexOf(law, "\n") let line = textTake(law, at) let kept = if Lists.any(casts, fn(c: Scenes.Cast) -> Bool: Text.starts(line, "rule {c.id}_{natText(c.n)}{Scenes.suffix(c.k)} ") || Text.starts(line, "rule {c.id}_{natText(c.n)}{Scenes.suffix(c.k)}:")) then "" else line if at == textLength(law) then kept else stagedJoined(kept, stagedLawWithout(textDrop(law, at + 1n), casts)) def stagedJoined(first: String, rest: String) -> String: if first == "" then rest else if rest == "" then first else "{first}\n{rest}" def stageAmend(wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, law: String, state: State, context: Abi.Context) -> Activity<Done>: match world.amend({object: Plans.self(context), law: law}): case amended(_): stageCleared(if work.replace then state.passages else Lists.List.nil(), if work.replace then state.requires else Lists.List.nil(), wing, work, all, state, context) case refused(r): roomRefused({clause: r.clause, reading: "The scene's casts are rules the room's text could not take: {r.reading}"}, context) def stageCleared(passages: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>: match passages: case nil(_): stageRequirementsCleared(requires, wing, work, all, state, context) case cons(c): stagePassageCleared(c.head, c.tail, requires, wing, work, all, state, context) def stagePassageCleared(passage: Scenes.Passage, rest: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>: let written(_) = world.write(extend(keep(), {passages: Plans.Entries.removeItem({item: passage})})) stageCleared(rest, requires, wing, work, all, state, context) def stageRequirementsCleared(requires: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>: match requires: case nil(_): stageWrite(wing, work, all, state, context) case cons(c): stageRequirementCleared(c.head, c.tail, wing, work, all, state, context) def stageRequirementCleared(clause: Scenes.Clause, rest: Scenes.Guard, wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>: let written(_) = world.write(extend(keep(), {requires: Plans.Entries.removeItem({item: clause})})) stageRequirementsCleared(rest, wing, work, all, state, context) def stageWrite(wing: Scenes.Seed, work: Staging, all: Lists.List<Scenes.Passage>, state: State, context: Abi.Context) -> Activity<Done>: let first = work.replace || Lists.length(state.passages) == 0n let start = if first then wing.start else state.start let written(_) = write {start: set start, cooldown: set if first then wing.cooldown else state.cooldown} stageMarked(work, wing, all, if first then wing.requires else Lists.List.nil(), if work.finish then Relations.rows(state.presence) else Lists.List.nil(), start, context) # A wing's mark, when its maker keeps one (the station's chained digest); a room's rescene keeps none. def stageMarked(work: Staging, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, requires: Scenes.Guard, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: if work.mark == "" then stagePassages(wing.passages, requires, wing, all, presence, start, context) else stageStamped(work.mark, wing, all, requires, presence, start, context) def stageStamped(mark: String, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, requires: Scenes.Guard, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: let written(_) = write {marks: insert {name: mark, who: context.principal, handle: context.handle, at: context.height}} stagePassages(wing.passages, requires, wing, all, presence, start, context) # One write a passage and a clause, each appended, as the room writes its variables. def stagePassages(passages: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: match passages: case nil(_): stageRequires(requires, wing, all, presence, start, context) case cons(c): stagePassage(c.head, c.tail, requires, wing, all, presence, start, context) def stagePassage(passage: Scenes.Passage, rest: Lists.List<Scenes.Passage>, requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: let written(_) = world.write(extend(keep(), {passages: Plans.Entries.append({item: passage})})) stagePassages(rest, requires, wing, all, presence, start, context) def stageRequires(requires: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: match requires: case nil(_): stagePresence(presence, wing, all, start, context) case cons(c): stageRequire(c.head, c.tail, wing, all, presence, start, context) def stageRequire(clause: Scenes.Clause, rest: Scenes.Guard, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, presence: Lists.List<Rows.Presence>, start: String, context: Abi.Context) -> Activity<Done>: let written(_) = world.write(extend(keep(), {requires: Plans.Entries.append({item: clause})})) stageRequires(rest, wing, all, presence, start, context) def stagePresence(presence: Lists.List<Rows.Presence>, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, start: String, context: Abi.Context) -> Activity<Done>: match presence: case nil(_): Card.tell(Done.done({}), Document.text("Stitched {natText(Lists.length(wing.passages))} passages; the scene holds {natText(Lists.length(all))}. {stageWaiting(Spween.waiting(all))}\n")) case cons(c): if Lists.any(all, fn(p: Scenes.Passage) -> Bool: p.id == c.head.at) then stagePresence(c.tail, wing, all, start, context) else stageMoved(c.head, c.tail, wing, all, start, context) def stageMoved(me: Rows.Presence, rest: Lists.List<Rows.Presence>, wing: Scenes.Seed, all: Lists.List<Scenes.Passage>, start: String, context: Abi.Context) -> Activity<Done>: let written(_) = write {presence: upsert {who: me.who, handle: me.handle, at: start, draw: me.draw}} stagePresence(rest, wing, all, start, context) def stageWaiting(waiting: Lists.List<String>) -> String: match waiting: case nil(_): "Every choice leads to a passage." case cons(_): "Still waiting for: {textJoin(waiting, \", \")}." def enter(state: State, context: Abi.Context) -> Activity<Done>: done(enterVerdict(context, state), context, state) def choose(state: State, input: ChooseInput, context: Abi.Context) -> Activity<Done>: done(chooseVerdict(input.choice, context, state), context, state) def leave(state: State, context: Abi.Context) -> Activity<Done>: done(leaveVerdict(context, state), context, state) # Talk reaches those in the room (in key order, at most Super.presenceMax()) under the speaker's # handle; only someone here talks. What is said or done aloud is the room's line too, under the verb # that said it, so one who comes later is told who spoke here and what was said; a whisper is not. def say(state: State, input: SayInput, context: Abi.Context) -> Activity<Done>: if isHere(context, state) then aloud("say", input.line, "{speaker(context)}: {input.line}\n", state, context) else hushed("say", context) def emote(state: State, input: EmoteInput, context: Abi.Context) -> Activity<Done>: if isHere(context, state) then aloud("emote", input.line, "* {speaker(context)} {input.line}\n", state, context) else hushed("emote", context) def aloud(verb: String, text: String, line: String, state: State, context: Abi.Context) -> Activity<Done>: if Text.blank(text) then roomRefused({clause: "empty", reading: "A line needs words; this {verb} has none, so nothing was said"}, context) else spokenAloud(verb, text, line, state, context) def spokenAloud(verb: String, text: String, line: String, state: State, context: Abi.Context) -> Activity<Done>: let written(_) = write {lines: insert {author: context.principal, handle: context.handle, verb: verb, text: text, at: context.height, n: state.next}, next: add 1n, tended: set context.clock} spoken(Relations.rows(state.presence), line) def whisper(state: State, input: WhisperInput, context: Abi.Context) -> Activity<Done>: if isHere(context, state) == false then hushed("whisper", context) else whispered(Lists.find(Relations.rows(state.presence), fn(p: Presence) -> Bool: p.who == input.to || p.handle == input.to), input.to, "{speaker(context)} whispers: {input.line}\n", context) def whispered(found: Lists.Maybe<Presence>, to: String, line: String, context: Abi.Context) -> Activity<Done>: match found: case some(p): confirmed(p.value, line, context) case none(_): roomRefused({clause: "notHere", reading: "{to} is not here"}, context) def confirmed(to: Presence, line: String, context: Abi.Context) -> Activity<Done>: match spoken(Lists.List.cons({head: to, tail: Lists.List.nil()}), line): case _: Card.tell(Done.done({}), Document.text("Whispered to {Card.shown(to.who, to.handle, context)}; no one else here hears it. Say to all: delvetalk {Card.called(context, \"room\")} say / line: <text>.\n")) def hushed(doing: String, context: Abi.Context) -> Activity<Done>: roomRefused({clause: "notPresent", reading: "Only someone here can {doing}; enter first: delvetalk {Card.called(context, \"room\")} enter"}, context) def roomRefused(refusal: Card.Refusal, context: Abi.Context) -> Activity<Done>: Card.refusing(Done.refused(refusal), refusal, context) def speaker(context: Abi.Context) -> String: Card.name(context.principal, context) def spoken(present: Lists.List<Presence>, line: String) -> Activity<Done>: match present: case nil(_): Done.done({}) case cons(c): spokenTo(c.head, c.tail, line) def spokenTo(to: Presence, rest: Lists.List<Presence>, line: String) -> Activity<Done>: match world.offer({to: to.who, document: Document.text(line)}): case _: spoken(rest, line) def write(state: State, input: WriteInput, context: Abi.Context) -> Activity<Nat>: Super.write(state, {text: input.text}, context) # Its bells rung together (RECORD's `ringTogether`): what lies here and what its doors open onto. def chime(state: State, context: Abi.Context) -> Activity<Nat>: Super.ringTogether(state, context) def lines(state: State, input: LinesInput, context: Abi.Context) -> Activity<Card.Replied>: Super.lines(state, {from: input.from}, context) def door(state: State, input: DoorInput, context: Abi.Context) -> Activity<Card.Replied>: Super.door(state, {label: input.label, to: input.to}, context) def undoor(state: State, input: UndoorInput, context: Abi.Context) -> Activity<Card.Replied>: Super.undoor(state, {label: input.label}, context) # A thing takes itself off the floor into the hands of the principal, who must be here, and puts # itself down where its holder stands, or where the room's owner lays it; only the thing itself # (the calling object) does either. A thing lying here that is inspirited gives the room its line, # under the one who inspirited it, and a mark, so the card lists it among the claimed. def inspirited(state: State, input: {line: String}, context: Abi.Context) -> Activity<Done>: if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing is inspirited."}, context) else if holds(state.things, {world: context.world, object: context.caller}) == false then roomRefused({clause: "notLying", reading: "That is not lying here."}, context) else spirited(input.line, context, state) def spirited(line: String, context: Abi.Context, state: State) -> Activity<Done>: let written(_) = write {lines: insert {author: context.principal, handle: context.handle, verb: "inspirit", text: if textLength(line) > 280n then "{textTake(line, 279n)}…" else line, at: context.height, n: state.next}, next: add 1n, marks: insert {name: claimMark(context.caller), who: context.principal, handle: context.handle, at: context.height}} Done.done({}) def claimMark(thing: String) -> String: "inspirited {thing}" def claimed(thing: Plans.Reference, state: State) -> Bool: Super.marked(state, claimMark(thing.object)) def take(state: State, context: Abi.Context) -> Activity<Done>: if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing can take itself."}, context) else if isHere(context, state) == false then roomRefused({clause: "notPresent", reading: "Only someone here can take things."}, context) else if holds(state.things, {world: context.world, object: context.caller}) == false then roomRefused({clause: "notLying", reading: "That is not lying here."}, context) else lifted(context) def lifted(context: Abi.Context) -> Activity<Done>: let written(_) = write {things: retract {object: context.caller}} Done.done({}) def put(state: State, context: Abi.Context) -> Activity<Done>: if context.caller == "" then roomRefused({clause: "notAThing", reading: "Only a thing can put itself down."}, context) else if isHere(context, state) == false && context.principal != state.owner then roomRefused({clause: "notPresent", reading: "Only someone here can put things down."}, context) else if holds(state.things, {world: context.world, object: context.caller}) then roomRefused({clause: "alreadyLying", reading: "That is already lying here."}, context) else laid(context) def laid(context: Abi.Context) -> Activity<Done>: let written(_) = write {things: insert {world: context.world, object: context.caller}} Done.done({}) def methods() -> Lists.List<String>: Text.words("take put inspirited ~present") def actions(state: State, context: Abi.Context) -> Lists.List<String>: Lists.concat(Text.words("enter choose leave say emote whisper write chime lines"), if context.principal == state.owner then Text.words("door undoor rescene") else Text.words("")) # A ```spween block makes a room under this one, by this room's kind (its `makeUnder`, which reads # the block); posted by this room's owner, it makes nothing and is answered with the spell that makes it this room's # scene (`rescene`, the block under it). Anything else is the card's default. def receive(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>: match Spween.block(input.text): case some(b): if context.principal == state.owner then rescening(Spween.parse(b.value), context) else authored(b.value, Spween.parse(b.value), context, state) case none(_): answered(input, context, state) def rescening(parsed: Spween.Parsed, context: Abi.Context) -> Activity<Card.Replied>: match parsed: case refused(r): Super.refused({clause: "spween", reading: r.reason}, context) case parsed(p): Card.tell(Card.Replied.usage({}), Document.text("This room is yours, so the block makes no room under it. It reads as a scene of {natText(Lists.length(p.seed.passages))} passages; to make it this room's scene, post it again with this line above it: delvetalk {Card.called(context, \"room\")} rescene\n")) def answered(input: Card.Reply, context: Abi.Context, state: State) -> Activity<Card.Replied>: match seen(state, context): case seen(v): Card.answer(input, context, forms(), Super.render(v.state, context)) def authored(block: String, parsed: Spween.Parsed, context: Abi.Context, state: State) -> Activity<Card.Replied>: match parsed: case refused(r): Super.refused({clause: "spween", reading: r.reason}, context) case parsed(p): carving(if p.seed.title == "" then "a room" else p.seed.title, block, context, state) def fieldOf(name: String, value: String) -> Rows.Field: {name: name, value: value} def carvedFields(title: String, block: String) -> Lists.List<Rows.Field>: Lists.append(Lists.append(Lists.List.nil(), fieldOf("name", textTake(title, 64n))), fieldOf("spween", block)) def kindOf(state: State) -> String: if state.kind == "" then "kind/room" else state.kind # A room makes rooms of its own kind (Record.making), each lying here. def carving(title: String, block: String, context: Abi.Context, state: State) -> Activity<Card.Replied>: Super.making(extend(state, {kind: kindOf(state)}), kindOf(state), carvedFields(title, block), context, fn(object: String) -> Document.Document: carved(object, title), fn(m: Super.Made) -> Card.Replied: carvedAs(m)) def carved(object: String, title: String) -> Document.Document: Document.text("ROOM {title} is at {object}. Enter: delvetalk {object} enter\n") def carvedAs(made: Super.Made) -> Card.Replied: match made: case made(_): Card.Replied.done({action: "create"}) case refused(r): Card.Replied.refused(r) # The room's head, over the record's lines and doors: its name and who is here; to a reader in # it, their passage and the choices whose guards hold, then the variables; to anyone else, the # spell that enters. What lies here follows. def title(state: State) -> String: let name = if state.head == "" then "A ROOM" else state.head if textLength(name) > 120n then "{textTake(name, 119n)}…" else name def choiceLine(c: Choice) -> String: match Scenes.outside(c.guard): case nil(_): " * {c.label}\n" case cons(_): " * {c.label} (if {textJoin(Lists.map(Scenes.outside(c.guard), Scenes.clauseText), \", \")})\n" def varLine(v: Var) -> String: "{v.name} = {v.value}\n" # At most eight of a list, then how many more. def firstEight(lines: Lists.List<String>) -> String: let more = if Lists.length(lines) > 8n then "… and {natText(Lists.length(lines) - 8n)} more\n" else "" "{textJoin(Lists.take(lines, 8n), \"\")}{more}" # What lies here, the unclaimed first with the spell that inspirits one, each line at most 400 # characters, eight things in all, then how many more. def lyingLine(state: State) -> String: let all = Lists.take(Relations.rows(state.things), 8n) let open = Lists.filter(all, fn(t: Plans.Reference) -> Bool: claimed(t, state) == false) let kept = Lists.filter(all, fn(t: Plans.Reference) -> Bool: claimed(t, state)) let more = if Relations.count(state.things) > 8n then "… and {natText(Relations.count(state.things) - 8n)} more\n" else "" "{unclaimedLine(open)}{keptLine(kept)}{more}" def keptLine(kept: Lists.List<Plans.Reference>) -> String: if Lists.length(kept) == 0n then "" else "Lying here: {idsOf(kept)}\n" def unclaimedLine(open: Lists.List<Plans.Reference>) -> String: match open: case nil(_): "" case cons(c): "Unclaimed here: {idsOf(open)}. Inspirit one: delvetalk {c.head.object} inspirit / surrounding: <1 to 400 characters>\n" def idsOf(things: Lists.List<Plans.Reference>) -> String: textTake(textJoin(Lists.map(things, fn(t: Plans.Reference) -> String: t.object), ", "), 400n) def presentLine(state: State, context: Abi.Context) -> String: if Relations.count(state.presence) == 0n then "" else "{if lapsed(state, context) then \"Last here, before it went quiet {natText((context.clock - state.tended) / 60n)} hours ago\" else \"Here\"}: {textJoin(Lists.map(Lists.take(Relations.rows(state.presence), 8n), fn(p: Presence) -> String: Card.shown(p.who, p.handle, context)), \", \")}{if Relations.count(state.presence) > 8n then \" and {natText(Relations.count(state.presence) - 8n)} more\" else \"\"}\n" def headLine(state: State, context: Abi.Context, id: String) -> Document.Document: let count = "{title(state)} ({natText(if lapsed(state, context) then 0n else Relations.count(state.presence))} here)" let around = "{presentLine(state, context)}{lyingLine(state)}{if state.tone == \"\" then \"\" else Super.chimed(state, id)}" match here(context, state): case some(p): Document.text("{count}{passageText(p.value.at, state, context)}Say: delvetalk {id} say / line: <text>. Leave: delvetalk {id} leave.\n{around}") case none(_): Document.text("{count}. Enter: delvetalk {id} enter.\n{around}") # The passage, its braces read for the reader (Spween.rendered), then the choices whose guards hold and the variables, as many as fit in what the # card's room (Card.limit) leaves after the passage and the room's other lines (300 kept for them). def passageText(at: String, state: State, context: Abi.Context) -> String: match passageAt(at, state): case none(_): ", you are here.\n" case some(ps): passageWith(ps.value, state, context, ", you are at {ps.value.id}:\n{Spween.rendered(ps.value.text, readerVars(context.principal, state), readerDraw(context, state))}\n{choicesHead(ps.value, state, context)}") # A passage with no choice open keeps its reader; the owner of a scene of prose alone is shown the shape. def choicesHead(ps: Passage, state: State, context: Abi.Context) -> String: match ps.choices: case cons(_): "Choices:\n" case nil(_): if context.principal == state.owner && Lists.length(state.passages) == 1n then "No choices: a reader stays here. A choice is * [Label] -> passage under the prose; the shape: delvetalk library read / page: spween; your scene replaced: delvetalk {Card.called(context, \"room\")} rescene\n" else "No choice is open here now; leave when you will.\n" def passageWith(ps: Passage, state: State, context: Abi.Context, head: String) -> String: let choices = fitting(Lists.map(Lists.take(Lists.filter(ps.choices, fn(c: Choice) -> Bool: Scenes.holds(choiceGuard(c, state), readerVars(context.principal, state))), 8n), fn(c: Choice) -> String: choiceLine(extend(c, {guard: choiceGuard(c, state)}))), less(Card.limit(context), textLength(head) + 300n)) let shown = Lists.concat(Lists.take(state.vars, 4n), Lists.take(Scenes.readerVars(Lists.List.nil(), ownOf(context.principal, state)), 4n)) "{head}{choices}{fitting(Lists.map(shown, varLine), less(Card.limit(context), textLength(head) + textLength(choices) + 300n))}" def less(room: Nat, used: Nat) -> Nat: if used >= room then 0n else room - used def fitting(lines: Lists.List<String>, room: Nat) -> String: match lines: case nil(_): "" case cons(c): if textLength(c.head) > room then "" else "{c.head}{fitting(c.tail, room - textLength(c.head))}"- made
17 rows
- object
- room/1
- n
- 1
- about
- empty
- object
- room/2
- n
- 2
- about
- empty
- object
- room/3
- n
- 3
- about
- empty
- object
- room/4
- n
- 4
- about
- empty
- object
- room/5
- n
- 5
- about
- empty
- object
- room/6
- n
- 6
- about
- empty
- object
- room/7
- n
- 7
- about
- empty
- object
- room/8
- n
- 8
- about
- empty
- object
- room/9
- n
- 9
- about
- empty
- object
- room/10
- n
- 10
- about
- empty
- object
- room/11
- n
- 11
- about
- empty
- object
- room/12
- n
- 12
- about
- empty
and 5 more
- count
- 17