; TETRIS3.A86 Freeware Copyright (c) 1996 by Richard Kanarek. ; =========== All Rights Reserved. ; ; ("Tweaks" for monochrome IBM CP/M-86 by Kirk Lawrence.) ; (The IBM CP/M-86 version now checks the video adaptor.) ; (If a color adaptor is found, the program displays in ) ; (40x25 mode. If a mono adaptor is found, 80x25 mode is) ; (used...thus insuring total video compatibility. ) ; ; TETRIS3 a clone of the popular Atari arcade game. Not at all an exact ; clone-- you'd be much happier with the arcade machine!-- but good enough ; to warrant wasting several irreplaceable hours of your life! ; ; Note that this game was written especially for maximum compatibility. ; This program requires only IBM PC DOS level of compatibility & support ; for an IBM compatible System Timer Tick Count located at memory location ; 046C:0. ; ; This source file was written for assembly by RASM86 (PCDOS) or ; ASM86 (CP/M-86). Both assemblers are by Digital Research Inc. ; ; Note: Set CPM=1 for CP/M-86, =0 for PCDOS ; ; For DOS: ; a. Make CPM EQU 0 ; b. Run RASM86 tetris3.a86 $LO ; c. Run LINK86 tetris3 [NOP, MAP[ALL]] ; d. Run exe2bin tetris3.exe tetris3.com ; e. Run del tetris3.exe (You may also delete tetris3.lst/sym/map, etc.) ; f. Run tetris3.com and enjoy! ; For CP/M-86: ; a. Make CPM EQU 1 ; b. Run ASM86 tetris3 ; c. Run GENCMD tetris3 8080 ; d. Delete unnecessary files-- if you wish. ; e. Run tetris3.cmd and enjoy! ; ; Tips for making this file MASM/TASM compatible: ; '!' is equal to starting a new line in M/TASM, i.e.: ; RASM86: ; push ax ! push bx ; M/TASM: ; push ax ; push bx ; Constants & strings are enclosed by single quotes ('). ; RASM86: ; string db 'this is a string' ; M/TASM: ; string db "this is a string" ; For easiest reading, set editor for TAB's = 8 spaces. ; Assembler Directive(s)... ; ========================= TITLE 'TETRIS3 v1.2 (c) 1996 by R. Kanarek' NOIFLIST ; Equates ; ======= ; Operating System & Hardware Platform EQUates-- ; SET PROPERLY BEFORE RECOMPILING! CPM EQU 1 ;Set as required: 0 = PCDOS, 1 = CP/M-86 RAINBOW EQU 0 ;Set as required: 0 = IBM PC, 1 = DEC Rainbow ; Misc. EQUates cr EQU 0Dh ;ASCII CR lf EQU 0Ah ;ASCII LF beep EQU 7d ;ASCII Bell esc EQU 27d IF RAINBOW ;DEC Rainbow PC... rb_tick_mul_equ EQU 3 ;Multiplies timer tick for DEC Rainbow PC's. tick_isr_off_pntr EQU ES: WORD PTR .190h tick_isr_seg_pntr EQU ES: WORD PTR .192h ENDIF IF RAINBOW EQ 0 ;Non-Dec Rainbow PC... tick_isr_off_pntr EQU ES: WORD PTR .70h tick_isr_seg_pntr EQU ES: WORD PTR .72h ENDIF ; EQUates used by some game procedures... ;game piece equates: col_equ EQU WORD PTR 0[si] row_equ EQU WORD PTR 2[si] pcs_equ EQU BYTE PTR 4[si] angle_equ EQU BYTE PTR 5[si] draw_char_equ EQU BYTE PTR 6[si] players_equ EQU 3 ;Number of player lives. init_wait_time_equ EQU 12d ;Initial game speed. ; Key Equates: ; ------------ ;Note: 'process_input' converts all a-z keys to upper case. ; Assigning one of the following equates a lower-case letter effectively ; disables it. dn_key EQU '2' ;Drop piece key. rt_key EQU '6' ;Move piece right key. lt_key EQU '4' ;Move piece left key. rotate_key EQU '5' ;Rotate key. sound_key EQU 'S' ;Sound on/off toggle quit_key EQU 1Bh ;Quit game key. redraw_key EQU 'R' ;Redraw Screen. advlvl_key EQU '+' ;Advance one level. IF CPM statline_key EQU 'T' ;Turns Status Line on/off (CP/M-86 only) ;NOTE: Starting with ver. 1.2, disabling ; the status line is no longer supported. ENDIF IF CPM EQ 0 ;DOS... dgroup GROUP CODE, DATA CSEG ORG 100h ;.COM program. ENDIF IF CPM ;CP/M-86... CSEG ORG 100h ;8080 mode program. ENDIF main: ;Main Program Body ;================= ;check for color int 11h and ax,30h cmp ax,30h jne start mov mono,1 ;Initialize Registers... ;----------------------- start: cld ;Clear Direction Flag. ;Assumed throughout pgrm. ;Disp intro msg... mov si, OFFSET welcome_msg call ansi_str ;Initialize timer procedures... call install_isrs ;Wait for keypress... xor dh, dh ;Prepare to wait4key as long as it takes. call wait4key ;Initialize Display (set video mode, etc.) call init_disp ;Allocate memory: call release_mem call get_mem ;Clear variables... call clr_vars jmps game_looptop1 game_looptop0: call inc_level ;Next level game_looptop1: call clr_mem ;Erase image data. call process_level ;Set up timing, etc. ;Game piece. call gen_next_gp ;Game board... call redraw ;Redraws entire screen. Also draws ; next game piece. ;Prompt player for keypress to start game. call axbx_stofgb mov si, OFFSET start_game_msg1 call poscur call ansi_str mov dh, 0 call wait4key mov si, OFFSET blankline_msg call poscur call ansi_str skip_prompt: drop_new_piece: ;Make current game piece "next game piece" call clearkb ;Clear keyboard. call next2cur ;current_gpiece = gb_scrn_dpcsn ;Now erase old "next" game piece. mov si, OFFSET gb_scrn_dpcsn mov draw_char_equ, ' ' call draw_gpcs2scr ;New next game piece... call gen_next_gp ;Get a new next game piece. mov si, OFFSET gb_scrn_dpcsn call draw_gpcs2scr ;Draw new next game piece to scrn. call center_curgp ;Center current_gpiece. ;Verify that the new game piece isn't obstructed. mov si, OFFSET current_gpiece ;SI = OFFSET current_gpiece. call cur2temp ;Make temp_gpiece == current_gpiece. mov ax, 250d xchg col_equ, ax ;Make current_gpiece col invalid-- ; prevents its being erased & written ; to the data image by 'chk4obs'. mov si, OFFSET temp_gpiece call chk4obs mov si, OFFSET current_gpiece mov col_equ, ax ;Restore current_gpiece col_equ value. cmp dl, 0 je newgp_not_obs jmp next_player ;If new gamepiece is obstructed, current ; life is lost! newgp_not_obs: call draw_gpcs2scr ;Draw new game piece to screen. call draw_gpcs2image ;Draw new game piece data image. ;Statistics... mov dl, pcs_equ ;(SI still = OFFSET current_gpiece.) call update_counts ;Update count variables. call update_tallies ;Write to screen. mov al, wait_time mov cur_wait_time, al move_looptop: mov dh, cur_wait_time call set_timer ml_looptop: call process_input test option, quit_equ ;If quit key was pressed, quit. jz ml_dontquit jmp game_over ml_dontquit: test option, advlvl_equ ;Skip this level? jz dont_advlvl ;No, then jump. Yes then... and option, NOT advlvl_equ ;Clr skip level option bit. call axbx_stofgb ;Pring 'Skipping Level' message mov si, OFFSET skiplevel_msg ; call poscur ; call ansi_str ; mov dh, 22d ;Wait for acknowledgement call wait4key mov si, OFFSET blankline_msg call poscur call ansi_str jmp game_looptop0 dont_advlvl: call chk_timer jnz ml_looptop call move_downc ;Move game piece down. jnc move_looptop ;Movement unobstructed, then loop. ;When this point is reached, the game piece can't be lowered anymore! call scan4frs ;Scan for full (complete) rows. ;Draw 'line completed' graphics as ; appropriate. mov di, OFFSET gb_rcount call isbcd60 ;Do we need to drop again (i.e. Level ; completed?) jz move_loopend ;Level Completed. Exit loop. jmp drop_new_piece ;Level NOT completed, jump (& drop another piece). move_loopend: ;This point is reached when a level is successfully completed. call axbx_stofgb call poscur mov si, OFFSET levelfin_msg ;Display prompt. call ansi_str call lower_curtain ;Award bonus points. call axbx_stofgb add ax, 0106h aaa call poscur mov dh, 0 call wait4key mov si, OFFSET blankline_msg call poscur call ansi_str jmp game_looptop0 next_player: mov si, OFFSET current_gpiece mov draw_char_equ, 'X' call draw_gpcs2scr ;Redraw the fatal game piece in 'X's. ;Make sure obstructed game piece remains on screen for at least a ; brief moment. mov dh, 10d call set_timer np_lptp: call chk_timer jnz np_lptp dec player_life jz game_over ;If player_equ lives are used up, jump. call axbx_stofgb call poscur mov si, OFFSET tryagain_msg call ansi_str mov dh, 0 ;wait4key (forever if nec.) call wait4key ; jmp game_looptop1 ;Retry this level. game_over: call update_tallies ;Display final tallies. ;Display left justified "Game Over" message... mov ax, WORD PTR gb_loc ;Make AX = col to write to. xchg al, ah ; add al, 2 ; aaa ; mov cx, ax ;CX = AX = Col to write to (BCD). mov ax, WORD PTR gb_loc + 2 ;Make BX = row to write to. xchg al, ah ; add al, 2 ; aaa ; mov bx, ax ; mov ax, cx ;AX,BX -> Location of "Game" mov si, OFFSET gameover_msg1 ;Write "Game" call poscur ; call ansi_str ; mov ax, bx ;Point AX,BX -> Location of "Over" inc al ; aaa ; mov bx, ax ; mov ax, cx ;AX,BX -> Location of "Over" mov si, OFFSET gameover_msg2 ;Write "Over" call poscur ;(position cursor for write) call ansi_str ;(write) mov dh, 255 ;Wait for key press (255 * 1/18 sec max) call wait4key ; good_bye: call bye ;Prepare to return to Operating System. fin: IF CPM ;CP/M-86... xor cl, cl mov dl, cl ;code 0 = terminate int 224d ENDIF IF CPM EQ 0 ;DOS.. int 20h ENDIF ; Game Modules: ; ============= ;Procedure List: ;--------------- ;lower_curtain ;Award bonus points for completing level with spare ; ; (blank) rows. ;add_bonus ;Add bonus points (for unused row) to score. & update ; ; display. ;axbx_stofgb ;Make AX,BX -> start of row beneath game board. ;redraw_gb ;Erase then redraw the active area of the game board up to & including ; ; row BX (bcd) ;scan4frs ;Scan for full rows. Draw fancy line as appropriate. ;process_input ;Get input key and move game piece accordingly. ;redraw ;Redraw the screen. ;center_curgp ;Position current game piece in dead center at start ; ; of game board. ;clr_vars ;Clear (initialized) misc variables. Useful if restarting ; ; game. ;process_level ;Read cur_game_table entry, process, increment. ;inc_level ;increment level count/pointer. ;update_tallies ;Update all count/score display's. ;gen_next_gp ;Generate next game piece. ;add2bcd6 ;Add a six digit unpacked BCD variable. ;disp_bcd6 ;Display a BCD6 number at a location specified. ;clr_bcd6 ;Clear a BCD6 string pointed to by [SI]. ;isbcd60 ;Is a BCD6 number = 0? ;isbcd61 ;Is a BCD6 number = 1? ;update_counts ;Update's game piece count variables. Does not update ; ; display. ;update_score ;Update's score tallay. Does not update display. ; -------- ;chk_row ;Check row to determine if it is completed. ;draw_fline ;Draw fancy line. ;scroll_down ;Scroll screen & image data down one line. ; -------- ;next2cur ;Copy next game piece record to current game piece. ;temp2cur ;Copy temp_gpiece to current_gpiece. ;cur2temp ;Copy current_gpiece to temp_gpiece. ; -------- ;move_rotate ;Rotate current game piece (if it is possible to do so). ;move_left ;Move current piece left if movement is unobstructed. ;move_right ;Move current piece right if movement is unobstructed. ;move_downc ;Move current game piece down if movement is unobstructed. ; ; Returns CF set if no movement is possible. ; -------- ;draw_background ;Draw the game board background. ;draw_gpcs2scr ;Draw a game piece to the screen. ;write2image ;Write to x,y coordinate specified relative to the ; data image. ;readfimage ;Read specified char from image data. ;draw_gpcs2image ;Draw a game piece to the data image array. ;chk4obs ;Checks for obstructions that might interfer with ; ;placing a game piece. lower_curtain: ;Award bonus points for completing level with spare ; (blank) rows. ;Calling: Nothing (data image). ;Return: Nothing (misc variables & screen) ;Notes: All registers preserved. ;Preserve Reg's: push ax ! push bx ! push cx ! push dx ! push si ! push di mov ax, WORD PTR gb_loc + 2 xchg al, ah add al, 4 aaa mov bx, ax ;BX = 1st row to check. mov ax, WORD PTR gb_loc xchg al, ah inc al aaa ;AX = 1st col to check. mov si, ax ;SI = AX = 1st col to check. lc_lptp0: mov cl, gb_cols_rows ! xor ch, ch lc_lptp1: call readfimage jc lc_fin cmp dh, 0B0h jne lc_fin inc al aaa loop lc_lptp1 ;This point is reached if a blank row is found! mov ax, si ;AX = 1st col (BCD) call poscur mov cl, gb_cols_rows ;(CH assumed == 0) mov dl, '' lc_lptp2: call write2con loop lc_lptp2 call add_bonus ;Add & display bouns. mov dh, 6d ;DH = Amount of time to wait while ; new curtain row is displayed. test option, sound_equ ;Sound Beep? jz lc_skip_snd ;No, then jump. Yes then... mov dl, beep ;DL = ^G (ASCII Bell) call write2con ;Write Bell to console. sub dh, 2 ;Reduce curtain row display time ; by amount of time Beep took (approx.) jnc lc_skip_snd ;If DH is >= 0, jump. Otherwise... xor dh, dh ;Make DH = 0. Then continue. lc_skip_snd: call set_timer lc_wait_lptp: call chk_timer jnz lc_wait_lptp lc_skip_wait: ;Check next row? mov ax, bx inc al aaa mov bx, WORD PTR gb_loc + 6 xchg bl, bh cmp ax, bx jae lc_fin mov bx, ax mov ax, si jmps lc_lptp0 lc_fin: ;Restore Reg's: pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret add_bonus: ;Add bonus points (for unused row) to score. & update ; display. ;Calling: Nothing. ;Return: Nothing. (misc var's & screen) ;Notes: All registers preserved. ; Screen tallies updated. ;Preserve Reg's: push si ! push di mov si, OFFSET bonus_score_bcd6 mov di, OFFSET gb_score call add2bcd6 ;Bonus added. call update_tallies ;Restore Reg's: pop di ! pop si ret axbx_stofgb: ;Make AX,BX -> Make AX,BX -> start of row beneath game board. ;Calling: Nothing (gb_loc) ;Return: AX,BX (BCD) ;Notes: All common reg's preserved. BX not validated. ; Changed: Now makes AX, BX == 05, gb_loc (row) + 1 mov ax, WORD PTR gb_loc + 6 xchg al, ah inc al aaa mov bx, ax ; mov ax, WORD PTR gb_loc ; xchg al, ah mov ax, 5 ret redraw_gb: ;Erase then redraw the active area of the game board up to & including ; row BX (bcd) ;Calling: BX = Row number (BX = --(gb_loc + 6) for full redraw) ;Return: Nothing (screen) ;Notes: All common reg's preserved. BX not validated. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ! push es mov si, bx ;SI = Target Row (was bx). mov es, gb_data_seg ;ES = segment of data image. xor di, di ;ES:DI-> 1st byte in game board. mov ax, WORD PTR gb_loc mov bx, WORD PTR gb_loc + 2 xchg al, ah ! xchg bl, bh ;BX = 1st row of game board. inc al aaa ;zAX = 1st column of game board. mov dh, ' ' ;DH = ' ' (space) rdgb_row_looptop: call poscur mov cl, gb_cols_rows ! xor ch, ch ;CX = num char's to write. rdgb_col_looptop: mov dl, ES: BYTE PTR [di] ;DL = char from data image. inc di ;ES:DI -> Next byte to read. cmp dl, 0B0h ;Is DL = blank char? jne rdgb_wchar rdgb_wblank: mov dl, dh ;DL = ' ' ;Fall into... rdgb_wchar: call write2con ;Fall into... rdgb_written: loop rdgb_col_looptop ;Write next column. rdgb_col_loopend: ;Write another row??? cmp bx, si je rdgb_row_loopend xchg ax, bx inc al aaa xchg ax, bx jmps rdgb_row_looptop rdgb_row_loopend: ;All done! ;Restore registers... pop es ! pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret scan4frs: ;Scan for full rows. Draw fancy line as appropriate. ;Calling: Nothing ;Return: Nothing ;Notes: All common reg's preserved. ; 'update_tallies' called after each complete line. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di mov si, OFFSET current_gpiece mov ax, row_equ xchg al, ah ;AX = Row to start checking at-- ;Row is assumed valid. mov cx, 0400h ;CH = num rows to check, CL = complete ; rows. sfr_looptop: mov bx, ax ;BX = cur row. call chk_row ;Check if current row is complete. jnc sfr_skip1 ;CF set if row is complete. If complete... ;If this point is reached, we have found a completed line! inc cl ;Inc score reg. call draw_fline ;Draw fancy line. call scroll_down ;Scroll game board & data image down. call update_score ;Adj. score. mov di, OFFSET gb_rcount ;Is row left to complete count == 0? call isbcd60 ; jz sfr_skip0 ;Yes, then jump. No then... mov si, OFFSET neg1_in_bcd6 call add2bcd6 ;Fall into... sfr_skip0: call update_tallies ;Update display. ;Fall into... sfr_skip1: dec ch ;Dec loop counter. jz sfr_lpend ;If loop counter is zero, jump. mov ax, bx ;AX = current row. inc al aaa jmps sfr_looptop sfr_lpend: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret process_input: ;Get input key and move game piece accordingly. ;Calling: Nothing ;Return: Nothing ;Notes: All common reg's preserved. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di call chk4key ;AL = 0 if no keypress waiting, !0 otherwise. ;CF = Set if no keypress waiting, Clr otherwise. jnc pi_keypressed jmp pi_fin ;No key pressed? Then jump! pi_keypressed: ;If this point is reached, a key was pressed. call getkey ;If this point is reached, AL = key pressed. pi_makeucase: ;make AL upper case... cmp al, 'a' jb pi_makeucase_fin cmp al, 'z' ja pi_makeucase_fin sub al, 32d ;AL now Uppercase! ;Fall into... pi_makeucase_fin: pi_down: cmp al, dn_key jne pi_ndown mov dh, cur_wait_time sar dh, 1 sar dh, 1 mov cur_wait_time, dh xor dh, dh call set_timer jmps pi_fin pi_ndown: pi_right: cmp al, rt_key jne pi_nright call move_right jmps pi_fin pi_nright: pi_left: cmp al, lt_key jne pi_nleft call move_left jmps pi_fin pi_nleft: pi_sound: cmp al, sound_key jne pi_nsound xor option, sound_equ jmps pi_fin pi_nsound: IF CPM ; pi_statline: ; cmp al, statline_key ; jne pi_nstatline ; xor option, statline_equ ; test option, statline_equ ; jz pi_sl_off ; pi_sl_on: ; mov si, OFFSET statline_on_msg ; call ansi_str ; jmps pi_fin ; pi_sl_off: ; mov si, OFFSET statline_off_msg ; call ansi_str ; jmps pi_fin ; pi_nstatline: ;NOTE: Starting with ver. 1.2, disabling the status line is no longer ; supported. ENDIF pi_quit: cmp al, quit_key jne pi_nquit or option, quit_equ jmps pi_fin pi_nquit: pi_redraw: cmp al, redraw_key jne pi_nredraw call redraw jmps pi_fin pi_nredraw: pi_rotate: cmp al, rotate_key jne pi_nrotate call move_rotate ;Rotate game piece. jmps pi_fin pi_nrotate: pi_advlvl: cmp al, advlvl_key jne pi_nadvlvl or option, advlvl_equ jmps pi_fin pi_nadvlvl: pi_fin: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret redraw: ;Redraw the screen. ;Calling: Nothing. ;Return: Nothing (screen). ;Notes: All common reg's preserved. ; Next game piece written to screen. ; 'update_tallies' called. ; 'redraw_gb' called. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Erase Console... mov si, OFFSET ansi_clrscr call ansi_str ;Redraw Screen.... call draw_background ;-Background. mov si, OFFSET gb_scrn_dpcsn call draw_gpcs2scr ;-Next game piece. call update_tallies ;-Tallies. ;Redraw the active area of the game board. mov ax, WORD PTR gb_loc + 6 xchg al, ah dec al aas mov bx, ax call redraw_gb rd_fin: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret center_curgp: ;Position current game piece in dead center at start ; of game board. ;Calling: Nothing (current_gpiece) ;Return: Nothing (current_gpiece) ;Notes: All common reg's preserved. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di mov si, OFFSET current_gpiece ;SI -> current_gpiece. ;Center current_gpiece on row... mov al, gb_cols_rows sar al, 1 ;AL = gb_cols_rows/2 (binary) sub al, 2 ;AL = AL - 2 (2= half of gpcs cols) aam ;AX = col to drop new piece (BCD) ; (relative to start of gb) add al, gb_loc + 1 aaa add ah, gb_loc ;AX = col to drop new piece (BCD) inc al ;Compensate for gb_loc... aaa ; ...being = col -1. xchg al, ah mov col_equ, ax ;Save current_gpiece start col. ;Put game piece at top of game board. mov ax, WORD PTR gb_loc + 2 mov row_equ, ax ;AX = 1st row of game board. ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret clr_vars: ;Clear misc game variables. Useful if restarting game. ;Calling: Nothing. ;Return: Nothing. ;Notes: All common reg's preserved. ;Preserve registers... push ax ! push cx ! push si mov al, init_wait_time_equ mov wait_time, al mov ax, cur_game_table mov cur_game_table, ax ;Erase all BCD6 tallies... mov si, OFFSET gb_count_table mov cx, 9 ;Number of gb_count_table entries to clear. cv_lptop0: call clr_bcd6 add si, 7 ;Point to next structure. loop cv_lptop0 mov al, players_equ mov player_life, al mov di, OFFSET gb_level mov si, OFFSET one_in_bcd6 call add2bcd6 mov option, default_options_equ ;Initialize options record flags as ; required. ;Restore registers... pop si ! pop cx ! pop ax ret inc_level: ;increment level count/pointer. ;Calling: cur_game_table/gb_level var. ;Return: misc variables ;Notes; All common reg's preserved. ; Increments 'gb_level' each time this proc is called. ;Preserve Reg's. push si ! push di ;Inc level count... mov si, OFFSET one_in_bcd6 mov di, OFFSET gb_level call add2bcd6 ;Increment 'cur_game_table'... mov si, cur_game_table add si, 5 cmp BYTE PTR [si], '$' jne pl_cgt_inc mov si, OFFSET game_table pl_cgt_inc: mov cur_game_table, si ;Restore Reg's pop di ! pop si ret process_level: ;Read cur_game_table entry, process, increment. Does not write to scrn. ;Calling: cur_game_table var. ;Return: misc variables ;Notes; All common reg's preserved. ; Increments 'gb_level' each time this proc is called. ; Does not write to screen! ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Prepare to process game level info... mov si, cur_game_table ;Adj wait_time variable... mov al, wait_time sub al, gt_wt_equ ja pl_save_wt mov al, 1 ;(1 = min wait time) pl_save_wt: mov wait_time, al mov cur_wait_time, al ;Line walls with blocks? cmp gt_lw_equ, 0 je pl_skip_lw ;If this point is reached, we have to line the walls with blocks... mov cl, gt_lw_equ ! xor ch, ch ;CX = number of col's to line ; on each side. mov ax, WORD PTR gb_loc xchg ah, al ;AX = Right col of gb. mov dx, WORD PTR gb_loc + 4 xchg dh, dl ;DX = Left col of gb. mov si, dx ;SI = " " " " mov bx, WORD PTR gb_loc + 2 xchg bh, bl ;BX = top row of gb. mov dx, WORD PTR gb_loc + 6 xchg dh, dl ;DX = bot row of gb. mov di, dx ;DI = " " " " mov dl, piece_char ;When this point is reached: ;AX, BX = gb_loc top left corner. ;CX = num col's to fill (each side) ;SI = Last col of gb_loc ;DI = bottom row of gb_loc ;DL = piece_char pl_lw_lptp0: inc al ;Move in one row... aaa ; xchg ax, si ; dec al ; aaa ; xchg ax, si ; push bx ;Save 1st row. pl_lw_lptp1: ;Write to screen & image data... ; call poscur ;First left column... ; call write2con call write2image xchg ax, si ; call poscur ;Then right column... ; call write2con call write2image xchg ax, si ;move down one row... xchg ax, bx inc al aaa xchg ax, bx cmp bx, di ;Have we reached the bottom row? jb pl_lw_lptp1 ;No, then continue! pl_lw_lpend1: pop bx ;Restore 1st row. loop pl_lw_lptp0 pl_lw_lpend0: mov si, cur_game_table ;Make SI -> cur_game_table once more. pl_skip_lw: ;Draw checkerboard pattern? cmp gt_cs_equ, 0 je pl_skip_cb ;No, then jump. Yes then... mov cl, gb_cols_rows ;CL = col's per row. mov ch, gt_cr_equ ;CH = Num rows to fill. mov al, gt_cs_equ aam mov bx, ax ;BX = start CB row relative ; to end of gb. (BCD) mov ax, WORD PTR gb_loc + 6 xchg al, ah sub al, bl aas sub ah, bh mov bx, ax ;BX = Start CB row (BCD) mov ax, WORD PTR gb_loc xchg al, ah inc al aaa mov si, ax ;SI = 1st col to write to. ;When this point is reached... ;BX = 1st row to write to. ;CL = Col's per row (writes per row) ;CH = Number of rows to write to. ;SI = 1st Column to write to. pl_cb_row: mov ax, si ;AX = 1st col. (BX assumed set) ; call poscur ;Pos cursor. mov di, cx ;Save CX in DI. pl_cb_col: mov dl, bl mov dh, cl and dx, 0101h xor dl,dh jnz pl_cb_col1 pl_cb_col0: mov dx, ' °' ;DL = '°', DH = ' ' call write2image ; xchg dl, dh ; call write2con jmps pl_cb_nextcol pl_cb_col1: mov dl, '÷' ;DL = '÷' call write2image ; call write2con ;Fall into... pl_cb_nextcol: inc al aaa ;AX -> next column. dec cl jne pl_cb_col ;If this point is reached, process next row. mov cx, di ;Restore CX. xchg bx, ax inc al aaa xchg bx, ax ;Row incremented. dec ch jnz pl_cb_row ;Fall into... pl_skip_cb: ;Erase current gb_rcount... mov si, OFFSET gb_rcount call clr_bcd6 ;Clear gb_rcount. ;Set gb_rcount mov di, si ;DI -> gb_rcount mov si, cur_game_table ;SI -> cur_game_tablech mov al, gt_rows_equ aam ;AX = num rows to win level (BCD) mov WORD PTR [di], ax ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret update_tallies: ;Update all count/score display's. ;Calling: Nothing. (misc var's) ;Return: Screen. ;Notes: All common reg's preserved. ; Game background assumed drawn. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di mov di, OFFSET gb_count_table mov si, OFFSET gb_scrn_count_table mov ax, 6 ;number of bytes in a 'gb_scrn_xxx' var. mov bx, 7 ;num of bytes in a 'gb_xxxxx' var. mov cx, 9 ;num of items to display. ut_looptop: call disp_bcd6 add si, ax add di, bx loop ut_looptop ;Update lives left display. mov ax, WORD PTR gb_loc xchg al, ah inc al aaa mov bx, WORD PTR gb_loc + 6 xchg bl, bh call poscur mov dl, player_life add dl, '0' call write2con ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret gen_next_gp: ;Generate next game piece. ;Calling: Nothing. ;Return: Nothing. (gb_scrn_dpcsn variable) ;Notes: All common reg's preserved. ; pcs_equ set to equal value of piece_char. ;Preserve registers... push ax ! push bx ! push si mov al, BYTE PTR random_num ! xor ah, ah mov bl, BYTE PTR random_num and bl, 3d ;BL = Next game piece angle. mov bh, 5d div bh ;AH = Next game piece. ;Save results to 'gb_scrn_dpcsn'... mov si, OFFSET gb_scrn_dpcsn mov angle_equ, bl mov pcs_equ, ah mov al, piece_char mov draw_char_equ, al ;Return... ;Restore registers... pop si ! pop bx ! pop ax ret add2bcd6: ;Add a six digit unpacked BCD variable. ;Calling: SI -> 000,000 amount to add. ; DI -> 000,000 sum. ; Where: 000,000 == Byte No. [LSB]xx,xx[MSB] ;Return: [sum] ;Notes: All common calling reg's preserved. ; BCD values assumed unpacked & in LSB---MSB ; order! ; CLD assumed. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ! push es mov ax, ds mov es, ax ;initialize ES = DS xor ah, ah mov cx, 3 a2b6_looptop0: lodsb ;AL = addend byte DS:SI add al, ah mov ah, 0 aaa add al, BYTE PTR [di] aaa stosb ;Save results in ES:DI loop a2b6_looptop0 inc si ;Skip inc di ; ',' mov cx, 3 a2b6_looptop1: lodsb ;AL = addend byte DS:SI add al, ah mov ah, 0 aaa add al, BYTE PTR [di] aaa stosb ;Save results in ES:DI loop a2b6_looptop1 ;Restore registers... pop es ! pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret disp_bcd6: ;Display a BCD6 number at a location specified. ;Calling: DI -> BCD6 number ; SI -> ANSI string to position cursor. ;Return: Screen. ;Notes: All common registers preserved. ; Numbers displayed are blank leading right justified. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di call ansi_str ;Position cursor. add di, 6 ;DI-> MSB mov cx, 7 ;CX = num char's to check. db6_skspace_lptp: cmp cx, 1 ;Always display LSB even if it's jbe db6_skipspace_lpend ; zero. mov al, BYTE PTR [di] cmp al, 0 je db6_wsp cmp al, ',' jne db6_skipspace_lpend db6_wsp: mov dl, ' ' call write2con dec di loop db6_skspace_lptp db6_skipspace_lpend: jcxz db6_fin db6_wnum_lptp: mov al, BYTE PTR [di] cmp al, ',' je db6_wnum_c or al, 30h db6_wnum_c: ;Jumped to if AL = ',' mov dl, al call write2con dec di loop db6_wnum_lptp db6_fin: ;When this point is reached, BCD6 number was written to the screen! ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret clr_bcd6: ;Clear a BCD6 string pointed to by [SI]. ;Calling: DS:SI -> BCD6 string to clear. ;Return: Nothing. ;Notes: All common reg's preserved. ;Preserve registers... push ax xor ax, ax mov WORD PTR [si], ax mov BYTE PTR 2[si], al mov BYTE PTR 3[si], ',' mov WORD PTR 4[si], ax mov BYTE PTR 6[si], al ;Restore registers... pop ax ret isbcd60: ;Is a BCD6 number = 0? ;Calling: DI -> BCD6 number. ;Return: ZF set if 0. ;Notes: All common reg's preserved. ;Preserve registers... push ax xor ax, ax cmp WORD PTR [di], ax jnz isbz_fin cmp BYTE PTR 2[di], al jnz isbz_fin cmp WORD PTR 4[di], ax jnz isbz_fin cmp BYTE PTR 6[di], al isbz_fin: ;Restore registers... pop ax ret isbcd61: ;Is a BCD6 number = 1? ;Calling: DI -> BCD6 number. ;Return: ZF set if EQ 1. ;Notes: All common reg's preserved. ;Preserve registers... push ax xor ax, ax inc al cmp WORD PTR [di], ax jne isb1_fin dec al cmp BYTE PTR 2[di], al jne isb1_fin cmp WORD PTR 4[di], ax jne isb1_fin cmp BYTE PTR 6[di], al isb1_fin: ;Restore registers... pop ax ret update_counts: ;Update's game piece count variables. Does not update display. ;Calling: DL = game piece. ;Return: Nothing. ;Notes: All common mareg's preserved. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di mov si, OFFSET one_in_bcd6 mov di, OFFSET gb_totcount call add2bcd6 ;make sure DL is valid... cmp dl, 4 ja uc_fin ;DL valid. mov al, 7 ;AL = num bytes in gb_pXcount mul dl ;AX = 7 * (size of gb_pXcount) mov di, OFFSET gb_count_table add di, ax ;DS:DI -> gb_pXcount. ;dI -> bcd6 var to increment. ;si ASSUMED STILL -> one_in_bcd6 call add2bcd6 uc_fin: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret update_score: ;Update's score tallay. Does not update display. ;Calling: CL = score multiple (1-4) ;Return: gb_p#count variables. ;Notes: All reg's preserved. ;Preserve registers... push ax ! push cx ! push si ! push di xor ch, ch mov si, OFFSET line_score_bcd6 mov di, OFFSET gb_score jcxz us_loopend us_looptop: call add2bcd6 loop us_looptop us_loopend: ;Restore registers... pop di ! pop si ! pop cx ! pop ax ret chk_row: ;Check row to determine if it is completed. ;Calling: BX = row (BCD) & Misc. Variables. ;Return: CF set if row is complete. ;Notes: All common reg's preserved. ; BX assumed within gb_loc but not verified! ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Make DI-> Start of row... ;Make AL == row in binary (relative to start of game board). mov ax, bx ;AX = row to check. (BCD) sub al, BYTE PTR gb_loc + 3 aas sub ah, BYTE PTR gb_loc + 2 jl cr_notcomp ;Verify row >= gb top row aad ;AL = gb_loc (top row) - row to chk. ;Make AX -> start of row in image data. mov ah, gb_cols_rows mov cl, ah ;Save gb_cols_rows in CL. xor ch, ch ;CX = CL = Num bytes to check. mul ah ;AX = offset to start of row to chk. mov di, ax ;gb_data_seg:DI -> st of row. mov es, gb_data_seg ;ES:DI -> st of row. mov al, '°' ;AL = char to scan for. repne scasb je cr_notcomp cr_complete: stc jmps cr_fin cr_notcomp: clc ;Fall into... cr_fin: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret draw_fline: ;Draw fancy line. ;Calling: BX = Row number (misc variables) ;Return: Screen. ;Notes: All common registers preserved. ; BX assumed within gb_loc but not verified! ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di mov cl, gb_cols_rows ;CL = number of col's in a row xor ch, ch ;CX = number of col's in a row mov si, cx ;SI = CX (Save a copy of CX in SI) mov ax, WORD PTR gb_loc xchg al, ah inc al aaa ;Draw 'ð' line (line 1 of 3)... ;CX (&SI) assumed set to equal number of loops. ;BX assumed set to row number (BCD) ;AX assumed set to 1st col to write to. call poscur mov dx, 05F0h ;DH = 5 (timer tick), DL = 'ð' (char to draw) dfl_looptop1: call write2con loop dfl_looptop1 ;Line of 'ð' drawn. call set_timer ;Wait DH timer ticks... dfl_wait1: call chk_timer jnz dfl_wait1 ;Draw '=' line (line 2 of 3)... ;SI assumed set to equal number of loops. ;BX assumed set to row number (BCD) ;AX assumed set to 1st col in gb. call poscur mov dl, 3Dh ;DH = 10 (timer tick), DL = '=' (char to draw) mov cx, si ;CX = number of loops. dfl_looptop2: call write2con loop dfl_looptop2 ;Line of '=' drawn. call set_timer ;Wait DH timer ticks... dfl_wait2: call chk_timer jnz dfl_wait2 ;Draw '-' line (line 3 of 3)... ;SI assumed set to equal number of loops. ;BX assumed set to row number (BCD) ;AX assumed set to 1st col. call poscur mov dl, 2Dh ;DL = '-' (char to draw) mov cx, si ;CX = number of loops. dfl_looptop3: call write2con dfl_skip3: loop dfl_looptop3 ;Line of '-' drawn. call set_timer ;Wait DH timer ticks... dfl_wait3: call chk_timer jnz dfl_wait3 ;Erase line (i.e. draw ' ' line (line 4 of 3))... ;SI assumed set to equal number of loops. ;BX assumed set to row number (BCD) ;AX assumed set to 1 bcd. call poscur mov dl, ' ' ;DL = ' ' (char to draw) mov cx, si ;CX = number of loops. dfl_looptop4: call write2con loop dfl_looptop4 ;Make beep sound? test option, sound_equ jz dfl_nobeep mov dl, beep ;prepare for 'beep' call write2con ;'beep'! dfl_nobeep: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret scroll_down: ;Scroll screen & image data down one line. ;Calling: BX = Row number in BCD ;Return: Nothing (Screen) ;Notes: All common registers preserved. ; BX assumed within gb_loc but not verified! ;Preserve registers... pushf ! push ax ! push bx ! push cx ! push dx ! push si ! push di ! push es mov es, gb_data_seg ;Make ES = image data seg. ;Is BX = TOP row? (if so, all we need to do is erase the top row. mov ax, WORD PTR gb_loc + 2 xchg al, ah ;AX = top row. BCD cmp bx, ax jae sd_nottop jmp sd_killtoprow sd_nottop: sd_scrollimage: ;Scroll the image data down one row... ;AX assumed == top row of gb (BCD). push bx ;Save bx. ;------ xchg ax, bx ;AX = row, BX = top row (both BCD) sub al, bl aas sub ah, bh aad ;AL = row as offset into data image. mov bl, gb_cols_rows mul bl ;AX -> Start of target line. add al, bl adc ah, 0 dec ax ;AX -> End of target line. mov di, ax ;DI -> End of target line. sub al, bl sbb ah, 0 ;AX -> End of source line. mov si, ax ;SI -> End of source line. mov cx, si inc cx ;CX = Num bytes to move. ;ES:DI -> End of target line. mov bx, ds ;Save DS in BX mov ds, gb_data_seg ;DS:SI -> End of source line. ;DS = ES. std ;Set direction flag-- DI & SI dec. rep movsb mov ds, bx ;Restore DS. pop bx ;Restore BX. ;----- sd_scrollimage_fin: ;When this point is reached, the data in the image data memory has ; been scrolled one line down. (The top line still must be erased.) sd_killtoprow: ;Erase the top row. (image data) mov cl, gb_cols_rows ! xor ch, ch ;CX = num bytes to erase. xor di, di ;DI = 0, ES:DI-> Start of image. mov al, '°' cld rep stosb ;First line erased. ;Now that we've taken care of the image data, update the screen... call redraw_gb ;All done! ;Restore registers... pop es ! pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ! popf ret next2cur: ;Copy next game piece record to current game piece. ;Calling: Nothing (gb_scrn_dpcsn) ;Return: Nothing (current_gpiece) ;Notes: All reg's preserved. ;Preserve registers... push cx ! push si ! push di mov cx, ds mov es, cx mov si, OFFSET gb_scrn_dpcsn mov di, OFFSET current_gpiece mov cx, LENGTH current_gpiece rep movsb ;Restore registers... pop di ! pop si ! pop cx ret temp2cur: ;Copy temp_gpiece to current_gpiece. ;Calling: Nothing (temp_gpiece). ;Return: Nothing (current_gpiece) ;Notes: All reg's preserved. ; CLD assumed. ;Preserve registers... push cx ! push si ! push di mov cx, ds mov es, cx mov si, OFFSET temp_gpiece mov di, OFFSET current_gpiece mov cx, LENGTH current_gpiece rep movsb ;Restore registers... pop di ! pop si ! pop cx ret cur2temp: ;Copy current_gpiece to temp_gpiece. ;Calling: Nothing (current_gpiece) ;Return: Nothing (temp_gpiece). ;Notes: All reg's preserved. ; CLD assumed. ;Preserve registers... push cx ! push si ! push di mov cx, ds mov es, cx mov di, OFFSET temp_gpiece mov si, OFFSET current_gpiece mov cx, LENGTH temp_gpiece rep movsb ;Restore registers... pop di ! pop si ! pop cx ret move_right: ;Move current game piece right. ;Calling: (Misc. Variables) ;Return: (Misc. Variables) ;Notes: All common registers preserved. ;Preserve registers... push ax ! push dx ! push si ;Make temp_gpiece = current_gpiece... call cur2temp ;Make SI -> temp_gpiece... mov si, OFFSET temp_gpiece mov al, piece_char mov draw_char_equ, al ;Inc. 'xx' loc in temp_gpiece... mov ax, col_equ xchg al, ah add al, 1 aaa xchg al, ah mov col_equ, ax ;Check for obstruction... call chk4obs test dl, dl jz mr_clear ;Else fall into... mr_fin: ;Restore reg's... pop si ! pop dx ! pop ax ret mr_clear: ;If this point is reached, its okay to move right! mov si, OFFSET current_gpiece ;Make SI-> current_gpiece. mov draw_char_equ, '°' call draw_gpcs2image ;Erase data image. mov draw_char_equ, ' ' call draw_gpcs2scr ;Erase screen image. call temp2cur call draw_gpcs2image ;Draw piece to data image. call draw_gpcs2scr ;Draw piece to screen. jmps mr_fin move_rotate: ;Rotate current game piece (if it is possible to do so). ;Calling: (Misc. Variables) ;Return: (Misc. Variables) ;Notes: All common registers preserved. ;Preserve registers... push ax ! push dx ! push si ;Make temp_gpiece = current_gpiece... call cur2temp ;Make SI -> temp_gpiece... mov si, OFFSET temp_gpiece mov al, piece_char mov draw_char_equ, al ;Inc. 'Angle' loc in temp_gpiece (make 0 if > 3)... mov al, angle_equ inc al and al, 3 mov angle_equ, al ;Check for obstruction... call chk4obs test dl, dl jz mrot_clear ;Else fall into... mrot_fin: ;Restore reg's... pop si ! pop dx ! pop ax ret mrot_clear: ;If this point is reached, its okay to rotate game piece! mov si, OFFSET current_gpiece ;Make SI-> current_gpiece. mov draw_char_equ, '°' call draw_gpcs2image ;Erase data image. mov draw_char_equ, ' ' call draw_gpcs2scr ;Erase screen image. call temp2cur call draw_gpcs2image ;Draw piece to data image. call draw_gpcs2scr ;Draw piece to screen. jmps mrot_fin move_left: ;Move current game piece left.. ;Calling: (Misc. Variables) ;Return: (Misc. Variables) ;Notes: All common registers preserved. ;Preserve registers... push ax ! push dx ! push si ;Make temp_gpiece = current_gpiece... call cur2temp ;Make SI -> temp_gpiece... mov si, OFFSET temp_gpiece mov al, piece_char mov draw_char_equ, al ;Dec. 'xx' loc in temp_gpiece... mov ax, col_equ xchg al, ah sub al, 1 aas xchg al, ah mov col_equ, ax ;Check for obstruction... call chk4obs test dl, dl jz ml_clear ;Else fall into... ml_fin: ;Restore reg's... pop si ! pop dx ! pop ax ret ml_clear: ;If this point is reached, its okay to move left! mov si, OFFSET current_gpiece ;Make SI-> current_gpiece. mov draw_char_equ, '°' call draw_gpcs2image ;Erase data image. mov draw_char_equ, ' ' call draw_gpcs2scr ;Erase screen image. call temp2cur call draw_gpcs2image ;Draw piece to data image. call draw_gpcs2scr ;Draw piece to screen. jmps ml_fin move_downc: ;Move current game piece down.. Return CF set if can't. ;Calling: (Misc. Variables) ;Return: (Misc. Variables) ;Notes: All common registers preserved. ; Returns CF set if movement isn't possible. ;Preserve registers... push ax ! push dx ! push si ;Make temp_gpiece = current_gpiece... call cur2temp ;Make SI -> temp_gpiece... mov si, OFFSET temp_gpiece ;Inc. 'yy' loc in temp_gpiece... mov ax, row_equ xchg al, ah add al, 1 aaa xchg al, ah mov row_equ, ax ;Check for obstruction... call chk4obs test dl, dl jz md_clear stc ;Set carry flag. ;Else fall into... md_fin: ;Restore reg's... pop si ! pop dx ! pop ax ret md_clear: ;If this point is reached, its okay to move down! mov si, OFFSET current_gpiece ;Make SI-> current_gpiece. mov draw_char_equ, '°' call draw_gpcs2image ;Erase data image. mov draw_char_equ, ' ' call draw_gpcs2scr ;Erase screen image. call temp2cur call draw_gpcs2image ;Draw piece to data image. call draw_gpcs2scr ;Draw piece to screen. clc ;Clear carry flag. jmps md_fin draw_background: ;Draw the game board background. ;Calling: Nothing (misc variables) ;Return: Nothing. ;Notes: All common registers preserved. ; Screen assumed cleared. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Draw vertical lines... ;make cx = number of rows to draw... mov ax, WORD PTR gb_loc + 6 xchg al, ah ;AX = Last row (BCD) mov cx, WORD PTR gb_loc + 2 xchg cl, ch ;CX = First row (BCD) sub al, cl aas sub ah, ch add al, 1 aaa aad xchg ax, cx ;CX = Number of rows to draw. ;(AX = Start row) ;make BX = Starting Row. mov bx, ax ;Make SI = Second column. mov ax, WORD PTR gb_loc + 4 xchg al, ah mov si, ax ;SI = Second Column (BCD) ;Make AX = First column. mov ax, WORD PTR gb_loc xchg al, ah ;AX = First Column (BCD) ;make dl = char to write with... mov dl, block_char jmps db_vert_loopstart db_vert_looptop: ;inc row number. xchg ax, bx ;AX = row number (BCD) inc al aaa xchg ax, bx ;AX = Column number (BCD) db_vert_loopstart: call poscur call write2con xchg ax, si call poscur call write2con xchg ax, si loop db_vert_looptop db_vert_loopend: ;Draw bottom part of game board "U"... ;AX, BX assumed -> start of hor line to draw - 1. ;AX, SI assumed -> end of hor line to draw + 1. ;DL = char to draw with. mov cx, ax ;Save Start Col in cx. mov ax, si ;AX = end column sub al, cl aas sub ah, ch ;AX = end col - start col sub al, 1 aas ;AX = number of char's to draw (BCD). aad ;AX = number of char's to draw (binary). xchg ax, cx ;CX = num char's to draw (binary) inc al aaa ;AX = Start Column (BCD) ;When this point is reached... ; AX,BX -> Start of hor line. ; CX = number of line segments to write. ; DL = Char to draw with. call poscur db_hor_looptop: call write2con loop db_hor_looptop ;Game board drawn! ;----------------- ;Add cursory text... mov si, OFFSET gb_screen call ansi_str ;Prepare to draw sample game pieces... ;1. Draw all samples with char piece_char... mov al, piece_char mov bx, 6d mov gb_scrn_dpcs0[bx], al mov gb_scrn_dpcs1[bx], al mov gb_scrn_dpcs2[bx], al mov gb_scrn_dpcs3[bx], al mov gb_scrn_dpcs4[bx], al mov gb_scrn_dpcsn[bx], al ;Also initialize char for next pcs disp. ;2. Draw pieces... mov si, OFFSET gb_scrn_dpcs0 call draw_gpcs2scr mov si, OFFSET gb_scrn_dpcs1 call draw_gpcs2scr mov si, OFFSET gb_scrn_dpcs2 call draw_gpcs2scr mov si, OFFSET gb_scrn_dpcs3 call draw_gpcs2scr mov si, OFFSET gb_scrn_dpcs4 call draw_gpcs2scr db_fin: ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret draw_gpcs2scr: ;Draw a game piece to the screen. ;Calling: si -> piece structure where... ; col db x, x }Unpacked BCD ; row db y, y } ; pcs db Piece number (0-4) ; angle db Angle 0-3 ; Where: 0=0ø, 1=90ø, ; 2=180ø, 3=270ø ; draw_chr db Character to draw with. ;Return: Screen. ;Notes: All registers saved. ; Calling registers are not verified. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Get pointer to picture of piece... mov al, pcs_equ mov ah, (4*4)*4 mul ah ;AX = Game Piece # * (size of single ; game piece data) * number of angle. mov bx, ax mov ah, 4*4 mov al, angle_equ mul ah ;AX = game piece angle * size of single ; game piece data add ax, bx add ax, OFFSET pcs_table mov di, ax ;DI ->1st char of game piece. mov cx, 0404h ;CL & CH = loop counters. mov ax, col_equ mov bx, row_equ xchg al, ah ;AX = Col to draw at. xchg bl, bh ;BX = Row to draw at. dg_row_looptop: call poscur dg_col_looptop: mov dl, BYTE PTR [di] ;DL = picture element. inc di ;Point di to next game piece picture element. cmp dl, '°' ; je dg_blank ;Jump if pix to draw is blank ('°'). dg_char: mov dl, draw_char_equ call write2con jmps dg_written dg_blank: push si ;Preserve SI mov si, OFFSET ansi_forward call ansi_str pop si ;Restore SI ;Fall into... dg_written: ;increment col... inc al aaa ;Dec col counter... dec ch jnz dg_col_looptop dg_col_loopend: ;If this point is reached, its time to start drawing another row. mov ch, 4 ;Reset col counter. mov ax, col_equ xchg al, ah ;AX = col to start writing to. xchg ax, bx ;AX = Row inc al aaa xchg ax, bx ;AX = col. dec cl ;Dec row counter jnz dg_row_looptop ;loop if CL != 0. dg_row_loopend: ;When this point is reached, the game piece has been drawn! ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret draw_gpcs2image: ;Draw specified game piece to data image. ;Calling: si -> piece structure where... ; col db 'x', 'x' ; row db 'y', 'y' ; pcs db Piece number (0-4) ; angle db Angle 0-3 ; Where: 0=0ø, 1=90ø, ; 2=180ø, 3=270ø ; draw_chr db Character to draw with. ;Return: Image data area. ;Notes: All registers saved. ; Calling registers are not verified. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ;Get pointer [DI] to picture of piece... mov al, pcs_equ mov ah, (4*4)*4 mul ah ;AX = Game Piece # * (size of single ; game piece data) * number of angle. mov bx, ax mov ah, 4*4 mov al, angle_equ mul ah ;AX = game piece angle * size of single ; game piece data add ax, bx add ax, OFFSET pcs_table mov di, ax ;DI ->1st char of game piece. ;initialize AX & BX with xx,yy mov ax, col_equ mov bx, row_equ xchg al, ah xchg bl, bh mov cx, 0404h ;Loop counter CH = Row, CL = column. dg2i_lptp: mov dl, BYTE PTR [di] ;DL = byte from game piece picture. inc di ;[di] -> next byte cmp dl, '°' ;If Pixel = '°' (i.e. blank) just je dg2i_skipwrite ; write blank. mov dl, draw_char_equ ;Otherwise write 'draw_char_equ'. call write2image ;DL = char to write. dg2i_skipwrite: dec cl ;Dec column loop counter. jz dg2i_inc_row ;If col loop count = 0, jump. inc al ;Inc col aaa jmps dg2i_lptp ;Loop (column). dg2i_inc_row: ;If this point is reached, 4 columns have been written to. Move ; on to next row. dec ch ;Any more rows to fill? jz dg2i_lpend ;No, then jump. mov ax, bx ;AX = Row. inc al aaa mov bx, ax ;BX = row. mov ax, col_equ xchg al, ah ;Restore AX to start column. mov cl, 4 ;Restore column loop counter. jmps dg2i_lptp dg2i_lpend: ;Game piece written to data! ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret write2image: ;Write to x,y coordinate specified. ;Calling: AX, BX = Column, Row to move cursor to (top right = 1,1,) ; DL: Char to write. ; Where: ; AH= '0'-'2' & AL= '0'-'9' ; BH= '0'-'8' & BL= '0'-'9' ;Return: Nothing. ; AX & BX are verified before they are used. Out of range ; values are ignored. ; It is assumed that gb_data_seg has been properly ; initialized before this routine is called. ;Preserve registers... push ax ! push bx ! push dx ! push es ;Verify that AX, BX do not point before start of gb... sub al, gb_loc + 1 ;Sub 1st col from AX aas sub ah, gb_loc aas dec al aas test ah, ah js w2i_axbx_outofrange xchg bx, ax ;AX = row, BX = col sub al, gb_loc + 3 ;Sub 1st row from AX aas sub ah, gb_loc + 2 aas test ah, ah js w2i_axbx_outofrange xchg bx, ax ;AX = col, BX = row ;If this point was reached, AX, BX > gb_loc ;Make (adjusted) AX, BX a binary offset. aad ;AL = binary col number xchg bx, ax aad ;AL = binary row number mov ah, gb_cols_rows mul ah ;AX = cols per rows * rows. add al, bl adc ah, 0 ;AX = orig AX,BX as a offset into image data. cmp ax, gb_bytes jae w2i_axbx_outofrange ;If pointer would be beyond data image ; abort! ;If this point is reached, its okay to write! mov es, gb_data_seg mov bx, ax mov al, dl mov ES: BYTE PTR [bx], al ;fall into... w2i_axbx_outofrange: ;(also w2i_fin:) ;Restore registers... pop es ! pop dx ! pop bx ! pop ax ret readfimage: ;Read a specified char from the image data. ;Calling: AX, BX = Column, Row to move cursor to (top right = 1,1,) ; Where: ; AH= '0'-'2' & AL= '0'-'9' ; BH= '0'-'8' & BL= '0'-'9' ;Return: DH = character present. ; CF Set if AX,BX point was beyond game board. ; It is assumed that gb_data_seg has been properly ; initialized before this routine is called. ; If AX,BX are invalid, DH will be random. ;Preserve registers... push ax ! push bx ! push cx ! push es ;Verify AX is > gb_loc start column mov cx, WORD PTR gb_loc xchg cl, ch cmp ax, cx jbe rf_invalid ;Verify AX is < gb_loc end column mov cx, WORD PTR gb_loc + 4 xchg ch, cl cmp ax, cx jae rf_invalid ;Verify BX is >= gb_loc start row mov cx, WORD PTR gb_loc + 2 xchg ch, cl cmp bx, cx jb rf_invalid ;Verify BX is < gb_loc end row. mov cx, WORD PTR gb_loc + 6 xchg ch, cl cmp bx, cx jae rf_invalid ;If this point is reached, AX,BX is a valid point on the game board! sub al, gb_loc + 1 aas sub ah, gb_loc aad dec al ;AL now == col relative to start of gb. (bin) ;(1st col inside gb = 0) xchg bx, ax sub al, gb_loc + 3 aas sub ah, gb_loc + 2 aad ;AL now == row relative to start of gb. (bin) ;(1st row inside gb = 0) mov ah, gb_cols_rows mul ah ;AX = offset to start of row. add al, bl adc ah, 0 ;AX -> char to read. mov bx, ax mov es, gb_data_seg mov dh, ES: BYTE PTR [bx] ;DH now = char at target, DL unchanged. clc ;Make sure CF is clear. rf_fin: ;Restore reg's pop es ! pop cx ! pop bx ! pop ax ret rf_invalid: stc ;Set CF. jmps rf_fin chk4obs: ;Checks for obstructions that might interfer with placing a game piece. ;Calling: si -> piece structure where... ; col db x, x }Unpacked BCD ; row db y, y } ; pcs db Piece number (0-4) ; angle db Angle 0-3 ; Where: 0=0ø, 1=90ø, ; 2=180ø, 3=270ø ; draw_chr db Character to draw with. ; current_gpiece STRUC ;Return: DL = 0 If not obstructed, !=0 if obstructed. ;Notes: All common registers saved. ; Theory of operations: ; 1. Erase current game piece from image area. ; 2. Check [si] structure. ; 3. Restore current game piece to image area. ;Preserve registers... push ax ! push bx ! push cx ! push si ! push di push dx ;1. Erase current game piece from image area. ;============================================ push si mov si, OFFSET current_gpiece mov al, draw_char_equ ;Save draw char. mov draw_char_equ, '°' call draw_gpcs2image ;Erase game piece in image. mov draw_char_equ, al ;Restore draw char. pop si ;2. Check [si] structure. ;======================== ;2a. Get pointer [DI] to picture of piece... mov al, pcs_equ mov ah, (4*4)*4 mul ah ;AX = Game Piece # * (size of single ; game piece data) * number of angle. mov bx, ax mov ah, 4*4 mov al, angle_equ mul ah ;AX = game piece angle * size of single ; game piece data add ax, bx add ax, OFFSET pcs_table mov di, ax ;DI ->1st char of game piece. ;2b. Initialize AX & BX with xx,yy mov ax, col_equ mov bx, row_equ xchg al, ah xchg bl, bh mov cx, 0404h ;Loop counter CH = Row, CL = column. c4o_lptp: mov dl, BYTE PTR [di] ;DL = byte to write. inc di ;[di] -> next byte call readfimage ;DL = picture byte, DH = byte at current location. ;CF set if point was beyond game board. jnc c4o_skip0 mov dh, 'X' ;Mark invalid points as points that ; are occupied. c4o_skip0: cmp dl, '°' ;Was char to write a '°'? je c4o_skip1 ;Yes, then who cares if the point ; to write to already is filled! Jump. cmp dh, '°' ;Is the char at the cur loc a '°'? je c4o_skip1 ;Yes, then jump. ;If this point is reached, the coordinates referenced to by AX,BX ; are valid but neither the char to write nor the char already ; present are '°'. jmps c4o_obstructed c4o_skip1: dec ch ;Dec column loop counter. jz c4o_inc_row ;If col loop count = 0, jump. inc al ;Inc col aaa jmps c4o_lptp ;Loop (column). c4o_inc_row: ;If this point is reached, 4 columns have been checked. Move ; on to next row. dec cl ;Any more rows to fill? jz c4o_lpend ;No, then jump. mov ax, bx ;AX = Row. inc al aaa mov bx, ax ;BX = row. mov ax, col_equ xchg al, ah ;Restore AX to start column. mov ch, 4 ;Restore column loop counter. jmps c4o_lptp c4o_lpend: ;Game piece checked! c4o_clear: xor al, al jmps c4o_fin c4o_obstructed: mov al, 0FFh ;Fall into... c4o_fin: ;Restore DH, set DL... pop dx mov dl, al ;3. Restore current game piece to image area. ;============================================ mov si, OFFSET current_gpiece call draw_gpcs2image ;Restore ;Restore registers... pop di ! pop si ! pop cx ! pop bx ! pop ax ret ; Subroutines ; =========== ;List of Procedures: ;------------------- ;install_isrs ;Install & initialize ISR's. Must be called before timer ; ; procedures are used. ;remove_isrs ;Remove installed ISR's and reinstall original ones. ;new_1c_isr ;New timer tick interrupt vector. Used for timer proc's & ; ; maintains random number. (v1.2) ;new_23_isr ;Restore original 1Ch isr vector upon ^C press. (DOS ONLY!) ; ;get_mem ;Get memory for tracking game board. ;clr_mem ;Clear data image (fill all bytes with '°'). ;release_mem ;Release unused ('shrink') memory (DOS only!). ; Move stack to (larger) internal area (DOS & CP/M-86). ;bye ;Return to OS. ;set_timer ;Set timer to count down DH number of timer ticks. ;chk_timer ;Checks if a previously set amount of time (in timer ticks) has ; elapsed. ;wait4key ;Wait BL system timer ticks for a key press while displaying ; rotating star. install_isrs: ;Install & initialize ISR's. Must be called before timer ; procedures are used. ;Calling: Nothing. ;Return: Nothing. (Timer proc's prepaired for use) ;Notes: All registers peserved. ;Preserve Reg's: push ax ! push bx ! push cx ! push dx ! push es ! push si ! push di ;Initialize 'random_num' variable. ;--------------------------------- xor ax, ax mov es, ax ;ES = 0 (throughout procedure). IF CPM EQ 0 ;DOS... mov ax, ES: WORD PTR .46Ch ;AX = LSW of system timer count. mov random_num, ax ;Save AX ENDIF IF CPM ;CP/M-86... mov cl, 31h ;Fun 31h: Get SysData addrs. int 0E0h ;ES:BX -> SysData mov ax, ES: WORD PTR .44h[bx] xchg al, ah ;AX = ASCII Min's sub ax, 3030h ;AX = BCD Min's. aad ;AL = Min's (binary) mov ah, 60d mul ah ;AX = Seconds. mov dx, ax ;DX = Seconds. mov ax, ES: WORD PTR .47h[bx] xchg al, ah ;AX = ASCII Sec's sub ax, 3030h ;AX = BCD Sec's aad ;AX = Sec's (binary) add ax, dx ;AX = New random (sort of) number. mov random_num, ax ;Save AX xor ax, ax mov es, ax ;Restore ES = 0. ENDIF ;Install new system timer tick ISR & insure that ISR is automatically ; removed upon program termination. ;Install new timer tick isr... mov ax, OFFSET new_1c_isr mov dx, cs ;DX:AX -> new system timer interrupt vector. pushf ;Save flags. cli ;Disable interrupts. xchg ax, tick_isr_off_pntr xchg dx, tick_isr_seg_pntr mov old_1c_isr, ax mov old_1c_isr + 2, dx ;Save original isr vector. ;New timer tick ISR installed! Install new ^C isr... ;CLI still assumed. IF CPM ;CP/M-86... ;Note CP/M-86 version of Tetris3 will NOT restore the original ISR ; vector automatically! ENDIF IF CPM EQ 0 ;DOS... ;Install new Int. 23h (^C ISR) vector at proper PSP location. mov ax, OFFSET new_23_isr mov dx, cs ;DX:AX = new Int. 23h ISR vector. xchg ES: WORD PTR .8Ch, ax xchg ES: WORD PTR .8Eh, dx mov old_23_isr, ax ;Save original Int. 23 ISR vector. mov old_23_isr + 2, dx ; ENDIF popf ;Restore flags. (presumably reinable interrupts) ;Restore Regs... pop di ! pop si ! pop es ! pop dx ! pop cx ! pop bx ! pop ax ret IF CPM EQ 0 ;DOS... new_23_isr: ;Restore original 1Ch isr vector upon ^C press. (DOS ONLY!) ;Calling: Nothing. ;Return: Nothing. ;Notes: This is a ISR. ; Control is passed to original Int. 23h after this ; ISR completes. ;Preserve Reg's push ax ! push ds mov ax, cs mov ds, ax ;Initialize DS. ;call remove_isrs call bye ;Restore Reg's pop ds ! pop ax jmpf CS: DWORD PTR old_23_isr ENDIF remove_isrs: ;Remove installed ISR's and reinstall original ones. ;Calling: Nothing. (Misc Variables) ;Return: Nothing. (ISR Vector table) ;Notes: All registers preserved. ; If 'new_1c_isr' was NOT installed, this proc does ; nothing. ;Preserve Reg's... push ax ! push bx ! push es ;Was the new ISR installed? cmp WORD PTR old_1c_isr, 0 jne r1c_remove cmp WORD PTR old_1c_isr + 2, 0 jne r1c_remove r1c_removed: ;This point is reached if a) the original ISR was reinstalled, or ; b) the original ISR didn't need to be reinstalled. ;Restore Reg's... pop es ! pop bx ! pop ax ret r1c_remove: xor ax, ax mov es, ax ;ES = 0 (throughout procedure). ;Remove new ISR's. mov ax, old_1c_isr ;AX = Orig 1Ch ISR effective addrs. mov bx, old_1c_isr + 2 ;BX = Orig 1Ch ISR base addrs. pushf ;Save flags (including interrupt) cli ;Disable interrupts. ;Remove new 1Ch isr and install original... mov tick_isr_off_pntr, ax mov tick_isr_seg_pntr, bx IF CPM EQ 0 ;DOS... ;Remove new Int 23h isr and install original... mov ax, old_23_isr mov bx, old_23_isr + 2 ;BX:AX = Int 23h orig. isr. mov CS: WORD PTR .8Ch, ax mov CS: WORD PTR .8Eh, bx ;Orig. Int 23h isr restored. ENDIF popf jmps r1c_removed new_1c_isr: ;New timer tick interrupt vector. Used for timer proc's & ; maintains random number. (v1.2) ;Calling: Nothing. ;Return: Misc timer variables. ;Notes: Control transfered to original timer interrupt after ; completion. inc CS:random_num ;Increment 'random_num' variable. IF RAINBOW ;DEC Rainbow... (both CP/M-86 & DOS) ;-------------- dec cs:timer_multiplier jnz n1c_fin mov cs:timer_multiplier, rb_tick_mul_equ ;Restore timer_multipler. ENDIF cmp CS: timer_count, 0 ;Is 'timer_count' == 0? je n1c_fin ;Yes, then don't decrement it! dec CS:timer_count ;No, then decrement it. ;Fall into... n1c_fin: jmpf CS: DWORD PTR old_1c_isr get_mem: ;Get memory for tracking game board. ;Calling: Nothing (gb_loc variable) ;Return: Misc. variables. ;Notes: All registers except flags preserved. ; Unused memory assumed already released. ; On error, this procedure displays error message ; and returns to the OS. ;Preserve registers... pushf ! push ax ! push bx ! push cx ! push dx ! push si ! push di push es ;Make ax = amount of memory to obtain. ;make al = number of col's between vertical lines... mov ax, WORD PTR gb_loc + 4 xchg al, ah sub al, gb_loc + 1 aas sub ah, gb_loc ;AX = number of col's per row + 1 (BCD) aad dec al ;AX = AL = number of col's per row (binary) mov gb_cols_rows, al ;Save AL in variable. mov dl, al ;Save AL in DL. ;make al = number of rows within "U"... mov ax, WORD PTR gb_loc + 6 xchg al, ah sub al, gb_loc + 3 aas sub ah, gb_loc + 2 aad ;AL = number of rows (binary) mul dl ;AX = total number of char positions in "U". mov gb_bytes, ax ;Save total number of bytes. mov cl, 4 shr ax, cl ;Convert AX from bytes to par's. inc ax ;Always round up for safety. IF CPM ;CP/M-86... ;CP/M-86 Int 224d Fun. 55d Allocate Mem ;Calling: CL = 37h (55d) ; DX = Offset to MCB ;Return: AL = 0 (okay) or 0FFh (fail) ;Notes: MCB format: ; dw ;M-Base (16 bit seg) ; dw ;M-Length (in pars of memory) ; db ;M-Ext (error code) ; MCB variables are input/output as appropriate ; 'db_data_seg' is the first element of the MCB. mov WORD PTR gb_data_seg + 2, ax mov cl, 55d mov dx, OFFSET gb_data_seg int 224d test al, al jz gm_cpm55_okay jmp mem_error ;If AL == 0, all went okay. Fall into gm_fin which should immediately ; follow IF block. gm_cpm55_okay: ENDIF IF CPM EQ 0 ;DOS... mov bx, ax ;BX = num par's to allocated. ;Get memory... ;DOS Int. 21h Fun 48h "Allocate Memory" ;Calling: BX = Num or Paragraphs requested. ;Return: AX = Seg. addrs of allocated memory block (MCB + ; 1par) OR DOS error code if CR is set. ; BX = Size of largest block of memory if AX = 8 & CF = ; set. ; CF = Carry Flag set on error. mov ah, 48h int 21h mov gb_data_seg, ax ;Just in case all went well, save segment. mov ax, 0 ;AX = return code. jnc gm_int2148_ok jmp mem_error ;Jump on error. gm_int2148_ok: ENDIF gm_fin: ;Restore registers... pop es pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ! popf ret mem_error: mov si, OFFSET get_mem_fail_msg call ansi_str mov dh, 0FFh call wait4key jmp good_bye clr_mem: ;Clear data image (fill all bytes with '°') ;Calling: gb_data_seg ;Return: gb_data_seg (all bytes = 0) ;Notes: All registers preserved. ; gb_data_seg assumed already initialized. ; CLD assumed. ;Preserve registers... pushf ! push ax ! push cx ! push di ! push es cld mov cx, gb_bytes mov ax, gb_data_seg mov es, ax xor di, di mov al, '°' rep stosb ;Restore registers... pop es ! pop di ! pop cx ! pop ax ! popf ret release_mem: ;Release unused ('shrink') memory.(DOS only!). Move stack to ; (larger) internal area (DOS & CP/M-86). ;Calling: Nothing. ;Return: Nothing. ;Notes: All registers EXCLUDING AX, saved. ; Assumed DS == SS; CLD. ; On error, this procedure displays error message ; and returns to the OS. IF CPM ;CP/M-86... ;As CP/M-86 allocates only as much memory to a program as it needs, ; there is no need to free any CP/M-86 memory. On the other hand, ; since CP/M-86 shares its tiny 96byte stack with a running program ; and since even small amounts of stack usage cause apparent stack ; overwrites which cause the current user number to change randomly ; after program termination, we'll give this program its own roomy ; stack. ;Preserve registers... pushf ! push ax ! push bx ! push cx ! push dx ! push si ! push di push es ;Move stack to new area... mov ax, ds mov es, ax ;ES = DS mov si, sp ;SS:SI ->Start of stack area to copy. mov cx, 98d ;CX = Num bytes to copy mov di, end_of_mem_equ sub di, cx ;ES:DI -> New SP. mov dx, di ;Save DI in DX. mov ax, ss mov bx, ds ;Save DS in BX mov ds, ax ;DS = SS cld rep movsb mov ds, bx ;Restore DS. cli mov ss, bx ;Make SS = DS. mov sp, dx ;Set new stack pointer. sti rm_fin: ;Restore registers... pop es pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ! popf ret ENDIF IF CPM EQ 0 ;DOS... ;Preserve registers... pushf ! push ax ! push bx ! push cx ! push dx ! push si ! push di push es ;Move stack to new area... mov ax, cs mov es, ax ;ES = CS (= DS = SS = CS) xor cx, cx sub cx, sp ;CX = num of stack bytes in use. mov ax, cx ;AX = CX mov si, sp mov di, end_of_mem_equ sub di, ax cld rep movsb ;Stack data transferred. mov cx, end_of_mem_equ ;CX -> end of stack. sub cx, ax ;CX = New stack pointer. mov sp, cx ;SP now updated. ;Prepare to shrink memory... mov bx, end_of_mem_equ ;BX = Size of mem in use in bytes mov cl, 4d shr bx, cl ;BX = Size of mem in use in par's inc bx ;Round BX up. push cs pop es ;DOS INT 21,4A - Modify Allocated Memory Block (SETBLOCK) ;Calling: AH = 4A ; BX = new requested block size in paragraphs ; ES = segment of the block (MCB + 1 para) ;Return: AX = error code if CF set (see DOS ERROR CODES) ; BX = maximum block size possible, if CF set and AX = 8 ;Notes: ; - modifies memory blocks allocated by INT 21,48 ; - can be used by programs to shrink or increase the size ; of allocated memory ; - PC-DOS version 2.1 and DOS 3.x will actually allocate the largest ; available block if CF is set. BX will equal the size allocated. ; - see also INT 21,49 mov ah, 4Ah int 21h jnc rm_int214A_okay jmp mem_error ;Jump on error. Otherwise... rm_int214A_okay: rm_fin: ;Restore registers... pop es pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ! popf ret ENDIF bye: ;Prepare to return to OS. ;Calling: CS = PSP ;Return: Nothing. ;Notes: No registers saved! call close_disp ;Restore console display. call clearkb ;Clear keyboard buffer one last time. call remove_isrs ;Restore ISRs. ret set_timer: ;Set timer to count down DH number of timer ticks. ;Calling: DH = Number of ticks to count down. ;Return: Nothing. ;Note: Timer functions fail @ midnight. ; All common reg's except flags preserved. mov timer_count, DH ret chk_timer: ;Checks if a previously set amount of time (in timer ticks) has ; elapsed. ;Calling: Nothing. ;Return: ZF set if timer has expired. ;Note: Timer functions fail @CCC midnight. ; All common registers preserved. ; Dir Flag (which should already be cleared) is cleared. cmp timer_count, 0 ret wait4key: ;Wait DH system timer ticks for a key press while displaying ; rotating star. ;Calling: DH = Timer ticks to wait. (Rounded up to a multiple ; of 5 as needed) ; If DH = 0, wait4key will wait forever. ; (Also, cursor position assumed set.) ;Return: Nothing. ;Notes: All common registers (except flags) preserved. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di call clearkb ;Clear keyboard. w4k_time: ;Make AL = DH/5 (allows time for rotating star)... mov al, dh ! xor ah, ah ;AX = DH (num ticks to wait) mov dl, 5 div dl ;AL.AH = DH/5 xor dl, dl ;DL = 0 cmp dl, ah ;Set carry flag if AH>0. adc al, 0 ;AL = DH / 5 rounded up as needed. mov dl, al ;DL = DH/5, rounded up if necessary. ;Make DI+BX -> first star_msg... mov di, OFFSET star_msg ;DI = OFFSET star_msg. xor bx, bx ;BX = 0 mov si, di ;SI = DI. inc si ;SI = DI + 1 (skips initial backspace) jmps w4k_lpstart w4k_lptop: ;DL assumed = number of loops to execute (remaining). mov si, di add si, bx ;SI -> String to display. w4k_lpstart: call ansi_str ;Print star string. mov dh, 5 call set_timer ;Set timer for 5 timer ticks. w4k_timerlooptop: call chk_timer ;Has time elapsed? jz w4k_timerloopend ;Yes, then jump. No, then... call chk4key ;Has a key been pressed? jc w4k_timerlooptop ;No, then jump. Yes then... jmps w4k_lpend ; jump. w4k_timerloopend: ;Dec. loop counter... cmp dl, 0 je w4k_lp_till_keyprs sub dl, 1 ;Dec loop counter. jbe w4k_lpend ;If loop counter <1, jump to end of loop. ;Make DI+BX -> Next string to print... w4k_lp_till_keyprs: add bx, 3d cmp BYTE PTR [di+bx], 0d jne w4k_lptop xor bx, bx jmps w4k_lptop w4k_lpend: call clearkb ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret ; Console I/O ; =========== ;List of Procedures: ;------------------- ;clrscr ;Clear screen. ;poscur ;Position cursor. ;init_disp ;Initialize Display. ;ansi_str ;Process ANSI screen writes. ;writes2con ;Write string to console. ;write2con ;Write char to console. ;chk4key ;Checks for a waiting keypress. ;clearkb ;Clear the keyboard buffer. ;getkey ;Get Keyboard input. ;Console I/O Code clrscr: ;Clear Screen. ;Calling: Nothing. ;Return: Nothing. ;Notes: All registers preserved. push dx ;Preserve register(s) inc ansi_str_again mov si, OFFSET ansi_clrscr call ansi_str dec ansi_str_again pop dx ;Restore register(s) ret poscur: ;Position cursor. ;Calling: AX, BX = Column, Row to move cursor to (top right = 1,1,) ; Where: AX & BX are BCD values. ;Return: Nothing. ;Notes: All common registers saved. ; This procedure takes into account the differences between ; DOS ANSI mode (top right = 1,1) & CP/M-86 (20h, 20h). ;Preserve registers... pushf ! push ax ! push bx ! push cx ! push dx ! push si ! push di IF CPM ;CP/M-86 ;Convert AX & BX into binary + 20h references. Also dec AX & BX by 1. sub al, 1 aas ;Make AX zero referenced. mov cl, al mov al, ah mov ah, 10d mul ah add al, cl ;AL = column number add al, 20h ;AL = col number + 20h mov ansi_col, al mov ax, bx ;AX = Row number in BCD sub al, 1 aas ;Make AX zero referenced. mov cl, al mov al, ah mov ah, 10d mul ah add al, cl ;AL = row number add al, 20h ;AL = Row number + 20h mov ansi_row, al ENDIF IF CPM EQ 0 ;DOS... add ax, 3030h xchg al, ah mov WORD PTR ansi_col, ax add bx, 3030h xchg bl, bh mov WORD PTR ansi_row, bx ENDIF ;When this point is reached... ; ansi_col & ansi_row initialized. inc ansi_str_again ;Disable ansi_str special char ; handling. mov si, OFFSET ansi_poscur call ansi_str dec ansi_str_again ;Restore registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ! popf ret init_disp: ;Initialize Display. ;Calling: Nothing. ;Return: Nothing. ;Notes: All common registers preserved. ;Preserve Reg's: push dx ;Set Video mode... inc ansi_str_again cmp mono,1 jne colorinit mov si,offset ansi_mono0 jmp moreinit colorinit: mov si, OFFSET ansi_vmode0 moreinit: call ansi_str dec ansi_str_again ;Restore reg's pop dx ret close_disp: ;Un-initialize display. ;Calling: Nothing. ;Return: Nothing. ;Notes: NO REGISTERS preserved! ; -- ;Set Video mode... cmp mono,1 jne colorclose mov si,offset ansi_mono1 jmp moreclose colorclose: mov si, OFFSET ansi_vmode1 moreclose: call ansi_str ;IF CPM ; ;Turn console status line ON... ; mov si, OFFSET statline_on_msg ; call ansi_str ;ENDIF ;Turn cursor (back) on... mov si, OFFSET ansi_curon call ansi_str ret ansi_str: ;Process ANSI screen writes. ;Calling: DS:SI ASCIIZ string to write ;Return: Nothing. ;Notes: All common registers preserved. ; The following characters are treated as control codes: ; 0 String terminator. ; ° Cursor advanced, no screen write. ;  Clear Screen. ; @xxyy Position cursor @ column, row. ; Where xx = 2xASCII '0'-'9' & Screen top right = 1,1. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di as_looptop: lodsb ;AL = byte to process. ;End of string reached yet? cmp al, 0 ;End of screen? jne as_skip000 jmp as_loopend as_skip000: ;Skip special characters? cmp ansi_str_again, 0 jne as_skipspecchar ;If this point is reached, we need to check for & process ; special characters cmp al, '°' ;Skip write? jne as_skip001 ;Skip forward one column. push si ;Preserve SI inc ansi_str_again mov si, OFFSET ansi_forward call ansi_str dec ansi_str_again pop si jmps as_looptop as_skip001: cmp al, '' ;Clear Screen? jne as_skip002 push si mov si, OFFSET ansi_clrscr inc ansi_str_again call ansi_str dec ansi_str_again pop si jmps as_looptop as_skip002: cmp al, '@' ;Move cursor? jne as_skip003 inc ansi_str_again ;get location to move to... lodsw mov bx, ax lodsw xchg al, ah xchg ax, bx xchg al, ah ;AX & BX now equal xx,yy (respectively) in ASCII. Make them BCD... sub ax, 3030h sub bx, 3030h call poscur dec ansi_str_again jmps as_looptop as_skip003: as_skipspecchar: ;If this point is reached, AL contains just an ordinary character ; to write to the screen. mov dl, al call write2con jmps as_looptop as_loopend: ;Restore Registers... pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret ;writes2con: ; ;Write string to console. ; ;Calling: DS:DX -> ASCII$ string to write. ; ;Return: Nothing. ; ;Notes: All common registers (including FLAGS) preserved. ; ; Note string must not contain '$' (except as terminator). ; ; ;Preserve registers... ; push ax ! push bx ! push cx ! push dx ! push si ! push di ! pushf ; push es ; ; IF CPM ; ;CP/M-86... ; mov cl, 9d ; int 224d ; ENDIF ; IF CPM EQ 0 ; ;DOS... ; mov ah, 9h ; int 21h ; ENDIF ; ; ;Restore registers... ; pop es ; popf ! pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ; ; ret write2con: ;Write char to console. ;Calling: DL = Char to write. ;Return: Nothing. ;Note: All common registers (including FLAGS) preserved. ;Preserve registers... push ax ! push bx ! push cx ! push dx ! push si ! push di ! pushf push es IF CPM mov cl, 2d int 224d ENDIF IF CPM EQ 0 ;DOS... ;DOS INT 21,2 - Display Output ;Calling: AH = 02 ; DL = character to output ;Return: Nothing ;Notes: ;- outputs character to STDOUT ;- backspace is treated as nondestructive cursor left ;- if Ctrl-Break is detected, INT 23 is executed mov ah, 2h int 21h ENDIF ;Restore registers... pop es popf ! pop di ! pop si ! pop dx ! pop cx ! pop bx ! pop ax ret chk4key: ;Checks for a waiting keypress. ;Calling: Nothing. ;Return: AL = 0 if no keypress waiting, !0 otherwise. ; CF = Set if no keypress waiting, Clr otherwise. ;Notes: All common registers (including FLAGS) preserved ; EXCEPT AX. ;Preserve registers... push bx ! push cx ! push dx ! push si ! push di ! pushf push es IF CPM ;CP/M-86... ;Prepare for... ;CP/M-86 Fun. 6h Direct Console I/O. ;Calling: CL = 6h ; DL = FFh Status & Input ; or DL = FEh Status ; or DL = char Output ;Return: AL FFh: Char waiting or 0 if no char waiting. ; FEh: 0 = No char waiting, FFh = Char Waiting ; char: Nothing mov cl, 6 mov dl, 0FEh int 224d ;Returns AL 0 or FFh ENDIF IF CPM EQ 0 ;DOS ;DOS INT 21,B - Check Standard Input Status ;Calling: AH = 0B ;Return: AL = 00 if no character available ; = FF if character available ;Notes: ;- checks STDIN for available characters ;- character is not returned ;- if Ctrl-Break is detected INT 23 is executed mov ah, 0Bh int 21h ENDIF ;Restore registers... pop es popf ! pop di ! pop si ! pop dx ! pop cx ! pop bx cmp al, 1 ret clearkb: ;Clear keyboard buffer. ;Calling: Nothing. ;Return: Nothing. ;Notes: All common registers (except flags) preserved. ;Preserve Registers.. push ax ck_lptp: call chk4key test al, al je ck_lpend call getkey jmps ck_lptp ck_lpend: ;Restore Regs.. pop ax ret getkey: ;Get Keyboard Input. ;Calling: Nothing. ;Return: AL = ASCII code. ;Notes: All common registers (except AX, including FLAGS) ; preserved. ; If an extended key is pressed, the second byte ; of the key code is returned. ;Preserve registers... push bx ! push cx ! push dx ! push si ! push di ! pushf push es getkey_again: IF CPM ;CP/M-86... ;Prepare for... ;CP/M-86 Fun. 6h Direct Console I/O. ;Calling: CL = 6h ; DL = FFh Status & Input ; or DL = FEh Status ; or DL = char Output ;Return: AL FFh: Char waiting or 0 if no char waiting. ; FEh: 0 = No char waiting, FFh = Char Waiting ; char: Nothing mov cl, 6 mov dl, 0FFh int 224d ;Returns AL 0 or FFh ENDIF IF CPM EQ 0 ;DOS ;DOS INT 21,6 - Direct Console I/O ; Calling: AH = 06 ; DL = (0-FE) character to output ; = FF if console input request ; Return: AL = input character if console input request (DL=FF) ; ZF = 0 if console request character available (in AL) ; = 1 if no character is ready, and function request ; was console input ;Notes: ;- reads from or writes to the console device depending on the value of DL ;- cannot output character FF (DL=FF indicates read function) ;- for console read, no echo is produced ;- returns 0 for extended keystroke, then function must be called again ; to return scan code ;- ignores Ctrl-Break and Ctrl-PrtSc mov ah, 6h mov dl, 0FFh int 21h ENDIF cmp al, 0 ;Was a key with an extended keystroke ; pressed? je getkey_again ;Yes, then get extended key. ;Restore registers... pop es popf ! pop di ! pop si ! pop dx ! pop cx ! pop bx ret ; Data Segment ; ============ IF CPM ;CP/M-86... ;CSEG still assumed! endcs EQU $ ;End of Code Segment. DSEG ORG OFFSET endcs ENDIF IF CPM EQ 0 ;DOS... DSEG ENDIF ;Console I/O Data ;---------------- ;ANSI Stuff... IF CPM ;CP/M-86 ansi_vmode1 db esc, 'a2', esc, 'E', esc, 'n', 0 ansi_vmode0 db esc, 'a0', esc, 'E', esc, 'n', 0 ansi_mono1 db esc,'E',esc,'n',0 ansi_mono0 db esc,'E',esc,'n',0 ; ^^ |Set V mode|Clr Scr & Home Cur|Cursor Off| ^^ ansi_clrscr db esc, 'E',0 ;Clear Screen/Home Cur Command. () ansi_poscur db esc, 'Y' ;Position cursor... (@) ansi_row db 0 ; Row ansi_col db 0 ; Col db 0 ansi_forward db esc,'C',0 ;Forward on character. () ansi_curon db esc,'m',0 ;Turn cursor (back) on. statline_on_msg db esc, '1', 0 ;Status line on/off escape statline_off_msg db esc, '0', 0 ; codes courtesy Mr. Kirk ; Lawrence. ENDIF IF CPM EQ 0 ;DOS... ansi_vmode1 db esc, '[=2h',0 ;Sets 80x25 video mode. ansi_vmode0 db esc, '[=0h',0 ;Sets 40x25 video mode. ansi_clrscr db esc, '[2J',0 ;Clear Screen/home Cur Command. () ansi_poscur db esc, '[' ;Position cursor... (@) ansi_row db 0, 0, ';' ; row ansi_col db 0, 0 ; column db 'f',0 ansi_forward db esc, '[1C',0 ;Forward on character. () ansi_curon db 0 ;Turn cursor (back) on. ENDIF ;Misc. Text message(s): ;---------------------- get_mem_fail_msg db cr, lf db 'Fatal Error while allocating/releasing OS memory!' db cr, lf db 'Returning to OS.' db cr, lf db 'Caution: Operating System may be unstable! Consider' db ' rebooting computer!' db cr, lf db 'Press any key to exit or wait',0,0 welcome_msg db cr, lf, '(*v1.2*)', cr, lf IF CPM ;CP/M-86... db 'TETRIS3 for CP/M-86 - Freeware (c) 1996 by Richard Kanarek', cr, lf db ' "Imitation is the best form of flattery"', cr, lf db ' * * * Also available for DOS! * * *' ENDIF IF CPM EQ 0 ;DOS... db 'TETRIS3 for PCDOS - Freeware (c) 1996 by Richard Kanarek', cr, lf db ' "Imitation is the best form of flattery"', cr, lf db ' * * * Also available for CP/M-86! * * *', cr, lf db cr, lf, 'IMPORTANT: An ANSI display driver MUST be installed' db ' if this program is', cr, lf db ' to be run successfully!' ENDIF db cr, lf, cr, lf db 'IMPORTANT: This version is intended for execution on' db cr, lf IF RAINBOW ;DEC RAINBOW db ' a DEC (8088) RAINBOW, *NOT* an IBM PC.' ENDIF IF RAINBOW EQ 0 ;IBM db ' an IBM PC/XT/AT or compatible.' ENDIF db cr, lf db ' If the host computer is not suitable', cr, lf db ' ABORT IMMEDIATELY! Otherwise, enjoy!', cr, lf db cr,lf,' =',16,' Turn on your NUM LOCK key now ',17,'=',cr,lf db cr,lf,' ...then press any key to continue...', 0 start_game_msg1 db 'Press any key to begin play',0 blankline_msg db ' ',0 levelfin_msg db 'Level Completed!', 0 tryagain_msg db 'Try again',0 gameover_msg1 db 'Game',0 gameover_msg2 db 'Over',0 skiplevel_msg db '+ Skipping Level, Press any key',0 star_msg db 8d, '|', 0d ;Used by 'wait4key' procedure. db 8d, '/', 0d ;Credits to Phil Katz! db 8d, '-', 0d db 8d, '\', 0d db 0d ;Misc. Variables: ;---------------- mono db 0 ;MONO flag ansi_str_again db 0 ;If not 0, procedure ansi_str ignores ;special characters. old_1c_isr dw 0, 0 ;Saves location of old Int 1Ch ISR. old_23_isr dw 0, 0 ;Saves location of old Int 23h ISR. timer_count db 0 ;Stores value to count down. random_num dw 0 ;Stores a random number. IF RAINBOW ;DEC PC RAINBOW... timer_multiplier db rb_tick_mul_equ ;Multiplies timer tick. ENDIF ;Game data... ;------------ gb_bytes dw 0 ;Number of bytes in gb_data_seg (above). player_life db players_equ IF CPM ;CP/M-86... ;gb_data_seg == 1st word of mcb in CP/M-86 ver. gb_data_seg dw 0 ;M-Base dw 0 ;M-Length db 0 ;M-Ext ENDIF IF CPM EQ 0 ;DOS... gb_data_seg dw 0 ;Seg for game board data. (allocated ; from os) ENDIF gb_cols_rows rb 1 ;Number of columns per row (binary) gb_loc db 1,3, 0,5 ;Top Left of active area of game board. db 2,5, 2,0 ;Bottom Right of active area of game board. ;Note: gb_loc is used to draw the active area game boarder-- ; all game pieces must fit INSIDE this area. ;Note: Neither row nor column may be less than 1 or more than 98. ;gb_loc db x,x, y,y }Unpacked ; db x,x, y,y } BCD ;Where x,x & y,y = MSB,LSB Unpacked BCD. Top Left = 1,1 ;gb_loc db 10xCol, 1xCol, 10xRow, 1xRow