; GAME6 - BIT OF A TREASURE ; ; Navigate the challenging Binary Mazes in an isometric perspective to find ; the Golden Byte, swapping level designs and avoiding traps in this logic ; puzzle adventure. ; ; Controls: ; ; Arrow keys - move the player ; Enter - toggle level designs (4 in total) ; ESC - restart the game after failed move ; ; Minimum CPU is Intel 386. ; ; Author: Krzysztof Krystian Jankowski ; Date: 2024-06/15 -> 06/16 ; License: MIT bits 16 ; 16-bit mode org 0x7c00 ; Boot sector origin cpu 386 ; Minimum CPU is Intel 386 ; =========================================== MEMORY =========================== VGA_MEMORY_ADR equ 0xA000 ; VGA memory address DBUFFER_MEMORY_ADR equ 0x9000 ; Doublebuffer memory address SCREEN_BUFFER_SIZE equ 0xFA00 ; Size of the VGA buffer size TIMER equ 0x046C ; BIOS timer BASE_MEM equ 0x7e00 ; Base memory address ; LIFE equ BASE_MEM+0x00 ; Number of lifes,1 byte LEVEL equ BASE_MEM+0x01 ; Current level,2 bytes PLAYER_POS equ BASE_MEM+0x03 ; Ship position,2 bytes PLAYER_TIMER equ BASE_MEM+0x05 ; Movement timer,2 bytes PLAYER_DIR equ BASE_MEM+0x07 ; Ship direction,1 byte LEVEL_DATA equ BASE_MEM+0x08 ; Level data,512 bytes ; =========================================== MAGIC NUMBERS ==================== SCREEN_WIDTH equ 320 ; VGA 13h Resolution: SCREEN_HEIGHT equ 200 ; 320x200 pixels SCREEN_CENTER equ SCREEN_WIDTH*SCREEN_HEIGHT/2+SCREEN_WIDTH/2 ; Center LEVEL_START_POS equ 168+320*40 ; Level start position PLAYER_START_POS equ LEVEL_START_POS+320*10-14 ; Player start position TREASURE_START_POS equ LEVEL_START_POS+320*114-14 ; Treasure start position SPRITE_SIZE equ 8 ; 8 pixels per sprite line SPRITE_LINES equ 8 ; 8 lines per sprite LEVEL_COLS equ 16 ; 16 columns per level LEVEL_ROWS equ 16 ; 16 rows per level COLOR_BACKGROUND equ 0x999b ; Color for background COLOR_TILE_MOVABLE equ 0x58 ; Color for movable tile COLOR_TILE_NONMOVABLE equ 0x7e ; Color for non-movable tile COLOR_TILE_WALL equ 0x36 ; Color for shaded wall tile COLOR_TILE_WALL_LIGH equ 0x35 ; Color for wall tile COLOR_BALL_MAIN equ 0x3d ; Color for the player ball COLOR_BALL_LIGH equ 0x6c ; Color for shading the ball COLOR_BALL_DEAD equ 0x30 ; Color for dead ball COLOR_TREASURE equ 0x5a ; Color for the treasure ; =========================================== BOOTSTRAP ======================== _start: xor ax,ax ; Clear AX mov ds,ax ; Set DS to 0 mov ax,0x13 ; Init VGA 320x200x256 int 0x10 ; Video BIOS interrupt push DBUFFER_MEMORY_ADR ; Set doublebuffer memory pop es ; as target ; =========================================== LEVEL DESIGNS ==================== create_levels: mov ax,0x1337 ; Seed for random number mov bx,0xabcd ; Second seed mov cx,0x80 ; Number of level lines (64) .fill_line: add bx,di ; Change second seed with line number ror ax,4 ; Rotate first seed xor ax,bx ; XOR with second seed mov word [LEVEL_DATA+di],ax ; Store the procedural level line inc di ; Move to the next line loop .fill_line ; =========================================== GAME LOOP ======================== restart_game: mov word [LEVEL],0x00 ; Reset level mov word [PLAYER_POS],PLAYER_START_POS ; Reset player position mov byte [PLAYER_TIMER],0x00 ; Reset player timer ; =========================================== REAL-TIME GAME LOOP ============== game_loop: ; =========================================== DRAW BACKGROUND ================== draw_bg: mov ax,COLOR_BACKGROUND ; Set background color (2 bytes) mov cx,SCREEN_BUFFER_SIZE ; Set buffer size to fullscreen rep stosw ; Fill the buffer with color draw_level_indicator: mov cx,[LEVEL] ; Get current level number inc cx ; Increment by 1 to avoid 0 mov bx,COLOR_TILE_MOVABLE ; Set sprite color mov si,tiles+8 ; Set sprite data addr (right tile) .draw_icon: call draw_sprite add di,12 ; Move 12px right for next icon loop .draw_icon ; =========================================== DRAW LEVEL ======================= draw_level: mov si,LEVEL_DATA ; Set level data address mov ax,[LEVEL] ; Get current level for offset mov bx,0x20 ; Level data offset mul bx ; Multiply by offset add si,ax ; Add current level offset mov dx,LEVEL_ROWS ; Number of rows in the level mov di,LEVEL_START_POS ; Set level start position .draw_row: mov ax,[si] ; Get level row data mov cx,LEVEL_COLS ; 16 bits per row .draw_tile_box: shl ax,1 ; Shift left to get the tile out jc .color_non_movable ; If carry flag is 0,draw ground mov bx,COLOR_TILE_MOVABLE ; Set sprite color jmp .draw_the_tile ; Skip to draw the tile .color_non_movable: mov bx,COLOR_TILE_NONMOVABLE; Set sprite color .draw_the_tile: push si xor si,si ; Set sprite ID to 0 add di,320*4-17 ; Isometric offset call draw_tile pop si inc di ; Move to the next tile position loop .draw_tile_box inc si inc si ; Shift source by 2 bytes add di,-320*60+136 ; Move to the next row,iso offset dec dx ; Decrement row count jnz .draw_row ; Draw the next row ; =========================================== DRAW TREASURE ==================== draw_treasure: xor bx,bx ; Set color to black mov si,treasure_sprite ; Set sprite data address mov word di,TREASURE_START_POS ; Set sprite position call draw_sprite ; Draw rigth silhouette dec di dec di ; Move 2px left call draw_sprite ; Draw left silhouette inc di ; Move 1px right (centered) mov bx,COLOR_TREASURE ; Set color to treasure call draw_sprite ; Draw treasure ; =========================================== CHECK COLLISIONS ============== check_collisions: mov si,[PLAYER_POS] ; Get player position add si,320*4+5 ; Move raycast to center mov al,[es:si] ; Get pixel color at raycast cmp al,COLOR_TREASURE ; Check if it matches treasure color je .collision_treasure ; Jump if collision with treasure cmp al,COLOR_TILE_MOVABLE ; Check if it matches movable color jne .collision_wall ; Jump if collision with non-movable jmp .collision_done ; No collision .collision_wall: mov ax,COLOR_BALL_DEAD ; Set color to dead ball call draw_player ; Draw dead ball Do not draw ball... .collision_treasure: ; ...after finding treasure call vga_blit ; Copy doublebuffer to VGA .waint_for_esc: ; If game snded,wait for ESC key in al,60h ; Read keyboard cmp al,0x01 ; Check if ESC key is pressed jne .waint_for_esc jmp restart_game .collision_done: ; =========================================== DRAW PLAYER ====================== xor ax,ax ; Set dead color to 0 call draw_player ; =========================================== KEYBOARD INPUT =================== check_player_timer: cmp byte [PLAYER_TIMER],0x00 ; Check if player can move jne .dec_timer .handle_keyboard: in al,60h ; Read keyboard mov bl,0x0f ; Set default direction to stopped cmp al,0x1C ; Enter pressed jne .no_enter inc word [LEVEL] ; Increase level desing number and word [LEVEL],0x03 ; Loop 0..3 mov bl,0xff ; Set direction to stopped .no_enter: cmp al,0x48 ; Up pressed jne .no_up xor bl,bl ; Set direction to 0 .no_up: cmp al,0x50 ; Down pressed jne .no_down mov bl,0x03 ; Set direction to 3 .no_down: cmp al,0x4D ; Right pressed jne .no_right mov bl,0x01 ; Set direction to 1 .no_right: cmp al,0x4B ; Left pressed jne .no_left mov bl,0x02 ; Set direction to 2 .no_left: cmp bl, 0x0f ; Check if nothing was pressed jz .done ; Skip if nothing was pressed .set_timer: mov byte [PLAYER_DIR], bl ; Save player direction mov byte [PLAYER_TIMER],0x04 ; Sat player timer for moving .dec_timer: dec byte [PLAYER_TIMER] ; Decrement player timer call update_player_pos ; Update player position .done: ; =========================================== VGA BLIT ========================= call vga_blit ; Update screen ; =========================================== DELAY CYCLE ====================== delay_timer: mov ax,[TIMER] ; Get current timer value inc ax ; Increment it by 1 cycle (42ms) .wait: cmp [TIMER],ax ; Compare with the current timer jl .wait ; Loop until equal ; =========================================== END OF GAME LOOP ================= jmp game_loop ; Repeat the game loop ; =========================================== PROCEDURES ====================== ; =========================================== UPDATE PLAYER POSITION ========= update_player_pos: pusha mov di,[PLAYER_POS] ; Get player position in VGA memory mov al,[PLAYER_DIR] ; Get player direction to AL cmp al,0xff ; Check if set to stopped je .done mov ah,0 ; And clear AH mov si,ax ; Set SI to rotation shl si,1 ; Shift left add di,[MLT + si] ; Movement Lookup Table mov word [PLAYER_POS],di ; Save new position .done: popa ret ; =========================================== DRAW PLAYER PROCEDURE ============ draw_player: mov di,[PLAYER_POS] ; Get player position in VGA memory mov si,ball_sprites ; Set sprite data start address mov bx,COLOR_BALL_MAIN ; Set color add bx,ax ; Add alternative (dead) color call draw_sprite add si,8 ; Move to the next sprite (shading) mov bx,COLOR_BALL_LIGH ; Set shading color call draw_sprite ret ; =========================================== VGA BLIT PROCEDURE =============== vga_blit: push es push ds push VGA_MEMORY_ADR ; Set VGA memory pop es ; as target push DBUFFER_MEMORY_ADR ; Set doublebuffer memory pop ds ; as source mov cx,0x7D00 ; Half of 320x200 pixels xor si,si ; Clear SI xor di,di ; Clear DI rep movsw ; Push words (2x pixels) pop ds pop es ret ; =========================================== DRAWING SPRITE PROCEDURE ========= draw_sprite: pusha mov dx,SPRITE_LINES ; Number of lines in the sprite .draw_row: mov al,[si] ; Get sprite row data mov cx,SPRITE_SIZE ; 8 bits per row .draw_pixel: shl al,1 ; Shift left to get the pixel out jnc .skip_pixel ; If carry flag is 0,skip mov [es:di],bl ; Carry flag is 1,set the pixel .skip_pixel: inc di ; Move to the next pixel position loop .draw_pixel ; Repeat for all 8 pixels in the row inc si add di,320-SPRITE_SIZE ; Move to the next line dec dx ; Decrement row count jnz .draw_row ; Draw the next row popa ret ; =========================================== DRAWING TILE PROCEDURE =========== draw_tile: add si,tiles ; Set tile data address call draw_sprite add si,8 ; Move to the next tile add di,8 ; Move position by one sprite call draw_sprite cmp bx,COLOR_TILE_MOVABLE ; Check if the tile is movable je .skip_box push di mov bx,COLOR_TILE_WALL add si,8 ; Move to the next tile sprite sub di,320*2+4 ; Set position (centered) call draw_sprite mov bx,COLOR_TILE_WALL_LIGH add si,8 ; Move to the next tile sprite call draw_sprite pop di .skip_box: ret ; =========================================== DATA ============================= MLT dw -322,-318,318,322 ; Movement Lookup Table ; 0 - up/left ; 1 - up/right ; 2 - down/left ; 3 - down/right treasure_sprite: db 0x3C,0xE7,0xFF,0x7E,0x7E,0x3C,0x18,0x3C ; Treasure sprite ball_sprites: db 0x3C,0x66,0x9F,0xBF,0xFF,0x7E,0x3C,0x00 ; Ball sprite db 0x00,0x2A,0x01,0x25,0x9B,0x46,0x3C,0x00 ; Ball sprite shading tiles: db 0x03,0x0F,0x3F,0xFF,0xFF,0x3F,0x0F,0x03 ; Tile ground left part db 0xC0,0xF0,0xFC,0xFF,0xFF,0xFC,0xF0,0xC0 ; Tile ground right part db 0x3C,0xFF,0xE7,0xFF,0xFF,0xFF,0xFF,0x3C ; Tile wall db 0x00,0x18,0x66,0x18,0x70,0x70,0x70,0x18 ; Tile wall light ; =========================================== BOOT SECTOR ====================== times 507 - ($ - $$) db 0 ; Pad remaining bytes p1x db 'P1X' ; P1X signature 4b dw 0xAA55 ; Boot signature