DEVICE ZXSPECTRUM128 include "../../_sdk/sys_h.asm" scrbuf=0xe000 ;делится на 0x800 tilemap=0xdd00 ;0x300 collisionmap=0xda00 ;0x300 (перед ней ~20 байт затирается) validmap=0xfc00 ;0x300 (перед ней ~20 байт затирается) VALID0=0xfb ;валидная, не надо обновлять VALID1=0x80 ;невалидная, надо обновлять, там можно более длинные процедуры VALID00=VALID0+256*VALID0 VALID01=VALID0+256*VALID1 VALID10=VALID1+256*VALID0 VALID11=VALID1+256*VALID1 validmaplinesize=32 validmapwid=validmaplinesize-2 ;в конце строки лежит validmapnext emptyattr=7 fieldwid=10 fieldhgt=8 fieldx=0 fieldy=0 coordsfactor=4 leftwallx=0 topwally=0 rightwallx=fieldwid*24*coordsfactor bottomwally=fieldhgt*24*coordsfactor dir_r=0x09 dir_l=0x08 dir_u=0x0b dir_d=0x0a collisionmaplinesize=32 collisionmapwid=fieldwid*3 collisionmaphgt=fieldhgt*3 collisionmapsize=collisionmaplinesize*collisionmaphgt tilemaplinesize=collisionmaplinesize tilemapwid=collisionmapwid tilemaphgt=collisionmaphgt tilemapsize=collisionmapsize maxemptytile=9 maxobjects=20 maxbullets=100 TERMINATOR=0x80 tanksize=16 tankdamagesize=11 ;грязный хак tankaimsize=8 tankspeed=4 ;2^n! bulletspeed=6 bulletenergy=20 startlives=5 maxlives=10 ;timer=23672 include "macro.asm" org PROGSTART begin OS_HIDEFROMPARENT ld e,3 ;6912 OS_SETGFX ;e=0:EGA, e=2:MC, e=3:6912, e=6:text ;+SET FOCUS ;e=-1: disable gfx (out: e=old gfxmode) ;OS_GETSCREENPAGES ;de=страницы 0-го экрана (d=старшая), hl=страницы 1-го экрана (h=старшая) ld a,(user_scr0_high) ;ok SETPG16K ld hl,valid00 ld de,VALID00 ld bc,valid00_size ldir ld hl,valid01 ld de,VALID01 ld c,valid01_size ldir ld hl,valid10 ld de,VALID10 ld c,valid10_size ldir ld hl,valid11 ld de,VALID11 ld c,valid11_size ldir ld a,1 ld (nlevel),a newlevel call cls call clscrbuf ld a,startlives ld (lives),a call prlives call initlevel call clearvalid call restoreobjects_clear call restorebullets_clear call prmap call fillcollisionmap OS_GETTIMER ;dehl=timer ;ld hl,(timer) ld (oldtimer),hl loop call restoreobjects call restorebullets ;ld a,2 ;out (0xfe),a call probjlist ;ld a,4 ;out (0xfe),a call prbulletlist ;ld a,0 ;out (0xfe),a loopdelay OS_GETTIMER ;dehl=timer ;ld hl,(timer) oldtimer=$+1 ld bc,0 ld (oldtimer),hl ld a,l sub c jr z,loopdelay ld b,a ;ld a,5 ;out (0xfe),a logicloop0 push bc call control call animate call logic call bulletlogic ;ld a,1 ;out (0xfe),a call fillcollisionmap ;ld a,5 ;out (0xfe),a call bulletcollision pop bc djnz logicloop0 ;ld a,6 ;out (0xfe),a call prvalid ;call displaycollisionmap ;TODO сделать что-нибудь, когда врагов не осталось ld a,(nenemies) or a jp nz,loop ld hl,nlevel inc (hl) jp newlevel initlevel call genmap ld ix,objlist call genobj_terminate ld ix,bulletlist call genbullet_terminate ld de,0x0010 ;x ld hl,0x02b0 ;y ld bc,params_tank ld a,1 ;a=dir call genobj ld a,(nlevel) inc a ld b,a initlevel_addenemies0 push bc ;ld de,0x0010 ;x = 0x0010..0x0370 call rndxcoord ;out: hl=x ex de,hl ld hl,0x0010 ;y ld bc,params_tanke ld a,2 ;a=dir call genobj ld hl,nenemies inc (hl) if 1==0 ld de,0x0370 ;x ld hl,0x0010 ;y ld bc,params_tanke ld a,2 ;a=dir call genobj ld hl,nenemies inc (hl) endif pop bc djnz initlevel_addenemies0 reter ret attrbox ;bc=yx ;a=фон ;d=y координата левого верхнего угла ;е=х координата левого верхнего угла ;0x5800+xcoordredbox+(32*ycoordredbox) ld l,d ;y ld h,0 add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl ld d,0x58 add hl,de ;x attrbox_lines ld e,l ld d,h push bc attrbox_pixels ld (de),a inc e dec c jr nz,attrbox_pixels pop bc ld de,32 add hl,de djnz attrbox_lines ret genmap ld hl,tilemap ld de,tilemap+1 ld bc,tilemapsize-1 ld (hl),0 ldir ld hl,tilemap+(3*tilemaplinesize) ld b,fieldhgt-2 genmaplines push bc push hl ld b,fieldwid genmapline push bc push hl push hl ld a,b ;1..fieldwid sub 2 ;n cp fieldwid-2 ;nc=левый или правый край adc a,nblocks+1 ;n + cy + (nblocks+1) sub b ;n + cy + nblocks - (n+2) ld c,a ;nblocks или nblocks-1 call rnd ld c,a ld b,0 ld de,9 call mulbcde ld bc,blocks add hl,bc ex de,hl pop hl ld bc,tilemaplinesize-2 dup 3 ld a,(de) ld (hl),a inc de inc hl ld a,(de) ld (hl),a inc de inc hl ld a,(de) ld (hl),a inc de add hl,bc edup pop hl inc hl inc hl inc hl pop bc djnz genmapline pop hl ld bc,tilemaplinesize*3 add hl,bc pop bc djnz genmaplines ret control GET_KEY cp key_esc jp z,quit call getkey ;c=%???lrduf (0=нажато) ld a,c rra ;f jr nc,control_noreleasefire ld a,1 ld (control_firehasbeenreleased),a control_noreleasefire ;кнопки должны срабатывать, когда мы стоим посредине клетки ((x&(8*coordsfactor-1)) == 4*coordsfactor, (y&(8*coordsfactor-1)) == 4*coordsfactor) ld ix,objlist ;первый объект - наш call checkevencoords ;nz=не посредине клетки ret nz ld a,c rra ;f jr c,control_nofire ;проверим, что до этого был момент, когда огонь не нажимали control_firehasbeenreleased=$+1 ld a,0 or a jr z,control_nofire ;не было момента, когда огонь не нажимали xor a ld (control_firehasbeenreleased),a ;ld ix,objlist ;первый объект - наш push bc call shoot pop bc control_nofire ld ix,objlist ;первый объект - наш ;если не нажата кнопка движения и мы едем, то остановиться rr c ld a,c cpl and 0x0f jr nz,control_nokeysreleased ld a,(ix+obj_anim) cp ANIM_GO jr nz,control_nokeysreleased SETANIM ANIM_STOP control_nokeysreleased ld a,c rra ;u jr c,control_nou call goifpossible ld (ix+obj_dir),0 control_nou rra ;d jr c,control_nod call goifpossible ld (ix+obj_dir),2 control_nod rra ;r jr c,control_nor call goifpossible ld (ix+obj_dir),1 control_nor rra ;l jr c,control_nol call goifpossible ld (ix+obj_dir),3 control_nol ret goifpossible ld c,a ld a,(ix+obj_anim) cp ANIM_APPEAR jr nz,$+4 ld a,ANIM_STOP cp ANIM_STOP ld a,c ret nz ;не стоим, так что не можем поехать SETANIM ANIM_GO ret checkevencoords ;out: nz=не посредине клетки ld a,(ix+obj_x) and 8*coordsfactor-1 cp 4*coordsfactor ret nz ld a,(ix+obj_y) and 8*coordsfactor-1 cp 4*coordsfactor ret shoot SETANIM ANIM_SHOOT ld c,(ix+obj_dir) ld b,0 ld hl,tankbulletcoords add hl,bc add hl,bc ld a,(hl) ;dx ld c,a ;%sxxxxxxx rla ;%xxxxxxx?, CY=s sbc a,a;%ssssssss ld b,a ;dx inc hl ld a,(hl) ;dy GETXDE_YHL ex de,hl ;hl=x add hl,bc ;x+dx push hl ;x+dx push de ;y ld e,a rla sbc a,a ld d,a ;dy pop hl ;y add hl,de ;hl = y+dy pop de ;de = x+dx ld a,(ix+obj_dir) ;a=dir ld bc,params_bullet curbulletlistend=$+2 ld ix,bulletlist call genobjorbullet genbullet_terminate ld bc,objterminator ld (ix+obj_objaddr),c ld (ix+(obj_objaddr+1)),b ld (ix+(obj_x+1)),TERMINATOR ld (curbulletlistend),ix ret genobjorbullet ;de=x ;hl=y ;bc=params (obj(16),energy(8),speed(8)) ;a=dir ld (ix+obj_dir),a PUTXDE_YHL SETANIM ANIM_APPEAR ld (ix+obj_delaycounter),1 ld (ix+obj_gundelaycounter),0 ld a,(bc) ld (ix+obj_objaddr),a inc bc ld a,(bc) ld (ix+(obj_objaddr+1)),a inc bc ld a,(bc) ld (ix+obj_energy),a inc bc ld a,(bc) ld (ix+obj_speed),a ld bc,objsize add ix,bc ret genobj ;de=x ;hl=y ;bc=params (obj(16),energy(8),speed(8)) ;a=dir curobjlistend=$+2 ld ix,objlist call genobjorbullet genobj_terminate ld bc,objterminator ld (ix+obj_objaddr),c ld (ix+(obj_objaddr+1)),b ld (ix+(obj_x+1)),TERMINATOR ld (curobjlistend),ix ret animate ld ix,objlist-objsize animate0_prepareregs ld a,TERMINATOR ld bc,objsize animate0 add ix,bc animate0_afterdel cp (ix+(obj_x+1)) ret z dec (ix+obj_animcounter) jp nz,animate0 ;текущая фаза анимации кончилась, ищем следующую ld l,(ix+obj_objaddr) ld h,(ix+(obj_objaddr+1)) ;hl=адрес описателя объекта (в начале лежит указатель на список анимаций) ld a,(hl) inc hl ld h,(hl) ld l,a ;hl=указатель на список анимаций ld e,(ix+obj_anim) ;номер анимации ld d,0 add hl,de add hl,de ld a,(hl) inc hl ld h,(hl) ld l,a ;hl=указатель на анимацию ld e,(ix+obj_animphase) ;номер фазы анимации inc e ;следующая фаза анимации ld (animate_pointer),hl add hl,de add hl,de add hl,de ld a,(hl) ;время фазы or a ;если 0, то это конец анимации jr nz,animate_noend inc hl ld a,(hl) ;режим зацикливания (0=переход на нулевую анимацию, 1=зацикливаемся тут, 2=удалить) cp 2 ;признак ANIMENDDIE jr z,animate_delete or a jr nz,animate_no0 ld (ix+obj_anim),a ;0-я анимация animate_no0 ld e,0 animate_pointer=$+1 ld hl,0 add hl,de add hl,de add hl,de ld a,(hl) ;время фазы animate_noend ld (ix+obj_animcounter),a ld (ix+obj_animphase),e ;следующая фаза анимации jp animate0_prepareregs animate_delete push ix pop hl ld de,objlist or a sbc hl,de jr z,animate_delete_player ld hl,curobjlistend call delobj ;копируем из ix+objsize в ix ld hl,nenemies dec (hl) ld a,TERMINATOR ld bc,objsize jp animate0_afterdel animate_delete_player call decreaselives call rndbottomcoords ;bc=x, de=y SETANIM ANIM_APPEAR ld (ix+obj_dir),1 ;a=dir jp animate0_prepareregs bulletlogic ld ix,bulletlist-objsize jr logic0 logic ld ix,objlist-objsize logic0 ld bc,objsize add ix,bc logic0_afterdel ld l,(ix+obj_objaddr) ld h,(ix+(obj_objaddr+1)) ;hl=адрес описателя объекта (в начале лежит указатель на список анимаций) inc hl inc hl jp (hl) ;hl=адрес обработчика объекта rndbottomcoords ;out: bc=x, de=y rndbottomcoords_retry call rndxcoord ex de,hl ld hl,0x02b0 ;y PUTXDE_YHL call checkobstacles_tank ;nc=препятствие jr nc,rndbottomcoords_retry ret rndxcoord ;out: hl=x ld c,fieldwid call rnd ld c,a ld b,0 ld de,8*coordsfactor*3 call mulbcde ld bc,4*coordsfactor add hl,bc ;x ret delobj ;hl=адрес указателя на конец списка (на терминатор) ;копируем из ix+objsize в ix ld (delobj_curlistend1),hl ld (delobj_curlistend2),hl ld c,(hl) inc hl ld b,(hl) ;указатель на конец списка (на терминатор) ld hl,objsize add hl,bc ld b,h ld c,l ;указатель на конец списка (после объекта-терминатора) ld d,hx ld e,lx ;de=ix ld a,e add a,objsize ld l,a adc a,d sub l ld h,a ;hl=ix+objsize ld a,c sub l ld c,a ld a,b sbc a,h ld b,a ;bc=objlistend-hl ld a,b or c ret z ;почему удаляем терминатор после встречи двух пуль??? TODO ldir ;копируем все следующие объекты, включая терминатор delobj_curlistend1=$+1 ld hl,(curobjlistend) ld bc,-objsize add hl,bc delobj_curlistend2=$+1 ld (curobjlistend),hl push ix call fillcollisionmap pop ix ret movetank ld a,(ix+obj_anim) cp ANIM_GO ret nz moveobj GETXDE_YHL ld b,(ix+obj_speed) ld c,(ix+obj_dir) inc c dec c call z,moveobj_u dec c call z,moveobj_r dec c call z,moveobj_d dec c call z,moveobj_l PUTXDE_YHL ret moveobj_u dec hl djnz $-1 ret moveobj_r inc de djnz $-1 ret moveobj_d inc hl djnz $-1 ret moveobj_l dec de djnz $-1 ret objtank dw anims_tank objtank_move GETXDE_YHL push de push hl call movetank call checkobstacles_tank ;nc=коллизия jr nc,objtank_collided ld c,tanksize ;размер call checkwalls ;nc=стена ;TODO как-то воткнуть checkwalls в moveobj, но не в ущерб пулям objtank_collided pop hl pop de jp c,logic0 ;не стена PUTXDE_YHL ;стена - восстановим старые координаты jp logic0 objtanke dw anims_tanke dec (ix+obj_gundelaycounter) jr nz,objtanke_nogundelaystop inc (ix+obj_gundelaycounter) ld a,(ix+obj_anim) cp ANIM_PREPARESHOOT jr nz,objtanke_noshoot push ix call shoot pop ix ld (ix+obj_gundelaycounter),50 jr objtanke_stop objtanke_noshoot objtanke_nogundelaystop ld a,(ix+obj_anim) cp ANIM_DIE jp z,objtanke_nologic ;логика должна срабатывать, когда мы стоим посредине клетки ((x&(8*coordsfactor-1)) == 4*coordsfactor, (y&(8*coordsfactor-1)) == 4*coordsfactor) call checkevencoords ;nz=не посредине клетки jp nz,objtanke_nologic ;едем энное растояние, потом случайно меняем направление или встаём dec (ix+obj_delaycounter) jr nz,objtanke_nonewmove ld (ix+obj_delaycounter),20 ld c,5 call rnd cp 4 jr z,objtanke_stop ld (ix+obj_dir),a SETANIM ANIM_GO jr objtanke_nonewmove objtanke_stop SETANIM ANIM_STOP objtanke_nonewmove ;если одна из координат близка к нашей (x-(tanksize/2*coordsfactor) > xe >= x+(tanksize/2*coordsfactor)), то встать, задержать и стрелять GETXDE_YHL push hl ;y ld hl,(objlist+obj_x) ;наша координата ld bc,-(tankaimsize/2*coordsfactor) add hl,bc or a sbc hl,de ;надо -tanksize*coordsfactor..-1 ld bc,tankaimsize*coordsfactor add hl,bc ;cy=координата близка к нашей pop hl ;y ex de,hl ;de=y, hl=x jr z,$+4 jr nc,objtanke_noprepareshootx ld hl,(objlist+obj_y) ;наша координата or a sbc hl,de ;y >= ye? тогда d, иначе u ld a,2 ;d jr nc,$+4 ;y >= xe ld a,0 ;u ld (ix+obj_dir),a jr objtanke_prepareshoot objtanke_noprepareshootx push hl ;x ld hl,(objlist+obj_y) ;наша координата ld bc,-(tankaimsize/2*coordsfactor) add hl,bc or a sbc hl,de ;надо -tanksize*coordsfactor..-1 ld bc,tankaimsize*coordsfactor add hl,bc ;cy=координата близка к нашей pop de ;x jr z,$+4 jr nc,objtanke_noprepareshoot ld hl,(objlist+obj_x) ;наша координата or a sbc hl,de ;x >= xe? тогда r, иначе l ld a,1 ;r jr nc,$+4 ;x >= xe ld a,3 ;l ld (ix+obj_dir),a objtanke_prepareshoot SETANIM ANIM_PREPARESHOOT ;ld (ix+obj_delaycounter),1 objtanke_noprepareshoot objtanke_nologic jp objtank_move objbullet dw 0 call moveobj ld c,0 ;размер call checkwalls ;nc=стена jp c,logic0 ;не стена ld hl,curbulletlistend call delobj ;копируем из ix+objsize в ix ld bc,-objsize add ix,bc jp logic0 bulletcollision ld ix,bulletlist-objsize ld ly,-(128-1) bulletcollision0 dec ly ld bc,objsize add ix,bc bulletcollision0_afterdel ld a,(ix+(obj_x+1)) cp TERMINATOR ret z GETXDE_YHL call calctilemapaddr_de_hl ld a,(hl) cp maxemptytile+1 jr nc,bulletcollision_collided call calccollisionmapaddr ld a,(hl) add a,ly ;номер текущей пули ;CY=1 при совпадении call nz,checkbulletcollision jp c,bulletcollision0 ;не коллизия bulletcollision_delete ld hl,curbulletlistend call delobj ;копируем из ix+objsize в ix jp bulletcollision0_afterdel bulletcollision_collided ;ld c,l GETXDE_YHL push hl ex de,hl ld a,(ix+obj_dir) rra ;nc=vertical direction ccf call divmul3 ;hl=x клетки карты ex (sp),hl ;сохранили x клетки карты ;hl=y ld a,(ix+obj_dir) rra ;nc=vertical direction call divmul3 ;hl=y клетки карты pop bc ;x клетки карты ld b,l ;y клетки карты ld a,c call calctilemapaddr_a_l ld a,(ix+obj_dir) dec a jr z,objbullet_collided_ver ;r dec a jr z,objbullet_collided_hor ;d dec a jr z,objbullet_collided_ver ;l ;u objbullet_collided_hor call degradetile inc c inc hl call degradetile inc c inc hl call degradetile jp bulletcollision_delete objbullet_collided_ver ld de,tilemaplinesize call degradetile inc b add hl,de call degradetile inc b add hl,de call degradetile jp bulletcollision_delete divmul3 ;если CY=1, то делим до знакомест и округляем до 3 ;иначе просто делим до знакомест ld de,8*coordsfactor jp nc,divhlde ;hl=x(y) клетки карты (без округления) ld de,8*coordsfactor*3 call divhlde ;hl=x(y) клетки поля ld c,l ld b,h add hl,hl add hl,bc ret ;hl=x(y) клетки карты degradetile ;bc=yx клетки карты ;hl=tilemap+ ;push bc push de ;push hl call degradetile_changetile call calcscrbufaddr ;de=scrbuf+ call restoretile ;pop hl pop de ;pop bc ret degradetile_changetile ld a,(hl) cp tilem ret z cp tileb ld (hl),tile0 ret z cp tilec ld (hl),tileb ret z ld (hl),tile0 ret objterminator dw 0 ret decreaselives lives=$+1 ld a,0 dec a ld (lives),a push af call prlives pop af ret nz gameover fieldEx=32 fieldEy=24 centr=(fieldEx/2)-(10/2)+(256*fieldEy/2) ;bc=yx ;a=фон ;d=y координата левого верхнего угла ;е=х координата левого верхнего угла ;0x5800+xcoordredbox+(32*ycoordredbox) ld c,12 ld b,3 ld de,centr-0x0101 ld a,0x57 ld (curattr),a call attrbox ld hl,endtext ld bc,centr call prtext gameoverloop YIELD GET_KEY cp key_esc jr nz,gameoverloop quit QUIT endtext db "Game over!",0 font incbin "zx.fnt" include "collisio.asm" include "tankgfx.asm" include "tankdata.asm" include "math.asm" include "input.asm" nlevel db 0 ;1.. nenemies db 0 end reobjlist ds 2*(maxobjects+1) rebulletlist ds 2*(maxbullets+1) objlist ds objsize*(maxobjects+1) objlistend bulletlist ds objsize*(maxbullets+1) bulletlistend ;display "End=",end ;display "Free after end=",/d,0xc000-end ;display "Size ",/d,end-begin," bytes" savebin "tank.com",begin,end-begin ;LABELSLIST "..\us\user.l"