Episode 8: The Saucers Strike Back
We return to our little project to do the obvious. Because, with the rocketship, torpedoes and saucers in place, there's still one thing missing to make this a proper game. And we'll address this in the most painfull *umm* -staking manner: Not a single frame will be left untouched or without computer generated images (CGI). — Promise!
So We're Going to Add an Enemy Torpedo
The process involves watching various footage of Computer Space to study, what's actually going on. Frame by frame.
And here are some fun facts, we returned with from our video sessions:
- The saucers' cannon is located at the bottom right side of the rim. A torpedo originates always from this spot, regardless of the direction of the shot or the saucer's motion.
- While this is somewhat concealed by the framing and by the saucers vanishing in off-screen areas at the sides and at top and bottom of the screen, respectively, it is always the same saucer that's shooting.
- In fact, there is only a single saucer and a mirror image. Besides the visual thrills of the second saucer image, the only internal concern of the second position is for hit detection. We may shoot at and collide with this fata morgana, but that is all there is.
- Like we've seen it already with the saucers' movements, a shot is restricted to 45° angles and takes one of the 8 major directions (N, NE, E, SE, S, SW, W, NW).
- Last but not least, there's just a single shot at any time.
Based on this knowledge, the thing should be straight forward to implement. We'll add the few instructions for managing the enemy torpedo (easier than what we did for the rocket ship's one) and start with some random targeting, just to see, if we did this right. — And, lo and behold, there's actually a torpedo. But for some reason unknown, there's no hit detection.
We stare at our code, find a little issue that shouldn't cause that much of an effect and retry. Still, the enemy torpedo passes elegantly through the outline of our rocket ship, and, … nothing, no spinning ship. We do some staring, again — BTW, we're beginning to understand, why "Men Staring at Code" wouldn't have been as much fun of a series title as "Halt and Catch Fire" —, but there's nothing to find. We insert an unconditional jump, just to test, if we're really passing this piece of code, … nothing, again. — ??? — Luckily, it's National Holiday in Austria and there's nothing else to do. Eventually, we realize, we've forgotton to update a jump target and the collision check is only visted, if we are shooting a torpedo ourselves. — Fine.
This is actually the first — and, for the time being, only — time, we're missing a debugger. Otherwise, we have to be grateful for our newfangled coding environment. It's that easy to edit the source on screen, insert or delete some instructions or entire blocks of code, a keypress later it has passed the assembler and we're reloading the virtual tape into our emulator. No re-editing of the source code. No clunky console typewriter. No address fixing with the online debugger after each newly inserted instruction. No going back to the source code to update it after any interim online coding sessions. No repunching. No feeding newly punched paper tape in the assembler to get a new object tape. — We've to stand in awe in front of those, who did a complex game like Spacewwar! in just 200 hours.
(Personally, I've always read this figure with a grain of salt. It's much like being asked about the time actually spent on a pet project and answering, Umm, about a month or so (mumble).)
Enemy A.I.
We've already implemented random-based movement for the saucers — and it doesn't seem to be much more about it in Computer Space. (Actually, it's probably more patterns of movement randomly selected, similar to the "ghosts" in Ms Pac-Man.) We're going to stick with this and see, how far it will get us.
As mentioned previously, the A.I. for the saucers in Computer Space is quite deadly and it's not an easy game. (In fact, the game is supposably — if this is a word — hooking its player by annoying her/him.) As also previously mentioned, I'm supposing, much of this deadly effect is attributed to the confined space, the game is taking place in, and that a rather dumb A.I. may perform even better under these circumstances than a more sophisticated one. — But how dumb is it?
Let's start with some cooling for nice intervals with some random noise added to it. If we've counted up on the cooling, the saucers will shoot. We select the direction nearest to the supposed position of the target (the player's rocket + some look-ahead in the direction of its movement delta + some random noise) and fire.
After some tinkering, this actually works — but it's not good enough. Not that dumb.
(In case you're keeping track of version numbers and are wondering, what may have happened to version 0.2, this was it.)
The next best thing is the saucer looking out for a target to appear in any of its shooting directions. We do this, by setting up a target position in front of the moving rocket, compare this to an epsilon (the size of the ship + some random noise), and, if the rocket happens to appear in this algorithmic crosshair, we go for it.
This works actually nice and is rather close to the original. It is even as mercilessly pounding a sitting ship as long as it is in range as the original game. Maybe we improve this later a bit by adding the noise rather to the target position than to the epsilon, but this is esentially what we're going with.
(Also, this is still something that could be done with a handful of logic chips and a good plan, just like in Computer Space.)
Update: This is how we actually do it:
- Set up epsilon by adding some random noise to a base value.
- Get reference position by adding the offset of the cannon to the saucer's position.
- Now, get dx:
- load rocket dx
- scale it for look-ahead
- (here we may add some random noise later)
- add rocket x-coordinate
- subtract reference x-coordinate
- store dx
- get absolute value and store it.
- Same for dy …
- Is abs. dy < epsilon ?
If so, we have a horizontal path to the target.- Set up index for lookup table depending on sign of dx.
- Jump to motion setup.
- Is abs. dx < epsilon ?
If so, we have a vertical path to the target.
Proceed as above, but check sign of dy … - Get absolute difference of abs. dx and abs. dy.
- Is it less than epsilon?
If so, we have a diagonal path to the target.- Check sign of dy and set up a base index for motion lookup.
- Check sign of dx and adjust the index for the lookup accordingly.
- Go there.
- No target found. Skip to exit.
- Motion lookup: Shift index left (×2), add to base of lookup table. Load torpedo dy and dx from the table indirectly.
- Finally, continue where we came from.
But there's still more to consider, namely motion and pace.
Watching the game, it's much like as if the saucers passed along a diagonal path faster than they would in horizontal or vertical directions. Even, if not so, the game's principal orientation is essentially horizontal, due to the 4:3 aspect ratio of its TV screen. So, a rather shallow, somewhat compressed movement vector for the diagonal motions may be favorable for our game on the round scope of the Type 30 CRT. We chose to implement this by defining "diagonals" in the ratio of (octal) 600:400, compared to a movement vector of a length of (octal) 600 in any other, principal direction. This looks pretty neat and we've still an option (sense switch 5) for true, geometrically correct diagonal motions for the saucers.
We add some code to compensate for these distored diagonals, which are to be inherited by the torpedoes, as well. But the result is not that great: The speed varies with the direction of the torpedo, which is pretty irritating, to say the least. So, we'll have diagonals proper for the torpedo and go with a ratio close to the square root of 2:
Moreover, a pleasing velocity for the torpedo doesn't work out just of the motion vectors of the saucers. Either it's too fast or too slow. Thus, torpedoes get a movement table of their own, similar to what we've implemented already for the saucers.
The Fata Morgana Awakens
As mentioned above, in Computer Space, there is really just a single saucer displayed twice. And only one of the images, the "real" ship, will shoot at you. But it isn't very likely that a casual player may actually discover this, as large areas of the game are clipped and the saucers are frequently crossing the screen, disappearing into the hidden nothingness of pure mathematical positions, shooting from this hidden ambush with shots suddenly appearing at the borders of the screen, and so on. It's hard to keep track, which saucer is which.
In our implementation, there are no hidden areas. So, will this still hold up?
As we're not too sure, we implement an option (sense switch 6) to select the main saucer at random for each frame. If deployed, even the mirror image gets its fair chance of going after the rocket ship. In order to not to give away this little secret of Computer Space too easily, we give this option a mystery name, "Saucer Piloting".
That's pretty much it. — And we've a playable game!
▶ Experience the code live: www.masswerk.at/icss/.
Phew!
- Scores and score display (1 digit each, hex).
- Time keeping and time display (dezimal, 2 digits).
- An idle/attract state of the game. (Saucers only.)
- Tweaking the parameters.
And here's our growing code, so far:
ironic computer space 0.3 nl 2016-10-26 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) etl, 15, law i 240 / enemy torpedo life etd, 16, law i 30 / enemy torpedo cooling (frames) ela, 17, sal 5s / scaling enemey torp look-ahead ete, 20, 24000 / epsilon for enemy aiming etn, 21, sar 6s / scaling noise for enemy torpedo aim ran, 22, 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 dzm \cwo /old control word /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 dzm \ets / torpedo state jsp etr / get torpedo cooling (\etc) /main loop fr0, load \ict, -2100 / initial instruction budget (delay) idx \frc / increment frame counter clf 1 / flag 1 indicates active player's ship clf 2 clf 3 jsp bg / display background jsp rkt / rocket routine jsp ufo lac \trs sza jsp trp / torpedo routine lac \ets sza jsp etm / enemy torpedo routine jsp df / check collisions lac \cw dac \cwo count \ict, . / use up rest of time of main loop jmp fr0 / next frame /collision / hit detection df, clf 5 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 dfe 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 dfe, lac \ets /enemy torpedo active? sza i jmp dfx lac (11000 dac \dxe dac \dye sar 1s dac \de2 law \px dap mx1 law \py dap my1 law \epx dap mx2 law \epy dap my2 jsp dmf sza i jmp dfx jsp etr /reset enemy torpedo stf 5 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 szf i 5 jsp urs /respawn saucers jmp dfx dxu, dzm \trs /set up saucer explosion, reset torpedoes dzm \ets 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 eta /continue for torpedo setup 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 eta 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 movement table szs i 50 /sense switch 5 to select table (ut1 or ut2) jmp . 3 add (ut1 /setup reference location for dy in \t1 jmp . 2 add (ut2 /select other table 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 eta lac \udc /udc = 0..5 sub \udd dac \udc sma jmp . 4 law 5 dac \udc jmp eta sad (6 dzm \udc jmp eta 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 jsp etr /reset torpedo, get cooling law 1 /set state to active dac \ufs urx, jmp . /saucer movement table (dy, dx) ut1, 600 0 -600 0 0 600 0 -600 400 400 -400 400 400 -400 -400 -400 ut2, 600 0 -600 0 0 600 0 -600 400 600 -400 600 400 -600 -400 -600 /saucer explosion (similar to spacewar) / jump here from inside ufo uxe, law uxs /set up instr at uxs to be xct-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 /saucer torpedo ai (setup) / here from inside ufo eta, lac \ets /torpedo already active? spa jmp ufi szf i 1 /rocket active? jmp ufi lac \ufs /saucers active? spq jmp ufi isp \etc /count up cooling jmp ufi dzm \etc lac \upx /position of tubes add (10400 dac \epx lac \upy sub (3400 dac \epy szs i 60 /sense switch 6 to select saucers at random jmp et1 / original shoots always from same saucer random sma /which saucer jmp et1 lac \epy add (400000 dac \epy et1, random sar 3s xct etn add ete spa cma dac \eps lac \rdx xct ela /look-ahead add \rpx sub \epx dac \dx /delta x spa cma dac \adx /abs delta x lac \rdy /same for y xct ela add \rpy sub \epy dac \dy spa cma dac \ady sub \eps spa jmp eah lac \adx sub \eps spa jmp eav lac \adx sub \ady spa cma sub eps sma jmp ufi ead, law 4 lio \dy spi law 5 lio \dx spi add (2 jmp et3 eah, law 2 lio \dx spi law 3 jmp et3 eav, cla lio \dy spi law 1 / jmp et3 et3, sal 1s /read corresponding dx/dy form movement table add (ut3 /setup reference location for dy in \t1 dac \t1 lac i \t1 /load dy indirectly from addr. in \t1 dac \edy idx \t1 /increment addr. in \t1 lac i \t1 /load dx indirectly from addr. in \t1 dac \edx xct etl dac \ets eti, lac \ict /update instruction count add (40 dac \ict jmp ufi /return to ufo /saucer torpedo movement table (dy, dx) ut3, 2400 0 -2400 0 0 2400 0 -2400 1600 1600 -1600 1600 1600 -1600 -1600 -1600 /saucer torpedo movement routine etm, dap etx isp \ets /count up life jmp . 2 jmp etr 1 /expired lac \epy add \edy dac \epy swap lac \epx add \edx dac \epx dpy -i 100 etx, jmp . /return etr, dap etx /reset dzm \ets xct etd dac \etc jmp etx /player rocket routine rkt, dap rkx 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 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 i /check next bit for fire jmp . 7 szs i 30 /sense switch 3 for single shot jmp . 4 lio \cwo ril 3s spi i /block if already set in old control word 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 lac \ufs spq /saucers active? jmp rkq /no 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) trp, 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 40 /sense switch 4 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.45 K 18-bit words, 02716 locations incl. variables.)
That's all for this post.
▶ Next: Episode 9: Scores, Just in Time
◀ Previous: Episode 7: Skeet in Space
▲ Back to the index.
2016-10-26, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/10. —