309 lines
23 KiB
HTML
309 lines
23 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='http://stevelosh.com/static/css/style.css' rel='stylesheet' type='text/css' /><link href='http://stevelosh.com/static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>CHIP-8 in Common Lisp: Input / Steve Losh</title></head><body><header><a id='logo' href='http://stevelosh.com/'>Steve Losh</a><nav><a href='http://stevelosh.com/blog/'>Blog</a> - <a href='http://stevelosh.com/projects/'>Projects</a> - <a href='http://stevelosh.com/photography/'>Photography</a> - <a href='http://stevelosh.com/links/'>Links</a> - <a href='http://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>CHIP-8 in Common Lisp: Input</a></h1><p class='date'>Posted on December 23rd, 2016.</p><p>In the previous posts we looked at how to emulate a <a href="https://en.wikipedia.org/wiki/CHIP-8">CHIP-8</a> CPU with Common
|
||
|
Lisp, and added a screen to see the results. This is enough for graphical demos
|
||
|
like <code>maze.rom</code>, but in this post we'll add user input so we can actually <em>play</em>
|
||
|
games.</p>
|
||
|
|
||
|
<p>The full series of posts so far:</p>
|
||
|
|
||
|
<ol>
|
||
|
<li><a href="../chip8-cpu/index.html">CHIP-8 in Common Lisp: The CPU</a></li>
|
||
|
<li><a href="../chip8-graphics/index.html">CHIP-8 in Common Lisp: Graphics</a></li>
|
||
|
<li><a href="index.html">CHIP-8 in Common Lisp: Input</a></li>
|
||
|
<li><a href="../chip8-sound/index.html">CHIP-8 in Common Lisp: Sound</a></li>
|
||
|
<li><a href="../../../2017/01/chip8-disassembly/index.html">CHIP-8 in Common Lisp: Disassembly</a></li>
|
||
|
<li><a href="../../../2017/01/chip8-debugging-infrastructure/index.html">CHIP-8 in Common Lisp: Debugging Infrastructure</a></li>
|
||
|
<li><a href="../../../2017/01/chip8-menus/index.html">CHIP-8 in Common Lisp: Menus</a></li>
|
||
|
</ol>
|
||
|
|
||
|
<p>The full emulator source is on <a href="https://bitbucket.org/sjl/cl-chip8">BitBucket</a> and <a href="https://github.com/sjl/cl-chip8">GitHub</a>.</p>
|
||
|
|
||
|
<ol class="table-of-contents"><li><a href="index.html#s1-the-keypad">The Keypad</a></li><li><a href="index.html#s2-architecture">Architecture</a></li><li><a href="index.html#s3-the-emulation-layer">The Emulation Layer</a><ol><li><a href="index.html#s4-keyboard-memory">Keyboard Memory</a></li><li><a href="index.html#s5-keyboard-branching-skp-and-sknp">Keyboard Branching: SKP and SKNP</a></li><li><a href="index.html#s6-waiting-for-input-ld-vx-k">Waiting for Input: LD Vx, K</a></li></ol></li><li><a href="index.html#s7-the-user-interface-layer">The User Interface Layer</a><ol><li><a href="index.html#s8-key-mappings">Key Mappings</a></li><li><a href="index.html#s9-qt-keyboard-overrides">Qt Keyboard Overrides</a></li></ol></li><li><a href="index.html#s10-results">Results</a></li><li><a href="index.html#s11-future">Future</a></li></ol>
|
||
|
|
||
|
<h2 id="s1-the-keypad"><a href="index.html#s1-the-keypad">The Keypad</a></h2>
|
||
|
|
||
|
<p>The CHIP-8 was designed to work with a hexadecimal keypad like this:</p>
|
||
|
|
||
|
<pre class="lineart">
|
||
|
┌─┬─┬─┬─┐
|
||
|
│1│2│3│C│
|
||
|
├─┼─┼─┼─┤
|
||
|
│4│5│6│D│
|
||
|
├─┼─┼─┼─┤
|
||
|
│7│8│9│E│
|
||
|
├─┼─┼─┼─┤
|
||
|
│A│0│B│F│
|
||
|
└─┴─┴─┴─┘
|
||
|
</pre>
|
||
|
|
||
|
<p>This is kind of a strange input device compared to most other keyboards and
|
||
|
controllers. Often it can be a bit tricky to figure out how to control
|
||
|
a particular game, since most don't include any instructions. But at least the
|
||
|
<em>implementation</em> is pretty simple.</p>
|
||
|
|
||
|
<h2 id="s2-architecture"><a href="index.html#s2-architecture">Architecture</a></h2>
|
||
|
|
||
|
<p>Once again we'll separate the emulation layer from the user interface layer,
|
||
|
like we did with the graphics. The <code>chip</code> struct will have an array of
|
||
|
"keyboard memory" to read from, and the user interface will write into this
|
||
|
array as the user presses keys.</p>
|
||
|
|
||
|
<h2 id="s3-the-emulation-layer"><a href="index.html#s3-the-emulation-layer">The Emulation Layer</a></h2>
|
||
|
|
||
|
<p>We'll start with the emulation side of things.</p>
|
||
|
|
||
|
<h3 id="s4-keyboard-memory"><a href="index.html#s4-keyboard-memory">Keyboard Memory</a></h3>
|
||
|
|
||
|
<p>First we'll add an array of sixteen booleans to the <code>chip</code> struct to hold the
|
||
|
currently-pressed state of each keypad key:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defstruct</span></i> chip
|
||
|
<span class="comment">; ...
|
||
|
</span> <span class="paren2">(<span class="code">keys <span class="paren3">(<span class="code">make-array 16 <span class="keyword">:element-type</span> 'boolean <span class="keyword">:initial-element</span> nil</span>)</span>
|
||
|
<span class="keyword">:type</span> <span class="paren3">(<span class="code">simple-array boolean <span class="paren4">(<span class="code">16</span>)</span></span>)</span>
|
||
|
<span class="keyword">:read-only</span> t</span>)</span>
|
||
|
<span class="comment">; ...
|
||
|
</span> </span>)</span></span></code></pre>
|
||
|
|
||
|
<p>We'll also add two functions for writing to this array that the user interface
|
||
|
will eventually use:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> keydown <span class="paren2">(<span class="code">chip key</span>)</span>
|
||
|
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">aref <span class="paren4">(<span class="code">chip-keys chip</span>)</span> key</span>)</span> t</span>)</span></span>)</span>
|
||
|
|
||
|
<span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> keyup <span class="paren2">(<span class="code">chip key</span>)</span>
|
||
|
<span class="paren2">(<span class="code">setf <span class="paren3">(<span class="code">aref <span class="paren4">(<span class="code">chip-keys chip</span>)</span> key</span>)</span> nil</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>And let's make sure to clear it when we reset the emulator:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> reset <span class="paren2">(<span class="code">chip</span>)</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">with-chip</span></i> <span class="paren3">(<span class="code">chip</span>)</span>
|
||
|
<span class="comment">; ...
|
||
|
</span> <span class="paren3">(<span class="code">fill keys nil</span>)</span>
|
||
|
<span class="comment">; ...
|
||
|
</span> </span>)</span>
|
||
|
<span class="paren2">(<span class="code">values</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<h3 id="s5-keyboard-branching-skp-and-sknp"><a href="index.html#s5-keyboard-branching-skp-and-sknp">Keyboard Branching: SKP and SKNP</a></h3>
|
||
|
|
||
|
<p>The CHIP-8 has two keyboard-related branching instructions: <code>SKP</code> and <code>SKNP</code>
|
||
|
("skip when pressed" and "skip when not pressed").</p>
|
||
|
|
||
|
<p>Much like the other branching instructions the make the CPU skip the next
|
||
|
instruction if a particular key is pressed. Their implementation is pretty
|
||
|
simple — to check if key <code>N</code> is pressed we just look at the value of index <code>N</code>
|
||
|
in the keyboard memory:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-skp <span class="paren2">(<span class="code">_ r _ _</span>)</span> <span class="comment">;; SKP
|
||
|
</span> <span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">aref keys <span class="paren4">(<span class="code">register r</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">incf program-counter 2</span>)</span></span>)</span></span>)</span>
|
||
|
|
||
|
<span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-sknp <span class="paren2">(<span class="code">_ r _ _</span>)</span> <span class="comment">;; SKNP
|
||
|
</span> <span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">not <span class="paren4">(<span class="code">aref keys <span class="paren5">(<span class="code">register r</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">incf program-counter 2</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<h3 id="s6-waiting-for-input-ld-vx-k"><a href="index.html#s6-waiting-for-input-ld-vx-k">Waiting for Input: LD Vx, K</a></h3>
|
||
|
|
||
|
<p>The other keyboard-related instruction is a bit trickier. From <a href="http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#Fx0A">Cowgod's
|
||
|
documentation</a>:</p>
|
||
|
|
||
|
<pre><code>Fx0A - LD Vx, K
|
||
|
Wait for a key press, store the value of the key in Vx.
|
||
|
|
||
|
All execution stops until a key is pressed, then the value of
|
||
|
that key is stored in Vx.
|
||
|
</code></pre>
|
||
|
|
||
|
<p>It's not 100% clear what "all execution" means, as we'll see when we get to the
|
||
|
post on audio. But we can put together a basic implementation pretty easily:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-instruction</span></i> op-ld-reg<key <span class="paren2">(<span class="code">_ r _ _</span>)</span> <span class="comment">;; LD Vx, Key (await)
|
||
|
</span> <span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">key <span class="paren5">(<span class="code">position t keys</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> key
|
||
|
<span class="paren4">(<span class="code">setf <span class="paren5">(<span class="code">register r</span>)</span> key</span>)</span>
|
||
|
<span class="comment">;; If we don't have a key, just execute this instruction
|
||
|
</span> <span class="comment">;; again next time.
|
||
|
</span> <span class="paren4">(<span class="code">decf program-counter 2</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The <code>(position t keys)</code> here takes advantage of the fact that the index of
|
||
|
a slot in the <code>keys</code> array also happens to be the name/number of the key. If
|
||
|
<code>position</code> doesn't find a <code>t</code> in the array it will just return <code>nil</code>.</p>
|
||
|
|
||
|
<p>To simulate the stopping of execution we just use a little trick: we rewind the
|
||
|
program counter so this instruction gets executed again on the next cycle.</p>
|
||
|
|
||
|
<p>We could probably come up with a way to avoid this busy-looping, but it would
|
||
|
add extra complexity, especially when we get to the pausing and debugging
|
||
|
infrastructure later. This should work fine for our needs.</p>
|
||
|
|
||
|
<h2 id="s7-the-user-interface-layer"><a href="index.html#s7-the-user-interface-layer">The User Interface Layer</a></h2>
|
||
|
|
||
|
<p>That's all we have to do on the emulation side of things. Now we need to get Qt
|
||
|
to read our keyboard input and pass it along to the <code>chip</code>.</p>
|
||
|
|
||
|
<h3 id="s8-key-mappings"><a href="index.html#s8-key-mappings">Key Mappings</a></h3>
|
||
|
|
||
|
<p>The first thing we'll need to do is decide how we'd like to map the CHIP-8's
|
||
|
keyboard onto our modern keyboard. If you've got a keyboard with a number pad
|
||
|
you might prefer something like this:</p>
|
||
|
|
||
|
<pre class="lineart">
|
||
|
Original Chip-8 Pad → Modern Numpad
|
||
|
┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐
|
||
|
│1│2│3│C│ │←│/│*│-│
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┼─┤
|
||
|
│4│5│6│D│ │7│8│9│+│
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┤ │
|
||
|
│7│8│9│E│ │4│5│6│ │
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┼─┤
|
||
|
│A│0│B│F│ │1│2│3│↲│
|
||
|
└─┴─┴─┴─┘ ├─┴─┼─┤ │
|
||
|
│0 │.│ │
|
||
|
└───┴─┴─┘
|
||
|
</pre>
|
||
|
|
||
|
<p>Which we can implement with a big <code>qtenumcase</code>:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> pad-key-for <span class="paren2">(<span class="code">code</span>)</span>
|
||
|
<span class="paren2">(<span class="code">qtenumcase code
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_clear</span>)</span> #x1</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_slash</span>)</span> #x2</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_asterisk</span>)</span> #x3</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_minus</span>)</span> #xC</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_7</span>)</span> #x4</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_8</span>)</span> #x5</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_9</span>)</span> #x6</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_plus</span>)</span> #xD</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_4</span>)</span> #x7</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_5</span>)</span> #x8</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_6</span>)</span> #x9</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_enter</span>)</span> #xE</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_1</span>)</span> #xA</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_2</span>)</span> #x0</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_3</span>)</span> #xB</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_0</span>)</span> #xF</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Note that even though the constants like <code>q+:qt.key_clear</code> end up being numbers,
|
||
|
you still need to surround them with parentheses for Qtools' magic name-mangling
|
||
|
to take effect properly. If you just say <code>(qtenumcase code (q+:qt.key_clear #x1) ...)</code>
|
||
|
you'll get an error like:</p>
|
||
|
|
||
|
<pre><code>; (QTOOLS:QTENUMCASE CHIP8.GUI.SCREEN::CODE
|
||
|
; (QTOOLS:Q+ "QT.KEY_CLEAR" 1))
|
||
|
; --> LET COND IF
|
||
|
; ==>
|
||
|
; (QT:ENUM-EQUAL #:KEY0 QTOOLS:Q+)
|
||
|
;
|
||
|
; caught WARNING:
|
||
|
; undefined variable: Q+
|
||
|
;
|
||
|
; compilation unit finished
|
||
|
; Undefined variable:
|
||
|
; Q+
|
||
|
; caught 1 WARNING condition</code></pre>
|
||
|
|
||
|
<p>The magic name-mangling in Qtools bothers me a little, but I'm sure the
|
||
|
alternative would be far more verbose, so I live with it.</p>
|
||
|
|
||
|
<p>Anyway, moving on. If you've got a laptop or a tenkeyless keyboard you might
|
||
|
prefer a key mapping scheme more like this:</p>
|
||
|
|
||
|
<pre class="lineart">
|
||
|
Original Chip-8 Pad → Laptop
|
||
|
┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐
|
||
|
│1│2│3│C│ │1│2│3│4│
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┼─┤
|
||
|
│4│5│6│D│ │Q│W│E│R│
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┼─┤
|
||
|
│7│8│9│E│ │A│S│D│F│
|
||
|
├─┼─┼─┼─┤ ├─┼─┼─┼─┤
|
||
|
│A│0│B│F│ │Z│X│C│V│
|
||
|
└─┴─┴─┴─┘ └─┴─┴─┴─┘
|
||
|
</pre>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defun</span></i> pad-key-for <span class="paren2">(<span class="code">code</span>)</span>
|
||
|
<span class="paren2">(<span class="code">qtenumcase code
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_1</span>)</span> #x1</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_2</span>)</span> #x2</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_3</span>)</span> #x3</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_4</span>)</span> #xC</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_q</span>)</span> #x4</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_w</span>)</span> #x5</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_e</span>)</span> #x6</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_r</span>)</span> #xD</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_a</span>)</span> #x7</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_s</span>)</span> #x8</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_d</span>)</span> #x9</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_f</span>)</span> #xE</span>)</span>
|
||
|
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_z</span>)</span> #xA</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_x</span>)</span> #x0</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_c</span>)</span> #xB</span>)</span>
|
||
|
<span class="paren3">(<span class="code"><span class="paren4">(<span class="code">q+:qt.key_v</span>)</span> #xF</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Of course the ideal solution is to make the mapping configurable at run time,
|
||
|
but I'll leave that as an exercise you can do if you're interested.</p>
|
||
|
|
||
|
<h3 id="s9-qt-keyboard-overrides"><a href="index.html#s9-qt-keyboard-overrides">Qt Keyboard Overrides</a></h3>
|
||
|
|
||
|
<p>Now that we have <code>pad-key-for</code> to turn a Qt key into a CHIP-8 key we can write
|
||
|
the overrides for our Qt <code>screen</code>. We'll start with the <code>key-press-event</code>:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen key-press-event</span>)</span> <span class="paren2">(<span class="code">ev</span>)</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">key <span class="paren5">(<span class="code">q+:key ev</span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">pad-key <span class="paren5">(<span class="code">pad-key-for key</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">when pad-key
|
||
|
<span class="paren4">(<span class="code">chip8::keydown chip pad-key</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">stop-overriding</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>We check to see if the key we got was a pad key, and if so we call <code>keyup</code>
|
||
|
from earlier to mark it in the keyboard array. We'll ignore any presses of
|
||
|
unknown keys in this handler.</p>
|
||
|
|
||
|
<p>Next we'll handle when the user releases a key:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">define-override</span></i> <span class="paren2">(<span class="code">screen key-release-event</span>)</span> <span class="paren2">(<span class="code">ev</span>)</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let*</span></i> <span class="paren3">(<span class="code"><span class="paren4">(<span class="code">key <span class="paren5">(<span class="code">q+:key ev</span>)</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">pad-key <span class="paren5">(<span class="code">pad-key-for key</span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> pad-key
|
||
|
<span class="paren4">(<span class="code">chip8::keyup chip pad-key</span>)</span>
|
||
|
<span class="paren4">(<span class="code">qtenumcase key
|
||
|
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_escape</span>)</span> <span class="paren6">(<span class="code">die screen</span>)</span></span>)</span>
|
||
|
<span class="paren5">(<span class="code"><span class="paren6">(<span class="code">q+:qt.key_f1</span>)</span> <span class="paren6">(<span class="code">chip8::reset chip</span>)</span></span>)</span>
|
||
|
<span class="paren5">(<span class="code">t <span class="paren6">(<span class="code">pr <span class="keyword">:unknown-key</span> <span class="paren1">(<span class="code">format nil <span class="string">"~X"</span> key</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code">stop-overriding</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Much like <code>key-press-event</code> we check for a pad key and call <code>keyup</code> if we got
|
||
|
one. We also set up a couple of other handy mappings to control the emulator:</p>
|
||
|
|
||
|
<ul>
|
||
|
<li><code>F1</code> calls <code>reset</code> so we can easily reset the emulator without having to tab
|
||
|
back to the Lisp REPL.</li>
|
||
|
<li><code>Esc</code> will quit the emulator (and GUI).</li>
|
||
|
</ul>
|
||
|
|
||
|
<p>I've also included a fallback case that will print out any unknown keys in hex.
|
||
|
This was useful to cross reference with the Qt docs when I was trying to figure
|
||
|
out what the Qt constant name was for a particular key like "clear" on the
|
||
|
number pad.</p>
|
||
|
|
||
|
<h2 id="s10-results"><a href="index.html#s10-results">Results</a></h2>
|
||
|
|
||
|
<p>That's all we need to do to handle user input! Now we can finally <em>play</em> some
|
||
|
games! <code>ufo.rom</code> is one of my favorites, as is <code>blitz.rom</code> (once I figured out
|
||
|
the screen clipping bug).</p>
|
||
|
|
||
|
<h2 id="s11-future"><a href="index.html#s11-future">Future</a></h2>
|
||
|
|
||
|
<p>We're nearing the end of the emulator — the only thing strictly necessary is
|
||
|
adding sound, which we'll take care of in the next post.</p>
|
||
|
|
||
|
<p>After that I'll talk about adding some debugging infrastructure to the emulator
|
||
|
so we can look at what's going on, as well as adding a basic graphical debugger
|
||
|
UI with Qt.</p>
|
||
|
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a> ・ <a href='https://twitter.com/stevelosh/'>Twitter</a> ・ <a href='https://instagram.com/thirtytwobirds/'>Instagram</a> ・ <a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>
|