wok-tiny view 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 source
1 ; Boot-Man
2 ;
3 ; (c) 2019 Guido van den Heuvel
4 ;
5 ; Boot-Man is a Pac-Man clone that fits (snugly) inside the Master Boot Record of a USB stick.
6 ; A USB stick with Boot-Man on it boots into the game (hence the name). Unfortunately, however,
7 ; Boot-Man leaves no room in the MBR for a partition table, which means that a USB stick with Boot-Man
8 ; in its MBR cannot be used to store data. In fact, Windows does not recognize a USB stick with
9 ; Boot-Man in its MBR as a valid storage medium.
10 ;
11 ; Controls of the game: you control Boot-Man using the arrow keys. No other user input is necessary. Some other
12 ; keys can also be used to control Boot-Man, this is a side effect of coding my own keyboard handler in
13 ; just a few bytes. There simply wasn't room for checking the validity of every key press.
14 ;
15 ; The game starts automatically, and when Boot-Man dies, a new game starts automatically after any key hit.
16 ;
17 ;
18 ; I've had to take a couple of liberties with the original Pac-Man game to fit Boot-Man inside the 510
19 ; bytes available in the MBR:
20 ;
21 ; * The ghosts start in the four corners of the maze, they do not emerge from a central cage like in the original
22 ;
23 ; * There's just a single level. If you finish the level, the game keeps running with an empty maze. While
24 ; it is rather difficult to finish the game (which is intentional, because of the single level), it is possible.
25 ;
26 ; * Boot-Man only has 1 life. If Boot-Man dies, another game is started automatically.
27 ;
28 ; * Power pills function differently from the original. When Boot-Man eats a power pill, all ghosts become
29 ; ethereal (represented in game by just their eyes being visible) and cease to chase Boot-Man. While ethereal,
30 ; Boot-Man can walk through ghosts with no ill effects. While I would really like to include the "ghost hunting"
31 ; from the original, which I consider to be an iconic part of the game, this simply isn't possible in the little
32 ; space available.
33 ;
34 ; * There's no score, and no fruit to get bonus points from.
35 ;
36 ; * All ghosts, as well as Boot-Man itself, have the same, constant movement speed. In the original, the ghosts
37 ; run at higher speeds than Pac-Man, while Pac-Man gets delayed slightly when eating and ghosts get delayed when moving
38 ; through the tunnel connecting both sides of the maze. This leads to very interesting dynamics and strategies
39 ; in the original that Boot-Man, by necessity, lacks.
40 ;
41 ;
42 ; Boot-Man runs in text mode. It uses some of the graphical characters found in IBM codepage 437 for its objects:
43 ; - Boot-Man itself is represented by the smiley face (☻), which is character 0x02 in the IBM charset
44 ; - The Ghosts are represented by the infinity symbol (∞), which is character 0xec. These represent
45 ; a ghost's eyes, with the ghost's body being represented simply by putting the character on a
46 ; coloured background
47 ; - The dots that represent Boot-Man's food are represented by the bullet character (•),
48 ; which is character 0xf9
49 ; - The power pills with which Boot-Man gains extra powers are represented by the diamond (♦),
50 ; which is character 0x04
51 ; - The walls of the maze are represented by the full block character (█), which is character 0xdb
52 ;
53 ; Boot-Man runs off BIOS clock. It should therefore run at the same speed on all PCs. The code is quite heavily
54 ; optimized for size, so code quality is questionable at best, and downright atrocious at worst.
56 ;%define WaitForAnyKey ; +9 bytes
57 %define MDAsupport ; +7 bytes
58 %define MDAinverse ; +5 bytes
59 %define MDA_CGA40 ; +17 bytes
61 cpu 8086 ; Boot-Man runs on the original IBM PC with a CGA card.
62 bits 16 ; Boot-Man runs in Real Mode. I am assuming that the BIOS leaves the CPU is Real Mode.
63 ; This is true for the vast majority of PC systems. If your system's BIOS
64 ; switches to Protected Mode or Long Mode during the boot process, Boot-Man
65 ; won't run on your machine.
66 org 0x600
68 %define COL40 40
69 %define COL80 80
71 start:
72 call copy ; Can run as bootsector or DOS COM file
74 moved:
75 push cs
76 pop ss
77 xor sp, sp ; Set up the stack.
79 mov ax, 1 ; int 0x10 / ah = 0: Switch video mode. Switch mode to 40x25 characters (al = 1).
80 int 0x10 ; In this mode, characters are approximately square, which means that horizontal
81 ; and vertical movement speeds are almost the same.
82 mov ax, 0xb800
83 %ifdef MDAsupport
84 %define LeftCorner (COL80*2*24+COL80-32)
85 %define PreviousLine (COL80*2+32)
86 %define Columns COL80
87 %define Margin ((COL80-32)/2)
88 %ifdef MDA_CGA40
89 mov dx, PreviousLine
90 %undef PreviousLine
91 %define PreviousLine dx
92 %endif
93 %else
94 %define LeftCorner (COL40*2*24+COL40-32)
95 %define PreviousLine (COL40*2+32)
96 %define Columns COL40
97 %define Margin ((COL40-32)/2)
98 %endif
99 initES:
100 mov es, ax ; Set up the es segment to point to video RAM
101 mov si, maze ; The first byte of the bit array containing the maze
102 %define ghost_n_incr 0x2f10
103 %ifdef MDAsupport
104 mov ah, 0xb0
105 %ifdef MDAinverse
106 %define ghost_attr 0x70
107 %define incr_attr 0x80
108 %undef ghost_n_incr
109 %define ghost_n_incr (ghost_attr*256 + incr_attr)
110 xor word [si + ghost_attr_patch - maze], ghost_n_incr^0x2f10
111 %endif
112 %ifdef MDA_CGA40
113 ; xor dl, (COL80*2+32)^(COL40*2+32)
114 xor dl, ah
115 xor word [si + LeftCorner_patch - maze], LeftCorner^(COL40*2*24+COL40-32)
116 xor byte [si + Columns_patch - maze], Columns^COL40
117 xor byte [si + Margin_patch - maze], Margin^((COL40-32)/2)
118 %endif
119 scasw
120 jb initES
121 %endif
123 mov ax, 0x0101 ; int 0x10 / ah = 1: Determine shape of hardware cursor. al = 1 avoid AMI BIOS bug.
124 mov ch, 0x20 ; With cx = 0x2000, this removes the hardware cursor from the screen altogether
125 int 0x10
127 %define ghost_color 0x2fec
128 %define color_increment 0x10
131 ;-----------------------------------------------------------------------------------------------------
132 ; buildmaze: builds the maze. The maze is stored in memory as a bit array, with 1 representing a wall
133 ; and 0 representing a food dot. Since the maze is left-right symmetrical, only half of the
134 ; maze is stored in memory. The positions of the power pills is hard-coded in the code.
135 ; Adding the power pills to the bit array would have necessitated 2 bits for every
136 ; character, increasing its size drastically.
137 ;
138 ; Both sides of the maze are drawn simultaneously. The left part is drawn left to right,
139 ; while the right part is drawn right to left. For efficiency reasons, the entire maze
140 ; is built from the bottom up. Therefore, the maze is stored upside down in memory
141 ;-----------------------------------------------------------------------------------------------------
142 ;buildmaze:
143 mov di, LeftCorner ; Lower left corner of maze in video ram
144 LeftCorner_patch equ $ - 2
145 mov cx, 34 ; Lines count to the lower left powerpill
146 .maze_outerloop:
147 mov bx, 0x003c ; The distance between a character in the maze and its
148 ; symmetric counterpart. Also functions as loop counter
149 lodsw ; Read 16 bits from the bit array, which represents one
150 xchg ax, bp ; 32 character-wide row of the maze
151 .maze_innerloop:
152 add bp, bp ; shift out a single bit to determine whether a wall or dot must be shown
153 mov ax, 0x01db ; Assume it is a wall character (0x01: blue; 0xdb: full solid block)
154 jc .draw ; Draw the character if a 1 was shifted out
155 mov ax, 0x0ff9 ; otherwise, assume a food character (0x0f: white; x0f9: bullet)
156 loop .draw ; See if instead of food we need to draw a power pill
157 mov cl, 125 ; Update powerpill address to draw remaining powerpills
158 mov al, 0x04 ; powerpill character (0x04: diamond - no need to set up colour again)
159 .draw:
160 stosw ; Store character + colour in video ram
161 mov [es:bx+di], ax ; Go to its symmetric counterpart and store it as well
162 sub bx, 4 ; Update the distance between the two sides of the maze
163 jns .maze_innerloop ; As long as the distance between the two halves is positive, we continue
165 sub di, PreviousLine ; Go to the previous line on the screen in video RAM.
166 jns .maze_outerloop ; Keep going as long as this line is on screen.
168 ;mov si, bootman_data ; Use si as a pointer to the game data. This reduces byte count of the code:
169 ; mov reg, [address] is a 4 byte instruction, while mov reg, [si] only has 2.
171 ;-----------------------------------------------------------------------------------------------------
172 ; game_loop: The main loop of the game. Tied to BIOS clock (which fires 18x per
173 ; second by default), to ensure that the game runs at the same speed on all machines
174 ;
175 ; The code first updates Boot-Man's position according to its movement direction
176 ; and keyboard input. Then the ghost AI is run, to determine the ghosts' movement
177 ; direction and update their position. Finally, Boot-Man and the ghosts are drawn
178 ; in their new positions. Collisions between Boot-Man and the ghosts are checked
179 ; before and after ghost movement. We need to detect for collisions twice, because
180 ; if you only check once, Boot-Man can change position with a ghost without colliding
181 ; (in the original, collisions are checked only once, and as a consequence, it is
182 ; possible in some circumstances to actually move Pac-Man through a ghost).
183 ;-----------------------------------------------------------------------------------------------------
185 game_loop:
186 hlt ; Wait for clock interrupt
187 mov ah, 0x00
188 int 0x1a ; BIOS clock read
189 xchg ax, dx
190 old_time equ $ + 1
191 cmp al, 0x12 ; Wait for time change
192 je game_loop
193 mov [old_time], al ; Save new time
195 mov bx, 3
196 mov ah, 0x01 ; BIOS Key available
197 int 0x16
198 cbw ; BIOS Read Key
199 je .no_key
200 int 0x16
201 mov al, ah
202 dec ah ; Escape ?
203 jne .convert_scancode
204 ;.exit:
205 xchg ax, bx ; int 0x10 / ax = 3: Switch video mode. Switch mode to 80x25 characters
206 int 0x10 ; Reset screen
207 int 0x20 ; Exit to DOS...
208 int 0x19 ; ...or reboot
210 ; This code converts al from scancode to movement direction.
211 ; Input: 0x48 (up), 0x4b (right), 0x50 (down), 0x4d (left)
212 ; Output: 0xce (up), 0xca (right), 0xc6 (down), 0xc2 (left)
213 ; fe xx: dec dh dec dl inc dh inc dl
215 .convert_scancode:
216 sub al, 0x47 ; 0x01 0x04 0x09 0x06
217 and al, 0xfe ; 0x00 0x04 0x08 0x06
218 cmp al, 9
219 jnc .no_key ; if al >= 0x50, ignore scancode;
220 ; this includes key release events
221 neg al ; 0x00 0xfc 0xf8 0xfa
222 add al, 0xce ; 0xce 0xca 0xc6 0xc8
223 test al, 2
224 jne .translated
225 mov al, 0xc2
226 .translated:
227 cmp al, [si + 2] ; If the new direction is the same as the current direction, ignore it
228 jz .no_key
229 mov [bx+si], al ; Set new direction to the direction derived from the keyboard input
230 .no_key:
231 dec byte [si + pace_offset] ; Decrease the pace counter. The pace counter determines the overall
232 ; speed of the game. We found that moving at a speed of 6 per second
233 ; gives good speed and control, so we use a counter to only move
234 ; once for every three times that the interrupt fires.
235 ; We also use the pace counter to include longer delays at game start
236 ; and after Boot-Man dies, by intitalizing the counter with higher values.
237 jnz game_loop ; If the pace counter is not 0, we immediately finish.
239 mov byte [si + pace_offset], bl ; Reset the pace counter to 3.
240 ;-----------------------------------------------------------------------------------------------------
241 ; Move Boot-Man
242 ;-----------------------------------------------------------------------------------------------------
243 call newpos_bootman ; Update dx to move 1 square in the direction indicated by al
244 ; newpos also checks for collisions with walls (in which case ZF is set)
245 jz .nodirchange ; ZF indicates that new position collides with wall. We therefore try to keep
246 ; moving in the current direction instead.
247 mov [bx+si], al ; If there's no collision, update the current movement direction
248 .nodirchange:
249 call newpos_bootman ; Update dx to move 1 square in direction al
250 jz .endbootman ; If there's a wall there, do nothing
251 ;.move:
252 mov ax, 0x0f20 ; Prepare to remove Boot-Man from screen, before drawing it in the new position
253 ; 0x0f = black background, white foreground; 0x20 = space character
254 cmp byte [es:di], ah ; Detect power pill (0x04)
255 ja .nopowerpill
256 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
257 ; as it accidentally contains 0x20, and this is one byte shorter than having an
258 ; explicit constant.
259 .nopowerpill:
260 xchg dx, [si + bm_offset_pos] ; Retrieve current position and store new position
261 call paint ; Actually remove Boot-Man from the screen
262 .endbootman:
263 ;-----------------------------------------------------------------------------------------------------
264 ; ghost AI and movement
265 ;
266 ; Determine the new movement direction for each ghost. Ghost movement direction is determined by
267 ; the following rule:
268 ; (1) Every ghost must keep moving
269 ; (2) It is forbidden for ghosts to suddenly start moving backwards. Unless Boot-Man just consumed
270 ; a powerpill, in which case ghosts are forbidden from continuing in the direction they were going
271 ; (3) Whenever a ghost has multiple movement options (i.e., it is at a crossroads), try moving 1 space
272 ; in each direction that is allowed, and calculate the distance to the target location after
273 ; that move. Choose the direction for which this distance is lowest as the new movement direction
274 ;
275 ; During normal movement, ghosts target a position that is related to the position of Boot-Man, as follows:
276 ;
277 ; number | ghost colour | target
278 ; -------+--------------+-------------------
279 ; 1 | purple | bootman's position
280 ; 2 | red | 4 squares below Boot-Man
281 ; 3 | cyan | 4 squares to the left of Boot-Man
282 ; 4 | green | 4 squares to the right of Boot-Man
283 ;
284 ; There's two different reasons for having slightly different AI for each ghost:
285 ; (1) If all ghosts have the same AI they tend to bunch together and stay there. With the current AI,
286 ; ghosts will sometimes bunch together, but they will split apart eventually
287 ; (2) With this setup, the ghosts tend to surround Boot-Man, making it harder for the player
288 ;
289 ; When Boot-Man picks up a power pill, a timer starts running, and ghosts become ethereal.
290 ; As long as the ghosts are ethereal, the
291 ; ghosts will not chase Boot-Man. Instead they will use the center of the big rectangular block
292 ; in the middle of the maze as their target. They cannot reach it, obviously, so the result is
293 ; that they will keep circling this block for as long as the timer runs.
294 ;
295 ; This AI is related to, but not the same as, the AI actually used in Pac-Man. The red Pac-Man ghost
296 ; uses Pac-Man itself as target, same as my purple ghost, while the pink Pac-Man ghost will
297 ; target 4 squares ahead of Pac-Man, in the direction Pac-Man is currently moving. The other ghosts'
298 ; movement is a bit more complex than that. I had to simplify the AI because of the limited code size.
299 ;-----------------------------------------------------------------------------------------------------
300 mov bl, 3 * gh_length + bm_length ; Set up offset to ghost data. With this, si + bx is a
301 ; pointer to the data from the last ghost. Also used as
302 ; loop counter to loop through all the ghosts
303 .ghost_ai_outer:
304 mov bp, si ; bp = minimum distance; start out at a big int
305 mov ah, [bx + si + gh_offset_dir] ; ah will become the forbidden movement direction. We start
306 ; with the current direction, which is forbidden if Boot-Man
307 ; just ate a power pill
308 cmp byte [si + timer_offset], 0x20 ; If timer_offset == 0x20, Boot-Man just picked up a power pill
309 jz .reverse ; so in that case we do not flip the direction.
310 xor ah, 8 ; Flip the current direction to obtain the forbidden direction in ah
311 .reverse:
312 mov al, 0xce ; al = current directions being tried. Doubles as loop counter
313 ; over all directions.
314 ; Values are the same as those used by the newpos routine
315 call test_collision ; dx = current ghost position
316 .ghost_ai_loop:
317 push dx
318 cmp al, ah ; If the current direction is the forbidden direction
319 jz .next ; we continue with the next direction
320 call newpos ; Update ghost position and check if it collides with a wall
321 jz .next ; if so, we continue with the next direction
322 push ax
323 mov ax, 0x0c10 ; Target position if ghosts are ethereal. Position 0x0c10
324 ; (x = 0x10, y = 0x0c) is in the center of the maze.
325 cmp byte [si + timer_offset], bh ; See if ghost timer runs. We compare with bh, which is known to be 0.
326 jnz .skip_target ; If ghost timer runs, we use the aforementioned target position
327 mov ax, [si + bm_offset_pos] ; Otherwise we use Boot-Man's current position,
328 add ax, [bx + si + gh_offset_focus] ; Updated with an offset that is different for each ghost
329 .skip_target:
330 ;-----------------------------------------------------------------------------------------------------
331 ; get_distance: Calculate distance between positions in cx (target position) and dx (ghost position)
332 ; This used to be a function, but I inlined it to save some space.
333 ; The square of the distance between the positions in cx and dx is calculated,
334 ; according to Pythagoras' theorem.
335 ;-----------------------------------------------------------------------------------------------------
336 sub al, dl ; after this, al contains the horizontal difference
337 sub ah, dh ; and ah the vertical difference
339 mov cl, ah
340 imul al
341 xchg ax, cx ; cx = square of horizontal difference
342 imul al ; ax = square of vertical difference
343 add cx, ax ; cx = distance squared between positions in cx and dx
344 pop ax
346 cmp cx, bp ; Compare this distance to the current minimum
347 jnc .next ; and if it is,
348 mov bp, cx ; update the minimum distance
349 mov [bx + si + gh_offset_dir], al ; set the movement direction to the current direction
350 mov [bx + si + gh_offset_pos], dx ; Store the new ghost position
351 .next:
352 pop dx ; Restore the current ghost position
353 sub al, 4 ; Update the current direction / loop counter
354 cmp al, 0xc2
355 jnc .ghost_ai_loop
357 mov ax, [bx + si + gh_offset_terrain] ; Remove the ghost in the old position from the screen
358 call paint ; by painting the terrain underneath that ghost that was
359 ; determined in the previous movement phase.
360 sub bx, gh_length ; Go to the next ghost,
361 jns .ghost_ai_outer ; and stop after the final ghost
364 .ghostterrain_loop: ; Second loop through all the ghosts, to determine terrain
365 ; underneath each one. This is used in the next movement phase
366 ; to restore the terrain underneath the ghosts.
367 ; Note that this "terrain storing" approach can trigger a bug
368 ; if Boot-Man and a ghost share a position. In that case
369 ; an extra Boot-Man character may be drawn on screen.
370 add bx, gh_length ; go to next ghost
371 call test_collision ; dx = current ghost position
372 call get_screenpos ; find the address in video ram of the updated ghost position,
373 mov ax, [es:di] ; store its content in ax
374 mov [bx + si + gh_offset_terrain], ax ; and copy it to ghostterrain
375 cmp bx, 3 * gh_length + bm_length ; and determine if it is the final ghost
376 jnz .ghostterrain_loop
378 mov ax, 0x0f00 ; Update ghost colour to black background, white eyes
379 ; Update difference between colours of successive ghosts. Value of 0x0
380 ; means all ghosts are the same colour when they are ethereal.
381 dec byte [si + timer_offset] ; Update ghost_timer to limit the period of the ghosts being ethereal
382 jns .ghostcolor
383 mov byte [si + timer_offset], bh ; Ghost timer was not running
384 mov ax, ghost_n_incr
385 ghost_attr_patch equ $ - 2
387 .ghostcolor:
388 mov cl, al ; cl = difference in colour between successive ghosts
389 mov al, ghost_color&255 ; 0xec = infinity symbol = ghost eyes
390 .ghostdraw: ; Draw the ghosts on the screen
391 mov dx, [bx + si + gh_offset_pos] ; dx = new ghost position
392 call paint ; show ghost in video ram
393 add ah, cl ; Update ghost colour.
394 sub bl, gh_length ; Loop over all ghosts
395 jns .ghostdraw ; until the final one.
397 mov ax, game_loop ; ret instruction will jump to game_loop
398 push ax
399 mov ax, word 0x0e02 ; Draw boot-man on the screen. 0x0e = black background, yellow foreground
400 ; 0x02 = smiley face
401 ;-----------------------------------------------------------------------------------------------------
402 ; paint: paints a character on screen at given x, y coordinates in dx
403 ; simple convenience function that gets called enough to be actually worth it, in terms
404 ; of code length.
405 ;-----------------------------------------------------------------------------------------------------
406 paint_bootman:
407 mov dx, [si + bm_offset_pos] ; dx = Boot-Man position
408 paint:
409 call get_screenpos ; Convert x, y coordinates in dx to video memory address
410 stosw ; stosw = shorter code for mov [es:di], ax
411 ; stosw also adds 2 to di, but that effect is ignored here
412 ret
415 ;-----------------------------------------------------------------------------------------------------
416 ; test_collision: if end of game, restart from the beginning
417 ;-----------------------------------------------------------------------------------------------------
419 test_collision:
420 mov dx, [bx + si + gh_offset_pos] ; dx = current ghost position
421 cmp dx, [si + bm_offset_pos] ; compare dx with Boot-Man position
422 jnz no_collision
423 cmp byte [si + timer_offset], bh ; Ghost timer was not running
424 jnz no_collision
425 pop ax ; Adjust stack
426 %ifdef WaitForAnyKey
427 ; Ghosts are visible and collide with boot-man, therefore boot-man is dead
428 mov ax, 0x0e0f ; Dead boot-man: 0x0e = black background, yellow foreground
429 ; 0x0f = 8 pointed star
430 call paint_bootman
431 cbw
432 int 0x16 ; Wait for any key
433 %endif
434 mov si, 0xfb5e ; restart boot sector
435 source equ $ - 2
438 ;-----------------------------------------------------------------------------------------------------
439 ; copy: self copy to a fixed location
440 ;-----------------------------------------------------------------------------------------------------
442 copy equ $ - 2 ; seek to 'pop si, sti' instruction (0x5e 0xfb)
443 ; pop si ; Get ip value (0x103 or 0x7C03, sometimes 0x0003)
444 ; sti ; Allow interrupts
445 push cs
446 pop ds
447 push cs ; Setup ds and es
448 pop es
449 mov di, moved ; Move self to a well known address
450 push di
451 mov ch, (end-moved)/256+1
452 cld ; Clear the direction flag. We use string instructions a lot as they have one-byte codes
453 mov [si+source-moved], si ; Save value to the restart with unpatched code
454 rep movsb
455 no_collision:
456 ret
459 ;-----------------------------------------------------------------------------------------------------
460 ; newpos: calculates a new position, starting from a position in dx and movement direction in al.
461 ; dl contains the x coordinate, while dh contains the y coordinate. The movement directions
462 ; in al are as follows:
463 ; 0xc2: move right
464 ; 0xc6: move down
465 ; 0xca: move left
466 ; 0xce: move up
467 ;
468 ; The reason for these fairly strange values is that they form the 2nd byte (the ModR/M byte)
469 ; of the instruction updating the position:
470 ; inc dl (0xfe, 0xc2), inc dh (0xfe), dec dl (0xfe, 0xca), dec dh (0xfe, 0xce)
471 ; The code first modifies itself to the correct instruction, then executes this instruction. The
472 ; reason for doing it in this way is that this is a lot shorter than the traditional way of
473 ; doing an if / elif / elif / else chain.
474 ;
475 ; Immediately after calculating the new position we also determine the address in video RAM
476 ; corresponding to this position. All lines of the screen are stored one after the other in RAM,
477 ; starting at 0xb800:0x0000. Since each line has 40 characters, and every character takes up
478 ; two bytes (one for colour, one for the character code), the equation to calculate video RAM
479 ; offset from x, y coordinates is as follows:
480 ;
481 ; offset = 2 * (40 * y + x + 4),
482 ;
483 ; with the +4 due to the fact that the maze is in the center of the screen, with a 4 character wide
484 ; border to the left.
485 ;
486 ; newpos and get_screenpos used to be two separate functions but since they were almost
487 ; always called one after the other, combining them saved some bytes of code.
488 ;-----------------------------------------------------------------------------------------------------
490 newpos_bootman:
491 mov al, [bx+si] ; al = new or current movement direction
492 dec bx
493 mov dx, [si + bm_offset_pos] ; dx = current position of Boot-Man
494 newpos:
495 mov [.modified_instruction + 1], al ; Here the instruction to be executed is modified
496 .modified_instruction:
497 db 0xfe, 0xc2 ; inc dl in machine code
498 and dl, 0x1f ; Deal with tunnels
499 get_screenpos:
500 xchg ax,di ; save ax
501 mov al, Columns
502 Columns_patch equ $ - 1
503 mul dh ; multiply ax by 0x28 = 40 decimal, the screen width
504 add al, dl
505 adc ah, bh
506 xchg ax, di ; di = y * 40 + x
507 %ifdef MDAsupport
508 add di, Margin
509 Margin_patch equ $ - 1
510 %else
511 scasw ; Skip the left border by adding 4 to di
512 scasw
513 %endif
514 add di, di ; Multiply di by 2
515 cmp byte [es:di], 0xdb ; Check to see if the new position collides with a wall
516 ; 0xdb = full block character that makes up the wall
517 ret
519 ; The maze, as a bit array. Ones denote walls, zeroes denote food dots / corridors
520 ; The maze is stored upside down to save one cmp instruction in buildmaze
521 maze: dw 0xffff, 0x8000, 0xbffd, 0x8081, 0xfabf, 0x8200, 0xbefd, 0x8001
522 dw 0xfebf, 0x0080, 0xfebf, 0x803f, 0xaebf, 0xaebf, 0x80bf, 0xfebf
523 dw 0x0080, 0xfefd, 0x8081, 0xbebf, 0x8000, 0xbefd, 0xbefd, 0x8001
524 dw 0xffff
526 bootman_data:
527 bootmanpos:
528 db 0x0f, 0x0f ; Boot-Man's x and y position
529 bootmandir:
530 db 0xca ; Boot-Man's direction
531 db 0xca ; Boot-Man's future direction
533 ghost_timer equ maze + 2 ; if > 0 ghosts are invisible, and is counted backwards to 0
534 ;pace_counter equ maze + 22 ; 0x3f
535 pace_counter: db 0x10
537 ghostdata:
538 ghostpos:
539 db 0x01, 0x01 ; 1st ghost, x and y position
540 ghostdir:
541 db 0xc2 ; direction
542 ghostterrain:
543 dw 0x0ff9 ; terrain underneath
544 ghostfocus:
545 db 0x0, 0x0 ; focus point for movement
546 secondghost:
547 db 0x01, 0x17 ; 2nd ghost, x and y position
548 db 0xce ; direction
549 dw 0x0ff9 ; terrain underneath
550 db 0x0, 0x4 ; focus point for movement
551 db 0x1e, 0x01 ; 3rd ghost, x and y position
552 db 0xca ; direction
553 dw 0x0ff9 ; terrain underneath
554 db 0xfc, 0x0 ; focus point for movement
555 db 0x1e, 0x17 ; 4th ghost, x and y position
556 db 0xce ; direction
557 dw 0x0ff9 ; terrain underneath
558 db 0x4, 0x0 ; focus point for movement
559 lastghost:
561 bm_length equ ghostdata - bootman_data
562 gh_length equ secondghost - ghostdata
563 gh_offset_dir equ ghostdir - ghostdata
564 gh_offset_pos equ ghostpos - ghostdata
565 gh_offset_terrain equ ghostterrain - ghostdata
566 gh_offset_focus equ ghostfocus - ghostdata
567 pace_offset equ pace_counter - bootman_data
568 timer_offset equ ghost_timer - bootman_data
569 bm_offset_pos equ bootmanpos - bootman_data
571 times 510 - ($ - $$) db 0
572 db 0x55
573 db 0xaa
575 end: