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: