;; spectris, 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 ;; 128k preferred for music, at least 48k for the reversed 'R' dataaddr: .equ 6400h level: .equ 0 score: .equ 1 tonextlevel .equ 2 ; lines still needed to go to next level keypressed .equ 3 ; key pressed at last time frame nextpiece .equ 4 ; self explanatory musicspeed .equ 6 ; <>0 -> music faster soundleft .equ 20 ; time frames of sound left-1 soundaddr .equ 21 ; address of next note, 2 bytes uppintaddr .equ 68h ; upper part of the interrupt address mdo: .equ 3421 mdod: .equ 3228 mreb: .equ 3228 mre: .equ 3047 mred: .equ 2876 mmib: .equ 2876 mmi: .equ 2715 mfa: .equ 2562 mfad: .equ 2419 msolb: .equ 2419 msol: .equ 2283 msold: .equ 2155 mlab: .equ 2155 mla: .equ 2034 mlad: .equ 1920 msib: .equ 1920 msi: .equ 1892 ayctrl .equ 65533 ; AY register port aydata .equ 49149 ; AY data port droppingmem: .equ 7800h ; pit memory, 4 blocks uppdroppingmem: .equ 78h randsub .equ 7C00h ; address of random subroutine currpiece .equ 7000h ; base address of pieces, 7 blocks uppcurrpiece .equ 70h box .equ 6800h ; address of box after decompression pieces .equ 6808h ; " of pieces after first decompression music .equ 6840h ; " of music after decompression levellines .equ 5 ; number of lines between levels stringink .equ 16 ; ASCII code of ink stringpaper .equ 17 ; paper stringflash .equ 18 ; etc stringbright .equ 19 ; etc stringat .equ 22 ; ASCII code of AT helpaddr .equ 16384+6144+342 ; where help block is memorized beginline: .ORG 23755 ;; 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 ;; box, pieces (first step), music decompression LD HL,compressed LD DE,box-1 decomprmusicloop: INC DE decomprmusicloop2: LD A,(HL) INC HL LD (DE),A SUB 200 ; A>200 indicates a previous block of A-200 ; bytes JR Z,initmusic ; 200 -> end JR C,decomprmusicloop LD C,A ; BC=A-200 LD B,0 PUSH HL LD L,(HL) ; (HL) lower byte of the block to copy LD H,D LDIR ; copy POP HL INC HL JR decomprmusicloop2 ; repeat, but not incrementing DE initmusic: ;; interrupt initialization DI IM 2 LD A,D ; uppintaddr=upper byte music LD I,A LD HL,intcall LD (uppintaddr*256+255),HL ; memorize the interrupt address ;; invert the "R" LD HL,65505 ; 2nd byte of UDG 'R' LD (HL),62 LD L,228 ; HL=5th byte LD (HL),62 INC HL ; sixth LD (HL),34 ;; "decompress" the pieces (2nd part) LD BC,currpiece ; destination LD E,8 ; DE=pieces newpiecegen: LD A,(DE) ; get two bytes from (DE) to HL LD H,A INC DE LD A,(DE) LD L,A INC DE loopgenpiece: XOR A ADD HL,HL ; shift left JR NC,genpieceemptyspace ; if NC, we write 0 LD A,B SUB uppcurrpiece-1 ; otherwise, a value that depends on the piece genpieceemptyspace: LD (BC),A INC BC BIT 4,C ; is C%64=16? JR Z,loopgenpiece ; if not, continue LD A,C ; here we sum 48 to BC ADD A,47 LD C,A INC BC BIT 6,E ; have we written all pieces? JR Z,newpiecegen ; if not, repeat LD HL,pressenterstr; write "press enter..." JR gotoprintstring gameover: ;; stop music DI CALL stopmusic ;; sound effect XOR A LD D,16 LD E,H beeploop6: OUT (254),A INC A XOR 16 LD B,A beeploop5: DJNZ beeploop5 DEC E JR NZ,beeploop6 DEC D JR NZ,beeploop6 ;; write "game over", "press enter..." LD HL,gameoverstr gotoprintstring: CALL printstring ; the call to print ;; wait for ENTER pressenterloop: LD A,0BFh IN A,(254) RRCA JR C,pressenterloop ;; start music and initialize the new game CALL restartmusic EI CALL initializegame newpiece: LD E,22 CALL testlines LD A,H SUB uppdroppingmem LD (IX+musicspeed),A ;; generate the new piece and test if there is space for it CALL initializepiece JR NZ,gameover ; if not... sorry! ;; calculates the number of 1/50th of seconds between vertical ;; movements CALL calculatetime ;; main loop, repeated until a piece cannot go down any more loop: ;; wait for an interrupt and update the screen CALL insertpiece HALT CALL updatevideo CALL insertpiece ;; get the joystick movements LD A,0EFh IN A,(254) ;; if pressed down, the piece falls down BIT 2,A JR NZ,notdown LD A,31 ; no key pressed LD B,1 notdown: ;; is it time to move the piece down? DEC B CALL Z,falldown ;; save the new key pressed in IX+keypressed, and calculate ;; if the key has just been pressed ;; bits in A: =1: consider, =0: discard LD C,(IX+keypressed) LD (IX+keypressed),A CPL AND C ;; save the current piece info - it may change! PUSH BC LD B,E LD C,L ;; if right, move the piece right BIT 3,A ; right JR Z,notright INC L notright: ;; if left, move the piece left BIT 4,A ; left JR Z,notleft DEC L notleft: ;; if fire, piece rotates RRCA JR NC,notrotate LD A,E ADD A,64 LD E,A notrotate: ;; test the new piece/piece position, if not valid, just get all ;; previous values CALL testpiece JR Z,validmove LD E,B LD L,C LD (IX+keypressed),31 validmove: POP BC ;; the following test can be failed only if the block is falling ;; if the test is successful, repeat CALL testpiece JR Z,loop ;; the tile cannot stay here, so we move the tile up again ;; and insert it definitely landeddown: LD BC,-32 ADD HL,BC CALL insertpiece ;; find a row that has 32 values != 0 checklineloop2: LD E,31 CALL testlines JR NC,newpiece ; if no line, generate a new piece ;; highlight the row making a noise PUSH HL LD BC,-((uppdroppingmem-88)*256+26) ADD HL,BC highlightnext: SET 6,(HL) LD A,7 LD C,35 beeploop4: OUT (254),A XOR 16 LD B,80 beeploop3: DJNZ beeploop3 DEC C JR NZ,beeploop4 INC HL LD A,L AND 15 JR NZ,highlightnext ;; increase the score and, if necessary, the level INC (IX+score) CALL lineremoved POP HL ;; remove the line and make the noise DEC HL LD BC,-32 LD E,L LD D,H ADD HL,BC lddloop: LDD LD A,H CP uppdroppingmem JR NC,lddloop CALL updatevideo LD A,7 LD B,190 beeploop2: PUSH BC OUT (254),A XOR 16 beeploop1: DJNZ beeploop1 POP BC DJNZ beeploop2 ;; check next row JR checklineloop2 ;; move the piece down falldown: LD C,32 ; B=0 ADD HL,BC ;; calculate the dropping time period at current level calculatetime: PUSH AF LD A,21 SUB (IX+level) LD B,A POP AF RET ;; copy all bytes in dropping mem that have bit 7=0 in the ;; attribute area updatevideo: PUSH HL LD HL,droppingmem loopupdatevideo: LD A,(HL) BIT 7,A JR NZ,skipupdatevideo RES 5,H ; HL=attribute on screen LD (HL),A SET 5,H skipupdatevideo: INC HL LD A,H CP 123 JR NZ,loopupdatevideo POP HL RET initializepiece: ;; choose a dropping piece CALL getnewpiece ;; test if the piece fits. If not, gameover LD HL,droppingmem+9; initial position of the piece ;; DE=address of tile ;; HL=coordinates of piece testpiece: CALL insertpiece JR insertpiece initializegame: ;; draw full screen of "invisible" boxes LD HL,16384 LD B,24 LD DE,box drawboxloop2: LD A,(DE) drawboxloop1: LD (HL),A INC L JR NZ,drawboxloop1 INC H INC E RES 3,E DJNZ drawboxloop2 ;; create the pit and blue screen (with the space for next tile) EX DE,HL ; DE=first attribute byte LD HL,droppingmem LD (HL),255 filluploop: BIT 0,D JR Z,writeblue LD A,E SUB 86 AND 156 JR Z,writecolor writeblue: LD A,9 writecolor: LD (DE),A ; blue screen byte INC DE LD A,L AND 31 SUB 6 CP 10 JR NC,nonzero INC (HL) nonzero: INC HL LD (HL),255 LD A,H CP uppdroppingmem+3 JR Z,nonzero JR C,filluploop ;; copy the RND math subroutine from ROM to RAM, and append RET. EX DE,HL ; DE=randsub LD HL,025FDh LD BC,40 LDIR LD A,0C9h ; RET LD (DE),A ;; set the level and line number (level will be incremented soon) LD (dataaddr+level),BC ; both are 0 ;; set the value of lines to next level to 1 (removed soon) LD (IX+tonextlevel),1 ;; finish to draw the screen CALL lineremoved ;; choose a new dropping piece, and put it as the next piece getnewpiece2: ;; get a random number from 0 to 6 inclusive LD A,7 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 ADD A,uppcurrpiece LD (IX+nextpiece),A ;; show next piece showhelp: LD D,(IX+nextpiece) LD E,0 LD HL,helpaddr ;; insert piece in memory or screen ;; DE=address of piece ;; HL=coordinates of piece (memory address or screen address) ;; Z if each part of the piece "deletes" one already present insertpiece: XOR A PUSH HL PUSH DE PUSH BC LD C,4 insertpieceloop: LD B,4 insertpieceloop2: PUSH AF LD A,(DE) AND A JR Z,testpassed XOR (HL) LD (HL),A JR Z,testpassed POP AF INC A PUSH AF testpassed: POP AF INC DE INC HL DJNZ insertpieceloop2 PUSH BC LD C,28 ; B is 0 ADD HL,BC POP BC DEC C JR NZ,insertpieceloop POP BC POP DE POP HL AND A RET testlines: LD HL,droppingmem checklineloop4: LD A,H CP uppdroppingmem+3; have we tested all rows RET Z ; if yes, return LD BC,2000h checklineloop3: LD A,(HL) AND A JR Z,afterincrementing2 INC C afterincrementing2: INC HL DJNZ checklineloop3 LD A,E CP C JR NC,checklineloop4; if not, go on with another row RET ;; print the string in HL printstring: LD A,(HL) INC HL CP 255 RET Z RST 10h JR printstring ;; write the string "score:n",and increment the level if necessary lineremoved: LD HL,scorestr LD A,(dataaddr+score) CALL printstringandnumber LD HL,dataaddr+tonextlevel DEC (HL) ; lines left for this level? RET NZ ; if yes, return ;; increment the level and print "level:n" LD (HL),levellines INC (IX+level) LD HL,levelstr LD A,(dataaddr+level) ;; write the string in HL and number in A printstringandnumber PUSH AF CALL printstring POP AF CALL 2D28h ; A to FP stack JP 2DE3h ; print number in FP stack ;; "next" and "level" levelstr: .byte stringpaper .byte 1 .byte stringink .byte 7 .byte stringat .byte 9 .byte 22 .text "NEXT" .byte stringat .byte 2 .byte 20 .byte stringink .byte 6 .text "LEVEL:" .byte stringink .byte 4 .byte 255 ;; "lines" scorestr: .byte stringat .byte 4 .byte 20 .byte stringpaper .byte 1 .byte stringink .byte 6 .text "LINES:" .byte stringink .byte 5 .byte 255 ;; "game over" gameoverstr: .byte stringat .byte 10 .byte 11 .byte stringpaper .byte 2 .byte stringink .byte 6 .byte stringflash .byte 1 .text "GAME OVE" .byte 161 .byte stringflash .byte 0 ;; "press ENTER..." pressenterstr: .byte stringat .byte 14 .byte 6 .byte stringpaper .byte 0 .byte stringink .byte 7 .text "P" .byte 161 .text "ESS ENTE" .byte 161 .text " TO STA" .byte 161 .text "T" .byte 255 ;; move next piece as the current piece (goes to DE) ;; and generates the new "next piece" getnewpiece: CALL showhelp PUSH DE ; save the block loaded in showhelp CALL getnewpiece2 POP DE RET ;; write note in HL, at registers D and D+1. At the end D=D+2 writenote: LD E,L CALL audiowrite INC D LD E,H CALL audiowrite INC D RET stopmusic: LD DE,073Fh ; sound off JR audiowrite restartmusic: LD HL,music ;; main loop for music generation soundloop: LD A,(HL) ; next note AND A ; is it the end? JR Z,restartmusic ; if at the end, restart it INC HL LD (dataaddr+soundaddr),HL ; save the new address LD E,A ;; get the lenght of the note, that is the 4 lower bits times 6 AND 15 ; lower part, the lenght ADD A,A LD C,A ADD A,A LD B,(IX+musicspeed) INC B DEC B JR Z,nottwice ADD A,C ; 6 times nottwice: LD (IX+soundleft),A ;; get the note, from the 4 upper bytes LD A,E AND 240 ; if 0, it is a pause RET Z RRCA RRCA RRCA LD HL,tonetable-2 ; otherwise, the note value is in this table LD C,A LD B,0 ADD HL,BC LD E,(HL) ; low (fine) pitch INC HL LD D,(HL) ; high (course) pitch EX DE,HL LD D,B ; channel 1 (B=0) CALL writenote LD BC,10 ADD HL,BC CALL writenote ; channel 2 LD DE,080Ch ; channel 1 volume on CALL audiowrite INC D CALL audiowrite ; channel 2 volume on LD DE,073Ch ; channels choice ;; write byte E in AY-register D audiowrite: LD BC,ayctrl ; select control port OUT (C),D ; send specified value LD BC,aydata ; select data port OUT (C),E ; send specified value RET ;; interrupt intcall: PUSH HL PUSH DE PUSH BC PUSH AF ;; if time over, we get a new note DEC (IX+soundleft) LD HL,(dataaddr+soundaddr) CALL Z,soundloop ;; if yust a time frame left, turn music off LD A,(IX+soundleft) DEC A CALL Z,stopmusic POP AF POP BC POP DE POP HL EI RET ;; table of notes tonetable: .word mdo/8 .word mre/8 .word mmi/8 .word mfa/8 .word msol/8 .word mla/8 .word msib/8 .word mdo/16 .word mre/16 .word msib/4 .word msi/4 .word mdod/8 ;; compressed music compressed: .byte 0 .byte 127 .byte 126 .byte 204 .byte 1 .byte 85 .byte 102 .byte 0 .byte 206 .byte 8 .byte 68 .byte 68 .byte 15 .byte 205 .byte 15 .byte 70 .byte 32 .byte 6 .byte 192 .byte 204 .byte 24 .byte 38 .byte 64 .byte 198 .byte 0 .byte 204 .byte 32 .byte 68 .byte 96 .byte 14 .byte 128 .byte 196 .byte 64 .byte 46 .byte 0 .byte 34 .byte 96 .byte 71 .byte 0 .byte 50 .byte 32 .byte 7 .byte 16 .byte 70 .byte 64 .byte 14 .byte 64 .byte 76 .byte 64 .byte 78 .byte 0 .byte 19 .byte 17 .byte 18 .byte 66 .byte 98 .byte 82 .byte 66 .byte 50 .byte 35 .byte 33 .byte 34 .byte 82 .byte 114 .byte 203 .byte 68 .byte 51 .byte 49 .byte 50 .byte 98 .byte 130 .byte 204 .byte 76 .byte 38 .byte 6 .byte 18 .byte 224 .byte 64 .byte 116 .byte 100 .byte 6 .byte 130 .byte 146 .byte 114 .byte 10 .byte 146 .byte 130 .byte 98 .byte 10 .byte 98 .byte 116 .byte 2 .byte 98 .byte 114 .byte 130 .byte 146 .byte 130 .byte 108 .byte 2 .byte 213 .byte 119 .byte 98 .byte 130 .byte 98 .byte 72 .byte 4 .byte 20 .byte 42 .byte 66 .byte 50 .byte 18 .byte 42 .byte 98 .byte 98 .byte 82 .byte 82 .byte 66 .byte 66 .byte 50 .byte 50 .byte 34 .byte 34 .byte 18 .byte 168 .byte 4 .byte 36 .byte 58 .byte 82 .byte 66 .byte 34 .byte 58 .byte 114 .byte 114 .byte 209 .byte 161 .byte 24 .byte 213 .byte 154 .byte 82 .byte 203 .byte 161 .byte 66 .byte 114 .byte 38 .byte 4 .byte 68 .byte 86 .byte 66 .byte 86 .byte 66 .byte 26 .byte 178 .byte 18 .byte 194 .byte 34 .byte 162 .byte 34 .byte 203 .byte 157 .byte 50 .byte 82 .byte 74 .byte 2 .byte 20 .byte 0 .byte 200 .byte 13 ; end BASIC line endline: .end