RetroChallenge, April 2018
One unlikely duo
This will be my first participation in the RetroChallenge
, and I would like to attempt
- Design an adapter to connect an original NES Zapper to a Tandy 1000 EX PC.
- Code a Duck Hunt clone and/or an original mini-game if I find inspiration.
- Use the Tandy-specific 16 color video mode. (It would be a shame not to!)
And if I have enough time:
- Also support CGA 320x200 video, and test the game on my XT-clone with a CGA card.
- Experiment a bit to find out if an unmodified Zapper can also work on a VGA monitor.
- Let the game also be played using a mouse (speed could be accelerated to make aiming more difficult.)
At the time personal computers were a new thing, decicated video monitors were expensive. Composite video output was therefore a
common feature on early PCs, so users could conveniently use the TV they already owned. Just like for video games consoles.
Computers such as IBM PCs had composite video output on CGA cards, but limited graphical capabilities (4 colors) and
no joystick ports, unless an expansion card was installed. On the other hand, computers from the
Tandy 1000 series
(from 1984) offered 16-color graphics, two built-in game ports and enhanced sound hardware. Much better suited for
The NES came out in 1985 and as everyone certainly knows, it also connected directly to TVs. The NES also came with a
light gun accessory known as the NES Zapper, which worked by detecting light emitted by the screen. To avoid interference from
other light sources in the room, the NES Zapper is
tuned for TV displays operating at a ~60 Hz vertical refresh rate, and ~15 kHz horizontal refresh rate.
It think the Zapper should be usable even if a Tandy 1000 or CGA card is driving the TV instead of a NES console. The
video timing should be identical, at least within some small tolerances that probably won't matter. So I think it is
an interesting thing to try, and given that the Tandy 1000 EX computer I will use and the NES Zapper are from the same era, it is
at least plausible that someone could have bought a NES Zapper to use with his Tandy 1000, provided a suitable adapter and
compatible game(s) had existed. (Maybe it did an I don't know!?)
For RC2018/04, I'd like to try to make the required Zapper adapter and code a game that uses it. For the game itself, I will
reuse the graphics library and tooling I made for RATillery
. But the game
code itself, the graphics (and music if I get there) will be new material created from April 1 to April 30 as per the
If I have time (and if I don't, I'll probably do it later anyway) I would like to test a theory. Because most VGA monitors do not
support horizontal refresh rate of 15 kHz (too low), the Zapper probably cannot be used (will test to confirm). But then, I think
there is still a way to use an unmodified Zapper with a VGA monitor and I would like to give it a try.
April 3 update
I first tested Duck Hunt to confirm that the Zapper still works and that it had no
problem with the monochrome monitor I planned to use for the time being. (I plan to begin
using the large color TV later). Duck Hunt worked fine, so I connected the Tandy 1000 EX and
prepared a floppy for the project.
Testing Duck Hunt
Next target: Building an adapter. Using the joystick port was obvious due to the presence of a
5 volt output (to supply the Zapper) and two button inputs (one for the trigger, one for the
light detect output).
I located a NES controller extension and the end of an old DIN-6 cable in my parts bin. After figuring
out the color code for both cables, I wired them into an adapter according to the table below:
|Button 1||Trigger||Pulled to GND by the trigger switch|
|Button 2||Detect||Continuously pulled to GND, but released when light is detected. (more on this later)|
I was very careful to double check everything, I even probed the Tandy 1000 EX joystick
port to make sure the 5v supply was where I expected it.
DIN6 from the bin
Figuring out the pinout
Checking the voltage
The state of the two joystick button inputs can be monitored by polling port 0x201. The
two significant bits reflect the button state. I wrote a simple basic program displaying
the value from port 0x201. I arranged for a blinking white rectangle to be present so I could
point the Zapper at it and see the effect.
I also had a scope wired to the zapper detection output to observe its behavior and timing closely.
Input test program
Trigger released, no light
Trigger on, no light
Trigger on, light detected
So it worked! But light detection was not reliable. In the rightmost picture above (the one showing
a white rectangle) the number 176 was only shown occasionally and briefly.
I looked at the output from the scope and saw that the signal went high for approx. 2ms. And after
thinking about it, it makes sense. While our eyes does not see it, the block on the screen is never
completely lit at once. The Zapper actually sees
the beam drawing each new scanline as the
previous one is already faded. Once the beam is lower than the block, the zapper does not
detect anything until the beam reaches the bottom of the screen and vertical refresh takes place (every
16ms / 60Hz).
As I am writing this, I suspect that aiming the Zapper at a completely white screen would result
in a much longer pulse. (Need to try this!)
Test with an oscilloscope
The code I had used so far was reading the IO port and displaying the value in a loop.
Interpreted BASIC being already slow, writing numbers on the screen in decimal between each
sampling of the IO port is obviously much too slow.
I made a new program that polled the port in a tight loop without screen updates, waiting
for a trigger first (worked very well) and then waiting for light (using a loop as a timeout
to detect misses). But it still failed to reliably sense pulses from the Zapper.
So detecting a 2ms pulse by polling an IO port in BASIC is probably impossible on this slow
machine. But it does not matter, I was using BASIC only as a temporary convenience for
quick experimentation. The game will of course be written in assembly. So the next
thing I will do now is to implement a simple test in assembly, so see if it will
be fast enough and reliable.
April 9 update
As planned, I redid the test in assembly language and now everything seems to work fine even though
it is an incomplete implementation where one can easily cheat by aiming at non-black screen areas,
or even by pointing the Zapper at another screen when pulling the trigger... But at least the
feasibility of the project is now confirmed!
The procedure I used is detailed below:
- Wait until the trigger is pulled
- Once pulled:
- Wait the next vertical retrace
- Monitor the light detection signal from the Zapper during a complete frame
- If light has been seen, consider this a hit
- Otherwise, it's a miss
- Wait until the trigger is released
- Goto start
Correcting the problems mentioned in the first paragraph should be possible by doing the hit
detection in two passes:
- First pass: Draw a black rectangle over the target, confirming no light is seen by the Zapper during a first frame.
- Second pass: Draw a white rectangle over the target, confirming light is seen by the Zapper during this second frame.
Only shots meeting the two conditions, that is no light in the first frame followed by light in the second, shall be
treated as hits.
If there are several targets, drawing the white rectangles can be done in extra frames. The exact moment where the zapper
light again will depend on which target was hit.
All this is in fact pretty much how the game Duck Hunt does things, except that in Duck Hunt, the whole screen becomes
black instead of only the targets. Why is that? Is it for technical reasons? My next tests will probably tell. To be continued...
April 15 update
This week, still no sight of a game, but the basics are under control and I think I have a good grasp of the capabilities of the Zapper. I
should have a playable demo soon!
Passive object rejection
The "cheating" issues I wrote about in my previous update are solved as planned. Drawing black for one frame, white in the next, while
monitoring the zapper to make sure its output follows exactly. Consider the pictures below: The square in the middle of the screen
is not shootable (passive), while the one near the upper left corner is. Pulling the trigger at the former reports a miss, but shooting at the later
is correctly recognized as a hit. An important concept for games, where shooting passive objects such as the scenery or score should do nothing.
Shootable object, reported as a hit
Non-shootable object, reported as a miss
Passive object rejection code
Of course, this technique causes flickering of the shootable object when pulling the trigger.
Getting the Y value by timing
The simple detection test I wrote about last week waits for vertical retrace and then monitors the Zapper during the full
frame. Why? Because the exact moment the detection pulse will come is not known in advance. It depends on the vertical position
of the object being shot at.
So this week it occurred to me that by timing how long it takes from the end of vertical refresh until light is first detected, it
should be possible to get the Y value corresponding to the screen area being pointed at by the Zapper. I wrote a new test program
showing a tall vertical bar, added code to count after how many cycles first light appears, and code to time how many cycles
the frame lasts. (I use the term cycle
here since it refers to iterations of the loop that polls the zapper for light and
the video status register. The exact counts depend on CPU speed.)
On this 200 pixel high display, Y is roughly equivalent to [cycles_to_first_light] * 200 / [frame_total_cycles]. The test program
computes the Y value and draws a red/pink square at the current height next to the vertical bar. In practise there
is a scale error, which I suppose is caused by the 40 extra black lines the video adapter probably adds to generate 240p NTSC video.
I'll investigate and correct for this only if I need to use it in a game and require extra precision...
I wonder if some NES games used this technique, I have not done research on games other than Duck Hunt.. In any case, for a game
with a great number of targets that are not vertically aligned, the process of drawing the targets in black (or blanking the screen) and
then in white could be done in parallel rather than one by one.
While I was at it, I added colors to test how the Zapper reacts. They are all reliably detected, even from good distance. On a black background,
it might be possible to get away without even drawing white over the object...
Also, on the right of the screen, the series of lines of increasing thickness was to test how many scanlines were required for
detection. But it turns out even a single scanline is detected.
April 22 update
April 30 final update
I will publish the schematics for the adapter, as well as the source code for the demo game. The following disclaimer will apply.
I cannot be held responsible for any damages that could occur to you
or your equipment while following the procedures present on this page.
Also, I GIVE ABSOLUTELY NO WARRANTY on the correctness and usability
of the informations on this page. Please note, however, that the procedures
above have worked in my case without any damages or problems.
Now you cannot say that I did not warn you :)