; VRAM addresses SPRATR equ $1B00 ; BASIC routines PLAY equ $73E5 ; System variables addresses CLIKSW equ $F3DB ; Keyboard click sound FORCLR equ $F3E9 ; Foreground colour RG1SAV equ $F3E0 ; V9938 register copy ; Game constants JOYUP equ 1 JOYDOWN equ 5 AUTO_OBJECT equ 3 BALL_OBJECT equ 4 UPPER_BOUND equ 8 LOWER_BOUND equ 150 RGOAL_BOUND equ 250 LGOAL_BOUND equ 4 PAD1_X1 equ 24 PAD1_X2 equ 20 PAD2_X1 equ 228 PAD2_X2 equ 232 PADCENTER equ 17 PAUSE equ 4 ; pause in seconds after a win CPUYCENTER equ 96 XREACTION equ 180 SUBREACTION equ 10 AUTO_THRESHOLD equ 4 ; internal value needed for CPU AI INITLEVEL equ 1 ; 1 slowest - 3 fastest, 4 two balls MAXLEVEL equ 4 HITSLEVEL equ 6 HITSWIN equ 10 MAGIC equ 1 ;---------------------------------------------------------- ; Main program ;---------------------------------------------------------- .BIOS .ORG $C500 xor a ld [CLIKSW],a ; Disables keyclick ld hl,$020c ld [FORCLR+1],hl ; Background dark green & border medium green call INIGRP ; initializes screen 2 ld a,[RG1SAV] or 3 ld b,a ld c,1 call WRTVDP ; 16x16 magnified sprites 16x16 MAIN_LOOP: call SHOW_MENU GAME_LOOP: call DRAW_OBJECTS call MOVE_OBJECTS call CHECK_WINNER jr nz,GAME_LOOP jr MAIN_LOOP ;------------------------------------------------------ ; ; Description: Initializes some data & shows selection screen ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ SHOW_MENU: call CLRSCR ld hl,0 ld [SCORE1P],hl ; reset both scores ld hl,$1300 ; text position ld de,MENUTXT call PRNTTEXT ld hl,INITDATA+20 ld [hl],2 ; set initially a 2P game @@WAITKEY: xor a call SNSMAT ; reads keyboard matrix bit 2,a jr z,@@SET2P bit 1,a jr nz,@@WAITKEY inc [hl] @@SET2P: call INITOBJECTS call CLRSCR ld hl,SPRITES ld de,$3800 ld bc,64 call LDIRVM ; loads sprite patterns ld hl,$0000 ; text position ld de,COURT call PRNTTEXT ; draw court & score panels call UPDATESCORE1 jp UPDATESCORE2 ;------------------------------------------------------ ; ; Description: Clears pattern table and fills color table with a fixed attribute ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ CLRSCR: ld hl,0 ld bc,$1800 ld a,0 call FILVRM ld hl,$2000 ld bc,$1800 ld a,$FC jp FILVRM ;------------------------------------------------------ ; ; Description: Check if any player is the winner ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ CHECK_WINNER: ld hl,$3C3B ; sets "WIN" string in text buffer ld [TXTBUFF],hl ld a,HITSWIN ld hl,SCORE1P cp [hl] ; has player 1 won? jr nz,@@WIN2P call UPDATESCORE1 jr @@DELAY @@WIN2P: inc hl cp [hl] ; has player 2 won? ret nz call UPDATESCORE2 @@DELAY: call CLRSPR ld hl,SND_PING call PLAY ld hl,SND_PONG call PLAY ld b,PAUSE*50 ; waits some seconds (not in MSX2+ or TR!) @@LOOP: halt djnz @@LOOP ret ;------------------------------------------------------ ; ; Description: ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ DRAW_OBJECTS: halt ld hl,SPRATR call SETWRT ld hl,SPRTAB ld a,[OBJECTS] ld b,a ; Number of objects to draw @@NXTSPRITE: push bc ld bc,$0498 otir ; dumps first 4 bytes to VRAM ld de,12 add hl,de pop bc djnz @@NXTSPRITE ret ;------------------------------------------------------ ; ; Description: ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ MOVE_OBJECTS: ld ix,SPRTAB ; sprite objects table ld a,[OBJECTS] ld b,a ; Number of objects to move @@OBJLOOP: push bc ld a,[ix+4] ; load type of object cp BALL_OBJECT jr nz,@@NOBALL call MOVE_BALL ; is a ball jr @@NXTOBJ @@NOBALL: cp AUTO_OBJECT jr nz,@@NOAUTO call MOVE_AUTO ; is a CPU player jr @@NXTOBJ @@NOAUTO: call MOVE_MAIN ; is a human player @@NXTOBJ: ld de,16 add ix,de pop bc djnz @@OBJLOOP ; increase level if minimum hits to change level have been reached ld hl,HITS ld a,HITSLEVEL cp [hl] ret nz ld [hl],0 inc hl ld a,[hl] cp MAXLEVEL ret z inc a jp SETLEVEL ;------------------------------------------------------ ; ; Description: Moves 1P(Human) paddle ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ MOVE_MAIN: cp 2 jr z,@@PLY2 ; test only stick 2 call GTSTCK ; test sticks 1 & 0 or a jr nz,@@MOVE @@PLY2: call GTSTCK or a ret z @@MOVE: push ix pop hl cp JOYUP jr nz,@@NOUP ld a,[hl] cp UPPER_BOUND ret c ld c,a ld a,[SPEED] neg jr @@UPDATEY @@NOUP: cp JOYDOWN ret nz ld a,[hl] cp LOWER_BOUND ret nc ld c,a ld a,[SPEED] @@UPDATEY: add c ; Y = Y (+/-) SPEED ld [hl],a ret ;------------------------------------------------------ ; ; Description: Moves CPU paddle ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ MOVE_AUTO: ld c,CPUYCENTER ; loads default CPU Y center ld iy,[BALLADDR] ; loads address of the ball with greatest X ld hl,CENTERCPU ld a,[hl] or a ld [hl],MAGIC jr nz,@@GETPOS ; jump if ball is not going towards paddle ld a,[MINX] cp [iy+1] ; test if ball's X is greater than the minimum x required to move CPU jr nc,@@GETPOS ; if it's not greater move padding using default Y as reference ld c,[iy] ; C = ball's Y @@GETPOS: push ix pop hl ld a,[hl] add a,PADCENTER ; center of the paddle sub c jr c,@@GODOWN cp AUTO_THRESHOLD ret c ld a,[hl] cp UPPER_BOUND ret c ld c,a ld a,[SPEED] neg jr @@UPDATEY @@GODOWN: neg cp AUTO_THRESHOLD ret c ld a,[hl] cp LOWER_BOUND ret nc ld c,a ld a,[SPEED] @@UPDATEY: add c ld [hl],a ret ;------------------------------------------------------ ; ; Description: Check if ball has hit a paddle ; In: A=Ball X coordinate, HL = GREATESTX address ; Out: ; Modifies: ; ;------------------------------------------------------ CHECK_HIT: cp PAD2_X1 jr c,@@CHECKPAD1 cp PAD2_X2 ret nc ld [hl],0 ; HL still holds GREATESTX address ld hl,SND_PONG ; the sound is PONG ld a,[SPRTAB+16] ; we must compare with paddle 2 Y jr @@PADBOUNDS @@CHECKPAD1: cp PAD1_X1 ret nc cp PAD1_X2 ret c ld hl,SND_PING ; the sound is PING ld a,[SPRTAB] ; we must compare with paddle 1 Y @@PADBOUNDS: ld c,a ; test if ball's Y is between paddle edges ld a,[ix] add 2 ; align with ball's center sub c ; substracts ball's Y coordinate cp 32 ret nc push af and $FC rra ; (POSY/4)*2 ld de,DIRTABLE ; WARNING!!, low byte can't be greater than F0 add a,e ld e,a ld a,[de] ld [ix+7],a ; X speed counter inc de ld a,[de] ld [ix+8],a ; Y speed counter call PLAY pop af ld hl,HITS inc [hl] cp PADCENTER ; is over/under paddle's center? ld a,[ix+6] jr nc,@@ALWPOSITIVE bit 7,a jr nz,@@INVX jr @@NEG @@ALWPOSITIVE: bit 7,a jr z,@@INVX @@NEG: neg ld [ix+6],a ; inverts Y increment @@INVX: ld a,[ix+5] neg ld [ix+5],a ; inverts X increment ld [ix+9],1 ; finish X temporal speed counter now ret ;------------------------------------------------------ ; ; Description: ; In: ; Out: ; Modifies: ; ;------------------------------------------------------ MOVE_BALL: ; Y(+0),X(+1),PATTERN(+2),COLOR(+3), ; TYPE(+4),BALLINCX(+5),BALLINCY(+6), ; SPDCOUNTX(+7),SPDCOUNTY(+8),SPDCTEMPX(+9),SPDCTEMPY(+10), ; 0(+11),0(+12),0(+13),0(+14),0(+15) bit 7,[ix+5] jr nz,@@NOCENTERCPU ; test if ball is going towards CPU paddle (positive increment) ld hl,CENTERCPU ld [hl],0 @@NOCENTERCPU: dec [ix+9] ; decrement X movement counter jr nz,@@MOVEY ld a,[ix+7] ld [ix+9],a ; restores X movement counter ld a,[SPEED] bit 7,[ix+5] jr nz,@@GOINGLEFT ; test if ball is going towards CPU paddle (positive increment) ld hl,GREATESTX add [ix+1] ; add coordinate X cp [hl] jr c,@@CHKGOAL ; is the CPU's approaching nearest ball? ld [hl],a ; if so, saves his X coordinate ld [BALLADDR],ix ; and his attribute address jr @@CHKGOAL @@GOINGLEFT: neg add [ix+1] ; add coordinate X @@CHKGOAL: cp RGOAL_BOUND ; is beyond right goal line? jr nc,@@SCORE1 cp LGOAL_BOUND ; is beyond left goal line? jr c,@@SCORE2 ld [ix+1],a ; saves X coordinate call CHECK_HIT @@MOVEY: dec [ix+10] ; decrement Y movement counter ret nz ld a,[ix+8] ld [ix+10],a ; restores Y movement counter ld a,[SPEED] bit 7,[ix+6] ; test if Y increment is negative jr z,@@NONEGY neg ; so speed is negative @@NONEGY: add [ix] ; add coordinate Y ld [ix],a ; saves updated Y coordinate cp UPPER_BOUND ; is beyond upper bound? jr c,@@BOUNDY cp LOWER_BOUND+33 ; is beyond lower bound? ret c @@BOUNDY: ld a,[ix+6] neg ; inverts Y increment ld [ix+6],a ret @@SCORE1: ld hl,SCORE1P inc [hl] ; 1P scores 1 point call NUM2TXT call UPDATESCORE1 jr INITOBJECTS @@SCORE2: ld hl,SCORE2P inc [hl] ; 2P scores 1 point call NUM2TXT call UPDATESCORE2 ;--- DON'T MOVE THE FOLLOWING SUBROUTINE FROM HERE!!--- ; ; Description: ; In: ; Out: ; Modifies: ; ;------------------------------------------------------- INITOBJECTS: ld a,208 ; always hide the fourth ball ld hl,SPRATR+12 call WRTVRM ld hl,INITDATA ld de,SPRTAB ld bc,(4*16)+7 ldir ld a,INITLEVEL ;--- DON'T MOVE THE FOLLOWING SUBROUTINE FROM HERE!!--- ; ; Description: Sets level parameters ; In: A = level ; Out: ; Modifies: ; ;------------------------------------------------------ SETLEVEL: bit 2,a ; level 4? jr z,@@NOADDBALL ld a,1 ld hl,OBJECTS ld [hl],4 ; fix a maximum of 4 objects to move/draw @@NOADDBALL: ld [LEVEL],a ld b,a inc a ld [SPEED],a ; SPEED is always LEVEL+1 (2-4) ld a,XREACTION @@SUBX: sub SUBREACTION djnz @@SUBX ld [MINX],a ret ;------------------------------------------------------ ; ; Description: Translates a number between 0-19 to ascii ; In: HL = score address ; Out: ; Modifies: ; ;------------------------------------------------------ NUM2TXT: ld de,$3031 ld a,[hl] sub 10 jr nc,@@GT10 add 10 dec e @@GT10: add a,d ld d,a ld [TXTBUFF],de ld hl,SND_POINT call PLAY ret ;------------------------------------------------------ ; Routines to print scores ;------------------------------------------------------ ; 1P routine UPDATESCORE1: ld hl,$0138 ; erase position ld de,$030F ; text position jr UPDATESCORE ; 2P routine UPDATESCORE2: ld hl,$0188 ; erase position ld de,$0323 ; text position UPDATESCORE: xor a ld bc,64 call FILVRM ; erases score panel inc h ld bc,64 call FILVRM inc h ld bc,64 call FILVRM inc h ld bc,64 call FILVRM ex de,hl ld de,TXTBUFF PRNTTEXT: ld a,[de] or a ret z cp 32 ; is a space? jr z,@@SPACE cp 13 ; is a newline? jr nz,@@ISCHAR ld a,h add a,6 ; next Y row ld h,a ld l,0 jr @@NXTCHR @@ISCHAR: push hl push de call PRNTBIGCHAR pop de pop hl @@SPACE: ld a,l add a,7 ; next X column ld l,a @@NXTCHR: inc de jr PRNTTEXT ;------------------------------------------------------ ; ; Description: ; In: H=Y(0-47),L=X(0-63) ; Out: ; Modifies: ; ;------------------------------------------------------ PRNTBIGCHAR: push hl ld de,FONTCHR-48*6 ld h,0 ld l,a push hl add hl,hl add hl,hl ; *4 add hl,de ex de,hl pop hl add hl,hl ; *2 add hl,de ex de,hl ; DE = address of char pattern pop hl ld b,6 ; every char has an height of 6 lines @@NXTLINE: push hl ld a,[de] ; load char byte pattern rlca call c,SETPIX ; if bit is on, draws pixel inc l ; increases X coordinate rlca call c,SETPIX inc l rlca call c,SETPIX inc l rlca call c,SETPIX inc l rlca call c,SETPIX inc l rlca call c,SETPIX inc l rlca call c,SETPIX inc l rlca call c,SETPIX pop hl inc de inc h ; increases Y coordinate djnz @@NXTLINE ret ;------------------------------------------------------ ; ; Description: ; In: ; Out: ; Modifies: Nothing ; ;------------------------------------------------------ SETPIX: push hl push af ld a,$F0 ; starting pattern to OR bit 0,l jr z,@@NOODDX cpl ; inverts pattern res 0,l @@NOODDX: ld c,a ; C = pattern to OR with background ld a,l add a,a add a,a bit 0,h jr z,@@NOODDY add a,4 @@NOODDY: ld l,a ; HL=Y*256+X*8 ld a,h ccf rra ld h,a ; draws the 4 scans of pixel call RDVRM or c call WRTVRM inc hl call RDVRM or c call WRTVRM inc hl call RDVRM or c call WRTVRM inc hl call RDVRM or c call WRTVRM pop af pop hl ret ;---------------------------------------------------------- ; Data ;---------------------------------------------------------- FONTCHR: DB $7E,$42,$42,$42,$42,$7E DB $02,$02,$02,$02,$02,$02 DB $7E,$02,$7E,$40,$40,$7E DB $7E,$02,$7E,$02,$02,$7E DB $42,$42,$7E,$02,$02,$02 DB $7E,$40,$7E,$02,$02,$7E DB $40,$40,$7E,$42,$42,$7E DB $7E,$02,$02,$02,$02,$02 DB $7E,$42,$7E,$42,$42,$7E DB $7E,$42,$7E,$02,$02,$02 DB $7E,$42,$42,$7E,$40,$40 ; P 3A DB $8A,$8A,$8A,$AA,$DA,$8A ; WI 3B DB $44,$64,$54,$4C,$44,$44 ; N 3C DB $4C,$AA,$AA,$AC,$AA,$4A ; OR 3D DB $3C,$42,$02,$1C,$00,$18 ; ? 3E DB $FF,$00,$00,$00,$00,$00 ; - 3F DB $00,$00,$00,$00,$00,$FF ; - 40 DB $00,$08,$08,$08,$08,$00 ; | 41 DB $FF,$08,$08,$08,$08,$00 ; -|- 42 DB $00,$08,$08,$08,$08,$FF ; _|_ 43 SPRITES: DB 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB $C0,$C0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; Y(+0),X(+1),PATTERN(+2),COLOR(+3), ; TYPE(+4),BALLINCX(+5),BALLINCY(+6), ; SPDCOUNTX(+7),SPDCOUNTY(+8),SPDCTEMPX(+9),SPDCTEMPY(+10), ; 0(+11),0(+12),0(+13),0(+14),0(+15) ; TYPE => ; 0:VOID ; 1:1P ; 2:2P ; 3:CPU ; 4:BALL INITDATA: DB 80,6,0,15,1,0,0,0,0,0,0,0,0,0,0,0 DB 80,220,0,15,2,0,0,0,0,0,0,0,0,0,0,0 DB 96,126,4,15,4,-1,1,1,2,1,2,0,0,0,0,0 DB 80,80,4,15,4,1,1,1,1,1,1,0,0,0,0,0 DB $30,$30,0 ; TXTBUFF (INITIALLY "00") DB 0 ; GREATESTX DB 3 ; OBJECTS (INITIAL VALUE : 3) DB 0 ; HITS DB MAGIC ; CENTERCPU DIRTABLE: DB 2,1,1,1,1,2,1,2,1,2,1,2,1,1,2,1 MENUTXT: DB $31,$3A,$20,$3D,$20,$32,$3A,$3E,0 ; 1P OR 2P? COURT: DB $3F,$3F,$3F,$3F,$42,$3F,$3F,$3F,$3F,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $20,$20,$20,$20,$41,$20,$20,$20,$20,13 DB $40,$40,$40,$40,$43,$40,$40,$40,$40,0 SND_PING: DB $22,"T255B",$22,0 SND_PONG: DB $22,"C",$22,0 SND_POINT: DB $22,"GA",$22,0 ;---------------------------------------------------------- ; Variables ;---------------------------------------------------------- .ORG $CF00 SPRTAB: DS 4*16 TXTBUFF: DS 3 GREATESTX: DS 1 OBJECTS: DS 1 HITS: DS 1 LEVEL: DS 1 CENTERCPU: DS 1 BALLADDR: DS 2 SPEED: DS 1 MINX: DS 1 SCORE1P: DS 1 SCORE2P: DS 1