wok-tiny annotate bootmine/stuff/mine.asm @ rev 182

Update bootpillman & bootsokoban
author Pascal Bellard <pascal.bellard@slitaz.org>
date Sun Sep 24 17:53:05 2023 +0000 (8 months ago)
parents e05af9592e8b
children 61c76233911e
rev   line source
pascal@180 1 bits 16
pascal@180 2
pascal@180 3 %define UNVEIL_ON_GAME_OVER
pascal@180 4 %define CGA_DISPLAY
pascal@180 5 %define DOS_QUIT
pascal@180 6 %define FAT_BOOT
pascal@180 7
pascal@180 8 cpu 8086
pascal@180 9
pascal@180 10 ;; Constants
pascal@180 11
pascal@180 12 ;; Boot sector size in bytes
pascal@180 13 %assign BootSector.Size 512
pascal@180 14
pascal@180 15 ;; Words in 16 bit x86 are 2 bytes
pascal@180 16 %assign WordSize 2
pascal@180 17
pascal@180 18 ;; This is the value to store in segment register to access the VGA text buffer.
pascal@180 19 ;; In 16 bit x86, segmented memory accesses are of the form:
pascal@180 20 ;;
pascal@180 21 ;; (segment register) * 0x10 + (offset register)
pascal@180 22 ;;
pascal@180 23 ;; The VGA text buffer is at 0xb80000, so if 0xb800 is stored in a segment
pascal@180 24 ;; register, then memory access instructions will be relative to the VGA text
pascal@180 25 ;; buffer, allowing easier access. For example, trying to access the nth byte of
pascal@180 26 ;; memory will *actually* access the nth byte of the text buffer.
pascal@180 27 %assign TextBuf.Seg 0xb800
pascal@180 28
pascal@180 29 ;; Dimensions of text buffer
pascal@180 30 %assign TextBuf.Width 40
pascal@180 31 %assign TextBuf.Height 25
pascal@180 32 %assign TextBuf.Size (TextBuf.Width * TextBuf.Height)
pascal@180 33
pascal@180 34 ;; Macro to get the index of a text buffer cell from coordinates
pascal@180 35 %define TextBuf.Index(y, x) ((y) * TextBuf.Width * 2 + (x) * 2)
pascal@180 36
pascal@180 37 ;; Length of Dirs array defined below
pascal@180 38 %assign Dirs.Len 8
pascal@180 39
pascal@180 40 ;; Keyboard scan codes
pascal@180 41 ;; http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html
pascal@180 42 %assign Key.ScanCode.Space 0x39
pascal@180 43 %assign Key.ScanCode.Up 0x48
pascal@180 44 %assign Key.ScanCode.Down 0x50
pascal@180 45 %assign Key.ScanCode.Left 0x4b
pascal@180 46 %assign Key.ScanCode.Right 0x4d
pascal@180 47 %assign Key.ScanCode.Enter 0x1c
pascal@180 48
pascal@180 49 ;; Keyboard ASCII codes
pascal@180 50 %assign Key.Ascii.RestartGame 'r'
pascal@182 51 %assign Key.Ascii.QuitGame 27
pascal@180 52
pascal@180 53 ;; This is a convenience macro for creating VGA characters. VGA characters are
pascal@180 54 ;; 16 bit words, with the lower byte as the ASCII value and the upper byte
pascal@180 55 ;; holding the foreground and background colors.
pascal@180 56 %define VgaChar(color, ascii) (((color) << 8) | (ascii))
pascal@180 57
pascal@180 58 ;; VGA colors to use for game items
pascal@180 59 ;; https://wiki.osdev.org/Text_UI#Colours
pascal@180 60 %assign Color.Veiled 0x77
pascal@180 61 %assign Color.Unveiled 0xf0
pascal@180 62 %assign Color.Cursor 0x00
pascal@180 63 %assign Color.Flag 0xcc
pascal@180 64 %assign Color.GameWinText 0x20
pascal@180 65 %assign Color.GameOverText 0xc0
pascal@180 66
pascal@180 67 ;; This value is used to calculate bomb frequency. The probability that any
pascal@180 68 ;; given cell is a bomb is (1/2)^n, where n = "number of ones in the binary
pascal@180 69 ;; representation of BombFreq".
pascal@180 70 ;;
pascal@180 71 ;; In other words, when BombFreq=0, every cell is a bomb, and appending a one
pascal@180 72 ;; halves the amount of bombs.
pascal@180 73 %assign BombFreq 0b111
pascal@180 74
pascal@180 75 %ifdef FAT_BOOT
pascal@180 76 jmp BootMine
pascal@180 77 nop
pascal@180 78 times 0x3B db 0
pascal@180 79 %endif
pascal@180 80
pascal@180 81 ;; BootMine is supported as both a DOS game and a boot sector game :)
pascal@180 82
pascal@180 83 ;; Entry point: set up graphics and run game
pascal@180 84 BootMine:
pascal@180 85 cld
pascal@180 86
pascal@180 87 ; VGA text mode 0x00
pascal@180 88 ; 320x200 pixel resolution
pascal@180 89 ; 40x25 text resolution
pascal@180 90 ; 16 colors
pascal@180 91 ; http://www.delorie.com/djgpp/doc/rbinter/id/74/0.html
pascal@180 92 xor ax, ax
pascal@180 93 int 0x10
pascal@180 94
pascal@180 95 %ifdef CGA_DISPLAY
pascal@180 96 ; Toggle intensity/blinking bit
pascal@180 97 ; http://www.techhelpmanual.com/140-int_10h_1003h__select_foreground_blink_or_bold_background.html
pascal@180 98 xor bx, bx ;blinking off
pascal@180 99 mov ds, bx
pascal@180 100 mov ax, 0x1003 ;toggle intensity/blinking bit (Jr, PS, TANDY 1000, EGA, VGA)
pascal@180 101 int 0x10
pascal@180 102 mov si, 0x463
pascal@180 103 lodsw ;get port address for 6845 video controller chip
pascal@180 104 add al, 4
pascal@180 105 xchg ax, dx
pascal@180 106 and byte [si], 0xdf ;mask value by 1101 1111 (to clear bit 5)
pascal@180 107 lodsb ;get new value of Mode Select Register
pascal@180 108 out dx, al ;disable blink (set for bold background)
pascal@180 109 %else
pascal@180 110 ; Disable blinking text
pascal@180 111 ; https://www.reddit.com/r/osdev/comments/70fcig/blinking_text/dn2t6u8?utm_source=share&utm_medium=web2x
pascal@180 112 ; Read I/O Address 0x03DA to reset index/data flip-flop
pascal@180 113 mov dx, 0x03DA
pascal@180 114 in al, dx
pascal@180 115 ; Write index 0x30 to 0x03C0 to set register index to 0x30
pascal@180 116 mov dl, 0xC0
pascal@180 117 mov al, 0x30
pascal@180 118 out dx, al
pascal@180 119 ; Read from 0x03C1 to get register contents
pascal@180 120 inc dx
pascal@180 121 in al, dx
pascal@180 122 ; Unset Bit 3 to disable Blink
pascal@180 123 and al, 0xF7
pascal@180 124 ; Write to 0x03C0 to update register with changed value
pascal@180 125 dec dx
pascal@180 126 out dx, al
pascal@180 127 %endif
pascal@180 128
pascal@180 129 ; Load VGA text buffer segment into segment registers
pascal@180 130 mov dx, TextBuf.Seg
pascal@180 131 mov es, dx
pascal@180 132 mov ds, dx
pascal@180 133
pascal@180 134 ; Disable VGA text mode cursor
pascal@180 135 ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor
pascal@180 136 mov ah, 0x01
pascal@180 137 mov ch, 0x3f
pascal@180 138 int 0x10
pascal@180 139
pascal@180 140 ;; Run game (the game is restarted by jumping here)
pascal@180 141 RunGame:
pascal@180 142
pascal@180 143 ;; Set all cells of game map to veiled '0' cells
pascal@180 144 ZeroTextBuf:
pascal@180 145 xor di, di
pascal@180 146 mov cx, TextBuf.Size
pascal@180 147 mov ax, VgaChar(Color.Veiled, '0')
pascal@180 148 rep stosw
pascal@180 149
pascal@180 150 %ifndef USE_RDTSC
pascal@180 151 ; Initialyze the simple pseudo-random number generator
pascal@180 152 ; seed = set_system_time()
pascal@182 153 %if 1
pascal@180 154 cbw
pascal@180 155 int 0x1a
pascal@180 156 push dx
pascal@182 157 %else
pascal@182 158 in al,(0x40) ; Read timer
pascal@182 159 push ax
pascal@182 160 %endif
pascal@180 161 %endif
pascal@180 162
pascal@180 163 ;; Populate text buffer with mines and digits
pascal@180 164 ;;
pascal@180 165 ;; This is done with a single triple-nested loop. The nested loops iterate over
pascal@180 166 ;; y coordinates, then x coordinates, then over the 8 adjacent cells at (y, x).
pascal@180 167 ;;
pascal@180 168 ;; Inside the 2nd loop level is bomb generation logic. Digit incrementing logic
pascal@180 169 ;; is in the 3rd loop level.
pascal@180 170 ;;
pascal@180 171 ;; Note that the coordinates on the outside border are skipped to avoid bounds
pascal@180 172 ;; checking logic.
pascal@180 173 PopulateTextBuf:
pascal@180 174 ; Iterate over y coordinates
pascal@180 175 mov bx, TextBuf.Height - 2
pascal@180 176
pascal@180 177 .LoopY:
pascal@180 178 ; Iterate over x coordinates. ch = 0 form ZeroTextBuf
pascal@180 179 %ifndef USE_RDTSC
pascal@180 180 mov cx, TextBuf.Width - 2
pascal@180 181 %else
pascal@180 182 mov cl, TextBuf.Width - 2
pascal@180 183 %endif
pascal@180 184
pascal@180 185 .LoopX:
pascal@180 186 ; di = &TextBuf[y][x]
pascal@180 187 call GetTextBufIndex
pascal@180 188 .si_value:
pascal@180 189
pascal@180 190 ; The register dl holds a boolean that is 1 if the current cell is a bomb, 0
pascal@180 191 ; otherwise.
pascal@180 192 %ifndef USE_RDTSC
pascal@180 193 ; It is calculated by bitwise and-ing the result of the simple pseudo-random number
pascal@180 194 ; generator seed = ((seed + LARGE_PRIME1) * LARGE_PRIME2) % LARGE_PRIME3
pascal@180 195 ;
pascal@180 196 ; dl = ! (prng() & BombFreq)
pascal@180 197 %assign Prime1 32749
pascal@180 198 %assign Prime2 65519
pascal@180 199 %assign Prime3 65521
pascal@180 200 pop ax
pascal@180 201 add ax, Prime1
pascal@180 202 mov bp, Prime2
pascal@180 203 mul bp
pascal@180 204 inc bp
pascal@180 205 inc bp
pascal@180 206 div bp
pascal@180 207 xchg ax, dx
pascal@180 208 push ax
pascal@180 209 %else
pascal@180 210 ; It is calculated by bitwise and-ing the result of rdtsc. (rdtsc returns the
pascal@180 211 ; amount of CPU cycles since boot, which works okay as a cheap random number
pascal@180 212 ; generator, and it's apparently supported on all x86 CPUs since the Pentium line)
pascal@180 213 ;
pascal@180 214 ; dl = ! (rdtsc() & BombFreq)
pascal@180 215 cpu 686
pascal@180 216 rdtsc
pascal@180 217 cpu 8086
pascal@180 218 %endif
pascal@180 219 and al, BombFreq
pascal@180 220 mov dx, '*' * 256 + 0
pascal@180 221
pascal@180 222 ; Initialize loop counter for .LoopDir
pascal@180 223 mov bp, Dirs.Len
pascal@180 224
pascal@180 225 ; If this cell isn't a bomb, then skip marking it as a bomb
pascal@180 226 jnz .LoopDir
pascal@180 227
pascal@180 228 ; Mark the current cell as a bomb
pascal@180 229 mov byte [di], dh
pascal@180 230 inc dx
pascal@180 231
pascal@180 232 ; Iterate over adjacent cells (directions)
pascal@180 233 .LoopDir:
pascal@180 234 ; Load adjacent cell offset from Dirs array into ax.
pascal@180 235 mov al, byte [cs:bp + si + Dirs - .si_value - 1]
pascal@180 236 cbw
pascal@180 237 ; Set di = pointer to adjacent cell
pascal@180 238 add di, ax
pascal@180 239
pascal@180 240 ; If adjacent cell is a bomb, skip digit incrementing
pascal@180 241 cmp byte [di], dh
pascal@180 242 je .LoopDirIsMine
pascal@180 243 ; The adjacent cell is a 0-7 digit and not a bomb. Add dl to the cell, which
pascal@180 244 ; is 1 if the original cell is a bomb. This gradually accumulates to the
pascal@180 245 ; amount of neighboring bombs and represents the number cells in the
pascal@180 246 ; minesweeper game.
pascal@180 247 add [di], dl
pascal@180 248 .LoopDirIsMine:
pascal@180 249 ; Restore di to original cell pointer
pascal@180 250 sub di, ax
pascal@180 251
pascal@180 252 ; Decrement adjacent direction loop counter and continue if nonzero
pascal@180 253 dec bp
pascal@180 254 jnz .LoopDir
pascal@180 255
pascal@180 256 ; Decrement x coordinate loop counter and continue if nonzero
pascal@180 257 loop .LoopX
pascal@180 258
pascal@180 259 ; Decrement y coordinate loop counter and continue if nonzero
pascal@180 260 dec bx
pascal@180 261 jnz .LoopY
pascal@180 262 %ifndef USE_RDTSC
pascal@180 263 pop ax
pascal@180 264 %endif
pascal@180 265
pascal@180 266 ;; Done populating the text buffer
pascal@180 267
pascal@180 268 ; Set the initial cursor color for game loop. The dl register is now used to
pascal@180 269 ; store the saved cell color that the cursor is on, since the cursor
pascal@180 270 ; overwrites the cell color with the cursor color.
pascal@180 271 mov dl, Color.Veiled
pascal@180 272
pascal@180 273 ;; Main loop to process key presses and update state
pascal@180 274 GameLoop:
pascal@180 275 ; Get keystroke
pascal@180 276 ; ah = BIOS scan code
pascal@180 277 ; al = ASCII character
pascal@180 278 ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html
pascal@180 279 xor ax, ax
pascal@180 280 int 0x16
pascal@182 281 %ifdef DOS_QUIT
pascal@182 282 cmp al, Key.Ascii.QuitGame
pascal@182 283 je Quit
pascal@182 284 %endif
pascal@180 285
pascal@180 286 ; bx and cx are zeroed from the PopulateTextBuf loops above
pascal@180 287 ; bx = y coord
pascal@180 288 ; cx = x coord
pascal@180 289
pascal@180 290 ; di = cell pointer
pascal@180 291 call GetTextBufIndex
pascal@180 292 ; Apply saved cell color
pascal@180 293 mov [di + 1], dl
pascal@180 294
pascal@180 295 ;; Detect win (a win occurs when every veiled cell is a mine)
pascal@180 296 DetectWin:
pascal@180 297 ; Use si register as cell pointer for win detection
pascal@180 298 xor si, si
pascal@180 299 ; Use bp as loop counter
pascal@180 300 mov bp, TextBuf.Size
pascal@180 301 .Loop:
pascal@180 302 ; if (char != '*' && (color == Color.Veiled || color == Color.Flag)) {
pascal@180 303 ; break; // Didn't win yet :(
pascal@180 304 ; }
pascal@180 305 ; Load VGA char into al
pascal@180 306 lodsb
pascal@180 307 cmp al, '*'
pascal@180 308 ; Load VGA color into al
pascal@180 309 lodsb
pascal@180 310 je .Continue
pascal@180 311 cmp al, Color.Veiled
pascal@180 312 je Break
pascal@180 313 cmp al, Color.Flag
pascal@180 314 je Break
pascal@180 315 .Continue:
pascal@180 316 dec bp
pascal@180 317 jnz .Loop
pascal@180 318 ; If loop completes without breaking, then we win! :)
pascal@180 319
pascal@180 320 ;; Show game win screen
pascal@180 321 ;;GameWin:
pascal@180 322 mov ah, Color.GameWinText
pascal@180 323 call GameEndHelper
pascal@180 324 db 'GAME WIN'
pascal@180 325
pascal@180 326 ;; Wait for restart key to be pressed, then restart game
pascal@180 327 WaitRestartLoop:
pascal@180 328 je RunGame
pascal@180 329 WaitRestart:
pascal@180 330 xor ax, ax
pascal@180 331 int 0x16
pascal@180 332 %ifdef DOS_QUIT
pascal@180 333 cmp al, Key.Ascii.QuitGame
pascal@182 334 jnz Quit.notQuit
pascal@182 335 Quit:
pascal@180 336 mov ax,0x0003 ; Restore text mode
pascal@180 337 int 0x10
pascal@180 338 int 0x20
pascal@182 339 int 0x19
pascal@180 340 .notQuit:
pascal@180 341 %endif
pascal@180 342 cmp al, Key.Ascii.RestartGame
pascal@180 343 jmp WaitRestartLoop
pascal@180 344
pascal@180 345 ;; Array of adjacent cell offsets. A byte in this array can be added to a text
pascal@180 346 ;; buffer cell pointer to get the pointer to an adjacent cell. This is used for
pascal@180 347 ;; spawning digit cells.
pascal@180 348 Dirs:
pascal@180 349 db TextBuf.Index(-1, -1)
pascal@180 350 db TextBuf.Index(-1, 0)
pascal@180 351 db TextBuf.Index(-1, +1)
pascal@180 352 db TextBuf.Index( 0, +1)
pascal@180 353 db TextBuf.Index(+1, +1)
pascal@180 354 db TextBuf.Index(+1, 0)
pascal@180 355 db TextBuf.Index(+1, -1)
pascal@180 356 db TextBuf.Index( 0, -1)
pascal@180 357
pascal@180 358 Break:
pascal@180 359 ; Didn't win yet
pascal@180 360 mov al, ah
pascal@180 361
pascal@180 362 ;; Process key press. This is an if-else chain that runs code depending on the
pascal@180 363 ;; key pressed.
pascal@180 364 CmpUp:
pascal@180 365 ; Move cursor up
pascal@180 366 dec bx
pascal@180 367 cmp al, Key.ScanCode.Up
pascal@180 368 je WrapCursor
pascal@180 369 inc bx
pascal@180 370 CmpDown:
pascal@180 371 ; Move cursor down
pascal@180 372 inc bx
pascal@180 373 cmp al, Key.ScanCode.Down
pascal@180 374 je WrapCursor
pascal@180 375 dec bx
pascal@180 376 CmpLeft:
pascal@180 377 ; Move cursor left
pascal@180 378 dec cx
pascal@180 379 cmp al, Key.ScanCode.Left
pascal@180 380 je WrapCursor
pascal@180 381 inc cx
pascal@180 382 CmpRight:
pascal@180 383 ; Move cursor right
pascal@180 384 inc cx
pascal@180 385 cmp al, Key.ScanCode.Right
pascal@180 386 je WrapCursor
pascal@180 387 dec cx
pascal@180 388 CmpEnter:
pascal@180 389 cmp al, Key.ScanCode.Enter
pascal@180 390 jne CmpSpace
pascal@180 391 ; Place flag by coloring current cell
pascal@180 392 mov dl, Color.Flag
pascal@180 393 mov [di + 1], dl
pascal@180 394 ; jmp GameLoop
pascal@180 395 CmpSpace:
pascal@180 396 cmp al, Key.ScanCode.Space
pascal@180 397 jne GameLoop
pascal@180 398
pascal@180 399 ;; If the player pressed space, clear the current cell
pascal@180 400 ClearCell:
pascal@180 401 ; Set ax = cell value
pascal@180 402 mov ax, [di]
pascal@180 403 call UnveilCell
pascal@180 404 ;; If-else chain checking the cell value
pascal@180 405 .CmpEmpty:
pascal@180 406 cmp al, '0'
pascal@180 407 jne .CmpMine
pascal@180 408 ; If cell is empty, run flood fill algorithm
pascal@180 409 call Flood
pascal@180 410 .jmpGameLoop:
pascal@180 411 jmp GameLoop
pascal@180 412 .CmpMine:
pascal@180 413 cmp al, '*'
pascal@180 414 ; No handling needed if cell is digit
pascal@180 415 jne .jmpGameLoop
pascal@180 416 ; If cell is bomb, game over :(
pascal@180 417
pascal@180 418 ;; Show game over screen
pascal@180 419
pascal@180 420 %ifdef UNVEIL_ON_GAME_OVER
pascal@180 421 mov cx, TextBuf.Size
pascal@180 422 xor si, si
pascal@180 423 .Loop:
pascal@180 424 ; Load VGA character into ax
pascal@180 425 lodsw
pascal@180 426 cmp al, '*'
pascal@180 427 jne .Next
pascal@180 428 and byte [si-1], Color.Unveiled
pascal@180 429 .Next:
pascal@180 430 loop .Loop
pascal@180 431 %endif
pascal@180 432 ;;GameOver:
pascal@180 433 mov ah, Color.GameOverText
pascal@180 434 call GameEndHelper
pascal@180 435 GameOverStr:
pascal@180 436 db 'GAME OVER'
pascal@180 437 %assign GameOverStr.Len $ - GameOverStr
pascal@180 438
pascal@180 439 ;; Helper code for GameWin and GameOver; print a string in the center of the
pascal@180 440 ;; text buffer, then wait for game to be restarted.
pascal@180 441 GameEndHelper:
pascal@180 442 pop si
pascal@180 443 mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - GameOverStr.Len / 2)
pascal@180 444 .Loop:
pascal@180 445 cs lodsb
pascal@180 446 cmp al, 'Z'
pascal@180 447 ja WaitRestart
pascal@180 448 stosw
pascal@180 449 jmp .Loop
pascal@180 450
pascal@180 451 ;; Set y and x coordinates of cursor to zero if they are out of bounds
pascal@180 452 WrapCursor:
pascal@180 453 .Y:
pascal@180 454 ; Wrap y cursor
pascal@180 455 cmp bx, TextBuf.Height
pascal@180 456 jb .X
pascal@180 457 xor bx, bx
pascal@180 458
pascal@180 459 .X:
pascal@180 460 ; Wrap x cursor
pascal@180 461 cmp cx, TextBuf.Width
pascal@180 462 jb SetCursorPos
pascal@180 463 xor cx, cx
pascal@180 464
pascal@180 465 ;; Redraw cursor in new position
pascal@180 466 SetCursorPos:
pascal@180 467 ; Get text buffer index (it changed)
pascal@180 468 call GetTextBufIndex
pascal@180 469 ; Draw cursor by changing cell to the cursor color, but save current color for
pascal@180 470 ; restoring in the next iteration of the game loop.
pascal@180 471 mov dl, Color.Cursor
pascal@180 472 xchg dl, [di + 1]
pascal@180 473
pascal@180 474 jmp ClearCell.jmpGameLoop
pascal@180 475
pascal@180 476 ;; Compute the text buffer index from y and x coordinates
pascal@180 477 ;;
pascal@180 478 ;; di = &TextBuf[bx = y][cx = x]
pascal@180 479 ;;
pascal@180 480 ;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime
pascal@180 481 ;;
pascal@180 482 ;; Parameters:
pascal@180 483 ;; * bx - y coordinate
pascal@180 484 ;; * cx - x coordinate
pascal@180 485 ;; Returns:
pascal@180 486 ;; * di - text buffer index
pascal@180 487 ;; * si - caller return address
pascal@180 488 GetTextBufIndex:
pascal@180 489 xchg ax, di
pascal@180 490 mov al, TextBuf.Width * 2
pascal@180 491 imul bl
pascal@180 492 xchg ax, di
pascal@180 493 add di, cx
pascal@180 494 add di, cx
pascal@180 495 ; load caller return address in si
pascal@180 496 pop si
pascal@180 497 push si
pascal@180 498 ret
pascal@180 499
pascal@180 500 ;; Unveil a cell so it is visible on the screen
pascal@180 501 ;;
pascal@180 502 ;; Parameters:
pascal@180 503 ;; * di - cell pointer in text buffer
pascal@180 504 ;; * al - cell ASCII value
pascal@180 505 ;; Returns:
pascal@180 506 ;; * dl - written VGA color code
pascal@180 507 UnveilCell:
pascal@180 508 ; TLDR: Use xor magic to make the cells colored.
pascal@180 509 ;
pascal@180 510 ; We have three cases to consider:
pascal@180 511 ;
pascal@180 512 ; Case 1: the cell is a digit from 1-8
pascal@180 513 ;
pascal@180 514 ; The ASCII values '1', '2', '3', ..., '8' are 0x31, 0x32, 0x33, ..., 0x38. In
pascal@180 515 ; other words, the upper nibble is always 0x3 and the lower nibble is the
pascal@180 516 ; digit. We want the VGA color code to be `Color.Unveiled | digit`. For
pascal@180 517 ; example, the color of a '3' cell would be 0xf3.
pascal@180 518 ;
pascal@180 519 ; We can accomplish this with the formula `cell_value ^ '0' ^ Color.Unveiled`.
pascal@180 520 ; Xor-ing by '0' (0x30), clears the upper nibble of the cell value, leaving
pascal@180 521 ; just the digit value. Xor-ing again by Color.Unveiled sets the upper nibble
pascal@180 522 ; to 0xf, leading to the value `Color.Unveiled | digit`.
pascal@180 523 ;
pascal@180 524 ; Since xor is associative, this can be done in one operation, by xor-ing the
pascal@180 525 ; cell value by ('0' ^ Color.Unveiled).
pascal@180 526 ;
pascal@180 527 ; Case 2: the cell is a bomb
pascal@180 528 ;
pascal@180 529 ; We don't really care about this case as long as the bomb is visible against
pascal@180 530 ; the background. The bomb turns out to be green, oh well.
pascal@180 531 ;
pascal@180 532 ; Case 3: the cell is an empty space
pascal@180 533 ;
pascal@180 534 ; This ends up coloring the cell bright yellow, which isn't a big problem.
pascal@180 535 mov dl, al
pascal@180 536 xor dl, '0' ^ Color.Unveiled
pascal@180 537 mov [di + 1], dl
pascal@180 538 ret
pascal@180 539
pascal@180 540 ;; Flood fill empty cells
pascal@180 541 ;;
pascal@180 542 ;; Parameters:
pascal@180 543 ;; * bx - cell y coordinate
pascal@180 544 ;; * cx - cell x coordinate
pascal@180 545 ;; Clobbered registers:
pascal@180 546 ;; * ax - cell value
pascal@180 547 ;; * di - cell pointer in text buffer
pascal@180 548 Flood:
pascal@180 549 ; Init: get cell pointer and value
pascal@180 550 call GetTextBufIndex
pascal@180 551 mov ax, [di]
pascal@180 552
pascal@180 553 ; Base case: bounds check y
pascal@180 554 cmp bx, TextBuf.Height
pascal@180 555 jae .Ret
pascal@180 556
pascal@180 557 ; Base case: bounds check x
pascal@180 558 cmp cx, TextBuf.Width
pascal@180 559 jae .Ret
pascal@180 560
pascal@180 561 cmp al, '0'
pascal@180 562
pascal@180 563 ; Base case: we visited this cell already or bomb
pascal@180 564 jb .Ret
pascal@180 565
pascal@180 566 ; Base case: nonempty cell unveiled and stop recursion
pascal@180 567 jne UnveilCell
pascal@180 568
pascal@180 569 ; Body: unveil empty cell
pascal@180 570 call UnveilCell
pascal@180 571
pascal@180 572 ; Body: mark cell as visited and empty
pascal@180 573 mov byte [di], ' '
pascal@180 574
pascal@180 575 ; Recursive case: flood adjacent cells
pascal@180 576
pascal@180 577 ; Flood down
pascal@180 578 inc bx
pascal@180 579 call Flood
pascal@180 580 dec bx
pascal@180 581
pascal@180 582 ; Flood left
pascal@180 583 dec cx
pascal@180 584 call Flood
pascal@180 585 inc cx
pascal@180 586
pascal@180 587 ; Flood right
pascal@180 588 inc cx
pascal@180 589 call Flood
pascal@180 590 dec cx
pascal@180 591
pascal@180 592 ; Flood up-left
pascal@180 593 dec cx
pascal@180 594 call .Flood_up
pascal@180 595 inc cx
pascal@180 596
pascal@180 597 ; Flood up-right
pascal@180 598 inc cx
pascal@180 599 call .Flood_up
pascal@180 600 dec cx
pascal@180 601
pascal@180 602 ; Flood down-left
pascal@180 603 inc bx
pascal@180 604 dec cx
pascal@180 605 call Flood
pascal@180 606 inc cx
pascal@180 607 dec bx
pascal@180 608
pascal@180 609 ; Flood down-right
pascal@180 610 inc bx
pascal@180 611 inc cx
pascal@180 612 call Flood
pascal@180 613 dec cx
pascal@180 614 dec bx
pascal@180 615
pascal@180 616 .Flood_up:
pascal@180 617 ; Flood up
pascal@180 618 dec bx
pascal@180 619 call Flood
pascal@180 620 inc bx
pascal@180 621
pascal@180 622 .Ret:
pascal@180 623 ret
pascal@180 624
pascal@180 625
pascal@180 626 ;; Print program size at build time
pascal@180 627 %assign CodeSize $ - $$
pascal@180 628 %warning Code is CodeSize bytes
pascal@180 629
pascal@180 630 %ifdef MBR_BOOT
pascal@180 631 %assign PartitionTable 0x1BE
pascal@180 632 %if CodeSize > PartitionTable
pascal@180 633 %assign OverFlow CodeSize - PartitionTable
pascal@180 634 %error Code is OverFlow bytes too large
pascal@180 635 %endif
pascal@180 636 %endif
pascal@180 637
pascal@180 638 CodeEnd:
pascal@180 639 ; Pad to size of boot sector, minus the size of a word for the boot sector
pascal@180 640 ; magic value. If the code is too big to fit in a boot sector, the `times`
pascal@180 641 ; directive uses a negative value, causing a build error.
pascal@180 642 times (BootSector.Size - WordSize) - CodeSize db 0
pascal@180 643
pascal@180 644 ; Boot sector magic
pascal@180 645 dw 0xaa55