Episode 7: Skeet in Space!
Meanwhile, we've scored a few milestones at once, like collision detection and torpedos, even some visual effects. In fact, we've everything in place required for a little game of skeet shooting in outer space. Best of all, removing most of the artificial throttling, we're now running at twice the frame rate, nurturing the best hopes regarding the outcome of this venture.
So it's high time for another writeup to catch up with the progress of our little project.
So far, we had a maneuverable ship and randomly moving saucers on the screen. The saucers were quite a bit of work, most of it dedicated to the few animated dots moving in the central "rotating" section of the saucers, and, quite perversely, to the idle phase of the saucers. (Just as a reminder, there are in total 4 phases out of two different idle behaviors, and the central animation is to be kept for any consecutive leg of movement.)
Now, we may actually do some with these objcts, maybe have them collide with each other?
I may be wrong, but I believe the original game to do some kind of pixel-wise hit detection, similar to what we see with sprites/MOBs in early game consoles and home computers. *) The next best option, in case we're lacking the hardware for this, is to detect collisions by comparing radii. But this involves time consuming operations like multiplications, even, if we opted for comparing distances to squared radii and thus saving the runtime required for costly square roots.
*) Update: I stand corrected. The original game features a rather generous hit detection and ships collide even at still a distance.
Moreover, having a closer look at our saucers, they are, while ideally disk-shaped in our imagination, far from disk-shaped on the screen. Actually, some kind of vertically compressed hit box would do the job. Maybe something that would be more like an octagon instead of a circular area?
Enter Spacewar! and the solution Steve Russell came up with:
- Compare the absolute delta x (dx) of the respective centers to epsilon 1.
- If it fits, compare the absolute delta y (dy) of objects to epsilon 1.
- If it still fits, clip the corners of the hitbox for extreme values of both dx and dy by adding dx to the result of the comparing operation dy – ε1 and compare this to another treshold, epsilon 2, of just half the size of epsilon 1.
In other words, we're checking, if
(ε1 + ε2) < abs(dx) + abs(dy).
This is next to ideal for our purpose. Instead of using the same epsilon for both dx and dy, we're going to use distinctive thresholds, thus achieving our vertical compression. We move the code into a subroutine and set up the values of the various epsilons with regard to the objects we're gong to compare. As we're checking for any overlap, the sums of the vertical and horizontal raddi (half the side-lengths), respectively, will do. And of course, we've to set up the respective center cooordinates of the objects, we're going to compare, as well.
Here, we've to remind ourselves that the rotational center of the rocket ship is not its geometrical center. In fact, it's somewhere below, in the "umbilical" region of the ship. Therefor, we've to subtract an offset based on the current sine and cosine of the ships rotational angle. As this happens to be just half the offset we already used to get the tip of the ship for the drawing routine, we're just storing this when we're inside the rockte ship's routine to be reused here.
Here's a quick look at the actual code for comparing the player's ship with the first of the two saucers:
lac \rpx /get rotational center of rocket sub \rxc dac \px lac \rpy add \ryc dac \py law \px /set up rocket for comparison dap mx1 law \py dap my1 lac (21000 /set up collision radii (x 34, y 24) in screen locs dac \dxe sar 1s dac \de2 lac (14000 dac \dye law \upx /first saucer dap mx2 law \upy dap my2 jsp dmf /compare them sza /hit? jmp dxr /yes (...) /subroutine to compare object positions (from spacewar, mod n.l.) dmf, dap dmx mx1, lac . /calc if collision mx2, sub . /delta x spa /take abs val cma dac \t sub \dxe / < epsilon x ? sma jmp dm0 /no my1, lac . my2, sub . spa cma sub \dye / < epsilon y ? sma jmp dm0 /no add \t sub \de2 / < epsilon 2 ? sma jmp dm0 law 1 /return 1 (in ac) for hit jmp dmx dm0, cla /return 0 for miss dmx, jmp .
But, aren't there two of the saucers on the screen? — Yes and no: It's more like a single saucer appearing twice at a constant vertical offset. Therefor, all we have to do is to set up a temporary value with the offset added for a new vertical position (
my2) and go for another round, using otherwise the parameters, we've set up earlier.
By this, we should be able to detect any ship-to-saucer collisions. So, what's going to happen in case, we were actually detecting one?
Symbolic Death (© Stewart Brand)
As indicated by the subtitle, the jest of death is in the notice, even more so for a video game. Therefor, it's of uttermost importance that the event should be symbolized to the player in a proper and orderly manner.
Computer Space doesn't disappoint here, informing us of the tragic event by a spinning animation of the ship. (The saucers are respawned quickly after a short flashing of the screen, making this a proper twitch game.) And, as this is just a symbolic death, live goes on and the player's rocket is repositioned on the screen, just as it was, in order to commence with the short-lived but epic quest of shooting saucers.
This is rather easy to implement: We just trick the rocket routine into performing a clockwise rotation, but at twice the speed. At second thought, we make this a progressively accelerating spinning motion, starting rather slowly, at twice the speed of a normal turn, becoming quicker and quicker, until it just stencils a nice dotted pattern into the long sustain of the Type 30 CRT. For this, we've to add some counters, but are paid off by a rather nice, while ephimeral effect.
Space Pigeons, or, Gosh, What I'm Supposed to Do with a Torpedo?
In the Apollo era, NASA dubbed any unidentified lunar emanations Moon pigeons, but, in spite of this code name, astronauts restrained their lunar sports activities to golf only, that is, as far as we know it. :-)
But we're here for some serious sport and are facing the by far fiercer space pigeon — and there's no room for error. So we'd better do something about it…
With hit detection already in place, it would be nice to implement a torpedo (that is, a photon torpedo, of course), so that we may have something to shoot at, while testing the program. This is rather straight forward, again, as there is just a single torpedo at a time and it's about just a single dot on the screen, advancing by the constant delta of its velocity. That is, if it wouldn't be for a peculiar property of Computer Space, namely guided missiles.
In Computer Space, if you've fired a torpedo (or missile) and happen to turn your ship, the torpedo is altering its direction, too. Steerable torpedos! Just think of it and the possibilities in gameplay! But, judging from footage of the game, the steering is rather quirky and not too easy to master — otherwise, the game might be to easy.
We've to come up with a similar solution. Additionally, the trail of the torpedo will be pretty visible in the afterglow of the Type 30 CRT, so it should be an aesthetical pleasing solution, as well. We implement this, quite like we did it for the acceleration of the ship, by a combination of frame skips (delayed response) and repetitive averaging with the old movement delta (for damping), where we derive the new factor from the scaled unit vector of the ship's current rotational angle.
In fact, we even implement two solutions to the problem: A default one, where torpedo steering only applies when the ship is actually rotated (similar to what is seen in footage of the game), and another one, to be activated by sense switch 3, where the torpedo eventually follows the rotation of the ship, but in a delayed manner at a greater turning radius. (In fact, this is just skipping the test for the turn controls currently being employed, but effects in an aesthetical pleasing curve and better control.)
Another peculiarity of Computer Space to be mentioned here, torpdoes are actually fired from the belly of the ship, its rotational center, and not from its bow — this is not a mistake.
Speaking of computer aesthetics with respect to the platform, the screen isn't the only thing to be considered. We are using some of the program flags, either to control the various passes and states of the drawing routines, or to trace the state of the player controls, and, as we've to repeatedly check for the player's rocket active state, we use another flag for this. Now, the program flags are implemented in hardware on the PDP-1, and there are console lights for them on the operator's console. And not only is the state of the flag register clearly visible to everyone near the machine, so is our use of it, as well.
Therefor, in order to deliver a proper program, we should pay attention to this part of the runtime experience, too. In our program, the indicators for flags 5 and 6 (at the right) will flash rapidly as we make heavy use of them in the drawing routines. With flag 4 being practically taboo most of the time, we've to consider a pleasing use of flags 1 to 3. We'll use flag 1 (at the left) for the active state of the player's rocket (it will flash each frame of an ongoing game, being on most of the time), flag 2 for thrust, and, finally, flag 3 for the torpedo trigger. Thus, the lights will give a somewhat coordinated feedback of the game's state and the player action as read by the machine.
Speaking of the experience and platform specific considerations, another thing to consider is the tactical behavior of the game. The saucers are meant to act randomly, but, as it is, with our random number generator implemented in software and nothing but our own program on the machine to be used as an additional source of randomness, the game is rather showing a deterministic, patterned behavior. So we really should look for some randomness and variety that even a single additional run of the random generator could provide. Looking around, the only additional source there is, is the player and the interaction via the control input. Since this input is that fine in granularity that we've, in fact, to skip most of it in order to model the behavior of Computer Space, there's some randomness in it: It's near to impossible that a player should be able to press any of the buttons exactly as long in two separate runs of the game. So, for the sake of randomness, we run the random number generator each time, we register the signal for the rocket to thrust. Therefor, even when starting fresh from a previously cleared memory (core memory is nonvolatile!), there should always be different games.
Returning to the torpedo and its course across the screen, we'll probably more often miss than hit — otherwise it may be quite a boring game. But, in case we score a hit, we should be notified properly by an attractive experience, since shooting at and eventually hitting saucers is the core purpose of this game, isn't it? The visual feedback of Computer Space is rather harsh, just a short flashing of the screen in reverse video. But there's sound — and did we mention already? —, it's a sound of rather peculiar nature.
However, we can't do any of this on the PDP-1. Reverse video is impossible with a X/Y random access display and — while Peter Samson famously did impressive playback of Bach on the PDP-1 by hoooking up special circuitry to the flag register (see this video of Lyle Bickley explaining the details) —, there's no sound at all on a default PDP-1. Thus, we've to come up with a solution of our own. Maybe a visual animation of an explosion?
Visual explosions by rendering some particles at a random offset should be rather simple, even more on a painted display. But, alas, what's supposed to be simple on paper rarely is so in real-life implementation. So it is in the case of our particle explosion. Just using some scaled random numbers as an offset to the central screen location of our effect won't do, since it shows pretty much the confining box of the scaled random number.
Obviously, we are in nedd of some additional random scaling for any of the offsets of our distinctive particles. Playing around with it, I'm always close to the solution found in Spacewar! (maybe, I've just seen too much of it). But, this isn't only the first particle explosion in history for a computer game, it's one of the most beautiful, ever — and it is already custom tailored to the specific display and hardware. A bespoke animation, to say the least. Therefor, we may have a closer look at this one, in order to recklessly steal it for our own purpose.
As I understand it, the explosion routine was originally written by J. M. Graetz and was later refined by Steve Russell in version 3 and never to be changed thereafter. First, the explosion graphics suffered from the very disease already mentioned above, a more than obvious confining box of the effect,
"(…) appropriately-named Crock Explosion. Something dramatic obviously had to happen when a ship was destroyed, but we were dealing with a plain dot-matrix screen. The original control program produced a random-dot burst confined within a small square whose outlines were all too discernible."
(J. M. Graetz, The Origin of Spacewar, Creative Computing, August issue 1981.)
The cure applied by Steve Russell was essentially an additional random shift, assembled on the fly for each of the particles. Further, the final explosion came at two sizes, expanding at a wide scale at the beginning and ending in smaller spread, with a gradually diminishing number of particles.
For our purpose, we modify the explosion routine a bit (at least, to show that we have a decent understanding of the code, we steal). We're going with three phases, starting small, expanding wider in the middle, and ending small again. And we're going to tune the scaling parameters to fit our purpose. Moreover, our explosion will be rather short in duration, thus we really go for it and draw a lush octal 30 (decimal 24) particles each of the frames.
Here's the code:
/saucer explosion (similar to spacewar) / jump here from inside ufo uxe, law uxs /set up instr at uxs to be xtc-ed in ux1 dap ux1 lac \ufs add (30 /done with explosion, 030 frames setup delay sma jmp ux3 sal 1s dac \uxc /repeat for 030 particles add (50 /first and last frames small explosion spa jmp ux0 sub (30 sma idx ux1 /increment addr in ux1 for bigger explosion ux0, random /start of loop, get random number and (6s /constrain to lowest 6 bits (as no of shifts) ior (scl /assemble a shift instruction for these dac ux2 /insert it at ux2 random /new random number cli scr 9s /split it into lower parts of ac and io sir 9s ux1, xct . /apply scaling (size) ux2, opr /apply shift for random spread add \upy /add center pos x/y swap add \upx dpy -i 100 /display a dot isp \uxc /redo \uxc times jmp ux0 ux3, isp \ufs /increment status counter and skip next on positive jmp ufi /exit, jump to end of ufo routine (saucers) jsp urs /last iteration, respawn saucers jmp ufi /exit uxs, scl 1s /scaling for small explosion scl 2s / and bigger one, to be xct-ed at ux1
And this is what we get:
▶ Experience the code live: www.masswerk.at/icss/.
Update/Note: While we were completele happy with this effect — and it was a beautiful one —, we eventually replaced it by something other that was not as much a Spacewar! trademark visual and more something of our own.
By this we've already arrived at a somewhat playable game. Still missing are the shots by the saucers and scoring, — and probably some fine-tuning, too.
However, we're that proud of our progress that we leave the shabby outskirts of pre-prerelease revisions behind and enter, by skipping version 0.09, the gleaming prerelease territory of 0.x-code. (At least, if Microsoft is allowed to skip Windows 9, why shouldn't we dare to skip version 0.09?)
And, last but not least, we remove most of the throttling applied to the code, thus running nearly at full speed, resulting in more than twice the frame rate we had before. (Thus, we're not too displeased with having to readjust most of the frame counts, skips, and scalings.) In fact, the game might be even a bit too fast — and we've still some buffer left. Therefor, we shouldn't face any severe timing issues, even, when we eventually are going to implement the score display.
And here's our growing code, so far:
ironic computer space 0.1 nl 2016-10-23 mul=mus /instruction code for multiplication div=dis /instruction code for division ioh=iot i /wait for completion pulse (CRT display) szm=sza sma-szf /skip on ac zero or minus spq=szm i /skip on ac plus and not zero define initialize A,B law B dap A term define index A,B,C idx A sas B jmp C term define swap rcl 9s rcl 9s term define load A,B lio (B dio A term define setup A,B law i B dac A term define count A,B isp A jmp B term /macros specific to the program define scale A,B,C lac A sar B dac C term define random lac ran rar 1s xor (355671 add (355671 dac ran term / Computer Space / Original arcade game by Nolan Bushnell and Ted Dabney, / Syzygy Engineering / Nutting Associates, 1971. / 'A simulated space battle that pits / computer-guided saucers against / a rocket ship that you control.' 3/ jmp sbf / ignore seq. break jmp a0 / start addr, use control boxes jmp a1 / alt. start addr, use testword controls /game parameters raa, 6, 1200 / rocket angular acceleration rvl, 7, sar 8s / scaling rocket velocity ras, 10, law i 6 / rocket acceleration sensitivity (skip) rad, 11, law i 4 / rocket acceleration damping (averaging) trv, 12, sar 7s / scaling torpedo velocity tlf, 13, law i 250 / torpedo life rsd, 14, 40 / rocket reset delay (frames) ran, 15, 0 / random number /routine to flush sequence breaks, if they occur. sbf, tyi lio 2 lac 0 lsm jmp i 1 /sine-cosine subroutine - Adams associates /calling sequence= number in AC, jda sin or jdacos. /argument is between +/- +2 pi, with binary point to right of bit 3. /answer has binary point to right of bit 0. Time = 2.35-? ms. /changed for auto-multiply , ddp 1/19/63 cos, 0 dap csx lac (62210 add cos dac sin jmp .+4 sin, 0 dap csx lac sin spa si1, add (311040 sub (62210 sma jmp si2 add (62210 si3, ral 2s mul (242763 dac sin mul sin dac cos mul (756103 add (121312 mul cos add (532511 mul cos add (144417 mul sin scl 3s dac cos xor sin sma jmp csx-1 lac (377777 lio sin spi cma jmp csx lac cos csx, jmp . si2, cma add (62210 sma jmp si3 add (62210 spa jmp .+3 sub (62210 jmp si3 sub (62210 jmp si1 /subroutines for background display nos=77 /number of stars /table of stars coors (nos words, 9 bits x and 9 bits y) bst, . nos/ /setup (nos random coors starting at bst) bsi, dap bsx / deposit return address init bsc, bst / deposit first addr of bst in bsc bsl, random / get a new random number bsc, dac . / store it in current loc of st index bsc, (dac bst+nos, bsl / increment bsl, repeat at bgl for nos times bsx, jmp . / return /display background stars (displays every 2nd frame only) bg, dap bgx / deposit return address lac \frc / check frame conter and (1 sza / skip every second frame jmp bgi / jump to star display szs i 10 / sense switch 1 for parallax effect jmp bgd lac \rdx szs i 20 / sense switch 2 for stronger effect jmp . 3 sar 1s add \rdx sar 3s cma add bgh dac bgh lac \rdy szs i 20 jmp . 3 sar 1s add \rdy sar 3s cma add bgv dac bgv jmp bgx / return bgd, lac \frc / advance x offset slowly and (177 sza jmp bgx law i 400 add bgh dac bgh jmp bgx / return bgi, init bgl, bst / init bgl to first addr of stars table bgl, lac . / get coors of next star (x/y) cli / clear io scr 9s / shift low 9 bits in high 9 bits of io (x) sal 9s / move remaining 9 bits in ac in high part (y) add bgv / add vertical offset swap / swap contents of ac and io add bgh / add horizontal offset dpy-i / display a dot at coors in ac (x) and io (y) index bgl, (lac bst+nos, bgl / repeat the loop at bgl nos times bgx, jmp . / return bgh, 0 bgv, 0 /here from start a0, law rcb /configure to read control boxes (sa 4) dap rcw jmp a2 a1, law rtw /configure to read testword (sa 5) dap rcw /start a new run a2, jsp bsi / initial setup of bg-stars dzm bgh / reset offsets of stars to zero dzm bgv /rocket setup ar, lac (240000 dac \rth / rotational angle lac (-200000 dac \rpx / pos x cma dac \rpy / pos y law 300 dac \rdx law i 500 dac \rdy dzm \rac / acceleration skip counter dzm \rxc / reset center offset dzm \ryc law 1 dac \rks /status dzm \trs /torpedo status counter /saucer setup au, lac (200000 dac \upy / pos x lac (140000 dac \upx / pos y dzm \udc / animation skip counter law 1 dac \udd / animation direction dzm \umo / direction code law 600 dac \udy / delta y dzm \udx / delta x law i 100 dac \ufc / duration of movement law 1 dac \uft / speed of center animation (1,3) law 1 dac \ufs / state counter /main loop fr0, load \ict, -2100 / initial instruction budget (delay) idx \frc / increment frame counter clf 1 /flag ship active jsp bg / display background jsp rkt / rocket routine jsp ufo lac \trs sza jsp tr jsp df / check collisions count \ict, . / use up rest of time of main loop jmp fr0 / next frame /collision / hit detection df, dap dfx lac \ufs /check saucer state spq /skip on positive jmp dfx lac \rks /check rocket state spq jmp dfx lac \rpx /get rotational center of rocket sub \rxc dac \px lac \rpy add \ryc dac \py law \px /set up rocket for comparison dap mx1 law \py dap my1 lac (21000 /set up collision radii (x 34, y 24) in screen locs dac \dxe sar 1s dac \de2 lac (14000 dac \dye law \upx /first saucer dap mx2 law \upy dap my2 jsp dmf /compare them sza /hit? jmp dxr /yes lac \upy /second saucer sub (400000 /vertical offset dac \vpy law \vpy dap my2 jsp dmf /compare them sza /hit? jmp dxr /yes dft, lac \trs /torpedo active sma jmp dfx lac (12200 /setup hit box (x 21, y 10) dac \dxe sar 1s dac \de2 lac (5000 dac \dfy law \tpx /set up torp as first object dap mx1 law \tpy dap my1 jsp dmf /compare sza jmp dxu /hit, respawn law \upy dap my2 jsp dmf /compare them sza /hit? jmp dxu jmp dfx dxr, law i 100 /set rocket state to collision sub rsd /additional delay before reset dac \rks dzm \rdx dzm \rdy dzm \trs lac \rth dac \th0 dzm \thc law i 10 dac \thd jsp urs /respawn saucers jmp dfx dxu, dzm \trs /set up saucer explosion lac i my2 /set up pos y dac \upy law i 30 30 dac \ufs dfx, jmp . /return /subroutine to compare object positions (from spacewar, mod n.l.) dmf, dap dmx mx1, lac . /calc if collision mx2, sub . /delta x spa /take abs val cma dac \t sub \dxe / < epsilon x ? sma jmp dm0 /no my1, lac . my2, sub . spa cma sub \dye / < epsilon y ? sma jmp dm0 /no add \t sub \de2 / < epsilon 2 ? sma jmp dm0 law 1 /return 1 (in ac) for hit jmp dmx dm0, cla /return 0 for miss dmx, jmp . /saucers ufo, dap ufx lac \ufs spq jmp uxe clf 6 lac \upx /update position add \udx dac \upx dac \px lac \upy add \udy dac \upy dac \py jsp sd /display first saucer lac \py sub (400000 /half-screen vertical offset dac \py jsp sd /display second saucer isp \ufc /increment leg counter jmp uf1 /continue lac \umo /new direction spa jmp uza /we're in a 3-steps stop (\umo: -3..-1) sar 3s / 010 set for single step stop sza jmp uz1 random sma jmp uf2 /set up new leg dzm \udx /stop dzm \udy lac ran /what kind of stop will it be? ral 3s spa jmp uz0 /three-steps stop law 10 /single-step stop, keep center animation dac \umo jmp uzb /get delay and continue uz0, law i 3 /first period of three-steps stop dac \umo /reuse \umo as step counter (negative) law 3 dac \uft /set animation to slow (3) uzb, jsp utd /get duration sar 1s dac \ufc jmp ufi /continue uza, cma /3-steps stop, dispatch on -\umo (-3..-1) add (. 3 dap . 2 idx \umo jmp . jmp uz1 jmp uz2 uz3, dzm \udd /3 - stop animation jmp . 2 uz2, jsp ucd /2 - still stopped, new animation direction jsp utd sar 1s dac \ufc jmp ufi uz1, stf 6 /1 - start over, but keep animation random jmp . 2 uf2, clf 6 /set up for a new leg (flag 6: keep anim. dir.) and (7 dac \umo /new motion code (0..7) sal 1s /read corresponding dx/dy form table umt add (umt / setup reference location for dy in \t1 dac \t1 lac i \t1 /load dy indirectly from addr. in \t1 dac \udy idx \t1 /increment addr. in \t1 lac i \t1 /load dx indirectly from addr. in \t1 dac \udx szf 6 i /skip next on flag 6 jsp ucd /set new direction for animation jsp utd /get delay dac \ufc uf1, lac \frc /increment center animation and \uft sza jmp ufi lac \udc /udc = 0..5 sub \udd dac \udc sma jmp . 4 law 5 dac \udc jmp ufi sad (6 dzm \udc ufi, lac \ict /update instruction count add (1200 dac \ict ufx, jmp . ucd, dap ucx /subroutine to set center animation lio ran ril 1s law 1 spi cma dac \udd lac ran /animation speed, favor faster rar 9s and (3 sza jmp . 3 law 1 jmp . 2 law 3 dac \uft ucx, jmp . utd, dap utx /subroutine to get random delay (leg length) random sar 9s sar 3s sub (270 utx, jmp . urs, dap urx /subroutine to respawn the saucers lac ran /random offset x to player's rocket rar 9s sar 2s dac \upx sar 1s add \upx add (400000 add \rpx dac \upx random /random pos y dac \upy jsp ucd /set up center animation dac \udd law i 40 /set up delay before next leg dac \ufc law i 1 /stopped (end of 3-steps stop) dac \umo law 1 /set state to active dac \ufs urx, jmp . /saucer movement table (dy, dx) umt, 600 0 -600 0 0 -600 0 600 400 400 -400 -400 -400 400 400 -400 /saucer explosion (similar to spacewar) / jump here from inside ufo uxe, law uxs /set up instr at uxs to be xtc-ed in ux1 dap ux1 lac \ufs add (30 /done with explosion, 030 frames setup delay sma jmp ux3 sal 1s dac \uxc /repeat for 030 particles add (50 /first and last frames small explosion spa jmp ux0 sub (30 sma idx ux1 /increment addr in ux1 for bigger explosion ux0, random /start of loop, get random number and (6s /constrain to lowest 6 bits (as no of shifts) ior (scl /assemble a shift instruction for these dac ux2 /insert it at ux2 random /new random number cli scr 9s /split it into lower parts of ac and io sir 9s ux1, xct . /apply scaling (size) ux2, opr /apply shift for random spread add \upy /add center pos x/y swap add \upx dpy -i 100 /display a dot isp \uxc /redo \uxc times jmp ux0 ux3, isp \ufs /increment status counter jmp ufi jsp urs /last iteration, respawn saucers jmp ufi uxs, scl 1s /scaling for small explosion scl 2s / and bigger one, to be xct-ed at ux1 /player rocket routine rkt, dap rkx clf 2 clf 3 lac \rks /check for collision state szm / < 0 ? jmp rka /no isp \rks /manage rocket hit state jmp rke idx \rks lac \th0 dac \rth jmp rcw rke, add rsd sma jmp rkx rt1, lac \thc /animate collision, spin clockwise progressively dac \thn lio raa /angular acceleration rt2, sil 1s isp \thn /shift left \thd times jmp rt2 isp \thd jmp rt3 law i 20 /add another shift every 20 frames dac \thd lac \thc sub (1 dac \thc rt3, swap cma cli -opr /make it a clockwise spin, clear io add \rth /update rotational angle dzm \cw /and continue with cleared control input jmp rkr rka, stf 1 /start active rocket rcw, jsp . /read control word (ccw, cw, trust, fire) dio \cw /merge (or) spacewar player inputs cla rcr 4s ior \cw dac \cw lio \cw /parse and process rotation lac \rth /load angle spi /sign cleared in io? add raa /no, add angular acceleration ril 1s /next bit spi sub raa rkr, sma /normalize 0 >= angle >= 2Pi (0311040) sub (311040 spa add (311040 dac \rth /update angle ril 1s /parse thrust input spi stf 2 /set flag 2 for thrust ril 1s spi stf 3 /set flag 3 for torp jda sin /get sin, store it in \sn dac \sn lac \rth jda cos /get cos, store it in \cs dac \cs szf i 2 /flag 5 set? update dx / dy jmp rk0 lac \frc /load frame counter isp \rac /sensitivity, frame skip jmp rk1 xct ras /reset counter dac \rac lac \sn /dx cma xct rvl /apply scaling for acceleration swap /swap result into io xct rad /damping, get loop count dac \rdc rx0, swap /get intermediate value from io add \rdx /average with old dx sar 1s swap /swap into io isp \rdc /increment loop jmp rx0 dio \rdx /store updated dx lac \cs /same for dy xct rvl swap xct rad dac \rdc ry0, swap add \rdy sar 1s swap isp \rdc jmp ry0 dio \rdy jmp rk1 rk0, dzm \rac rk1, scale \sn, 4s, \sn1 /get position of rocket tip sar 2s /offset x = (sin >> 4) - (sin >> 8) cma add \sn1 dac \sn1 scale \cs, 4s, \cn1 /offset y = (cos >> 4) - (cos >> 8) sar 2s cma add \cn1 dac \cn1 lac \rpx /update pos x (add dx) add \rdx dac \rpx sub \sn1 /subtract offset for tip, store it in \px dac \px lac \rpy /same for y add \rdy dac \rpy add \cn1 dac \py scale \sn1, 1s, \rxc /store half-offset for hit detection scale \cn1, 1s, \ryc scale \sn, 6s, \sn8 /scaled sine, 8 steps sar 1s dac \sn4 /4 steps sar 1s dac \sn2 /2 steps sar 1s dac \sn1 /1 step dac \sm1 add \sn2 dac \sn3 /3 steps dac \sm3 add \sn2 dac \sn5 /5 steps add \sn1 dac \sn6 /6 steps dac \sm6 scale \cs, 6s, \cn8 /scaled cosine, 8 steps sar 1s dac \cn4 /4 steps sar 1s dac \cn2 /2 steps sar 1s dac \cn1 /1 step dac \cm1 add \cn2 dac \cn3 /3 steps dac \cm3 add \cn2 dac \cn5 /5 steps add \cn1 dac \cn6 /6 steps dac \cm6 jsp rod /display it szf i 3 /fire torpedo? jmp rkq /no szf i 1 /are we active? jmp rkq /no lac \trs sza /torpedo already active? jmp rkq /yes xct tlf /set up torpedo dac \trs lac \rpx /copy position dac \tpx lac \rpy dac \tpy lac \sn /dx cma xct trv /apply scaling for velocity dac \tdx lac \cs /dy xct trv dac \tdy rkq, szf i 2 /advance random on thrust jmp rki / to prevent patterned behavior of saucers random rki, lac \ict /update instruction count add (600 dac \ict rkx, jmp . /torpedo (rocket) tr, dap trx isp \trs /count up status counter (zero = inactive) jmp . 2 trx, jmp . szf i 1 jmp tr1 lac \frc /frame skip and (3 sza jmp tr1 szs 30 /sense switch 3 for high agility jmp tr2 lac \cw /check control input for rotation (ccw, cw) and (600000 sza i /steering? jmp tr1 /no tr2, lac \cs /update with steering xct trv /scale cos of rocket add \tdy /average 3 times with current dy sar 1s add \tdy sar 1s add \tdy sar 1s dac \tdy /store updated dy add \tpy /update pos y dac \tpy swap lac \sn /same for sine, dx and pos x cma xct trv add \tdx sar 1s add \tdx sar 1s add \tdx sar 1s dac \tdx add \tpx dac \tpx dpy -i /display adot and jump to return jmp trx tr1, lac \tpy /no steering, simple update add \tdy dac \tpy swap lac \tpx add \tdx dac \tpx dpy -i jmp trx /control word get routines /read control boxes rcb, dap rcx cli iot 11 rcx, jmp . /read testword rtw, dap rtx lat swap rtx, jmp . /rocket display / step rearwards - x add \sn1, y sub \cn1 / step outwards - x add \cm1, y add \sm1 / step inwards - x sub \cm1, y sub \sm1 define disp dpy-i 100 /display a dot at brightness level +1 term rod, dap rox stf 6 /set flag 6 lac \px lio \py disp rop, swap /y +4, x +6 sub \cn4 add \sm6 swap add \sn4 add \cm6 disp swap /y +5, x +3 sub \cn5 add \sm3 swap add \sn5 add \cm3 disp swap /y +6, x +1 sub \cn6 add \sm1 swap add \sn6 add \cm1 disp swap /y +6, x -1 sub \cn6 sub \sm1 swap add \sn6 sub \cm1 disp swap /y +8, x +6 sub \cn8 add \sm6 swap add \sn8 add \cm6 disp swap /y 3, x -10 sub \cn3 sub \sm3 sub \sm6 sub \sm1 swap add \sn3 sub \cm3 sub \cm6 sub \cm1 disp swap /y +7, x +6 sub \cn8 add \cn1 add \sm6 swap add \sn8 sub \sn1 add \cm6 disp szf i 6 /flag 6 set? (skip on not zero) jmp rot clf 6 /clear flag 6 lac \cm1 /invert unit vector components cma dac \cm1 lac \sm1 cma dac \sm1 lac \cm3 cma dac \cm3 lac \sm3 cma dac \sm3 lac \cm6 cma dac \cm6 lac \sm6 cma dac \sm6 lac \px /load first pos lio \py jmp rop /second pass for other side rot, szf i 2 /no, flag 2 set? rox, jmp . /no, return swap /draw exhaust flame sub \sm6 /x -11 sub \sm6 add \sm1 swap sub \cm6 sub \cm6 add \cm1 dac \px /store position dio \py lac \frc /load frame counter and (4 sza /is it zero? (state switch) jmp ro2 ro1, lac \py /state 1, display at y-1 add \cn1 swap lac \px sub \sn1 disp jmp rox ro2, lac \py /state 2, display at y+5 sub \cn5 swap lac \px add \sn5 disp jmp rox /jump to return /saucer display define sdisp B /display a dot (opt. brightness parameter) dpy -4000 B / request completion pulse term sd, dap sdx clf 5 clf 6 law i 4 dac \sdc sdl, law 5000 /y +/- 10, x +/- 4 szf 6 cma add \py swap law 2000 szf 5 cma add \px disp law 3400 /y +/- 7, x +/- 7 szf 6 cma add \py swap law 3400 szf 5 cma add \px disp law 3400 /y +/- 7, x x+/- 15 szf 6 cma add \py swap law 7400 szf 5 cma add \px disp lac \sdc /dispatch on \sdc (-4..-1) for passes (set flags) cma add (sdd dap sdd idx \sdc /increment counter and jump sdd, jmp . jmp sd1 jmp sd2 jmp sd3 stf 6 /2nd pass jmp sdl sd3, stf 5 /3rd pass jmp sdl sd2, clf 6 /4th pass jmp sdl sd1, lio \py /done, display outer dots lac (12400 /y 0, x + 21 (right side) add \px sdisp 100 lac (-12400 /y 0, x - 21 (left side) add \px ioh sdisp 100 add (1000 swap lac \udc /draw first group of dots at the left sal 1s / dispatch on \udc x 3 (udc = 0..5) for clipping add \udc add (sd4 1 dap \sd4 swap lio \py sd4, jmp . add (1000 /0 nop nop add (1000 /1 ioh sdisp add (1000 /2 ioh sdisp add (1000 /3 ioh sdisp add (1000 /4 ioh sdisp add (3000 /5, display 4 dots ioh sdisp add (1000 ioh sdisp add (1000 ioh sdisp add (1000 ioh sdisp add (3000 /display 4 dots sdisp add (1000 ioh sdisp add (1000 ioh sdisp add (1000 ioh sdisp add (3000 /draw group of remaining dots at the right swap lac \udc /dispatch on \udc x 3 (udc = 0..5) for clipping add (sd5 1 dap sd5 swap lio \py sd5, jmp . jmp sd0 jmp sd0 jmp sd9 jmp sd8 jmp sd7 jmp sd6 sd6, ioh /4 dots sdisp add (1000 sd7, ioh /3 dots sdisp add (1000 sd8, ioh /2 dots sdisp add (1000 sd9, ioh /last dot sdisp sd0, ioh /fetch and clear last completion pulse sdx, jmp . /return constants variables start 4
(~ 1.24 K 18-bit words, 02354 locations incl. variables.)
That's all for this post.
▶ Next: Episode 8: The Saucers Strike Back
◀ Previous: Episode 6: Saucers!
▲ Back to the index.
2016-10-23/24, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/10. —