ESC
其他 15 分钟阅读

Teaching my kid to code with a modern MUD

Teaching my kid to code with a modern MUD

来源:Hacker News

The first group produced a ton of stuff. The Internet Archive has a playable [collection of thousands of stacks. If you remember the Early Web, just browsing the thumbnails should give you a familiar feeling. Games! Zines! Fandoms! Manifestos! Somehow porn! Before the web, people with a Mac and Something To Say traded stacks on floppies.

I fell into the second camp. At nine years old I found HyperCard on my elementary school’s Macintosh SE and quickly discovered that unlike most computer programs, everything in HyperCard was editable. I started making the first thing that popped into my mind, which was a Street Fighter style fighting game with two crudely drawn players.

I didn’t know anything about programming, much less advanced gamedev concepts like sprites. But I knew I could link buttons to cards. So I did that. With the determination only a hyperfocused child can muster, I set about drawing individual cards for every possible combination of player moves and positions, with buttons as “controls” to move between them. I’d found me a hammer, and I could see how to make a game out of nothing but nails.

You’re on card 154. Player 1 is in quadrant 2, idling. Player 2 is in quadrant 3, defending. If player 1 presses the “attack” button, go to card…

It took me weeks staying late after school. But it worked. I made a game. I could play my own creation. I’ve spent my whole career chasing that high.

But that was just the beginning. One of the really clever things Bill Atkinson did was to make it so when you edited HyperCard behaviors with the GUI, it just wrote the corresponding code for you. About halfway through my laborious clicking-and-linking project, I noticed the Script… button in the editor, pieced together its implications, and started using a copy & paste HyperTalk snippet and a systematic naming scheme for all my hundreds of cards to speed up development.

Near the end the computer teacher (I’m pretty sure we just called the class “computers”) noticed what I was up to and dug the HyperCard Reference Manual out of a drawer for me. This was my first experience reading a tech manual cover to cover.

I’m not going to make my daughter learn HyperCard. I’m a bit of a Troll Dad, but not that mean.

But I did want to reproduce the magical combination of creative freedom and mechanical constraints that made HyperCard so eye-opening for young Nick. Canon needed:

The great thing about a text-based world is that you can add anything you want just by typing “there is a [thing] here”. You’re constrained in how you can build – no fancy 3D visualizations or impressively realistic physics engines – but have unlimited freedom in what you can build.

Players and Rooms are both just text descriptions and, optionally, images. To make either, you just answer a question: what does this look like?

Items are everything else. They are also text descriptions and images, so you can flavor an item as anything you want: an inanimate object, a pet, an NPC, a spell effect, a vehicle, a doorway, a quest marker, a message, etc.

But in addition to text and images, items are also made of actions and state, via Cant. It looks like this:

item "Hooded Lantern" { describe "An iron lantern with a hinged hood." on "light" { narrate "{player}’s {item} swings open and warm light spills out." } } That’s enough to produce a lantern the player can light:

Here’s a slightly more complicated item, a magic 8-ball:

item "Magic 8-Ball" { describe "A prophetic billiard ball. Give it a shake?" or "Give it a shake to see your future." or "Dare you shake it?" on "shake" { say "It is certain." or "Outlook good." or "Signs point to yes." or "Don’t count on it." or "My sources say no." or "Reply hazy, try again." or "Ask again later." } }

The or keyword works anywhere Cant takes a text string. It randomly picks a variant each time the string is used: when a player looks at an item, or triggers an action. I wanted this baked into Canon at the lowest level.

There’s something especially delightful, to a new programmer, about an RNG. You tell the computer what to do, and it does it. But you also told it to exercise a tiny bit of its own agency. It’s both surprising and not surprising. You programmed every response, you know all the things it can say, and yet you don’t know exactly what it will say when you press the key or click the button. It’s like suddenly being able to tickle yourself.

Notice in addition to shake, this item has a clone button. Every item in Canon can be cloned. Items you own (which include anything you’ve cloned) can be edited. Every game or toy or quest made by another player is also a tutorial.

The 8-ball is one of the first toys I made to tempt the kiddo. I left it lying in the starting zone. Even when you know nothing about conditional logic, even if you don’t look at the code, it’s easy to see how it works from a few minutes of playing with the ball. And it’s easy to imagine how you could modify it into a flipping coin, or an NPC that tells random jokes.

If you are a programmer, one thing you might notice about this is that it’s horrible. This is an inefficient way to write something that could obviously be randInt(1,20). But it is extremely approachable.

Cant is a language designed to be bad in educational ways. It’s optimized for ease of understanding over speed of writing. It’s the opposite of DRY.

Let’s expand that editor window a little:

An experienced programmer will notice this code could’ve been a one-liner. But a beginner will notice the code is already a lot less work than clicking 20 buttons and 20 text inputs.

The code and the structured editor are synced. Any valid expression in one immediately updates the other. I wanted this to tempt the kiddo the same way HyperCard’s Script… button tempted me.

Okay, saying random strings is fun, but it’s not much of a game. What else can this thing do?

item "Dueling Wand" { describe """ A hazel {item} wound with copper wire, humming faintly. {when item vigor <= 0}Its light is out. A defeated duelist’s wand, until someone mends it.{end} """ borne "A hazel {item} {when item vigor <= 0}hangs dark and quiet{else}crackles with impatience{end} in {player}’s grip." afield "A hazel {item} lies here, {when item vigor <= 0}its lights dimmed{else}crackling with magic potential{end}." state { vigor 30 } on "fireball" { when item vigor > 0 { when target vigor > 0 { subtract target vigor 1d20 narrate "{player}’s {item} roars! A ball of fire breaks over {target}." when target vigor <= 0 { narrate "{target}’s wand goes dark. {player} stands victorious." } } otherwise { narrate "{player}’s {item} finds no one standing to strike." } } otherwise { narrate "{player} attempts to cast but their {item} is dark until mended." } } on "mend" { when target vigor >= 30 { narrate "{player}’s wand hums, finds nothing to mend on {target}, and declines to waste the charm." } otherwise { add target vigor 1d10 narrate "Green light from {player}’s {item} knits itself over {target}." when target vigor > 30 { narrate "{target} sparkles briefly with excess power." set target vigor 30 } } } }

This is a PVP encounter. Two or more players can pick up Dueling Wands and fight each other. The key thing to notice here is:

state { vigor 30 }

Items in Canon can hold two kinds of state: marks and numbers.

Marks are boolean flags. A lantern could be LIT, an NPC could be SUSPICIOUS, a door could be LOCKED.

Numbers are what they sound like. A campfire could have warmth 10, an NPC could have health 20, a pickable lock could have attempts 5.

Marks are always UPPERCASE, numbers always lower. The casing is the type.

Note also that vigor above is on the wand, not the player. Players have no state themselves, only on the items they carry. When you target a player with a fireball, you are targeting their wand. Or rather, you are targeting *any items they carry that answer to state vigor.

In this way, all encounters and mini-games in Canon are consensual* and opt-in. Don’t want to duel? Simply don’t pick up the wand.

State names are completely arbitrary. This wand has vigor, but another set of weapons might have life or health or stamina. You can build a suite of items that all answer to the same state and interact, or pick unique names for unique encounters.

Here’s an NPC using both marks and dialog:

item "The Dialog Tree" { describe "A talking tree, only too happy to bend your ear about the joys of being a plant." remembers state { PLEASED false } on "chat with the tree" { dialog "Welcome, welcome, have a seat…" or "Hello there, little animal…" { choice "How’s being a tree?" { dialog "Oh, the absolute best! You simply must try photosynthesis." mark item PLEASED } choice "Uh, excuse me, I was just leafing…" { narrate "{item} looks a little crest… er, branchfallen." unmark item PLEASED } } } }

This creates your classic RPG dialog box. Dialog can nest arbitrarily deep, and contain any other Cant effect as the result of a dialog choice, or present different choices based on number and mark state.

Notice the tree’s mood changes depending on your answer, tracked by the PLEASED mark.

The remembers keyword tells an item to retain its state when left alone in an empty room. Without it, state resets when no players are around so encounters are fresh for new visitors. In the Dialog Tree’s case, we want it to retain its mood from whoever last spoke to it.

If you look closely you can also see the Dialog Tree’s image changes. Item images can respond to marks and numbers. Cant is all about text, so images are handled separately in the item editor:

The same is true for Players. You can change your character’s image based on the state of whatever items they carry. Here’s a simple paper doll item that has no action, but does set a state:

item "Fancy Red Pirate Coat" { describe "A fancy red coat, fit for a pirate captain." state { OVERCOATED true } }

And here is a player where both text description and active image respond to the presence or absence of the coat:

Rather than (or in addition to) altering a player’s descriptive text, we can also give items an optional borne description which is appended to the player’s description.

item "Fancy Red Pirate Coat" { describe "A fancy red coat, fit for a pirate captain." borne "{player} is fashionably draped in a {item}." state { OVERCOATED true } }

Screenshot of Sky Captain Fal](https://archive.org/details/hypercardstacks) Screenshot of Sky Captain Fal

You can play character customizer (many people’s favorite game in any game!) all day long, with just text and images.

You can make a lot of fun dynamic objects with images and conditionals and state. But a simple bit of flavor text is still often the best part. Out of all the items I’ve made, my daughter’s favorite (or at least most frequently employed) remains the humble snowball:

item "Snowball" { describe "An expertly packed snowball." on "throw" { narrate "{player} hits {target} in the face with a snowball! _Paff!_" } }

But not too much

You may notice in both the Dueling Wand and responsive player/item images above, conditionals are very limited. Each when takes a single condition. There is no and, or, etc. There is also no equivalent of else if. when takes a single condition, and a single optional otherwise block. If you want more complicated control flow, you can nest when. Again, this will probably strike an experienced programmer as bad, but it serves a purpose.

For one thing, the structured editor is complicated enough. The Dueling Wand alone, while far from the most sophisticated item you can build, gets visually noisy:

Adding optional combining blocks would make it harder to navigate. But more to the point, the simple when is meant to be good enough exactly until it isn’t.

Cant is a language designed to be frustrating in enlightening ways. A reasonable objection might be that it will teach bad coding habits. But from my own childhood experience, what it will actually teach is why the good habits are good and how the layers of abstraction and expressibility get built one on top of another.

The kiddo is already building cool stuff with Cant. Eventually she’ll become annoyed with its limitations. But at that point she will also be able to articulate those limitations, and imagine ways it could be improved. And that will be a gateway. When she graduates to a bigger fuller more expressive programming language, she’ll understand why it’s better.

Another thing I want Canon to teach is how to hack. Programming outside the lines. The kid is already very fond of mischief and loopholes, so this is playing to her strengths.

You may have noticed a slight flaw in some of the examples above. If you can edit every Dueling Wand, you can just edit your own wand to give yourself extra vigor, or never take damage. If you can edit every dialog, you can just sneak a peek to see which choices give you the ending you want. Every encounter is hackable.

This, too, is intentional. For purposes of nurturing a growing engineer brain, creating puzzles, beating puzzles, and hacking puzzles are all win conditions.

From the broader game perspective, it’s part of “everything is consensual”. Canon has no permanent points to win or lose. There are no scarce resources attached to your character that someone can cost you. The only reason to have a PVP duel is to have fun with your friends. The only reason to play a mini-game, or fulfill a quest someone crafted, is to have the experience they crafted for you. Or if you’re the sort of person who has more fun taking apart the experience to see how they made it, you can do that too.

That said, there are a few concessions to authorial control. The main one is Kingdoms. The Canon map consists of a shared overworld, and individual zones belonging to each player. The overworld is mostly a flavorful way to travel between Kingdoms, and not a secure place to leave things. Anything you set down in the overworld can be picked up by another player. But in your Kingdom, only you can place items in a room, and only you can remove them. Other players can clone your creations to figure out how they work, but they can’t modify the originals. This lets you create stable multi-room, multi-item games and encounters.

Want a quest where you talk to a series of NPCs in different villages to piece together a mystery? Or have to find the right keycards to open the right doors in a maze? Or you collect and tend to an egg with the right magical ingredients to hatch a baby dragon? Those are all easy to make in your Kingdom.

This is an overworld. The colored nodes with ⌂ glyphs are entrances to Kingdoms, which have their own maps. In both cases, the map data structure is a tree, or an undirected acyclic graph. The map grows only by budding new rooms, never by connecting two existing rooms.

With no fixed dimensions, a “room” can be anything you describe. You can make a literal single room in a building, or a whole building, or a vast terrain. An empty void. A pocket universe. A state of mind. The abstract map matches the total freedom of text.

It allows Kingdom owners to lock their doors, creating private lairs, invite-only guild halls, and puzzles that depend on gated movement, while enforcing that locks only work in the direction away from the root node. In Canon you can lock someone out, never in. You can make lairs, but you can’t make dungeons.

It teaches the kiddo something about user interface abstractions and the clever sorts of hacks that go into game design. For example:

Many popular level designs cannot be expressed purely in Canon’s map structure. Imagine a network of treehouses, connected by rope bridges, above a forest floor. The treehouse village is a series of connected rooms to the north, south, east and west. The forest floor is the same. Each forest floor room should also be connected to its matching canopy room by up and down exits, but these would form cycles in the map graph.

Canon solves this the way DOOM did: by faking it. A teleport Cant action attached to an item offers to transport players between arbitrary rooms.

item "Rope Ladder" { describe "A rope ladder, dropping out of the canopy like an invitation." on "climb" { say "The rungs creak under you." teleport "lomise-anthil-nara" narrate "{player} hauls up out of sight." } }

The quoted address is the destination room’s locus: a randomly generated id you can only learn by standing in the room. This is so the Cant remains portable, but you can’t simply guess the address of an unfamiliar room, since teleportation can bypass locks. (That’s right, token security is also on the curriculum!)

Ladders and elevators are just the start. You can use teleportation to build:

Mounts: create a Horse or a Motorcycle or a Dragon to carry you to different destinations with a ride action.

NPCs: combine dialog and teleport for an Elevator Attendant or Portal Mage.

Hearthstones and Bookmarks: found a cool spot on the map? Craft yourself a quick teleport to return to it any time.

I seeded Canon with a bunch of toys and games for the kiddo to take apart and remix. But I wanted to make sure she could create her own stuff from the start. So I also included a Wish function. Once a day, each player can Wish to the Machine God. An LLM will craft an item to your specifications, including functional Cant with explanatory comments.