A Continuation of rack8.
Published 29 November 2020 at Yours, Kewbish. 1,544 words. Subscribe via RSS.
Introduction
After maybe a month of intermittent work, it works!
If you’re not sure what ‘it’ is, you’ll probably want to read the first blog post of this microseries. In it, I touch on the experience of my first couple days with Racket and Chip-8, a game emulator. It turns out that trying to figure out how to piece together the Chip-8 documentation and synthesize it into Racket wasn’t too bad, after all. Oh, the joys of what is essentially a glorified set of print statements. I’m very happy with how rack8 (the pet name I’ve been calling it) has turned out1.
In the above blog post, I left off with working opcode interpreting and somewhat-working internals. Back then, I was on a bit of a hunt for the proper graphics library, as well as how to manage taking keyboard input without hanging. It was a toss-up between charterm and racket-ncurses – both would do what I wanted.
This is the devlog, of sorts, of the remainder of development, and my next steps.
Managing Display
I initially thought the ncurses binding would be a better fit. ncurses was something I’d briefly tried before in a toy Python project, and it didn’t seem difficult to learn. It also explicitly stated an option for non-hanging input, which would be a must, and made the library very attractive. It’d be impossible to try to rig together a short delay system, at least with my limited experience. So, I raco pkg install
ed it, and was on my merry way.
It turned out, however, things were not as straightforward as they were advertised. (Are things ever?) First, the ncurses FFI was something like a year and a half old, and relied on libncurses5
. I had libncurses6
, and symlinking (not that I knew what to symlink) didn’t seem to work. There seemed no way to get the correct library besides manually make pkg
ing it from the AUR snapshot, so I also attempted to learn how to do that. Surprisingly, it worked, but that wasn’t the end of the issues with the FFI.
It also seemed to escape from the window each time a key was pressed, and spit out some errors that I couldn’t manage to trace. I’m unsure if this was an issue with my implementation, though I copied directly from the given example, or from the package itself, or from the ncurses5
I managed to MacGyver.
I briefly flirted with the idea of just doing things with (printf)
s and hanging input, but I figured that wouldn’t be very satisfying either. Seeing as the ncurses FFI, which was technically more recent than charterm, was already outdated (or perhaps just not working in its abandoned stage) wasn’t quite acting as expected, I was hesistant to try charterm. However, I didn’t have any better solution, so one raco pkg install charterm
later, I braced myself for the worst.
After some issues with slow, character by character display, I finally had some output. At this point, it was just blocks of seemingly random Ms everywhere. I had two issues at this point: not getting newlines, and not drawing from memory properly
charterm seems to have an issue with printing newlines with its (charterm-display)
function. Initially, this was an error on my part (all errors here are errors on my part, to be honest) – the documentation explicitly stated to…
avoid trying to send your own control sequences or using newlines
…but clearly this didn’t register (Chip-8 pun not intended). I then switched to printing each line (or horizontal row) of the display onto the current charterm display, with a newline in between. I had some issues with the correct placement of the end brackets for for-loops, leading to a lot of time wasted trying to change parentheses around.
Two smaller tips here:
- one, maybe properly indent your code so parenthesis groupings are properly visible (oops)
- and two, don’t
(with-charterm)
every operation. I found that, when I tried this, there’d be a visible delay in printing each line / character, as opposed to just one large operation. I’m not entirely sure why this is either. Just wrapping the entirety of the inside of the graphics display function in the(with charterm)
was much more efficient and effective.
With regards to the DRAW opcode itself, I remember adapting much of it from this pastebin, which I found off a (now lost to the depths of search history) Reddit post. Before that, I had a very janky solution that was most definitely not up to the technical implementation. It was a rather pleasant surprise to see a little Connect4 counter on my screen, after much work and suffering2.
Keyboard Control
In comparison to the display work, keyboard mappings were a lot easier to develop. While charterm doesn’t have a completely blocking-less option, I managed to use a delay of 0.001 seconds3. My test ROM used was Connect4, which ends up only using the blocking keypress opcode, if I recall correctly, but the delay should be theoretically imperceptible even if a shorter reaction is needed. (On that note, I should probably test with more ROMs. Mental note to add to the todo list.)
Chip-8 itself has a very unique4 16-key mapping that I had to figure out how to model. My implementation used a hash-map5, which I mapped to (apparently what everyone else used):
+---+---+---+---+
| 0 | 1 | 2 | 3 |
+---------------+
| 4 | 5 | 6 | 7 |
+---------------+
| 8 | 9 | A | B |
+---------------+
| C | D | E | F |
+---+---+---+---+
charterm, helpfully (Racket is also to thank for this) returns nice keycodes, so I was able to map these very easily, and even add an extra condition for a global quit key.
Conclusion
So is the state of my journey with Racket. I’m probably not going to tinker with rack8 much more, though I plan on loading some more ROMs up and testing if all my opcodes are sound. I’ll also make the repo public on GitHub at some point, if anyone is interested in perusing my very badly written Racket. When I become slightly more familiar with Racket, I might write a short guide on how to make a Chip-8 interpreter in it: the only articles I can find for writing Chip-8 emulators in anything remotely similar is ‘Chicken Scheme’. I have no idea what that is, but perhaps making a Racket guide may be useful, both for others and for my own learning. (The Feynman technique is a thing, right?)
In making rack8, I’ve also managed to steer myself entirely off the track of my original plan for CPSC 110 review. The material’s still there on edX, so I should probably take the time to do the course properly. However, I think this little side excursion has been a valuable experience, both in terms of learning Racket, and having some fun with emulators. Overall, I find when I start with a hands-on project, and the higher-level details of a concept, I learn things better. I suppose rack8 can serve as my introduction to Racket syntax (so when I start properly studying, it won’t be as jarring) and some of its concepts.
My next steps, in this analogy of projects, would probably to do more theory and ‘dictionary-definition’ work. I was planning on doing more algorithms (essentially just the coursework of the various CPSC courses UBC has publicly available) anyway, so I think tackling that will be my next step. Racket has been surprisingly not that painful, and rack8’s been a charming little project to tinker with. It might be interesting, once I have more time / finish the never-ending list of things I want to program, to investigate more emulator development, but who knows what I’ll end up doing.
-
The experience of finally getting display to properly work was somewhat akin to getting the Liquid Galaxy system set up: I had no idea what I was doing, no documentation (that I could understand), and there were no clear pointers as to where I was going wrong. ↩︎
-
An interesting story – I was originally very confused as to why the Chip-8 was printing just blocks of 8 characters, instead of one at a time. I tried everything, including completely rewriting the DRAW opcode implementation, copy-pasting things around. In the end, the issue was that I had
bitwise-and
ed something I should havebitwise-xor
ed. It’s interesting how three letters can waste so much time, but I suppose this is the essence of debugging. ↩︎ -
Actually, now that I think about it, it might be possible to set a zero-second blocking timeout. I’ll try this and edit the post a bit later with how it’s gone. ↩︎
-
Well, unique to us, I suppose. Apparently, these were quite common ‘back in the day’, and I see a lot of 16-key keypads around, so they must be useful for something (?) ↩︎
-
As an aside, Racket hashmaps (or dictionaries, I’m unsure of the technically correct description) and their syntax are very unique. I’m used to the
[key]: [value]
representation, but Racket just puts a.
in between, and surrounds keys with parentheses. Though I suppose the parentheses are very on-brand for Racket. ↩︎