* --------------- * Space Invaders (VMU style) Public Release 1 30/08/2000 * * Rednuht 2000 * www.jumpstation.co.uk * spaceinvaders@jumpstation.co.uk * * Please feel free to rip this sucker apart and use bits in your own programs * (if you do, a mention would be nice) * * There is much missing from this game, please add new and exciting features * * Features(current) * Load screen and credits * Reuasble [Press A] label * Moving player, aliens and shot * Score and level display system * Multi-sprite; aliens and player * Cool Dreamcast icon and gameover screen * Double buffered screen display and reuasble blitter function * Multiple lives and game speeds allowing increased difficulty as play progresses * It was written by me ! (yes, that is a feature !) * * Missing Features ( I refuse to work on this any more ) * Mother ship - the sprites are ready, you just need to display move and hit it * Sound - I thought about it but as the emulator does not yet do sound I gave it a miss this time * Aliens shoot back, I was just to lazy * Proper Font rountine to allow text credits (mine is just a big graphic) * High score table * Allow higher scores (I cheated) * Death sequences for player and aliens, mine just vanish. * any cool feature/add-on you like * * A quick word about what follows; * All the comments were added for MY benifit and maybe a little inaccurate :) * I can not and will not be held responsable for anything that you may use or do with this information * * I claim full credit for of all the work here excluding; * The FINDLINE rountine : Alessandro * GETKEYS and SETSCR : Marcus * The initialiation comments etc : Tyro * (without the skeleton source I would not have even started coding this game) * * And thanks to the whole VMU community * for more info check the web site www.jumpstation.co.uk/si.html * * Look out for [vmu-bandit] coming soon * --------------- .include "sfr.i" ; main defintions for registers etc BSWAP EQU 0 ; VRMAD2 bank swapper ;========================================================================= fontO = $1E ; font offset - only used in the digit draw routine fontY = $1F ; font Y pos - only used in the digit draw routine fontX = $20 ; font X pos - only used in the digit draw routine mclev = $21 ; the current level (more speed changes) dbug = $22 ; temp var 4 debuging clev = $23 ; the current level levc = $24 ; counter for delaying aliens based on current level keyp = $25 ; storeage for current keypresses (should be cleared when used) alnrf = $26 ; temp var used as a reference to the current point in the map data alnd = $27 ; which direct are the aliens travelling? (0 left, 1 right) alnf = $28 ; temp var, alien display frame (offset) alndy = $29 ; temp var, alien display y coord (the offset) alny = $2A ; temp var, alien y coord (not the offset) alnc = $2B ; temp var, alien counter (per row) alnr = $2C ; temp var, number of rows of aliens rowc = $2D ; temp var, for sprite row counter plyY = $2E ; player pos plyX = $2F ; player pos plyH = $30 ; player sprite height (loaded from memory) plylvs = $31 ; player lives plyscr = $32 ; player score shipV = $33 ; mother ship visible - i.e. shot or not shipF = $34 ; frame 1 - 4 shipX = $35 ; mother shipX pos, moves in increments of two (nice and fast) plysht = $36 ; number of shots fired shpsht = $37 ; number of shots dropped level = $38 ; level currently played alienmv =$39 ; map of per line information for the aliens ; including 3 bytes for line Y pos 1,2,3 alienmap=$4C ; each alien has ; bit 7 - visiblity - shot or not - 1 = shot, 0 = not ; bit 6 - animation step - frame 1(0) or 2(1) ; bits 5-0 for xpos - increments of one ; total number of possible aliens is 6 x 3 = 18 ; next available address = $4C+$12+$5E plysx = $5E ; players shoot X pos plysy = $5F ; players shoot Y pos plysv = $60 ; players shoot visibility, could have been a BIT but no need shtmap = $61 ; each shot needs a X pos and Y pos ; max num shoots aliens can fire is based on current level ; upto 5 ; $61 +(5x2) next byte available is $6B alnsY = $6B ; aliens Y pos when clearing alnsX = $6C ; aliens X pos when clearing, minus frame bit alnsF = $6D ; aliens frame bit when clearing alnn = $6E ; number of aliens alive bw = $6F ; when clearing, is it black or white * ----------- * Default initialisation stuff * leave well alone, until you need it) * .org 0 jmpf start .org $3 jmp nop_irq .org $b jmp nop_irq .org $13 jmp nop_irq .org $1b jmp t1int .org $23 jmp nop_irq .org $2b jmp nop_irq .org $33 jmp nop_irq .org $3b jmp nop_irq .org $43 jmp nop_irq .org $4b clr1 p3int,0 clr1 p3int,1 nop_irq: reti .org $130 t1int: push ie clr1 ie,7 not1 ext,0 jmpf t1int pop ie reti .org $1f0 goodbye: not1 ext,0 jmpf goodbye ; leave game mode * ---------------- * File information * .org $200 .byte "SPACE INVADERS " ; 16 byte .byte "http://www.jumpstation.co.uk " ; 32 byte * ---------------- * File icon stuff * .org $240 .word 1 ; number of icons (max = 3) .word 10 ; animation speed * Icon file created with img2byteicon - see www.jumpstation.co.uk/img2 for more details .include "icon.i" * more default initialisation stuff start: mov #$a1,ocr ; write $a1 into Oscillation Control Register (OCR) mov #$09,mcr ; write $09 into Mode Control Register (MCR) mov #$80,vccr ; write $80 into LCD Contrast Control Register (VCCR) clr1 p3int,0 ; clear bit 0 in Port 3 Interrupt Control Register ; (P3INT) clr1 p1,7 ; clear bit 7 in Port 1 Latch (P1) mov #$ff,p3 ; write $ff in Port 3 Latch (P3) * My initialisaion stuff pregame: CLR1 PSW,RAMBK0 ; switch to the other bank, Barclays :) LD $1D SET1 PSW,RAMBK0 ; switch to the other bank, HSBC ;0 BN ACC,0,.white ; even number of seconds .black: MOV #$FF,bw ; solid back gound (night) BR resetgame .white: MOV #$00,bw ; clear background (day) * Each time you play the game start here resetgame: MOV #$02,plylvs ; start game with 3 lives MOV #$28,clev ; level, 58=slow, 00=fast (use increments of 8) MOV #$00,level ; its not level one until next level is called. MOV #$00,alnn ; erm, just bad coding CALL loadscr ; startup the loading screen CALL nextlevel ; all the per level initialasion stuff goes here * the main game engine all runs from here .gamestart: .gameloop: CALL osblit ; copy the fresh frame data from memory to the LCD CALL alienupdate ; goto the alienupdate sub may return depending on delay CALL drawshots ; draw player shot if exists .x1: CALLF getkeys ; Marcus's classic getkey routine BN ACC,4,.fire ; fire .x2: CALLF getkeys ; check for button presses BN ACC,5,.buttonb ; branch if button B is pressed, show score .x3: CALLF getkeys ; any input BN ACC,2,.goleft ; branch if press left .x4: CALLF getkeys ; guess BN ACC,3,.goright ; branch if press right BR .gameloop ; after screen update start again .goright: LD plyx ; load current x position BE #42,.gameloop ; is the current x pos as far over as possible (player sprite is 5 pixels wide) CALLF drwply ; clear the visual position of player INC ACC ; increment x pos and move right ST plyx ; store the new player pos from the ACC to the memory locale CALLF drwply ; update the visual position of player BR .speed ; speed .goleft: LD plyx ; load current x position BE #0,.gameloop ; is the current x pos as far left as possible? if so just go back to looping CALLF drwply ; clear the visual position of player DEC ACC ; decrement x pos and move left ST plyx ; store the new player pos from the ACC to the memory locale CALLF drwply ; update the visual position of player BR .speed ; speed .buttonb: CALL showscore ; pause the game and show the score BR .x3 ; check to see if user is holding more than one button/direction .fire: CALL shoot ; kill them ALL !! BR .x2 ; check to see if user is holding more than one button/direction .speed: BR .gameloop ; always go back * --------- * an offscreen buffer system * last thing to be added * it takes image/screen data from work RAM and blits it to the LCD * all existing screen related operations can refer to the work RAM * the off screen data is formated so that it can be byte for byte directly copied to the LCD osblit: CLR1 ocr,5 ; speed PUSH ACC PUSH B PUSH C PUSH 2 PUSH xbnk PUSH VSEL PUSH VRMAD1 PUSH VRMAD2 SET1 VSEL,4 ; autoincrement please MOV #$80,VRMAD1 ; the pointer to our off screen buffer MOV #$80,2 ; the pointer to the LCD memory XOR ACC ; clear the ACC ST xbnk ; start with the top 16 pixels CLR1 VRMAD2,BSWAP ; start with the top 16 pixels .blit: LD VTRBF ; load 8 pixels of screen data ST @R2 ; save it to the LCD memory LD VRMAD1 ; get ready BNZ .continue ; if its zero then we need to change the bank MOV #$80,2 ; the pointer to the LCD memory MOV #$80,VRMAD1 ; the pointer to our off screen buffer SET1 VRMAD2,BSWAP ; next memory bank please BN xbnk,BSWAP,.b2 ; have we been here before? BR .quitin ; lets get out of here .b2: SET1 xbnk,BSWAP ; next LCD memory bank BR .blit ; more blittin .continue: INC 2 ; next LCD memory location BR .blit ; still more to go .quitin: POP VRMAD2 POP VRMAD1 POP VSEL POP xbnk POP 2 POP C POP B POP ACC SET1 ocr,5 ; put on the breaks RET ; finished blitting image data * ----------------- * clears the offscreen buffer system osclear: CLR1 ocr,5 ; go faster now PUSH ACC PUSH B PUSH C SET1 VSEL,4 ; autoincrement please MOV #$80,VRMAD1 ; the pointer to our off screen buffer XOR ACC ; clear the ACC ST VRMAD2 ; start with the top 16 pixels ST B ; save it MOV #$81,C ; setup counter .clear: LD bw ; is it night or day? ST VTRBF ; clear 8 pixels of the offscreen buffer DEC C ; one less LD C ; better check BNZ .continue ; if its zero then we need to change the bank MOV #$81,C ; just so we get here again MOV #$80,VRMAD1 ; the pointer to our off screen buffer SET1 VRMAD2,BSWAP ; next memory bank please INC B ; next 16 pixels please LD B ; get ready BE #$02,.quitin ; we are updating the LCD icon memory today .continue: BR .clear ; still more to go .quitin: POP C POP B POP ACC CLR1 VSEL,4 ; disable autoincrement SET1 ocr,5 ; wooh horsey RET ; finished blitting image data * --------- * Support routine for the update shot thing * if the missle is at a line where an alien exist then we need to check if it has hit one * takes ACC as the alien row reference * if all aliens are dead then next level shotXchk: MOV #alienmap,0 ST B LD plysY SUB #$04 ST alnsY .nready: LD B BZ .ready ; need to create correct offset so we only check that roow LD 0 ADD #$06 ST 0 DEC B BR .nready .ready MOV #$06,rowc ; temp var .schk: LD @R0 BP ACC,7,.nchk ; already dead don't waste our time BN ACC,6,.frm1 MOV #$04,alnsF BR .btn .frm1: MOV #$00,alnsF .btn: AND #%00111111 ; clear the frame bit, so we get just the X pos ST alnsX ; 4 clearing DEC ACC ; so it only looks at matching the missles point BE plysX,.gchk INC ACC BE plysX,.gchk INC ACC BE plysX,.gchk INC ACC BE plysX,.gchk BR .nchk .gchk: LD @R0 OR #%10000000 ; alien is dead AND #%10000000 ; hide rouge aliens ST @R0 CALL clearAlien INC plyscr MOV #$00,plysv ; shot is dead DEC alnn LD alnn BNZ .fasta JMP nextlevel .fasta: LD mclev BZ .goba DEC mclev BR .goba .nchk: INC 0 DBNZ rowc,.schk ; more !! .goba: RET * --------- * Update shot if exist playershot: CLR1 ocr,5 ; we need the speed LD plysv ; check to see if play has already a shot in the air BZ .notfragging ; nothing to see here CALL drawshot ; clear it from its current position MOV #alienmv+2,1 MOV #$02,alndy ; temp var .getone: LD @R1 ADD #$04 BNE plysY,.notgotone LD alndy CALL shotXchk ; now check the Y ; this routine may have killed out shot, just check LD plysv BZ .notfragging ; came saw killed ! .notgotone: DEC alndy DEC 1 LD 1 BNE #alienmv-1,.getone .deba: LD plysy BZ .noshot ; if Y is 0 shot vanishes DEC plysy CALL drawshot ; needs to be drawn in its new location BR .notfragging ; not an acurate label, sorry .noshot: MOV #$00,plysv ; its all over .notfragging: SET1 ocr,5 ; set the speed back 2 normal RET * --------- * update shots if any exist * convient place to call playershots as well as handling the aliens shot array. drawshots: CALL playershot ; keep it seperate ; loop through # shot/s ; check Y if in 4 player locations then check X ; if hit lower player lives, draw to erase player then dec lives (check 4 game over) ; and only then redraw as new life player ; if Y is below player (out of range) then set visiblity to not! RET *-------------- * show score and level wait half sec and then for A or B to be pressed to return showscore: ; Show current score (and level ? ) .scw: MOV #scoret,trh CALL setscr CLR1 ocr,5 ; CALL printscore SET1 ocr,5 ; CALL osblit .nfre: CALLF getkeys BN ACC,4,.nfre ; wait until they let go of A .nfrv: CALLF getkeys BN ACC,5,.nfrv ; wait until they let go of B .scwi: CALLF getkeys BN ACC,5,.showa ; if they press B tell them to press A BN ACC,4,.quita ; they know what they are doing BR .scwi .showa: MOV #pressa,trh CALL drawlabel ; draw the [press A] label CALL osblit .nb: MOV #$FF,keyp CALL halfsec ; wait 1/2 a sec LD keyp BN ACC,5,.nb ; wait until they let go of B BR .scw .quita: LD plyx BE #$FF,.nogame CALL osclear CALL drwply CLR1 ocr,5 ; CALL aliendraw SET1 ocr,5 ; LD plysV BZ .nogame CALL drawshot .nogame: RET *-------------- * player has pressed fire do we shoot or scoot? * I was going to let the play shoot more than one bullet but * 4 this screen size i dought it would work. shoot: LD plysv ; check to see if play has already a shot in the air BNZ .alreadyfragging MOV #$01,plysv ; see me now! LD plyx ADD #$01 ; offset to center of player sprite ST plysx ; set it LD plyy ; not really needed but means player could be anywhere ADD #$01 ; offset to above player sprite ST plysy ; set it ; well thats all we need to do, let the shot routine handle any movement, collisions etc. CALL drawshot .alreadyfragging: RET * ----------- * clear alien * because this only called to clear individual aliens, when it is used to draw * it will earse. * not used to draw array of all alive aliens clearAlien: PUSH acc PUSH B PUSH C MOV #$00,rowc ; clear the sprite row offset .ROW: LD alnsy ; always the same height ADD rowc ; add he row counter CALL findline ST VRMAD1 MOV #alienf1,trh ; big address LD rowc ; offset to sprite data ADD alnsF ; frame offset LDC ; huge address ST B ; save it (sprite data that is) LD alnsx ; this is input CALL findbytes ; takes the input of X coord and returns ; the offset for byte 1, byte one shifted and byte two shifted ADD VRMAD1 ; add current offset with new offset ST VRMAD1 ; save it ready for screen access LD B ; sprite data 1 XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! LD C ; load sprite 2 BZ .plysbyte ; skip the next bit as the sprite2 has no new bits INC VRMAD1 ; second sprite byte please (increment the byte address to the lcd memory) XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! ; now, have we drawn all rows for this sprite? .plysbyte: MOV #$04,ACC ; this sprite is 4 pixels high INC rowc BNE rowc,.ROW ; still going POP C POP B pop acc RET ; because we use call we need to return * ----------- * draw current players shot (3x3 sprite) drawshot: PUSH acc PUSH B PUSH C MOV #$00,rowc ; clear the sprite row offset .ROW: LD plysy ; always the same height ADD rowc ; add he row counter CALL findline ST VRMAD1 MOV #pshot,trh ; big address LD rowc ; offset to sprite data LDC ; huge address ST B ; save it (sprite data that is) LD plysx ; this is input CALL findbytes ; takes the input of X coord and returns ; the offset for byte 1, byte one shifted and byte two shifted ADD VRMAD1 ; add current offset with new offset ST VRMAD1 ; save it ready for screen access LD B ; sprite data 1 XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! LD C ; load sprite 2 BZ .plysbyte ; skip the next bit as the sprite2 has no new bits INC VRMAD1 ; second sprite byte please (increment the byte address to the lcd memory) XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! ; now, have we drawn all rows for this sprite? .plysbyte: MOV #$03,ACC ; this sprite is 3 pixels high INC rowc BNE rowc,.ROW ; still going POP C POP B pop acc RET ; because we use call we need to return * ------------- * initialise the level * reset aliens and player alike nextlevel: LD alnn ; check the toatl live aliens BNZ .ms ; if its not 0 then do no update the level INC level .ms: LD level BE #$01,.nomd ; first level, do not display score LD alnn BNZ .sm CALL showscore .nomd: LD clev BZ .l SUB #$08 ; make the delay less so the game is faster ST clev .sm: LD clev .l: ST mclev ST levc MOV #$17,plyx ; center player on screen MOV #$04,plyH ; how high MOV #$1C,plyY ; where should player 1 be drawn? MOV #$00,plysv ; players shot is invisible MOV #alienmv,1 ; store that address of the map in the @R1 reference point, MOV #$04,@R1 ; row one of the aliens starts here INC 1 ; next row MOV #$09,@R1 ; row two of the aliens starts here INC 1 ; next row MOV #$0E,@R1 ; row three of the aliens starts here MOV #$00,alnd ; going left to start with LD alnn ; number of live aliens BNZ .smli ; do not reset aliens if some still live MOV #alienmap,1 ; store that address of the map in the @R1 reference point, MOV #18,alnn ; number of live aliens to start with MOV #$0B,B ; B is the start X pos MOV #$00,C ; C is the flag to add to the X pos XOR ACC ; CALL resetrow ; reset alien row MOV #$0B,B ; B is the start X pos MOV #$40,C ; C is the flag to add to the X pos XOR ACC ; CALL resetrow ; reset alien row MOV #$0B,B ; B is the start X pos MOV #$00,C ; C is the flag to add to the X pos XOR ACC ; CALL resetrow ; reset alien row .smli: CLR1 ocr,5 ; we need the speed 2 b 5 mhz to access the LCD screen CALL osclear CALL drwply ; update the visual position of player CALL aliendraw ; draw the aliens (so we can clear and redraw later) SET1 ocr,5 ; set the speed back 2 normal RET ; alien update ; update the visual representation of the aliens ; and moves them alienupdate: LD levc BZ .l DEC levc ; sub 1 from the level speed counter BR .g .l: LD mclev ST levc CLR1 ocr,5 ; we need the speed 2 b 5 mhz to access the LCD screen CALL aliendraw ; erase the current happy aliens CALL alienswap ; swaps the frame bit in all aliens ; do the moving here ; if left go left LD alnd BNZ .right .left: CALL aliensgoleft BR .DSE .right: CALL aliensgoright .dse: CALL aliendraw ; imprint the aliens onto the screen again CALL levelchk ; see if we have won/lost this level SET1 ocr,5 ; set the speed back 2 normal .g: RET ; return to where hench thy came from ; loop through all the aliens looking for the lowest one and if any are alive levelchk: PUSH ACC PUSH B PUSH C MOV #alienmap+18,1 ; last alien not 1st MOV #$03,B ; which row .ar: DEC B ; one less MOV #$06,C ; which alien .na: DEC 1 ; one b4 the last 1 LD @R1 BN ACC,7,.testbottom ; is it alive? DEC C ; onless b4 next line LD C BNZ .tr ; oo next line BR .ar .tr LD 1 BNE #alienmap,.na ; wait until we back at the 1st BR .fint .testbottom: ; this is the last alien (lowest), has it hit the player yet? MOV #alienmv,1 LD B ADD 1 ; address not value ST 1 ; save the new value LD @R1 BNE #$19,.fint ; not got there yet ; if we get here aliens win, kill player LD plylvs BNZ .fintl ; if we get here its all GAMEOVER!! MOV #$FF,plyx ; so we know its game over CALL gameover CALL showscore POP C ; pop the stack POP B ; because we are about to POP ACC ; blow this popsicle stand JMP resetgame .fintl: DEC plylvs CALL nextlevel .fint: POP C POP B POP ACC RET * --------------- * simply find the lowest X value of LIVE aliens and if 0 then we need to go down a go back * kills off the ACC and 1 aliensgoleft: MOV #$FF,alnr ; reuse one of my temp vars MOV #alienmap,1 ; store that address of the map in the @R1 reference point, .la: LD @R1 ; load data via the offset AND #%10111111 ; clear the frame flag BP ACC,7,.gtn ; its dead, so skip to next BE alnr,.lt ; just cause the CY flag to be set .lt: BN PSW,7,.gtn ; got to next ST alnr ; its the new lowest value .gtn: INC 1 LD 1 BNE #alienmap+18,.la ; if we get this far check for alnr = 0 LD alnr BNZ .quitola ; we have not reached the edge yet buba MOV #alienmv,1 INC @R1 ; update 1st row INC 1 INC @R1 ; update 2nd row INC 1 INC @R1 ; update 3rd row INC alnd ; go right little alien dudes BR .ql ; do not update, wait until next time .quitola: MOV #alienmap,1 ; store that address of the map in the @R1 reference point, .fl: LD @R1 BP ACC,7,.flu ; its dead, so skip to next DEC @R1 ; one less for this aliens X pos .flu: INC 1 ; next LD 1 BNE #alienmap+18,.fl ; wait until we have updated them all .ql: RET ; simply find the highest X value of LIVE aliens and if 42 then we need to go down a go back ; kills off the ACC and 1 aliensgoright: MOV #$00,alnr ; reuse one of my temp vars MOV #alienmap,1 ; store that address of the map in the @R1 reference point, .ra: LD @R1 ; load data via the offset AND #%10111111 ; clear the frame flag BP ACC,7,.gtr ; its dead, so skip to next BE alnr,.rt ; just cause the CY flag to be set .rt: BP PSW,7,.gtr ; goto next ST alnr ; its the new highest value .gtr: INC 1 LD 1 BNE #alienmap+18,.ra ; if we get this far check for alnr = 0 LD alnr BNE #$2C,.quitora ; we have not reached the edge yet buba MOV #alienmv,1 INC @R1 ; update 1st row INC 1 INC @R1 ; update 2nd row INC 1 INC @R1 ; update 3rd row DEC alnd ; go left little alien buds BR .qr ; do not update, wait until next time .quitora: MOV #alienmap,1 ; store that address of the map in the @R1 reference point, .fr: LD @R1 BP ACC,7,.flu ; its dead, so skip to next INC @R1 ; one more for this aliens X pos .flu: INC 1 ; next LD 1 BNE #alienmap+18,.fr ; wait until we have updated them all .qr: RET * puts the aliens in a nice line ; Reset row uses the address stored in 1 ; and the ACC as the flag to reset the alien row data ; B is the start X pos ; C is the frame faze flag (bit 6) resetrow: .resetrowl: BE #$06,.finr ; finished all aliens in this row XCH B XOR C ; set the frame bit ST @R1 ; set the value for this alien XOR C ; unset the frame bit ADD #$06 ; spacing between the aliens (including their width) XCH B INC 1 ; next map pos please INC ACC ; do it 6 times BR .resetrowl ; .finr: RET ; our work here is done ; alienswap ; all registers preserved ; swaps the frame bit for each alien in the map alienswap: PUSH 1 PUSH ACC PUSH B MOV #alienmap,1 ; store that address of the map in the @R1 reference point, XOR ACC ; loop counter .swap: BE #$12,.swapend ST B ; protect the loop counter LD @R1 ; load data via the offset XOR #%01000000 ; swap the the frame flag bit ST @R1 ; save the result LD B ; get the loop counter back INC 1 INC ACC BR .swap .swapend: POP B POP ACC POP 1 RET ; no kitty! these are my cheesy poofs ; Aliendraw, draws all the visible aliens ; no input all done by memory maps all registers preserved. aliendraw: PUSH ACC PUSH 1 ; the @R1 current address PUSH B PUSH C MOV #$00,alnr ; row counter MOV #$00,alnrf ; .outeralienrow: LD alnr ; load ACC with current alien line BE #$03,.fina ; no more alien lines to draw so quit MOV #alienmv,1 ; store that address of the map in the @R1 reference point, ; notice use of # symbol ADD 1 ; create offset to alien line (1 is the value at an address) ST 1 ; put it back in the offset address for @R1 LD @R1 ; get the value from the map ST alny ; store the y coord for later MOV #$00,rowc ; for each row or sprite data .inneralienrow: LD rowc ; check it BE #$04,.nextrowaliens ; only 4 pixels heigh LD alny ; prepare to find offset CALL findline ; find the screen offset from the value of the acc as Y ST alndy ; save it, we will use it for each sprite in this row MOV #$00,alnc ; number of aliens per row .aliendraw: LD alnc BE #$06,.nextrow ; only 6 aliens wide ; get the x pos and flags MOV #alienmap,1 ; store that address of the map in the @R1 reference point, ADD 1 ; add ACC and address in 1 to get map offset ADD alnrf ; without this we only get the first line of aliens ST 1 ; update offset reference LD @R1 ; load map data from address in 1 BP ACC,7,.nextalien ; if this alien is dead do not show it BPC ACC,6,.faze2 ; get anim frame for alien .faze1: MOV #$00,alnf ; save frame offset BR .faze ; draw it .faze2: MOV #$04,alnf ; save frame offset .faze: ST C ; store the X pos XOR ACC ; clear the offset MOV #alienf1,trh ; big address ADD alnf ; offset to sprite data ADD rowc ; offset to sprite data LDC ; huge address ST B ; b is now the byte of sprite data LD C ; ACC is now the X pos CALL findbytes ; B and C will become the sprite data while ACC is the screen offset ADD alndy ; change the current offset ST VRMAD1 ; set it in stone LD B ; sprite data 1 ; CLR1 ocr,5 ; we need the speed 2 b 5 mhz to access the LCD screen XOR VTRBF ; replace what is already on the screen. ST VTRBF ; OUTPUT !! LD C ; output second byte ? BZ .nosprite INC VRMAD1 XOR VTRBF ; replace/mask what is already on the screen. ST VTRBF ; OUTPUT !! .nosprite: .nextalien: INC alnc BR .aliendraw ; we don't decide when this loop finishes, let some one else do it. .nextrow: INC alny ; ypos of current sprite row INC rowc ; update the offset counter BR .inneralienrow ; always try the next one .nextrowaliens: LD alnrf ADD #$06 ; update ST alnrf INC alnr ; update ;MOV #$04,alnrf BR .outeralienrow ; we don't decide when this loop finishes, let some one else do it. .fina: POP C POP B POP 1 ; POP ACC RET ; return now our work is done * ------------ * loading screen plus scrolling credits * loadscr: .nfre: CALLF getkeys BN ACC,4,.nfre ; wait until they let go of A ;show load screen wait time MOV #credits,trh CALL setscr CALL osblit ; wait a second of two CALL halfsec CALL halfsec CALL halfsec CALL halfsec MOV #$05,B ; BR .nfre .lmbp: MOV #pressa, trh CALL drawlabel ; draw the [press A] label CALL osblit MOV #$FF,keyp CALL halfsec ; wait 1/2 a sec LD keyp BN ACC,4,.scrollq ; branch if button A is pressed MOV #credits,trh CALL setscr CALL osblit MOV #$FF,keyp CALL halfsec LD keyp BN ACC,4,.scrollq ; branch if button A is pressed DEC B LD B BZ .lmoq ; only five times b4 scrolling BR .lmbp .lmoq: ; now we get down to a little scrolling MOV #credits,TRH .morescroll: CALL vssec ; on the emu this delay is unnessesary CALLF getkeys BN ACC,4,.scrollq ; branch if button A is pressed CALL setscr CALL osblit LD TRL ADD #$06 ; bytes per line ST TRL LD TRH ADDC #$00 ; will be 1 if the CY flag was set from the last addition ST TRH BNE #>credits+4,.morescroll ; check the high byte CALL halfsec ; wait 1/2 a sec BR loadscr .scrollq: ; it all ends here ! onto the game ! RET * ------------ * game over screen + [Press A] * show gameover screen wait time, A pressed to return gameover: MOV #gmoverscr,trh CALL setscr ; this copies an image to the LCD screen CALL osblit ; wait a second of two CALL halfsec CALL halfsec CALL halfsec CALL halfsec .gmbp: MOV #pressa, trh CALL drawlabel ; draw the [press A] label CALL osblit MOV #$FF,keyp CALL halfsec ; wait 1/2 a sec LD keyp BN ACC,4,.gmoq ; branch if button A is pressed MOV #gmoverscr,trh CALL setscr ; this copies an image to the LCD screen CALL osblit MOV #$FF,keyp CALL halfsec LD keyp BN ACC,4,.gmoq ; branch if button A is pressed BR .gmbp .gmoq: RET * ----------- * draw player - draw the player sprite onto the screen at its current position * all registers are preserved, plyx is used as input drwply: PUSH acc PUSH B PUSH C CLR1 ocr,5 ; we need the speed MOV #$00,rowc ; clear the sprite row offset .ROW: LD plylvs ; what life? ROL ; x2 ADD ACC ; Plus iteself means these two instruction make it x4 ST alnr ; save it in a temp var LD plyY ; always the same height ADD rowc ; add he row counter CALL findline ST VRMAD1 MOV #plyls,trh ; big address LD rowc ; offset to sprite data ADD alnr ; add offset to sprite of current life LDC ; huge address ST B ; save it (sprite data that is) LD plyx ; this is input CALL findbytes ; takes the input of X coord and returns ; the offset for byte 1, byte one shifted and byte two shifted ADD VRMAD1 ; add current offset with new offset ST VRMAD1 ; save it ready for screen access LD B ; sprite data 1 XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! LD C ; load sprite 2 BZ .ply1byte ; skip the next bit as the sprite2 has no new bits INC VRMAD1 ; second sprite byte please (increment the byte address to the lcd memory) XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! ; now, have we drawn all rows for this sprite? .ply1byte: LD plyH INC rowc BNE rowc,.ROW ; still going SET1 ocr,5 ; set the speed back 2 normal POP C POP B pop acc RET ; because we use call we need to return * find bytes ; Input ACC as X coordinate ; Input B as single byte representing pixels of a sprite ; Output ACC as offset to first byte for sprite data to be chucked at ; Output B as first byte of sprite data rotated to be correct on screen. ; Output C as second byte of sprite data rotated from the first findbytes: PUSH B ; can't lose this ST B ; back up the x coord ; Shift right to get divide by 8 CLR1 PSW,CY ; clear CY RORC ; divide by 2 i.e. 22 / 2 = 11 CLR1 PSW,CY ; clear CY RORC ; divide by 2 i.e. 11 / 2 = 5 CLR1 PSW,CY ; clear CY RORC ; divide by 2 i.e. 5 / 2 = 2 ; this is the answer to 1st byte ST C ; save it for later. CLR1 PSW,CY ; clear CY ROLC ; times 2 i.e. 2 * 2 = 4 CLR1 PSW,CY ; clear CY ROLC ; times 2 i.e. 4 * 2 = 8 CLR1 PSW,CY ; clear CY ROLC ; times 2 i.e. 8 * 2 = 16 ; pop ACC and times the answer, sub answer, whats left is bits to shift. ; i.e. x=22, 22/8=2, 2*8=16, 22-16=6 XCH B ; swap ACC with B SUB B ; i.e. 22-16 = 6 ; ACC now contains number of bits to shift by. POP B ; is once again the sprite data PUSH C ; protect offset number of bytes MOV #$00,C ; clear, ready for some shift'in .shapeshifter: BZ .quitin ; finished? XCH B ; the sprite data swapped for the loop counter CLR1 PSW,CY ; clear CY RORC ; bit 0 is push into the CY flag XCH C ; sprite 1 is swaped with sprite data 2 RORC ; previously mention CY flag is shifted into bit 7 XCH C ; swap sprite 2 back with sprite 1 XCH B ; swap sprite 1 back with loop counter DEC ACC ; decrement the loop counter BR .shapeshifter ; try again .quitin: POP ACC ; damm im naughty, pop C from the stack into the ACC. ; so B and C are the two sprite bytes shifted ; and ACC is the offset to the first byte RET ; "thank you, come again" * --------------- * Routine kindly donated by Alessandro * my routine to do the same jobs was about 10 times larger * FindLine: ; ; takes a line-number in ACC (0 - 31) ; ; returns line-start in ACC ($80, $86, $90, $96 etc.) ; and sets VRMAD2-register to $00/$FF ; ; no other registers will be affected ; rol ; multiply with 8 rol rol ; bn acc,3,.fl_l1 sub #2 ; line start correction, if uneven line number .fl_l1: mov #0,VRMAD2 ; default: bank 0 bn acc,7,.fl_l2 mov #$FF,VRMAD2 ; select bank 1 if line-number was 16 or more .fl_l2: or #%10000000 ; add $80 (start of XRAM) ret ; because we use call we need to return * ----------- * half second delay(ish) * i changed this to be more accetable in the game its is no longer 1/2 a second * more like 1/4 sec halfsec: PUSH ACC MOV #$04,ACC ; 1F for emu, 4 for real CALL delay POP ACC RET * ----------- * delay * while delaying gets inputs and stores them in keyp delay: PUSH B .delay: CALL vssec ST B LD keyp BNE #$FF,.ndel ; have we got a key press yet CALLF getkeys ; nope ST keyp ; ah, now we have .ndel: LD B DBNZ ACC,.delay POP B RET * ----------- * a delay of the delay loop :) * ?th of a second delay vssec: PUSH ACC MOV #$FF,ACC .vssec: DBNZ ACC,.vssec POP ACC RET * ------ * divi - gives me a decimal score/level * example * input ACC = 56, B = 10 * output C = 5, ACC = 6 divi: MOV #$00,C .m: SUB B BP PSW,CY,.nomore ; is this still going INC C BR .m .nomore: ADD B ; get back to where we were RET * --------- * off5 creates an offset times by 5 * ACC = in, ACC = output off5: PUSH B MOV #$00,B .ga: BZ .fin XCH B ADD #$05 XCH B DEC ACC BR .ga .fin: XCH B POP B RET * ---------- * Print score and level details to specific locations * fits in with the template image * does 2 digits for level and then three digits for score * groovy template makes it all look much better. printscore: LD level MOV #10,B CALL divi ; set div XCH C CALL off5 ; create offset ST fontO XCH C MOV #35,fontX MOV #4,fontY CALL drawdigit ; tens of level MOV #1,B CALL divi ; set div XCH C CALL off5 ; create offset ST fontO XCH C MOV #41,fontX MOV #4,fontY CALL drawdigit ; units of level LD plyscr MOV #100,B CALL divi ; set div XCH C CALL off5 ; create offset ST fontO XCH C MOV #14,fontX MOV #24,fontY CALL drawdigit ; hundereds of level MOV #10,B CALL divi ; set div XCH C CALL off5 ; create offset ST fontO XCH C MOV #20,fontX MOV #24,fontY CALL drawdigit ; tens of score MOV #1,B CALL divi ; set div XCH C CALL off5 ; create offset ST fontO XCH C MOV #26,fontX MOV #24,fontY CALL drawdigit ; units of score RET * ------------ * Draws a 4x5 character sprite at XY * this is my 3rd custom sprite routine, i really need to make it all into one big one * fontX = X * fontY = Y * fontO = O offset to correct data drawdigit: PUSH acc PUSH B PUSH C PUSH rowc MOV #$00,rowc ; clear the sprite row offset .ROW: LD fontY ADD rowc ; add he row counter CALL findline ST VRMAD1 MOV #numtab,trh ; big address LD rowc ; offset to sprite data ADD fontO ; frame offset LDC ; huge address ST B ; save it (sprite data that is) LD fontX ; this is input CALL findbytes ; takes the input of X coord and returns ; the offset for byte 1, byte one shifted and byte two shifted ADD VRMAD1 ; add current offset with new offset ST VRMAD1 ; save it ready for screen access LD B ; sprite data 1 XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! LD C ; load sprite 2 BZ .plysbyte ; skip the next bit as the sprite2 has no new bits INC VRMAD1 ; second sprite byte please (increment the byte address to the lcd memory) XOR VTRBF ; remove/mask what is already on the screen. ST VTRBF ; OUTPUT !! ; now, have we drawn all rows for this sprite? .plysbyte: MOV #$05,ACC ; this sprite is 5 pixels high INC rowc BNE rowc,.ROW ; still going POP rowc POP C POP B pop acc RET ; because we use call we need to return * ----------- * drawlabel * draws label from trl,trh with bitmask * direct rip of setscr just a few changes * ANDs the bitmask first * then ORs the label data drawlabel: PUSH VSEL CLR1 VSEL,4 ; disable autoincrement CLR1 ocr,5 PUSH acc PUSH c MOV #$D0,VRMAD1 ; start at line 10 XOR ACC ST VRMAD2 ST c .lsloop: LDC AND VTRBF ; take current screen data and AND it with the bit mask ST VTRBF INC VRMAD1 LD VRMAD1 AND #$f BNE #$c,.lsskip LD VRMAD1 ADD #4 ST VRMAD1 BNZ .lsskip MOV #$FF,VRMAD2 MOV #$80,VRMAD1 .lsskip: INC c LD c BNE #$42,.lsloop ; just eleven lines * 6 = $42 MOV #$D0,VRMAD1 ; start at line 10 XOR ACC ST VRMAD2 LD C ; keep the data pointer (the data follows the bitmask) .dsloop: LDC OR VTRBF ; take current screen data and OR it with the label ST VTRBF INC VRMAD1 LD VRMAD1 AND #$f BNE #$c,.dsskip LD VRMAD1 ADD #4 ST VRMAD1 BNZ .dsskip MOV #$FF,VRMAD2 MOV #$80,VRMAD1 .dsskip: INC c LD c BNE #$84,.dsloop ; just eleven lines POP c POP acc SET1 ocr,5 POP VSEL RET * ------------------------------------------------------ * Set Screen - Marcus's original * modified to store everything in work RAM setscr: PUSH VSEL CLR1 VSEL,4 ; disable autoincrement clr1 ocr,5 push acc push VRMAD2 push c push VRMAD1 mov #$80,VRMAD1 xor acc CLR1 VRMAD2,BSWAP st c .sloop: ldc st VTRBF inc VRMAD1 ld VRMAD1 and #$f bne #$c,.sskip ld VRMAD1 add #4 st VRMAD1 bnz .sskip SET1 VRMAD2,BSWAP mov #$80,VRMAD1 .sskip: inc c ld c bne #$c0,.sloop pop VRMAD1 pop c pop VRMAD2 pop acc set1 ocr,5 POP VSEL ret plyls: .byte %00100000 ; --#----- Last life sprite .byte %01010000 ; -#-#---- .byte %10001000 ; #---#--- .byte %01010000 ; -#-#---- .byte %00100000 ; --#----- Second life sprite .byte %01010000 ; -#-#---- .byte %11011000 ; ##-##--- .byte %10001000 ; #---#--- .byte %00100000 ; --#----- First life sprite .byte %01110000 ; -###---- .byte %11111000 ; #####--- .byte %10101000 ; #-#-#--- alienf1: .byte %01100000 ; -##----- .byte %10110000 ; #-##---- .byte %01100000 ; -##----- .byte %10010000 ; #--#---- alienf2: .byte %01100000 ; -##----- .byte %11010000 ; ##-#---- .byte %01100000 ; -##----- .byte %01100000 ; -##----- shipf1: .byte %01111100 ; -#####-- .byte %11100010 ; ###---#- .byte %01111100 ; -#####-- shipf2: .byte %01111100 ; -#####-- .byte %10110010 ; #-##--#- .byte %01111100 ; -#####-- shipf3: .byte %01111100 ; -#####-- .byte %10011010 ; #--##-#- .byte %01111100 ; -#####-- shipf4: .byte %01111100 ; -#####-- .byte %10001110 ; #---###- .byte %01111100 ; -#####-- pshot: .byte %01000000 ; -#------ .byte %01000000 ; -#------ .byte %10100000 ; #-#----- pressA: .include "pressa.i" ; press A pop-up and bit mask (bitmask first) credits: .include "credit.i" ; main screen and scolling credits gmoverscr: ; game over screen .include "gameover.i" scoret: ; score table template .include "score.i" numtab: ; number table (fonts) .include "numbers.i" * ---------------------------------------------------------------------- * getkeys - loads Port 3 data into ACC * also handles Mode-button (QUIT), Dreamcast Connect and Sleep * direct copy of Marcus's Tetris code (as part of the skeleton code) getkeys: bp p7,0,quit ; Quit, if Dreamcast Connection ld p3 ; read key status bn acc,6,quit ; if Mode Key pressed, Quit bn acc,7,sleep ; if Sleep Key pressed, then Pause ret ; otherwise return with pressed key in ACC quit: jmpf goodbye ; Long Jump, in case we are too far away ; for a 'normal' branch sleep: set1 pcon,0 ; activate HALT mode (saves power) bn p3,7,sleep ; wait until Sleep Key is released mov #0,vccr ; turn off LCD ; sleepmore: set1 pcon,0 ; activate HALT mode (saves power) bp p7,0,quit ; Docked? bp p3,7,sleepmore ; no Sleep Key pressed yet mov #$80,vccr ; turn on LCD again ; waitsleepup: set1 pcon,0 ; activate HALT modus (saves power) bn p3,7,waitsleepup br getkeys ; continue to wait for keypress * ---------------------------------------------------------------------- * End .cnop 0,$200 ; pad to an even number of blocks