wok-tiny diff boot-man/stuff/boot-man.asm @ rev 186
Add bootlife
author | Pascal Bellard <pascal.bellard@slitaz.org> |
---|---|
date | Sun Feb 04 18:02:38 2024 +0000 (3 months ago) |
parents | 5d44015ce878 |
children |
line diff
1.1 --- a/boot-man/stuff/boot-man.asm Wed Sep 27 17:13:33 2023 +0000 1.2 +++ b/boot-man/stuff/boot-man.asm Sun Feb 04 18:02:38 2024 +0000 1.3 @@ -53,36 +53,79 @@ 1.4 ; Boot-Man runs off BIOS clock. It should therefore run at the same speed on all PCs. The code is quite heavily 1.5 ; optimized for size, so code quality is questionable at best, and downright atrocious at worst. 1.6 1.7 -;%define WaitForAnyKey 1.8 - 1.9 -org 0x0600 ; The code will move to a well known position to allow some patches. 1.10 +;%define WaitForAnyKey ; +9 bytes 1.11 +%define MDAsupport ; +7 bytes 1.12 +%define MDAinverse ; +5 bytes 1.13 +%define MDA_CGA40 ; +17 bytes 1.14 1.15 cpu 8086 ; Boot-Man runs on the original IBM PC with a CGA card. 1.16 bits 16 ; Boot-Man runs in Real Mode. I am assuming that the BIOS leaves the CPU is Real Mode. 1.17 ; This is true for the vast majority of PC systems. If your system's BIOS 1.18 ; switches to Protected Mode or Long Mode during the boot process, Boot-Man 1.19 ; won't run on your machine. 1.20 +org 0x600 1.21 1.22 +%define COL40 40 1.23 +%define COL80 80 1.24 + 1.25 start: 1.26 call copy ; Can run as bootsector or DOS COM file 1.27 1.28 moved: 1.29 push cs 1.30 pop ss 1.31 - mov sp, cx ; Set up the stack. 1.32 + xor sp, sp ; Set up the stack. 1.33 1.34 - xchg ax, cx 1.35 - inc ax ; int 0x10 / ah = 0: Switch video mode. Switch mode to 40x25 characters (al = 1). 1.36 + mov ax, 1 ; int 0x10 / ah = 0: Switch video mode. Switch mode to 40x25 characters (al = 1). 1.37 int 0x10 ; In this mode, characters are approximately square, which means that horizontal 1.38 ; and vertical movement speeds are almost the same. 1.39 - 1.40 - mov cx, 0xb800 1.41 - mov es, cx ; Set up the es segment to point to video RAM 1.42 + mov ax, 0xb800 1.43 +%ifdef MDAsupport 1.44 + %define LeftCorner (COL80*2*24+COL80-32) 1.45 + %define PreviousLine (COL80*2+32) 1.46 + %define Columns COL80 1.47 + %define Margin ((COL80-32)/2) 1.48 + %ifdef MDA_CGA40 1.49 + mov dx, PreviousLine 1.50 + %undef PreviousLine 1.51 + %define PreviousLine dx 1.52 + %endif 1.53 +%else 1.54 + %define LeftCorner (COL40*2*24+COL40-32) 1.55 + %define PreviousLine (COL40*2+32) 1.56 + %define Columns COL40 1.57 + %define Margin ((COL40-32)/2) 1.58 +%endif 1.59 +initES: 1.60 + mov es, ax ; Set up the es segment to point to video RAM 1.61 + mov si, maze ; The first byte of the bit array containing the maze 1.62 +%define ghost_n_incr 0x2f10 1.63 +%ifdef MDAsupport 1.64 + mov ah, 0xb0 1.65 + %ifdef MDAinverse 1.66 + %define ghost_attr 0x70 1.67 + %define incr_attr 0x80 1.68 + %undef ghost_n_incr 1.69 + %define ghost_n_incr (ghost_attr*256 + incr_attr) 1.70 + xor word [si + ghost_attr_patch - maze], ghost_n_incr^0x2f10 1.71 + %endif 1.72 + %ifdef MDA_CGA40 1.73 +; xor dl, (COL80*2+32)^(COL40*2+32) 1.74 + xor dl, ah 1.75 + xor word [si + LeftCorner_patch - maze], LeftCorner^(COL40*2*24+COL40-32) 1.76 + xor byte [si + Columns_patch - maze], Columns^COL40 1.77 + xor byte [si + Margin_patch - maze], Margin^((COL40-32)/2) 1.78 + %endif 1.79 + scasw 1.80 + jb initES 1.81 +%endif 1.82 1.83 mov ax, 0x0101 ; int 0x10 / ah = 1: Determine shape of hardware cursor. al = 1 avoid AMI BIOS bug. 1.84 mov ch, 0x20 ; With cx = 0x2000, this removes the hardware cursor from the screen altogether 1.85 int 0x10 1.86 1.87 +%define ghost_color 0x2fec 1.88 +%define color_increment 0x10 1.89 1.90 1.91 ;----------------------------------------------------------------------------------------------------- 1.92 @@ -96,38 +139,35 @@ 1.93 ; while the right part is drawn right to left. For efficiency reasons, the entire maze 1.94 ; is built from the bottom up. Therefore, the maze is stored upside down in memory 1.95 ;----------------------------------------------------------------------------------------------------- 1.96 -buildmaze: 1.97 - mov di, 0x0788 ; Lower left corner of maze in video ram 1.98 - mov si, maze ; The first byte of the bit array containing the maze 1.99 - mov dx, 0x05fa ; Address in video ram of the lower left powerpill 1.100 +;buildmaze: 1.101 + mov di, LeftCorner ; Lower left corner of maze in video ram 1.102 +LeftCorner_patch equ $ - 2 1.103 + mov cx, 34 ; Lines count to the lower left powerpill 1.104 .maze_outerloop: 1.105 - mov cx, 0x003c ; The distance between a character in the maze and its 1.106 + mov bx, 0x003c ; The distance between a character in the maze and its 1.107 ; symmetric counterpart. Also functions as loop counter 1.108 lodsw ; Read 16 bits from the bit array, which represents one 1.109 - ; 32 character-wide row of the maze 1.110 + xchg ax, bp ; 32 character-wide row of the maze 1.111 .maze_innerloop: 1.112 - shl ax, 1 ; shift out a single bit to determine whether a wall or dot must be shown 1.113 - push ax 1.114 + add bp, bp ; shift out a single bit to determine whether a wall or dot must be shown 1.115 mov ax, 0x01db ; Assume it is a wall character (0x01: blue; 0xdb: full solid block) 1.116 jc .draw ; Draw the character if a 1 was shifted out 1.117 mov ax, 0x0ff9 ; otherwise, assume a food character (0x0f: white; x0f9: bullet) 1.118 - cmp di, dx ; See if instead of food we need to draw a power pill 1.119 - jnz .draw 1.120 - mov dh, 0x00 ; Update powerpill address to draw remaining powerpills 1.121 + loop .draw ; See if instead of food we need to draw a power pill 1.122 + mov cl, 125 ; Update powerpill address to draw remaining powerpills 1.123 mov al, 0x04 ; powerpill character (0x04: diamond - no need to set up colour again) 1.124 .draw: 1.125 stosw ; Store character + colour in video ram 1.126 - push di 1.127 - add di, cx ; Go to its symmetric counterpart 1.128 - stosw ; and store it as well 1.129 - pop di 1.130 - pop ax 1.131 - sub cx, 4 ; Update the distance between the two sides of the maze 1.132 + mov [es:bx+di], ax ; Go to its symmetric counterpart and store it as well 1.133 + sub bx, 4 ; Update the distance between the two sides of the maze 1.134 jns .maze_innerloop ; As long as the distance between the two halves is positive, we continue 1.135 1.136 - sub di, 0x70 ; Go to the previous line on the screen in video RAM. 1.137 + sub di, PreviousLine ; Go to the previous line on the screen in video RAM. 1.138 jns .maze_outerloop ; Keep going as long as this line is on screen. 1.139 1.140 + ;mov si, bootman_data ; Use si as a pointer to the game data. This reduces byte count of the code: 1.141 + ; mov reg, [address] is a 4 byte instruction, while mov reg, [si] only has 2. 1.142 + 1.143 ;----------------------------------------------------------------------------------------------------- 1.144 ; game_loop: The main loop of the game. Tied to BIOS clock (which fires 18x per 1.145 ; second by default), to ensure that the game runs at the same speed on all machines 1.146 @@ -141,29 +181,28 @@ 1.147 ; (in the original, collisions are checked only once, and as a consequence, it is 1.148 ; possible in some circumstances to actually move Pac-Man through a ghost). 1.149 ;----------------------------------------------------------------------------------------------------- 1.150 + 1.151 game_loop: 1.152 hlt ; Wait for clock interrupt 1.153 - mov ah,0x00 1.154 + mov ah, 0x00 1.155 int 0x1a ; BIOS clock read 1.156 - xchg ax,dx 1.157 + xchg ax, dx 1.158 old_time equ $ + 1 1.159 - cmp ax,0x1234 ; Wait for time change 1.160 + cmp al, 0x12 ; Wait for time change 1.161 je game_loop 1.162 - mov [old_time],ax ; Save new time 1.163 + mov [old_time], al ; Save new time 1.164 1.165 - mov si, bootman_data ; Use si as a pointer to the game data. This reduces byte count of the code: 1.166 - ; mov reg, [address] is a 4 byte instruction, while mov reg, [si] only has 2. 1.167 - 1.168 - mov ah,0x01 ; BIOS Key available 1.169 + mov bx, 3 1.170 + mov ah, 0x01 ; BIOS Key available 1.171 int 0x16 1.172 cbw ; BIOS Read Key 1.173 je .no_key 1.174 int 0x16 1.175 - mov al,ah 1.176 + mov al, ah 1.177 dec ah ; Escape ? 1.178 jne .convert_scancode 1.179 -.exit: 1.180 - mov al,3 1.181 +;.exit: 1.182 + xchg ax, bx ; int 0x10 / ax = 3: Switch video mode. Switch mode to 80x25 characters 1.183 int 0x10 ; Reset screen 1.184 int 0x20 ; Exit to DOS... 1.185 int 0x19 ; ...or reboot 1.186 @@ -171,22 +210,23 @@ 1.187 ; This code converts al from scancode to movement direction. 1.188 ; Input: 0x48 (up), 0x4b (right), 0x50 (down), 0x4d (left) 1.189 ; Output: 0xce (up), 0xca (right), 0xc6 (down), 0xc2 (left) 1.190 + ; fe xx: dec dh dec dl inc dh inc dl 1.191 1.192 .convert_scancode: 1.193 sub al, 0x47 ; 0x01 0x04 0x09 0x06 1.194 - and al,0xfe ; 0x00 0x04 0x08 0x06 1.195 + and al, 0xfe ; 0x00 0x04 0x08 0x06 1.196 cmp al, 9 1.197 jnc .no_key ; if al >= 0x50, ignore scancode; 1.198 ; this includes key release events 1.199 neg al ; 0x00 0xfc 0xf8 0xfa 1.200 - add al,0xce ; 0xce 0xca 0xc6 0xc8 1.201 - test al,2 1.202 + add al, 0xce ; 0xce 0xca 0xc6 0xc8 1.203 + test al, 2 1.204 jne .translated 1.205 - mov al,0xc2 1.206 + mov al, 0xc2 1.207 .translated: 1.208 cmp al, [si + 2] ; If the new direction is the same as the current direction, ignore it 1.209 jz .no_key 1.210 - mov [si + 3], al ; Set new direction to the direction derived from the keyboard input 1.211 + mov [bx+si], al ; Set new direction to the direction derived from the keyboard input 1.212 .no_key: 1.213 dec byte [si + pace_offset] ; Decrease the pace counter. The pace counter determines the overall 1.214 ; speed of the game. We found that moving at a speed of 6 per second 1.215 @@ -196,30 +236,28 @@ 1.216 ; and after Boot-Man dies, by intitalizing the counter with higher values. 1.217 jnz game_loop ; If the pace counter is not 0, we immediately finish. 1.218 1.219 - mov byte [si + pace_offset], 0x3; Reset the pace counter. 1.220 + mov byte [si + pace_offset], bl ; Reset the pace counter to 3. 1.221 ;----------------------------------------------------------------------------------------------------- 1.222 ; Move Boot-Man 1.223 ;----------------------------------------------------------------------------------------------------- 1.224 - mov al, [si + 3] ; al = new movement direction, as determined by keyboard input 1.225 call newpos_bootman ; Update dx to move 1 square in the direction indicated by al 1.226 ; newpos also checks for collisions with walls (in which case ZF is set) 1.227 jz .nodirchange ; ZF indicates that new position collides with wall. We therefore try to keep 1.228 ; moving in the current direction instead. 1.229 - mov [si + 2], al ; If there's no collision, update the current movement direction 1.230 + mov [bx+si], al ; If there's no collision, update the current movement direction 1.231 .nodirchange: 1.232 - mov al, [si + 2] ; al = current movement direction 1.233 call newpos_bootman ; Update dx to move 1 square in direction al 1.234 jz .endbootman ; If there's a wall there, do nothing 1.235 -.move: 1.236 +;.move: 1.237 mov ax, 0x0f20 ; Prepare to remove Boot-Man from screen, before drawing it in the new position 1.238 ; 0x0f = black background, white foreground; 0x20 = space character 1.239 - cmp byte [es:di], 0x04 ; Detect power pill 1.240 - jnz .nopowerpill 1.241 + cmp byte [es:di], ah ; Detect power pill (0x04) 1.242 + ja .nopowerpill 1.243 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 1.244 ; as it accidentally contains 0x20, and this is one byte shorter than having an 1.245 ; explicit constant. 1.246 .nopowerpill: 1.247 - xchg dx, [si] ; Retrieve current position and store new position 1.248 + xchg dx, [si + bm_offset_pos] ; Retrieve current position and store new position 1.249 call paint ; Actually remove Boot-Man from the screen 1.250 .endbootman: 1.251 ;----------------------------------------------------------------------------------------------------- 1.252 @@ -259,13 +297,12 @@ 1.253 ; target 4 squares ahead of Pac-Man, in the direction Pac-Man is currently moving. The other ghosts' 1.254 ; movement is a bit more complex than that. I had to simplify the AI because of the limited code size. 1.255 ;----------------------------------------------------------------------------------------------------- 1.256 - mov bx, 3 * gh_length + bm_length ; Set up offset to ghost data. With this, si + bx is a 1.257 + mov bl, 3 * gh_length + bm_length ; Set up offset to ghost data. With this, si + bx is a 1.258 ; pointer to the data from the last ghost. Also used as 1.259 ; loop counter to loop through all the ghosts 1.260 - mov byte [si + collision_offset], bh ; Reset collision detection. BH happens to be 0 at this point 1.261 .ghost_ai_outer: 1.262 - mov bp, 0xffff ; bp = minimum distance; start out at maxint 1.263 - mov ah, [bx + si] ; ah will become the forbidden movement direction. We start 1.264 + mov bp, si ; bp = minimum distance; start out at a big int 1.265 + mov ah, [bx + si + gh_offset_dir] ; ah will become the forbidden movement direction. We start 1.266 ; with the current direction, which is forbidden if Boot-Man 1.267 ; just ate a power pill 1.268 cmp byte [si + timer_offset], 0x20 ; If timer_offset == 0x20, Boot-Man just picked up a power pill 1.269 @@ -275,24 +312,20 @@ 1.270 mov al, 0xce ; al = current directions being tried. Doubles as loop counter 1.271 ; over all directions. 1.272 ; Values are the same as those used by the newpos routine 1.273 - mov dx, [bx + si + gh_offset_pos] ; dx = current ghost position 1.274 - cmp dx, [si] ; compare dx with Boot-Man position 1.275 - jne .ghost_ai_loop ; If they are equal, 1.276 - mov [si + collision_offset], al ; We store a non-zero value in the collision_detect flag 1.277 - ; We use al here as we know it to be non-zero, and this reduces 1.278 - ; code size compared to using a literal constant. 1.279 + call test_collision ; dx = current ghost position 1.280 .ghost_ai_loop: 1.281 push dx 1.282 cmp al, ah ; If the current direction is the forbidden direction 1.283 jz .next ; we continue with the next direction 1.284 call newpos ; Update ghost position and check if it collides with a wall 1.285 jz .next ; if so, we continue with the next direction 1.286 - mov cx, 0x0c10 ; Target position if ghosts are ethereal. Position 0x0c10 1.287 + push ax 1.288 + mov ax, 0x0c10 ; Target position if ghosts are ethereal. Position 0x0c10 1.289 ; (x = 0x10, y = 0x0c) is in the center of the maze. 1.290 cmp byte [si + timer_offset], bh ; See if ghost timer runs. We compare with bh, which is known to be 0. 1.291 jnz .skip_target ; If ghost timer runs, we use the aforementioned target position 1.292 - mov cx, [si] ; Otherwise we use Boot-Man's current position, 1.293 - add cx, [bx + si + gh_offset_focus] ; Updated with an offset that is different for each ghost 1.294 + mov ax, [si + bm_offset_pos] ; Otherwise we use Boot-Man's current position, 1.295 + add ax, [bx + si + gh_offset_focus] ; Updated with an offset that is different for each ghost 1.296 .skip_target: 1.297 ;----------------------------------------------------------------------------------------------------- 1.298 ; get_distance: Calculate distance between positions in cx (target position) and dx (ghost position) 1.299 @@ -300,28 +333,20 @@ 1.300 ; The square of the distance between the positions in cx and dx is calculated, 1.301 ; according to Pythagoras' theorem. 1.302 ;----------------------------------------------------------------------------------------------------- 1.303 - push ax 1.304 - sub cl, dl ; after this, cl contains the horizontal difference 1.305 - sub ch, dh ; and ch the vertical difference 1.306 + sub al, dl ; after this, al contains the horizontal difference 1.307 + sub ah, dh ; and ah the vertical difference 1.308 1.309 - mov al, ch 1.310 - cbw 1.311 - xchg ax,cx 1.312 - cbw ; expand cl to ax and ch to cx 1.313 - 1.314 - push dx 1.315 - mul ax 1.316 - xchg ax,cx ; cx = square of vertical difference 1.317 - mul ax ; ax = square of horizontal difference 1.318 - pop dx 1.319 - 1.320 + mov cl, ah 1.321 + imul al 1.322 + xchg ax, cx ; cx = square of horizontal difference 1.323 + imul al ; ax = square of vertical difference 1.324 add cx, ax ; cx = distance squared between positions in cx and dx 1.325 pop ax 1.326 1.327 cmp cx, bp ; Compare this distance to the current minimum 1.328 jnc .next ; and if it is, 1.329 mov bp, cx ; update the minimum distance 1.330 - mov [bx + si], al ; set the movement direction to the current direction 1.331 + mov [bx + si + gh_offset_dir], al ; set the movement direction to the current direction 1.332 mov [bx + si + gh_offset_pos], dx ; Store the new ghost position 1.333 .next: 1.334 pop dx ; Restore the current ghost position 1.335 @@ -342,29 +367,62 @@ 1.336 ; Note that this "terrain storing" approach can trigger a bug 1.337 ; if Boot-Man and a ghost share a position. In that case 1.338 ; an extra Boot-Man character may be drawn on screen. 1.339 - mov dx, [bx + si + gh_offset_pos + gh_length] ; dx = updated ghost position 1.340 - cmp dx, [si] ; compare dx with Boot-Man's position 1.341 - jne .skip_collision ; and if they coincide, 1.342 - mov [si + collision_offset], al ; set the collision detect flag to a non-zero value. 1.343 -.skip_collision: 1.344 + add bx, gh_length ; go to next ghost 1.345 + call test_collision ; dx = current ghost position 1.346 call get_screenpos ; find the address in video ram of the updated ghost position, 1.347 mov ax, [es:di] ; store its content in ax 1.348 - mov [bx + si + gh_offset_terrain + gh_length], ax ; and copy it to ghostterrain 1.349 - add bx, gh_length ; go to next ghost 1.350 + mov [bx + si + gh_offset_terrain], ax ; and copy it to ghostterrain 1.351 cmp bx, 3 * gh_length + bm_length ; and determine if it is the final ghost 1.352 jnz .ghostterrain_loop 1.353 1.354 - ; Test if ghosts are invisible 1.355 - mov ax, 0x2fec ; Assume ghost is visible: 0x2f = purple background, white text 1.356 - ; 0xec = infinity symbol = ghost eyes 1.357 - mov cl, 0x10 ; cl = difference in colour between successive ghosts 1.358 - ; ch is set to zero as that leads to smaller code 1.359 - cmp byte [si + timer_offset], bh ; See if ghost timer is running (note bh is still zero at this point) 1.360 - jnz .ghosts_invisible ; If it is, ghosts are ethereal 1.361 + mov ax, 0x0f00 ; Update ghost colour to black background, white eyes 1.362 + ; Update difference between colours of successive ghosts. Value of 0x0 1.363 + ; means all ghosts are the same colour when they are ethereal. 1.364 + dec byte [si + timer_offset] ; Update ghost_timer to limit the period of the ghosts being ethereal 1.365 + jns .ghostcolor 1.366 + mov byte [si + timer_offset], bh ; Ghost timer was not running 1.367 + mov ax, ghost_n_incr 1.368 +ghost_attr_patch equ $ - 2 1.369 + 1.370 +.ghostcolor: 1.371 + mov cl, al ; cl = difference in colour between successive ghosts 1.372 + mov al, ghost_color&255 ; 0xec = infinity symbol = ghost eyes 1.373 +.ghostdraw: ; Draw the ghosts on the screen 1.374 + mov dx, [bx + si + gh_offset_pos] ; dx = new ghost position 1.375 + call paint ; show ghost in video ram 1.376 + add ah, cl ; Update ghost colour. 1.377 + sub bl, gh_length ; Loop over all ghosts 1.378 + jns .ghostdraw ; until the final one. 1.379 1.380 - cmp byte [si + collision_offset], bh ; Ghosts are visible, so test for collisions 1.381 - jz .no_collision 1.382 - 1.383 + mov ax, game_loop ; ret instruction will jump to game_loop 1.384 + push ax 1.385 + mov ax, word 0x0e02 ; Draw boot-man on the screen. 0x0e = black background, yellow foreground 1.386 + ; 0x02 = smiley face 1.387 +;----------------------------------------------------------------------------------------------------- 1.388 +; paint: paints a character on screen at given x, y coordinates in dx 1.389 +; simple convenience function that gets called enough to be actually worth it, in terms 1.390 +; of code length. 1.391 +;----------------------------------------------------------------------------------------------------- 1.392 +paint_bootman: 1.393 + mov dx, [si + bm_offset_pos] ; dx = Boot-Man position 1.394 +paint: 1.395 + call get_screenpos ; Convert x, y coordinates in dx to video memory address 1.396 + stosw ; stosw = shorter code for mov [es:di], ax 1.397 + ; stosw also adds 2 to di, but that effect is ignored here 1.398 + ret 1.399 + 1.400 + 1.401 +;----------------------------------------------------------------------------------------------------- 1.402 +; test_collision: if end of game, restart from the beginning 1.403 +;----------------------------------------------------------------------------------------------------- 1.404 + 1.405 +test_collision: 1.406 + mov dx, [bx + si + gh_offset_pos] ; dx = current ghost position 1.407 + cmp dx, [si + bm_offset_pos] ; compare dx with Boot-Man position 1.408 + jnz no_collision 1.409 + cmp byte [si + timer_offset], bh ; Ghost timer was not running 1.410 + jnz no_collision 1.411 + pop ax ; Adjust stack 1.412 %ifdef WaitForAnyKey 1.413 ; Ghosts are visible and collide with boot-man, therefore boot-man is dead 1.414 mov ax, 0x0e0f ; Dead boot-man: 0x0e = black background, yellow foreground 1.415 @@ -373,51 +431,28 @@ 1.416 cbw 1.417 int 0x16 ; Wait for any key 1.418 %endif 1.419 -jump_start equ $ + 1 1.420 - mov si,0xFFFC ; restart boot sector 1.421 - push si 1.422 - ret 1.423 - 1.424 - ; Ghosts are invisible 1.425 -.ghosts_invisible: 1.426 - dec byte [si + timer_offset] ; Update ghost_timer to limit the period of the ghosts being ethereal 1.427 - mov ah, 0x0f ; Update ghost colour to black background, white eyes 1.428 - mov cl, 0x0 ; Update difference between colours of successive ghosts. Value of 0x0 1.429 - ; means all ghosts are the same colour when they are ethereal. 1.430 - 1.431 -.no_collision: 1.432 -.ghostdraw: ; Draw the ghosts on the screen 1.433 - mov dx, [bx + si + gh_offset_pos] ; dx = new ghost position 1.434 - call paint ; show ghost in video ram 1.435 - add ah, cl ; Update ghost colour. 1.436 - sub bx, gh_length ; Loop over all ghosts 1.437 - jns .ghostdraw ; until the final one. 1.438 - 1.439 - 1.440 - mov ax, word 0x0e02 ; Draw boot-man on the screen. 0x0e = black background, yellow foreground 1.441 - ; 0x02 = smiley face 1.442 - call paint_bootman ; show Boot-Man 1.443 - 1.444 -.end: 1.445 - jmp game_loop 1.446 + mov si, 0xfb5e ; restart boot sector 1.447 +source equ $ - 2 1.448 1.449 1.450 ;----------------------------------------------------------------------------------------------------- 1.451 ; copy: self copy to a fixed location 1.452 ;----------------------------------------------------------------------------------------------------- 1.453 -copy: 1.454 - pop si ; Get ip value (0x103 or 0x7C03, sometimes 0x0003) 1.455 + 1.456 +copy equ $ - 2 ; seek to 'pop si, sti' instruction (0x5e 0xfb) 1.457 +; pop si ; Get ip value (0x103 or 0x7C03, sometimes 0x0003) 1.458 +; sti ; Allow interrupts 1.459 push cs 1.460 pop ds 1.461 push cs ; Setup ds and es 1.462 pop es 1.463 - and [si+jump_start-moved], si ; Save value to the restart with unpatched code 1.464 mov di, moved ; Move self to a well known address 1.465 push di 1.466 - mov cx, 512-3 1.467 - sti ; Allow interrupts 1.468 + mov ch, (end-moved)/256+1 1.469 cld ; Clear the direction flag. We use string instructions a lot as they have one-byte codes 1.470 + mov [si+source-moved], si ; Save value to the restart with unpatched code 1.471 rep movsb 1.472 +no_collision: 1.473 ret 1.474 1.475 1.476 @@ -453,7 +488,9 @@ 1.477 ;----------------------------------------------------------------------------------------------------- 1.478 1.479 newpos_bootman: 1.480 - mov dx, [si] ; dx = current position of Boot-Man 1.481 + mov al, [bx+si] ; al = new or current movement direction 1.482 + dec bx 1.483 + mov dx, [si + bm_offset_pos] ; dx = current position of Boot-Man 1.484 newpos: 1.485 mov [.modified_instruction + 1], al ; Here the instruction to be executed is modified 1.486 .modified_instruction: 1.487 @@ -461,84 +498,78 @@ 1.488 and dl, 0x1f ; Deal with tunnels 1.489 get_screenpos: 1.490 xchg ax,di ; save ax 1.491 - mov al, 0x28 1.492 + mov al, Columns 1.493 +Columns_patch equ $ - 1 1.494 mul dh ; multiply ax by 0x28 = 40 decimal, the screen width 1.495 add al, dl 1.496 - adc ah, 0 1.497 + adc ah, bh 1.498 xchg ax, di ; di = y * 40 + x 1.499 +%ifdef MDAsupport 1.500 + add di, Margin 1.501 +Margin_patch equ $ - 1 1.502 +%else 1.503 scasw ; Skip the left border by adding 4 to di 1.504 scasw 1.505 - shl di, 1 ; Multiply di by 2 1.506 +%endif 1.507 + add di, di ; Multiply di by 2 1.508 cmp byte [es:di], 0xdb ; Check to see if the new position collides with a wall 1.509 ; 0xdb = full block character that makes up the wall 1.510 ret 1.511 1.512 -;----------------------------------------------------------------------------------------------------- 1.513 -; paint: paints a character on screen at given x, y coordinates in dx 1.514 -; simple convenience function that gets called enough to be actually worth it, in terms 1.515 -; of code length. 1.516 -;----------------------------------------------------------------------------------------------------- 1.517 -paint_bootman: 1.518 - mov dx, [si] ; dx = Boot-Man position 1.519 -paint: 1.520 - call get_screenpos ; Convert x, y coordinates in dx to video memory address 1.521 - stosw ; stosw = shorter code for mov [es:di], ax 1.522 - ; stosw also adds 2 to di, but that effect is ignored here 1.523 - ret 1.524 - 1.525 - 1.526 -bootman_data: 1.527 - db 0x0f, 0x0f ; Boot-Man's x and y position 1.528 - db 0xca ; Boot-Man's direction 1.529 - db 0xca ; Boot-Man's future direction 1.530 - 1.531 -pace_counter: db 0x10 1.532 -ghost_timer: db 0x0 ; if > 0 ghosts are invisible, and is counted backwards to 0 1.533 - 1.534 -ghostdata: 1.535 - db 0xc2 ; 1st ghost, direction 1.536 -ghostpos: 1.537 - db 0x01, 0x01 ; x and y position 1.538 -ghostterrain: 1.539 - dw 0x0ff9 ; terrain underneath 1.540 -ghostfocus: 1.541 - db 0x0, 0x0 ; focus point for movement 1.542 -secondghost: 1.543 - db 0xce ; 2nd ghost, direction 1.544 - db 0x01, 0x17 ; x and y position 1.545 - dw 0x0ff9 ; terrain underneath 1.546 - db 0x0, 0x4 ; focus point for movement 1.547 - db 0xca ; 3rd ghost, direction 1.548 - db 0x1e, 0x01 ; x and y position 1.549 - dw 0x0ff9 ; terrain underneath 1.550 - db 0xfc, 0x0 ; focus point for movement 1.551 - db 0xce ; 4th ghost, direction 1.552 - db 0x1e, 0x17 ; x and y position 1.553 - dw 0x0ff9 ; terrain underneath 1.554 - db 0x4, 0x0 ; focus point for movement 1.555 -lastghost: 1.556 - 1.557 -bm_length equ ghostdata - bootman_data 1.558 -gh_length equ secondghost - ghostdata 1.559 -gh_offset_pos equ ghostpos - ghostdata 1.560 -gh_offset_terrain equ ghostterrain - ghostdata 1.561 -gh_offset_focus equ ghostfocus - ghostdata 1.562 -pace_offset equ pace_counter - bootman_data 1.563 -timer_offset equ ghost_timer - bootman_data 1.564 - 1.565 ; The maze, as a bit array. Ones denote walls, zeroes denote food dots / corridors 1.566 ; The maze is stored upside down to save one cmp instruction in buildmaze 1.567 maze: dw 0xffff, 0x8000, 0xbffd, 0x8081, 0xfabf, 0x8200, 0xbefd, 0x8001 1.568 dw 0xfebf, 0x0080, 0xfebf, 0x803f, 0xaebf, 0xaebf, 0x80bf, 0xfebf 1.569 dw 0x0080, 0xfefd, 0x8081, 0xbebf, 0x8000, 0xbefd, 0xbefd, 0x8001 1.570 dw 0xffff 1.571 -maze_length: equ $ - maze 1.572 1.573 -; Collision detection flag. It is initialized by the code 1.574 -collision_detect: 1.575 +bootman_data: 1.576 +bootmanpos: 1.577 + db 0x0f, 0x0f ; Boot-Man's x and y position 1.578 +bootmandir: 1.579 + db 0xca ; Boot-Man's direction 1.580 + db 0xca ; Boot-Man's future direction 1.581 1.582 -collision_offset equ collision_detect - bootman_data 1.583 +ghost_timer equ maze + 2 ; if > 0 ghosts are invisible, and is counted backwards to 0 1.584 +;pace_counter equ maze + 22 ; 0x3f 1.585 +pace_counter: db 0x10 1.586 + 1.587 +ghostdata: 1.588 +ghostpos: 1.589 + db 0x01, 0x01 ; 1st ghost, x and y position 1.590 +ghostdir: 1.591 + db 0xc2 ; direction 1.592 +ghostterrain: 1.593 + dw 0x0ff9 ; terrain underneath 1.594 +ghostfocus: 1.595 + db 0x0, 0x0 ; focus point for movement 1.596 +secondghost: 1.597 + db 0x01, 0x17 ; 2nd ghost, x and y position 1.598 + db 0xce ; direction 1.599 + dw 0x0ff9 ; terrain underneath 1.600 + db 0x0, 0x4 ; focus point for movement 1.601 + db 0x1e, 0x01 ; 3rd ghost, x and y position 1.602 + db 0xca ; direction 1.603 + dw 0x0ff9 ; terrain underneath 1.604 + db 0xfc, 0x0 ; focus point for movement 1.605 + db 0x1e, 0x17 ; 4th ghost, x and y position 1.606 + db 0xce ; direction 1.607 + dw 0x0ff9 ; terrain underneath 1.608 + db 0x4, 0x0 ; focus point for movement 1.609 +lastghost: 1.610 + 1.611 +bm_length equ ghostdata - bootman_data 1.612 +gh_length equ secondghost - ghostdata 1.613 +gh_offset_dir equ ghostdir - ghostdata 1.614 +gh_offset_pos equ ghostpos - ghostdata 1.615 +gh_offset_terrain equ ghostterrain - ghostdata 1.616 +gh_offset_focus equ ghostfocus - ghostdata 1.617 +pace_offset equ pace_counter - bootman_data 1.618 +timer_offset equ ghost_timer - bootman_data 1.619 +bm_offset_pos equ bootmanpos - bootman_data 1.620 1.621 times 510 - ($ - $$) db 0 1.622 db 0x55 1.623 -db 0xaa 1.624 \ No newline at end of file 1.625 +db 0xaa 1.626 + 1.627 +end: