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