;Cpmtris v1.0 - a tetris clone for Z80-based CP/M machines. ;Copyright (C) 1996 Russell Marks. See 'README' for license details. ;conversion of my OPL tetris for the psion series 3 via my ZX81 tetris ; (both also free and available on request) org 0100h jp indirect defb 0 ;cpmtris is, in this version, patched to run on a 8 MHz ;80x24 VT100 machine ;104h - number of columns ; must have at least 50 columns ; you don't really need to set this; it isn't used for anything. cols: defb 80 ;105h - number of lines ; must have at least 10 lines lines: defb 24 ;106h - 10*Mhz of your Z80. ; e.g. on a 3.5MHz Spectrum +3 it would be 35. ; most CP/M machines have speeds around the 3 to 4 MHz mark, ; so if you don't know the speed of your box just use 35. mhz10: defb 80 ;107h - terminal type ;you can roll your own terminal routines, but there's hard-coded ;support for a few terminals which saves you the effort. they are: ; type 1 - VT100 ; type 2 - VT52, PCW, Spectrum +3, etc. ; type 3 - ZCN ; type 4 - VC404 (volker-craig) ;set this to zero if you're writing your own clear screen and cursor ;move routines. ;note that the terminal type does not influence the number of lines, ;columns and the 'mhz10' setting - all those must still be set (above). termtype: defb 1 ;108h-10fh - reserved (currently unused) defb 0,0,0,0,0,0,0,0 ;the routines below are allowed to corrupt af/bc/de/hl, ; but other registers must remain intact. You can use these routines ; from them: ;the routine at 180h prints the num. in de in decimal, preserving all regs. ;the routine at 183h prints the char in a, preserving all regs. ;110h - clear screen ;this example is for a VT100. hackcls: ld de,mcls ld c,9 jp 5 mcls: defb 27,'[H',27,'[J$' defb 0 ;120h - move to (x,y), where (0,0) is top-left ;entry: (b,c)=(x,y) ;this example is for a VT100. hackmove: ld a,27 call 0183h ld a,'[' call 0183h ld e,c inc e ld d,0 call 0180h ld a,';' call 0183h ld e,b inc e ;d still 0 call 0180h ld a,'H' jp 0183h ;140h - extra patch space, in case the above isn't enough ;all the space up to 17fh is usable defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 ;180h - print de as decimal number (for VT100 cursor move) ;saves af/bc/de/hl jp dispdec2 ;183h - print char in a ;saves all regs jp putchar ;we jump to here first, so that the 1st jump always stays the same ; no matter what hacking is done in future, and any binary patches ; made by overwriting the first record still work. indirect: jp main dispdec2: push af push bc push de push hl call dispdec pop hl pop de pop bc pop af ret ;the shapes shps: defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 1,0,0,0,0,1,1,0,1,0,0,0,0,1,1,0 defb 1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0 defb 0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 0,1,0,0,1,1,0,0,0,1,0,0,1,1,0,0 defb 1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0 defb 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 defb 1,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0 defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 defb 0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0 defb 0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0 defb 1,1,1,0,0,1,1,0,1,1,1,0,1,1,0,0 defb 0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 0,1,1,0,1,0,0,0,0,1,0,0,0,0,0,0 defb 0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0 defb 0,1,0,0,0,0,0,0,1,1,0,0,0,0,1,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 defb 1,1,0,0,0,0,0,0,0,1,0,0,0,0,1,0 defb 0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0 defb 0,1,0,0,1,0,0,0,0,1,1,0,0,0,0,0 defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;map of the 10x21 screen (and some spare) scrn: defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 defw 0,0,0,0,0, 0,0,0,0,0 mtitle: defb 'Cpmtris by RJM 96',0 mkeys1: defb 'K - left, L - right',0 mkeys2: defb 'S - rotate, D - drop',0 mgamovr: defb '*** GAME OVER! ***',0 mlines: defb 'Lines: 0',0 mprskey: defb 'Again? (y/n)',0 mbotline: defb '--==--==--==--==--==',0 ybot equ 20 ;bottom of scrn timout equ 20 ;timeout in frames before block drops one place shp: defb 0 ;current shape frm: defb 0 ;current frame (rotation) xy: y: defb 0 x: defb 0 timc: defb 0 tmp: defb 0 linesdun: defw 0 xstatpos: defb 30 ;terminal-related stuff compact: defb 0 ;if 1 use doubled-up pixels (" '.:") pause: defw 0 ;for ZCN we make a new font which is basically a copy of ZCN's but ;with some UDGs on the end. zcnfont equ 0eac0h newzcnfont equ 08000h zcnchars: defb 128,129,130,131 zcncdat: defb 000h,022h,000h,000h,022h,000h defb 000h,022h,000h,077h,0ffh,0ffh defb 077h,0ffh,0ffh,000h,022h,000h defb 077h,0ffh,0ffh,077h,0ffh,0ffh main: ;if on ZCN, we'll be using the 'compact' 2-pixel-per-char mode. ;since we can (admittedly fairly kludgely) hack up a few udgs, do ;that. (looks a lot nicer.) ld a,(066h) cp 0f7h call z,zcninit call srand call chkargs call playgame jr main chkargs: ld a,(05dh) cp 32 ret z ;set terminal type if 0<=a<=3 sub 48 ret c cp 5 ret nc ld (termtype),a cp 3 jr z,carg1 ;set to 24 lines if termtype specified and not ZCN type ld a,24 ld (lines),a carg1: ld a,(06dh) cp 32 ret z ;set mhz10 ld hl,06dh call atoi ld a,l ld (mhz10),a ret ld d,h ld e,l call dispdec ld a,' ' call putchar ld a,'$' ld (070h),a ld de,06dh ld c,9 call 5 jp 0 playgame: ld a,(lines) cp 20 jr nc,notcpt ld a,1 ld (compact),a notcpt: ;work out frame pause ld a,(mhz10) ld h,0 ld l,a ld de,72 call multiply ld (pause),hl call cls ;show title/keys ld a,(xstatpos) ld b,a ld c,0 ld hl,mtitle call mvaddstr ld a,(lines) cp 20 jr c,nokeyhlp ld a,(xstatpos) ld b,a ld c,18 ld hl,mkeys1 call mvaddstr ld a,(xstatpos) ld b,a ld c,20 ld hl,mkeys2 call mvaddstr nokeyhlp: call initscr ld hl,0 ld (linesdun),hl ld a,(xstatpos) ld b,a ld c,7 ld hl,mlines call mvaddstr blklp: ;gen new blk ld hl,7 call rand ld a,l ld (shp),a call rand16 ld a,l and 3 ld (frm),a xor a ld (y),a ld a,4 ld (x),a ld a,timout ld (timc),a ;if hit now, game over ld bc,(xy) ld a,(frm) call hitshp jp c,endgam ld a,1 call drawshp ;frame loop hitlp: call inkey cp 'k' ;left (k) jr nz,skip1 xor a call drawshp ld bc,(xy) dec b ld a,(frm) call hitshp jr c,skip0a ld a,(x) dec a ld (x),a skip0a: ld a,1 call drawshp jp skipn skip1: cp 'l' ;right (l) jr nz,skip2 xor a call drawshp ld bc,(xy) inc b ld a,(frm) call hitshp jr c,skip1a ld a,(x) inc a ld (x),a skip1a: ld a,1 call drawshp jp skipn skip2: cp 's' ;rot (s) jr nz,skip3 ld a,(frm) inc a and 3 ld (tmp),a xor a call drawshp ld bc,(xy) ld a,(tmp) call hitshp jr c,skip2a ld a,(tmp) ld (frm),a skip2a: ld a,1 call drawshp jp skipn skip3: cp 'd' ;drop (d) jr nz,skip4 xor a call drawshp droplp: ld bc,(xy) inc c ld a,(frm) call hitshp jr c,skip3a ld a,(y) inc a ld (y),a jr droplp skip3a: ld a,1 call drawshp jr endhit skip4: cp 27 jp z,abortgam ld a,(timc) dec a ld (timc),a jr nz,skipn xor a call drawshp ld bc,(xy) inc c ld a,(frm) call hitshp jr nc,skip5 ld a,1 call drawshp jr endhit skip5: ld a,(y) inc a ld (y),a ld a,timout ld (timc),a ld a,1 call drawshp skipn: ;pause between frames ld bc,(pause) pauselp: dec bc ld a,b or c jr nz,pauselp jp hitlp endhit: call dolines ld a,(xstatpos) add a,7 ld b,a ld c,7 call move ld de,(linesdun) call dispdec jp blklp endgam: ;draw last hit block. ;this is messy, but if you get a 1x4 block it looks ;incredibly unfair otherwise. ld a,1 call drawshp abortgam: ld a,(xstatpos) ld b,a ld c,5 ld hl,mgamovr call mvaddstr ld a,(lines) ld e,a ld a,15 cp e jr c,agam1 ld a,3 agam1: ld c,a dec c ld a,(xstatpos) ld b,a ld hl,mprskey call mvaddstr call stashcur ;wait for no key mpklp: call inkey and a jr nz,mpklp ;wait for key mpklp2: call inkey and a jr z,mpklp2 ;if key was y, ret cp 'y' ret z ;if key wasn't n, repeat cp 'n' jr nz,mpklp2 ;otherwise, quit... bombout: call cls jp 0 ;draw/undraw blk on 10x20 scrn ;entry: (b,c)=(x,y), a=1 to draw, 0 to undraw drawblk: ;do scrn() ld h,0 ld l,c call mul10 ld d,0 ld e,b add hl,de ld de,scrn add hl,de ld (hl),a ld e,a ld a,(compact) and a ld a,e jr nz,compblk ;actually draw push af rlc b call move pop af and a jr z,dblkskip ;think hard before changing these, they're also used by the ; scrolling stuff. at the very least, it's important to ; preserve the z flag. dblkon: ld a,'[' call putchar ld a,']' call putchar ret dblkoff: dblkskip: ld a,' ' call putchar ld a,' ' call putchar ret ;multiply hl by 10 mul10: push de add hl,hl ;*2 ld d,h ld e,l add hl,hl ;*4 add hl,hl ;*8 add hl,de ;*10 pop de ret ;this draws a 'compact' block, which can be used on a machine ;with less than 20 lines. It makes the play area only 10x10 onscreen. ;this is really intended to support ZCN boxes, but should work ;for other things too. ;it's called from drawblk when needed. ;entry: hl points to byte set in scrn, (b,c)=(x,y). cbchars: defb ' .',39,':' ;i.e. " .':" compblk: ;scrn() has already been done, we just have to draw/undraw. and a rr c rl e ;so c=y/2 and bit 0 of e=y&1 push de push hl call move pop hl pop de ;work out what the char there should be, from looking at scrn(). ;build up the 0-3 offset in cbchars in d. ld a,(hl) rra rl d ld bc,10 bit 0,e jr z,cblkskip ld bc,-10 cblkskip: add hl,bc ld a,(hl) rra rl d ;if y&1=1, bits 0 and 1 of d are the wrong way round. ;fix that here. bit 0,e jr z,cblk2 rr d rr b ;bit 0 -> b rr d rr c ;bit 1 -> c rl b rl d ;b -> bit 1 rl c rl d ;c -> bit 0 cblk2: ;get correct char and print it. ld a,d and 3 ld h,0 ld l,a ld de,cbchars add hl,de ld a,(hl) jp putchar ;draw shape ;entry: a=1 to draw, 0 to undraw dsasav: defb 0 drawshp: ld (dsasav),a ;let hl=offset in shp() ld a,(frm) ld h,0 ld l,a add hl,hl add hl,hl ld de,shps add hl,de ex de,hl ld a,(shp) ld h,0 ld l,a add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl add hl,de ld de,(xy) ld c,4 dsylp: ld a,(x) ld d,a ld b,4 dsxlp: ld a,(hl) and a jr z,dsskip push bc push de push hl ld b,d ld c,e ld a,(dsasav) call drawblk pop hl pop de pop bc dsskip: inc hl inc d djnz dsxlp push de ld de,12 add hl,de pop de inc e dec c jr nz,dsylp ld a,(dsasav) and a ret z ;if we're drawing rather than undrawing, move cursor out of the way ;FALLS THROUGH ;move cursor to safe pos out of the way stashcur: ld b,22 ld c,0 call move ret ;entry: (b,c)=(x,y), a=frm hsxy: hsy: defb 0 hsx: defb 0 hitshp: ld (hsxy),bc ;let hl=offset in shp() ld h,0 ld l,a add hl,hl add hl,hl ld de,shps add hl,de ex de,hl ld a,(shp) ld h,0 ld l,a add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl add hl,de ld de,(hsxy) ld c,4 hsylp: ld a,(hsx) ld d,a ld b,4 hsxlp: ld a,(hl) and a jr z,hsskip ;if <0 or >9, always hit ld a,d cp 10 ccf ret c ;now proper check push de push hl ld h,0 ld l,e call mul10 ld e,d ld d,0 add hl,de ld de,scrn add hl,de ld a,(hl) pop hl pop de and a scf ret nz hsskip: inc hl inc d djnz hsxlp push de ld de,12 add hl,de pop de inc e dec c jr nz,hsylp and a ret initscr: ld hl,scrn ld de,scrn+1 ld bc,210 ld (hl),0 ldir ld hl,ybot call mul10 ld de,scrn add hl,de ld d,h ld e,l inc de ld bc,9 ld (hl),1 ldir ;draw blank play area if needed ld a,(compact) and a jr z,iscrncmp ld c,10 iscrylp: ld b,10 iscrxlp: push bc dec b dec c call move ld a,(cbchars) call putchar pop bc djnz iscrxlp dec c jr nz,iscrylp iscrncmp: ;only draw bottom line if (lines)>=ybot+1 (21) ld a,(lines) ld c,ybot inc c cp c ret c dec c ld b,0 ld hl,mbotline call mvaddstr ret dolines: ld c,ybot-1 dlylp: ;e used as total ld e,0 ld b,10 dlxlp: push de ld h,0 ld l,c call mul10 ld de,scrn add hl,de ld d,0 ld e,b dec e add hl,de pop de ld a,(hl) add a,e ld e,a djnz dlxlp ld a,e and a ret z cp 10 jr nz,dlskip push bc call scrlscr pop bc inc c dlskip: dec c jr nz,dlylp ret ;scroll up from line c scrlscr: dec c ssylp: push bc ;copy scrn() array line ld h,0 ld l,c call mul10 ld de,scrn add hl,de push hl ld de,10 add hl,de pop de ex de,hl ld bc,10 ldir pop bc push bc ;copy to screen ;right now, hl points to scrn() data for this line, ; and c+1 is the line to draw it on. inc c push bc push hl ld a,(compact) and a call z,move pop hl pop bc ld b,10 ssxlp: ld a,(compact) and a jr nz,ssskip1 ;normal ld a,(hl) and a call nz,dblkon call z,dblkoff jr ssskip2 ssskip1: ;compact push bc ld a,10 sub b ld b,a push hl call compblk pop hl pop bc ssskip2: inc hl djnz ssxlp pop bc dec c jr nz,ssylp ld hl,(linesdun) inc hl ld (linesdun),hl ret ;move to (b,c) and display asciiz at hl mvaddstr: push hl call move pop hl mvaslp: ld a,(hl) and a ret z call putchar inc hl jr mvaslp ;get non-blocking input. ;returns a=char or 0 if none pressed inkey: call kbhit ld a,0 ret nc jp getch ;the built-in terminal support cls: ld a,(termtype) and a jp z,hackcls cp 1 jr nz,cls2 ;VT100 ld de,mclsvt100 ld c,9 jp 5 mclsvt100: defb 27,'[H',27,'[J$' cls2: cp 2 jr nz,cls3 ;VT52 ld de,mclsvt52 ld c,9 jp 5 mclsvt52: defb 27,'H',27,'J$' cls3: cp 3 jr nz,cls4 ;ZCN ld a,1 jp putchar cls4: cp 4 ret nz ;VC404 ld a,24 jp putchar move: ld a,(termtype) and a jp z,hackmove cp 1 jr nz,move2 ;VT100 ld a,27 call putchar ld a,'[' call putchar ld e,c inc e ld d,0 call dispdec2 ld a,';' call putchar ld e,b inc e ;d still 0 call dispdec2 ld a,'H' jp putchar move2: cp 2 jr nz,move3 ;VT52 ld a,27 call putchar ld a,'Y' call putchar ld a,32 add a,c call putchar ld a,32 add a,b jp putchar move3: cp 3 jr nz,move4 ;ZCN ld a,16 call putchar ld a,32 add a,c call putchar ld a,32 add a,b jp putchar move4: cp 4 ret nz ;VC404 ld a,16 call putchar ld a,32 add a,c call putchar ld a,32 add a,b jp putchar zcninit: ;we know it's ZCN, so set termtype etc. ld a,3 ld (termtype),a ld a,120 ld (cols),a ld a,10 ld (lines),a ld a,46 ld (mhz10),a ld hl,zcnchars ld de,cbchars ld bc,4 ldir ld hl,zcnfont ld de,newzcnfont ld bc,96*6 ldir ld hl,zcncdat ld bc,4*6 ldir ;enable the new font ld de,newzcnfont-192 ld c,142 call 5 ;might as well turn off the cursor, too. ld a,4 call putchar ret ;stuff from ZX81 version of zcnlib's maths.z ;this doesn't require undocumented Z80 ops, which are causing ;probs on the 'cpm' CP/M emulator I'm testing this on. ;ZX81-friendly (non-ix-using) version; probably slower, but at least ; it works :-) ;divide ;gives z1=x/y and z2=x mod y ;entry: hl=x, de=y ;exit: hl=z1 (result), de=z2 (remainder) ;af/bc corrupt dividey: defw 0 divide: ld b,h ld c,l ;see if we're trying to divide by zero ld a,d or e ret z ld (dividey),de ld de,0 ld hl,0 ld a,16 dvlp: push af and a rl l rl h and a rl e rl d bit 7,b jr z,dvs1 ld a,1 or l ld l,a dvs1: push hl and a push de ld de,(dividey) sbc hl,de pop de jp m,dvs2 ;nasty! fiddle the stack ex (sp),hl ld a,1 or e ld e,a dvs2: pop hl and a rl c rl b pop af dec a jr nz,dvlp ;finally! got the results. ex de,hl ;exit: hl=result, de=remainder ret ;this from zcnlib's maths.z ;this does z=x*y ;entry: hl=x, de=y ;exit: hl=z ;af/bc/de corrupt multiply: ld b,h ld c,l ld hl,0 ld a,16 mulp: and a rr d rr e jr nc,musp add hl,bc musp: and a rl c rl b dec a jr nz,mulp ret numtmp: defb '0000000000000000$' ;16 zeroes and '$' ;convert number in de to ascii in internal buffer ;entry: de=number ;exit: de=addr of number in internal buffer, '$' terminated itoa: ld b,10 ;FALLS THROUGH ;call here for routine with functionality as above but with b=base ;convert number in de to ascii, in given base (unsigned) ;entry: de=number, b=base (from 2 to 36) ;exit: af/bc/de/hl corrupt itoabase: ld hl,numtmp+16 ld a,'$' ld (hl),a dispnlp: push bc push hl ex de,hl ld e,b ld d,0 call divide ld a,e add a,48 cp 58 jr c,dispn1 add a,7 ;compensate for >=10 dispn1: ex de,hl ;so de now is result of division pop hl pop bc dec hl ld (hl),a ld a,d or e jp nz,dispnlp ex de,hl ret ;display number in de, in decimal dispdec: call itoa ld c,9 jp 5 ;like C func. entry: hl=addr of number, exit: hl=actual number atoi: ld b,10 ;FALLS THROUGH ;convert number of specified base as ASCII at hl to number. ;the ASCII number should be terminated by a non-digit in this base. ;supports bases from 2 to 36 ;entry: hl=address of first digit of ASCII number, ; b=base (e.g. 10 for decimal) ;exit: hl=number atoibase: ld de,0 ;total of number so far. atoilp: ld a,(hl) ;uppercase it call atoiislw jr nc,atoi0 res 5,a atoi0: sub 48 jr c,atoidone ;if not digit, end cp 10 jr c,atoi0a sub 7 ;compensate if we're using letters atoi0a: cp b jr nc,atoidone ;if not digit, end ;otherwise, multiply our running total by base and add this. push hl push bc push af ld l,b ld h,0 call multiply pop af ld e,a ld d,0 add hl,de ex de,hl pop bc pop hl inc hl jr atoilp atoidone: ;got number in de - put it in hl. ex de,hl ret atoiislw: cp 'a' ccf ret nc cp '{' ret