Examplia

Author
Company
Released
Genre
Size
7.9 KB
Rating
No rating
(0 Reviews)
Board Count
2 / 2

Learning ZZT-OOP By Example With Examplia

Long Walks Through Short Code In Examplia, A ZZT-OOP Example World

Authored By: Dr. Dos
Published: Jul 20, 2022
RSS icon

Page #2/2
< 1 2

Alleyway

examplia

This tiny little corner doesn't have a whole lot to share. It's kind of amazing how few commands ZZT-OOP actually contains, and how just a few frequently used commands make up such large portions of code.

The darker bins and garbage cans with their use of #bind have already been covered, so our first target will be Fry, the blue guy, who will teach us how to try.

Fry And #Try

@Fry #end :touch #if blocked n oh FRY: I'm going to try to move north, but if I can't, whatever's in my way better watch out!!! /i :loop #try n shoot n /i #loop :oh FRY: Well I've had my fun I guess.

Fry is unique in that he doesn't do anything prior to being touched. When he is, he'll begin by checking a new condition for the #if command! #If blocked will check is the object is obstructed in the specified direction. "Obstructed" here means "the tile is not flagged as walkable", a set of elements that consists solely of empties and fake walls. Pushable elements like the player, boulders, gems, and such will still cause the blocked condition to return true, even if the object can move in that direction.

Part of designing a ZZT world means considering how the player will approach objects. This object can be touched from any direction, and so touching it from the north will immediately cause it to jump to the :oh label. Realistically though, the odds are that the player will touch the object from the south as they enter the alley, or from the east after checking out the other objects in the area.

Here it's no problem as touching the object from a different direction will let Fry continue to do his thing (provided of course that the player decides to keep touching the object). In more complex situations it might be helpful to check if the object is specifically blocked by the player, which is done with another condition we'll see later.

If Fry's path is clear he enters a loop and does just what he says he will. Using #try, Fry will attempt to move to the north if possible. Here, Fry can push things out of the way, so the check isn't quite the same as #if blocked.

All these times I've been saying "jump to a label or execute a command" we've been jumping to a label. This time, let's introduce a command instead. #Shoot will fire a bullet in the specified direction. Breakable walls and the player can be shot at point blank range, but other objects can't be shot at point blank range, an actual bullet element has to be produced by this command in order to do that.

If you look closely, you'll see the command used is shoot n, without the leading... let's call it an octothorpe. This is no typo! Commands that take commands like this don't actually need the prefix, though if you do place one the command will function all the same. This is a minor space saver, but more importantly it does mean that you can't have an object display text as part of this command. Instead you need to provide a label and have the text below it.

As Long As We're Learning Names Of Symbols...

If Fry busts his way through the breakable wall, the player can head to the top right corner of the board and find a collectible trinket lifted from Town of ZZT. This ampersand (despite being named Necklace) shows off two more commands.

@Necklace #end :touch #lock You have found an AMPERSAND! #play sage+age+age+age-dage-age-age #give score 100 #die

As if ZZT's graphical capabilities weren't exciting enough, there's music as well! #Play can be used to create some lovely PC speaker music if you dare. Mastering the single channel instrument in all its square wave glory is something relatively few have really done in ZZT's history. This has led to a lack of expectations for outright music. The same can't quite be said for creating simple sound effects, which are an easy way to fumble your way through to create a brief little item collection sound or other noise to keep your game from being almost completely silent. ...which Examplia basically is.

Consult ZZT-OOP 101 for a better idea of the syntax for ZZT sounds, but as far as compositions go, I won't be any help there. You can also of course play sound effects used elsewhere in ZZT for consistency, and these can actually be quite nice when it means an object walks into a passage and plays the passage sound. Sound is the aspect of ZZT game development that other ZZTers will be the most sympathetic towards. Do not feel obligated to master it, but if you have a bit of a musical background and don't mind a challenge, some surprisingly good songs have been composed over the years.

Also: #die. This handy command will destroy an object, perfect for picking up items like this one. Sadly, there is no way to replicate the behavior used when picking up built-in ZZT items which place the player on top of their tile.

