https://kokoscript.com/2024/015.html
Home /usr/foxpeople/koko
foxgirl's blog gallery
games programming
fonts store
fursona more
= buttonz =
kokoscript
^ there's mine! ^
= cool ppl =
ViktorTheGreat CherryPixelBun
Roxie Damien
CD-ROM Journal Virtual Moose
Jae Mel
echoparallax MewMus
Foxball Techokami
Sleeps-Darkly
= other =
Get Firefox I was on cohost
Crouton VIM - Vi IMproved
WordHopper
/usr/foxpeople/koko/site-root/2024/015.html - Netscape Navigator 4.76
/usr/foxpeople/koko/site-root/2024/015.html
Back Forward Reload Home Shop
[hdrBlog]
Blog > Post
[socialImag]
A Year of WordHopper - Development Retrospective
02 Nov 2024
---------------------------------------------------------------------
Editor's note: I originally started writing this as part of a larger
1-year anniversary celebration for WordHopper. However, a combination
of less free time + suddenly many more side projects to take care of
pushed the anniversary out a bit, to the point where things will have
to happen piecemeal for now. I decided the retrospective dev journal
would be the best place to start, just to dust off the blog a bit :)
---------------------------------------------------------------------
At the start of 2023, I decided to set a resolution for myself:
release a game project. Didn't matter how big or small, it had to be
something.
I rarely do New Year's resolutions. Hell, I didn't set one this year.
I'd set similar gamedev-related goals for myself in years past, but
they never stuck. So I gave up trying to hold myself to anything. And
yet, little did I know I'd eventually end up fulfilling my 2023
resolution when I set it. Just... not in the way I expected, for
better or worse.
---------------------------------------------------------------------
The story with WordHopper starts pretty early on in 2020, just before
the pandemic. It was the start of a new semester at university, and I
was between game projects; I'd just shelved the FoxGame prototype,
and Endless Attack REMIX was out another year, so I wasn't really
working on... well, much of anything.
Around that time, I was also revisiting PopCap's Bookworm, a
childhood favorite game of mine. Upon doing further research, I was
displeased to see that it'd been erased from all storefronts by this
point. EA (who now owns PopCap) even refuses to acknowledge the
game's existence! Noticing the severe lack of word games that weren't
your typical ad-riddled mobile game sludge, I decided to take matters
into my own hands.
Recognizing the appeal in Bookworm's core "letter stringing"
mechanic, I started work on a new game built around the same concept.
In place of Bookworm's puzzle-like, slower gameplay, I wanted to aim
for something more arcade-like, with a faster pace of play. I also
decided to eschew Bookworm's staggered grids in favor of a more
traditional grid.
And thus, WordHopper was born.
Screenshot from an early prototype of WordHopper
And promptly died two days later.
I'm... not quite sure why I shelved it so quickly! I definitely had
the skills needed to finish it in the engine I was working with, and
was looking for a small, easier project I could reasonably see myself
completing. And yet, I still lost interest. Oh well, so it goes...
---------------------------------------------------------------------
Off the shelf
The story picks back up at the start of 2023, when I set that
resolution for myself. The plan I had in mind would be to finish the
first "chapter" of the RPG project I was working on at the time (
thyme), and release that as a demo by the end of Summer. Failing
that, I decided plan B would involve picking WordHopper back up,
finishing it in a few months, and releasing it on mobile- just to
have something to point towards if I wanted to look for work in the
games industry (spoiler alert, this didn't happen).
Development on thyme progressed smoothly for a bit, but schoolwork
and cancer in the family slowed things down tremendously. All I got
done was a near-complete dialog language interpreter, before Summer
started and I found myself fighting with the engine to get things
working in the way I wanted (namely, asset loading/memory
management). Ultimately I decided it was more work than it was worth,
so thyme's fate was up in the air by this point.
Not too long after, I learned of an upcoming DOS game jam. This was
where the script completely flipped. With thyme starting to languish
in development hell, pivoting to a smaller project for the time being
sounded like a good idea. And, having built up a solid (if a bit
lacking) foundation for DOS development with REMIX, a DOS-themed jam
was a perfect fit for me. So, I decided to take a walk, and
brainstormed for a bit.
One of the first ideas I had for the jam was an overhead
action-puzzle dodgeball sort of game, where the player would pick up
and throw objects to activate things and take down enemies.But
because the idea was a bit too open-ended, would take time to
solidify the design, and would require a lot more work on the engine,
I decided to move on. Another idea I had was to bring back and flesh
out Gap Jumper, an endless runner prototype I made way back in 2013.
But I wasn't too enthusiastic about that, so again, I moved on.
Eventually, things clicked; WordHopper was my plan B anyway, the
design work was already complete, and wouldn't require much from the
engine (in fact, I ended up removing more from the engine than I
added); why not rework it as a DOS game?
At 7:23 PM on May 28, 2023, I copied REMIX's source over to a new
directory, and began gutting the game-specific parts. WordHopper was
my new "main" project, now under the codename parsley. Deadline was
July 16; I had a month and a half to finish it. No sweat, right?
---------------------------------------------------------------------
Early development
The first few weeks of development were relatively uneventful, and
admittedly a bit slow; I'd pretty much go about my day, and whenever
I had the time, I'd spend an hour or two working on the game. Having
learned my mistakes from prior projects where "set dressing" would
take priority before the actual game, I decided to channel my focus
on getting the game's design fully implemented before touching any of
the visual elements. So for a while, WordHopper looked like this:
Screenshot from a later prototype of WordHopper, now a DOS game. The
letter grid is represented by pink squares with letters in them.
This was after the first day of development; the script to generate
source files for the game's dictionary and RNG weight values,
playfully titled Dictomorph, had just been written. Over the next
couple days, focus shifted to the game's "failsafe" system, whereby
the game would choose a set of "guaranteed" words to hide in the
field (in the event RNG fails to produce anything usable to the
player).
Screenshot from a later prototype of WordHopper. A start letter is
picked for the center of the grid (T), with failsafe word "leaves"
picked and placed in the top left of the grid.
The next day, I added a generic stack to the engine in order to
support my next targets: paths, player movement, and word validity.
Any time the player were to make a move, the coordinates and
corresponding letter from the field were pushed onto a stack, and
when it came time to render the path or check if the entered word is
valid, that stack would be traversed.
Screenshot of WordHopper just before the test field was replaced,
showing a letter path and stack.
It seems like I took a week-long break after implementing stacks, but
from there I was able to knock out a few things one after another
over the next few days: the aforementioned word validity checks, a
(basic) gameover screen, scoring, an additional "jump" control scheme
(numpad was the sole control scheme until then), and the ability to
pause the game.
Somewhere in the middle of implementing those, I considered the core
game "complete" enough to the point where I marked the first alpha
version, v1.00a. I gave a build of this version to my girlfriend to
get some ideas for the game's music (I somehow knew in advance I
wouldn't have time to write it), and left her to it. This was
followed by v1.10a, before it was finally time for me to move onto
set dressing (or as I called it at the time, the "fancy field").
So for those keeping track, it was now June 15. I'd spent a third of
the jam getting the core game together, which wasn't bad. But for
what I had in mind, there was still much more work left.
---------------------------------------------------------------------
Fancy field
This was probably the most interesting part of the game's alpha
cycle, both in terms of getting to iterate quicker thanks to the base
gameplay already being in place, but also because I completely
Fucking Forgot what my target hardware was.
Screenshot of WordHopper with a near-final version of the field. The
path doesn't have connecting segments, only circles drawn over
visited letters.
Screenshot of WordHopper with a near-final version of the field. Path
segments are in place, but diagonals produce a wide checkerboard
pattern instead.
Despite a minor hiccup in getting diagonal path segments drawn (seen
above), it didn't take long to get the fancy field draw routines
implemented. So, I decided to mark it as v1.20a, went to test on real
hardware (a Pentium 75 system), and...
WordHopper running on real hardware. The word "pain" is on the letter
path, and a framerate graph in the upper-right of the screen reports
the game running at 14 FPS.
Oh... yeah that's not great. That, combined with a bug I'd just
discovered where some words were mistakenly always deemed invalid
("dead" was one) meant I had both a performance and a logic issue to
take care of. I decided to start with addressing the fancy field
performance issues first.
Unfortunately, the first thing I did was change the pattern of the
path when it's valid from a diagonal stripe to a horizontal stripe.
The way I implemented the diagonal pattern was pretty naive; I was
just drawing diagonal lines across the entire screen in a blended
mode (such that they'd only end up on the path). Aside from how much
wasted effort this is for the CPU, it's not cache-friendly at all; so
I switched to horizontal lines. I also limited the pattern to a
bounding rect based on the min/max coordinates found in the player's
path. In hindsight, the diagonal pattern could've just as easily been
implemented using calls to hline and a bounding rect instead, and had
similar performance.
The next performance improvement involved redoing how horizontal/
vertical path segments were drawn. For whatever reason, I was using
groups of calls to allegro's fastline- much like I was with diagonal
segments and the old diagonal pattern- which, contrary to what the
name implies, aren't fast if all you need is a horizontal line. And,
as it just so happened, I was using these fastlines to draw a
rectangle... which would be better served by fillrect, which
essentially does several hlines under the hood.
The calls to fastline for the diagonal path were also switched out
for a single call to polygon. At first I tried using hlines for this,
but it didn't look great as the shape needed to follow the tangent of
the circles that made up the "nodes" of the path. This, plus some
culling on off-screen letter sprites and replacing the path circles
with sprites, led to the game running at a full 70 FPS on real
hardware. Phew!
And so, freed from performance issues, I wrote a placeholder title
screen, added in additional difficulties, "zen" mode, aaaaand...
Forgot to work on the game.
---------------------------------------------------------------------
July 8
8 days before the jam deadline.
The game was technically still in alpha. Despite trying to continue
work on it, I'd been tied up in prior obligations the preceding few
days. All I could do was make a checklist of what needed to be done.
Whether I liked it or not, I was about to self-impose my first
crunch.
Checklist: get basic menu working, take inventory of memory
allocation across gamestates (do last?), implement sound effects,
implement music, implement grid slide animation, implement in/out
transition, implement bnuuy, implement typing controlscheme, jump
control scheme aim hint, make tutorial gamestate, prettify title,
credits gamestate, fix certain words being invalid (add checks to
dictomorph), alternate weighting method? (lowest possible priority),
bigger dictionary, filter dictionary, readme and other docs n stuff,
demo version? (small dict - filter that, 10 word limit, no HS, no
modes aside from timed easy), packaging for win/mac/linux, packaging
for web. only a few of these are checked off.
The checklist on the 10th; imagine everything being unchecked
The following week essentially boiled down to chipping away at the
checklist, so I'll try to breeze through what happened when.
* On the 8th, I put together the tutorial and credits screens.
* hold on im going to break out of the list for this one
On the 9th, I finally tracked down the source of the "invalid valid
words" bug. Initially, I thought this was an issue with the binary
search done over the game's dictionary. When stepping through the
search routine with a bugged word, I noticed one of the comparisons
threw the "tightening" of the search bounds away from the target
word. Upon looking at which words were being compared, I found my
answer: the dictionary wasn't in sorted order.
Because the origin word list contained words with accents, these
accented letters were swapped with their non-accented variants in
order to work with the design of the game. Although that word list
was sorted in alphabetic order, words with accented letters were
outliers; they didn't follow the same order had the accents been
removed.
Because Dictomorph didn't verify the order of the word list, the game
would end up with a "mostly" sorted dictionary. And because the
theory behind binary search relies on a sorted dataset, well...
Needless to say, I chucked the word list into sort, re-Dictomorphed
it, recompiled, and the previously invalid words became valid. An
extra check was then added to Dictomorph to prevent that from
happening again.
* On the 10th, I took care of some memory management between game
states (essentially freeing sprites when they're about to become
unused), a bit of high score logic, and implemented the calls to
play/loop the music (and probably tweaked the MIDIs my GF gave
me, too).
* The 11th was dedicated to the word entry transition animation.
Working with tweens in my engine is kind of a nightmare, hence
why it took a while...
* On the 12th, I wrote the driver setup screen and the save file
("profile") system (mostly ported from REMIX). I then had to tie
the latter into the game for things like high scores, zen longest
word, and driver settings.
* The 13th was largely a polishing day. The "slide" animation for
the field was added, the "jump" control scheme was given a
reticle, sound effects were sprinkled in, the gameover screen was
given its little animation sequence, and the rabbit sprite was
finally added into gameplay.
Same checklist as prior, with many more items checked off
The checklist on the 14th, down to the wire
Aside from having to track down that sorting bug, development was
going rather uneventfully; just faster and more stressful than I'd
anticipated. That was until the 14th, when I discovered an amazing
exploit: the double-hop.
I suddenly realized I was handling control schemes separately from
each other. In other words, each frame I was handling the actions for
two different input methods, without them canceling each other out.
Noticing this, I conducted an experiment: jumping by pressing space
and pressing a numpad key on the same frame. It took a few tries, but
lo and behold:
Screenshot of a near-final version of WordHopper. An invalid move has
been made (jumping 2 across, 1 down)
I was immediately faced with a dilemma: do I keep the exploit in as a
fun technique? Or do I patch it out, seeing as the rest of the game
was completely^1 bug-free? Instead, I went with a middle-ground: left
it in, but with some devastating consequences. If you have a copy of
the game, go ahead and try it out (maybe not in zen mode, though)
[tooHappy]
The last few days were, once again, uneventful. On the 14th, I added
in the exploit check and added the "warping field" effect to the word
entry transition. On the 15th, I added in the "type" control scheme
(despite personally finding it a pretty bad way to play), and
filtered out the valid word list. On the 16th- the final day- I
quickly put together a "proper" title/menu screen (including the
rabbit vector used in the promo art/title), sprinkled in some #ifdefs
to make a demo version, and zipped up the builds. 38 minutes before
the deadline, I fulfilled my new year's resolution: WordHopper was
released.
Development notes and drawings for WordHopper, primarily failsafe
placement and animation notes Development notes and drawings for
WordHopper, primarily path drawing and tile culling
Single piece of scratch paper I used during development; some
plaintext notes were also taken, but those are lost to time...
The following morning, I pushed the announcement posts out to my
socials, and stepped outside for the first time in over a week. On
that walk, I learned the macOS version was broken for literally
everyone. Not the best thing to hear after I thought I was done...
Because of course, I wasn't. It was time for v2.00, along with a new
deadline: September 9, to coincide with that year's VCFMW.
---------------------------------------------------------------------
Round Two
I don't really remember much of v2.00's development process, aside
from the very last day. I was starting from an already-established
base, had a checklist already put together to figure out what I
wanted to do for the update (based on player feedback and ideas cut
from v1.00), and had a bit more time to finish it. Naturally, I used
the first couple of weeks to take a break from working on the game.
There was a bit of a thorn in my side this round; eventually, I'd
have to manage university at the same time, seeing as my final
semester started mid-way through August. Cue me frantically cobbling
the rest of the update together on my laptop during classes and on
the train for a few weeks before the deadline (still got straight-As
that semester, so no harm done!). There was even a point where I was
working on some of the mouse-related code from my phone over SSH.
That being said, I was ramming straight into the deadline yet again,
this time a little harder than before. It was about 4 AM when I
decided to officially mark v2.00 for release, and I was supposed to
wake up at 6 that morning. As a result, v2.00 released in a tiny bit
of an unpolished state. But, nevertheless, it was done, and I was
able to enjoy tabling for the first time at VCFMW. Because yes,
WordHopper would be demoed there!
WordHopper on display at a convention table
It was pretty fun getting to watch people play the game firsthand.
I'd never conducted any playtests before (and hadn't for v1.00), so
watching people play something I'd spent the last few months of my
life on was kind of fascinating (even if I didn't take notes and
ultimately didn't get much out of it).
And so, with VCFMW coming to a close and me being a perfectionist, I
got to work on v2.01 straight after the show. Perfect timing too,
because I learned I had something important to address:
itch.io comment: "Zen mode just let me play one round and then would
exit back to DOS prompt."
The game was crashing on Windows XP.
Throughout development, I never thought to test the DOS version of
the game under Windows 2000/XP, which both ship with a DOS emulator
of their own called NTVDM. I kind of assumed that anyone wanting to
play under XP would do so under DOSBox, and because no crash reports
came in for v1.00, I had no reason to think anything of it. But,
after pulling out my trusty ThinkPad R40 and trying out zen mode, I
could indeed reproduce the crash.
Since I was travelling between home and my dorm, I couldn't actively
debug the issue (my backpack could only carry so much). But, from the
couple of weekends where I could go through a bisect of the game's
code, I was able to find which commit introduced the crash. Oddly
enough, the game was crashing on a call to realloc that dealt with
updating the longest zen word, which had sat there unchanged since
v1.00. I even tried testing v1.00 under NTVDM, and it was completely
fine. So, I decided to try swapping out the realloc for a malloc, and
that got past the crash.
Diff for replacing a call to realloc with alloc instead
Obviously this wasn't an ideal fix since it would leak memory
(though, by small amounts); that's a bug by definition, even if it's
invisible. After spending some time to fix the things I'd originally
intended to fix for v2.01, I came back to the NTVDM issue and
eventually found the root cause. Or, rather, a step away from it.
During the bisect, I never checked to see if certain game settings
would change the crashing behavior. At some point I was using a save
file from my development copy, which had the sound drivers disabled,
and I didn't see the crash happen. I didn't put two and two together
at the time, and assumed it had something to do with a longest word
already being in memory from the save file. But, after coming back to
get rid of the leak, I tried disabling sound on a whim on the latest
commit. No crash. Enabled it again, sure enough, it came back.
So, I decided to take the nuclear approach and just prevented the
sound drivers from loading if the game detected it was running under
NTVDM. There was no way I was delving into Allegro's sound driver
code to figure out the real source of the issue. And besides, NTVDM's
sound emulation sucks; MIDI events don't have good timing, and SB
samples always play at a weird rate. I decided disabling sound
outright was good enough because of this. It was either that or a
leak.
With a bit of work afterwards, v2.01 was marked and released in a
state I was finally happy with, and I decided to call the game
complete at this point. No more updates aside from bug fixes,
which... there were none (that I knew of, at least)!
---------------------------------------------------------------------
"No more updates" and other fun jokes you can tell yourself
A month later, I got an idea for a potential leaderboard system. That
was something WordHopper was sorely lacking; there wasn't much of a
community, and no one was really comparing scores. Having a
leaderboard would've solved this, but... how do you get scores from a
DOS system to the internet? Sure, TCP/IP stacks exist, but not
everyone wants to network their DOS systems. That's where I had an
ace up my sleeve:
Post from me, Nov 8 2023: "I know I've said I'm done with WH for the
time being, but there's a part of me that really wants to add online
leaderboards - though tbh I think putting QR codes in a DOS game
would give some people a heart attack"
Using QR codes as a one-way connection to the internet sounded like
such a good plan. And, given phones have QR scanners baked into their
OSes by this point, the flow I had in mind was perfect.
Unfortunately, the more I wrote down plans for this system, the more
complicated it got. Eventually, it got to the point where the amount
of code I foresaw myself writing for it rivaled the amount I'd
written for the game itself.
Mockup screenshot of WordHopper's game over screen, with a QR code
added for online score submission
Screenshot of an in-progress leaderboard page on WordHopper's website
Screenshot of an in-progress score entry page on WordHopper's website
Mockup for the modified gameover screen, and the in-progress
leaderboard/entry pages.
Marshaling score data from a QR code into a scorekeeping server is
easy, and if that was all I had to do, I'm sure v2.10 would've
actually been out by now. In reality, most of the planning for this
system dealt with guarding against the potential for cheated scores,
and in the end, it still would've been trivial to reverse; just hook
a debugger up to the game, or use something like Ghidra. Because
writing reverse-resistant code is something I've never looked into,
all I could do was add more server-side checks, increase the
complexity of the score "ticket" sent to the server, and hope that
it'd be enough.
By the time I realized there was nothing I could do to completely
thwart cheated scores, I'd started getting burnt out. I reduced the
scope of v2.10 to just an extra difficulty mode and some size/speed
optimizations, but halfway through those, I finally lost steam, and
postponed the update until sometime after the game's first
anniversary.
Life events happened, WordHopper fell by the wayside, and that brings
us to today. It's been a little over a year since the last update to
the game was released (aside from the PPC OS X port).
Looking back
I have a lot to thank WordHopper for. But, at the same time, I kind
of resent it. Let's start with the good.
Depending on your definition of "complete", WordHopper was the first
complete game project of mine. I'd been working on my own games for a
decade prior to its release, but none of them ever came close to
being a "finished" product; always small prototypes or art tests. I
had a few small games release here and there, but they were either
minuscule and derivative (DOSWord) or jokes (Elite Frog), so I
hesitate to call them complete products.
Not only was WordHopper my first complete game, it was also my first
commercial game. I don't really want to share numbers here, but for a
$2 PWYW DOS word game, it made a decent chunk of change- not enough
to live off of comparative to how much time I put in, but definitely
a bit more than I was expecting. It was enough for me to get some fun
things from that year's VCFMW and MFF.
All that being said... WordHopper was also the first time I'd
experienced crunch for a game. Which was a bit startling, since for
the past decade I'd grown to treat game development as a fun hobby,
something like creating a painting only to solve a puzzle made from
it. In the final days of v1.00 and v2.00's development, I'd work on
the game for a few hours, only to be met with a deep gut feeling
which I can only describe as some weird mix of anxiety, boredom, and
nausea. I really shouldn't have been pushing myself that hard for a
casual game jam with no competition, but hindsight is 2020... 3.
Graph of line changes to a git repo over time; the graph
exponentially increases just before both major releases of the game
Graph of total line changes to the game over time, visualizing the
crunch before each release
Going back to something I mentioned a few times earlier, WordHopper
was also always meant to be a "plan B", and not a true representation
of the skills I'd developed over the past decade. That was supposed
to be thyme. And yet, WordHopper still gets in the way of me starting
work on larger-scale projects, as I have an obligation to finish up
v2.10 (which has since evolved into a semi-engine update). Speaking
of v2.10...
...and looking forward
This post is the first in a series of "anniversary celebrations" for
WordHopper. The v2.10 update will be next, which will add an extra
difficulty, graphical tweaks, and some rewrites of the codebase/
engine. Alongside this, the game's soundtrack will be released; it'll
contain renders of the game's tracks as played with a Sound Canvas
soundfont and on real OPL3 hardware, as well as CD/"Red Book"
versions designed for the soundtrack release.
Lastly, the game will go open source. The plan is to dual-license the
game as a whole; the assets will still fall under "proprietary"
territory, requiring a purchase of the game. However, I may lax this
requirement in the future and release them under the same license as
the code (likely some form of GPL).
More info will be posted as these releases happen.
And that's it!
If you're one of the many that played WordHopper and stuck around to
the end of this post, thank you. As much as I love to see people play
the finished game, it makes me even happier when people express
interest in its development. Talking about what makes the game tick
(and the process it took to make it tick)- even though it's
relatively small- is something I really enjoy. It's part of why I'm
releasing the source as soon as I can.
I hope you all look forward to these upcoming celebrations!
---------------------------------------------------------------------
1. v1.00 shipped with a really bad memory leak but shhhh let's not
discuss that -
---------------------------------------------------------------------
cdrom
floppy
floppy
/
systemtour
dumpster