;z80 ;zx-spectrum ;assembler ;; 1kanoid, entry for the 2002 minigame competition ;; by Paolo Ferraris (pieffe8@libero.it) - public domain ;; works with any ZX Spectrum (16k/48k/128k/etc...) and ;; most Spectrum emulators - assemblable with TASM dataaddr: .equ 6400h cx: .equ 0 ; current ball position (0,0=topleft) cy: .equ 1 ; refers to center dx: .equ 2 ; ball direction (1, down, right, 255 opp.) dy: .equ 3 tx: .equ 4 ; how many time units are still needed for a ty: .equ 5 ; movement delayx: .equ 6 ; time units needed for a movement delayy: .equ 7 shippos: .equ 8 ; ship position (refers to center of ship) shipflags: .equ 9 ;; bit 0: opened exit (H bonus) ;; bit 1: attaching ship (C bonus) ;; bit 2: bigger ship (E bonus) ;; bit 3: shooting ship (L bonus) ;; bit 4: ball is attached (beginning of round, or C bonus) ;; bit 5: bullet shot droppingchar: .equ 10 ; 0=no character, 1-6=dropping char oldcx: .equ 11 ; variables kept for deleting ships, balls oldcy: .equ 12 timer: .equ 13 ; used for ball speed (2 bytes) detachtime .equ 15 ; if detachtime=timer%256 then detach level: .equ 16 ; current level lives: .equ 17 ; current lives blocksleft: .equ 18 ; dcx: .equ 19 ; dropping char coordinates (center) dcy: .equ 20 bulletx .equ 21 ; coordinates of bullet (near top dot) bullety .equ 22 getcoords: .equ 022B0h blockaddr: .equ 06300h msblkaddr: .equ 63h attraddr .equ 5800h ; where the attribute memory starts blocktype: .equ 6500h ; block type for each row randsub .equ 6900h ; address of random subroutine backcolor: .equ 79 ; background color uppport09876: .equ 0EFh ; joystick port (upper byte) beginline: .ORG 23755 ;; BASIC program: ;; ;; 0 RANDOMIZE: RANDOMIZE USR : REM ;; .byte 0 ; line number (higher part) .byte 0 ; line number (lower part) .word endline-beginline-4 .byte 0F9h ; RANDOMIZE .byte ':' ; .byte 0F9h ; RANDOMIZE .byte 0C0h ; USR .byte '0' .byte 0Eh ; number begins .byte 0 ; .byte 0 ; .word start ; address of start code .byte 0 .byte ':' ; .byte 0EAh ; REM start: LD IX,dataaddr ;; copy the RND math subroutine from ROM to RAM, and append RET. LD HL,025FDh LD DE,randsub LD BC,40 LDIR LD A,0C9h ; RET LD (DE),A newgame: LD HL,0500h ; 5 lives, level 0 (1 soon) LD (dataaddr+level),HL ; write the value in memory gotonextlevel: ;; increases the level LD HL,dataaddr+level BIT 4,(HL) RET NZ INC (HL) ;; erases the blocks memory LD HL,blockaddr XOR A initlevelloop0: LD (HL),A ; A=0 INC L JR NZ,initlevelloop0 ;; calculates the blocktypes ;; the number of times a block has to be hit in order to be ;; destroyed ;; at first level the four values are 1,0,0,0 ;; then ;; 1,1,0,0 ;; 2,1,0,0 ;; 2,1,1,0 ;; 2,2,1,0 ;; 3,2,1,0 ;; 3,2,1,1 ;; 3,2,2,1 ;; 3,3,2,1 ;; 4,3,2,1 ;; 4,3,2,2 ;; 4,3,3,2 ;; 4,4,3,2 ;; 4,4,3,3 ;; 4,4,4,3 ;; 4,4,4,4 LD B,(IX+level) ; current level LD HL,blocktype+4 initlevelloop1: LD (HL),0 ; reset all four block types (0=no block) DEC L JR NZ,initlevelloop1 LD E,L ; E=0; INC L ; E=255, HL=blocktype+1 LD C,L ; C will contain the maximal value of L initlevelloop2: ;; here we increment the value of the blocktype that is in HL LD A,(HL) INC (HL) ; we increment the byte CP 3 ; was the value 3? Now 4? JR Z,rewind ; if yes, we "rewind" AND A ; if the byte was 0, we increment the JR NZ,noaddblocks ; number of blocks and start over LD A,E ADD A,33 ; add 33 to the number of blocks LD E,A noaddblocks: DEC L ; next block to increment JR NZ,norewind ; if it is not zero, we dont rewind ;; we cannot decrement L any more ;; then we will increment C (if <4) and L will get the new value ;; of C rewind: BIT 2,C ; if C is already 4, we don't JR NZ,noinc ; increment it INC C noinc: LD L,C ; L gets the value of C norewind: DJNZ initlevelloop2 ; repeat as many times as the level number LD (IX+blocksleft),E LD DE,blockaddr+48 ; we skip the first 3 lines LD B,3 initlevelloop6: LD L,1 PUSH BC LD C,4 ; we fill the next 4 lines initlevelloop5: LD A,(HL) ; get the value to insert LD B,16 ; the 16 blocks per line initlevelloop4: LD (DE),A ; copy in the block INC DE DJNZ initlevelloop4 INC HL DEC C JR NZ,initlevelloop5 ; repeat for next line POP BC DJNZ initlevelloop6 ; repeat for next 4-line blocks startball: ;; redraw the screen LD A,backcolor LD (23693),A ; screen color LD (23624),A ; border color XOR A CALL 229Bh ; BORDER A CALL 0D6Bh ; CLS ;; copy last line of blocks (empty line) to fill with black the ;; bottom of the screen LD HL,5A16h blackloop: LD (HL),7 DEC L JR NZ,blackloop LD DE,5A20h LD BC,00E0h LDIR ;; draw all blocks (must be real blocks) LD BC,blockaddr showblocksloop: LD A,C AND 15 CP 11 CALL C,writeblock INC C JR NZ,showblocksloop ;; prints the level letter LD DE,04D0h LD A,(IX+level) ADD A,64 CALL printchar ;; copy the initial situation of the ball, stick, etc... LD HL,startballmem LD DE,dataaddr LD BC,endballmem-startballmem LDIR ;; show the latest tokens CALL drawball CALL showlives ;; here is the main loop mainloop: CALL drawship ;; ball speed handling LD HL,(dataaddr+timer) INC HL ;; time to detach the ball? LD A,L CP (IX+detachtime) JR NZ,nodetach RES 4,(IX+shipflags) nodetach: LD A,H ;; if too fast, slow it CP 208 JR C,nooverflow SUB 16 LD H,A nooverflow: LD (dataaddr+timer),HL ;; calculate ball speed AND 240 RRCA RRCA RRCA RRCA LD B,A ; how many time the ball movement is repeated repeat: PUSH BC BIT 4,(IX+shipflags); if ball is not attached, it moves CALL Z,moveball POP BC DJNZ repeat HALT ;; is there a bullet to handle? CALL handleball ; move ball LD HL,dataaddr+shipflags BIT 5,(HL) CALL NZ,handlebullet ; move bullet (moves it and destroy blocks) CALL drawship CALL drawletter ; delete the falling letter ;; move the letter down LD DE,(dataaddr+dcx) INC D LD (dataaddr+dcx),DE ;; is the letter lost LD A,D ; dcy CP 172 JR Z,lostletter ;; can it be got by the ship? CP 164 CALL NC,gettingletter JR notlostletter lostletter: LD (IX+droppingchar),0 notlostletter: CALL drawletter ; redraw the letter ;; checks if no blocks left LD A,(IX+blocksleft) AND A JR Z,gotonextlevel2 ;; got exit? BIT 0,(HL) JR Z,notexit LD A,(IX+shippos) CP 164 gotonextlevel2: JP Z,gotonextlevel notexit: CALL moveship JR mainloop ; repeat indefinitely ;; moves the ball (calculates new coordinates, no screen update) moveball: ;; see if we should change the x coordinate LD HL,dataaddr+tx DEC (HL) CALL Z,movex ; change the x coordinate ;; see if we should change the y coordinate LD HL,dataaddr+ty DEC (HL) RET NZ ; do not change the y coordinate, return ;; handles the y movement ;; HL=dataaddr+ty movey: ;; reset the ty value LD A,(IX+delayy) LD (HL),A ;; see if the ball hits the ship LD DE,(dataaddr+cx) ;; does the ball go down? LD HL,dataaddr+dy BIT 7,(HL) JR NZ,continuemovey; if not, ship it ;; is it at the correct y position? LD A,D CP 166 JR NZ,continuemovey; if not, ship it ;; calculate the relative position of the ball respect to the ship ;; position LD A,E CALL normalizedist JR NC,continuemovey; it means outside the ship, skip it LD (IX+dx),B ; B=1 if in the right side, 255 if left side ;; calculate new delays LD BC,0208h ; delays for "vertical" movement CP 4 JR C,newdir INC B LD C,B ; BC=0303h, delays for diagonal movements CP 8 JR C,newdir LD BC,0802h ; delays for "horizontal" movements newdir: LD (dataaddr+delayx),BC ; write these delays ;; will ball be attached? BIT 1,(IX+shipflags) JR Z,reversexy ;; set the attached flag, and the 5,1 secs. time before detaching SET 4,(IX+shipflags) LD A,(IX+timer) LD (IX+detachtime),A ;; invert the ball direction, increase the ball speed and beep ;; HL=dataaddr+tx or dataaddr+ty reversexy: ;; invert the direction XOR A SUB (HL) LD (HL),A ;; increase the ball speed LD HL,(dataaddr+timer) LD DE,128 ADD HL,DE LD (dataaddr+timer),HL ;; beep LD HL,1643 LD DE,1 PUSH IX CALL 03B5h ; ROM call POP IX RET ;; balls doesn't hit the ship continuemovey: ;; calculate the new (possible) ball position LD A,D ADD A,(HL) LD D,A ;; see if the balls hits the border ADD A,(HL) CP 183 JR NC,reversexy ; if yes, bounce ;; check if one of three points are inside a block PUSH DE LD D,A CALL block DEC E CALL NC,block INC E INC E ;; the third one is tested in "testblocks" JR testblocks ;; move the x-coordinate of the ball ;; HL=dataaddr+tx movex: ;; set the delay LD A,(IX+delayx) LD (HL),A ;; new ball position in DE LD DE,(dataaddr+cx) LD A,E LD HL,dataaddr+dx ADD A,(HL) LD E,A ;; check if the ball hits the border ADD A,(HL) CP 176 JR NC,reversexy ;; check if one of three points are inside a block PUSH DE LD E,A CALL block INC D CALL NC,block DEC D DEC D ;; here movey merge ;; test the last point testblocks: CALL NC,block POP DE ; re-get the new possible x-y position JR C,reversexy ; if a point is internal, bounce ;; the ball moves freely, update x-y position LD (dataaddr+cx),DE ;; check if the new y coordinate is too low LD A,D CP 181 RET NZ ; if not, we can exit ;; if yes, life lost POP BC ; we eliminate the ret address POP BC ; we eliminate another element from the stack DEC (IX+lives) JP Z,newgame ; if no more lives, new game JP startball ; if not, just a new ball ;; test if a pair of coordinates DE are in a block. If yes, ;; we block is reduced/destroyed with bonuses released ;; C flag: block hit ;; NC: block not hit ;; preserves DE,HL block: ;; if the y is too low, it is not in any block LD A,D CP 128 RET NC ;; calculate the corresponding block memory address in BC LD A,E ; XXXX---- RLCA ; XXX----X RLCA ; XX----XX RLCA ; X----XXX XOR D AND 135 ; *0000*** XOR D ; XYYYYXXX RLCA ; YYYYXXXX LD C,A LD B,msblkaddr ;; if no block, return LD A,(BC) AND A RET Z ;; decrease the block, and eventually deal with its destruction DEC A LD (BC),A PUSH HL CALL Z,handledestroyblock POP HL ;; write the color of the block, with the block address in BC ;; sets C flag to true. Preserves BC,DE,HL writeblock: PUSH HL PUSH BC LD A,(BC) ;; calculate the attribute memory address of the first byte LD B,0 LD HL,attraddr+33 ADD HL,BC ADD HL,BC ;; calculate the new attribute value ADD A,A ADD A,A ADD A,A ADD A,7 ;; write that value in that memory address and next one LD (HL),A INC HL LD (HL),A SCF ; set carry flag POP BC POP HL RET ;; deal with bonus release from block destruction ;; DE=coordinates of a point inside the block ;; preserves BC,DE,HL handledestroyblock: DEC (IX+blocksleft) ; decrease the number of blocks ;; exit is opened? BIT 0,(IX+shipflags) RET NZ ; if yes, no other bonus ;; already a falling letter? LD A,(IX+droppingchar) AND A RET NZ ; if yes, no other bonus ;; get a number betwween 0 and 31, inclusive LD A,32 PUSH DE PUSH HL PUSH BC CALL 02D28h ; A in FP stack CALL randsub ; random number <1 in FP stack RST 28h ; do the following operations: .byte 4 ; multiplication .byte 39 ; INT .byte 56 ; end of operations CALL 02DA2h ; result from FP stack to A POP BC POP HL POP DE ;; if that number >=6, no bonus CP 6 RET NC ;; write that value, increase by 1, in droppingchar INC A LD (IX+droppingchar),A ;; memorize the coordinates of the center of the letter LD A,D AND 248 ADD A,4 LD (IX+dcy),A LD A,E AND 240 ADD A,8 LD (IX+dcx),A ;; draw the falling letter ;; preserves HL,DE,BC drawletter: PUSH HL PUSH DE LD E,(IX+droppingchar) LD D,0 LD HL,droptypes ADD HL,DE LD A,(HL) LD HL,(dataaddr+dcx) LD DE,0404H ADD HL,DE EX DE,HL CALL printchar POP DE POP HL RET ;; A=x coordinate of something ;; returns: B=1 if A>=shippos, B=-1 otherwise ;; A=distance from the center of ship, from the ;; normalized if the ship is large, and always positive ;; C flag if A<12, NC otherwise ;; preserves HL,DE,C normalizedist: LD B,1 SUB (IX+shippos) JR NC,reducesize ; positive x direction CPL LD B,255 reducesize: BIT 2,(IX+shipflags) JR Z,notlarge2 AND A RRA notlarge2: CP 12 RET ;; draws the ship ;; returns HL=dataaddr+shipflags drawship: LD HL,dataaddr+shipflags LD D,175 LD A,(IX+shippos) ADD A,8 LD E,A LD A,128 BIT 3,(HL) CALL NZ,printbyte LD BC,3*256+12 BIT 2,(HL) JR Z,notabigship LD BC,6*256+24 notabigship: LD A,E SUB C LD E,A INC D loopdrawship: LD A,255 CALL printbyte LD A,8 ADD A,E LD E,A DJNZ loopdrawship RET ;; handle the keys pressed, and the ship movement, and of the ball ;; also if it is attached moveship: ;; read input port LD A,uppport09876 IN A,(254) LD D,A RRCA LD E,(IX+shippos) JR C,notzero ; 0 not pressed ;; 0 is pressed RES 4,(HL) ; detach ;; check if a bullet shoots BIT 3,(HL) JR Z,notzero BIT 5,(HL) JR NZ,notzero ;; generate the bulet and draw it SET 5,(HL) PUSH DE LD D,160 CALL writeanddrawbullet ; LD (dataaddr+bulletx),DE ; a ; CALL drawbullet ; a POP DE notzero: LD A,E ;; get the possible x-movements accordingly, to the key pressed LD BC,2*256+8 ; +8 if ball direction, +2 if opposite BIT 3,D ; 7 pressed JR Z,moveleftright LD BC,248*256+254 ; -8 if ball direction, -2 if opposite BIT 4,D ; 6 pressed JR NZ,continue2 moveleftright: ;; choose the movement, accordingly to the relative ball position LD D,B CP (IX+cx) JR NC,continue LD D,C continue: ;; add this relative movement LD E,A ; the old shippos for later ADD A,D continue2: ;; choose the ship position boundaries in case of large or small ship LD BC,12*256+165 BIT 2,(HL) JR Z,notlarge LD BC,24*256+153 notlarge: ;; check if the boundaries are respected CP B JR NC,notlower LD A,B notlower: CP C JR C,newshippos LD A,C DEC A newshippos: ;; save the ship pos LD (IX+shippos),A ;; if ball is attached, move the ball accordingly to the ship BIT 4,(HL) RET Z SUB E ; relative movement of the ship ADD A,(IX+cx) ; the new position of the ball ;; it can happen that the ball moves outside the border because ;; at the very edge of the ship, that must be corrected CP 240 JR C,attachbranch1 XOR A attachbranch1: CP 2 JR NC,attachbranch2 LD A,2 attachbranch2: CP 174 JR C,attachbranch3 LD A,173 attachbranch3: LD (IX+cx),A ; new value memorized ;; delete the ball and redraw it in the new position handleball: CALL drawball LD HL,(dataaddr+cx) LD (dataaddr+oldcx),HL ;; draws the ball drawball: LD HL,(dataaddr+oldcx) LD BC,0405h ADD HL,BC EX DE,HL LD A,'o' ;; prints xor-ed the character in A in any x-y coordinate (in DE) ;; preserves BC,DE,HL printchar: PUSH BC PUSH HL PUSH DE LD BC,3C00h ; 256 less than the character set address LD H,C ; set to 0 LD L,A ; the character ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,BC ; the first byte of character LD B,8 ; number of lines printcharloop: LD A,(HL) ;; this is the the best spent byte. Doing a non conditioned CALL ;; would work anyway, but it would be slower, and we would have ;; more glithing problems AND A CALL NZ,printbyte ; it fastens a lot, and costs 1 byte! INC D INC HL DJNZ printcharloop POP DE POP HL POP BC RET ;; handles the bullet movement, drawing, and block hit ;; HL=dataaddr+shipflags ;; preserves HL handlebullet: ;; remove the bullet from the screen LD DE,(dataaddr+bulletx) PUSH DE CALL drawbullet POP DE ;; update the bullet position LD A,D SUB 8 LD D,A writeanddrawbullet: LD (dataaddr+bulletx),DE ;; check if it hits a block CALL NC,block ;; if no, redraw the bullet, otherwise reset the flag JR NC,drawbullet RES 5,(HL) RET ;; draw the bullet ;; DE=bullet coordinates ;; preserves HL,BC drawbullet: PUSH HL LD HL,0804h ADD HL,DE EX DE,HL POP HL LD A,'|' JR printchar ;; handles if the ships gets a falling letter ;; HL=dataaddr+shipflags, DE=lettercoordinates ;; preserves HL ;; we already know that the height of the letter is okay gettingletter: LD A,E ; dcx CALL normalizedist ; x-distance of letter from the center ; of ship, normalized, in A RET NC ; if that distance is greater than 11 ;; got a falling letter? If B=0, no letter is falling... LD B,(IX+droppingchar) INC B DEC B RET Z ;; delete the letter LD (IX+droppingchar),0 ;; reset all flags except the bullet shot one LD A,(HL) AND 32 LD (HL),A DJNZ no1 ; is P? ;; hide, increase, show again CALL showlives INC (IX+lives) showlives: LD DE,04F0h LD A,(IX+lives) ADD A,'0'-1 ; we show one life less than memorized JR printchar no1: DJNZ no2 ; is S? ;; decrease the upper byte of the timer by 64 or to 64, the higher LD DE,dataaddr+timer+1 LD C,64 LD A,(DE) SUB C CP C JR NC,nottoolow LD A,C nottoolow: LD (DE),A no2: DJNZ no3 ; is H? ;; open door XOR A LD (5AB7h),A SET 0,(HL) ; set the shipflags flag no3: DJNZ no4 ; is C? SET 1,(HL) ; set the shipflags flag no4: DJNZ no5 ; is E? SET 2,(HL) ; set the shipflags flag no5: DEC B ; is L? RET NZ ; return if not SET 3,(HL) ; set the shipflags flag RET ;; xor byte A in memory in coordinates x-y of screen in DE ;; preserves HL,DE