An Object Named Ruffian

The beauty of ZZT's simple graphics means that it's trivial to spoof something's appearance. The ruffian in the alley is no enemy creature for the player to fight, just another object with some words of wisdom.

@Ruffian #end :touch #zap touch /n RUFFIAN: Help yourself to some ammo, though this isn't really a "shooting" kind of game. #end :touch #give ammo 5 RUFFIAN: Have some more ammo. I guess. #end :shot #zap touch #lock RUFFIAN: Welp I know when I've been owned. /i #shoot seek /i/i/i RUFFIAN: Jerk. #unlock #end :touch RUFFIAN: You don't get any more ammo now! #end

All this code and you've actually almost every last bit of it before! The player's (hopefully) new friend provides a bit of ammo, moves out of the way, and will provide more ammo when touched again.

...Unless the player betrays them and shoots them with their newfound bullets.

It's time for :shot, a built-in label that objects will be sent to upon being struck by a bullet. If the player happens to be rude, the ruffian will chastise them and fire a bullet right back with #shoot seek. This time we see shooting in a more natural form rather than as a failure condition for #try.

They'll also #zap touch again and stop providing infinite ammo. That's fair.

This specific arrangement of :touch and #zap touch only works properly because we know the player won't have any ammo prior to talking to the ruffian. If this wasn't the case, things would get a bit more involved to properly manage our labels as it would be possible to shoot the ruffian before talking to them, and then talking to them would instead go to the :touch that leads to ammo being given out.

Actually, even this arrangement has a bug. If the ruffian is shot a second time (you monster), they'll #zap the final touch label and no longer be able to display any such text. See if you can come up with a way to fix this scenario before viewing my solution below.

[...] :shot #zap touch #zap shot :shot #lock RUFFIAN: Welp I know when I've been owned. [...]

By zapping the shot label as well, and adding in a second :shot label a little lower, it's possible to make the ruffian object only #zap touch the first time they're shot.

On The Beach

examplia

Let's head back over to the beach where we'll rapidly get through a few objects.

Sea Shells

@Shell #end :touch You find a gem in the sea shell! #give gems 1 #become fake

This little purple shell waits patiently for the player to investigate before turning over a hidden gem. You could #zap the touch label and keep it around, but once that gem's been collected there's no need for it to be in the player's way. The shell doesn't use #die though, as this always results in the object being replaced with an empty tile which would leave an unsightly mark in the sand much like the gems from the ATM tore up the boardwalk.

#Become fake is used instead to save the day! The command will instead replace the object with the specified element. While this can take a color parameter as well, here none is specified, resulting in the object using the color of the tile it's currently on top of, which happens to be a yellow on dark red fake. Now our beach remains pristine!

The Party

