_runtimePath_ "../../runtime" _runtimeStart_ &h7FFF _arraysStart_ &h7FFF _codeRomType_ ROMv3 'use this after _runtimeStart_ to specify maximum number of sprites if you have more than 48 _maxNumSprites_ 56 'size of your most complex expression, (temporary variables required)*2, defaults to 8 _tempVarSize_ 4 'free string work area, (better not use any of the string runtime!) free STRINGWORKAREA 'defines the amount of contiguous RAM needed for sprite stripes, (in this case 15*6 + 1), also min address and search direction _spriteStripeChunks_ 15, &h3BA0, descending 'frequency and volume LUTS for player bullet shoot sound, (modelled in Desmos) const PBL_SIZ = 32 const PBF_LUT = &h7FA0 const PBV_LUT = &h7FE0 def word(PBF_LUT, y, 0.0, 1.0, PBL_SIZ) = exp(-3.0*y)*6000.0 + 14000.0 def byte(PBV_LUT, y, 0.0, 1.0, PBL_SIZ) = exp(-5.0*pow(y, 3.0))*32.0 'frequency LUT for saucer sound, (modelled in Desmos) const SCF_SIZ=12 const SCF_LUT=&h7EA0 'def word(SCF_LUT, y, 0.0, 180.0, SCF_SIZ) = sin(y) * sin(y) * 2121.0 + 2121.0 def word(SCF_LUT, y, 0.0, SCF_SIZ, SCF_SIZ) = (y % (SCF_SIZ/2)) * 300.0 + 3000.0 const IEF_SIZ=3 const IEF_LUT=&h7EC0 'frequency LUT for invader explosion sound, (modelled in Desmos) def word(IEF_LUT, y, 0.0, IEF_SIZ, IEF_SIZ) = 6000.0*(1 - exp(-0.5*y)) const PEF_LUT=&h7DA0 const PEF_SIZ=32 def word(PEF_LUT, 0.0, PEF_SIZ, PEF_SIZ) = rand(PEF_SIZ) / PEF_SIZ * 300.0 module "InvaderSprites.i" const LIFE_Y = 4 const LIFE_X = 136 const HIGH_Y = 1 const HIGH_X = 97 const SCORE_Y = 1 const SCORE_X = 31 const SCORE_LEN = 6 const LEVEL_Y = 1 const LEVEL_X = 2 const LEVEL_LEN = 5 const SAUCER_LEN = 5 dim highBCD%(SCORE_LEN - 1) = 0 dim scoreBCD%(SCORE_LEN - 1) = 0 dim pointsBCD%(SCORE_LEN - 1) = 0 dim levelBCD%(LEVEL_LEN - 1) = 0 dim saucerBCD%(SAUCER_LEN - 1) = 0 const ILIST_END = 128 const INVADERS_J = 5 const INVADERS_I = 10 dim invaders(INVADERS_J - 1, INVADERS_I - 1) = {0+1*256, 0+1*256, 0+1*256, 0+1*256, 0+1*256, 0+1*256, 0+1*256, 0+1*256, 0+1*256, ILIST_END+1*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+2*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256, 0+3*256} const INVADERS_H = 8 const INVADERS_Y = 10 const INVADERS_X = 12 'first saucer takes 28 seconds, all others take 25 until game over const SAUCER_DELAY = 28 const SAUCER_YPOS = 9 const SAUCER_XSTART = 0 const SAUCER_XEND = 141 dim saucerx%(1) = SAUCER_XSTART, SAUCER_XEND dim iypos%(INVADERS_J - 1) = 4*INVADERS_Y, 3*INVADERS_Y, 2*INVADERS_Y, 1*INVADERS_Y, 0*INVADERS_Y dim ixpos%(INVADERS_I - 1) = 0*INVADERS_X, 1*INVADERS_X, 2*INVADERS_X, 3*INVADERS_X, 4*INVADERS_X, 5*INVADERS_X, 6*INVADERS_X, 7*INVADERS_X, 8*INVADERS_X, 9*INVADERS_X 'struct{invader, xy, prev, next} dim istruct((INVADERS_J * INVADERS_I) - 1, 3) dim iaddress(INVADERS_J - 1, INVADERS_I - 1) const DIVX = &h0400 def byte(DIVX, x, 0.0, 160.0, 160) = floor(x / INVADERS_X) const DIVY = &h0500 def byte(DIVY, x, 0.0, 120.0, 120) = floor(x / INVADERS_Y) const IORIGIN_Y = 56 const IORIGIN_X = 1 const INV_SML = 0 const INV_MED = 0 const INV_BIG = 0 const MAX_LIVES = 3 const NUM_BARRIERS = 3 const BARRIER_Y = 90 const BARRIER_X = 20 const BARRIER_OFFSET = 48 const PLAYER_Y = 114 const PLAYER_X = 1 const PLAYER_XEND = 147 const LEVEL_X6 = 24 const IEXPLODE_DELAY = 10 const BEXPLODE_DELAY = 8 const SEXPLODE_DELAY = 15 const NUM_ITYPES = 3 dim irsprites%(NUM_ITYPES - 1) = InvSmlRt, InvMedRt, InvBigRt dim ilsprites%(NUM_ITYPES - 1) = InvSmlLt, InvMedLt, InvBigLt dim iscores%(NUM_ITYPES - 1) = 30, 20, 10 const NUM_SSCORES = 16 dim sscores(NUM_SSCORES - 1) = 100, 50, 50, 100, 150, 100, 100, 50, 300, 100, 100, 100, 50, 150, 100, 50 const INVADER_DEAD = &h8000 const NUM_IBULLETS = 3 dim ibxy(NUM_IBULLETS - 1) = 0 def livesLevel, delayLevel, px, py, ixorigin, iyorigin, ii, jj, xflip, iflip, icurrent, ishoot, sevol, timeTicks, pflip, iscore, imarch, endgame, pbxy, pbullet, ipbcount, ibindex, iexplode, itimer, bexplode, btimer, saucerxy, satimer, sefreq, stimer, sexplode, audmarch, audshoot, aviexplode, oldbutton call initSystem reset: call resetLevel start: call startLevel init: call initVars repeat 'wait call waitScanline call handleInput call drawPlayerBullet call drawPlayer call movePlayer call checkInvaders call checkNextLevel call drawInvaderBullets call checkInvaderBullets call drawInvaders call updateSaucer call updateScore call updateAudio call updateTime if &(endgame.lo) then goto start if &(endgame.hi) then goto reset &forever proc drawPlayerBullet 'no bullets while invader is asploding if &(iexplode) then return 'no bullet so exit if pbullet.hi &&= 0 then return asm 'setup SYS function LDWI SYS_VDrawBits_134 STW giga_sysFn endasm gosub checkPlayerBullet asm 'setup SYS params LDWI 0x3F00 STW giga_sysArg0 LDW _pbxy STW giga_sysArg4 endasm if &(pbullet.lo) asm 'draw bullet LDI 0xF8 ST giga_sysArg2 SYS 134 endasm pbxy.hi = pbxy.hi - 2 if pbxy.hi &&< 14 pbullet = 0 asm 'erase bullet if top of screen LDI 0x00 ST giga_sysArg2 SYS 134 endasm endif return endif asm 'erase bullet if pbullet.lo = 0 LDI 0x00 ST giga_sysArg2 SYS 134 endasm pbullet = 0 endproc proc drawPlayer if pflip.lo &= 1 sprite FlipX, Player + 1, px, py else sprite NoFlip, Player + 0, px, py endif endproc proc handleInput local button button = get("BUTTON_STATE") XOR 255 if &(button AND &h01) then pflip = &h0101 if &(button AND &h02) then pflip = &h0100 if (oldbutton AND &h80) &&= 0 if &(button AND &h80) gosub playerBullet endif endif oldbutton = button endproc playerBullet: if &(pbullet.hi) then return inc ipbcount.lo audshoot = &h0100 pbullet = &h0101 if &(pflip.lo) pbxy = (px + 6) + ((py - 5 + 8) LSL 8) else pbxy = (px + 5) + ((py - 5 + 8) LSL 8) endif return proc movePlayer if &(pflip.hi) pflip.hi = 0 if &(pflip.lo) inc px if px &&> PLAYER_XEND px = PLAYER_XEND endif else dec px if px &&< PLAYER_X px = PLAYER_X endif endif endif endproc proc drawInvaders local x, y, invader if &(iexplode) then return 'list is empty, waiting for last invader to explode if icurrent &= 0 then return 'get current linked list entry invader = deek(icurrent) x = ixorigin + peek(icurrent + 2) y = iyorigin - peek(icurrent + 3) 'invaders landed if y &&>= PLAYER_Y - 7 call gameOver return endif 'draw invader, (erase edge for last invader left to right) if xflip &> 0 if xflip &= 3 if x &> 0 'we can call a proc inside another proc because called proc does not modify local/param slots call eraseInvaderEdge, x, y endif endif sprite FlipX, irsprites(invader.hi - 1) + ((x LSR 1) AND 1), x, y else sprite NoFlip, ilsprites(invader.hi - 1) + ((x LSR 1) AND 1), x, y endif 'erase previous row invader if (imarch) sprite NoFlip, InvBlk, x, y - INVADERS_Y endif 'horizontal march flip if x &>= PLAYER_XEND iflip = -2 elseif x &&<= 1 iflip = 2 if ipbcount.hi &&= (INVADERS_J * INVADERS_I) - 1 iflip = 3 endif endif 'next entry in linked list icurrent = deek(icurrent + 6) 'reached end of linked list if (invader.lo AND ILIST_END) &&= 0 then return if iflip &&<> xflip 'vertical march imarch = 1 iyorigin = iyorigin + INVADERS_H else 'horizontal march imarch = 0 ixorigin = ixorigin + iflip endif xflip = iflip endproc psmashBarrier: asm LDWI 0x0C00 STW giga_sysArg0 'FGBG colour LDWI 0xFEFF ADDW _pbxy STW giga_sysArg4 'offset LD giga_rand0 ANDI 0xC0 ST giga_sysArg2 SYS 134 'left damage INC giga_sysArg4 LDI 0x00 ST giga_sysArg2 SYS 134 'center damage INC giga_sysArg4 LD giga_rand2 ANDI 0xC0 ST giga_sysArg2 SYS 134 'right damage endasm return proc drawBarriers local i, x, y x = BARRIER_X : y = BARRIER_Y for i=0 to NUM_BARRIERS - 1 sprite NoFlip, Barrier, x, y x = x + BARRIER_OFFSET next i endproc proc checkInvaders local i, j, x, y, iaddr, iprev, inext, invader 'erase invader explosion and restart invader march if &(iexplode) inc itimer.lo if itimer.lo &= IEXPLODE_DELAY sprite NoFlip, InvBlk, iexplode.lo, iexplode.hi itimer.lo = 0 : iexplode = itimer.lo endif return endif if pbullet.hi &&= 0 then return y = iyorigin - (pbxy.hi - (8 + 3)) if y &&< 0 then return x = pbxy.lo - ixorigin if x &&< 0 then return i = peek(DIVX + x) if i &&>= INVADERS_I then return j = (INVADERS_J - 1) - peek(DIVY + y) if j &&>= INVADERS_J then return if j &&< 0 then return 'invader already erased iaddr = iaddress(j, i) : invader = deek(iaddr) if invader.hi &&= 0 then return 'erase invader poke iaddr + 1, 0 pbullet.lo = 0 iprev = deek(iaddr + 4) inext = deek(iaddr + 6) doke iprev + 6, inext doke inext + 4, iprev if icurrent &&= iaddr then icurrent = inext 'update score iscore = invaders(j, i).hi 'invader shoot needs to know which invaders are dead invaders(j, i) = invaders(j, i) OR INVADER_DEAD 'update linked list end if &(invader.lo AND ILIST_END) then poke iprev, peek(iprev) OR ILIST_END call erasePlayerBullet 'explode invader x = ixorigin + ((i LSL 3) + (i LSL 2)) 'x = ixorigin + i*INVADERS_X j = (INVADERS_J - 1) - j y = iyorigin - ((j LSL 3) + j + j) 'y = iyorigin - j*INVADERS_Y; if xflip &&> 0 sprite NoFlip, IExplode, x, y else sprite FlipX, IExplode + 1, x, y endif 'halt march to display explosion itimer = 0 : iexplode = x + (y LSL 8) gosub disableShootSound 'erase previous row invader if invaders just marched vertically if (imarch) sprite NoFlip, InvBlk, x, y - INVADERS_Y endif inc ipbcount.hi endproc proc playerExplode local bxy, t, f, v, i for ibindex=0 &to NUM_IBULLETS - 1 bxy = ibxy(ibindex) if &(bxy) then call eraseInvaderBullet, bxy ibxy(ibindex) = 0 next ibindex t = 0 : f = t for v=63 downto 0 f = deek(PEF_LUT + (t LSL 1)) sound on, 1, f, v, 0 sound on, 2, f, v, 0 sound on, 3, f, v, 0 sound on, 4, f, v, 0 inc t if t &&= PEF_SIZ then t = 0 sprite NoFlip, PExplode + ((v LSR 2) AND 1), px, py wait next v sound off endproc proc drawInvaderBullets local bxy if timeTicks.hi &< 3 then return for ibindex=0 to NUM_IBULLETS - 1 bxy = ibxy(ibindex) if (bxy) asm 'setup SYS function LDWI SYS_VDrawBits_134 STW giga_sysFn LDWI 0x3F00 STW giga_sysArg0 endasm asm 'draw bullet LDW _drawInvaderBullets_bxy STW giga_sysArg4 LDI 0x1F ST giga_sysArg2 SYS 134 endasm inc bxy.hi if bxy.hi &>= 121 asm 'erase bullet at bottom of screen LDI 0x00 ST giga_sysArg2 SYS 134 endasm bxy = 0 elseif peek(bxy + &h0700) &&= &h0C asm 'erase bullet LDI 0x00 ST giga_sysArg2 SYS 134 endasm if bxy.hi &&> PLAYER_Y ibxy(ibindex) = 0 call playerDied return endif gosub ismashBarrier bxy = 0 endif else call getNextInvaderShot, bxy endif ibxy(ibindex) = bxy next ibindex endproc proc checkNextLevel if ipbcount.hi &&= (INVADERS_J * INVADERS_I) icurrent = 0 : ishoot = icurrent if iexplode &&= 0 if aviexplode &&= 0 endgame.lo = 1 inc delayLevel.lo if delayLevel.hi &&> 1 delayLevel.hi = delayLevel.hi LSR 1 endif livesLevel.lo = livesLevel.lo + (INVADERS_H/2) endif endif endif endproc 'bxy is in the same slot as drawInvaderBullets, so it is passed in by reference 'drawInvaderBullets only uses slot 1 which matches our param slot, (bxy), so we are free to use locals proc getNextInvaderShot, bxy local i, j, xy 'list is empty, waiting for last invader to explode if ishoot &&= 0 then return 'next shoot candidate ishoot = deek(ishoot + 6) if &(rnd(0) AND delayLevel.hi) bxy = 0 return endif i = peek(ishoot) AND &h0F j = (peek(ishoot) LSR 4) AND &h07 xy = deek(ishoot + 2) 'invaders checks for clear path before firing, (can fail if player manages to shoot out of order column invaders) if j &= 4 if (invaders(j, i) AND INVADER_DEAD) &= 0 bxy = ixorigin + xy.lo + 5 + ((iyorigin - xy.hi + 14) LSL 8) endif elseif (invaders(j+1, i) AND INVADER_DEAD) bxy = ixorigin + xy.lo + 5 + ((iyorigin - xy.hi + 14) LSL 8) endif endproc proc checkInvaderBullets local bxy if (bexplode) sprite NoFlip, BExplode, bexplode.lo, bexplode.hi inc btimer if btimer &= BEXPLODE_DELAY sprite NoFlip, BulBlk, bexplode.lo, bexplode.hi btimer = 0 : bexplode = btimer endif return endif if &(iexplode) then return if pbullet.hi &&= 0 then return if peek(pbxy - &h0100) &= &h3F for ibindex=0 to NUM_IBULLETS - 1 bxy = ibxy(ibindex) if (bxy) if bxy.lo &= pbxy.lo call eraseInvaderBullet, bxy ibxy(ibindex) = 0 bexplode = pbxy - (3 + 11*256) return endif endif next ibindex endif endproc checkPlayerBullet: if peek(pbxy) &= &h0C pbullet.lo = 0 gosub psmashBarrier elseif peek(pbxy) &&= &h03 pbullet.lo = 0 sexplode = 1 endif return proc eraseInvaderBullet, xy asm LDWI SYS_VDrawBits_134 STW giga_sysFn LDWI 0x3F00 STW giga_sysArg0 LDW _eraseInvaderBullet_xy STW giga_sysArg4 LDI 0x00 ST giga_sysArg2 SYS 134 endasm endproc proc erasePlayerBullet asm LDWI SYS_VDrawBits_134 STW giga_sysFn LDWI 0x3F00 STW giga_sysArg0 LDW _pbxy STW giga_sysArg4 LDI 0x00 ST giga_sysArg2 SYS 134 endasm endproc ismashBarrier: asm LDWI 0x0C00 STW giga_sysArg0 'FGBG colour LDWI 0x02FF ADDW _drawInvaderBullets_bxy STW giga_sysArg4 'offset LD giga_rand0 ANDI 0x03 ST giga_sysArg2 SYS 134 'left damage INC giga_sysArg4 LDI 0x00 ST giga_sysArg2 SYS 134 'center damage INC giga_sysArg4 LD giga_rand2 ANDI 0x03 ST giga_sysArg2 SYS 134 'right damage endasm return proc eraseInvaderEdge, x, y asm LDWI SYS_VDrawBits_134 STW giga_sysFn 'setup SYS function LDWI 0x0000 STW giga_sysArg0 'FGBG colour LD _eraseInvaderEdge_x SUBI 1 'x - 1 to account for extra pixel when marching by 3 ST register0 LD _eraseInvaderEdge_y ADDI 8 ST register0 + 1 LDW register0 STW giga_sysArg4 'addr = x + ((y + 8) LSL 8) LDI 0x00 ST giga_sysArg2 SYS 134 endasm endproc proc gameOver endgame.hi = 1 call playerExplode endproc proc playerDied livesLevel.hi = livesLevel.hi - 1 call drawLives call playerExplode if livesLevel.hi &= 0 then endgame.hi = 1 endproc proc updateScore if iscore &&= 0 then return bcdint @pointsBCD, peek(@iscores + (iscore - 1)) call drawScore iscore = 0 endproc proc updateTime inc timeTicks.lo if timeTicks.lo &&= 60 timeTicks.lo = 0 inc timeTicks.hi endif endproc proc updateSaucer local xy, odd, spoints, i, sy 'wait until there is room for the saucer if iyorigin &= IORIGIN_Y then return 'player hit saucer if &(sexplode) pbullet.lo = 0 stimer.lo = saucerxy.lo spoints = sscores(ipbcount.lo AND (NUM_SSCORES - 1)) bcdint @pointsBCD, spoints bcdint @saucerBCD, spoints call drawScore return endif 'update at 30Hz if &(timeTicks.lo AND 1) then return 'saucerxy [odd:1 y:7][x:8] if timeTicks.hi &&>= SAUCER_DELAY timeTicks.hi = 3 'set seconds counter so that bullets are updated odd = ipbcount.lo AND 1 saucerxy = (SAUCER_YPOS*256) + saucerx(odd) if &(odd) then saucerxy = saucerxy OR &h8000 endif if (stimer) if stimer.hi &< SEXPLODE_DELAY if ((timeTicks.lo LSR 2) AND 1) sprite NoFlip, SExplode + 0, stimer.lo, SAUCER_YPOS else sprite NoFlip, SExplode + 1, stimer.lo, SAUCER_YPOS endif endif inc stimer.hi if stimer.hi &= SEXPLODE_DELAY xy = stimer.lo + 3 + (256*(SAUCER_YPOS + 1)) sprite NoFlip, SauBlk, stimer.lo, SAUCER_YPOS call drawSaucerScore, xy elseif stimer.hi &&= (SEXPLODE_DELAY + SEXPLODE_DELAY + SEXPLODE_DELAY) sprite NoFlip, SauBlk, stimer.lo, SAUCER_YPOS stimer = 0 endif endif if saucerxy &&= 0 then return xy = saucerxy AND &h7FFF odd = saucerxy AND &h8000 i = (saucerxy.lo LSR 1) AND 3 if i &&= 3 then i = 0 sy = xy.hi + 3 if (odd) sprite NoFlip, Saucer + 0, xy.lo, xy.hi sprite NoFlip, SaucerStripLt + (2 - i), xy.lo, sy saucerxy.lo = saucerxy.lo - 1 if saucerxy.lo &= SAUCER_XSTART saucerxy = 0 sound off, 4 sprite NoFlip, SauBlk, SAUCER_XSTART, SAUCER_YPOS endif else sprite FlipX, Saucer + 1, xy.lo, xy.hi sprite FlipX, SaucerStripRt + i, xy.lo, sy inc saucerxy.lo if saucerxy.lo &> SAUCER_XEND saucerxy = 0 sound off, 4 sprite NoFlip, SauBlk, SAUCER_XEND, SAUCER_YPOS endif endif endproc 'overwrite waveform 0 in audio memory, (invader march) 'load wave, ../../res/audio/Invader/IMarch.gtwav, &h0700, 4 dim inotes%(3) = 40, 38, 36, 34 proc updateAudio local t, n, v, i set SOUND_TIMER, 255 i = (INVADERS_J * INVADERS_I - 1) - ipbcount.hi + 3 'invader march if audmarch.lo &= 0 n = get("MIDI_NOTE", peek(@inotes + (audmarch.hi AND 3))) sound on, 1, n, 63, 3 elseif audmarch.lo &&= min(i - 1, 4) sound off, 1 endif if audmarch.lo &>= i audmarch.lo = 0 inc audmarch.hi else inc audmarch.lo endif 'player shoot if (audshoot.hi) n = deek(PBF_LUT + (audshoot.lo LSL 1)) v = peek(PBV_LUT + audshoot.lo) sound on, 2, n, v, 3 sound on, 3, n, v, 0 inc audshoot.lo if audshoot.lo &= PBL_SIZ gosub disableShootSound endif endif 'saucer sound and explosion if (saucerxy OR sefreq) n = deek(SCF_LUT + (satimer LSL 1)) - sefreq sound on, 4, n, 32, 2 inc satimer if satimer &= SCF_SIZ then satimer = 0 if (sexplode) sefreq = 1 sexplode = 0 : saucerxy = sexplode endif if (sefreq) sound on, 3, n LSR 4, sevol, 0 if satimer &= 0 sefreq = sefreq + 600 sevol = sevol - 15 if sevol &<= 0 sevol = 60 sefreq = 0 sound off, 3 sound off, 4 endif endif endif endif 'invader explosion if (iexplode OR aviexplode) v = 63 - aviexplode sound on, 2, deek(IEF_LUT + (itimer.hi LSL 1)), v, 3 inc itimer.hi if itimer.hi &= IEF_SIZ then itimer.hi = 0 aviexplode = aviexplode + 2 if aviexplode &>= 64 itimer.hi = 0 : aviexplode = itimer.hi sound off, 2 endif endif endproc disableShootSound: audshoot = 0 sound off, 2 sound off, 3 return proc drawScore local i, char bcdadd @pointsBCD, @scoreBCD, SCORE_LEN char = SCORE_X for i=0 to SCORE_LEN-1 sprite NoFlip, Digit + peek(@scoreBCD + SCORE_LEN-1 - i), char, SCORE_Y char = char + 6 next i 'bcdcmp requires bcd addrs to point to msd if bcdcmp(@scoreBCD+(SCORE_LEN-1), @highBCD+(SCORE_LEN-1), SCORE_LEN) &= 1 bcdcpy @scoreBCD, @highBCD, SCORE_LEN call drawHigh endif endproc proc drawHigh local i, char char = HIGH_X for i=0 to SCORE_LEN-1 sprite NoFlip, Digit + peek(@highBCD + SCORE_LEN-1 - i), char, HIGH_Y char = char + 6 next i endproc proc drawLevel local i, char sprite NoFlip, Level, LEVEL_X, LEVEL_Y char = LEVEL_X + 6 for i=0 to LEVEL_LEN-4 sprite NoFlip, Digit + peek(@levelBCD + LEVEL_LEN-4 - i), char, LEVEL_Y char = char + 6 next i endproc proc drawSaucerScore, xy local i, char char = xy for i=0 to SAUCER_LEN-3 sprite NoFlip, DigitS + peek(@saucerBCD + SAUCER_LEN-3 - i), char.lo, char.hi char.lo = char.lo + 4 next i endproc proc drawLives local i, plife i = 1 plife = LIFE_X while i &<= livesLevel.hi sprite NoFlip, PLife, plife, LIFE_Y plife = plife + 8 inc i wend while i &<= MAX_LIVES sprite NoFlip, PBlk, plife, LIFE_Y plife = plife + 8 inc i wend endproc proc initInvaders local i, j, index, iprev, inext, invader index = 0 : iprev = index : inext = iprev for j=INVADERS_J - 1 downto 0 for i=0 to INVADERS_I - 1 if index &= 0 iprev = @istruct(INVADERS_J * INVADERS_I - 1, 0) else iprev = addr(istruct(index - 1, 0)) endif if index &= (INVADERS_J * INVADERS_I) - 1 inext = @istruct(0, 0) else inext = addr(istruct(index + 1, 0)) endif iaddress(j, i) = addr(istruct(index, 0)) 'invader = [end:1 j:3 i:4][frame:8] 'struct{invader, xy, prev, next} invader = invaders(j, i) AND &h7FFF invaders(j, i) = invader invader.lo = invader.lo OR ((j LSL 4) AND &h70) OR (i AND &h0F) istruct(index, 0) = invader istruct(index, 1) = ixpos(i) + (iypos(j) LSL 8) istruct(index, 2) = iprev istruct(index, 3) = inext inc index next i next j for i=0 to NUM_IBULLETS - 1 ibxy(i) = 0 next i icurrent = @istruct(0, 0) ishoot = icurrent endproc proc initVars 'initialises all variables, (to zero), starting at @timeTicks init vars @timeTicks endproc proc startLevel sound off bcdint @pointsBCD, 0 bcdint @saucerBCD, 0 bcdint @levelBCD, delayLevel.lo ii = 0 : jj = INVADERS_J - 1 iflip = 2 : xflip = iflip if livesLevel.lo &> LEVEL_X6 then livesLevel.lo = 0 ixorigin = IORIGIN_X : iyorigin = IORIGIN_Y + livesLevel.lo cls call drawScore call drawHigh call drawLives call drawLevel call drawBarriers call initInvaders if delayLevel.lo &&>= 100 call easterEgg else call uneasterEgg endif endproc proc resetLevel livesLevel = 3*256 + 0 delayLevel = 31*256 + 0 bcdint @scoreBCD, 0 sevol = 60 px = PLAYER_X : py = PLAYER_Y call uneasterEgg endproc proc initSystem 'audio fix for ROMv5a poke &h21, peek(&h21) OR 3 mode 2 set FGBG_COLOUR, 0 endproc proc easterEgg local vtable, vaddr vaddr = &h7F 'for vtable=&h0100 to &h0178 step 2 for vtable=&h0100 to &h01EE step 2 poke vtable, vaddr dec vaddr next vtable endproc proc uneasterEgg local vtable, vaddr vaddr = &h08 for vtable=&h0100 to &h01EE step 2 poke vtable, vaddr inc vaddr next vtable endproc proc waitScanline 'wait repeat until &(get("VIDEO_Y") AND 1) endproc