wok-tiny diff 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
line diff
     1.1 --- a/bootmine/stuff/mine.asm	Wed Oct 04 13:13:17 2023 +0000
     1.2 +++ b/bootmine/stuff/mine.asm	Sun Feb 04 18:02:38 2024 +0000
     1.3 @@ -1,12 +1,20 @@
     1.4  bits 16
     1.5  
     1.6 -%define UNVEIL_ON_GAME_OVER
     1.7 -%define CGA_DISPLAY
     1.8 -%define DOS_QUIT
     1.9 -%define FAT_BOOT
    1.10 -%define RESTART_ON_ANY_KEY
    1.11 +%if 1
    1.12 +%define UNVEIL_ON_GAME_OVER	+16
    1.13 +%define CGA_DISPLAY		+6
    1.14 +%define MDA_SUPPORT		+74
    1.15 +%define DOS_QUIT		+12
    1.16 +%define UNDO_FLAG		+10
    1.17 +%define EXPLOSION		+4
    1.18 +;%define LAZY_CHECK		-4  cylinder board
    1.19 +;%define USE_RDTSC		-15 need a 586+
    1.20 +%else
    1.21 +%define USE_RDTSC		-15 need a 586+
    1.22 +%endif
    1.23  
    1.24  cpu 8086
    1.25 +org 0x7c00
    1.26  
    1.27  ;; Constants
    1.28  
    1.29 @@ -25,7 +33,11 @@
    1.30  ;; register, then memory access instructions will be relative to the VGA text
    1.31  ;; buffer, allowing easier access. For example, trying to access the nth byte of
    1.32  ;; memory will *actually* access the nth byte of the text buffer.
    1.33 +%ifdef MDA_SUPPORT
    1.34 +%assign TextBuf.Seg 0xb000
    1.35 +%else
    1.36  %assign TextBuf.Seg 0xb800
    1.37 +%endif
    1.38  
    1.39  ;; Dimensions of text buffer
    1.40  %assign TextBuf.Width 40
    1.41 @@ -35,21 +47,15 @@
    1.42  ;; Macro to get the index of a text buffer cell from coordinates
    1.43  %define TextBuf.Index(y, x) ((y) * TextBuf.Width * 2 + (x) * 2)
    1.44  
    1.45 -;; Length of Dirs array defined below
    1.46 -%assign Dirs.Len 8
    1.47 -
    1.48  ;; Keyboard scan codes
    1.49  ;; http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html
    1.50 -%assign Key.ScanCode.Space 0x39
    1.51 -%assign Key.ScanCode.Up 0x48
    1.52 -%assign Key.ScanCode.Down 0x50
    1.53 -%assign Key.ScanCode.Left 0x4b
    1.54 -%assign Key.ScanCode.Right 0x4d
    1.55 -%assign Key.ScanCode.Enter 0x1c
    1.56 -
    1.57 -;; Keyboard ASCII codes
    1.58 -%assign Key.Ascii.RestartGame 'r'
    1.59 -%assign Key.Ascii.QuitGame 27
    1.60 +%assign Key.ScanCode.Space    0x39
    1.61 +%assign Key.ScanCode.Up       0x48
    1.62 +%assign Key.ScanCode.Down     0x50
    1.63 +%assign Key.ScanCode.Left     0x4b
    1.64 +%assign Key.ScanCode.Right    0x4d
    1.65 +%assign Key.ScanCode.Enter    0x1c
    1.66 +%assign Key.ScanCode.QuitGame 0x01
    1.67  
    1.68  ;; This is a convenience macro for creating VGA characters. VGA characters are
    1.69  ;; 16 bit words, with the lower byte as the ASCII value and the upper byte
    1.70 @@ -58,32 +64,40 @@
    1.71  
    1.72  ;; VGA colors to use for game items
    1.73  ;; https://wiki.osdev.org/Text_UI#Colours
    1.74 -%assign Color.Veiled 0x77
    1.75 -%assign Color.Unveiled 0xf0
    1.76 -%assign Color.Cursor 0x00
    1.77 -%assign Color.Flag 0xcc
    1.78 -%assign Color.GameWinText 0x20
    1.79 -%assign Color.GameOverText 0xc0
    1.80 +%assign Color.Veiled         0x77
    1.81 +%assign Color.Unveiled       0xf0
    1.82 +%assign Color.Cursor         0x00
    1.83 +%assign Color.Flag           0xcc
    1.84 +%assign Color.GameWinText    0x20
    1.85 +%assign Color.GameOverText   0xc0
    1.86 +
    1.87 +%ifdef MDA_SUPPORT
    1.88 +%assign Mda.InverseAttr      0x70 ; 0x[7F][08]	Bit 3: High intensity.
    1.89 +%assign Mda.CursorAttr       0x09 ; 0x?[19]	Bits 0-2: 1 => underline, other values => no underline.
    1.90 +%assign Mda.HiddenAttr       0x00 ; 0x[08][08]	Bit 7: Blink.
    1.91 +%assign Mda.Veiled           0xdb ; 0xb1
    1.92 +%assign Mda.Flag             0x02
    1.93 +%assign Mda.Cursor           0xb1 ; 0x16
    1.94 +%assign Mda.Screen.Offset    0x8000
    1.95 +%assign Mda.Buffer           0x8000
    1.96 +%define Mda.Attr(cga, mda)   (((cga) << 8) | (mda))
    1.97 +%endif
    1.98  
    1.99  ;; This value is used to calculate bomb frequency. The probability that any
   1.100  ;; given cell is a bomb is (1/2)^n, where n = "number of ones in the binary
   1.101  ;; representation of BombFreq".
   1.102  ;;
   1.103 -;; In other words, when BombFreq=0, every cell is a bomb, and appending a one
   1.104 +;; In other words, when BombFreq=-1, every cell is a bomb, and appending a one
   1.105  ;; halves the amount of bombs.
   1.106 -%assign BombFreq 0b111
   1.107 -
   1.108 -%ifdef FAT_BOOT
   1.109 -  jmp BootMine
   1.110 -  nop
   1.111 -  times 0x3B db 0
   1.112 -%endif
   1.113 +%assign BombFreq             0b111
   1.114 +%assign Ascii.Bomb           '*'
   1.115  
   1.116  ;; BootMine is supported as both a DOS game and a boot sector game :)
   1.117  
   1.118  ;; Entry point: set up graphics and run game
   1.119  BootMine:
   1.120    cld
   1.121 +  sti                   ;allow interrupts
   1.122  
   1.123    ; VGA text mode 0x00
   1.124    ; 320x200 pixel resolution
   1.125 @@ -127,11 +141,6 @@
   1.126    out dx, al
   1.127  %endif
   1.128  
   1.129 -  ; Load VGA text buffer segment into segment registers
   1.130 -  mov dx, TextBuf.Seg
   1.131 -  mov es, dx
   1.132 -  mov ds, dx
   1.133 -
   1.134    ; Disable VGA text mode cursor
   1.135    ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor
   1.136    mov ah, 0x01
   1.137 @@ -140,25 +149,47 @@
   1.138  
   1.139  ;; Run game (the game is restarted by jumping here)
   1.140  RunGame:
   1.141 +  push cs
   1.142 +  pop ss
   1.143 +  mov sp, TextBuf.Seg
   1.144  
   1.145 -;; Set all cells of game map to veiled '0' cells
   1.146 -ZeroTextBuf:
   1.147 -  xor di, di
   1.148 -  mov cx, TextBuf.Size
   1.149 -  mov ax, VgaChar(Color.Veiled, '0')
   1.150 -  rep stosw
   1.151 +  ; Load VGA text buffer segment into segment registers
   1.152 +  mov es, sp
   1.153 +  mov ds, sp
   1.154  
   1.155  %ifndef USE_RDTSC
   1.156    ; Initialyze the simple pseudo-random number generator
   1.157    ; seed = set_system_time()
   1.158 - %if 1
   1.159 -  cbw
   1.160 +  mov ah, 0
   1.161    int 0x1a
   1.162 -  push dx
   1.163 +%endif
   1.164 +
   1.165 +;; Set all cells of game map to veiled '0' cells
   1.166 +ZeroTextBuf:
   1.167 +%ifdef MDA_SUPPORT
   1.168 + %ifdef UNVEIL_ON_GAME_OVER
   1.169 +  call FillScreen
   1.170   %else
   1.171 -  in al,(0x40)    ; Read timer
   1.172 -  push ax
   1.173 +  mov ax, VgaChar(Color.Veiled, '0')
   1.174 +  mov di, Mda.Screen.Offset
   1.175 +  mov bx, TextBuf.Width - Mda.Screen.Offset
   1.176 +  mov bp, TextBuf.Height
   1.177 +.1:
   1.178 +  mov cx, TextBuf.Width
   1.179 +.2:
   1.180 +  mov [cs:di + Mda.Buffer - Mda.Screen.Offset], ax
   1.181 +  mov byte [bx + di], Mda.Veiled
   1.182 +  stosw
   1.183 +  loop .2
   1.184 +  add bx, TextBuf.Width * 2
   1.185 +  dec bp
   1.186 +  jnz .1
   1.187   %endif
   1.188 +%else
   1.189 +  mov ax, VgaChar(Color.Veiled, '0')
   1.190 +  mov cx, TextBuf.Size
   1.191 +  xor di, di
   1.192 +  rep stosw
   1.193  %endif
   1.194  
   1.195  ;; Populate text buffer with mines and digits
   1.196 @@ -172,150 +203,98 @@
   1.197  ;; Note that the coordinates on the outside border are skipped to avoid bounds
   1.198  ;; checking logic.
   1.199  PopulateTextBuf:
   1.200 -  ; Iterate over y coordinates
   1.201 -  mov bx, TextBuf.Height - 2
   1.202 +  ; Iterate over y coordinates. ch = 0 form ZeroTextBuf
   1.203 +  mov cl, TextBuf.Height - 2
   1.204  
   1.205  .LoopY:
   1.206 -  ; Iterate over x coordinates. ch = 0 form ZeroTextBuf
   1.207 -%ifndef USE_RDTSC
   1.208 -  mov cx, TextBuf.Width - 2
   1.209 -%else
   1.210 -  mov cl, TextBuf.Width - 2
   1.211 -%endif
   1.212 +  mov di, TextBuf.Width - 2
   1.213  
   1.214  .LoopX:
   1.215 -  ; di = &TextBuf[y][x]
   1.216 -  call GetTextBufIndex
   1.217 -.si_value:
   1.218  
   1.219 -  ; The register dl holds a boolean that is 1 if the current cell is a bomb, 0
   1.220 +  ; The register al holds a boolean that is 1 if the current cell is a bomb, 0
   1.221    ; otherwise.
   1.222  %ifndef USE_RDTSC
   1.223    ; It is calculated by bitwise and-ing the result of the simple pseudo-random number
   1.224    ; generator seed = ((seed + LARGE_PRIME1) * LARGE_PRIME2) % LARGE_PRIME3
   1.225    ;
   1.226 -  ; dl = ! (prng() & BombFreq)
   1.227 +  ; al = ! (prng() & BombFreq)
   1.228  %assign Prime1 32749
   1.229  %assign Prime2 65519
   1.230  %assign Prime3 65521
   1.231 -  pop ax
   1.232 +  xchg ax, dx
   1.233    add ax, Prime1
   1.234 -  mov bp, Prime2
   1.235 -  mul bp
   1.236 -  inc bp
   1.237 -  inc bp	
   1.238 -  div bp
   1.239 -  xchg ax, dx
   1.240 -  push ax
   1.241 +  mov si, Prime2
   1.242 +  mul si
   1.243 +  inc si
   1.244 +  inc si
   1.245 +  div si
   1.246 +  test dl, cl
   1.247  %else
   1.248    ; It is calculated by bitwise and-ing the result of rdtsc. (rdtsc returns the
   1.249    ; amount of CPU cycles since boot, which works okay as a cheap random number
   1.250    ; generator, and it's apparently supported on all x86 CPUs since the Pentium line)
   1.251    ;
   1.252 -  ; dl = ! (rdtsc() & BombFreq)
   1.253 +  ; al = ! (rdtsc() & BombFreq)
   1.254  cpu 686
   1.255    rdtsc
   1.256  cpu 8086
   1.257 +  test al, BombFreq
   1.258  %endif
   1.259 -  and al, BombFreq
   1.260 -  mov dx, '*' * 256 + 0
   1.261  
   1.262 -  ; Initialize loop counter for .LoopDir
   1.263 -  mov bp, Dirs.Len
   1.264 +  mov ax, VgaChar(Color.Veiled, Ascii.Bomb)
   1.265 +  
   1.266 +  ; If this cell isn't a bomb, then skip marking it as a bomb
   1.267 +  jnz .Iterated
   1.268  
   1.269 -  ; If this cell isn't a bomb, then skip marking it as a bomb
   1.270 -  jnz .LoopDir
   1.271 +  ; si = &TextBuf[y][x]
   1.272 +  call GetTextBufIndex
   1.273  
   1.274    ; Mark the current cell as a bomb
   1.275 -  mov byte [di], dh
   1.276 -  inc dx
   1.277 -
   1.278    ; Iterate over adjacent cells (directions)
   1.279 -.LoopDir:
   1.280 -  ; Load adjacent cell offset from Dirs array into ax.
   1.281 -  mov al, byte [cs:bp + si + Dirs - .si_value - 1]
   1.282 -  cbw
   1.283 -  ; Set di = pointer to adjacent cell
   1.284 -  add di, ax
   1.285 -
   1.286 -  ; If adjacent cell is a bomb, skip digit incrementing
   1.287 -  cmp byte [di], dh
   1.288 -  je .LoopDirIsMine
   1.289 -  ; The adjacent cell is a 0-7 digit and not a bomb. Add dl to the cell, which
   1.290 -  ; is 1 if the original cell is a bomb. This gradually accumulates to the
   1.291 -  ; amount of neighboring bombs and represents the number cells in the
   1.292 -  ; minesweeper game.
   1.293 -  add [di], dl
   1.294 -.LoopDirIsMine:
   1.295 -  ; Restore di to original cell pointer
   1.296 -  sub di, ax
   1.297 -
   1.298 -  ; Decrement adjacent direction loop counter and continue if nonzero
   1.299 -  dec bp
   1.300 -  jnz .LoopDir
   1.301 +  call AdjacentCells
   1.302 +.Iterated:
   1.303  
   1.304    ; Decrement x coordinate loop counter and continue if nonzero
   1.305 -  loop .LoopX
   1.306 +  dec di
   1.307 +  jnz .LoopX
   1.308  
   1.309    ; Decrement y coordinate loop counter and continue if nonzero
   1.310 -  dec bx
   1.311 -  jnz .LoopY
   1.312 -%ifndef USE_RDTSC
   1.313 -  pop ax
   1.314 -%endif
   1.315 +  loop .LoopY
   1.316 +  ; cl and di are zeroed from the PopulateTextBuf loops above
   1.317  
   1.318  ;; Done populating the text buffer
   1.319  
   1.320 -  ; Set the initial cursor color for game loop. The dl register is now used to
   1.321 +  ; Set the initial cursor color for game loop. The dh register is now used to
   1.322    ; store the saved cell color that the cursor is on, since the cursor
   1.323    ; overwrites the cell color with the cursor color.
   1.324 -  mov dl, Color.Veiled
   1.325 +  xchg ax, dx
   1.326  
   1.327  ;; Main loop to process key presses and update state
   1.328  GameLoop:
   1.329 -  ; Get keystroke
   1.330 -  ; ah = BIOS scan code
   1.331 -  ; al = ASCII character
   1.332 -  ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html
   1.333 -  xor ax, ax
   1.334 -  int 0x16
   1.335 -%ifdef DOS_QUIT
   1.336 -  cmp al, Key.Ascii.QuitGame
   1.337 -  je Quit
   1.338 -%endif
   1.339 -
   1.340 -  ; bx and cx are zeroed from the PopulateTextBuf loops above
   1.341 -  ; bx = y coord
   1.342 -  ; cx = x coord
   1.343 -
   1.344 -  ; di = cell pointer
   1.345 -  call GetTextBufIndex
   1.346 -  ; Apply saved cell color
   1.347 -  mov [di + 1], dl
   1.348  
   1.349  ;; Detect win (a win occurs when every veiled cell is a mine)
   1.350  DetectWin:
   1.351 -  ; Use si register as cell pointer for win detection
   1.352 -  xor si, si
   1.353 -  ; Use bp as loop counter
   1.354 -  mov bp, TextBuf.Size
   1.355 -.Loop:
   1.356 -  ; if (char != '*' && (color == Color.Veiled || color == Color.Flag)) {
   1.357 +  mov bx, TextBuf.Size
   1.358 +  ; if (char != Ascii.Bomb && (color == Color.Veiled || color == Color.Flag)) {
   1.359    ;     break; // Didn't win yet :(
   1.360    ; }
   1.361 -  ; Load VGA char into al
   1.362 -  lodsb
   1.363 -  cmp al, '*'
   1.364 -  ; Load VGA color into al
   1.365 -  lodsb
   1.366 +  ; Use si register as cell pointer for win detection
   1.367 +%ifdef MDA_SUPPORT
   1.368 +  mov si, Mda.Buffer
   1.369 +.Loop:
   1.370 +  cs lodsw
   1.371 +%else
   1.372 +  xor si, si
   1.373 +.Loop:
   1.374 +  lodsw
   1.375 +%endif
   1.376 +  cmp al, Ascii.Bomb
   1.377    je .Continue
   1.378 -  cmp al, Color.Veiled
   1.379 -  je Break
   1.380 -  cmp al, Color.Flag
   1.381 -  je Break
   1.382 +  cmp ah, Color.Unveiled
   1.383 +  jb .Break
   1.384  .Continue:
   1.385 -  dec bp
   1.386 -  jnz .Loop
   1.387 +  dec bx
   1.388 +  jne .Loop
   1.389    ; If loop completes without breaking, then we win! :)
   1.390  
   1.391  ;; Show game win screen
   1.392 @@ -324,188 +303,247 @@
   1.393    call GameEndHelper
   1.394    db 'GAME WIN'
   1.395  
   1.396 -;; Wait for restart key to be pressed, then restart game
   1.397 +.Break:
   1.398  WaitRestart:
   1.399 -  xor ax, ax
   1.400 +;; if bx == 0: Wait for restart key to be pressed, then restart game
   1.401 +  ; Get keystroke
   1.402 +  ; ah = BIOS scan code
   1.403 +  ; al = ASCII character
   1.404 +  ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html
   1.405 +  mov ah, 0
   1.406    int 0x16
   1.407  %ifdef DOS_QUIT
   1.408 -  cmp al, Key.Ascii.QuitGame
   1.409 -  jnz Quit.notQuit
   1.410 -Quit:
   1.411 -  mov ax,0x0003           ; Restore text mode
   1.412 -  int 0x10
   1.413 -  int 0x20
   1.414 -  int 0x19
   1.415 -.notQuit:
   1.416 +  dec ah
   1.417 +  je Quit
   1.418  %endif
   1.419 -%ifndef RESTART_ON_ANY_KEY
   1.420 -  cmp al, Key.Ascii.RestartGame
   1.421 -  jne WaitRestart
   1.422 +  dec bx
   1.423 +  js RunGame
   1.424 +
   1.425 +  ; cl = y coord
   1.426 +  ; di = x coord
   1.427 +
   1.428 +  ; si = cell pointer
   1.429 +  call GetTextBufIndex
   1.430 +  ; Apply saved cell color
   1.431 +%ifdef MDA_SUPPORT
   1.432 +  call SetColor
   1.433 +%else
   1.434 +  mov byte [si + 1], dh
   1.435  %endif
   1.436 -  jmp RunGame
   1.437  
   1.438 -;; Array of adjacent cell offsets. A byte in this array can be added to a text
   1.439 -;; buffer cell pointer to get the pointer to an adjacent cell. This is used for
   1.440 -;; spawning digit cells.
   1.441 -Dirs:
   1.442 -  db TextBuf.Index(-1, -1)
   1.443 -  db TextBuf.Index(-1,  0)
   1.444 -  db TextBuf.Index(-1, +1)
   1.445 -  db TextBuf.Index( 0, +1)
   1.446 -  db TextBuf.Index(+1, +1)
   1.447 -  db TextBuf.Index(+1,  0)
   1.448 -  db TextBuf.Index(+1, -1)
   1.449 -  db TextBuf.Index( 0, -1)
   1.450 -
   1.451 -Break:
   1.452    ; Didn't win yet
   1.453 -  mov al, ah
   1.454 +  xchg al, ah
   1.455  
   1.456  ;; Process key press. This is an if-else chain that runs code depending on the
   1.457  ;; key pressed.
   1.458  CmpUp:
   1.459    ; Move cursor up
   1.460 -  dec bx
   1.461 -  cmp al, Key.ScanCode.Up
   1.462 +  dec cx
   1.463 +%ifdef DOS_QUIT
   1.464 +  sub al, Key.ScanCode.Up-1
   1.465 +%else
   1.466 +  sub al, Key.ScanCode.Up
   1.467 +%endif
   1.468    je WrapCursor
   1.469 -  inc bx
   1.470 +  inc cx
   1.471  CmpDown:
   1.472    ; Move cursor down
   1.473 -  inc bx
   1.474 -  cmp al, Key.ScanCode.Down
   1.475 +  inc cx
   1.476 +  sub al, Key.ScanCode.Down - Key.ScanCode.Up
   1.477    je WrapCursor
   1.478 -  dec bx
   1.479 +  dec cx
   1.480  CmpLeft:
   1.481    ; Move cursor left
   1.482 -  dec cx
   1.483 -  cmp al, Key.ScanCode.Left
   1.484 +  dec di
   1.485 +  sub al, Key.ScanCode.Left - Key.ScanCode.Down
   1.486    je WrapCursor
   1.487 -  inc cx
   1.488 +  inc di
   1.489  CmpRight:
   1.490    ; Move cursor right
   1.491 -  inc cx
   1.492 -  cmp al, Key.ScanCode.Right
   1.493 +  inc di
   1.494 +  sub al, Key.ScanCode.Right - Key.ScanCode.Left
   1.495    je WrapCursor
   1.496 -  dec cx
   1.497 +  dec di
   1.498  CmpEnter:
   1.499 -  cmp al, Key.ScanCode.Enter
   1.500 +  sub al, Key.ScanCode.Enter - Key.ScanCode.Right
   1.501    jne CmpSpace
   1.502    ; Place flag by coloring current cell
   1.503 -  mov dl, Color.Flag
   1.504 -  mov [di + 1], dl
   1.505 +%ifdef MDA_SUPPORT
   1.506 + %ifdef UNDO_FLAG
   1.507 +  cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Unveiled
   1.508 +  jae GameLoop
   1.509 +  xor byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Flag ^ Color.Veiled
   1.510 +  mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset]
   1.511 + %else
   1.512 +  mov dh, Color.Flag
   1.513 + %endif
   1.514 +  call SetColor
   1.515 +%else
   1.516 + %ifdef UNDO_FLAG
   1.517 +  inc si
   1.518 +  cmp byte [si], Color.Unveiled
   1.519 +  jae GameLoop
   1.520 +  xor byte [si], Color.Flag ^ Color.Veiled
   1.521 +  mov dx, [si]
   1.522 + %else
   1.523 +  mov dh, Color.Flag
   1.524 +  mov [si + 1], dh
   1.525 + %endif
   1.526 +%endif
   1.527  ;  jmp GameLoop
   1.528  CmpSpace:
   1.529 -  cmp al, Key.ScanCode.Space
   1.530 +  sub al, Key.ScanCode.Space - Key.ScanCode.Enter
   1.531    jne GameLoop
   1.532  
   1.533  ;; If the player pressed space, clear the current cell
   1.534  ClearCell:
   1.535 -  ; Set ax = cell value
   1.536 -  mov ax, [di]
   1.537 -  call UnveilCell
   1.538 -;; If-else chain checking the cell value
   1.539 -.CmpEmpty:
   1.540 -  cmp al, '0'
   1.541 -  jne .CmpMine
   1.542 +%ifdef EXPLOSION
   1.543 + %ifdef MDA_SUPPORT
   1.544 +  mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset]
   1.545 + %else
   1.546 +  mov al, [si]
   1.547 + %endif
   1.548 +  cmp al, Ascii.Bomb
   1.549 +%else
   1.550 + %ifdef MDA_SUPPORT
   1.551 +  cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset], Ascii.Bomb
   1.552 + %else
   1.553 +  cmp byte [si], Ascii.Bomb
   1.554 + %endif
   1.555 +%endif
   1.556 +  je GameOver
   1.557 +
   1.558    ; If cell is empty, run flood fill algorithm
   1.559    call Flood
   1.560 -.jmpGameLoop:
   1.561 +%ifdef MDA_SUPPORT
   1.562 +GetCharAndColor:
   1.563 +  mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset]
   1.564 +%endif
   1.565 +jmpGameLoop:
   1.566    jmp GameLoop
   1.567 -.CmpMine:
   1.568 -  cmp al, '*'
   1.569 -  ; No handling needed if cell is digit
   1.570 -  jne .jmpGameLoop
   1.571 +
   1.572 +;; Helper code for GameWin and GameOver; print a string in the center of the
   1.573 +;; text buffer, then wait for game to be restarted.
   1.574 +GameEndHelper:
   1.575 +  pop si
   1.576 +%ifdef MDA_SUPPORT
   1.577 +  mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2) + Mda.Screen.Offset
   1.578 +%else
   1.579 +  mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2)
   1.580 +%endif
   1.581 +  xor bx, bx
   1.582 +.String:
   1.583 +  cs lodsb
   1.584 +  cmp al, 'Z'
   1.585 +  ja WaitRestart
   1.586 +%ifdef MDA_SUPPORT
   1.587 +  mov [di + TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2) - Mda.Screen.Offset], al
   1.588 +%endif
   1.589 +  stosw
   1.590 +  jmp .String
   1.591 +
   1.592 +%ifdef DOS_QUIT
   1.593 +Quit:
   1.594 +  mov al,0x03           ; Restore text mode
   1.595 +  int 0x10
   1.596 +  int 0x20
   1.597 +  int 0x19
   1.598 +%endif
   1.599 +
   1.600 +GameOver:
   1.601    ; If cell is bomb, game over :(
   1.602 +%ifdef EXPLOSION
   1.603 +  call UnveilCell
   1.604 +%endif  
   1.605  
   1.606  ;; Show game over screen
   1.607  
   1.608  %ifdef UNVEIL_ON_GAME_OVER
   1.609 + %ifdef MDA_SUPPORT
   1.610 +  call UnveilBomb
   1.611 + %else
   1.612    mov cx, TextBuf.Size
   1.613    xor si, si
   1.614  .Loop:
   1.615    ; Load VGA character into ax
   1.616    lodsw
   1.617 -  cmp al, '*'
   1.618 +  cmp al, Ascii.Bomb
   1.619    jne .Next
   1.620    and byte [si-1], Color.Unveiled
   1.621  .Next:
   1.622    loop .Loop
   1.623 + %endif
   1.624  %endif
   1.625 -;;GameOver:
   1.626    mov ah, Color.GameOverText
   1.627    call GameEndHelper
   1.628  GameOverStr:
   1.629    db 'GAME OVER'
   1.630 -%assign GameOverStr.Len $ - GameOverStr
   1.631 -
   1.632 -;; Helper code for GameWin and GameOver; print a string in the center of the
   1.633 -;; text buffer, then wait for game to be restarted.
   1.634 -GameEndHelper:
   1.635 -  pop si
   1.636 -  mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - GameOverStr.Len / 2)
   1.637 -.Loop:
   1.638 -  cs lodsb
   1.639 -  cmp al, 'Z'
   1.640 -  ja WaitRestart
   1.641 -  stosw
   1.642 -  jmp .Loop
   1.643 +GameOverStr.End:
   1.644  
   1.645  ;; Set y and x coordinates of cursor to zero if they are out of bounds
   1.646 +ResetCursor:
   1.647 +  xchg ax, cx
   1.648 +  xor di, di
   1.649 +
   1.650  WrapCursor:
   1.651 -.Y:
   1.652 -  ; Wrap y cursor
   1.653 -  cmp bx, TextBuf.Height
   1.654 -  jb .X
   1.655 -  xor bx, bx
   1.656 -
   1.657 -.X:
   1.658 -  ; Wrap x cursor
   1.659 -  cmp cx, TextBuf.Width
   1.660 -  jb SetCursorPos
   1.661 -  xor cx, cx
   1.662 -
   1.663 -;; Redraw cursor in new position
   1.664 -SetCursorPos:
   1.665    ; Get text buffer index (it changed)
   1.666    call GetTextBufIndex
   1.667 +  jae ResetCursor
   1.668 +  
   1.669    ; Draw cursor by changing cell to the cursor color, but save current color for
   1.670    ; restoring in the next iteration of the game loop.
   1.671 -  mov dl, Color.Cursor
   1.672 -  xchg dl, [di + 1]
   1.673 +%ifdef MDA_SUPPORT
   1.674 +  mov dx, Mda.Attr(Color.Cursor, Mda.Cursor)
   1.675 +  call SetScreenColor
   1.676 +  jmp GetCharAndColor
   1.677 +%else
   1.678 +  mov dh, Color.Cursor
   1.679 +  xchg byte [si + 1], dh
   1.680 +  jmp jmpGameLoop
   1.681 +%endif
   1.682  
   1.683 -  jmp ClearCell.jmpGameLoop
   1.684 -
   1.685 -;; Compute the text buffer index from y and x coordinates
   1.686 -;;
   1.687 -;; di = &TextBuf[bx = y][cx = x]
   1.688 -;;
   1.689 -;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime
   1.690 -;;
   1.691 -;; Parameters:
   1.692 -;;   * bx - y coordinate
   1.693 -;;   * cx - x coordinate
   1.694 -;; Returns:
   1.695 -;;   * di - text buffer index
   1.696 -;;   * si - caller return address
   1.697 -GetTextBufIndex:
   1.698 -  xchg ax, di
   1.699 -  mov al, TextBuf.Width * 2
   1.700 -  imul bl
   1.701 -  xchg ax, di
   1.702 -  add di, cx
   1.703 -  add di, cx
   1.704 -  ; load caller return address in si
   1.705 -  pop si
   1.706 -  push si
   1.707 +%ifdef UNVEIL_ON_GAME_OVER
   1.708 + %ifdef MDA_SUPPORT
   1.709 +FillScreen:
   1.710 +  mov cx, Mda.Veiled
   1.711 +UnveilBomb:
   1.712 +  mov si, Mda.Buffer
   1.713 +  mov bp, TextBuf.Height
   1.714 +  mov bx, 0 - TextBuf.Width + Mda.Buffer
   1.715 +.LoopLine:
   1.716 +  mov di, TextBuf.Width
   1.717 +  add bx, 2*TextBuf.Width
   1.718 +.Loop:
   1.719 +  mov ax, VgaChar(Color.Veiled, '0')
   1.720 +  or cl, cl
   1.721 +  js .Fill
   1.722 +  ; Load VGA character into ax
   1.723 +  mov ax,[cs:si]
   1.724 +  cmp al, Ascii.Bomb
   1.725 +  jne .Next
   1.726 +  and ah, Color.Unveiled
   1.727 +  mov cl, al
   1.728 +.Fill:
   1.729 +  mov [si], ax
   1.730 +  mov [cs:si], ax
   1.731 +  mov [bx + si + Mda.Screen.Offset - Mda.Buffer], cl
   1.732 +.Next:
   1.733 +  lodsw
   1.734 +  dec di
   1.735 +  jnz .Loop
   1.736 +  dec bp
   1.737 +  jnz .LoopLine
   1.738    ret
   1.739 + %endif
   1.740 +%endif
   1.741  
   1.742  ;; Unveil a cell so it is visible on the screen
   1.743  ;;
   1.744  ;; Parameters:
   1.745 -;;   * di - cell pointer in text buffer
   1.746 +;;   * si - cell pointer in text buffer
   1.747  ;;   * al - cell ASCII value
   1.748  ;; Returns:
   1.749 -;;   * dl - written VGA color code
   1.750 +;;   * dh - written VGA color code
   1.751  UnveilCell:
   1.752    ; TLDR: Use xor magic to make the cells colored.
   1.753    ;
   1.754 @@ -529,113 +567,159 @@
   1.755    ; Case 2: the cell is a bomb
   1.756    ;
   1.757    ; We don't really care about this case as long as the bomb is visible against
   1.758 -  ; the background. The bomb turns out to be green, oh well.
   1.759 -  ;
   1.760 -  ; Case 3: the cell is an empty space
   1.761 -  ;
   1.762 -  ; This ends up coloring the cell bright yellow, which isn't a big problem.
   1.763 +  ; the background. The bomb turns out to be bright yellow, oh well.
   1.764 +%ifdef MDA_SUPPORT
   1.765    mov dl, al
   1.766 -  xor dl, '0' ^ Color.Unveiled
   1.767 -  mov [di + 1], dl
   1.768 +  mov [si], al
   1.769 +%endif
   1.770 +  xor al, '0' ^ Color.Unveiled
   1.771 +  mov dh, al
   1.772 +%ifdef MDA_SUPPORT
   1.773 +SetColor:
   1.774 +  mov [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], dh
   1.775 +  cmp dh, Color.Flag
   1.776 +  ja .NotVeiled
   1.777 +  mov dl, Mda.Veiled
   1.778 +  jne .NotVeiled
   1.779 +  mov dl, Mda.Flag
   1.780 +.NotVeiled:
   1.781 +SetScreenColor:
   1.782 +  mov [bx + si], dl
   1.783 +%endif
   1.784 +  mov [si + 1], dh
   1.785    ret
   1.786  
   1.787  ;; Flood fill empty cells
   1.788  ;;
   1.789  ;; Parameters:
   1.790 -;;   * bx - cell y coordinate
   1.791 -;;   * cx - cell x coordinate
   1.792 -;; Clobbered registers:
   1.793 -;;   * ax - cell value
   1.794 -;;   * di - cell pointer in text buffer
   1.795 -Flood:
   1.796 -  ; Init: get cell pointer and value
   1.797 -  call GetTextBufIndex
   1.798 -  mov ax, [di]
   1.799 -
   1.800 -  ; Base case: bounds check y
   1.801 -  cmp bx, TextBuf.Height
   1.802 -  jae .Ret
   1.803 -
   1.804 -  ; Base case: bounds check x
   1.805 -  cmp cx, TextBuf.Width
   1.806 -  jae .Ret
   1.807 -
   1.808 +;;   * cl - cell y coordinate
   1.809 +;;   * di - cell x coordinate
   1.810 +;;   * si - cell pointer in text buffer
   1.811 +FloodStep:
   1.812 +%ifdef MDA_SUPPORT
   1.813 +  mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset]
   1.814 +%else
   1.815 +  mov al, [si]
   1.816 +%endif
   1.817    cmp al, '0'
   1.818  
   1.819 +  ; Base case: nonempty cell unveiled and stop recursion
   1.820 +  ja UnveilCell
   1.821 +
   1.822 +  mov ax, VgaChar(Color.Unveiled, ' ')
   1.823 +  
   1.824    ; Base case: we visited this cell already or bomb
   1.825 -  jb .Ret
   1.826 -
   1.827 -  ; Base case: nonempty cell unveiled and stop recursion
   1.828 -  jne UnveilCell
   1.829 -
   1.830 -  ; Body: unveil empty cell
   1.831 -  call UnveilCell
   1.832 +  jb Ret
   1.833  
   1.834    ; Body: mark cell as visited and empty
   1.835 -  mov byte [di], ' '
   1.836 +%ifdef MDA_SUPPORT
   1.837 +  mov [si], ax
   1.838 +  mov [bx + si], al
   1.839 +AdjacentCells:
   1.840 +  mov [bp + si + Mda.Buffer - Mda.Screen.Offset], ax
   1.841 +%else
   1.842 +AdjacentCells:
   1.843 +  mov [si], ax
   1.844 +%endif
   1.845  
   1.846    ; Recursive case: flood adjacent cells
   1.847  
   1.848 -  ; Flood down
   1.849 -  inc bx
   1.850 -  call Flood
   1.851 -  dec bx
   1.852 +  ; Flood left-row
   1.853 +  dec di
   1.854 +  call UpAndFloodRow
   1.855  
   1.856 -  ; Flood left
   1.857 +  ; Flood center-row
   1.858 +  call FloodNextRow
   1.859 +
   1.860 +  ; Flood right-row
   1.861 +  call FloodNextRow
   1.862 +  
   1.863 +  ; re-center
   1.864    dec cx
   1.865 -  call Flood
   1.866 -  inc cx
   1.867 +  dec di
   1.868  
   1.869 -  ; Flood right
   1.870 -  inc cx
   1.871 -  call Flood
   1.872 -  dec cx
   1.873 -
   1.874 -  ; Flood up-left
   1.875 -  dec cx
   1.876 -  call .Flood_up
   1.877 -  inc cx
   1.878 -
   1.879 -  ; Flood up-right
   1.880 -  inc cx
   1.881 -  call .Flood_up
   1.882 -  dec cx
   1.883 -
   1.884 -  ; Flood down-left
   1.885 -  inc bx
   1.886 -  dec cx
   1.887 -  call Flood
   1.888 -  inc cx
   1.889 -  dec bx
   1.890 -
   1.891 -  ; Flood down-right
   1.892 -  inc bx
   1.893 -  inc cx
   1.894 -  call Flood
   1.895 -  dec cx
   1.896 -  dec bx
   1.897 -
   1.898 -.Flood_up:
   1.899 -  ; Flood up
   1.900 -  dec bx
   1.901 -  call Flood
   1.902 -  inc bx
   1.903 -
   1.904 -.Ret:
   1.905 +;; Compute the text buffer index from y and x coordinates
   1.906 +;;
   1.907 +;; si = &TextBuf[cl = y][di = x]
   1.908 +;;
   1.909 +;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime
   1.910 +;;
   1.911 +;; Parameters:
   1.912 +;;   * cl - y coordinate
   1.913 +;;   * di - x coordinate
   1.914 +;; Returns:
   1.915 +;;   * si - text buffer index
   1.916 +;;   * carry - x and y are valid coordinates
   1.917 +GetTextBufIndex:
   1.918 +  xchg ax, si
   1.919 +  mov al, TextBuf.Width * 2
   1.920 +  imul cl
   1.921 +  xchg ax, si
   1.922 +%ifdef MDA_SUPPORT
   1.923 +  lea bx, [si + TextBuf.Width - Mda.Screen.Offset]
   1.924 +  lea si, [bx + di - TextBuf.Width]
   1.925 +  add si, di
   1.926 + %ifdef LAZY_CHECK
   1.927 +  cmp si, TextBuf.Size * 2 + Mda.Screen.Offset
   1.928 + %endif
   1.929 +%else
   1.930 +  add si, di
   1.931 +  add si, di
   1.932 + %ifdef LAZY_CHECK
   1.933 +  cmp si, TextBuf.Size * 2
   1.934 + %endif
   1.935 +%endif
   1.936 +%ifndef LAZY_CHECK
   1.937 +  ; Base case: bounds check y
   1.938 +  cmp cl, TextBuf.Height
   1.939 +  jae Ret
   1.940 +  ; Base case: bounds check x
   1.941 +  cmp di, TextBuf.Width
   1.942 +%endif
   1.943 +Ret:
   1.944    ret
   1.945  
   1.946 +FloodNextRow:
   1.947 +  inc di
   1.948 +  dec cx
   1.949 +UpAndFloodRow:
   1.950 +  dec cx
   1.951 +FloodRow:
   1.952 +  call Flood
   1.953 +  call FloodDown
   1.954 +FloodDown:
   1.955 +  inc cx
   1.956 +Flood:
   1.957 +  ; si = &TextBuf[y][x]
   1.958 +  call GetTextBufIndex
   1.959 +  jae Ret
   1.960 +  
   1.961 +  cmp al, Ascii.Bomb
   1.962 +  jne FloodStep
   1.963 +Counter:
   1.964 +  ; If adjacent cell is a bomb, skip digit incrementing
   1.965 +%ifdef MDA_SUPPORT
   1.966 +  cmp [bp + si + Mda.Buffer - Mda.Screen.Offset], al
   1.967 +%else
   1.968 +  cmp [si], al
   1.969 +%endif
   1.970 +  je .IsMine
   1.971 +  ; The adjacent cell is a 0-7 digit and not a bomb. Add 1 to the cell.
   1.972 +  ; This gradually accumulates to the
   1.973 +  ; amount of neighboring bombs and represents the number cells in the
   1.974 +  ; minesweeper game.
   1.975 +%ifdef MDA_SUPPORT
   1.976 +  inc byte [bp + si + Mda.Buffer - Mda.Screen.Offset]
   1.977 +%else
   1.978 +  inc byte [si]
   1.979 +%endif
   1.980 +.IsMine:
   1.981 +  ret
   1.982  
   1.983  ;; Print program size at build time
   1.984  %assign CodeSize $ - $$
   1.985 -%warning Code is CodeSize bytes
   1.986 -
   1.987 -%ifdef MBR_BOOT
   1.988 -%assign PartitionTable 0x1BE
   1.989 -%if CodeSize > PartitionTable
   1.990 -%assign OverFlow CodeSize - PartitionTable
   1.991 -%error Code is OverFlow bytes too large
   1.992 -%endif
   1.993 -%endif
   1.994 +%assign Size $ - BootMine
   1.995 +%warning Code is Size bytes
   1.996  
   1.997  CodeEnd:
   1.998    ; Pad to size of boot sector, minus the size of a word for the boot sector