A comically oversized boom box is blasting some music (it's not). The boom box is of no interest at this point. The dancers demonstrate some basic movement in a loop. It's all things we've seen before.

@note :loop ?rndns /i/i #char 14 /i/i ?rndns #char 13 /i/i #send loop

The music note representing that music is our next focus. It too is in an endless movement loop, but one using a command to move randomly. Somewhat. Specifically we're using ?rndns, which will choose a random direction to the north or the south. ZZT offers an oddly restrictive set of random movement commands which often require combining them with other keywords to cover the whole assortment of random movement. (There's no "rndew" direction for example. You'd have to say ?rndp n to get a random direction perpendicular to north.

The random movement isn't the real focus though. Our focus is on the fact that we're using ?rndns and not /rndns. As mentioned before the slash command will prevent the object's code from continuing until that movement is successful, which isn't always what we want. We've also seen #try which would let the object keep going regardless of whether or not it moved. That would work just fine here, but we're going to use ? as a pseudo-shorthand for #try instead as it also allows combining multiple directions on a single line.

I say "pseudo" because while / and #go are one and the same, there is a difference here between ? and its longer #try form. That is: only #try supports optional labels/commands to execute if the movement doesn't happen. Obviously it's preferable to have shorter code with ? when you don't actually need an optional label/command.

You'll also notice the music note changes its appearance at a fixed rate. This is through the #char command which takes a number from 1 through 255 to change to that ASCII character. The ASCII Characters page is available as a reference, but third-party editors tend to have ways to select the character you want the object to begin using and then automatically convert it to its numeric value. Some you'll wind up using frequently enough that you may very well just memorize a handful.

#send loop - This object runs in an endless loop, but it does so without the use of #restart. Instead this object is using #send, a command that can be passed a label and will then make the object go to that label. This is an "internal" event as opposed to an "external" event like being touched or thudding into a wall, so even if this object were locked, it would still loop properly.

It's a bit surprising to have it made it this far without needing #send, as it's the basis of most events and offers more capabilities than merely telling an object to move to a different part of its code. It won't be long before we see even more.

One Object Fire

The bonfire the crowd is surrounding is also rather animated, and while you could very well use a series of objects with #char to do a fire effect, only one object is needed here. The lower left log of the fire holds the code we're looking at next.

@One Object Fire 'Thanks to Barney9651 #cycle 1 /i #change red normal red fake #change red water red normal #change red solid red water /i #change red breakable red solid #change red fake red breakable #restart

Admittedly, #change was already covered, but I wanted to showcase Barney9651's One Object Fire, an iconic effect that uses the command to create an impressive animation that has been seen in countless ZZT games. These kind of effects help make worlds much more pleasing to look at. They can also make them look much worse. An entire board of this may come off a bit garish, so use discretion.

'Thanks to Barney9651 is a proper ZZT comment. Way earlier on it was discussed how ' is also used to mark zapped labels, which is all comments really are. A mixture of limited memory, inexperienced developers, and simplicity of a lot of code means that actual comments are hard to come by in ZZT worlds. Typically by the time you're working with something complex enough where comments are really helpful, you're doing something involved enough where using memory that doesn't need to be used is a bad idea and keeping notes outside of ZZT is often a better idea.

But! Do not let fears of running out of memory keep you from commenting your code, especially as a beginner! Examplia at the time of writing takes up a little more than 18,000 of the 20,000 bytes of memory available for individual ZZT boards. This is a very dense board, one which doesn't represent the structure of most ZZT worlds which aren't trying to cram everything in one place.

A Fellow Tourist

@Tourist #end :touch TOURIST: If I had afishing rod I bet I could catch something nice from this pier!

A basic object whose purpose here is to provide a hint to the player that they might want to obtain a fishing rod at some point if they haven't. Showing this off for the sake of design rather than any new code.

Go Fish

@Fishing Spot :loop #if contact arrow #char 176 /i#loop :arrow #char 27 /i#loop :touch #if rod cast Something seems to be bubbling in the water nearby... #loop

On the pier, one of adjacent water tiles is secretly an object in disguise.

#if contact arrow - We've still got conditions to learn about! This one checks if the player is adjacent to the object to the north, south, east, or west. These two paired loops ensure the object looks like water when the player isn't adjacent, but reveal an arrow when they are in order to communicate that this object isn't mere water and can be interacted with.

/i#loop - An advanced technique being shown off. Since movement shorthand commands don't actually advance code to the next line it's possible to "stack" a second command afterwards. The memory savings are tiny, but you often want to idle before looping as otherwise ZZT will process some loops dozens of times per cycle which can actually slow ZZT down if there are enough busy loops like this. The idle command forces a pause resulting in the loop being limited to one execution per cycle, which is all you'd ever need for a condition like contact that can't change mid-cycle.

#Loop isn't a dedicated command, but another instance of some shorthand included in ZZT-OOP. Much like how there's no need to write #go, one can skip writing #send and just turn it into #<label>. ZZT will assume any undefined command is meant as a call to #send.

The only gotcha is that when using a label name that doesn't actually exist in the code #send fakelabel will do nothing and continue to the next instruction whereas #fakelabel will raise a bad command error and halt code execution entirely. Obviously you're not going to write code for labels that don't exist, but if you zap all occurrences of a label before trying to jump to it, the effect is the same.

@Fishing Spot [...] :cast #lock You cast your rod and aim for the bubbles... /i You snag something! $ /i It's a ticket for the zoo! Thank goodness it's not completely drenched! /i #set ticket #char 176 #send bubbling:stop

With the rod the player can go fishing and obtain the mysterious item bubbling nearby. This consists of previously covered commands at the start. It's worth pointing out the use of $ with no additional text. This is an instance of turning a one line message into two lines, and really all you need is the blank line. Some ZZTers like to use this technique to make it clear to themselves later that the text displaying as a message window is a deliberate choice and not just an accidental extra line break.

#send bubbling:stop is also not a new command, just a new usage of one. #Send can be used for more than just directing the object running its code to jump to a label. It can also direct other objects to labels as well (provided they aren't locked). Any object with the name @bubbling will be redirected to :stop if their code has such a label. If not, the object continues doing whatever it was doing uninterrupted.

And, just as before the #send component is optional. You can also simply use #bubbling:stop to produce the same effect.

Bubbles

@bubbling :loop /i/i #char 250 /i/i #char 249 /i/i #char 248 /i/i #char 9 /i/i #char 176 /i/i/i/i/i/i #loop :stop #char 176

The final beach object are the bubbles in the water. Nothing to cover here, just showing this object's :stop label that was referenced in the previous object.

Zoo of Zero Animal Welfare Concerns

examplia

It is very difficult to make an ethical looking zoo when the animals are given six tiles to move around in, but ZZT's core gameplay says your job is to shoot these creatures before they kill you. Of course, nothing is stopping you from turning that on its head.

Even with the newfound ticket, how are you supposed to get inside? The fence is made of text and not objects. The gate is made of sliders. There's nothing to interact with! Fortunately, the zoo's attendant will be the one initiating the interaction this time.

Attendant

@Attendant :loop #if alligned check /i#loop :check #others:gatecheck /i#loop :ticket #if ticket good ATTENDANT: Hey! No admittance without a ticket! [...]

#if alligned check - Our final condition! Alligned returns true if the player is aligned with the object. NOTE THE EXTRA "L"! Yes, this command is a typo and you will have to type that typo. Typing aligned with just one "l" will be treated as a flag name.

While this does tell the attendant that the player is aligned with them, it's still not precise enough to do all the work. #If alligned doesn't care about anything in the way so relying on this one alone check would result in the attendant looking for a ticket when the player was aligned all the way out in the boardwalk area.

#others:gatecheck - The solution here is to use some more objects. On the bottom row of the board below the yellow path through the zoo are two more objects that also look to see if the player is aligned with them.

#end :gatecheck #if alligned #attendant:ticket

#others:gatecheck - These nameless objects are still able to receive messages without a name to reference them by. #Send has three special names that allow targets to be specified without providing a specific name. #Send all will attempt to send the event to every object on the board, even the one executing the #send. #Send others does the same, but excludes the object executing the command. Lastly, #send self can be used to reference the object that is making the #send call.

So when these objects are executing #loop, they're really running #send loop, and that's really #send self:loop! (To really blow you mind, #if contact move is really #if contact #send self:move. All these "label or command" parameters were really just commands the entire time!!)

@Attendant [...] ATTENDANT: Hey! No admittance without a ticket! :altloop #if not alligned loop /i#altloop #end :good $You flash your ticket to the attendant ATTENDANT: Right this way my esteemed guest! #change purple sliderew yellow fake #end

Back in the attendant object, another possibility has to be handled. If the player is indeed aligned at the zoo's entrance, but doesn't have a ticket, the attendant needs to be able to start checking for the player's next attempt to enter, but needs to wait until they are out of the way so as not to constantly pop up messages due to still being aligned the first time.

This is done with the use of a second loop here which waits until the player isn't aligned with the object, and then returns to the original loop allowing the interaction to replay as many times as needed until the player manages to get their ticket.

Signs And External Text

@Sign #end :touch The sign contains information about bears and lions. !-bearex.txt;Learn about bears !-lionex.txt;Learn about lions #end

I bet you weren't expecting a return to hyperlinks. The code presented here is the entirety of the object's code, and any and all bear and lion facts must be kept elsewhere.

ZZT as it turns out (the feature is undocumented) allows you to display arbitrary text files via a modified hyperlink command. Using !- instead of just !, a filename can be passed instead of a label. By default, the filename is treated as having an extension of .HLP, which is often what external text is used for, but the extension can be whatever you like. As ZZT expects all its files to be in the same directory, be wary of using names with potential overlap from other people's creations. Here I'm ending my external filenames with "ex" for "Examplia" in hopes of reducing the likelihood of a file collision.

This text display uses ZZT's help file system, which while similar is not the same as how text is handled elsewhere. You cannot execute ZZT-OOP from files like this. Any commands are just displayed as text, though ones pertaining to text like hyperlinks and $ formatting function as normal.

This feature is mostly used to either display game specific documentation, or to embed large quantities of text without counting against the board's memory. Regardless of what you use it for, keep in mind that you'll need to make sure your lines fit within a message window and that they are plain text files that only use valid ASCII characters.

Also be sure your line endings are the correct CRLF format used by MS-DOS, otherwise ZZT won't display your text files properly. If you're using KevEdit, you can write your text in there and export it to a file to easily get everything in a valid ZZT format ready to be displayed in a message window.

Also remember that the only way to display these files is by getting the player to select a hyperlink to one. Some creativity may be needed to make the process feel seamless if you're integrating dialog into the world.

Admiral Chompy

The true star of the zoo is Admiral Chompy, the friendly shark who knows cool shark facts.

@Admiral Chompy #end :touch #zap touch Wow! It's Admiral Chompy! Or at least a man dressed like him. [...] #end [...] :touch #restore touch #zap touch * ADMIRAL CHOMPY does a funky dance * #end

Admiral Chompy here does indeed have a few shark facts not stored in a text file displayed one by one. His purpose here is just to help demonstrate the need for one #restore, but many #zap commands. In this instance, he gets introduced once, and then after looping all his facts, he both restores and then immediately re-zaps the first :touch to create a loop of shark facts (and funky dances).

The Not So Grand Hotel

Only one area remains, and we're almost out of things to introduce, so let's get through what fun not-shark facts can be learned from the tiny hotel.

examplia

Doors That Aren't ZZT Doors

ZZT's style of doors works great for arcade-style adventures that aren't going for realism. You grab the key, you unlock the door, the door disappears. This is a bit sillier when trying to create more realistic spaces. The doors in hotel are specifically taken from an old ZZT Toolkit SNACK, but you'll find similar ones in countless worlds. These ones in particular are nicely polished with a good animation and sound effects that make them feel rather natural to use. (It is likely they originate from something other than SNACK, but the history of ZZT doors is not thoroughly documented.)

@door #end :touch #lock #play 5xxx8 #char 92 /n#char 179 /e/i/i/i/i/i#play 8xxx5 #char 92 /w#char 196 /s #unlock #end

Now that's a high quality door, though you may want to add another idle command or two to give the player ample time get through. What's here is enough idles that the player probably won't have to worry about the door automatically shutting in your face, but in most instances a slightly longer animation won't cause any harm. Here I avoided doing so solely because of how cramped the hotel is on the board, with the bathroom version of this door blocking access to the object the player wants to investigate. In that situation, you don't want to keep the player waiting.

A Bed To Die For

@Bed #end :touch #lock The bed is so comfortable, you can't help but take a nap! /i You awaken and feel quite refreshed! $ /i #endgame #give health 500 #unlock

#endgame - This new command does exactly what it says. It ends the game. Unless it doesn't.

See, what #endgame actually does is sets the health counter to zero. The way ZZT works is that it's the player that actually ends the game upon running out of health. So by running #endgame and then immediately afterwards giving the player health back, you can effectively set the player's health to an arbitrary value.

It is critical that the two statements follow one after another. If the player gets a chance to act after the initial #endgame the game will end, and while they will still get the health and be able to move around, ZZT will be in its game over mode where everything runs super fast.

Maestro, If You Please

@Sheet Music #end :touch Sheet music for "The ZZT Blues". Maybe that busker could use this? !1;Take it !;Nah #end :1 #set sheet #die

Left behind from a previous guest of the hotel is some sheet music.

!;Nah - One last trick of hyperlinks. This time no label is specified which results in a fake choice that just closes out the text window. Sometimes, it can look a little nicer to present both a yes and a no option, even if the no option doesn't have any specific code associated with it.

The Busker

That sheet music comes in handy though, as it's finally time to return to the boardwalk and talk with the musician.

examplia

@Busker #end :touch BUSKER: I lost my music so all I can do is improv, but I'm pretty good at it. Leave some gems in the case over there if you'd like to hear me play. #if sheet !1;Try this sheet music instead. #end :1 #zap touch #clear sheet #set gaveasheet BUSKER: Fantastic! I don't know if this is any good, but put some gems in the case and we can find out. #end

First, talking to the busker provides some information on how to get them to play. Then, a check is made for the flag being set from picking up the sheet music from the hotel. Instead of jumping to a label, this will display a hyperlink.

If the flag is set and the player gives him the sheet music, the flag is then cleared and a new one is set instead gaveasheet. Here we are using flags not to track if the player has an item, but as a way to mark that an event has happened.

@case #end :touch People have been leaving gems in the busker's saxophone case. !1;Donate 1 gem. #end :1 #take gems 1 tp #lock #give score 10 #busker:blow #if gaveasheet good #bad #end [...]

Next, the case allows the player to pay one gem to hear some music. Whether the song played is from the sheet music or from the improv is dependent on whether or not the flag gaveasheet has been set or not, requiring the player to not just have, but to turn in the sheet music.

Note the position of #lock being after the #take! If it was the other way around, not having a gem would cause the object to never reach #unlock!

@Busker [...] :blow #char 2 /i#char 1 /i#blow

Making a donation send a message to the busker to jump to this label where they will begin to animate between the two different smiley face characters.

@case [...] :bad #play w---b.c /i/i/i/i/i/i/i/i/i/i/i/i/i/i/i/i /i/i/i/i/i/i/i/i/i/i/i/i/i/i/i/i #busker:done #end :good #play icd#cd#sf /i/i/i/i/i/i/i/i/i/i/i/i/i/i/i/i [...] #busker:done #unlock

The case object is still running though. Based on the status of the checked flag it either jumps to :good or defaults to :bad. "Music" (depending on what label wins out) is played before the object tells the busker object to stop animating, and then unlocks itself.

As both labels end with the same two lines, it may be wise to instead add a :done label to the case itself called from both the music labels in order to avoid repetition. Not only would this save memory, but it also means if the code changes during development it only has to be updated in one position rather than two. Think about how your code is structured and what may save you from future headaches!

@Busker [...] :done #char 1 /i BUSKER: Howzat? #end

Finally, the busker returns to his regular character and asks for some feedback. And with that, a series of objects work together to create a scene.

And That's Everything!

That ought to cover it! Some hopefully straightforward examples of ZZT-OOP's commands presented in ways relevant to ZZT game design. Examplia hardly covers everything you can do with ZZT-OOP, should be helpful as a reference to help jump start your own ZZT creations.

If you find yourself with questions, the two best things to do are to experiment and to ask! Just tack on another board to your world in development and see if something works, doesn't work, or behaves unexpectedly! Or, if you want to talk with more experienced ZZTers, the Worlds of ZZT Discord is open to anyone with an interest in ZZT and has a number of people happy to provide the help and guidance you're after!

Page #2/2
< 1 2

====== A Worlds of ZZT Production ======

The Worlds of ZZT project is committed to the preservation of ZZT and its history.

This article was produced thanks to supporters on Patreon.

Support Worlds of ZZT on Patreon!
Top of Page
Article directory
Main page