The Devlog
ANYWAY.
For once in my life I actually made quite a few backups over the course of the engine's development. The process of creating Wordles of ZZT was, as is often the case with my ZZT worlds, more fun than actually playing the finished product. It was a product of many afternoons, and many breaks so as to actually get things done that I should be doing. I've sorted my exported boards by date, and would like to go through them and describe the process, the conundrums, and the realizations that had to happen in order for this ZZT game to exist.
Awordle1.brd - Jan. 13th
The earliest exported board takes a very different approach compared to what ended up being used. In this original attempt, selecting a letter would also encode it, allowing the position and letter of your input to recalled at all times. Keys and gems are used to encode this information with those elements chosen for their short names and ability to have arbitrary colors set.
Torches are used to store the number of guesses remaining. The actual word is stored using flags in the same format as the final version. In this case, the test word early on was "TRAIN", stored as T1, R2, A3, I4, and N5
A very early attempt at parsing here is really trying to do quite a lot with one object. The parser object turns the keys and gems into bombs and doors to read an individual value with the door color determining which "page" of letters to work with when checking the bomb's color. Only the letter "A" has a code path, which then checks for flags A1-A5 and gives one gem or two, which I would assume is meant to encode the color, but in this code an A as the first letter in the solution gives one gem while all others give two, which doesn't match "TRAIN".
On this board, a guess of "AAAAA" results in the first "A" being marked yellow before erroring out.
Bwordle2.brd - Jan. 13thThe same framework is expanded. The parser object now has codepaths for checking against all letters in such a way that the intent is to make the engine capable of handling any word with only the five flags set for each letter in the solution needing to change. This would make it very easy for anybody to make their own puzzles without having to do tedious editing by hand, or using external scripts to generate a ZZT board.
This looks a lot more robust. "AAAAA" correctly returns its one green, but runs into its first issue that all the other tiles have been marked yellow, when they should actually be red as the only "A" in the solution has been placed properly. At this point I was still very much trying to figure out what information needs to persist and for how long.
If you're only working with green and red letters though, it's looking great.
Of course, one downside to this super moddable word approach is that even this is more than 19,800 bytes. This all-in-one approach wouldn't be feasible, though that wouldn't stop me from trying for a bit longer.
CwrdleOK.brd - Jan. 13thBeginning the baby steps of memory conservation as I didn't realize how much more effort would be needed to actually process everything. This board removes some text just to free a minute amount of space, bringing the board size down closer to 19,500 bytes.
DwrdleOK.brd - Jan. 21stA row of objects with no code has been added. I'm not sure what role they were meant to serve, but there is clearly not enough memory for them. Something is mid-adjustment here as the object that set the solution flags is gone, and the main guess grid fails to do anything when a letter is input, though the parser still encodes the letters. I don't think "OK" should be in the file name when this is a step backwards in terms of what's functioning.
Ebrokel.brd - Feb. 2nd"Brokel" is a bad sign. The five smiley objects have code now. I've realized there's a problem when a word has the same letter appear more than once, so the solution has changed to "SILLY" to better work with this scenario. The smiley objects will try and take two gems and move north if they can as a physical counter of how many times a letter has appeared? If they can't, they take one gem before marking the letter as green, or if they can't take any gems, they mark the letter as red.
At least that's the theory. This is "Brokel" because it clocks in at 20006 bytes and thus cannot be played in ZZT 3.2. So ends Wordles of ZZT Mk. I.
Fnewnoylw.brd - Feb. 2ndStarting over from scratch and trying some new approaches to things. The word remains "SILLY", and the input object now sets the flag and then zaps labels named :S
, :I
, :L
, and :Y
. The reason for this is some safe assumptions have finally been realized. If a letter isn't used in the solution, it's red. This is a constant, so now the input labels automatically tell the count object in the top left to place a red invisible to the south (below our numbers in text) and move to the next letter's position. The used letters have a second copy of the label that instead checks if your guessed letter matches the position of that letter in the solution. If so, the count object places a green invisible south and moves on to the next letter. If a letter is used in the solution, in the position it was placed, it's green.
When the time comes to parse, the parser can check if it's blocked to the south. If so, it's either red or green based on the color. A green awards one point to track progress towards guessing the entire word.
None of this information is sent to the main grid, so no information is recorded as the player moves on to the next guess. A codepath for handling letters in the solution but in the wrong position remains stubbed and does nothing but make the parser continue advancing. On the bright side, the lack of encoding and decoding specific letters drops the memory footprint down to 9,000 bytes.
G552pm.brd - Feb. 2ndThis is where I was convinced I was on the right track.
It's also where I decided to focus on myself first and stop using invisible walls for things, intending to make engine components and data invisible when the project was done. The purple arrow tracks the basic information, placing a key of green, red, or yellow based on if your letter is perfectly correct, incorrect, or mispositioned.
The second area tries to count how many yellows for each letter you've gotten. The duplicate "L" is missing, and walls block the movement of these letters beyond the maximum number of yellows you should be able to get.
Looking at this now, I think with the understanding of how yellows actually work now, this might have been a salvageable system. Of course, the story of this engine's development is thinking I had cracked the case until I implemented it and ran into a situation I hadn't considered. All I can safely say here is that this particular version is on the right track still.
I've also shifted over to using slimes to mark my letters. The previous linewall method looked cleaner, but meant a lot more code as the only way to target just the bordering linewalls was with eight #put
commands for each color and some tweaked X/Y-Steps to make the object capable of addressing diagonals. The slimes meant big memory savings, and I've grown to like the light animation they provide similar to the official Wordle's flipping of letters in contrast to the instant feedback of Hello Wordl.
My last ZZT engine of immense complexity was Zee!, and I had a focus on trying to make the engine presentable as well. This is the first hint of that with Wordles of ZZT.
H619pm.brd - Feb. 2ndOh no! Where did the letter count objects go?
Oh no indeed. I'm trying to be too clever here. A comment in the parser object's code states that "first multiletter uses purple second uses cyan". There's recognition that letters that appear multiple times will need special parsing, and giving each "multiletter" a dedicated color for its yellows might let me roll all this counting information into the parser.
It absolutely won't. One thing that is being recognized here is that you can only have two "multiletters". I'm not sure if this version was intending to discard instances where a letter appears three times ("SASSY") or more (not any English words, but with no word list you could potentially want to do a generic Mastermind engine with support for four or five identical characters). Ultimately I felt it was important to support triples. If I was accepting the compromise there, I could have compromised doubles as well and probably had it go unnoticed. Once I had that properly implemented triples, support fours and fives was not a problem, other than a very minute amount of extra space (relatively) needed.
Nothing related to these extra colors is implemented beyond a popup saying they're going to be addressed. A guess of "LLLLL" results in the two correct greens on the main grid, with the other letters never getting any coloring. Recognizing the need to go back for additional parsing afterwards when yellows appear is a major step in getting this engine working.
Ifeb22.brd - Feb. 22ndNearly three weeks pass before work resumes. Often on days when I was working on this, it was pretty much all I'd do for hours, and other responsibilities whether ZZT related or household chores would get neglected. Oops.
What's important here, is that during that break I had a sudden unexpected insight late one night. The trouble I'd kept running into was trying to figure out how to determine when a potential yellow should actually be yellow, and when it should become red. The epiphany was two simple rules:
- If you have all the greens for a letter => you have zero actual yellows.
- Actual yellows = Min(potential yellows, # of times the letter occurs) - # of greens
For a word like "SILLY", guessing "LLLLL" would turn all the yellows green due to the first rule. Guessing "LLXXL" should clamp the potential yellows to two with the Min() function, subtract nothing, and turn the rest red. Guessing "LXLXL" should clamp the potential yellows to two with the Min() function, subtract one due to the green, leaving you with one actual yellow with the last potential yellow turning red.
It may be worth mentioning that at no point during development did I look at any JavaScript for Wordle or its clones. I had assumed this would be irrelevant as the approach making a game like this changes dramatically when you have even the most basic support for strings and arithmetic. I would not be surprised if such a formula was actually being used by code written in other, more complex, languages.
There's still trouble here with this one though as recognizing a need for such code paths was only being considered by special objects added to handle multiletters. This would let me handle "LLLLL", but it was only here I realized the same issue can occur when there's just one letter. Going back to "TRAIN" as the solution, "AAAAA" also needs to clamp your maximum yellows just the same.
Here with "SILLY" you can see the false yellows persisting as it's incorrectly assumed that this is only a problem with multiletters.
Jgod.brd - Feb. 23rdHere we have yet another busted attempt. This time each letter has its own parser object, with the second "L" being just locked into place with plans for it to just be skipped over in parsing if it in some way identified itself as a duplicate letter that would already have been handled. In this board's state, guessing "IIIII" results in a new object "Arith" (named for Arithmetic, and also almost Final Fantasy 7) proclaiming it's about to do math, and then not actually doing it. The plan here was to offload the math to an object in order to share the code for all the other purple arrows without having to actually duplicate it.
Krevamp.brd - Feb. 23rdSome layout changes. The row of spikes here indicates that I've realized I can just have one wide parser object rather than need one per each spike group as they'd later be described as. This is just a work in progress to shuffle the layout around as some consideration is needed for objects like the guess counter which uses an adjusted X-Step to instantly jump into the next position when it's time to move to the next guess.
LFeb23Yay.brd - Feb. 23rdOptimism in the filename! The pieces are finally coming together now, though there's a (now) hilarious over-complication where what would become the lone object to return "MAX#" flags is here five objects that handle switching when the parser passes over them. This is a bad solution that uses five objects when one is plenty, yet it should work. It doesn't though, with something somewhere being busted and a guess of "SILLY" leaving the second "L" without any color and other guesses sometimes causing the parser to crash into a wall unexpectedly.
Despite the filename being optimistic, the desperation shows in the board titles with "2/23 Wordle FINAL PLEASE Real 2", sequel to "2/23 Wordle FINAL PLEASE Real", sequel to "2/22 Wordle FINAL PLEASE". Oh there are so many board files to go...
Msoclose.brd - Feb. 23rdSeems pretty identical to the previous, though with the addition of text that pops up to try and explain what the parser is doing as it moves. Just trying to better debug the previous board. Something seems screwy with the double "L"s, and entering a correct guess marks everything green except the second L which is now red.
Amusingly, even guessing the second "L" in the right spot without any other occurrences of the letter still results in a red.
polish.zzt - Feb. 24thSuddenly, rather than exporting boards there's the creation of a brand new ZZT file. That's polish as in "make it shine" not as in "Poland". This file contains boards with two different puzzles, "SILKY" and "SILLY" with the names of the boards also describing the speed as "Slow" or "Faster" and whether or not the puzzle is "Vocal" about what it's doing.
Notes are taken and stored on the board about what work needs to be done to change a puzzle solution. It also seems to work! These boards make the switch to using a single object to handle "MAX#" flags used in the final product. It's polishing because it's finally finally working. There's still quite a bit to be done, but these examples show off "YLSIL", "LLLLL", and "SILLY" behaving exactly as intended! It's still not entirely done, as minor additions need to be made to support lengthier repetitions of letters. A followup "wip.brd" changes the puzzle to "SASSY" and passes with flying colors.
This version also has attempts at sending the final parser off before the main parser is complete. At least in the guesses for this GIF, it gets away with it.
testpuzl.zzt - Feb. 24thThe thing is, there's not exactly a ZZT test suite out there, and I definitely don't want to commit to a template board to make puzzles from only to have to redo the whole thing if a bug is caught later.
I assembled a small test world, quietly uploaded it with a web version of Zeta, and asked some non-ZZT friends to try it out. Six puzzles are included, "TORCH", "BOMBS", "SILLY", "DEEDS", "PLANE", and "SASSY". In addition are three debug puzzles to handle the extremes of repetition with "AWWWW", "WWWWA", and "ZZZZZ" as solutions.
Well, both testers managed to immediately break things by entering the first puzzle, and accidentally leaving it resulting in the main menu board clearing all the flags and breaking the first puzzle. They also took their time mentioning this, and I was scouring code, and wondering if some bizarre Zeta issue had been uncovered or something as there was surely no way they'd both have ran into a broken first puzzle only to eventually get them to admit that they "might have" left the first puzzle by mistake on their first attempt.
Despite the massive time wasted it was at least a good reminder that for non-ZZTers, dropping them into a ZZT engine is incredibly disorienting. (A help object on the menu does say not to leave puzzles though!!!) Despite the initial hurdle, the rest of the testing went smoothly, and now the only thing stopping me from making a template was wanting to include as much as possible on the board before locking things in.
Originally I wasn't going to use Zookeeper, and was just going to do some basic find/replaces with the strings in the file itself. I'm glad I didn't in the end as Zookeeper would make unexpected bugs easier to adjust, but even so the puzzle generator script still made a lot of assumptions for things like stat indices that needed find/replace operations in the first place.
As it would turn out, I did make a big blunder after this point. I starting working from one of the hand crafted test puzzles, forgetting that only the "ZZZZZ" puzzle actually had the code paths to handle that many letters being repeated. This screwed me over when I was trying to add feedback on letters as I was missing some mandatory code. It became quickly clear when experimenting with potential methods to implement letter feedback that while I thought I had a reasonable amount of memory, such a thing was going to be rough going, made worse by the missing code giving me slightly more space than I actually had.
toobig.brd - Feb. 24thThis board takes up 21,580 bytes and lacks support for a letter occurring three or more times in the puzzle! The first proposed solution was of course going to be the fanciest, using cycle 0 bombs to draw a keyboard, with hidden objects next to them that were intended to just put bombs of the correct color on top. With nothing more than the most obvious code this was already too much. There's nothing here even attempting to get from making a guess to getting those objects to recolor the bombs. It's a dead end.
manual.brd - Feb. 24thThen I remembered "You Fuckin' Make It Then". I was really doubting it would be possible to make one of these keyboards in any form and have enough memory to implement the code actually needed. After all, it hadn't been since the initial attempts that actually recording individual letters that make up a guess was a thing, and now suddenly I need it again? My horrid little plan was to instead surround the player with objects, one for the keyboard, and the other three dedicated to bringing up a similar letter selection but manually marking the letters on the keyboard. Make it the user's problem. That's the programmers way.
Alas, this was still just barely over the limit. It perhaps could have been done, and with the optimizations discovered writing this article that's even more likely. I think I realized my issue with the missing "MAX#" support for more unusual words. This version is lacking support for five letters, though not four. I think I decided that was an acceptable loss if it meant getting even a shoddy key marking system in place.
ready.brd - Feb. 24thIt was not meant to be though. Instead I pivoted to making the game board look a little more cheerful. The home office was born, with authentic Super ZZT style borders which I think look really nice, but they do burn stats to use. Regardless, this board is below 19,000 bytes. The dictionaries are coded, though many of the final animations and results based events don't yet exist. I was also a little unhappy with how much blank blue space there was.
Alas, somewhere along the lines of trying to make a giant keyboard, I managed to break something and had to start stitching things together from older working versions and this new office layout.
pristine.brd - Feb. 27thRepair work went okay and the board started coming together. Putting a large calendar in the corner where there was just a bunch of empty background space before helped define the board a bit more. At this point, what work remained was primarily just rearranging the board to my liking, as well as the liking of objects with custom X and Y-Steps, which I would frequently cause to break
TEMPLATE.zzt - Feb. 27thHere's the final version used for actual puzzle generation! The calendar has gone from daily to monthly. The guess history has used what little space remained. It has a bug where it breaks for four of the five letters when recording the final guess. There's an accidental dark blue solid by the guess area. The puzzle solution isn't displayed when you lose. Oops
The saving grace is at least that these were pretty minor changes after having generated all my puzzle boards and stitched them together. The wall was erased. I had the good fortune of the guess history objects being the final stats, so after fixing them on one board I was able to just copy and paste those objects on every other board without worry. The puzzle solution was a last second addition not done most of this article was actually written. That was my last moment of tedious work on the project.
And So It Goes
I hope you enjoyed getting a glimpse at how this monstrosity works and how it came to be! I don't think anybody will be getting their Wordle fix from this version, but sometimes creating a game isn't about the final product, but the journey of making it. Between this and Zee! I've had to do plenty of thinking about counters versus flags, versus invisibles, versus zaps/restores. I hope folks find it impressive if they can't find it fun. (They darn well better after hearing that folks in the MegaZeux Discord were already surprised that it was possible to make in Weave, and quickly discounted the idea of a 3.2 compatible version of the game.)
As ZZT ages into its thirties, and development on enhanced ports like Weave allow ZZT to do more that we don't forget that ZZT's history is full of the impossible becoming a reality. I hope to do work with Weave in the future, but there's something about the harsh limits of the original ZZT that make it fun to see just how absurd creations can be.