# HG changeset patch # User Pascal Bellard # Date 1707069758 0 # Node ID 34a0a44065394321e2f8a83a2ac588fe1186b67d # Parent 61c76233911ebe27ab3bf0a2f513eedb5b84c5fe Add bootlife diff -r 61c76233911e -r 34a0a4406539 boot-man/stuff/boot-man.asm --- a/boot-man/stuff/boot-man.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/boot-man/stuff/boot-man.asm Sun Feb 04 18:02:38 2024 +0000 @@ -53,36 +53,79 @@ ; Boot-Man runs off BIOS clock. It should therefore run at the same speed on all PCs. The code is quite heavily ; optimized for size, so code quality is questionable at best, and downright atrocious at worst. -;%define WaitForAnyKey - -org 0x0600 ; The code will move to a well known position to allow some patches. +;%define WaitForAnyKey ; +9 bytes +%define MDAsupport ; +7 bytes +%define MDAinverse ; +5 bytes +%define MDA_CGA40 ; +17 bytes cpu 8086 ; Boot-Man runs on the original IBM PC with a CGA card. bits 16 ; Boot-Man runs in Real Mode. I am assuming that the BIOS leaves the CPU is Real Mode. ; This is true for the vast majority of PC systems. If your system's BIOS ; switches to Protected Mode or Long Mode during the boot process, Boot-Man ; won't run on your machine. +org 0x600 +%define COL40 40 +%define COL80 80 + start: call copy ; Can run as bootsector or DOS COM file moved: push cs pop ss - mov sp, cx ; Set up the stack. + xor sp, sp ; Set up the stack. - xchg ax, cx - inc ax ; int 0x10 / ah = 0: Switch video mode. Switch mode to 40x25 characters (al = 1). + mov ax, 1 ; int 0x10 / ah = 0: Switch video mode. Switch mode to 40x25 characters (al = 1). int 0x10 ; In this mode, characters are approximately square, which means that horizontal ; and vertical movement speeds are almost the same. - - mov cx, 0xb800 - mov es, cx ; Set up the es segment to point to video RAM + mov ax, 0xb800 +%ifdef MDAsupport + %define LeftCorner (COL80*2*24+COL80-32) + %define PreviousLine (COL80*2+32) + %define Columns COL80 + %define Margin ((COL80-32)/2) + %ifdef MDA_CGA40 + mov dx, PreviousLine + %undef PreviousLine + %define PreviousLine dx + %endif +%else + %define LeftCorner (COL40*2*24+COL40-32) + %define PreviousLine (COL40*2+32) + %define Columns COL40 + %define Margin ((COL40-32)/2) +%endif +initES: + mov es, ax ; Set up the es segment to point to video RAM + mov si, maze ; The first byte of the bit array containing the maze +%define ghost_n_incr 0x2f10 +%ifdef MDAsupport + mov ah, 0xb0 + %ifdef MDAinverse + %define ghost_attr 0x70 + %define incr_attr 0x80 + %undef ghost_n_incr + %define ghost_n_incr (ghost_attr*256 + incr_attr) + xor word [si + ghost_attr_patch - maze], ghost_n_incr^0x2f10 + %endif + %ifdef MDA_CGA40 +; xor dl, (COL80*2+32)^(COL40*2+32) + xor dl, ah + xor word [si + LeftCorner_patch - maze], LeftCorner^(COL40*2*24+COL40-32) + xor byte [si + Columns_patch - maze], Columns^COL40 + xor byte [si + Margin_patch - maze], Margin^((COL40-32)/2) + %endif + scasw + jb initES +%endif mov ax, 0x0101 ; int 0x10 / ah = 1: Determine shape of hardware cursor. al = 1 avoid AMI BIOS bug. mov ch, 0x20 ; With cx = 0x2000, this removes the hardware cursor from the screen altogether int 0x10 +%define ghost_color 0x2fec +%define color_increment 0x10 ;----------------------------------------------------------------------------------------------------- @@ -96,38 +139,35 @@ ; while the right part is drawn right to left. For efficiency reasons, the entire maze ; is built from the bottom up. Therefore, the maze is stored upside down in memory ;----------------------------------------------------------------------------------------------------- -buildmaze: - mov di, 0x0788 ; Lower left corner of maze in video ram - mov si, maze ; The first byte of the bit array containing the maze - mov dx, 0x05fa ; Address in video ram of the lower left powerpill +;buildmaze: + mov di, LeftCorner ; Lower left corner of maze in video ram +LeftCorner_patch equ $ - 2 + mov cx, 34 ; Lines count to the lower left powerpill .maze_outerloop: - mov cx, 0x003c ; The distance between a character in the maze and its + mov bx, 0x003c ; The distance between a character in the maze and its ; symmetric counterpart. Also functions as loop counter lodsw ; Read 16 bits from the bit array, which represents one - ; 32 character-wide row of the maze + xchg ax, bp ; 32 character-wide row of the maze .maze_innerloop: - shl ax, 1 ; shift out a single bit to determine whether a wall or dot must be shown - push ax + add bp, bp ; shift out a single bit to determine whether a wall or dot must be shown mov ax, 0x01db ; Assume it is a wall character (0x01: blue; 0xdb: full solid block) jc .draw ; Draw the character if a 1 was shifted out mov ax, 0x0ff9 ; otherwise, assume a food character (0x0f: white; x0f9: bullet) - cmp di, dx ; See if instead of food we need to draw a power pill - jnz .draw - mov dh, 0x00 ; Update powerpill address to draw remaining powerpills + loop .draw ; See if instead of food we need to draw a power pill + mov cl, 125 ; Update powerpill address to draw remaining powerpills mov al, 0x04 ; powerpill character (0x04: diamond - no need to set up colour again) .draw: stosw ; Store character + colour in video ram - push di - add di, cx ; Go to its symmetric counterpart - stosw ; and store it as well - pop di - pop ax - sub cx, 4 ; Update the distance between the two sides of the maze + mov [es:bx+di], ax ; Go to its symmetric counterpart and store it as well + sub bx, 4 ; Update the distance between the two sides of the maze jns .maze_innerloop ; As long as the distance between the two halves is positive, we continue - sub di, 0x70 ; Go to the previous line on the screen in video RAM. + sub di, PreviousLine ; Go to the previous line on the screen in video RAM. jns .maze_outerloop ; Keep going as long as this line is on screen. + ;mov si, bootman_data ; Use si as a pointer to the game data. This reduces byte count of the code: + ; mov reg, [address] is a 4 byte instruction, while mov reg, [si] only has 2. + ;----------------------------------------------------------------------------------------------------- ; game_loop: The main loop of the game. Tied to BIOS clock (which fires 18x per ; second by default), to ensure that the game runs at the same speed on all machines @@ -141,29 +181,28 @@ ; (in the original, collisions are checked only once, and as a consequence, it is ; possible in some circumstances to actually move Pac-Man through a ghost). ;----------------------------------------------------------------------------------------------------- + game_loop: hlt ; Wait for clock interrupt - mov ah,0x00 + mov ah, 0x00 int 0x1a ; BIOS clock read - xchg ax,dx + xchg ax, dx old_time equ $ + 1 - cmp ax,0x1234 ; Wait for time change + cmp al, 0x12 ; Wait for time change je game_loop - mov [old_time],ax ; Save new time + mov [old_time], al ; Save new time - mov si, bootman_data ; Use si as a pointer to the game data. This reduces byte count of the code: - ; mov reg, [address] is a 4 byte instruction, while mov reg, [si] only has 2. - - mov ah,0x01 ; BIOS Key available + mov bx, 3 + mov ah, 0x01 ; BIOS Key available int 0x16 cbw ; BIOS Read Key je .no_key int 0x16 - mov al,ah + mov al, ah dec ah ; Escape ? jne .convert_scancode -.exit: - mov al,3 +;.exit: + xchg ax, bx ; int 0x10 / ax = 3: Switch video mode. Switch mode to 80x25 characters int 0x10 ; Reset screen int 0x20 ; Exit to DOS... int 0x19 ; ...or reboot @@ -171,22 +210,23 @@ ; This code converts al from scancode to movement direction. ; Input: 0x48 (up), 0x4b (right), 0x50 (down), 0x4d (left) ; Output: 0xce (up), 0xca (right), 0xc6 (down), 0xc2 (left) + ; fe xx: dec dh dec dl inc dh inc dl .convert_scancode: sub al, 0x47 ; 0x01 0x04 0x09 0x06 - and al,0xfe ; 0x00 0x04 0x08 0x06 + and al, 0xfe ; 0x00 0x04 0x08 0x06 cmp al, 9 jnc .no_key ; if al >= 0x50, ignore scancode; ; this includes key release events neg al ; 0x00 0xfc 0xf8 0xfa - add al,0xce ; 0xce 0xca 0xc6 0xc8 - test al,2 + add al, 0xce ; 0xce 0xca 0xc6 0xc8 + test al, 2 jne .translated - mov al,0xc2 + mov al, 0xc2 .translated: cmp al, [si + 2] ; If the new direction is the same as the current direction, ignore it jz .no_key - mov [si + 3], al ; Set new direction to the direction derived from the keyboard input + mov [bx+si], al ; Set new direction to the direction derived from the keyboard input .no_key: dec byte [si + pace_offset] ; Decrease the pace counter. The pace counter determines the overall ; speed of the game. We found that moving at a speed of 6 per second @@ -196,30 +236,28 @@ ; and after Boot-Man dies, by intitalizing the counter with higher values. jnz game_loop ; If the pace counter is not 0, we immediately finish. - mov byte [si + pace_offset], 0x3; Reset the pace counter. + mov byte [si + pace_offset], bl ; Reset the pace counter to 3. ;----------------------------------------------------------------------------------------------------- ; Move Boot-Man ;----------------------------------------------------------------------------------------------------- - mov al, [si + 3] ; al = new movement direction, as determined by keyboard input call newpos_bootman ; Update dx to move 1 square in the direction indicated by al ; newpos also checks for collisions with walls (in which case ZF is set) jz .nodirchange ; ZF indicates that new position collides with wall. We therefore try to keep ; moving in the current direction instead. - mov [si + 2], al ; If there's no collision, update the current movement direction + mov [bx+si], al ; If there's no collision, update the current movement direction .nodirchange: - mov al, [si + 2] ; al = current movement direction call newpos_bootman ; Update dx to move 1 square in direction al jz .endbootman ; If there's a wall there, do nothing -.move: +;.move: mov ax, 0x0f20 ; Prepare to remove Boot-Man from screen, before drawing it in the new position ; 0x0f = black background, white foreground; 0x20 = space character - cmp byte [es:di], 0x04 ; Detect power pill - jnz .nopowerpill + cmp byte [es:di], ah ; Detect power pill (0x04) + ja .nopowerpill mov byte [si + timer_offset], al; If Boot-Man just ate a power pill, set up the ghost timer to 0x20. We use al here ; as it accidentally contains 0x20, and this is one byte shorter than having an ; explicit constant. .nopowerpill: - xchg dx, [si] ; Retrieve current position and store new position + xchg dx, [si + bm_offset_pos] ; Retrieve current position and store new position call paint ; Actually remove Boot-Man from the screen .endbootman: ;----------------------------------------------------------------------------------------------------- @@ -259,13 +297,12 @@ ; target 4 squares ahead of Pac-Man, in the direction Pac-Man is currently moving. The other ghosts' ; movement is a bit more complex than that. I had to simplify the AI because of the limited code size. ;----------------------------------------------------------------------------------------------------- - mov bx, 3 * gh_length + bm_length ; Set up offset to ghost data. With this, si + bx is a + mov bl, 3 * gh_length + bm_length ; Set up offset to ghost data. With this, si + bx is a ; pointer to the data from the last ghost. Also used as ; loop counter to loop through all the ghosts - mov byte [si + collision_offset], bh ; Reset collision detection. BH happens to be 0 at this point .ghost_ai_outer: - mov bp, 0xffff ; bp = minimum distance; start out at maxint - mov ah, [bx + si] ; ah will become the forbidden movement direction. We start + mov bp, si ; bp = minimum distance; start out at a big int + mov ah, [bx + si + gh_offset_dir] ; ah will become the forbidden movement direction. We start ; with the current direction, which is forbidden if Boot-Man ; just ate a power pill cmp byte [si + timer_offset], 0x20 ; If timer_offset == 0x20, Boot-Man just picked up a power pill @@ -275,24 +312,20 @@ mov al, 0xce ; al = current directions being tried. Doubles as loop counter ; over all directions. ; Values are the same as those used by the newpos routine - mov dx, [bx + si + gh_offset_pos] ; dx = current ghost position - cmp dx, [si] ; compare dx with Boot-Man position - jne .ghost_ai_loop ; If they are equal, - mov [si + collision_offset], al ; We store a non-zero value in the collision_detect flag - ; We use al here as we know it to be non-zero, and this reduces - ; code size compared to using a literal constant. + call test_collision ; dx = current ghost position .ghost_ai_loop: push dx cmp al, ah ; If the current direction is the forbidden direction jz .next ; we continue with the next direction call newpos ; Update ghost position and check if it collides with a wall jz .next ; if so, we continue with the next direction - mov cx, 0x0c10 ; Target position if ghosts are ethereal. Position 0x0c10 + push ax + mov ax, 0x0c10 ; Target position if ghosts are ethereal. Position 0x0c10 ; (x = 0x10, y = 0x0c) is in the center of the maze. cmp byte [si + timer_offset], bh ; See if ghost timer runs. We compare with bh, which is known to be 0. jnz .skip_target ; If ghost timer runs, we use the aforementioned target position - mov cx, [si] ; Otherwise we use Boot-Man's current position, - add cx, [bx + si + gh_offset_focus] ; Updated with an offset that is different for each ghost + mov ax, [si + bm_offset_pos] ; Otherwise we use Boot-Man's current position, + add ax, [bx + si + gh_offset_focus] ; Updated with an offset that is different for each ghost .skip_target: ;----------------------------------------------------------------------------------------------------- ; get_distance: Calculate distance between positions in cx (target position) and dx (ghost position) @@ -300,28 +333,20 @@ ; The square of the distance between the positions in cx and dx is calculated, ; according to Pythagoras' theorem. ;----------------------------------------------------------------------------------------------------- - push ax - sub cl, dl ; after this, cl contains the horizontal difference - sub ch, dh ; and ch the vertical difference + sub al, dl ; after this, al contains the horizontal difference + sub ah, dh ; and ah the vertical difference - mov al, ch - cbw - xchg ax,cx - cbw ; expand cl to ax and ch to cx - - push dx - mul ax - xchg ax,cx ; cx = square of vertical difference - mul ax ; ax = square of horizontal difference - pop dx - + mov cl, ah + imul al + xchg ax, cx ; cx = square of horizontal difference + imul al ; ax = square of vertical difference add cx, ax ; cx = distance squared between positions in cx and dx pop ax cmp cx, bp ; Compare this distance to the current minimum jnc .next ; and if it is, mov bp, cx ; update the minimum distance - mov [bx + si], al ; set the movement direction to the current direction + mov [bx + si + gh_offset_dir], al ; set the movement direction to the current direction mov [bx + si + gh_offset_pos], dx ; Store the new ghost position .next: pop dx ; Restore the current ghost position @@ -342,29 +367,62 @@ ; Note that this "terrain storing" approach can trigger a bug ; if Boot-Man and a ghost share a position. In that case ; an extra Boot-Man character may be drawn on screen. - mov dx, [bx + si + gh_offset_pos + gh_length] ; dx = updated ghost position - cmp dx, [si] ; compare dx with Boot-Man's position - jne .skip_collision ; and if they coincide, - mov [si + collision_offset], al ; set the collision detect flag to a non-zero value. -.skip_collision: + add bx, gh_length ; go to next ghost + call test_collision ; dx = current ghost position call get_screenpos ; find the address in video ram of the updated ghost position, mov ax, [es:di] ; store its content in ax - mov [bx + si + gh_offset_terrain + gh_length], ax ; and copy it to ghostterrain - add bx, gh_length ; go to next ghost + mov [bx + si + gh_offset_terrain], ax ; and copy it to ghostterrain cmp bx, 3 * gh_length + bm_length ; and determine if it is the final ghost jnz .ghostterrain_loop - ; Test if ghosts are invisible - mov ax, 0x2fec ; Assume ghost is visible: 0x2f = purple background, white text - ; 0xec = infinity symbol = ghost eyes - mov cl, 0x10 ; cl = difference in colour between successive ghosts - ; ch is set to zero as that leads to smaller code - cmp byte [si + timer_offset], bh ; See if ghost timer is running (note bh is still zero at this point) - jnz .ghosts_invisible ; If it is, ghosts are ethereal + mov ax, 0x0f00 ; Update ghost colour to black background, white eyes + ; Update difference between colours of successive ghosts. Value of 0x0 + ; means all ghosts are the same colour when they are ethereal. + dec byte [si + timer_offset] ; Update ghost_timer to limit the period of the ghosts being ethereal + jns .ghostcolor + mov byte [si + timer_offset], bh ; Ghost timer was not running + mov ax, ghost_n_incr +ghost_attr_patch equ $ - 2 + +.ghostcolor: + mov cl, al ; cl = difference in colour between successive ghosts + mov al, ghost_color&255 ; 0xec = infinity symbol = ghost eyes +.ghostdraw: ; Draw the ghosts on the screen + mov dx, [bx + si + gh_offset_pos] ; dx = new ghost position + call paint ; show ghost in video ram + add ah, cl ; Update ghost colour. + sub bl, gh_length ; Loop over all ghosts + jns .ghostdraw ; until the final one. - cmp byte [si + collision_offset], bh ; Ghosts are visible, so test for collisions - jz .no_collision - + mov ax, game_loop ; ret instruction will jump to game_loop + push ax + mov ax, word 0x0e02 ; Draw boot-man on the screen. 0x0e = black background, yellow foreground + ; 0x02 = smiley face +;----------------------------------------------------------------------------------------------------- +; paint: paints a character on screen at given x, y coordinates in dx +; simple convenience function that gets called enough to be actually worth it, in terms +; of code length. +;----------------------------------------------------------------------------------------------------- +paint_bootman: + mov dx, [si + bm_offset_pos] ; dx = Boot-Man position +paint: + call get_screenpos ; Convert x, y coordinates in dx to video memory address + stosw ; stosw = shorter code for mov [es:di], ax + ; stosw also adds 2 to di, but that effect is ignored here + ret + + +;----------------------------------------------------------------------------------------------------- +; test_collision: if end of game, restart from the beginning +;----------------------------------------------------------------------------------------------------- + +test_collision: + mov dx, [bx + si + gh_offset_pos] ; dx = current ghost position + cmp dx, [si + bm_offset_pos] ; compare dx with Boot-Man position + jnz no_collision + cmp byte [si + timer_offset], bh ; Ghost timer was not running + jnz no_collision + pop ax ; Adjust stack %ifdef WaitForAnyKey ; Ghosts are visible and collide with boot-man, therefore boot-man is dead mov ax, 0x0e0f ; Dead boot-man: 0x0e = black background, yellow foreground @@ -373,51 +431,28 @@ cbw int 0x16 ; Wait for any key %endif -jump_start equ $ + 1 - mov si,0xFFFC ; restart boot sector - push si - ret - - ; Ghosts are invisible -.ghosts_invisible: - dec byte [si + timer_offset] ; Update ghost_timer to limit the period of the ghosts being ethereal - mov ah, 0x0f ; Update ghost colour to black background, white eyes - mov cl, 0x0 ; Update difference between colours of successive ghosts. Value of 0x0 - ; means all ghosts are the same colour when they are ethereal. - -.no_collision: -.ghostdraw: ; Draw the ghosts on the screen - mov dx, [bx + si + gh_offset_pos] ; dx = new ghost position - call paint ; show ghost in video ram - add ah, cl ; Update ghost colour. - sub bx, gh_length ; Loop over all ghosts - jns .ghostdraw ; until the final one. - - - mov ax, word 0x0e02 ; Draw boot-man on the screen. 0x0e = black background, yellow foreground - ; 0x02 = smiley face - call paint_bootman ; show Boot-Man - -.end: - jmp game_loop + mov si, 0xfb5e ; restart boot sector +source equ $ - 2 ;----------------------------------------------------------------------------------------------------- ; copy: self copy to a fixed location ;----------------------------------------------------------------------------------------------------- -copy: - pop si ; Get ip value (0x103 or 0x7C03, sometimes 0x0003) + +copy equ $ - 2 ; seek to 'pop si, sti' instruction (0x5e 0xfb) +; pop si ; Get ip value (0x103 or 0x7C03, sometimes 0x0003) +; sti ; Allow interrupts push cs pop ds push cs ; Setup ds and es pop es - and [si+jump_start-moved], si ; Save value to the restart with unpatched code mov di, moved ; Move self to a well known address push di - mov cx, 512-3 - sti ; Allow interrupts + mov ch, (end-moved)/256+1 cld ; Clear the direction flag. We use string instructions a lot as they have one-byte codes + mov [si+source-moved], si ; Save value to the restart with unpatched code rep movsb +no_collision: ret @@ -453,7 +488,9 @@ ;----------------------------------------------------------------------------------------------------- newpos_bootman: - mov dx, [si] ; dx = current position of Boot-Man + mov al, [bx+si] ; al = new or current movement direction + dec bx + mov dx, [si + bm_offset_pos] ; dx = current position of Boot-Man newpos: mov [.modified_instruction + 1], al ; Here the instruction to be executed is modified .modified_instruction: @@ -461,84 +498,78 @@ and dl, 0x1f ; Deal with tunnels get_screenpos: xchg ax,di ; save ax - mov al, 0x28 + mov al, Columns +Columns_patch equ $ - 1 mul dh ; multiply ax by 0x28 = 40 decimal, the screen width add al, dl - adc ah, 0 + adc ah, bh xchg ax, di ; di = y * 40 + x +%ifdef MDAsupport + add di, Margin +Margin_patch equ $ - 1 +%else scasw ; Skip the left border by adding 4 to di scasw - shl di, 1 ; Multiply di by 2 +%endif + add di, di ; Multiply di by 2 cmp byte [es:di], 0xdb ; Check to see if the new position collides with a wall ; 0xdb = full block character that makes up the wall ret -;----------------------------------------------------------------------------------------------------- -; paint: paints a character on screen at given x, y coordinates in dx -; simple convenience function that gets called enough to be actually worth it, in terms -; of code length. -;----------------------------------------------------------------------------------------------------- -paint_bootman: - mov dx, [si] ; dx = Boot-Man position -paint: - call get_screenpos ; Convert x, y coordinates in dx to video memory address - stosw ; stosw = shorter code for mov [es:di], ax - ; stosw also adds 2 to di, but that effect is ignored here - ret - - -bootman_data: - db 0x0f, 0x0f ; Boot-Man's x and y position - db 0xca ; Boot-Man's direction - db 0xca ; Boot-Man's future direction - -pace_counter: db 0x10 -ghost_timer: db 0x0 ; if > 0 ghosts are invisible, and is counted backwards to 0 - -ghostdata: - db 0xc2 ; 1st ghost, direction -ghostpos: - db 0x01, 0x01 ; x and y position -ghostterrain: - dw 0x0ff9 ; terrain underneath -ghostfocus: - db 0x0, 0x0 ; focus point for movement -secondghost: - db 0xce ; 2nd ghost, direction - db 0x01, 0x17 ; x and y position - dw 0x0ff9 ; terrain underneath - db 0x0, 0x4 ; focus point for movement - db 0xca ; 3rd ghost, direction - db 0x1e, 0x01 ; x and y position - dw 0x0ff9 ; terrain underneath - db 0xfc, 0x0 ; focus point for movement - db 0xce ; 4th ghost, direction - db 0x1e, 0x17 ; x and y position - dw 0x0ff9 ; terrain underneath - db 0x4, 0x0 ; focus point for movement -lastghost: - -bm_length equ ghostdata - bootman_data -gh_length equ secondghost - ghostdata -gh_offset_pos equ ghostpos - ghostdata -gh_offset_terrain equ ghostterrain - ghostdata -gh_offset_focus equ ghostfocus - ghostdata -pace_offset equ pace_counter - bootman_data -timer_offset equ ghost_timer - bootman_data - ; The maze, as a bit array. Ones denote walls, zeroes denote food dots / corridors ; The maze is stored upside down to save one cmp instruction in buildmaze maze: dw 0xffff, 0x8000, 0xbffd, 0x8081, 0xfabf, 0x8200, 0xbefd, 0x8001 dw 0xfebf, 0x0080, 0xfebf, 0x803f, 0xaebf, 0xaebf, 0x80bf, 0xfebf dw 0x0080, 0xfefd, 0x8081, 0xbebf, 0x8000, 0xbefd, 0xbefd, 0x8001 dw 0xffff -maze_length: equ $ - maze -; Collision detection flag. It is initialized by the code -collision_detect: +bootman_data: +bootmanpos: + db 0x0f, 0x0f ; Boot-Man's x and y position +bootmandir: + db 0xca ; Boot-Man's direction + db 0xca ; Boot-Man's future direction -collision_offset equ collision_detect - bootman_data +ghost_timer equ maze + 2 ; if > 0 ghosts are invisible, and is counted backwards to 0 +;pace_counter equ maze + 22 ; 0x3f +pace_counter: db 0x10 + +ghostdata: +ghostpos: + db 0x01, 0x01 ; 1st ghost, x and y position +ghostdir: + db 0xc2 ; direction +ghostterrain: + dw 0x0ff9 ; terrain underneath +ghostfocus: + db 0x0, 0x0 ; focus point for movement +secondghost: + db 0x01, 0x17 ; 2nd ghost, x and y position + db 0xce ; direction + dw 0x0ff9 ; terrain underneath + db 0x0, 0x4 ; focus point for movement + db 0x1e, 0x01 ; 3rd ghost, x and y position + db 0xca ; direction + dw 0x0ff9 ; terrain underneath + db 0xfc, 0x0 ; focus point for movement + db 0x1e, 0x17 ; 4th ghost, x and y position + db 0xce ; direction + dw 0x0ff9 ; terrain underneath + db 0x4, 0x0 ; focus point for movement +lastghost: + +bm_length equ ghostdata - bootman_data +gh_length equ secondghost - ghostdata +gh_offset_dir equ ghostdir - ghostdata +gh_offset_pos equ ghostpos - ghostdata +gh_offset_terrain equ ghostterrain - ghostdata +gh_offset_focus equ ghostfocus - ghostdata +pace_offset equ pace_counter - bootman_data +timer_offset equ ghost_timer - bootman_data +bm_offset_pos equ bootmanpos - bootman_data times 510 - ($ - $$) db 0 db 0x55 -db 0xaa \ No newline at end of file +db 0xaa + +end: diff -r 61c76233911e -r 34a0a4406539 bootbricks/stuff/bricks.asm --- a/bootbricks/stuff/bricks.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/bootbricks/stuff/bricks.asm Sun Feb 04 18:02:38 2024 +0000 @@ -14,32 +14,52 @@ ; Press Left Alt to move the paddle to the right ; -old_time: equ 16 ; Old time +old_time: equ 16 ; word: Old time ball_x: equ 14 ; X-coordinate of ball (8.8 fraction) ball_y: equ 12 ; Y-coordinate of ball (8.8 fraction) ball_xs: equ 10 ; X-speed of ball (8.8 fraction) ball_ys: equ 8 ; Y-speed of ball (8.8 fraction) -beep: equ 6 ; Frame count to turn off sound -bricks: equ 4 ; Remaining bricks -score: equ 2 ; Current score -balls: equ 0 ; Remaining balls +beep: equ 6 ; byte: Frame count to turn off sound +bricks: equ 4 ; word: Remaining bricks +score: equ 2 ; word: Current score +balls: equ 0 ; byte: Remaining balls + +%define MDA_SUPPORT ; ; Start of the game ; start: + cld sti ; Allow interrupts - mov ax,0xb800 ; Address of video screen - mov ds,ax ; Setup DS - mov es,ax ; Setup ES - sub sp,32 - cbw + push cs + pop ss + mov sp,0xb800 ; Address of video screen +.set_segments: + mov ds,sp ; Setup DS + mov es,sp ; Setup ES +%ifdef MDA_SUPPORT + xor di,di + mov sp,0xb000 ; Address of video screen + inc word [di] + jz .set_segments + xchg ax,di +%else + xor ax,ax +%endif push ax ; Reset score mov al,4 push ax ; Balls remaining mov bp,sp ; Setup stack frame for globals mov al,0x02 ; Text mode 80x25x16 colors int 0x10 ; Setup +%if 0 + ; Disable VGA text mode cursor + ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor + mov ah, 0x01 + mov ch, 0x3f + int 0x10 +%endif ; ; Start another level ; @@ -48,7 +68,6 @@ xor di,di mov ax,0x01b1 ; Draw top border mov cx,80 - cld rep stosw mov cl,24 ; 24 rows .1: @@ -64,13 +83,17 @@ .2: mov cl,39 ; 39 bricks per row .3: + test ah,0x07 + jne .4 +%ifdef MDA_SUPPORT + mov ah,0x02 +%else + mov ah,0x01 +%endif +.4: stosw stosw inc ah ; Increase attribute color - cmp ah,0x08 - jne .4 - mov ah,0x01 -.4: loop .3 pop cx @@ -85,9 +108,8 @@ another_ball: mov byte [bp+ball_x+1],0x28 ; Center X mov byte [bp+ball_y+1],0x14 ; Center Y - xor ax,ax - mov [bp+ball_xs],ax ; Static on screen - mov [bp+ball_ys],ax + and word [bp+ball_xs],0 ; Static on screen + and word [bp+ball_ys],0 mov byte [bp+beep],0x01 mov si,0x0ffe ; Don't erase ball yet @@ -172,7 +194,7 @@ mov al,[bx] cmp al,0xb1 ; Touching borders jne .3 - mov cx,5423 ; 1193180 / 220 + mov cl,5423/256 ; 1193180 / 220 call speaker ; Generate sound pop bx pop ax @@ -197,30 +219,29 @@ shl bx,cl mov [bp+ball_xs],bx ; New X speed for ball mov word [bp+ball_ys],0xff80 ; Update Y speed for ball - mov cx,2711 ; 1193180 / 440 + mov cl,2711/256 ; 1193180 / 440 call speaker ; Generate sound - pop bx +.16: pop bx pop ax jmp .14 .4: cmp al,0xdb ; Touching brick jne .5 - mov cx,1355 ; 1193180 / 880 + mov cl,1355/256 ; 1193180 / 880 call speaker ; Generate sound test bl,2 ; Aligned with brick? jne .10 ; Yes, jump dec bx ; Align dec bx -.10: xor ax,ax ; Erase brick - mov [bx],ax - mov [bx+2],ax +.10: and word [bx],0 ; Erase brick + and word [bx+2],0 inc word [bp+score] ; Increase score neg word [bp+ball_ys] ; Negate Y speed (rebound) + dec word [bp+bricks] ; One brick less on screen + jne .16 ; Fully completed? No, jump. pop bx pop ax - dec word [bp+bricks] ; One brick less on screen - jne .14 ; Fully completed? No, jump. jmp another_level ; Start another level .5: @@ -237,7 +258,7 @@ ; Ball lost ; ball_lost: - mov cx,10846 ; 1193180 / 110 + mov cl,10846/256 ; 1193180 / 110 call speaker ; Generate sound and word [si],0 ; Erase ball @@ -257,31 +278,27 @@ je .0 mov [bp+old_time],dx - dec byte [bp+beep] ; Decrease time to turn off beep - jne .1 + dec byte [bp+beep] ; Decrease time to turn off beep + jne speaker.2 .2: in al,0x61 and al,0xfc ; Turn off - out 0x61,al -.1: - - ret + jmp speaker.1 ; ; Generate sound on PC speaker ; speaker: + mov byte [bp+beep],3 ; Duration mov al,0xb6 ; Setup timer 2 out 0x43,al - mov al,cl ; Low byte of timer count - out 0x42,al - mov al,ch ; High byte of timer count + out 0x42,al ; Low byte of timer count + xchg ax,cx ; High byte of timer count out 0x42,al in al,0x61 or al,0x03 ; Connect PC speaker to timer 2 - out 0x61,al - mov byte [bp+beep],3 ; Duration - ret +.1: out 0x61,al +.2: ret ; ; Locate ball on screen @@ -291,7 +308,7 @@ mul ah ; AH = Y coordinate (row) mov bl,bh ; BH = X coordinate (column) mov bh,0 - shl bx,1 + add bx,bx add bx,ax ret diff -r 61c76233911e -r 34a0a4406539 bootfbird/stuff/fbird.asm --- a/bootfbird/stuff/fbird.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/bootfbird/stuff/fbird.asm Sun Feb 04 18:02:38 2024 +0000 @@ -11,45 +11,55 @@ ; previous score. ; +%define MDA_SUPPORT + use16 cpu 8086 -restart: - mov ax,0x0002 ; Set 80x25 text mode - int 0x10 ; Call BIOS +%ifdef FAT_BOOT + jmp bootbr + nop + times 0x3B db 0 +bootbr: +%endif + cld ; Reset direction flag (so stosw increments registers) + sti ; Allow interrupts + push cs + pop ss + mov sp,0xb800 ; Point to video segment +SetSegments: + mov ds,sp ; Both the source (common access) + mov es,sp ; and target segments ; ; Game restart ; -fb21: cld ; Reset direction flag (so stosw increments registers) - sti ; Allow interrupts - mov ax,0xb800 ; Point to video segment - mov ds,ax ; Both the source (common access) - mov es,ax ; and target segments - mov di,pipe ; Init variables in video segment (saves big bytes) - cbw - stosw ; pipe - stosw ; score - stosw ; grav - mov al,0xa0 - stosw ; next - mov al,0x60 - stosw ; bird +fb21: + mov ax,0x0002 ; Set 80x25 text mode + int 0x10 ; Call BIOS + mov di,next ; Init variables in video segment (saves big bytes) +%ifdef MDA_SUPPORT + mov sp,0xb000 + inc byte [di] + je SetSegments +%endif + mov ax,0x60a0 + stosw ; next, bird + xor ax,ax + stosw ; grav, pipe + stosb + + ; Disable text mode cursor + ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor + mov ah, 0x01 + mov ch, 0x3f + int 0x10 mov di,0x004a ; Game title - mov ax,0x0f46 ; 'F' in white, good old ASCII - stosw - mov al,0x2d ; '-' - stosw - mov al,0x42 ; 'B' - stosw - mov al,0x49 ; 'I' - stosw - mov al,0x52 ; 'R' - stosw - mov al,0x44 ; 'D' - stosw + call print_string + db 'F-BIRD' mov bp,80 ; Introduce 80 columns of scenery fb1: call scroll_scenery +%define SI(n) [si+n-0x0fa2] dec bp jnz fb1 @@ -64,101 +74,43 @@ ; ; Main loop ; -fb12: mov si,bird - lodsw ; Bird falls... - add al,[grav] ; ...because of gravity... +fb12: mov ax,[bird] ; Bird falls... + add al,ah ; ...because of gravity... mov [bird],al ; ...into new position. and al,0xf8 ; Row is a 5.3 fraction, nullify fraction mov ah,0x14 ; Given integer is x8, multiply by 20 to get 160 per line mul ah ; Row into screen add ax,$0020 ; Fixed column xchg ax,di ; Pass to DI (AX cannot be used as pointer) - lodsb ; mov al,[frame] + mov dx,$0d1f ; Draw body + mov ax,bp ; [frame] + mov bx,-160 and al,4 ; Wing movement each 4 frames jz fb15 - mov al,[di-160] ; Get character below - mov word [di-160],$0d1e ; Draw upper wing - add al,[di] ; Add another character below - shr al,1 ; Normalize - mov word [di],$0d14 ; Draw body + mov ax,$0d1e ; Draw upper wing + xchg ax,[bx+di] ; Get character below jmp short fb16 - -fb15: mov al,[di] ; Get character below - mov word [di],$0d1f ; Draw body -fb16: add al,[di+2] ; Get character below head - mov word [di+2],$0d10 ; Draw head - cmp al,0x40 ; Collision with scenery? - jz fb19 ; ; Stars and game over ; - mov byte [di],$2a ; '*' Asterisks to indicate crashing - mov byte [di+2],$2a +fb19: mov al,$2a ; '*' Asterisks to indicate crashing + stosb + inc di + stosb mov di,0x07CA - mov ax,0x0f42 ; 'B' in white, good old ASCII - stosw - mov al,0x4F ; 'O' - stosw - mov al,0x4E ; 'N' - stosw - mov al,0x4B ; 'K' - stosw - mov al,0x21 ; '!' - stosw - mov bp,100 ; Wait 100 frames + call print_string + db 'BONK!' + mov bp,-100 ; Wait 100 frames fb20: call wait_frame - dec bp jnz fb20 jmp fb21 ; Restart -fb19: call wait_frame ; Wait for frame - mov al,[frame] - and al,7 ; 8 frames have passed? - jnz fb17 ; No, jump - inc word [grav] ; Increase gravity -fb17: - mov al,$20 - mov [di-160],al ; Delete bird from screen - mov [di+2],al - stosb - call scroll_scenery ; Scroll scenery - call scroll_scenery ; Scroll scenery - cmp byte [0x00a0],0xb0 ; Passed a column? - jz fb27 - cmp byte [0x00a2],0xb0 ; Passed a column? -fb27: jnz fb24 - inc word [score] ; Increase score - mov ax,[score] - mov di,0x008e ; Show current score -fb25: xor dx,dx ; Extend AX to 32 bits - mov bx,10 ; Divisor is 10 - div bx ; Divide - add dx,0x0c30 ; Convert remaining 0-9 to ASCII, also put color - xchg ax,dx - std - stosw - mov byte [di],0x20 ; Clean at least one character of prev. score - cld - xchg ax,dx - or ax,ax ; Score digits still remain? - jnz fb25 ; Yes, jump -fb24: mov ah,0x01 ; Any key pressed? - int 0x16 - jz fb26 ; No, go to main loop - mov ah,0x00 - int 0x16 ; Get key - dec ah ; Escape key? - jne fb4 ; No, jump - mov al,3 - int 0x10 - int 0x20 ; Exit to DOS or to oblivion (boot sector) - int 0x19 -fb4: mov ax,[bird] - sub ax,0x10 ; Move bird two rows upward - cmp ax,0x08 ; Make sure the bird doesn't fly free outside screen +fb4: mov al,[bird] + cmp al,0x18 ; Make sure the bird doesn't fly free outside screen jb fb18 + sub al,0x10 ; Move bird two rows upward +fb18: cbw ; Reset gravity mov [bird],ax -fb18: mov byte [grav],0 ; Reset gravity mov al,0xb6 ; Flap sound out (0x43),al mov al,0x90 @@ -170,6 +122,56 @@ out (0x61),al fb26: jmp fb12 +fb16: mov dl,$14 ; Draw body +fb15: or al,[di] ; Add another character below + mov [di],dx ; Draw body + mov dl,$10 ; Draw head + xchg dx,[di+2] ; Get character below head + add al,dl + cmp al,0x40 ; Collision with scenery? + jnz fb19 + call wait_frame ; Wait for frame + mov ax,bp ; [frame] + and al,7 ; 8 frames have passed? + jnz fb17 ; No, jump + inc byte SI(grav) ; Increase gravity +fb17: + mov al,$20 + mov [bx+di],al ; Delete bird from screen + stosb + inc di + stosb + call scroll_scenery ; Scroll scenery + call scroll_scenery ; Scroll scenery + mov di,bx + mov al,0xb0 + scasb ; Passed a column? + jz fb27 + inc di + scasb ; Passed a column? + jnz fb24 +fb27: mov di,0x008e ; Show current score +fb25: dec di + dec di + mov ax,0x0c30 ; Convert remaining 0-9 to ASCII, also put color + xchg [di], ax + or al, 0x30 + cmp al, '9' + je fb25 + inc ax + stosb +fb24: mov ah,0x01 ; Any key pressed? + int 0x16 + jz fb26 ; No, go to main loop + mov ah,0x00 + int 0x16 ; Get key + dec ah ; Escape key? + jne fb4 ; No, jump +quit: mov al,3 + int 0x10 + int 0x20 ; Exit to DOS or to oblivion (boot sector) + int 0x19 + ; ; Scroll scenery one column at a time ; @@ -179,6 +181,7 @@ ; mov si,0x00a2 ; Point to row 1, column 1 in SI mov di,0x00a0 ; Point to row 1, column 0 in DI + mov bx,di fb2: mov cx,79 ; 79 columns repz ; Scroll!!! movsw @@ -190,42 +193,49 @@ ; ; Insert houses ; - mov word [0x0f9e],0x02df ; Terrain + mov word SI(0x0f9e),0x02df ; Terrain in al,(0x40) ; Get "random" number - and al,0x70 + test al,0x70 jz fb5 - mov bx,0x0408 ; House of one floor - mov [0x0efe],bx + mov dx,0x0408 ; House of one floor mov di,0x0e5e - and al,0x20 ; Check "random" number + mov [bx+di],dx + test al,0x20 ; Check "random" number jz fb3 - mov [di],bx ; House of two floors - sub di,0x00a0 + mov [di],dx ; House of two floors + sub di,bx fb3: mov word [di],0x091e ; Add roof ; ; Check if it's time to insert a column ; -fb5: dec word [next] ; Decrease time (column really) for next pipe - mov bx,[next] - cmp bx,0x03 ; bx = 3,2,1,0 for the four columns making the pipe - ja fb6 - jne fb8 - in al,(0x40) ; Get "random" number - and ax,0x0007 ; Between 0 and 7 +fb5: dec byte SI(next) ; Decrease time (column really) for next pipe + mov dh,SI(next) + mov dl,0xb1 ; Leftmost + mov cl,3 + cmp dh,cl ; bl = 3,2,1,0 for the four columns making the pipe + ja fb6 ; No yet, jump + jne fb8 ; No leftmost, jump + and al,0x07 ; Between 0 and 7 add al,0x04 ; Between 4 and 11 - mov [tall],ax ; This will tell how tall the pipe is -fb8: mov cx,[tall] - or bx,bx ; Rightmost? + mov SI(tall),al ; This will tell how tall the pipe is + jmp fb7 ; Leftmost, jump + +fb8: mov dl,0xdb + or dh,dh ; Rightmost? + jnz fb7 ; No, jump mov dl,0xb0 - jz fb7 ; Yes, jump - mov dl,0xdb - cmp bx,0x03 ; Leftmost? - jb fb7 ; No, jump - mov dl,0xb1 + dec word SI(pipe) ; Increase total pipes shown + or byte SI(pipe+1),0xfe + mov ax,SI(pipe) + sar ax,cl + add al,0x50 ; Decrease distance between pipes + mov SI(next),al ; Time for next pipe fb7: mov di,0x013e ; Start from top of screen + xchg ax,dx mov ah,0x0a - mov al,dl + push ax mov dx,0x009e + mov cl,SI(tall) fb9: stosw add di,dx loop fb9 @@ -234,56 +244,53 @@ add di,0x009e*6+10 mov al,0xdf stosw - add di,dx -fb10: mov al,dl + pop ax +fb10: add di,dx stosw - add di,dx - cmp di,0x0f00 + cmp di,0x0ea0 jb fb10 - or bx,bx - jnz fb6 - mov ax,[pipe] - inc ax ; Increase total pipes shown - mov [pipe],ax - mov cl,3 - shr ax,cl - mov ah,0x50 ; Decrease distance between pipes - sub ah,al - cmp ah,0x10 - ja fb11 - mov ah,0x10 -fb11: mov [next],ah ; Time for next pipe fb6: ret ; ; Wait for a frame ; wait_frame: - - mov bx, tick fb14: hlt ; Wait for clock interrupt mov ah,0x00 ; Use base clock tick int 0x1a - cmp dx,[bx] + cmp dx,[tick] jz fb14 - mov [bx],dx - inc word [bx+frame-tick] ; Increase frame count + mov [tick],dx in al,(0x61) - and al,0xfc ; Turn off sound + and al,0xfc ; Turn off sound out (0x61),al + inc bp ; Increase frame count ret - times 507 - ($-$$) db 0 ; pad remaining 507 bytes with zeroes + ; + ; Print a string + ; +print_string: + pop si + mov ah, 0xf ; white + db 0x3C ; cmp al,0xab +pr1: stosw + cs lodsb + cmp al,0xbd + jne pr1 + dec si + push si + ret - db "OTG" ; 3 unused bytes + times 510 - ($-$$) db 0 ; pad remaining 510 bytes with zeroes db 0x55,0xaa ; Bootable signature -pipe: equ 0x0fa0 -score: equ 0x0fa2 -grav: equ 0x0fa4 -next: equ 0x0fa6 -bird: equ 0x0fa8 -frame: equ 0x0faa -tall: equ 0x0fac -tick: equ 0x0fae +tick: equ 0x0fa0 ; word + +next: equ tick+2 ; byte +bird: equ tick+3 ; byte +grav: equ bird+1 ; byte +pipe: equ tick+5 ; word + +tall: equ tick+7 ; byte diff -r 61c76233911e -r 34a0a4406539 bootlife/receipt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootlife/receipt Sun Feb 04 18:02:38 2024 +0000 @@ -0,0 +1,33 @@ +# SliTaz package receipt. + +PACKAGE="bootlife" +VERSION="1.0" +CATEGORY="games" +SHORT_DESC="Bootable Conway's life game in a 512-byte boot sector." +MAINTAINER="pascal.bellard@slitaz.org" +LICENSE="MIT" +WEB_SITE="http://www.slitaz.org" +TARGET="i486" + +BUILD_DEPENDS="nasm" + +# Rules to configure and make the package. +compile_rules() +{ + mkdir -p $src + nasm -o $src/life.com -l $src/life.lst $stuff/life.asm +} + +# Rules to gen a SliTaz package suitable for Tazpkg. +genpkg_rules() +{ + mkdir -p $fs/boot + cp $src/life.com $fs/boot/life +} + +# Post install/remove commands for Tazpkg. +post_install() +{ + grep -qs ^bootlife $1/boot/bootmenu || + echo "life Life,life Conway's life game (may run under DOS if renamed to life.com)" >> $1/boot/bootmenu +} diff -r 61c76233911e -r 34a0a4406539 bootlife/stuff/life.asm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootlife/stuff/life.asm Sun Feb 04 18:02:38 2024 +0000 @@ -0,0 +1,413 @@ +bits 16 + +%define ResetScreen +%define StepCounter + +cpu 8086 +org 0x7c00 + +;; Constants + +;; Boot sector size in bytes +%assign BootSector.Size 512 + +;; Words in 16 bit x86 are 2 bytes +%assign WordSize 2 + +%assign DigitsNumber 6 + +;; This is the value to store in segment register to access the MDA and the VGA text buffer. +;; In 16 bit x86, segmented memory accesses are of the form: +;; +;; (segment register) * 0x10 + (offset register) +;; +;; The MDA text buffer is at 0xb00000, so if 0xb000 is stored in a segment +;; register, then memory access instructions will be relative to the MDA text +;; buffer, allowing easier access. For example, trying to access the nth byte of +;; memory will *actually* access the nth byte of the text buffer. +%assign TextBuf.Seg 0xb000 +%assign Vga.TextBuf.Offset 0x8000 + +%assign BitMap 0x8000 + +;; Dimensions of text buffer +%assign TextBuf.Width 80 +%assign TextBuf.Height 25 +%assign TextBuf.Size (TextBuf.Width * TextBuf.Height) + +;; Macro to get the index of a text buffer cell from coordinates +%define TextBuf.Index(y, x) ((y) * TextBuf.Width * 2 + (x) * 2) + +%assign Pixel.UpDown 0xdb +%assign Pixel.Up 0xdf +%assign Pixel.Down 0xdc +%assign Pixel.Free 0x20 +%assign Cursor.Up 0x1e +%assign Cursor.Down 0x1f + +;; Keyboard scan codes +;; http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html +%assign Key.ScanCode.Tab 0x0f +%assign Key.ScanCode.Space 0x39 +%assign Key.ScanCode.Up 0x48 +%assign Key.ScanCode.Down 0x50 +%assign Key.ScanCode.Left 0x4b +%assign Key.ScanCode.Right 0x4d +%assign Key.ScanCode.Enter 0x1c +%assign Key.ScanCode.QuitGame 0x01 + +;; BootLife is supported as both a DOS game and a boot sector game :) + +;; Entry point: set up graphics and run game +BootLife: + cld + sti ;allow interrupts + +%ifdef ResetScreen + ; MDA/VGA text mode 0x03 + ; 80x25 text resolution + mov ax, 3 + int 0x10 + + ; Disable VGA text mode cursor + ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor + mov ah, 0x01 + mov ch, 0x3f + int 0x10 +%endif + +;; Run game (the game is restarted by jumping here) +RunGame: + push cs + pop ss + mov sp, TextBuf.Seg + + ; Load VGA text buffer segment into segment registers + mov es, sp + push cs + pop ds + +;; +tick: + call get_data +data: +%define INITDATA_COLS 36 +%define INITDATA_POS 24 +%define INITDATA_OFFSET TextBuf.Width*5+10 +datainit: + db 0x01 +cursor: db 0x00 +xpos: db 0x00,0x00,0x14 +ypos: db 0x00 + db 0x00,0x30,0x30 +cmd: db 0x00,0x0c , 0x80,0x08,0x03,0xc0 + db 0x03,0x04,0x31,0x00,0x30 , 0x40,0x34,0x14 +cursor_pos: + db 0x00 + db 0x00,0x04,0x01,0x01,0x00 , 0x80,0x08,0x00,0x00 + db 0x00,0x30 +datainit_end: +;012345670123456701234567012345670123 +; X ; +; X X ; +; XX XX XX; +; X X XX XX; +;XX X X XX ; +;XX X X XX X X ; +; X X X ; +; X X ; +; XX ; +;awk 'BEGIN { n = split("0x00,0x00...0x00", t,",") +; i=1; x=strtonum(t[i]); b=8; c=36 +; while (i <= n) { +; if (and(x,1) == 0) printf " "; else printf "X" +; x=rshift(x,1) +; if (--c == 0) { printf "\n" ; c=36 ; } +; if (--b == 0) { x=strtonum(t[++i]) ; b=8 ; } +; } +; printf "\n" +;}' < /dev/null + + +get_data: + pop bp +%define BP(x) [bp + x - data] +ZeroBuf: + mov dx, INITDATA_COLS-INITDATA_POS + mov cx, TextBuf.Size*2+DigitsNumber+1 + mov bx, BitMap +.1: + mov byte [bx], dh + inc bx + loop .1 + + mov bx, BitMap+INITDATA_POS+INITDATA_OFFSET + mov si, bp + mov cl, datainit_end-datainit +.next_byte: + lodsb + mov ah,8 +.next_bit: + shr al,1 + adc byte [bx], dh + inc bx + dec dx + jnz .noln + mov dl, INITDATA_COLS + add bx, TextBuf.Width-INITDATA_COLS +.noln: + dec ah + jnz .next_bit + loop .next_byte + +;; Main loop to process key presses and update state +GameLoop: +;; Display board + xor di, di + mov si, TextBuf.Width + mov bx, BitMap + mov dx, TextBuf.Height + 256 + push bx +.line: + mov cx, si +.next: + mov ax, Pixel.Free + 256*Pixel.Up + test byte [bx + si], dh + jz .NotDown + mov ax, Pixel.Down + 256*Pixel.UpDown +.NotDown: + test byte [bx], dh + jz .NotUp + mov al, ah +.NotUp: + inc bx + call PutChar + loop .next + add bx, si + dec dl + jnz .line + pop bx + + call PutCursor + +%ifdef StepCounter + mov si, BitMap+TextBuf.Size*2+DigitsNumber +.next_digit: + mov byte [si], ch + dec si + inc byte [si] + cmp byte [si], 10 + je .next_digit + mov cl, DigitsNumber + mov si, BitMap+TextBuf.Size*2 + mov di, 6 +.digit: + lodsb + or al, '0' + call PutChar + loop .digit +%endif + + cmp byte BP(cmd), Key.ScanCode.Enter + jnz wait_key + + mov ah,0x01 ; Any key pressed? + int 0x16 + jz nokey ; No, go to main loop +wait_key: + mov ah,0x00 + int 0x16 ; Get key + mov al, ah + mov byte BP(cmd), al + cmp al, Key.ScanCode.QuitGame + jne notesc ; No, jump +%ifdef ResetScreen + mov al,3 + int 0x10 +%endif + int 0x20 ; Exit to DOS or to oblivion (boot sector) + int 0x19 + +notesc: + mov cx, BP(ypos) + mov di, BP(xpos) + + cmp al, Key.ScanCode.Tab + jne not_tab + call GetTextBufIndex + xor byte [bx + si], 1 +GoGameLoop: + jmp GameLoop + +PutCursor: + xchg ax, dx + xchg al, BP(cursor) + mov di, BP(cursor_pos) + cmp al, 0 + je Ret +PutChar: + mov [es:di + Vga.TextBuf.Offset], al + stosb + inc di +Ret: ret + +not_tab: +CmpUp: + ; Move cursor up + dec cx + cmp al, Key.ScanCode.Up + je WrapCursor + inc cx +CmpDown: + ; Move cursor down + inc cx + cmp al, Key.ScanCode.Down + je WrapCursor + dec cx +CmpLeft: + ; Move cursor left + dec di + cmp al, Key.ScanCode.Left + je WrapCursor + inc di +CmpRight: + ; Move cursor right + inc di + cmp al, Key.ScanCode.Right + jne nokey +WrapCursor: + call GetTextBufIndex + mov BP(ypos), cx + mov BP(xpos), di + mov al, Cursor.Up + shr cx, 1 + adc al, ch ; Cursor.Down = Cursor.Up + 1 + add cx, cx + call GetTextBufIndex + add si, di + mov BP(cursor_pos), si + mov BP(cursor), al +GoGameLoop2: + jmp GoGameLoop + +wait_tick: + hlt ; Wait for clock interrupt +nokey: + mov ah, 0x00 ; Use base clock tick + int 0x1a + cmp dx, BP(tick) + jz wait_tick + mov BP(tick), dx + +;; Compute next board + mov cx, TextBuf.Height*2-1 +.loop_y: + mov di, TextBuf.Width-1 +.loop_x: + call CountCells ; si = &TextBuf[cx = y][di = x] + sub al, 3 ; empty cell with 3 neighbors or full cell with 2 neighbors + je .alive + sub al, [bx + si] ; full cell with 3 neighbors + jne .dead +.alive: + or byte [bx + si], 2 +.dead: + dec di + jns .loop_x + dec cx + jns .loop_y + + mov cx, TextBuf.Size*2 +.loop_shr: + shr byte [bx], 1 + inc bx + loop .loop_shr + + jmp GoGameLoop2 + + ; Count adjacent cells +CountCells: + xor ax, ax + + ; Count left-row + dec di + call UpAndCountRow + + ; Count center-row + call CountNextRow + + ; Count right-row + call CountNextRow + + ; re-center + dec cx + dec di + +;; Compute the text buffer index from y and x coordinates +;; +;; si = &TextBuf[cx = y][di = x] +;; +;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime +;; +;; Parameters: +;; * cx - y coordinate +;; * di - x coordinate +;; Returns: +;; * si - text buffer index +GetTextBufIndex: + ; Base case: bounds check y + mov si, TextBuf.Width + cmp cx, TextBuf.Height*2 + jb .check_x + sub cx, TextBuf.Height*2 ; overflow ? + jns .check_x + add cx, TextBuf.Height*4 ; underflow ! +.check_x: + ; Base case: bounds check x + cmp di, si + jb .check_ok + sub di, si + jns .check_ok + add di, TextBuf.Width*2 +.check_ok: + xchg ax, si + imul cl + xchg ax, si + add si, di + ret + +CountNextRow: + inc di + dec cx +UpAndCountRow: + dec cx +CountRow: + call Count + call CountDown +CountDown: + inc cx +Count: + ; si = &TextBuf[cx = y][di = x] + call GetTextBufIndex +%if 0 + cmp [bx + si], ah +%else + test byte [bx + si], 1 +%endif + je .Ret + inc ax +.Ret: ret + +;; Print program size at build time +%assign CodeSize $ - $$ +%assign Size $ - BootLife +%warning Code is Size bytes + +CodeEnd: + ; Pad to size of boot sector, minus the size of a word for the boot sector + ; magic value. If the code is too big to fit in a boot sector, the `times` + ; directive uses a negative value, causing a build error. + times (BootSector.Size - WordSize) - CodeSize db 0 + + ; Boot sector magic + dw 0xaa55 diff -r 61c76233911e -r 34a0a4406539 bootmine/stuff/mine.asm --- a/bootmine/stuff/mine.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/bootmine/stuff/mine.asm Sun Feb 04 18:02:38 2024 +0000 @@ -1,12 +1,20 @@ bits 16 -%define UNVEIL_ON_GAME_OVER -%define CGA_DISPLAY -%define DOS_QUIT -%define FAT_BOOT -%define RESTART_ON_ANY_KEY +%if 1 +%define UNVEIL_ON_GAME_OVER +16 +%define CGA_DISPLAY +6 +%define MDA_SUPPORT +74 +%define DOS_QUIT +12 +%define UNDO_FLAG +10 +%define EXPLOSION +4 +;%define LAZY_CHECK -4 cylinder board +;%define USE_RDTSC -15 need a 586+ +%else +%define USE_RDTSC -15 need a 586+ +%endif cpu 8086 +org 0x7c00 ;; Constants @@ -25,7 +33,11 @@ ;; register, then memory access instructions will be relative to the VGA text ;; buffer, allowing easier access. For example, trying to access the nth byte of ;; memory will *actually* access the nth byte of the text buffer. +%ifdef MDA_SUPPORT +%assign TextBuf.Seg 0xb000 +%else %assign TextBuf.Seg 0xb800 +%endif ;; Dimensions of text buffer %assign TextBuf.Width 40 @@ -35,21 +47,15 @@ ;; Macro to get the index of a text buffer cell from coordinates %define TextBuf.Index(y, x) ((y) * TextBuf.Width * 2 + (x) * 2) -;; Length of Dirs array defined below -%assign Dirs.Len 8 - ;; Keyboard scan codes ;; http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html -%assign Key.ScanCode.Space 0x39 -%assign Key.ScanCode.Up 0x48 -%assign Key.ScanCode.Down 0x50 -%assign Key.ScanCode.Left 0x4b -%assign Key.ScanCode.Right 0x4d -%assign Key.ScanCode.Enter 0x1c - -;; Keyboard ASCII codes -%assign Key.Ascii.RestartGame 'r' -%assign Key.Ascii.QuitGame 27 +%assign Key.ScanCode.Space 0x39 +%assign Key.ScanCode.Up 0x48 +%assign Key.ScanCode.Down 0x50 +%assign Key.ScanCode.Left 0x4b +%assign Key.ScanCode.Right 0x4d +%assign Key.ScanCode.Enter 0x1c +%assign Key.ScanCode.QuitGame 0x01 ;; This is a convenience macro for creating VGA characters. VGA characters are ;; 16 bit words, with the lower byte as the ASCII value and the upper byte @@ -58,32 +64,40 @@ ;; VGA colors to use for game items ;; https://wiki.osdev.org/Text_UI#Colours -%assign Color.Veiled 0x77 -%assign Color.Unveiled 0xf0 -%assign Color.Cursor 0x00 -%assign Color.Flag 0xcc -%assign Color.GameWinText 0x20 -%assign Color.GameOverText 0xc0 +%assign Color.Veiled 0x77 +%assign Color.Unveiled 0xf0 +%assign Color.Cursor 0x00 +%assign Color.Flag 0xcc +%assign Color.GameWinText 0x20 +%assign Color.GameOverText 0xc0 + +%ifdef MDA_SUPPORT +%assign Mda.InverseAttr 0x70 ; 0x[7F][08] Bit 3: High intensity. +%assign Mda.CursorAttr 0x09 ; 0x?[19] Bits 0-2: 1 => underline, other values => no underline. +%assign Mda.HiddenAttr 0x00 ; 0x[08][08] Bit 7: Blink. +%assign Mda.Veiled 0xdb ; 0xb1 +%assign Mda.Flag 0x02 +%assign Mda.Cursor 0xb1 ; 0x16 +%assign Mda.Screen.Offset 0x8000 +%assign Mda.Buffer 0x8000 +%define Mda.Attr(cga, mda) (((cga) << 8) | (mda)) +%endif ;; This value is used to calculate bomb frequency. The probability that any ;; given cell is a bomb is (1/2)^n, where n = "number of ones in the binary ;; representation of BombFreq". ;; -;; In other words, when BombFreq=0, every cell is a bomb, and appending a one +;; In other words, when BombFreq=-1, every cell is a bomb, and appending a one ;; halves the amount of bombs. -%assign BombFreq 0b111 - -%ifdef FAT_BOOT - jmp BootMine - nop - times 0x3B db 0 -%endif +%assign BombFreq 0b111 +%assign Ascii.Bomb '*' ;; BootMine is supported as both a DOS game and a boot sector game :) ;; Entry point: set up graphics and run game BootMine: cld + sti ;allow interrupts ; VGA text mode 0x00 ; 320x200 pixel resolution @@ -127,11 +141,6 @@ out dx, al %endif - ; Load VGA text buffer segment into segment registers - mov dx, TextBuf.Seg - mov es, dx - mov ds, dx - ; Disable VGA text mode cursor ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor mov ah, 0x01 @@ -140,25 +149,47 @@ ;; Run game (the game is restarted by jumping here) RunGame: + push cs + pop ss + mov sp, TextBuf.Seg -;; Set all cells of game map to veiled '0' cells -ZeroTextBuf: - xor di, di - mov cx, TextBuf.Size - mov ax, VgaChar(Color.Veiled, '0') - rep stosw + ; Load VGA text buffer segment into segment registers + mov es, sp + mov ds, sp %ifndef USE_RDTSC ; Initialyze the simple pseudo-random number generator ; seed = set_system_time() - %if 1 - cbw + mov ah, 0 int 0x1a - push dx +%endif + +;; Set all cells of game map to veiled '0' cells +ZeroTextBuf: +%ifdef MDA_SUPPORT + %ifdef UNVEIL_ON_GAME_OVER + call FillScreen %else - in al,(0x40) ; Read timer - push ax + mov ax, VgaChar(Color.Veiled, '0') + mov di, Mda.Screen.Offset + mov bx, TextBuf.Width - Mda.Screen.Offset + mov bp, TextBuf.Height +.1: + mov cx, TextBuf.Width +.2: + mov [cs:di + Mda.Buffer - Mda.Screen.Offset], ax + mov byte [bx + di], Mda.Veiled + stosw + loop .2 + add bx, TextBuf.Width * 2 + dec bp + jnz .1 %endif +%else + mov ax, VgaChar(Color.Veiled, '0') + mov cx, TextBuf.Size + xor di, di + rep stosw %endif ;; Populate text buffer with mines and digits @@ -172,150 +203,98 @@ ;; Note that the coordinates on the outside border are skipped to avoid bounds ;; checking logic. PopulateTextBuf: - ; Iterate over y coordinates - mov bx, TextBuf.Height - 2 + ; Iterate over y coordinates. ch = 0 form ZeroTextBuf + mov cl, TextBuf.Height - 2 .LoopY: - ; Iterate over x coordinates. ch = 0 form ZeroTextBuf -%ifndef USE_RDTSC - mov cx, TextBuf.Width - 2 -%else - mov cl, TextBuf.Width - 2 -%endif + mov di, TextBuf.Width - 2 .LoopX: - ; di = &TextBuf[y][x] - call GetTextBufIndex -.si_value: - ; The register dl holds a boolean that is 1 if the current cell is a bomb, 0 + ; The register al holds a boolean that is 1 if the current cell is a bomb, 0 ; otherwise. %ifndef USE_RDTSC ; It is calculated by bitwise and-ing the result of the simple pseudo-random number ; generator seed = ((seed + LARGE_PRIME1) * LARGE_PRIME2) % LARGE_PRIME3 ; - ; dl = ! (prng() & BombFreq) + ; al = ! (prng() & BombFreq) %assign Prime1 32749 %assign Prime2 65519 %assign Prime3 65521 - pop ax + xchg ax, dx add ax, Prime1 - mov bp, Prime2 - mul bp - inc bp - inc bp - div bp - xchg ax, dx - push ax + mov si, Prime2 + mul si + inc si + inc si + div si + test dl, cl %else ; It is calculated by bitwise and-ing the result of rdtsc. (rdtsc returns the ; amount of CPU cycles since boot, which works okay as a cheap random number ; generator, and it's apparently supported on all x86 CPUs since the Pentium line) ; - ; dl = ! (rdtsc() & BombFreq) + ; al = ! (rdtsc() & BombFreq) cpu 686 rdtsc cpu 8086 + test al, BombFreq %endif - and al, BombFreq - mov dx, '*' * 256 + 0 - ; Initialize loop counter for .LoopDir - mov bp, Dirs.Len + mov ax, VgaChar(Color.Veiled, Ascii.Bomb) + + ; If this cell isn't a bomb, then skip marking it as a bomb + jnz .Iterated - ; If this cell isn't a bomb, then skip marking it as a bomb - jnz .LoopDir + ; si = &TextBuf[y][x] + call GetTextBufIndex ; Mark the current cell as a bomb - mov byte [di], dh - inc dx - ; Iterate over adjacent cells (directions) -.LoopDir: - ; Load adjacent cell offset from Dirs array into ax. - mov al, byte [cs:bp + si + Dirs - .si_value - 1] - cbw - ; Set di = pointer to adjacent cell - add di, ax - - ; If adjacent cell is a bomb, skip digit incrementing - cmp byte [di], dh - je .LoopDirIsMine - ; The adjacent cell is a 0-7 digit and not a bomb. Add dl to the cell, which - ; is 1 if the original cell is a bomb. This gradually accumulates to the - ; amount of neighboring bombs and represents the number cells in the - ; minesweeper game. - add [di], dl -.LoopDirIsMine: - ; Restore di to original cell pointer - sub di, ax - - ; Decrement adjacent direction loop counter and continue if nonzero - dec bp - jnz .LoopDir + call AdjacentCells +.Iterated: ; Decrement x coordinate loop counter and continue if nonzero - loop .LoopX + dec di + jnz .LoopX ; Decrement y coordinate loop counter and continue if nonzero - dec bx - jnz .LoopY -%ifndef USE_RDTSC - pop ax -%endif + loop .LoopY + ; cl and di are zeroed from the PopulateTextBuf loops above ;; Done populating the text buffer - ; Set the initial cursor color for game loop. The dl register is now used to + ; Set the initial cursor color for game loop. The dh register is now used to ; store the saved cell color that the cursor is on, since the cursor ; overwrites the cell color with the cursor color. - mov dl, Color.Veiled + xchg ax, dx ;; Main loop to process key presses and update state GameLoop: - ; Get keystroke - ; ah = BIOS scan code - ; al = ASCII character - ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html - xor ax, ax - int 0x16 -%ifdef DOS_QUIT - cmp al, Key.Ascii.QuitGame - je Quit -%endif - - ; bx and cx are zeroed from the PopulateTextBuf loops above - ; bx = y coord - ; cx = x coord - - ; di = cell pointer - call GetTextBufIndex - ; Apply saved cell color - mov [di + 1], dl ;; Detect win (a win occurs when every veiled cell is a mine) DetectWin: - ; Use si register as cell pointer for win detection - xor si, si - ; Use bp as loop counter - mov bp, TextBuf.Size -.Loop: - ; if (char != '*' && (color == Color.Veiled || color == Color.Flag)) { + mov bx, TextBuf.Size + ; if (char != Ascii.Bomb && (color == Color.Veiled || color == Color.Flag)) { ; break; // Didn't win yet :( ; } - ; Load VGA char into al - lodsb - cmp al, '*' - ; Load VGA color into al - lodsb + ; Use si register as cell pointer for win detection +%ifdef MDA_SUPPORT + mov si, Mda.Buffer +.Loop: + cs lodsw +%else + xor si, si +.Loop: + lodsw +%endif + cmp al, Ascii.Bomb je .Continue - cmp al, Color.Veiled - je Break - cmp al, Color.Flag - je Break + cmp ah, Color.Unveiled + jb .Break .Continue: - dec bp - jnz .Loop + dec bx + jne .Loop ; If loop completes without breaking, then we win! :) ;; Show game win screen @@ -324,188 +303,247 @@ call GameEndHelper db 'GAME WIN' -;; Wait for restart key to be pressed, then restart game +.Break: WaitRestart: - xor ax, ax +;; if bx == 0: Wait for restart key to be pressed, then restart game + ; Get keystroke + ; ah = BIOS scan code + ; al = ASCII character + ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html + mov ah, 0 int 0x16 %ifdef DOS_QUIT - cmp al, Key.Ascii.QuitGame - jnz Quit.notQuit -Quit: - mov ax,0x0003 ; Restore text mode - int 0x10 - int 0x20 - int 0x19 -.notQuit: + dec ah + je Quit %endif -%ifndef RESTART_ON_ANY_KEY - cmp al, Key.Ascii.RestartGame - jne WaitRestart + dec bx + js RunGame + + ; cl = y coord + ; di = x coord + + ; si = cell pointer + call GetTextBufIndex + ; Apply saved cell color +%ifdef MDA_SUPPORT + call SetColor +%else + mov byte [si + 1], dh %endif - jmp RunGame -;; Array of adjacent cell offsets. A byte in this array can be added to a text -;; buffer cell pointer to get the pointer to an adjacent cell. This is used for -;; spawning digit cells. -Dirs: - db TextBuf.Index(-1, -1) - db TextBuf.Index(-1, 0) - db TextBuf.Index(-1, +1) - db TextBuf.Index( 0, +1) - db TextBuf.Index(+1, +1) - db TextBuf.Index(+1, 0) - db TextBuf.Index(+1, -1) - db TextBuf.Index( 0, -1) - -Break: ; Didn't win yet - mov al, ah + xchg al, ah ;; Process key press. This is an if-else chain that runs code depending on the ;; key pressed. CmpUp: ; Move cursor up - dec bx - cmp al, Key.ScanCode.Up + dec cx +%ifdef DOS_QUIT + sub al, Key.ScanCode.Up-1 +%else + sub al, Key.ScanCode.Up +%endif je WrapCursor - inc bx + inc cx CmpDown: ; Move cursor down - inc bx - cmp al, Key.ScanCode.Down + inc cx + sub al, Key.ScanCode.Down - Key.ScanCode.Up je WrapCursor - dec bx + dec cx CmpLeft: ; Move cursor left - dec cx - cmp al, Key.ScanCode.Left + dec di + sub al, Key.ScanCode.Left - Key.ScanCode.Down je WrapCursor - inc cx + inc di CmpRight: ; Move cursor right - inc cx - cmp al, Key.ScanCode.Right + inc di + sub al, Key.ScanCode.Right - Key.ScanCode.Left je WrapCursor - dec cx + dec di CmpEnter: - cmp al, Key.ScanCode.Enter + sub al, Key.ScanCode.Enter - Key.ScanCode.Right jne CmpSpace ; Place flag by coloring current cell - mov dl, Color.Flag - mov [di + 1], dl +%ifdef MDA_SUPPORT + %ifdef UNDO_FLAG + cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Unveiled + jae GameLoop + xor byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Flag ^ Color.Veiled + mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset] + %else + mov dh, Color.Flag + %endif + call SetColor +%else + %ifdef UNDO_FLAG + inc si + cmp byte [si], Color.Unveiled + jae GameLoop + xor byte [si], Color.Flag ^ Color.Veiled + mov dx, [si] + %else + mov dh, Color.Flag + mov [si + 1], dh + %endif +%endif ; jmp GameLoop CmpSpace: - cmp al, Key.ScanCode.Space + sub al, Key.ScanCode.Space - Key.ScanCode.Enter jne GameLoop ;; If the player pressed space, clear the current cell ClearCell: - ; Set ax = cell value - mov ax, [di] - call UnveilCell -;; If-else chain checking the cell value -.CmpEmpty: - cmp al, '0' - jne .CmpMine +%ifdef EXPLOSION + %ifdef MDA_SUPPORT + mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset] + %else + mov al, [si] + %endif + cmp al, Ascii.Bomb +%else + %ifdef MDA_SUPPORT + cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset], Ascii.Bomb + %else + cmp byte [si], Ascii.Bomb + %endif +%endif + je GameOver + ; If cell is empty, run flood fill algorithm call Flood -.jmpGameLoop: +%ifdef MDA_SUPPORT +GetCharAndColor: + mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset] +%endif +jmpGameLoop: jmp GameLoop -.CmpMine: - cmp al, '*' - ; No handling needed if cell is digit - jne .jmpGameLoop + +;; Helper code for GameWin and GameOver; print a string in the center of the +;; text buffer, then wait for game to be restarted. +GameEndHelper: + pop si +%ifdef MDA_SUPPORT + mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2) + Mda.Screen.Offset +%else + mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2) +%endif + xor bx, bx +.String: + cs lodsb + cmp al, 'Z' + ja WaitRestart +%ifdef MDA_SUPPORT + mov [di + TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2) - Mda.Screen.Offset], al +%endif + stosw + jmp .String + +%ifdef DOS_QUIT +Quit: + mov al,0x03 ; Restore text mode + int 0x10 + int 0x20 + int 0x19 +%endif + +GameOver: ; If cell is bomb, game over :( +%ifdef EXPLOSION + call UnveilCell +%endif ;; Show game over screen %ifdef UNVEIL_ON_GAME_OVER + %ifdef MDA_SUPPORT + call UnveilBomb + %else mov cx, TextBuf.Size xor si, si .Loop: ; Load VGA character into ax lodsw - cmp al, '*' + cmp al, Ascii.Bomb jne .Next and byte [si-1], Color.Unveiled .Next: loop .Loop + %endif %endif -;;GameOver: mov ah, Color.GameOverText call GameEndHelper GameOverStr: db 'GAME OVER' -%assign GameOverStr.Len $ - GameOverStr - -;; Helper code for GameWin and GameOver; print a string in the center of the -;; text buffer, then wait for game to be restarted. -GameEndHelper: - pop si - mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - GameOverStr.Len / 2) -.Loop: - cs lodsb - cmp al, 'Z' - ja WaitRestart - stosw - jmp .Loop +GameOverStr.End: ;; Set y and x coordinates of cursor to zero if they are out of bounds +ResetCursor: + xchg ax, cx + xor di, di + WrapCursor: -.Y: - ; Wrap y cursor - cmp bx, TextBuf.Height - jb .X - xor bx, bx - -.X: - ; Wrap x cursor - cmp cx, TextBuf.Width - jb SetCursorPos - xor cx, cx - -;; Redraw cursor in new position -SetCursorPos: ; Get text buffer index (it changed) call GetTextBufIndex + jae ResetCursor + ; Draw cursor by changing cell to the cursor color, but save current color for ; restoring in the next iteration of the game loop. - mov dl, Color.Cursor - xchg dl, [di + 1] +%ifdef MDA_SUPPORT + mov dx, Mda.Attr(Color.Cursor, Mda.Cursor) + call SetScreenColor + jmp GetCharAndColor +%else + mov dh, Color.Cursor + xchg byte [si + 1], dh + jmp jmpGameLoop +%endif - jmp ClearCell.jmpGameLoop - -;; Compute the text buffer index from y and x coordinates -;; -;; di = &TextBuf[bx = y][cx = x] -;; -;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime -;; -;; Parameters: -;; * bx - y coordinate -;; * cx - x coordinate -;; Returns: -;; * di - text buffer index -;; * si - caller return address -GetTextBufIndex: - xchg ax, di - mov al, TextBuf.Width * 2 - imul bl - xchg ax, di - add di, cx - add di, cx - ; load caller return address in si - pop si - push si +%ifdef UNVEIL_ON_GAME_OVER + %ifdef MDA_SUPPORT +FillScreen: + mov cx, Mda.Veiled +UnveilBomb: + mov si, Mda.Buffer + mov bp, TextBuf.Height + mov bx, 0 - TextBuf.Width + Mda.Buffer +.LoopLine: + mov di, TextBuf.Width + add bx, 2*TextBuf.Width +.Loop: + mov ax, VgaChar(Color.Veiled, '0') + or cl, cl + js .Fill + ; Load VGA character into ax + mov ax,[cs:si] + cmp al, Ascii.Bomb + jne .Next + and ah, Color.Unveiled + mov cl, al +.Fill: + mov [si], ax + mov [cs:si], ax + mov [bx + si + Mda.Screen.Offset - Mda.Buffer], cl +.Next: + lodsw + dec di + jnz .Loop + dec bp + jnz .LoopLine ret + %endif +%endif ;; Unveil a cell so it is visible on the screen ;; ;; Parameters: -;; * di - cell pointer in text buffer +;; * si - cell pointer in text buffer ;; * al - cell ASCII value ;; Returns: -;; * dl - written VGA color code +;; * dh - written VGA color code UnveilCell: ; TLDR: Use xor magic to make the cells colored. ; @@ -529,113 +567,159 @@ ; Case 2: the cell is a bomb ; ; We don't really care about this case as long as the bomb is visible against - ; the background. The bomb turns out to be green, oh well. - ; - ; Case 3: the cell is an empty space - ; - ; This ends up coloring the cell bright yellow, which isn't a big problem. + ; the background. The bomb turns out to be bright yellow, oh well. +%ifdef MDA_SUPPORT mov dl, al - xor dl, '0' ^ Color.Unveiled - mov [di + 1], dl + mov [si], al +%endif + xor al, '0' ^ Color.Unveiled + mov dh, al +%ifdef MDA_SUPPORT +SetColor: + mov [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], dh + cmp dh, Color.Flag + ja .NotVeiled + mov dl, Mda.Veiled + jne .NotVeiled + mov dl, Mda.Flag +.NotVeiled: +SetScreenColor: + mov [bx + si], dl +%endif + mov [si + 1], dh ret ;; Flood fill empty cells ;; ;; Parameters: -;; * bx - cell y coordinate -;; * cx - cell x coordinate -;; Clobbered registers: -;; * ax - cell value -;; * di - cell pointer in text buffer -Flood: - ; Init: get cell pointer and value - call GetTextBufIndex - mov ax, [di] - - ; Base case: bounds check y - cmp bx, TextBuf.Height - jae .Ret - - ; Base case: bounds check x - cmp cx, TextBuf.Width - jae .Ret - +;; * cl - cell y coordinate +;; * di - cell x coordinate +;; * si - cell pointer in text buffer +FloodStep: +%ifdef MDA_SUPPORT + mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset] +%else + mov al, [si] +%endif cmp al, '0' + ; Base case: nonempty cell unveiled and stop recursion + ja UnveilCell + + mov ax, VgaChar(Color.Unveiled, ' ') + ; Base case: we visited this cell already or bomb - jb .Ret - - ; Base case: nonempty cell unveiled and stop recursion - jne UnveilCell - - ; Body: unveil empty cell - call UnveilCell + jb Ret ; Body: mark cell as visited and empty - mov byte [di], ' ' +%ifdef MDA_SUPPORT + mov [si], ax + mov [bx + si], al +AdjacentCells: + mov [bp + si + Mda.Buffer - Mda.Screen.Offset], ax +%else +AdjacentCells: + mov [si], ax +%endif ; Recursive case: flood adjacent cells - ; Flood down - inc bx - call Flood - dec bx + ; Flood left-row + dec di + call UpAndFloodRow - ; Flood left + ; Flood center-row + call FloodNextRow + + ; Flood right-row + call FloodNextRow + + ; re-center dec cx - call Flood - inc cx + dec di - ; Flood right - inc cx - call Flood - dec cx - - ; Flood up-left - dec cx - call .Flood_up - inc cx - - ; Flood up-right - inc cx - call .Flood_up - dec cx - - ; Flood down-left - inc bx - dec cx - call Flood - inc cx - dec bx - - ; Flood down-right - inc bx - inc cx - call Flood - dec cx - dec bx - -.Flood_up: - ; Flood up - dec bx - call Flood - inc bx - -.Ret: +;; Compute the text buffer index from y and x coordinates +;; +;; si = &TextBuf[cl = y][di = x] +;; +;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime +;; +;; Parameters: +;; * cl - y coordinate +;; * di - x coordinate +;; Returns: +;; * si - text buffer index +;; * carry - x and y are valid coordinates +GetTextBufIndex: + xchg ax, si + mov al, TextBuf.Width * 2 + imul cl + xchg ax, si +%ifdef MDA_SUPPORT + lea bx, [si + TextBuf.Width - Mda.Screen.Offset] + lea si, [bx + di - TextBuf.Width] + add si, di + %ifdef LAZY_CHECK + cmp si, TextBuf.Size * 2 + Mda.Screen.Offset + %endif +%else + add si, di + add si, di + %ifdef LAZY_CHECK + cmp si, TextBuf.Size * 2 + %endif +%endif +%ifndef LAZY_CHECK + ; Base case: bounds check y + cmp cl, TextBuf.Height + jae Ret + ; Base case: bounds check x + cmp di, TextBuf.Width +%endif +Ret: ret +FloodNextRow: + inc di + dec cx +UpAndFloodRow: + dec cx +FloodRow: + call Flood + call FloodDown +FloodDown: + inc cx +Flood: + ; si = &TextBuf[y][x] + call GetTextBufIndex + jae Ret + + cmp al, Ascii.Bomb + jne FloodStep +Counter: + ; If adjacent cell is a bomb, skip digit incrementing +%ifdef MDA_SUPPORT + cmp [bp + si + Mda.Buffer - Mda.Screen.Offset], al +%else + cmp [si], al +%endif + je .IsMine + ; The adjacent cell is a 0-7 digit and not a bomb. Add 1 to the cell. + ; This gradually accumulates to the + ; amount of neighboring bombs and represents the number cells in the + ; minesweeper game. +%ifdef MDA_SUPPORT + inc byte [bp + si + Mda.Buffer - Mda.Screen.Offset] +%else + inc byte [si] +%endif +.IsMine: + ret ;; Print program size at build time %assign CodeSize $ - $$ -%warning Code is CodeSize bytes - -%ifdef MBR_BOOT -%assign PartitionTable 0x1BE -%if CodeSize > PartitionTable -%assign OverFlow CodeSize - PartitionTable -%error Code is OverFlow bytes too large -%endif -%endif +%assign Size $ - BootMine +%warning Code is Size bytes CodeEnd: ; Pad to size of boot sector, minus the size of a word for the boot sector diff -r 61c76233911e -r 34a0a4406539 bootris/stuff/tetranglix.asm --- a/bootris/stuff/tetranglix.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/bootris/stuff/tetranglix.asm Sun Feb 04 18:02:38 2024 +0000 @@ -1,6 +1,8 @@ ; Modified by nanochess for compatibility with VirtualBox. ; to require only 8086 and also now it's in color. +%define MDA_SUPPORT + BITS 16 BSS EQU 0x7E00 @@ -57,11 +59,20 @@ mov ax, 0x103 ; Some BIOS crash without the 03. int 0x10 - mov es, sp - ; White spaces on black background. xor di, di +%ifdef MDA_SUPPORT + mov ax, sp +mda: + mov es, ax + mov ah, 0xb0 + inc word [es:di] + jz mda + mov ah, 0x0F +%else + mov es, sp mov ax, 0x0F00 +%endif mov ch, 8 ; At least 80x25x2. rep stosw call pop_check @@ -72,7 +83,8 @@ ; Carry set if colliding. tetramino_collision_check: - lea bx, [bp + check_collision - tetramino_collision_check] +%define BP(x) [bp + x - tetramino_collision_check] + lea bx, BP(check_collision) ; Processes the current tetramino, calling bx per "tetramino pixel". ; bx -> where to call to; al contains tetramino pixel, di the address into stack. @@ -243,7 +255,7 @@ mov ah,al ; Load tetramino bitmap in dl. - mov dl, [bp + di + (tetraminos - tetramino_collision_check) - 1] + mov dl, BP(di + tetraminos - 1) mov cl, 4 shl dx, cl @@ -282,32 +294,31 @@ xor ah, ah int 0x16 - .exit: + mov al, ah dec ah je exit_dos - mov al, ah ; Go left. .left: - cmp al, LEFT_SCANCODE-1 + cmp al, LEFT_SCANCODE je .call_bp ; Go right. .right: - cmp al, RIGHT_SCANCODE-1 + cmp al, RIGHT_SCANCODE jne .rotate add byte [si],2 .call_bp: dec byte [si] - xor al, (LEFT_SCANCODE-1) ^ (RIGHT_SCANCODE-1) + xor al, LEFT_SCANCODE ^ RIGHT_SCANCODE call bp jc .left ; Rotate it. .rotate: - cmp al, UP_SCANCODE-1 + cmp al, UP_SCANCODE jne .vertical_increment inc cx @@ -376,12 +387,7 @@ ; Joins the current tetramino to the stack, and any complete lines together. ; si -> OFFSET. - push es - - push ds - pop es - - lea bx, [bp + merge - tetramino_collision_check] + lea bx, BP(merge) call tetramino_process mov si, STACK + 15 @@ -399,8 +405,19 @@ jz .next_line lea cx, [si - (STACK - 1)] +%if 1 lea di, [si + 16] + push es + push ds + pop es rep movsb + pop es +%else + .loop_movsb: + lodsb + mov [si + 16 - 1], al + loop .loop_movsb +%endif mov cl, 64 call upd_score @@ -411,7 +428,6 @@ jb .loop_lines cld - pop es jmp .borders @@ -482,14 +498,13 @@ add di, (80 - 8) * 2 .load_tetramino: - lodsb - or al,al - mov ah,al - mov al,0xdb + mov ax, 0xdb + or ah, [si] ; Output two characters for "squarish" output. jne .load_tetramino2 mov ax, [es:di] .load_tetramino2: + inc si stosw stosw @@ -498,23 +513,19 @@ jmp .event_loop upd_score: - push ds - mov bx, SCREEN_SEGMENT - mov ds, bx - mov bx, SCORE_DIGITS * 2 + mov di, SCORE_DIGITS * 2 .chk_score: - dec bx - dec bx + dec di + dec di js pop_check.game_over mov al, '0' - xchg [bx], al + xchg [es:di], al or al, 0x30 cmp al, '9' je .chk_score inc ax - mov [bx], al - pop ds + stosb loop upd_score ret diff -r 61c76233911e -r 34a0a4406539 bootsokoban/stuff/sokoban.asm --- a/bootsokoban/stuff/sokoban.asm Wed Oct 04 13:13:17 2023 +0000 +++ b/bootsokoban/stuff/sokoban.asm Sun Feb 04 18:02:38 2024 +0000 @@ -2,17 +2,340 @@ cpu 8086 %define CURRENT_LEVEL 0x7E00 -%define CURRENT_LEVEL_4 0x7E04 +%define CURRENT_LEVEL_2 0x7E02 %define SCREEN_DS 0xb800 +%define HISTORY SCREEN_DS -%define MOVE_COUNT +%define MDA_SUPPORT +7 bytes + %define MDA_CGA40 +25 bytes +%define MOVE_COUNT +20 bytes +%define UNDO +35 bytes + %define FAST_UNDO +5 bytes + +%define COL80 80 +%define COL40 40 +%ifdef MDA_SUPPORT + %define COLS COL80 +%else + %define COLS COL40 +%endif +%define LINES 25 boot: +tail: + call get_data + +; data section: + +; 0000 0000 EMPTY +; 0000 0001 SPOT +; 0000 0010 BRICK +%define BRICK 2 +; 0000 0011 BRICK ON SPOT +; 0000 0100 WALL +; 0000 0101 OUTSIDE +; 0000 0110 PLAYER +%define PLAYER 6 +; 0000 0111 PLAYER ON SPOT +display_chars: db 0, 0x07 ; blank + db 0xF9, 0x07 ; spot + db 0xFE, 0x0A ; brick +%ifdef MDA_SUPPORT + db 4, 0x0C ; brick on spot +%else + db 0xFE, 0x0C ; brick on spot +%endif + db 0xB0, 0x71 ; wall + db 0, 0x07 ; out blank + db 1, 0x0F ; player + db 1, 0x0F ; player on spot + +str_you_win: db 'YOU WIN! ',1,1,1 + +test_level: + +%define p(a,b,c) ((c&7)+((b&7)*6)+((a&7)*6*6)) +%define LEVEL 2 + +;# convert a sokojs level +;echo ";$1" +;sed '/^" /!d;s|^" ||;s|".*||;y|_.$*# @+|012345@Z|' $1 | awk 'BEGIN { q=""; l=0 } +;function out() { +; line[l] = "" +; while (length(q) > 2) { +; line[l] = line[l] sprintf(" p(%c,%c,%c),", substr(q,1,1), substr(q,2,1), substr(q,3,1)) +; q = substr(q,4) +; } +; l++ +;} +;{ if (index($0,"@") > 0) { px=l; py=index($0,"@")-1 } +; if (index($0,"Z") > 0) { px=l; py=index($0,"Z")-1 } +; r=length($0); q = q $0; out() +;} +;END { q = q "55"; if (length(q) > 2) out() +; print "%define WIDTH " r; print "%define HEIGHT " l +; print "%define PLAYERXY WIDTH*" px "+" py " ; " r*px+py +; if ((r*px+py) % 256 >= 90 ) print "%define EOS PLAYERXY" +; print " dw PLAYERXY" +; for (i = 0; i < l; i++) print line[i] +;}' | sed 's|,$||;s|^ p| db p|;s|@|0|;s|Z|1|' + +%if LEVEL == 1 +;sokojs sokojs/level57.htm +%define WIDTH 14 +%define HEIGHT 10 +%define PLAYERXY WIDTH*4+7 +%define EOS PLAYERXY + dw PLAYERXY + db p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(5,5, 4) + db p(1,1, 0), p(0,4,0), p(0,0,0), p(0,4,4), p(4, 4,1) + db p(1, 0,0), p(4,0,2), p(0,0,2), p(0,0,4) + db p(4,1,1), p(0,0,4), p(2,4,4), p(4,4,0), p(0,4, 4) + db p(1,1, 0), p(0,0,0), p(8,0,4), p(4,0,0), p(4, 4,1) + db p(1, 0,0), p(4,0,4), p(0,0,2), p(0,4,4) + db p(4,4,4), p(4,4,4), p(0,4,4), p(2,0,2), p(0,4, 5) + db p(5,4, 0), p(2,0,0), p(2,0,2), p(0,2,0), p(4, 5,5) + db p(4, 0,0), p(0,0,4), p(0,0,0), p(0,0,4) + db p(5,5,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4, 5) +%elif LEVEL == 2 +;sokojs sokojs/level83.htm modified +%define WIDTH 15 +%define HEIGHT 14 +%define PLAYERXY WIDTH*7+13 +%define EOS PLAYERXY + dw PLAYERXY + db p(4,4,4), p(4,5,5), p(5,5,5), p(4,4,4), p(4,5,5) + db p(4,0,0), p(4,4,4), p(4,4,4), p(4,1,1), p(4,4,4) + db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(1,1,4) + db p(4,0,2), p(0,0,0), p(2,2,0), p(4,3,3), p(3,1,4) + db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(3,1,4) + db p(4,0,0), p(2,0,2), p(0,2,0), p(4,3,1), p(3,1,4) + db p(4,4,0), p(2,0,2), p(0,2,0), p(1,3,1), p(3,1,4) + db p(4,0,0), p(2,0,2), p(0,2,0), p(1,3,1), p(3,1,4) + db p(4,0,0), p(2,0,2), p(0,2,0), p(4,3,1), p(3,1,4) + db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(3,1,4) + db p(4,0,2), p(0,0,0), p(2,2,0), p(4,3,3), p(3,1,4) + db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(1,1,4) + db p(4,0,0), p(4,4,4), p(4,4,4), p(4,1,1), p(4,4,4) + db p(4,4,4), p(4,5,5), p(5,5,5), p(4,4,4), p(4,5,5) +%elif LEVEL == 3 +;sokojs ALDMGR/level0.htm +%define WIDTH 19 +%define HEIGHT 18 +%define PLAYERXY WIDTH+8 +%define EOS PLAYERXY + dw PLAYERXY + db p(5,5,5), p(5,5,5), p(5,4,4), p(4,5,5), p(5,5,5), p(5,5,5), p(5, 5,5) + db p(4, 4,4), p(4,4,0), p(8,0,4), p(4,4,4), p(4,4,4), p(5,5, 5) + db p(5,4, 0), p(0,0,4), p(0,2,0), p(4,0,0), p(0,0,0), p(4,4,5) + db p(5,4,4), p(0,2,0), p(4,4,0), p(0,4,2), p(0,0,0), p(0,0,4), p(4, 5,4) + db p(0, 2,0), p(2,0,4), p(0,0,4), p(0,2,4), p(4,0,2), p(0,4, 4) + db p(4,0, 3), p(1,1,0), p(4,4,0), p(4,2,0), p(4,4,2), p(0,0,4) + db p(4,0,2), p(1,1,3), p(0,0,4), p(0,4,0), p(2,0,0), p(0,0,4), p(4, 4,0) + db p(3, 2,4), p(2,3,0), p(4,2,0), p(0,0,2), p(0,0,4), p(4,5, 4) + db p(1,1, 0), p(4,0,1), p(1,4,0), p(0,4,4), p(4,2,4), p(4,5,5) + db p(4,0,4), p(4,4,4), p(4,0,4), p(0,2,0), p(0,2,0), p(0,4,4), p(5, 4,0) + db p(2, 0,4), p(0,2,0), p(0,2,0), p(2,0,2), p(0,2,0), p(4,4, 4) + db p(0,1, 1), p(4,1,1), p(0,2,0), p(4,0,0), p(0,2,0), p(2,0,4) + db p(4,0,1), p(1,2,1), p(1,0,4), p(0,4,0), p(2,4,4), p(0,0,0), p(4, 4,0) + db p(1, 1,1), p(1,1,2), p(4,0,4), p(0,0,0), p(0,0,2), p(0,4, 4) + db p(0,1, 1), p(1,1,1), p(0,4,0), p(4,2,2), p(2,0,2), p(0,4,4) + db p(4,0,1), p(1,1,1), p(1,2,4), p(0,4,0), p(0,0,0), p(0,4,4), p(5, 4,0) + db p(1, 1,1), p(1,1,0), p(4,0,4), p(4,4,4), p(4,4,4), p(5,5, 5) + db p(4,4, 4), p(4,4,4), p(4,5,4), p(5,5,5), p(5,5,5), p(5,5,5) +%else +;sokojs David_W_Skinner_Arranged/level174.htm +%define WIDTH 30 +%define HEIGHT 20 +%define PLAYERXY WIDTH*7+12 ; 222 +%define EOS PLAYERXY + dw PLAYERXY + db p(5,5,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,5,5) + db p(5,5,4), p(0,0,0), p(0,4,0), p(0,0,0), p(4,0,0), p(0,0,4), p(0,0,0), p(0,4,0), p(0,0,0), p(4,5,5) + db p(4,4,4), p(1,2,2), p(1,4,1), p(2,2,1), p(4,1,2), p(2,1,4), p(1,2,2), p(1,4,1), p(2,2,1), p(4,4,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,4,4), p(1,2,2), p(1,0,1), p(2,2,1), p(0,1,2), p(2,1,0), p(1,2,2), p(1,0,1), p(2,2,1), p(4,4,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,4,4), p(1,2,2), p(1,0,1), p(2,2,1), p(0,1,2), p(2,1,0), p(1,2,2), p(1,0,1), p(2,2,1), p(4,4,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4), p(4,0,2), p(0,4,4), p(0,2,0), p(4,4,0), p(2,0,4) + db p(4,0,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,3,0), p(0,3,1), p(3,0,0), p(3,1,3), p(0,0,3), p(1,0,4) + db p(4,4,4), p(1,2,2), p(1,4,1), p(2,2,1), p(4,1,2), p(2,1,4), p(1,2,2), p(1,4,1), p(2,2,1), p(4,4,4) + db p(5,5,4), p(0,0,0), p(0,4,0), p(0,0,0), p(4,0,0), p(0,0,4), p(0,0,0), p(0,4,0), p(0,0,0), p(4,5,5) + db p(5,5,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,4,4), p(4,5,5) +%endif + +get_data: + pop bp +%define BP(x) [bp+x-display_chars] + ; set up stack + push cs + pop ss + xor sp, sp + +%ifdef MDA_SUPPORT + %ifdef MDA_CGA40 + ; screen memory in text mode + mov cx, SCREEN_DS +.init_es: +%define patch_ofs 0x170 +%define BPDI(x) [bp+di+x-display_chars-patch_ofs] + mov di, patch_ofs + xor word BPDI(patch_win_pos), ((COL40 * 12 + COL40/2 - 6) * 2) ^ ((COL80 * 12 + COL80/2 - 6) * 2) + xor word BPDI(patch_middle), ((25 * COL40) - (WIDTH & 0xFE) - (COL40 * (HEIGHT & 0xFE))) ^ ((25 * COL80) - (WIDTH & 0xFE) - (COL80 * (HEIGHT & 0xFE))) + xor byte BPDI(patch_next_row),((COL40 - WIDTH) * 2) ^ ((COL80 - WIDTH) * 2) + xor word BPDI(patch_count_pos),(COL40 + 2 + (LINES-1)*COL40*2) ^ (COL80 + 2 + (LINES-1)*COL80*2) + mov es, cx + mov ch, 0xb0 + inc byte [es:di] + je .init_es + %endif +%endif +restart: + cld sti ; Allow interrupts + + push cs + pop ds + +%ifdef UNDO + mov word [HISTORY], sp +%endif + +back: + call init_data +%ifdef UNDO + mov word BP(tail), es ; HISTORY = es +%endif + +mainloop: + ; read key + xor ax, ax + cwd +%ifdef UNDO + mov si, BP(tail) + or ah, [si] + jnz .arrows +%endif +.wait_for_esc: + int 0x16 + + dec ah ; esc + je quit + +.chk_restart: + or dx, dx + jne restart + +.arrows: + xchg al, ah + + inc dx + cmp al, 0x4d-1 ; right arrow + je .try_move_right + cmp al, 0x4b-1 ; left arrow + je .try_move_left + + mov dl, WIDTH ; (width of current level) to the right = 1 down + cmp al, 0x50-1 ; down arrow + je .try_move_down + cmp al, 0x48-1 ; up arrow +%ifdef UNDO + je .try_move_up + + sub al, 0xE-1 ; backspace + jne .redraw + mov [si-1], al + jmp back +%else + jne .redraw +%endif + +.try_move_left: +.try_move_up: + neg dx +.try_move_right: +.try_move_down: + +%ifdef UNDO + xchg [si], al + inc si + mov BP(tail), si + or al, al + jnz .not_end + mov [si], ax +.not_end: +%endif + + call try_move + +%ifdef UNDO + %ifdef FAST_UNDO + lodsw + or ah, ah + jnz mainloop + %endif +%endif + +.redraw: + call draw_current_level + + dec bx + jne mainloop ; found a spotless brick + +win: + ; print a nice win message to the middle of the screen + lea si, BP(str_you_win) + + ; destination position on screen + mov di, (COLS * 12 + COLS/2 - 6) * 2 +patch_win_pos equ $ - 2 + +.loop: + lodsb + +%ifdef EOS + %if EOS & 0x80 == 0 + cbw + %else + mov ah, 0 + %endif + cmp al, EOS & 255 + je mainloop.wait_for_esc +%else + cbw + cmp al, 'Z' + ja mainloop.wait_for_esc +%endif + + mov ah, 0x0F + stosw + jmp .loop + + +;; functions: + +quit: + mov ax, 0x0003 ; text mode 80x25 16 colours + int 0x10 + int 0x20 + int 0x19 + +init_data: +%ifdef MDA_SUPPORT + %ifdef MDA_CGA40 + push es + %endif +%endif ; clear screen (re-set text mode) mov ax, 0x0001 ; text mode 40x25 16 colours -%define COLS 40 -%define LINES 25 int 0x10 ; disable cursor @@ -20,405 +343,162 @@ mov ch, 0x3f int 0x10 - ; set up stack - push cs - pop ss - xor sp, sp + ; set current level to test level by copying + lea si, BP(test_level) push cs - pop ds - push cs pop es + mov di, CURRENT_LEVEL ; next address to copy to + + ; copy player position ("uncompressed") + movsw + + mov cx, (( WIDTH * HEIGHT ) +2 ) /3 +uncompress: + ; load one "compressed" byte and store 3 "uncompressed" bytes + lodsb + ; aam n: ah = al / n al = al % n + aam 36 + mov [di], ah ; p/6/6 + inc di + aam 6 + xchg al, ah + stosw ; p/6%6 p%6 + + loop uncompress + or byte [di - ((( WIDTH * HEIGHT ) +2 ) /3)*3 + PLAYERXY], PLAYER + +%ifdef MDA_SUPPORT + %ifdef MDA_CGA40 + pop es + %else + ; screen memory in text mode + mov ch, SCREEN_DS/256 +.init_es: + mov es, cx + mov ch, 0xb0 + mov al, 0xff + scasb + je .init_es + %endif +%else + ; screen memory in text mode + mov ch, SCREEN_DS/256 + mov es, cx + %if 1 + mov al, 0xff + scasb + je quit + %endif +%endif - call get_data +draw_current_level: + + ; print in the middle and not in the corner + mov di, (25 * COLS) - (WIDTH & 0xFE) - (COLS * (HEIGHT & 0xFE)) +patch_middle equ $ - 2 + + mov si, CURRENT_LEVEL_2 ; source byte + mov bx, HEIGHT * 256 + 1 +.looph: + mov cx, WIDTH +.loop: + lodsb + cbw + cmp al, BRICK ; spotless brick + jne .not_brick +%ifdef HUGE_SPOTLESS_COUNT + mov bl, al +%else + inc bx ; 254 max +%endif +.not_brick: + xchg ax, si + add si, si + mov si, BP(si+display_chars) + xchg ax, si -; data section: + stosw -; 0000 0000 EMPTY -; 0000 0001 SPOT -; 0000 0010 BRICK -; 0000 0011 BRICK ON SPOT -; 0000 0100 WALL -; 0000 1000 PLAYER -; 0000 1001 PLAYER ON SPOT -test_level: + ; subtract 1 from X axis counter + loop .loop -%define p(a,b,c) ((c&7)+((b&7)*6)+((a&7)*6*6)) -%if 1 - db 15, 14 ;width, height - dw 15*7+13 ;playerxy -%define PLAYER 9 - db p(4,4,4), p(4,5,5), p(5,5,5), p(4,4,4), p(4,5,5) - db p(4,0,0), p(4,4,4), p(4,4,4), p(4,1,1), p(4,4,4) - db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(1,1,4) - db p(4,0,2), p(0,0,0), p(2,2,0), p(4,3,3), p(3,1,4) - db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(3,1,4) - db p(4,0,0), p(2,0,2), p(0,2,0), p(4,3,1), p(3,1,4) - db p(4,4,0), p(2,0,2), p(0,2,0), p(1,3,1), p(3,1,4) - db p(4,0,0), p(2,0,2), p(0,2,0), p(1,3,1), p(3,PLAYER,4) - db p(4,0,0), p(2,0,2), p(0,2,0), p(4,3,1), p(3,1,4) - db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(3,1,4) - db p(4,0,2), p(0,0,0), p(2,2,0), p(4,3,3), p(3,1,4) - db p(4,0,2), p(0,2,0), p(2,0,0), p(4,1,1), p(1,1,4) - db p(4,0,0), p(4,4,4), p(4,4,4), p(4,1,1), p(4,4,4) - db p(4,4,4), p(4,5,5), p(5,5,5), p(4,4,4), p(4,5,5) -%else - db 21, 18 ;width, height - dw 21+8 ;playerxy -%define PLAYER 8 - db p(5,5,5), p(5,5,5), p(5,4,4), p(4,5,5), p(5,5,5), p(5,5,5), p(5,5,5) - db p(5,5,4), p(4,4,4), p(4,0,8), p(0,4,4), p(4,4,4), p(4,4,5), p(5,5,5) - db p(5,5,4), p(0,0,0), p(4,0,2), p(0,4,0), p(0,0,0), p(0,4,4), p(5,5,5) - db p(5,4,4), p(0,2,0), p(4,4,0), p(0,4,2), p(0,0,0), p(0,0,4), p(4,5,5) - db p(5,4,0), p(2,0,2), p(0,4,0), p(0,4,0), p(2,4,4), p(0,2,0), p(4,5,5) - db p(4,4,0), p(3,1,1), p(0,4,4), p(0,4,2), p(0,4,4), p(2,0,0), p(4,5,5) - db p(4,0,2), p(1,1,3), p(0,0,4), p(0,4,0), p(2,0,0), p(0,0,4), p(4,5,5) - db p(4,0,3), p(2,4,2), p(3,0,4), p(2,0,0), p(0,2,0), p(0,4,4), p(5,5,5) - db p(4,1,1), p(0,4,0), p(1,1,4), p(0,0,4), p(4,4,2), p(4,4,5), p(5,5,5) - db p(4,0,4), p(4,4,4), p(4,0,4), p(0,2,0), p(0,2,0), p(0,4,4), p(5,5,5) - db p(4,0,2), p(0,4,0), p(2,0,0), p(2,0,2), p(0,2,0), p(2,0,4), p(4,5,5) - db p(4,0,1), p(1,4,1), p(1,0,2), p(0,4,0), p(0,0,2), p(0,2,0), p(4,5,5) - db p(4,0,1), p(1,2,1), p(1,0,4), p(0,4,0), p(2,4,4), p(0,0,0), p(4,5,5) - db p(4,0,1), p(1,1,1), p(1,2,4), p(0,4,0), p(0,0,0), p(0,2,0), p(4,5,5) - db p(4,0,1), p(1,1,1), p(1,0,4), p(0,4,2), p(2,2,0), p(2,0,4), p(4,5,5) - db p(4,0,1), p(1,1,1), p(1,2,4), p(0,4,0), p(0,0,0), p(0,4,4), p(5,5,5) - db p(4,0,1), p(1,1,1), p(1,0,4), p(0,4,4), p(4,4,4), p(4,4,5), p(5,5,5) - db p(5,4,4), p(4,4,4), p(4,4,5), p(4,5,5), p(5,5,5), p(5,5,5), p(5,5,5) + ; jump to next row down +patch_next_row equ $ + 2 + add di, (COLS - WIDTH) * 2 + + dec bh ; subtract 1 from Y axis counter + jnz .looph + ret + +try_move: + ; try to move the player + ; dx = offset of how much to move by + + mov di,CURRENT_LEVEL_2 + + ; calculate requested destination position + mov bx, dx + add bx, [di - 2] + + ; get value at destination position + cmp byte [bx + di], 4 + je .cant_push ; it's a wall + test byte [bx + di], 0x02 + jz .dont_push ; it's not a brick (on spot, or not), so don't try pushing + + ; try pushing brick + mov ax, bx ; store player's destination position (brick's current position) + add bx, dx ; bx = next brick position + + ; get value at destination position + test byte [bx + di], 0x0E ; test if the destination is occupied at all by ANDing with 0000 1110 + jnz .cant_push + + ; all checks passed! push the brick + mov dl, BRICK + call .move_object +%ifdef OPEN_LEVEL + jc .cant_push %endif - -display_chars: db 0, 0x07 ; blank - db 249, 0x07 ; spot - db 0xFE, 0x0A ; brick - db 0xFE, 0x0C ; brick on spot - db 176, 0x71 ; wall - db 0, 0x07 ; out blank - db "6", 0x07 ; (no 6) - db "7", 0x07 ; (no 7) - db 1, 0x0F ; player - db 1, 0x0F ; player on spot - -str_you_win: db 'YOU WIN! ',1,1,1,0 - -get_data: - pop bp - - ; set current level to test level by copying - mov si, bp - - ; get width and height and multiply by each other - lodsw - - mov di, CURRENT_LEVEL ; next address to copy to - - ; copy map size and player position ("uncompressed") - stosw - mul ah - - ; set multiplied width and height + 4 as counter - mov cx, ax - ;add cx, 4 - - lodsw - stosw - - xchg ax,bx - add bx,di ;save player location - mov dx,36*256+6 - -.copy_level_loop: - ; load one "compressed" byte and store 3 "uncompressed" bytes - lodsb - mov ah,0 - div dh - stosb ; p/6/6 - mov al,ah - cbw - div dl - stosw ; p/6%6 p%6 - - loop .copy_level_loop - mov byte [bx],PLAYER - - call draw_current_level - -.mainloop: - ; read key - xor ax, ax - int 0x16 - - mov al, byte [CURRENT_LEVEL] ; (width of current level) to the right = 1 down - dec ah ; esc - jne .not_boot -.exit: - mov al, 0x03 ; text mode 80x25 16 colours - int 0x10 - int 0x20 - int 0x19 - -.not_boot: - cmp ah, 0x50-1 ; down arrow - je .try_move_down - - cmp ah, 0x48-1 ; up arrow - je .try_move_up - - mov al, 1 - cmp ah, 0x4d-1 ; right arrow - je .try_move_right - - cmp ah, 0x4b-1 ; left arrow - jne .redraw - -.try_move_left: -.try_move_up: - neg al -.try_move_right: -.try_move_down: %ifdef MOVE_COUNT - push ax - push ds - mov bx, SCREEN_DS - mov ds, bx - mov bx, COLS + 2 + (LINES-1)*COLS*2 - + push di + mov di, COLS + 2 + (LINES-1)*COLS*2 +patch_count_pos equ $ - 2 .chk_score: - dec bx - dec bx + dec di + dec di mov al, '0' - xchg [bx], al + xchg [es:di], al or al, 0x30 cmp al, '9' je .chk_score inc ax - mov [bx], al - pop ds - pop ax + stosb + pop di %endif - call try_move -.redraw: - call draw_current_level +.dont_push: + mov ax, bx ; bx = next player position + xchg ax, [di - 2] ; update player position in memory + ; ax = current player position -.check_win: - - ; get width and height - mov ax, [CURRENT_LEVEL] ; al = width; ah = height - mul ah - mov cx, ax ; cx = size of map - - xor bx, bx ; bx = number of bricks-NOT-on-a-spot - - mov si, CURRENT_LEVEL_4 -.check_win_loop: - lodsb - cmp al, 2 - jne .not_a_brick - inc bx -.not_a_brick: - loop .check_win_loop - - ; so, did we win? is the number of spotless bricks == 0?? - cmp bx, 0 - je win - jmp .mainloop - - -win: - ; print a nice win message to the middle of the screen - lea si, [bp+str_you_win-test_level] - - ; destination position on screen - mov ax, SCREEN_DS - mov es, ax - mov di, (COLS * 12 + COLS/2 - 6) * 2 - - mov ah, 0x0F -.loop: - cs lodsb - - cmp al, 0 - je wait_for_esc - - stosw - jmp .loop - -wait_for_esc: - ; read key - xor ax, ax - int 0x16 - - dec ah ; esc - je get_data.exit - jmp boot -; halt: -; cli ; clear interrupt flag -; hlt ; halt execution - - -;; functions: - -draw_current_level: - ; get width and height - mov cx, [CURRENT_LEVEL] ; cl = width; ch = height - push cx ; put it in the stack for later reuse - - ; print in the middle and not in the corner - mov di, 25*COLS; middle of screen - - ; offset by half of width - mov bx, 0x00FE - and bl, cl - sub di, bx - - ; offset by half of height - mov cl, ch - and cx, 0x00FE - mov ax, COLS - mul cx - sub di, ax - - - mov si, CURRENT_LEVEL_4 ; source byte - - ; screen memory in text mode - mov ax, SCREEN_DS - mov es, ax - -.loop: - push si - lodsb - cbw - xchg ax, si - add si, si - mov ax, [bp+si+display_chars-test_level] - pop si - - stosw - - inc si - pop cx ; get counters - dec cl ; subtract 1 from X axis counter - jz .nextrow - push cx - jmp .loop - -.nextrow: - dec ch ; subtract 1 from Y axis counter - jz .finished - mov cl, [CURRENT_LEVEL] - push cx - - ; jump to next row down - xor ch, ch - neg cx - add cx, COLS - add cx, cx - add di, cx - - jmp .loop - -.finished: - ret - -try_move: - ; try to move the player - ; al = offset of how much to move by - - ; extend al into ax (signed) - test al, al ; check if negative - js .negative_al - xor ah, ah - jmp .after_al -.negative_al: - mov ah, 0xFF -.after_al: - push ax - - mov di,CURRENT_LEVEL_4 - - ; calculate total level size - mov ax, [CURRENT_LEVEL] - mul ah - - ; calculate requested destination position - pop bx - push bx - mov dx, [di - 2] - add bx, dx - - ; check if in bounds - cmp bx, 0 - jl .finished - cmp bx, ax - jg .finished - - ; get value at destination position - mov cl, [bx + di] - cmp cl, 4 - je .cant_push ; it's a wall - test cl, 0x02 - jz .dont_push ; it's not a brick (on spot, or not), so don't try pushing - - ; try pushing brick - pop cx ; get move offset - push bx ; store player's destination position (brick's current position) - - mov dx, bx ; dx = current brick position - add bx, cx ; bx = next brick position - + mov dl, PLAYER +.move_object: +%ifdef OPEN_LEVEL ; check bounds - cmp bx, 0 - jl .cant_push - cmp bx, ax - jg .cant_push - - ; get value at destination position - mov ch, [bx + di] - test ch, 0x0E ; test if the destination is occupied at all by ANDing with 0000 1110 - jnz .cant_push - - ; all checks passed! push the brick - - ; add new brick to screen - or ch, 0x02 ; add brick bit, by ORing with 0000 0010 - mov [bx + di], ch - - ; remove old brick from screen - add di, dx - mov cl, [di] - and cl, 0xFD ; remove brick bit, by ANDing with 1111 1101 - mov [di], cl - sub di, dx - - mov dx, [di - 2] ; dx = current player position - pop bx ; bx = next player position - jmp .redraw_player + cmp bx, WIDTH * HEIGHT + cmc + jc .cant_push +%endif + ; add new object to screen + or byte [bx + di], dl ; add object bits + ; remove old object from screen + xchg ax, bx + xor byte [bx + di], dl ; remove object bits .cant_push: - pop bx - jmp .finished - -.dont_push: - pop cx ; don't need to have this offset in the stack anymore - -.redraw_player: - ; remove old player from screen - add di, dx - mov cl, [di] - and cl, 0xF7 ; remove player bit, by ANDing with 1111 0111 - mov [di], cl - sub di, dx - - ; add new player to screen - mov ch, [bx + di] - or ch, 0x08 ; add player bit, by ORing with 0000 1000 - mov [bx + di], ch - - ; update player position in memory - mov [di - 2], bx - -.finished: ret diff -r 61c76233911e -r 34a0a4406539 x86test/receipt --- a/x86test/receipt Wed Oct 04 13:13:17 2023 +0000 +++ b/x86test/receipt Sun Feb 04 18:02:38 2024 +0000 @@ -33,10 +33,10 @@ # Rules to configure and make the package. compile_rules() { - mkdir -p $src && cd $src - tune_lzma 36,mf=bt2 PB 0 - test8086_88=$((0x$(sed '/test8086_88$/!d;s|.*text:0*||;s| .*||' patch.lst))) - dd if=patch.bin bs=1 skip=$test8086_88 2> /dev/null | dd conv=notrunc of=x86test bs=1 seek=$((0xA00+$test8086_88)) 2> /dev/null + mkdir -p $src && cd $src && cp $stuff/x86test . + tune_lzma 35,mf=bt2,lc=3,lp=0,pb=0 LC 3 LP 0 PB 0 + patch=$((0x$(sed '/patch$/!d;s|.*text:0*||;s| .*||' patch.lst))) + dd if=patch.bin bs=1 skip=$patch 2> /dev/null | dd conv=notrunc of=x86test bs=1 seek=$((0xA00+$patch)) 2> /dev/null ./pack x86test x86test.packed dd if=bootloader.bin of=x86test conv=notrunc 2> /dev/null } diff -r 61c76233911e -r 34a0a4406539 x86test/stuff/patch.S --- a/x86test/stuff/patch.S Wed Oct 04 13:13:17 2023 +0000 +++ b/x86test/stuff/patch.S Sun Feb 04 18:02:38 2024 +0000 @@ -1,6 +1,8 @@ .code16 - .org 0xc3 + .org 0xb1 +str_NEC_V20: .string "NEC V-20" +str_NEC_V30: .string "NEC V-30" str_8086: .string "8086 (16-bit NMOS)" str_8088: .string "8088 (8-bit NMOS)" str_80C86: .string "80C86 (16-bit CMOS)" @@ -11,29 +13,49 @@ test_width8_16: - .org 0x18a + .org 0x165 + +patch: + movw $str_80188-str_80186, %dx + movw $str_80186, %si + jnz width8_16 + +pre_186: /* + * NEC V20/30 support 80186 instructions e.g. pusha (executed + * as 2-byte nop on 8086/88). + */ + movb $str_NEC_V30-str_NEC_V20, %dl + movw $str_NEC_V20, %si + + movw %sp, %ax + pushaw + xchgw %ax, %sp + subw %sp, %ax + jnz width8_16 + test8086_88: pushf - xorw %cx, %cx + xchgw %ax, %cx + movw $0x46c, %di movw %cx, %es - es movw 0x46c, %bx # BIOS tick count l.o. word + es movw (%di), %bx # BIOS tick count l.o. word 1: pushw %cx sti rep cs lodsb cli orw %cx, %cx popw %cx - movw $str_8088, %ax + movb $str_8088-str_8086, %dl movw $str_8086, %si jnz nmos_8086_88 - movb $str_80C88, %al + incw %dx movw $str_80C86, %si - es cmpw 0x046c, %bx + es cmpw (%di), %bx loope 1b nmos_8086_88: popf - call test_width8_16 +width8_16: call test_width8_16 jz 1f - xchgw %ax, %si + addw %dx, %si 1: ret .org 0x1bb