; SPCWAR -- GT40 (PDP-11) Spacewar, copyright 1973 by Botond G. Eross, ; Artificial Intelligence Project, Computer Science Department, Stanford University. ; Recovered from the Stanford SAILDART archive (SW.P11[11,BO], filedate 1975-04-11); ; SailDart page directory and HTML wrapper stripped, HTML entities unescaped. ; C⊗; .TITLE SPCWAR GT40 SPACEWAR PROGRAM COMMENT⊗ COPYRIGHT 1973 by Botond G. Eross Artificial Intelligence Project Computer Science Department Stanford University Stanford, California 94305 (I've been reading too many DEC listings.) This is yet another adaptation of the infamous traditional PDP-1 Spacewar game, this time for the DEC Graphic 11. The inspiration for this version came from Ralph Gorin's Stanford A. I. Project PDP-10 Spacewar. However, the opinions expressed in this program are the author's alone and do not necessarily represent those of the Intergalactic Olympic Committee, the Spacewar League of America, or the management. A warning to the export user of this program: the language in which it is written is not standard PAL-11, but a modified version which I kludged up from an obsolete assembler. This source file needs to be assembled together with my own file of PDP-11 definitions. Bo Eross Stanford A. I. Project August-September 1973 ⊗ ;OPERATING INSTRUCTIONS AND RULES OF THE GAME COMMENT⊗ Program starting address is 1000. Halting the program and restarting it at this location will reset the scores to zero. The game uses the display screen as the playing field. The screen represents a finite two-dimensional universe with a sun at the center and a (purely decorative) background of stars. The sun exerts a gravitational pull which varies by the inverse-square law that we all know and love. The universe is toroidal, so that when an object moves off one side of the screen it appears on the opposite side. The two players have spaceships which they may maneuver around on the screen. Each ship can fire deadly torpedoes out of a launching tube in its nose. The object of the game is to shoot your opponent down and prevent him from doing the same to you. A "hand" of spacewar starts with the screen empty except for a five- second countdown that shows in the center of the screen. When the countdown reaches zero, the universe appears with the ships in their starting positions: player 1 at the bottom left and player 2 at top right. The "hand" is over when at least one ship has been blown up and the other (if he's still around) is clearly a survivor. At the beginning of a hand, each player has 50 torpedoes in his ship's stores; if he runs out, that's all. A count of the number of torps remaining appears near each player's starting position. The player's score shows in the same area. The score is tallied at the end of each hand and accumulates throughout the game, which ends when both players agree that they've had enough. The controls for the ships are the front-panel data switches on the PDP-11. Pushing a switch up starts an operation, down stops it. However, it takes time to reload a torpedo tube, so there is a noticeable delay after firing before you can fire again. A bright spot appears in the nose of each ship when its torp tube is loaded. The switches are assigned according to this table: Player Switch 1 2 Function Switch 15 3 Rotate counterclockwise (turn left) Number: 14 2 Rotate clockwise (turn right) 12 1 Fire the engine (accelerate forward) 11 0 Shoot a torpedo. The ships' motions are subject to the laws of Newtonian mechanics: you keep going the way you were headed until something happens to change your speed or direction. The things that change your velocity are the sun's gravity and firing your thruster. Rotation is at a constant angular velocity. The "left" and "right" switches cancel each other if they are both on. The torps are really Photon Torpedoes, which have no mass. They are therefore immune to gravitation (go in a straight line) and they can pass through the sun. A torp does not necessarily travel in the direction you aimed it, however. Its velocity depends on which way the ship's nose was pointed and which way it was travelling at launch time. Thus, if you fire a torp straight back along your path and you're travelling at the right speed, the torp will stand still. Each torpedo has a fixed lifetime. If it hasn't hit anything when its time has run out, it will explode harmlessly. Just to add a little spice to the game, there are four "space mines". The mines appear at the beginning of each hand, in randomly-determined positions and approximately circular orbits about the sun. If anything hits a mine, it will be destroyed. You can destroy mines by hitting them with torpedoes. You can also shoot torpedoes down if your aim is good. The sun is another hazard: it destroys anything that approaches it too closely, except torpedoes. The final hazard is the other ship. If the two collide, both explode. This gives you a last resort if you've used up all of your torps: you can try to take your opponent down with you. Scoring is as follows: one point for shooting your opponent, one point for surviving to the end of the hand, and minus one point if you manage to shoot yourself. The hand ends two seconds after the first ship is destroyed unless some of that ship's torpedoes are still around. If they are, the hand continues until the last torp of the last-exploded ship has expired. So beware! It is not enough to finish your opponent off. You must continue to defend yourself against the cruel universe. ⊗ ;PROGRAM ORIGIN, ACCUMULATOR DEFINITIONS, BASIC PARAMETERS ;ACCUMULATORS AC0 ←%0 ;Register names used generally MQ0 ←%1 AC1 ←%2 MQ1 ←%3 T0 ←%4 T1 ←%5 X ←AC0 ;More obvious mnemonic names for position crunching. Y ←AC1 ANG ←MQ0 SHIP ←MQ1 ;Nearly always points to a ship. MODL ←T0 ;Location of the drawing teemplate. QUAD ←T1 ;Quadrant of an angle. TYP ←MQ0 ;Type of collision OBJ1 ←SHIP ;Objects involved OBJ2 ←T0 DL ←T1 ;Display list pointer for collision scanner. COS ←T0 ;Used only in the one-time initialization. SIN ←T1 ;Parameters DEFSHP ←2 ;Default number of ships MXSHIP ←2 ;Max number of ships, DEFMIN ←4 MXMINE ←4 ; mines, TRPLIM ←62 ;Limit 50 torps per ship ATLIM ←"50 ;Torp limit in ASCII. NSBRT ←6 ;# of star brightness levels NSTARS ←10 ;# of stars of each brightness. MXSBRT ←D.PNT+.INT7 MNSBRT ←D.PNT+.INT2 DBLDSP←1 ;Use double-precision position, velocity, acceleration. XOFF ←.GXUL+1⊗<-1> ;Screen position of center of sun. YOFF ←.GYUL+1⊗<-1> MOTLVL ←3 ;Priority level for MOTION PRXLVL ←2 ; for PROX EXPLVL ←2 ; for EXPLODE ;DESCRIPTIONS OF OBJECTS (leagues, scaled to be furlongs, ; see the description of the universe) SHIPL←40⊗5 ;Half-length of a ship SHIPW←10⊗5 ;Half-width of a ship OVALR←*2 ;"Radius" of a ship's colision oval. MINER←10⊗5 ;Radius of a mine TORPR←5⊗5 ;Radius of a torp for torp-torp collisions COMMENT⊗ For collision purposes, a ship is defined to be an ellipse of width 2*SHIPW with foci at SHIPL from its center. ⊗ .←1000 PDL: ;THIS SHOULD BE ENOUGH ROOM FOR THE STACK. START: JMP @(PC)+ STRTAD: .WORD CRANKUP ;Gets changed after the one-time setup. ;DESCRIPTION OF THE UNIVERSE COMMENT⊗ DEFINITIONS OF PHYSICAL UNITS LENGTH: The Astrological Unit (AU) is the fundamental unit of distance, defined as the width of the displayable portion of the GT40 screen. The Furlong (FL) is 2↑-15 AU. This is the shortest useful distance in our universe, though in order to avoid roundoff errors some things are kept in fractional furlongs. The League (L) is 2↑-10 AU. This is the spacing of resolvable points in the Display Processor's world. TIME: The fundamental unit is the Tick, 1/60 second, which is the frequency of the KW-11L clock. The Fortnight is an integer multiple of ticks, chosen to be as small as possible while still guaranteeing that the motion calculations can run to completion in this time. ANGLE: The Semiwheel (SW), defined as 1/2 of a full revolution, is the fundamental unit. The Hemi-demi-semiwheel (HW) is 2↑-15 SW. This is the smallest conceivable increment of revolution in our quantized universe. In fact, the quantum of rotation is 2↑9 HW or 2↑-6 SW: That is the spacing of the entries in the sin-cos table, and should be fine enough for all practical purposes. FUNDAMENTAL PHYSICAL CONSTANTS G The Sun's gravitational constant, picked out of a hat to make reasonable-looking orbits. Units are AU↑3/FN↑2. A The acceleration of a ship at full thrust, arbitrarily chosen to maximize the aesthetic appeal of the dynamics of the game. Units are AU/FN↑2. R The rotation rate of a ship being steered left or right, chosen in a manner similar to A. Units are SW/FN. T The launch speed of a torpedo relative to the launching ship. Units are AU/FN. S The radius of the sun; the minimum radius of an orbit. Picked with an eye toward maintaining as much precision as possible in the fixed-point calculations that determine the acceleration due to gravity. ⊗ ;FURTHER DESCRIPTION OF THE UNIVERSE COMMENT⊗ IMPORTANT NOTATIONAL NOTE The reason behind the above choice of units is to simplify, as much as possible, the calculations. One may consider a position measurement to be either in integral furlongs or in fractional astrological units (with the radix point just right of the sign bit); the binary representation is the same in either case. Similarly, angles can be thought of as fractional semiwheels or integral hemi-demi-semiwheels. The reason for the choice of semiwheels instead of degrees or radians is that angular computations automatically wrap around the full circle from 2↑15-1 to -2↑15 hemi-demi- semiwheels (i.e., from π-ε to -π). Because this machine lacks even multiply and divide hardware, not to mention floating point arithmetic, we have to make do with scaled integer operations. The multiply routines assume that the operands are signed fractions with 15 bits of precision, and they return a 30-bit fraction with the implied radix point right after the sign. To make life easier, there is a semi-automatic scaling system. For each variable that is subject to some scaling operation, there is defined a symbol which is the scaling of that variable. The SCALE macro can be invoked using these symbols to get the results of an operation in the right position. Certain important parameters, such as the gravitational constant, can be patched in core to alter the physics of the game, but if the desired change requires a change in scaling the program must be recompiled. The literal meaning of the scale of a variable is the position of the binary point to the right of bit 15: that is, a scale of 0 means the point is between bits 15 and 14; a scale of 1 means between 14 and 13. ⊗ ;DEFINITION OF PHYSICAL CONSTANTS .MACR FPCON XSYM,SYM,XVAL,VAL ;Define a pseudo-floating constant. .EXP←XVAL .FRA←VAL NORM .EXP,.FRA XSYM ←.EXP SYM: .WORD .FRA .ENDM .MACR NORM EXP,FRA ;Sub-macro to normalize. .IFNZ FRA∧100000 EXP←EXP+1 FRA←⊗<-1> .ELSF Z FRA∧40000 EXP←EXP-1 FRA←FRA⊗1 NORM EXP,FRA .ENDC .ENDM XTPFN←2 TPFN←3 ;3 ticks per fortnight TRPLIF ←1130/TPFN ;Torps live 10 seconds TRELOD ←132/TPFN ;It takes 1.5 seconds to reload. SHPTRP ←TRPLIF+TRELOD-1/TRELOD+1 ;Max torps/ship MXTORP ←SHPTRP*MXSHIP ;Overall maximum no. of torps. ;G = 1.5*2↑-23 au↑3/tick↑2 FPCON XG,G,-30+17,3*TPFN*TPFN ;A = 1.5*2↑-20 au/tick↑2 FPCON XA,A,-25+17,3*TPFN*TPFN XR←-10 ;R = 1.5*2↑-8 sw/tick R:140000⊗XR*TPFN ;scaled to add directly to heading. ;T = 1.25*2↑-10 au/tick FPCON XT,T,-14+17,5*TPFN XS←-6 ;S = 2↑-6 au S: .WORD 100000⊗XS ;Scaled to add directly to distances. SQ: .WORD 100000⊗<2*XS> ;S↑2 (for minimum orbit distance test) XD←0 ;Scaling of distances XID←-XS ;Scaling of inverse distance XGD←XG+XID ;Scaling of G/D for mine velocity calculation, .IFNZ XGD∧1 ;has to be even for the square root. XGD←XGD+1 .ENDC ;HANDY DANDY MACROS ;SCALE , ;SCALE operates on (AC0,MQ0). SCALE1 does (AC1,MQ1). .MACR SCALE CUR,DES .SC←- .IFNZ .SC .IFG .SC .REPT .SC ASL MQ0 ROL AC0 .ENDR .ELSE .SC←0-.SC .REPT .SC ASR AC0 ROR MQ0 .ENDR .ENDC .ENDC .ENDM .MACR SCALE1 CUR,DES .SC←- .IFNZ .SC .IFG .SC .REPT .SC ASL MQ1 ROL AC1 .ENDR .ELSE .SC←0-.SC .REPT .SC ASR AC1 ROR MQ1 .ENDR .ENDC .ENDC .ENDM .MACR RNDAC AC ASL AC+1 ADC AC .ENDM ;PROGRAM ORGANIZATION COMMENT⊗ The basic assumptions behind the way I chose to write this program are (1) it is extremely important to maintain a fine time resolution in order to do a decent simulation of orbits, and (2) it is not quite so important to do all of the calculations for collisions in real time since the time window during which objects may pass through each other is fairly broad. Accordingly, the program is broken up into sections that operate at different levels of priority, using the PDP-11 interrupt structure as a framework. The core of this priority framework is the procedure SETPRI, which takes a priority level and a procedure address as arguments. If the priority saved on the stack is lower than the given one, SETPRI changes the program's priority level and jumps to the procedure; otherwise it merely returns. Thus an interrupt-level procedure can effectively generate a lower-level interrupt to get another procedure going if it is not already in progress. The highest priority is the short routine CLKSER which takes the clock interrupts. This routine merely counts ticks until a fortnight has passed (only two, according to plan, but this quantity can easily be changed if it turns out to be necessary). At the proper time, this routine activates MOTION. MOTION runs at priority 3 and does the real-time calculations for all orbits. If it finds something coming too close to the sun, MOTION will mark the object as dead - otherwise, MOTION does not concern itself with collisions. When this section has finished one fortnight's calculations, it starts PROX if that program is not already running. PROX runs at priority 2 to perform the tests for collisions. It does an exhaustive check on every pair of objects, and allows objects that have been dead for less that 1/10 second to participate. This procedure is not strictly necessary and may in fact be changed later to save some compute cycles, but it's a nice way to take care of the pornographic case where more than two things reach the same place at the same time. When an object (other than a torpedo) is found to be dead, the subroutine KILLIT marks it and enables the display halt interrupt. The next time the display stops, EXPLODE scans the display list for just-killed objects. These things it re-draws as a starburst. There are four stages in the death of an object: just killed, when KILLIT has marked it dead; exploding, when EXPLODE has made the burst pattern; the post-explosion fade (ships only), which gives a time delay after the next-to-last ship has exploded before starting a new game; and fully dead, when all traces of the object have been removed from the display list. EXPLODE runs at priority 2, but takes precedence over PROX because the interrupt comes at BR4. ⊗ ;RESTART INITIALIZATION PWROFF: RESTRT: RESET ;Start here for a whole new game. MOV #PDL,SP ;Set up stack and MOV #PWROFF,PWR.V ;power-fail trap. MOV (PC)+,@(PC)+ PSWD 7 PWR.V+2 CLR .PSW ;Priority 0. ;Brand new game... set scores to 0. MOV #DEFMIN,NMINES MOV #DEFSHP,T1 ;Default number of ships MOV T1,NSHIPS MOV #DLIST,AC0 MOV #DPYSCR,MQ0 CLRSCR: MOV #D.JMP,(AC0)+ ;Display list jumps to score. MOV MQ0,(AC0)+ ADD #DSCORE-DPYSCR-6,MQ0 MOV #" ,(MQ0)+ ;Display score ← 0 MOV #" ,(MQ0)+ MOV #" 0,(MQ0)+ ADD #DSCEND-DSCORE,MQ0 MOV AC0,(MQ0) ;End of score - jump back to dpy list. ADD #SCORE-DSCEND,MQ0 CLR (MQ0) ;Clear the real score. ADD #SHPEND-SHPTAB+DPYSCR-SCORE,MQ0 ;Move to next ship. DEC T1 BGT CLRSCR MOV AC0,DSHPNT ;Save pointer → first ship in dpy list. ;Fall through. ;New hand - set the stars in the sky. NEWHND: MOV #MXSBRT,AC1 ;Begin with max brightness. MOV #DSTARS,MQ1 SETSTR: MOV AC1,(MQ1)+ ;Set point mode and brightness. MOV #NSTARS,T1 ;This many at each intensity. SETST1: PUSHJ RANDOM ;Get a coordinate. ASR MQ0 ;Normalize to screen dimensions. MOV MQ0,AC0 ASR AC0 ASR AC0 SUB AC0,MQ0 .REPT 5 ;Convert to display units. ASR MQ0 .ENDR ADD #XOFF,MQ0 BIC #176000,MQ0 BIS #40000,MQ0 ;Turn on the brightness bit, MOV MQ0,(MQ1)+ ;save as X location. PUSHJ RANDOM ;Do the Y in a similar way. ASR MQ0 MOV MQ0,AC0 ASR AC0 ASR AC0 SUB AC0,MQ0 .REPT 5 ASR MQ0 .ENDR ADD #YOFF,MQ0 BIC #176000,MQ0 MOV MQ0,(MQ1)+ DEC T1 ;Test for enough at this intensity. BGT SETST1 SUB #.INT1-.INT0,AC1 ;Go to next intensity. CMP AC1,#MNSBRT BGE SETSTR ;Fall through. ;Set up ships MOV DSHPNT,AC0 ;→ dpy list. CLR SWITCH MOV NSHIPS,T1 MOV #TRPLFT,SHIP NXTSHP: MOV #ATLIM,DTLFT-TRPLFT-2(SHIP) ;Display torp limit MOV #TRPLIM,(SHIP)+ ;and real torp limit. CLR (SHIP) ;Reload timer MOV INANG-RELTIM(SHIP),HEADING-RELTIM(SHIP) ;Heading ADD #VELOCX-RELTIM,SHIP ;Skip over torp velocity, MOV #/2,T0 ;nose, and thrust. CLRVEL: CLR (SHIP)+ ;Clear ship's velocity DEC T0 BGT CLRVEL MOV SHIP,AC1 ADD #INPOS-POSITX,AC1 MOV #/2,T0 SETPOS: MOV (AC1)+,(SHIP)+ ;Set initial position. DEC T0 BGT SETPOS CLR (SHIP)+ ;Clear status. MOV AC0,(SHIP)+ ;Set pointer back to dpy list. MOV #D.JMP,(AC0)+ ;Make dpy list jump to ship's MOV SHIP,(AC0)+ ;base address. MOV SHIP,AC1 ;Leave the base address alone. MOV #D.PNT,(AC1)+ ;Point mode instruction, CLR (AC1)+ ;X and CLR (AC1)+ ;Y coordinates for dpy. MOV #D.SVEC+.INT6,(AC1)+;Start drawing - short vector CLR (AC1)+ ;torp tube, CLR (AC1)+ ;move to tail MOV #D.SVEC+.INT6+.BLINK,(AC1)+ ;Blinker for flame CLR (AC1)+ ;flame. MOV #D.SVEC+.INT4+.NOBLINK,(AC1)+;Display back to normal. MOV SHIP,MQ0 ADD #MODEL+3-BASE,MQ0 ;Get address of model. DRWSHP: MOV #40000,(AC1)+ ;Set intensity bits for the model. TSTB (MQ0)+ BEQ DRWEND ;A zero ends the drawing, BPL DRWSHP ;positive means visible, CLR -2(AC1) ;negative is invisible. BR DRWSHP DRWEND: MOV #D.JMP,-2(AC1) ;Finish up with a DJMP back to MOV AC0,(AC1) ;the dpy list. PUSH AC0 PUSH T1 MOV HEADING-BASE(SHIP),AC0 ;Get the heading, PUSHJ SHIPROT ;Go draw the ship. POP T1 POP AC0 ADD #SHPEND-SHPTAB+TRPLFT-BASE,SHIP ;move to next ship. DEC T1 BGT NXTSHP ;Fall through. ;Lay mines MOV AC0,DMIPNT ;Save pointers to mines in dpy list. MOV #FREEST,SHIP MOV NMINES,T1 BGT LAYMI0 JMP MINEND LAYMI0: PUSH AC0 ;Save display list pointer. LAYMIN: MOV #2+<2*DBLDSP>,T0 LAYMI1: CLR (SHIP)+ ;Clear mine's velocity. DEC T0 BGT LAYMI1 MOV #2,AC1 LAYMI2: PUSHJ RANDOM ;Make a random position. ASR MQ0 MOV MQ0,(SHIP)+ .IFNZ DBLDSP CLR (SHIP)+ .ENDC DEC AC1 BGT LAYMI2 PUSHJ RANDOM ;Random direction for later. MOV #2,(SHIP)+ ;Status and identifier for mine. POP AC0 MOV AC0,(SHIP)+ ;Pointer to display list. MOV #D.JMP,(AC0)+ ;Set the dpy list to jump to the mine. MOV SHIP,(AC0)+ PUSH AC0 PUSH T1 PUSH SHIP MOV #D.PNT,(SHIP)+ ;Mine drawing... CLR (SHIP)+ ;location on screen, CLR (SHIP)+ MOV #D.SVEC,(SHIP)+ ;a short vector to offset MOV (PC)+,(SHIP)+ ;to the start of a character, SVEC -.CXOFF,-.CYOFF,.INVIS MOV #D.CHR+.INT4,(SHIP)+;Character mode, MOV #'*,(SHIP)+ ;display an asterisk, MOV #D.JMP,(SHIP)+ ;and jump back to the display list. MOV AC0,(SHIP)+ MOV (SP),SHIP PUSHJ DSQ ;Square of distance from sun... BGT LAYM2A POP SHIP ;Too close. The mine will explode BR LAYMI4 ;immediately. LAYM2A: PUSHJ SQRT ;Distance from sun, MOV MQ0,T0 MOV #100000⊗<-XID>,AC0 CLR MQ0 PUSHJ UDIV ;inverse of distance. CLC ROR MQ0 ADC MQ0 PUSH MQ0 MOV G,T0 PUSHJ SMUL ;G/D SCALE XG+XID,XGD PUSHJ SQRT ;sqrt(G/D) MOV MQ0,AC0 POP MQ0 PUSH AC0 MOV 2(SP),SHIP MOV POSITY-BASE(SHIP),T0 NEG T0 MOV POSITX-BASE(SHIP),T1 PUSHJ TWOMUL ;(-Y,X)/D SCALE XD+XID,1 SCALE1 XD+XID,1 RNDAC AC0 RNDAC AC1 MOV AC0,T0 MOV AC1,T1 POP MQ0 PUSHJ TWOMUL ;(-Y,X)/D * sqrt(G/D)... SCALE XGD/2+1,XD ;is the initial velocity for SCALE1 XGD/2+1,XD ;a circular orbit. .IFZ DBLDSP RNDAC AC0 RNDAC AC1 .ENDC TST RANDNO BPL LAYMI3 NEG AC0 ;Randomly reverse direction. NEG AC1 .IFNZ DBLDSP NEG MQ0 SBC AC0 NEG MQ1 SBC MQ1 .ENDC LAYMI3: .IFNZ DBLDSP ;Give the mine its velocity. POP T0 MOV MQ0,VELOCX+2-BASE(T0) MOV MQ1,VELOCY+2-BASE(T0) MOV T0,SHIP .ELSE POP SHIP .ENDC MOV AC0,VELOCX-BASE(SHIP) MOV AC1,VELOCY-BASE(SHIP) LAYMI4: ADD #14+<2*MBRSTL>,SHIP ;Advance to next mine. POP T1 DEC T1 BLE LAYMI5 JMP LAYMIN LAYMI5: POP AC0 MINEND: ;Fall through. ;Initialize torp list, and count down. MOV AC0,DTRPNT MOV SHIP,TRPNT MOV #MXTORP,T1 CLRTRP: MOV #D.JMP,(AC0)+ ;Set each torp slot in the dpy list TST (AC0)+ ;to a DJMP .+1 MOV AC0,-2(AC0) DEC T1 BGT CLRTRP MOV #D.NOP,(AC0)+ ;end the list with a DNOP MOV #D.JMP,(AC0)+ ;and a DJMP back to the beginning. MOV #DPRO,(AC0) ;Now for the big count down, and let the games commence. MOV #CTDWN,LFK.V ;Set up clock vector MOV (PC)+,@(PC)+ PSWD LFK.P .WORD LFK.V+2 MOV #DOFINT,DHLT.V ;and display stop vector. MOV (PC)+,@(PC)+ PSWD EXPLVL .WORD DHLT.V+2 MOV #74,TICCNT ;Exactly one second at a time, MOVB #'5,DWNCNT ;Count down from five. MOV #D.SETS+.STPDIS+.NOITAL+.SYNCH,DCTDWN MOV #DCTDWN,D.PC MOV #LFK.EN,LFK.K CRANK: WAIT ;Wait for the count to finish. TST DCTDWN BNE CRANK MOV #1,TICCNT MOV #CLKSER,LFK.V ;Interrupt vectors for main program. MOV #EXPLODE,DHLT.V ;Fall through. ;MAIN PROGRAM (LEVEL 0) MOV #D.SETS+.STPDIS+.LPDIM+.NOITAL+.SYNCH,DPRO MOV NSHIPS,NACTS CLR NEXPS MOV #DPRO,D.PC ;Start up the display, MOV #LFK.EN,LFK.K ;enable the clock. MAIN: WAIT ;Wait until... CMP NACTS,#1 ;at most one ship remains BGT MAIN BLT MAIN1 CMP NSHIPS,#1 ;(unless it's a one-man game) BEQ MAIN MAIN1: TST NEXPS ;and there are no phantoms left. BGT MAIN MOV #DOFINT,DHLT.V ;All conditions met... CLR LFK.K ;Disable the clock COM DCTDWN ;stop the display MOV #D.SETS+.STOP+.STPEN,DPRO ENDIT: WAIT ;Wait until it's run down. TST DCTDWN BNE ENDIT TST NACTS ;Is there a survivor? BLE NXTHND MOV DSHPNT,AC0 FNDSCR: TST (AC0)+ ;There is. Search the display list MOV (AC0)+,SHIP CMP AC0,SHIP ;until you find him. BEQ FNDSCR INC SCORE-BASE(SHIP) ;Add one to his score. MOV SCORE-BASE(SHIP),MQ0 MOV #DSCORE-BASE,AC1 ADD SHIP,AC1 MOV #6,T1 PUSHJ BINASC ;Display the score. NXTHND: JMP NEWHND ;Start the next game. ;CLOCK LEVEL AND LEVEL-SHIFTING CLKSER: MOV .CSW,SWITCH ;Read the console switches. DEC TICCNT ;Count the time, BGT CLKRET ;test for a fortnight gone by. JSR R5,SETPRI ;Yup. Attempt to start MOTION. PSWD MOTLVL .WORD MOTION .BEEP ;HORRORS! It's already going! That means ;it overran its allotted time. Show audible ;displeasure at this state of affairs. CLKRET: RTI ;THE LEVEL-SHIFTER SETPRI: TVAR MOV 6(SP),(SP) ;Get the preceding priority off the stack. BIC #177437,(SP) CMP (SP)+,(R5)+ ;Compare with the desired level. BGE CNTSET MOV -2(R5),.PSW ;It's lower. Set the required level, MOV (R5),R5 ;get the procedure address, RTS R5 ;and cleverly leave the registers ;and stack as they were. CNTSET: TST (R5)+ ;It's too high. Bypass the procedure RTS R5 ;address and return. ;MOTION ROUTINE (LEVEL 3) MOTION: MOV #TPFN,TICCNT ;Reset his clock. PUSHACS MOV DSHPNT,AC1 NXTOBJ: CMP (AC1)+,#D.JMP ;Each display list entry is a DJMP. BNE OBJDON MOV (AC1)+,SHIP ;Get data address. CMP SHIP,AC1 ;Null entry (DJMP to next location)? BEQ NXTOBJ PUSH AC1 ;Nope. Save the index MOVB TYPE-BASE(SHIP),AC1 TSTB STAT-BASE(SHIP) ;Test whether living or dead. BEQ LIVOBJ ADD #DEDTAB-LIVTAB,AC1 LIVOBJ: PUSHJ @LIVTAB(AC1) ;Dispatch to proper calculations. POP AC1 BR NXTOBJ ;Repeat for each object. LIVTAB: .WORD LVSHIP .WORD LVMINE .WORD LVTORP DEDTAB: .WORD DDSHIP .WORD DDMINE .WORD DDTORP OBJDON: POPACS ;All done. Get ready to return. JSR R5,SETPRI ;Crank up PROX if it isn't running. PSWD PRXLVL .WORD PROX RTI ;DEAD SHIP MOTION DDSHIP: CMPB STAT-BASE(SHIP),#1 ;Exploded yet? BEQ FLASHX CMPB STAT-BASE(SHIP),#-100 BEQ FLASHX INCB STAT-BASE(SHIP) ;Yes. How long ago? BMI DDSHPX ;It's a phantom. BEQ HIDES2 ;It's an expired phantom. CMPB STAT-BASE(SHIP),#100 ;Flash is finished... BEQ HIDES ;erase it from the screen. MOV #IBURST,T0 MOV #IBRSTL,AC0 INCEXP: BITB #1,STAT-BASE(SHIP) BNE FLASHX ;Increment burst every other time. MOV #DRAWING-BASE,T1 ADD SHIP,T1 DDSHP1: ADD (T0)+,(T1)+ ;Add the burst increment to TST (T1)+ ;the burst. DEC AC0 BGT DDSHP1 FLASH: BITB #6,STAT-BASE(SHIP) ;Gradually decrease the brightness. BNE FLASHX SUB #.INT1-.INT0,DRAWING-2-BASE(SHIP) FLASHX: JMP GRAV ;The shrapnel is still subject to gravity. HIDES: JSR R5,KILLIT ;The explosion just finished... .BYTE -100,0 ;erase it and set delay timer. BR DDSHPX HIDES2: TSTB NTACT-BASE(SHIP) ;Expired phantom... BEQ BURYS ;Any torps still alive? DECB STAT-BASE(SHIP) ;Yes. Let the phantom live BR DDSHPX ;for a while. BURYS: DEC NEXPS BURY: MOV DPYITM-BASE(SHIP),T0 ;The explosion is over... ADD #4,T0 ;make the display list entry null. MOV T0,-2(T0) DDSHPX: POPJ ;Dead mine DDMINE: CMPB STAT-BASE(SHIP),#1 BEQ FLASHX INCB STAT-BASE(SHIP) CMPB STAT-BASE(SHIP),#100 BEQ BURY MOV #IMBRST,T0 MOV #IMBRSL,AC0 BR INCEXP ;TORPEDO MOTION LVTORP: DEC TRPAGE-BASE(SHIP) ;Live torp. Check its age. BGT LVTRP2 LVTRP1: INCB STAT-BASE(SHIP) ;Too old. Kill it. MOV #D.PNT+.INT7,(SHIP) ;Show a small flash. LVTRP2: JMP POSCAL ;Update the position. DDTORP: INCB STAT-BASE(SHIP) ;Dead torp - CMPB STAT-BASE(SHIP),#40;is it dead enough? BGT BURYT ;Yes. Get rid of it. BITB #3,STAT-BASE(SHIP) BNE DDTRP1 ;Otherwise, gradually diminish SUB #.INT1-.INT0,(SHIP) ;its brightness. DDTRP1: JMP POSCAL ;Keep it moving. BURYT: MOV TRPOWN-BASE(SHIP),T0;Torp completely dead... DECB NTACT-BASE(T0) ;Count down the owner's BR BURY ;active torp counter. ;LIVE SHIP MOTION LVSHIP: DEC RELTIM-BASE(SHIP) ;Torp reload delay over? BGT LVSHP0 CLR RELTIM-BASE(SHIP) ;Yes... TSTB TRPLFT-BASE(SHIP) ;Do you have torps left? BEQ LVSHP0 BIS #40000,TRPTUB-BASE(SHIP) ;turn the indicator on. LVSHP0: PUSH BUTBIT-BASE(SHIP) ;Get the switch-test bit. CLR AC0 BIT (SP),SWITCH BEQ LVSHP1 ;Turning left? ADD R,AC0 ;Yes. LVSHP1: CLC ROR (SP) BIT (SP),SWITCH BEQ LVSHP2 ;Turning right? SUB R,AC0 ;Yes. Left and right cancel each other. LVSHP2: TST AC0 BEQ LVSHP3 ;Turning? ADD HEADING-BASE(SHIP),AC0 ;Yes. Update heading, MOV AC0,HEADING-BASE(SHIP) PUSHJ SHIPROT ;rotate. LVSHP3: ASR (SP) BIT (SP),SWITCH BEQ LVSHP4 ;Thrusting? .IFNZ DBLDSP ;Yes. Add thrust increment to velocity. ADD THRSTX+2-BASE(SHIP),VELOCX+2-BASE(SHIP) ADC VELOCX-BASE(SHIP) ADD THRSTY+2-BASE(SHIP),VELOCY+2-BASE(SHIP) ADC VELOCY-BASE(SHIP) .ENDC ADD THRSTX-BASE(SHIP),VELOCX-BASE(SHIP) ADD THRSTY-BASE(SHIP),VELOCY-BASE(SHIP) BIS #40000,FLAME+2-BASE(SHIP) ;Turn flame on. BR LVSHP5 LVSHP4: BIC #40000,FLAME+2-BASE(SHIP) ;Not thrusting. Turn flame off. LVSHP5: ;Fall through to next page. ;TORPEDO LAUNCHING ASR (SP) BIT (SP),SWITCH ;Do you want to fire? BEQ ENDLNCH BIT #40000,TRPTUB-BASE(SHIP) ;You do, but is your gun loaded? BEQ ENDLNCH ;Not yet. You lose. CMPB NTACT-BASE(SHIP),#SHPTRP ;How many torps do you have out? BGE ENDLNCH ;Too many. Your gun is jammed. MOV DTRPNT,T0 ;Scan the display list for a LNCH1: CMP (T0)+,#D.JMP ;slot for a torpedo. BNE ENDLNCH ;If there isn't one, you lose. MOV (T0)+,T1 CMP T1,T0 BNE LNCH1 BIC #40000,TRPTUB-BASE(SHIP) ;Turn off the loaded indicator MOV #TRELOD,RELTIM-BASE(SHIP) ;and set the reload timer. INCB NTACT-BASE(SHIP) ;Increment the active torp count. PUSH SHIP SUB #4,T0 ;T0 points to display list entry. MOV T0,T1 SUB DTRPNT,T1 ;Make a pointer to the corresponding .REPT 4 ;free storage block ASL T1 .ENDR ADD TRPNT,T1 ;in T1. MOV SHIP,(T1)+ ;Name of torp's owner. MOV #TRPLIF,(T1)+ ;Lifetime of torp. MOV SHIP,AC0 ;Torp velocity ← ship velocity plus ADD #TVELX-BASE,AC0 ;launch speed vector, and ADD #VELOCX-BASE,SHIP ;Torp position ← ship position plus MOV #4,MQ0 ;nose offset vector. LNCH2: MOV (SHIP)+,(T1) ;Move high part of data vel. or pos., ADD (AC0)+,(T1)+ ;add offset, .IFNZ DBLDSP ;move low part. MOV (SHIP)+,(T1)+ .ENDC DEC MQ0 BGT LNCH2 MOV #4,(T1)+ ;Set type and status, MOV T0,(T1)+ ;and pointer to dpy list. MOV T1,AC0 ;Save torp's base address. MOV #D.PNT+.INT4,(T1)+ ;Point mode, MOV #40000+XOFF,(T1)+ ;location in DPY coordinates. MOV #YOFF,(T1)+ MOV #D.JMP,(T1)+ ;End the torp with a jump MOV T0,(T1) ;back to the display list, and ADD #4,(T1) ;change the display list entry MOV AC0,2(T0) ;to jump to the torp. POP SHIP ;Now resume the ship's calculations. DECB TRPLFT-BASE(SHIP) ;Update the remaining torps counter. MOVB TRPLFT-BASE(SHIP),MQ0 MOV SHIP,AC1 ADD #DTLFT-BASE,AC1 MOV #2,T1 PUSHJ BINASC ;and display it. ENDLNCH: ZAP ;We're done with the button bit. ;Fall through to gravity calculation. ;GRAVITY CALCULATION FOR SHIPS AND MINES LVMINE: GRAV: PUSHJ DSQ BGT GRAV1 ;Below minimum orbit... TSTB STAT-BASE(SHIP) ;If it is alive, BNE POSCAL JSR R5,KILLIT ;it won't be for long. .WORD 1 BR POSCAL GRAV1: PUSH SHIP PUSHJ SQRT ;D ≤ .707 au scaled XD. MOV MQ0,T0 MOV #100000⊗<-XID>,AC0 CLR MQ0 PUSHJ UDIV ;1/D, scaled XID. CLC ROR MQ0 ADC MQ0 ;and rounded. PUSH MQ0 MOV POSITX-BASE(SHIP),T0 MOV POSITY-BASE(SHIP),T1 PUSHJ TWOMUL ;(X,Y)/D scaled XD+XID SCALE XD+XID,1 ;-1 ≤ (X/D) ≤ 1 SCALE1 XD+XID,1 RNDAC AC0 RNDAC AC1 POP T0 PUSH AC1 PUSH AC0 MOV G,MQ0 NEG MQ0 PUSHJ SMUL ;-G/D scaled XG+XID RNDAC AC0 MOV AC0,MQ0 MOV T0,AC0 POP T0 POP T1 PUSH AC0 PUSHJ TWOMUL ;-G*(X,Y)/D↑2 scaled XG+XID+1 RNDAC AC0 RNDAC AC1 MOV AC0,T0 MOV AC1,T1 POP MQ0 PUSHJ TWOMUL ;Accel. ← -G*(X,Y)/D↑3 SCALE XG+<2*XID>+1,XD ;Rescale the same as distance. SCALE1 XG+<2*XID>+1,XD .IFNZ DBLDSP ;Double-precision distances? POP T0 ADD MQ0,VELOCX+2-BASE(T0) ;Yes. Add low part of acceleration ADC VELOCX-BASE(T0) ;to velocity. ADD MQ1,VELOCY+2-BASE(T0) ADC VELOCY-BASE(T0) MOV T0,SHIP .ELSE RNDAC AC0 ;Otherwise, round acceleration. RNDAC AC1 POP SHIP .ENDC ADD AC0,VELOCX-BASE(SHIP) ;Add high part. ADD AC1,VELOCY-BASE(SHIP) ;Fall through to next page. ;POSITION CALCULATIONS... SHIPS, MINES, TORPS. POSCAL: .IFNZ DBLDSP ;Add velocity vector to position ADD VELOCX+2-BASE(SHIP),POSITX+2-BASE(SHIP) ADC POSITX-BASE(SHIP) ADD VELOCY+2-BASE(SHIP),POSITY+2-BASE(SHIP) ADC POSITY-BASE(SHIP) .ENDC MOV VELOCX-BASE(SHIP),AC0 ;Add X velocity to position, ADD POSITX-BASE(SHIP),AC0 ROL AC0 ;Mod 1 au by setting sign of result ASR AC0 ;equal to next bit. MOV AC0,POSITX-BASE(SHIP) NEG AC0 ASR AC0 ASR AC0 ADD POSITX-BASE(SHIP),AC0 ADD #XOFF⊗5,AC0 ;Add screen center offset .REPT 5 ;Shift to screen coordinates ASR AC0 .ENDR ADC AC0 ;and round off. BIC #176000,AC0 ;Make sure it's on the screen. BIC #1777,XPOS-BASE(SHIP) BIS AC0,XPOS-BASE(SHIP) MOV VELOCY-BASE(SHIP),AC0 ;Same thing exactly for the Y position. ADD POSITY-BASE(SHIP),AC0 ROL AC0 ASR AC0 MOV AC0,POSITY-BASE(SHIP) NEG AC0 ASR AC0 ASR AC0 ADD POSITY-BASE(SHIP),AC0 ADD #YOFF⊗5,AC0 .REPT 5 ASR AC0 .ENDR ADC AC0 BIC #176000,AC0 MOV AC0,YPOS-BASE(SHIP) POPJ ;SHIP ROTATION ROUTINE ;Enter with heading angle in AC0, pointer to ship in SHIP. ;Clobbers everything but SHIP. SHIPROT: ADD #400,AC0 ;Round angle to 7 bits. MOV AC0,ANG SWAB ANG BIC #177701,ANG ;Get first-quadrant part ASL ANG ;Set for two-word table entries. ROL AC0 ROL AC0 ROL AC0 ROL AC0 MOV AC0,QUAD ;and quadrant index. BIC #177771,QUAD ;Rotate the thrust and nose vectors BIT #2,QUAD BNE TROT1 ;1st or third quadrant - get x and y components in table order .IFNZ DBLDSP MOV THRTB1(ANG),THRSTX+2-BASE(SHIP) MOV THRTB1+2(ANG),THRSTY+2-BASE(SHIP) .ENDC MOV THRTAB(ANG),THRSTX-BASE(SHIP) MOV THRTAB+2(ANG),THRSTY-BASE(SHIP) MOV NOSTAB(ANG),NOSEX-BASE(SHIP) MOV NOSTAB+2(ANG),NOSEY-BASE(SHIP) MOV TVTAB(ANG),TVELX-BASE(SHIP) MOV TVTAB+2(ANG),TVELY-BASE(SHIP) BIT #4,QUAD BEQ TROT3 ;3rd quadrant - negate x and y. .IFNZ DBLDSP NEG THRSTX-BASE(SHIP) NEG THRSTX+2-BASE(SHIP) SBC THRSTX-BASE(SHIP) .ELSE NEG THRSTX-BASE(SHIP) .ENDC NEG NOSEX-BASE(SHIP) NEG TVELX-BASE(SHIP) BR TROT2 ;2nd or 4th quadrant - get x,y in reverse order TROT1: .IFNZ DBLDSP MOV THRTB1(ANG),THRSTY+2-BASE(SHIP) MOV THRTB1+2(ANG),THRSTX+2-BASE(SHIP) .ENDC MOV THRTAB(ANG),THRSTY-BASE(SHIP) MOV THRTAB+2(ANG),THRSTX-BASE(SHIP) MOV NOSTAB(ANG),NOSEY-BASE(SHIP) MOV NOSTAB+2(ANG),NOSEX-BASE(SHIP) MOV TVTAB(ANG),TVELY-BASE(SHIP) MOV TVTAB+2(ANG),TVELX-BASE(SHIP) BIT #4,QUAD BNE TROT2 ;2nd quadrant - negate x. .IFNZ DBLDSP NEG THRSTX-BASE(SHIP) NEG THRSTX+2-BASE(SHIP) SBC THRSTX-BASE(SHIP) .ELSE NEG THRSTX-BASE(SHIP) .ENDC NEG NOSEX-BASE(SHIP) NEG TVELX-BASE(SHIP) BR TROT3 ;3rd or 4th quadrant - negate y. TROT2: .IFNZ DBLDSP NEG THRSTY-BASE(SHIP) NEG THRSTY+2-BASE(SHIP) SBC THRSTY-BASE(SHIP) .ELSE NEG THRSTY-BASE(SHIP) .ENDC NEG NOSEY-BASE(SHIP) NEG TVELY-BASE(SHIP) TROT3: ;Fall through to next page. ;ROTATION CONTINUED MOV ROTDIS(QUAD),QUAD ;Address of quadrant fixer for drawing. ASL ANG ASL ANG ASL ANG ADD #POINTS,ANG ;Go to the correct angle table. PUSH SHIP MOV SHIP,MODL ADD #MODEL-BASE,MODL ;Address of model ADD #DRAWING-BASE,SHIP ;and where to put the results. TVAR ;2 temp cells needed for converting TVAR ;(X,Y) pairs to deltas. SROT1: PUSH SHIP ;We need a register. MOVB (MODL)+,SHIP ;Get a point number from the model. BEQ SROT6 ;Zero means end. BPL SROT2 NEG SHIP ;Get absolute value SROT2: ADD SHIP,ANG ;Address of that point in angle table. JMP (QUAD) ;Do the quadrant transform. SRQ0: MOVB (ANG),X ;1st quadrant. MOVB 1(ANG),Y BR SROT3 SRQ1: MOVB (ANG),Y ;2nd quadrant MOVB 1(ANG),X NEG X BR SROT3 SRQ2: MOVB (ANG),X ;3rd quadrant MOVB 1(ANG),Y NEG X BR SRQ3A SRQ3: MOVB (ANG),Y ;4th quadrant MOVB 1(ANG),X SRQ3A: NEG Y SROT3: SUB SHIP,ANG ;Restore the base address POP SHIP ;Bring back the "where to" address. SUB (SP),X ;Convert the X and Y to ADD X,(SP) ;deltas, saving them for next time. SUB 2(SP),Y ADD Y,2(SP) TST Y ;Make a short relative vector word... BGE RELV1 NEG Y ;Convert Y to sign-magnitude format. BIS #100,Y RELV1: TST X BGE RELV2 NEG X ;Same for X. BIS #100,X RELV2: SWAB X ;Shift X left 7 ASR X BIS X,Y ;Merge into one word. SROT4: TST (SHIP)+ ;Don't mess with display control words. BMI SROT4 MOV -(SHIP),X ;Get the brightness bit from the drawing, BIC #137777,X BIS X,Y ;Put it into the new vector. MOV Y,(SHIP)+ BR SROT1 ;Get next point. SROT6: ZAPS 3 ;All done. Get rid of the temp cells, POP SHIP ;bring back the ship index. POPJ ROTDIS: .WORD SRQ0 ;Quadrant dispatch table. .WORD SRQ1 .WORD SRQ2 .WORD SRQ3 ;COMPUTE SQUARE OF DISTANCE FROM SUN DSQ: MOV POSITX-BASE(SHIP),MQ0 MOV MQ0,T0 PUSHJ SMUL ;X↑2 ≤ 1/4 au. PUSH AC0 PUSH MQ0 ;Save it on stack. MOV POSITY-BASE(SHIP),MQ0 MOV MQ0,T0 PUSHJ SMUL ;Y↑2 ≤ 1/4 au. ADD (SP)+,MQ0 ;Double-precision add: ADC AC0 ADD (SP)+,AC0 ;D↑2 = X↑2 + Y↑2 ≤1/2 au. CMP AC0,SQ ;Compare D↑2 with square of minimum POPJ ;orbit, return the condition code. ;PROXIMITY CHECK (LEVEL 2) COMMENT⊗ Check for pairs of things that are colliding. This routine makes a yes-no-maybe check, killing things that are definitely colliding and passing the maybes on to COLIDE. Objects are eligible for collisions up until 1/10 second after they have exploded (If you get too close to a blast, the blast gets you). ⊗ PROX: PUSHACS MOV DSHPNT,DL ;Scan from beginning of dpy list. PROX1: CMP (DL)+,#D.JMP BEQ PROX2 ;End of list? POPACS ;Yes. Return. RTI ;The call on COLIDE will be here. PROX2: MOV (DL)+,OBJ1 CMP OBJ1,DL ;Check for a null entry. BEQ PROX1 PUSH DL ;Not null. Assume a live thing MOVB TYPE-BASE(OBJ1),-(SP) ;for the time being. TSTB STAT-BASE(OBJ1) ;Is it really alive? BEQ PROX4 CMPB TYPE-BASE(OBJ1),#4 ;It isn't. Is it a torp? BEQ PROX2A CMPB STAT-BASE(OBJ1),#6/TPFN ;No. How long has it been dead? BLOS PROX3 PROX2A: ZAPS 2 ;Too long. Keep scanning. BR PROX1 PROX3: COMB 1(SP) ;Not too long. Just flag it. PROX4: CMP (DL)+,#D.JMP ;Scan some more for things that BEQ PROX5 ;may be colliding with this one. PROX4A: ZAP ;There aren't any more. Look for POP DL ;another first object to check. BR PROX1 PROX5: MOV (DL)+,OBJ2 ;Looking for a second object... CMP OBJ2,DL BEQ PROX4 ;Again skip null entries. TSTB STAT-BASE(OBJ2) ;Found one. Is it dead? BEQ PROX6 CMPB STAT-BASE(OBJ2),#6/TPFN ;Yes. Too dead? BHI PROX4 ;Too too. Scan on. TST (SP) ;Not too. Is object 1 dead? BMI PROX4 ;It is. Scan some more. ;Fall through to beginning of collision test. ;Collision test 1 - take care of non-ships. PROX6: MOV POSITX-BASE(OBJ1),X ;Compute deltas X and Y. SUB POSITX-BASE(OBJ2),X MOV POSITY-BASE(OBJ1),Y SUB POSITY-BASE(OBJ2),Y PUSHJ DIST ;Get the distance. MOVB TYPE-BASE(OBJ2),TYP TSTB (SP) ;Is object 1 a ship? BEQ SPROX ;Yes. More crunching involved. ADD (SP),TYP ;No. Combined collision code is BIC #177700,TYP ;4 for mine-mine, 6 for mine-torp, CMP X,PRXTB1(TYP) ;8 for torp-torp. BGT PROX4 ;Compare distance with appropriate radius. PRXKIL: TST (SP) ;Less than the radius... BMI PROX7 ;If object 1 is alive, JSR R5,KILLIT ;do it in. .WORD 1 PROX7: TSTB STAT-BASE(OBJ2) ;Likewise object 2. BNE PROX8 MOV OBJ1,Y MOV OBJ2,OBJ1 JSR R5,KILLIT .WORD 1 MOV Y,OBJ1 ;An added fillip for torp-to-torp collisions... TST (SP) ;If both objects were alive... BMI PROX4 COMB 1(SP) CMP TYP,#10 ;and both were torps, then... BNE PROX4 MOV #VELOCX-BASE,X MOV X,Y ADD OBJ1,X ADD OBJ2,Y ADD (X),(Y) ;set their velocities equal to ASR (Y) ;the mean of their individual MOV (Y)+,(X)+ ;velocities. .IFNZ DBLDSP CMP (Y)+,(X)+ ;Don't bother with the low-order parts. .ENDC ADD (X),(Y) ASR (Y) MOV (Y)+,(X)+ BR PROX4A ;Scan for more. PROX8: MOVB #-1,1(SP) BR PROX4 PRXTB1 ←.-4 ;Collision threshold values. .WORD 2*MINER .WORD MINER+TORPR .WORD 2*TORPR ;Collision test 2 - ship involved. SPROX: CMP X,PRXTB3(TYP) ;Within maximum kill radius? BGT PROX4 ;No. Ignore. CMP X,PRXTB2(TYP) ;Within minimum radius for a BGT MAYBE ;definite kill? SHPKIL: CMP TYP,#4 ;Yes. Ship-torp collision? BNE PRXKIL ;No. Just kill them. TST (SP) ;Ship already dead? BLT PRXKIL ;If so, don't score. PUSHACS ;Yes. MOV TRPOWN-BASE(OBJ2),AC1 ;Get the torp's owner. CMP AC1,OBJ1 ;Same as the ship? BNE STCOL1 DEC SCORE-BASE(AC1) ;Yup. You goofed. You lose one BR STCOL2 ;gold star. STCOL1: INC SCORE-BASE(AC1) ;Nope. Score a big point for a kill. STCOL2: MOV SCORE-BASE(AC1),MQ0 ADD #DSCORE-BASE,AC1 MOV #6,T1 PUSHJ BINASC ;Display the updated score, POPACS BR PRXKIL ;and now kill the objects. MAYBE: PUSHJ OVALT ;Fine collision checker... CMP X,PRXTB4(TYP) ;Is object 2 within the ship's BLE SHPKIL ;collision ellipse? TST TYP ;No. If object 2 isn't a ship, BEQ MAYBE1 JMP PROX4 ;there's no collision. MAYBE1: MOV OBJ2,X ;They're both ships... MOV OBJ1,OBJ2 ;interchange their pointers MOV X,OBJ1 PUSHJ OVALT ;and check again. MOV OBJ2,Y ;Put the pointers back. MOV OBJ1,OBJ2 MOV Y,OBJ1 CMP X,PRXTB4 BLE PRXKIL ;Ship 1 too close to ship 2! JMP PROX4 ;No collision. PRXTB2: .WORD 2*SHIPW ;Table of max radii for a definite .WORD SHIPW+MINER ;ship collision. .WORD SHIPW PRXTB3: .WORD 2*SHIPL ;Table of min radii for a definite no. .WORD SHIPL+MINER .WORD SHIPL PRXTB4: .WORD OVALR+SHIPW ;Table of oval "radii" for the hairy .WORD OVALR+MINER ;collision test. .WORD OVALR ;Ship's detailed collision outline COMMENT⊗ Enter with a pointer to a ship in OBJ1, to another thing in OBJ2. Returns the sum of distances from OBJ2's center to the two foci of the ship's elliptical outline, in X. Clobbers Y. ⊗ OVALT: TVAR MOV POSITX-BASE(OBJ1),X ;Get delta X from nose to OBJ2, ADD NOSEX-BASE(OBJ1),X SUB POSITX-BASE(OBJ2),X PUSH X ;Save it. MOV POSITY-BASE(OBJ1),Y ;Delta Y from nose. ADD NOSEY-BASE(OBJ1),Y SUB POSITY-BASE(OBJ2),Y PUSH Y PUSHJ DIST ;Nose distance, MOV X,4(SP) ;saved for later. MOV NOSEY-BASE(OBJ1),Y ;Convert nose delta Y to NEG Y ;tail by subtracting the ASL Y ;length of the ship. ADD (SP)+,Y MOV NOSEX-BASE(OBJ1),X ;Same for delta X. NEG X ASL X ADD (SP)+,X PUSHJ DIST ;Tail distance. ADD (SP)+,X ;Sum of distances. POPJ ;Distance calculation COMMENT⊗ This procedure calculates the distance between two points as D = max(delta X, delta Y) + 1/2 min(delta X, delta Y). This metric gives "circles" of equal distance the shape of octagons, a fair approximation to a circle. Enter with delta X and delta Y (may be negative) in X and Y. Returns distance in X. Clobbers Y, transparent to all other registers. Reduces the deltas modulo 1 au to take care of screen wraparaund. ⊗ DIST: ASL X ;Mod 1 au by making the sign the same as ASR X ;the most significant bit. BPL DIST1 ;Abs(delta X) NEG X DIST1: ASL Y ASR Y BPL DIST2 ;Abs(delta Y) NEG Y DIST2: CMP X,Y BLE DIST3 ;Which is smaller? ASR Y ;Delta Y. Cut it in half. BR DIST4 DIST3: ASR X ;Delta X. DIST4: ADD Y,X ;And that's the distance. POPJ ;MARK AN OBJECT FOR EXPLOSION COMMENT⊗ Enter with SHIP pointing at the thing that should be blown up. This routine just marks the object as dead (status=1) and enables the DP to interrupt when it stops. When the DP interrupts, EXPLODE runs at level 2 to create the drawing of the explosion. Thus we avoid timing interference with the DP. The global variable KILCTR is incremented for each object that needs to be exploded and decremented each time one has been redrawn. ⊗ KILLIT: CMPB TYPE-BASE(SHIP),#4 ;Torps get special handling. BEQ KILTRP PUSH .PSW ;Avoid possible race conditions MOV (PC)+,@(PC)+ ;by setting the priority to 7. PSWD 7 .WORD .PSW MOVB (R5)+,STAT-BASE(SHIP) ;Mark it as dead. INC R5 INC KILCTR MOV (PC)+,@(PC)+ ;Enable the DP interrupt. DSETS .STPEN+.STOP .WORD DPRO POP .PSW ;Restore the old priority. RTS R5 KILTRP: MOV #D.PNT+.INT7,(SHIP) ;Torp dying - make it bright. MOVB (R5)+,STAT-BASE(SHIP) ;Flag it as dead. INC R5 RTS R5 ;MAKE AN EXPLOSION (LEVEL 2) EXPLODE: PUSHACS EXPL1: MOV DSHPNT,T0 ;Scan down the display list... EXPL2: CMP T0,DTRPNT ;If we're into the torps, we're done... BGE EXPDON ;(torp explosions happen in PROX). CMP #D.JMP,(T0)+ ;End of list? BNE EXPDON MOV (T0)+,SHIP ;No. CMP SHIP,T0 ;Object already dead? BEQ EXPL2 ;Yes. Go on by. CMPB #1,STAT-BASE(SHIP) ;Marked for explosion? BEQ EXPL3 CMPB #-100,STAT-BASE(SHIP) ;No. Post-explosion disappearance? BNE EXPL2 MOV SHIP,T1 ;Yes. Wipe out the drawing ;without removing it from the BR XSHIP2 ;display list. EXPL3: MOVB TYPE-BASE(SHIP),T1 JMP @EXPDIS(T1) ;Go to the right type of explosion. EXPDIS: .WORD XSHIP .WORD XMINE EXPDON: MOV (PC)+,@(PC)+ ;End of the list. PSWD 7 ;Lock out interrupts to avoid races. .WORD .PSW TST KILCTR ;Has every marked object been exploded? BGT EXPMOR EXPD1: MOV (PC)+,@(PC)+ ;It has. Disable the DP halt DSETS .STPDIS+.LPDIM+.NOITAL+.SYNCH .WORD DPRO MOV #DPRO,D.PC ;and start the display running again. POPACS RTI EXPMOR: MOV (PC)+,@(PC)+ ;Something else got marked while we were PSWD EXPLVL ;scanning the list, so set the priority .WORD .PSW ;back and scan the list again. BR EXPL1 ;SHIP EXPLODING XSHIP: DEC NACTS ;One less ship alive, INC NEXPS ;one more dying. MOV #BURST,AC0 ;Burst prototype address MOV #BURSTL,AC1 ;and length. BLOW: MOV SHIP,T1 ADD #DRAWING-2-BASE,T1 ;Base address of drawing. MOV #D.SVEC+.INT7,(T1)+ ;Bright vector mode. XSHIP0: .BEEP ;Explosions make a noise, don't they? XSHIP1: MOV (AC0)+,(T1)+ ;Move the burst into the ship's DEC AC1 ;drawing area. BGT XSHIP1 XSHIP2: MOV #D.JMP,(T1)+ ;End with a DJMP MOV DPYITM-BASE(SHIP),(T1) ;back to the display list. ADD #4,(T1) XDONE: MOV (PC)+,@(PC)+ ;Finished this explosion... PSWD 7 ;lock out interrupts. .WORD .PSW INCB STAT-BASE(SHIP) ;Set status to exploded. DEC KILCTR ;Synchronize with the explosion count. BLE EXPD1 ;All done. Return. MOV (PC)+,@(PC)+ ;There's more to come... PSWD EXPLVL ;get back to the right priority .WORD .PSW BR EXPL2 ;and resume scanning. ;Mine exploding XMINE: MOV #MBURST,AC0 MOV #MBRSTL,AC1 BR BLOW ;SUBROUTINES: two-at-a-time multiplier COMMENT⊗ This routine multiplies the numbers in T0 and T1 by the number in MQ0. The result of the first multiply is in AC0 and MQ0; the second in AC1 and MQ1. All numbers are treated as fractions with the radix point just right of the sign. The reason for this double-duty routine is to cut down the overhead of the commonly used operation (x,y)←(r cos t, r sin t). Combining the two multiplications saves a little less than 1/2 of the time the second multiplication would take by itself. The comments describing this routine may be deduced from those for SMUL, on the next page. ⊗ TWOMUL: PUSH MQ0 PUSH #20 ;Loop count goes on the stack because we're CLR AC0 ;out of registers. CLR AC1 CLR MQ1 ROR MQ0 TM1: BCS TM2 ROR AC1 ROR MQ1 BR TM3 TM2: ADD T1,AC1 ROR AC1 ROR MQ1 ADD T0,AC0 TM3: ROR AC0 ROR MQ0 DEC (SP) BNE TM1 TST (SP)+ ;Get rid of the used counter. TST (SP) BPL TM4 PUSH T0 BIC #100000,(SP) SUB (SP)+,AC0 PUSH T1 BIC #100000,(SP) SUB (SP)+,AC1 TM4: BIC #100000,(SP) TST T0 BPL TM5 SUB (SP),AC0 TM5: TST T1 BPL TM6 SUB (SP),AC1 TM6: TST (SP)+ ;Finish cleaning up the stack. ASL MQ0 ROL AC0 ASL MQ1 ROL AC1 POPJ ;Single multiplier COMMENT⊗ Revised 11 Apr 75 by Bo. The previous version used the more elaborate algorithm of adding or subtracting on changes between strings of 0's and 1's. The drawback to that scheme is that you need an arithmetic right shift that looks at overflow and complements the sign if ov is on. That takes some messing around and costs cycles. This version does a 16 by 16 unsigned multiply with a sign correction at the end, saving considerable time in the loop. Call with the multiplier in MQ0 and the multiplicand in T0. Returns the double-length signed product (fractional scaling) in (AC0,MQ0). Transparent to the other registers. ⊗ SMUL: PUSH T1 ;Make a register for the loop count. PUSH MQ0 ;Save multiplier for the sign correction. MOV #20,T1 ;Initial loop count. CLR AC0 ;Product starts out zero. ROR MQ0 ;Look at first bit of multiplier. SM1: BCC SM2 ;If multiplier bit is a one, ADD T0,AC0 ;do the addition. SM2: ROR AC0 ;Shift the whole thing right, putting the ROR MQ0 ;next multiplier bit into C. DEC T1 ;Count iterations. BNE SM1 TST T0 ;Now for the correction. If the multplicand BPL SM3 ;is negative, MOV (SP),T1 BIC #100000,T1 ;subtract all but the sign bit of the multiplier. SUB T1,AC0 SM3: TST (SP)+ ;If the multiplier is negative, BPL SM4 MOV T0,T1 BIC #100000,T1 ;subtract all but the sign of the multiplicand. SUB T1,AC0 SM4: ASL MQ0 ;Finally, put the product into fractional scaling. ROL AC0 POP T1 ;Restore the clobbered temp register. POPJ ;El Cheapo binary-to-ASCII converter. ;Enter with number to be converted in MQ0, number of digits in T1, ;address plus one of low-order byte of ASCII string in AC1. ;This routine doesn't check whether the number will fit. It does ;provide leading spaces and minus sign. It clobbers everything ;but SHIP. BINASC: MOV #12,T0 PUSH MQ0 BPL BINA1 NEG MQ0 BINA1: CLR AC0 PUSHJ UDIV ASR AC0 ADD #'0,AC0 MOVB AC0,-(AC1) DEC T1 TST MQ0 BNE BINA1 TST (SP)+ BPL BINA2 MOVB #'-,-(AC1) DEC T1 BINA2: DEC T1 BMI BINA3 MOVB #' ,-(AC1) BR BINA2 BINA3: POPJ ;Divide routine (BLECH!) ;This is a very dirty UNSIGNED divide. Divisor in T0, ;dividend in (AC0,MQ0). Quotient in MQ0, remainder in AC0. ;Caution: the dividend had better be positive and less than ;the divisor, or garbage WILL result. UDIV: PUSH #21 NEG T0 UDIV1: ADD T0,AC0 BCS UDIV2 SUB T0,AC0 CLC UDIV2: ROL MQ0 ROL AC0 DEC (SP) BGT UDIV1 NEG T0 ZAP POPJ ;This gives a 16-bit unsigned result, which if shifted right 1 ;(rounded?) is scaled as a fraction. ;Square-root routine COMMENT⊗ This routine uses the desk-calculator algorithm, based on the theorem that the sum of the first n odd integers is n↑2. Enter with a double-precision fraction in (AC0,MQ0). Returns the 15-bit fractional result in MQ0. Clobbers AC0, T0, and T1. NOTE: If you really want to, you can even use an unsigned number ≥ 1 as the input. This routine will return the correct unsigned result. Since the error in the result is always negative, you might just want to use this feature to give that extra bit for rounding. The time required for this type of square root is about the same as for a multiply or a divide, so it is much more efficient than Newton's method on a machine without hardware multiply/divide. I first wrote this routine in 1963 as a programming exercise for a 1401. I've written it again for each new machine that I've run into, as an exercise. Now, 10 years later, I finally get a chance to use it! ⊗ SQRT: CLC ;Move the radix point to an even position, ROR AC0 ;preserving the 30 bits of the fraction. ROR MQ0 MOV #100000,T0 ;This holds successive odd numbers for subtracting. MOV #40000,T1 ;This keeps track of bit positions in the subtractor. SQRT1: ADD T1,T0 ;Make that odd number in the next position over. ADD T0,AC0 ;Subtract it. BCS SQRT2 ;Did it go? SUB T0,AC0 ;No. Add it back ASL MQ0 ;and shift a zero into the result. ROL AC0 ;That last one in the subtractor becomes a zero BR SQRT3 ;because it will no longer be the last significant bit. SQRT2: ROL MQ0 ;Yes. Shift a one into the result. ROL AC0 SUB T1,T0 ;The subtractor becomes the next higher even number. SQRT3: ASR T1 ;Now extend the subtractor right. BNE SQRT1 ;Did we run out of bits? BCC SQRT4 ;Yes. But there's a special hack to get the sixteenth ;bit of the result anyway... TST MQ0 ;If there would be a carry into bit 0 of AC0, BPL SQRT1 INC T0 ;we take care of it here. BR SQRT1 ;Warning...that hack makes the remainder unusable ;without some more hacking. SQRT4: POPJ ;Pseudo-random number generator COMMENT⊗ Uses the linear congruential method as recommended by Knuth, Vol 2, Sec 3.6. ⊗ RANDOM: MOV RANDNO,MQ0 MOV #26435,T0 PUSHJ SMUL ASR AC0 ROR MQ0 ADD #33031,MQ0 MOV MQ0,RANDNO POPJ ;COUNT-DOWN HACK FOR STARTUP. CTDWN: DEC TICCNT ;Count ticks... BGT IGNINT DECB DWNCNT ;One second gone by. Show it on dpy. CMPB DWNCNT,#'0 ;Count reached zero? BEQ STOPCT MOV #74,TICCNT ;Nope. Wait another second. IGNINT: RTI STOPCT: CLR LFK.K ;Countdown done. Disable the clock, MOV #D.SETS+.STOP+.STPEN,DCTDWN ;enable the display halt. RTI DOFINT: CLR DCTDWN ;Display halted. Set flag for main. RTI ;Display routine for countdown DCTDWN: D.SETS ;Set brightness and synchronize. DLOC XOFF-.CXOFF,YOFF-.CYOFF ;Display one char at center. D.CHR+.INT7 DWNCNT: 0 ;This will be the digit displayed. D.JMP ;Go do it again. DCTDWN ;THE DISPLAY LIST DPRO: DSETS .STPDIS+.LPDIM+.NOITAL+.SYNCH DLOC 200,0 DLVEC .INT1+.SOLID ;Draw a box at the edges of LVEC 0,1377 ;the universe. LVEC 1377,0 LVEC 0,-1377 LVEC -1377,0 DLOC XOFF,YOFF ;Go to center of universe DRPNT .INT3+.BLINK+.LPDIS RPNT 10,0 ;The sun is a short-term variable star. RPNT -1,3 ;Here is its varying component RPNT -1,3 RPNT -3,1 RPNT -3,1 RPNT -3,-1 RPNT -3,-1 RPNT -1,-3 RPNT -1,-3 RPNT 1,-3 RPNT 1,-3 RPNT 3,-1 RPNT 3,-1 RPNT 3,1 RPNT 3,1 RPNT 1,3 DRPNT .INT4+.NOBLINK ;and its steady component. RPNT 1,3 RPNT -1,3 RPNT -1,3 RPNT -3,1 RPNT -3,1 RPNT -3,-1 RPNT -3,-1 RPNT -1,-3 RPNT -1,-3 RPNT 1,-3 RPNT 1,-3 RPNT 3,-1 RPNT 3,-1 RPNT 3,1 RPNT 3,1 RPNT 1,3 DSTARS: .←.+> ;Room for all the stars DLIST: ;The list itself... .←.+<10*MXSHIP> ;Room for scores and ships. .←.+<4*MXMINE> ;Space for the mines .←.+<4*MXTORP> ;Leave room for all of the torps. DNOP ;Flag end of display list DJMP DPRO ;SHIP DESCRIPTION TABLES DBL←2*DBLDSP+2 ;Single or double precision. ;Description of ship #1. These labels are only used to create ;symbolic displacements relative to SHPTAB or to BASE. SHPTAB: BUTBIT: .WORD 10 ;Button-test bit INANG: .WORD 0 ;Initial orientation angle INPOS: ;Initial position .IFNZ DBLDSP .WORD 20000,0 .WORD 20000,0 .ELSE .WORD 20000,20000 .ENDC DPYSCR: DLOC .GXUL-<10*.CXSPC>,.GYUL ;The display list jumps here D.CHR+.INT4 ;to display the player's score. .ASCII "012345" DSCORE: EVEN DLOC .GXUL-<10*.CXSPC>,.GYUL-<2*.CYSPC> D.CHR .ASCII "01" DTLFT: .ASCII " TORPS" EVEN D.JMP DSCEND: .WORD 0 ;The following bytes are indices into the POINTS table for generating ;the ship drawing. MODEL: .BYTE -40,-2,-4,-6,10,12,14,16,20,22,6 .BYTE 26,30,10,-20,32,34,22,0 EVEN SCORE: .←.+2 TRPLFT: .←.+1 ;# of torps remaining NTACT: .←.+1 ;# of torps active RELTIM: .←.+2 ;Torp tube reload timer TVELX: .←.+2 ;Torp launch velocity TVELY: .←.+2 NOSEX: .←.+2 ;Relative location of nose NOSEY: .←.+2 HEADING:.←.+2 THRSTX: .←.+DBL ;Thrust components THRSTY: .←.+DBL VELOCX: .←.+DBL ;Velocity components VELOCY: .←.+DBL POSITX: .←.+DBL ;Position coordinates POSITY: .←.+DBL TYPE: .←.+1 ;0 ⊃ ship, 2 ⊃ mine, 4 ⊃ torp STAT: .←.+1 ;Status = 0 means alive and well ; = 1 means just killed ;between 2 and 31, exploding ;negative, post-explosion fade. DPYITM: .←.+2 ;Pointer back to display list entry BASE: .←.+2 ;Put the DP in absolute point mode. XPOS: .←.+2 ;Display coordinates of center of ship YPOS: .←.+2 .←.+2 ;Short vector mode. DRAWING: .←.+2 ;First piece of the drawing is TRPTUB ←DRAWING ;the torpedo tube. .←.+2 ;then move to the tail. FLAME: .←.+2 ;Short vector mode, blink on. .←.+2 ;the flame itself .←.+2 ;Turn blink off again .←.+46 ;The rest of the drawing, ending with a DJMP SHPEND: ;back to the display list. ;SHIP #2 AND TORPEDO DESCRIPTIONS. ;This block uses the same relative labels as ship #1. .WORD 100000 ;Button test .WORD 100000 ;initial heading .IFNZ DBLDSP ;and position. .WORD 160000,0,160000,0 .ELSE .WORD 160000,160000 .ENDC DLOC 0,2*.CYSPC ;Score display. D.CHR+.INT4 .ASCII "012345" EVEN DLOC 0,0 D.CHR .ASCII "99 TORPS" EVEN D.JMP 0 ;Model. This had better be the same length for all ships, padded with ;zeros if necessary. .BYTE -40,-2,-4,-6,10,12,14,16,20,22,6 .BYTE 24,26,10,-20,34,36,22,0 EVEN .IFNZ <.-SHPEND>- .ERR YOU FUCKED UP WITH YOUR SHIP DESCRIPTIONS! .ENDC .←.+SHPEND-SCORE ;Torpedoes are given blocks in free storage. Referenced relative to BASE, ;they have the same information for items VELOCX through YPOS except that ;XPOS has the intensify bit turned on to display the torp as a point. ;Following YPOS is a DJMP back to the display list. TRPAGE ←VELOCX-2 ;Age counter for the torp's self-destruct mechanism TRPOWN ←TRPAGE-2 ;Pointer to who launched it (for killer scoring). ;EXPLOSION DRAWINGS BURST: SVEC 0,0,.INVIS ;Ship explosion, initial state. SVEC 1,0,.VIS SVEC 0,1,.INVIS SVEC 1,1,.VIS SVEC -2,-2,.INVIS SVEC 0,1,.VIS SVEC -1,0,.INVIS SVEC -1,1,.VIS SVEC 2,-2,.INVIS SVEC -1,0,.VIS SVEC 0,-1,.INVIS SVEC -1,-1,.VIS SVEC 2,2,.INVIS SVEC 0,-1,.VIS SVEC 1,0,.INVIS SVEC 1,-1,.VIS BURSTL←<.-BURST>/2 IBURST: SVEC 1,0,.INVIS ;Increments for the invisible SVEC 1,2,.INVIS ;vectors in the ship burst. SVEC 2,1,.INVIS SVEC 2,1,.INVIS SVEC 1,2,.INVIS SVEC 1,2,.INVIS SVEC 2,1,.INVIS SVEC 2,1,.INVIS IBRSTL←<.-IBURST>/2 MBURST: SVEC 0,0,.INVIS ;Initial form of an exploding mine. SVEC 1,1,.VIS SVEC -1,-1,.INVIS SVEC -1,1,.VIS SVEC 1,-1,.INVIS SVEC -1,-1,.VIS SVEC 1,1,.INVIS SVEC 1,-1,.VIS MBRSTL←<.-MBURST>/2 IMBRST: SVEC 1,1,.INVIS ;Increment for the mine explosions. SVEC 2,0,.INVIS SVEC 0,2,.INVIS SVEC 2,0,.INVIS IMBRSL←<.-IMBRST>/2 ;DATA AREA - MISCELLANEOUS STUFF ;Miscellaneous variables DSHPNT: .WORD 0 ;Point to ships in dpy list. DMIPNT: .WORD 0 ;Point to mines. DTRPNT: .WORD 0 ;Point to torps in display list TRPNT: .WORD 0 ;and free storage. FREPNT: .WORD 0 ;Pointer to free space. NSHIPS: .WORD 0 ;Number of ships NMINES: .WORD 0 ;Number of mines TICCNT: .WORD 0 ;Count clock ticks SWITCH: .WORD 0 ;Switch settings KILCTR: .WORD 0 ;Count things waiting to explode. NACTS: .WORD 0 ;Number of live ships NEXPS: .WORD 0 ;Number of exploding ships RANDNO: .WORD 123456 ;Random number ;Tables of vectors rotated in all directions at initialize time ;(32 two-word entries in each table) THRTAB: .←.+200 ;Thrust vectors .IFNZ DBLDSP THRTB1: .←.+200 ;Low-order parts of thrust vectors .ENDC NOSTAB: .←.+200 ;Relative location of ship's nose. TVTAB: .←.+200 ;Torpedo launch velocity. ;POINT TABLE FOR MAKING THE SHIP DRAWINGS COMMENT⊗ I keep a set of 16 standard points in the array POINTS. These represent short displacements from the center of a ship to various spots in a ship at heading angle 0. The initializing routine generates 32 rotations of these points covering the first quadrant. These displacements are in integral leagues. A ship model consists of an ordered set of pointers to these points, interpreted as a "follow-the-dots" sequence of drawing instructions. These pointers are further coded with a negative sign meaning to draw an invisible vector. Generating a ship drawing in some specified orientation consists of (1) setting up the base address of one of the point sets, (2) setting up the call to the proper procedure to get the points into the proper quadrant, and (3) interpreting the ship's model, using the specified point set, to generate the display short vector words. The visible/invisible flags in the ship models are used only at initialization time. The rest of the time, the intensify bits that are already in the display area stay there. Because of this convention, setting the visual indicators for thrust and loaded torp tube is made easy - just change the state of a couple of bits in the display area. One further convention for ship drawings: the first point is always the torp tube, and the third is always the tail flame. ⊗ POINTS ←.-2 .BYTE -25,0 ;Tailpipe .BYTE -35,0 ;Flame .BYTE -25,3 ;Outline of body .BYTE -10,5 .BYTE 12,5 .BYTE 33,0 .BYTE 12,-5 .BYTE -10,-5 .BYTE -25,-3 .BYTE -30,3 ;Tailfin points .BYTE -30,12 .BYTE -12,12 .BYTE -12,-12 .BYTE -30,-12 .BYTE -30,-3 .BYTE 3,0 ;Torp tube .←.+<37*40> ;Leave room for the rotated points. FREEST: ;The torpedo data blocks, at 32 words each, come here. ;Yes, torps only need 17 words each, but 32 is a much nicer number. ;ONE-TIME ONLY INITIALIZATION - ROTATE VECTORS ;These routines set up the tables of rotations, then ;get clobbered by free storage. CRANKUP: RESET MOV #PDL,SP ;Set up stack and MOV #CRANKUP,PWR.V ;power-fail trap. MOV (PC)+,@(PC)+ PSWD 7 PWR.V+2 CLR .PSW ;Priority 0. MOV #TRGTAB,R0 ;Indexes forward through cosines, MOV #TRGEND+2,R1 ;backward through sines. .IFNZ DBLDSP PUSH #THRTB1 ;Address of low part of thrust. .ENDC PUSH #THRTAB ;Ad of high part of thrust PUSH #NOSTAB ;Ad of noses. PUSH #TVTAB ;Torp launch vectors. PUSH #POINTS+42 ;First loc for rotate points. NXTANG: MOV (R0)+,COS ;Trig function lookup, MOV -(R1),SIN PUSH R0 ;save addresses. PUSH R1 MOV A,MQ0 PUSHJ TWOMUL ;Current angle of thrust, PUSH T1 ;Scale clobbers this. SCALE XA,XD ;scaled the same as distance. SCALE1 XA,XD POP T1 .IFNZ DBLDSP PUSH MQ1 ;Save low-order parts in THRTB1, MOV 16(SP),MQ1 MOV MQ0,(MQ1)+ POP MQ0 MOV MQ0,(MQ1)+ MOV MQ1,14(SP) .ELSE RNDAC AC0 ;or round if not double precision. RNDAC AC1 .ENDC MOV 12(SP),MQ1 MOV AC0,(MQ1)+ ;High parts in THRTAB. MOV AC1,(MQ1)+ MOV MQ1,12(SP) MOV #SHIPL,MQ0 ;Current angle for PUSHJ TWOMUL ;nose offset, RNDAC AC0 ;rounded. RNDAC AC1 MOV 10(SP),MQ1 MOV AC0,(MQ1)+ ;Result goes to NOSTAB. MOV AC1,(MQ1)+ MOV MQ1,10(SP) MOV T,MQ0 ;Torpedo launch vectors PUSHJ TWOMUL ;go to TVTAB. PUSH T1 SCALE XT,XD SCALE1 XT,XD POP T1 RNDAC AC0 RNDAC AC1 MOV 6(SP),MQ1 MOV AC0,(MQ1)+ MOV AC1,(MQ1)+ MOV MQ1,6(SP) TST SIN ;For angle = 0, don't rotate points. BEQ FRSTIM ;(They're already there). ;Fall through to next page. ;Rotate the points in the ship drawings. PUSH #20 ;16 points in table. MOV #POINTS+2,MQ0 ;They come from here (at angle = 0) MOV 6(SP),MQ1 ;and go there. NXTPNT: MOVB (MQ0)+,X ;Get the point. MOVB (MQ0)+,Y PUSH MQ0 ;We'll be clobbering these registers. PUSH MQ1 PUSHJ VECROT ;Make the rotation, RNDAC AC0 ;round off. RNDAC AC1 POP MQ1 ;Get the addresses back. POP MQ0 MOVB X,(MQ1)+ ;Store the rotated point. MOVB Y,(MQ1)+ DEC (SP) ;Loop count. BGT NXTPNT ZAP ;Clean the stack, MOV MQ1,4(SP) ;save address for next angle. FRSTIM: POP R1 ;Done with an angle, POP R0 ;get back trig indices. CMP R0,#TRGEND ;Are we at 31/32 of 90 deg? BEQ ENDNIT JMP NXTANG ENDNIT: MOV #RESTRT,STRTAD ;Change the start address. JMP RESTRT ;VECTOR ROTATE ROUTINE (FOR SETTING UP TABLES) COMMENT⊗ Enter with X component in X, Y in Y, cos and sin of angle in COS and SIN. Does the vector*matrix multiply, assuming fractional arithmetic. Returns the double-precision X in (AC0,MQ0) and Y in (AC1,MQ1). Leaves COS and SIN unchanged. ⊗ VECROT: PUSH AC0 ;Save X on stack. MOV Y,MQ0 PUSHJ TWOMUL ;do Y*COS and Y*SIN PUSH AC1 ;Save results in reverse order. PUSH MQ1 PUSH AC0 PUSH MQ0 MOV 10(SP),MQ0 ;Get X back, PUSHJ TWOMUL ;do X*COS and X*SIN. ADD (SP)+,MQ1 ;Y'←X*SIN+Y*COS ADC AC1 ADD (SP)+,AC1 NOP SUB (SP)+,MQ0 ;X'←X*COS-Y*SIN SBC AC0 SUB (SP)+,AC0 ZAP ;Clean up the stack. POPJ ;TABLE OF COSINES FOR 32 EQUALLY-SPACED INCREMENTS FROM 0 TO 90 DEG. ;These values are 15-bit fractions, generated by a SAIL program. TRGTAB: .WORD 77777, 77731, 77543, 77236, 76613, 76052, 75175, 74205 .WORD 73102, 71666, 70343, 66713, 65156, 63320, 61362, 57330 .WORD 55203, 52766, 50464, 46100, 43435, 40717, 36127, 33273 .WORD 30374, 25440, 22451, 17432, 14371, 11311, 06214, 03110 TRGEND: .WORD 00000 .END START