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