Minesweeper Duo is two games of Minesweeper in one: the classic solo mode everybody knows and plays while pretending they are working, and a multiplayer mode which is the equivalent of Windows Live Messenger's Minesweeper Flags (which people play while pretending they have meaningful social interactions with Internet friends.)
Minesweeper is a logic puzzle game where the player has to clear the entirety of the board (revealing each square) without digging up hidden mines. Uncovered squares may display a value from 1 to 8, which indicates the number of adjacent mines; if a tile has no bomb nearby then all surrounding cells are automatically revealed.
In multiplayer, the goal is not to uncover the field, but instead collect more mines than the opponent. For each mine discovered, the player may play again. Whoever has the absolute lowest score may use a clear bomb once, which will reveal a 5x5 area before ending the turn (right click to toggle the usage of the clear bomb.)
What's under the hood
Surprisingly this is the most complex code I've ever written in QuickBASIC. It features Mode-Y, sound effects, mouse and joystick support, expanded memory and some nifty INI system. It also showcases the usage of palette swaps for player flags (and some interface elements) as well as a half-decent fill algorithm that isn't a recursive function (the stack size is so small girls are laughing at it, clear sign that a recursive function would be a terrible idea.)
Someday, when I'm older, grumpier and less motivated, I might get back to it and implement an half-assed local TCP/IP multiplayer mode (I believe DosBOX can emulate that sort of thing,) larger playing fields, multiple visual themes, and custom-shaped fields (disclaimer: I'm not sure all of this can fit in the program's base memory.)
Doing away with CALL ABSOLUTE
Instead of relying again on assembly code for various things (like checking for the existence of a file, handling mouse, or memory operations,) the code uses QuickBASIC's built-in CALL INTERRUPTX instruction, which gives access to BIOS interrupts. This instruction is not part of the QBASIC 1.1 set but has been rewritten in 1992 by Hans Lunsing, so if you want to run the source in QBASIC like the weirdo you are, you just need to add the ported instruction to the source. For good measure, even the classic STRIG() and STICK() functions (used to detect gamepad button press and analog stick directions respectively) were replaced with CALL INTERRUPTX (huge thanks should go to Ralf Brown and his amazing list of DOS interrupts.)
For keyboard controls, I used again the ASM code provided by Milo Sedlacek, except this time, about half of it has been scrapped. Long story short, the code featured two massive chunks for the installation and un-installation of the keyboard handler. Both processes are done by replacing the default keyboard handler (whose address is stored at register &h31, interrupt 9.) This part was easily handled with CALL INTERRUPTX, so why keep the ASM code, right? The head was also removed since the install/uninstall chunks were no longer present (a jump statement was used to easily start either section of the code.) As far as I'm aware, proper keyboard handling MUST to be done in assembly, which means I'm stuck with that code until my son finds a girlfriend or until pig flies (whichever comes first, I'm not picky.)
Before I move on, I'd like to talk a bit about my new Frankenstein's monster of a menu. As you figured out looking at the screenshots, graphic user interfaces are not exactly my strong suit (my actual strong suit is a 15th century wrought iron armor replica I bought in Germany,) and that's why I redesigned it multiple times (I'm having Panda Blast! flashbacks.) What started as a simple hard-coded system (fast but inconvenient for the ever-changing design) soon devolved into pseudo-code interpreted by the engine... the menu system contains some twenty instructions including jumpers, primitive conditional branching and barebone variable manipulation. The menu pseudo-code hogs a little more than 2Kb of memory. I've been working on this abomination for the past couple of days as the deadline approached but ultimately, it got things done, albeit at a crawling pace.
This is the newest trick I learned, and it's pretty cool. EMS allows you to reserve a bunch of pages of 16KB each to store stuff. If the maximum number of pages available is limited by the hardware, the number of bytes you can access at once is always 64KB. In order to read or write to pages, you have to move them to a 64KB frame starting at a specific memory segment. It's like wearing blinders while reading a map: to see a specific area of the map, you'd have to move it around so it would fit within your narrowed field of view. Why you would decide to wear blinders while reading maps is none of my concern, but that's how you'd do it. Or so I've heard. I don't have a fetish account on furaffinity (please don't check that out.)
Anyway, this system is pretty neat because the first physical page is always located at the same memory segment and every byte in frame can easily be accessed with a 16-bit integer. If you understand how memory segments and offsets works, you'll feel right at home with EMS ("page-units" are 16,384 offsets or 1,024 segments, while "segment-units" are only 16 offsets, otherwise, it's more or less the same.)
As a side note, I used to rely on yet another assembly snippet to move data in memory, but EMS 4.0 comes with a built-in function to do it, and so, this was used instead (the downside being that EMS 4.0 is REQUIRED for the game to run at all, even if no memory page is reserved.)
Ka-boom goes the clear bomb
With EMS, stuffing sound files into memory was easy as pie. The hardest part was to obtain the original sound effects from Windows Live Messenger Minesweeper Flags, given that the service was discontinued in late 2014. Thankfully, some people uploaded their games on YouTube but not before adding techno, rap or whatever they thought would be a cool soundtrack to their Mega Epic Haxxor Noob Pawnage Video (Gone Sexual in the Hood Not Clickbait.) One person (ONE) actually had a usable recording (thanks derekdevries) and the video even included each and every sound the game had! Except for the clear bomb effect that needed some of my audio know-how voodoo to remove the noise in the back, everything was ready to go! Files were resampled to 11,025hz 8-bit mono so everything (a little less than six seconds) would fit in 64KB (if you're interested in the 48khz 16-bit stereo files for whatever reason, get in touch.)
Internally, each sound is stored contiguously in memory, sometimes sprawling over multiple pages. Due to the fact that only 4 logical pages can be mapped to physical memory at once, and given that any sound could start anywhere on any page, each chunk couldn't be larger than 3 pages (49.152 bytes.) An exception could have been made for files larger than 3 pages (but still at most 4) so they would always start at byte 0, thus covering at most 4 full pages.
While I'm on the topic, I must admit that I'm really confused as how to mix multiple audio files at once. I know the SoundBlaster 16 can do it in hardware, but how does one keep in memory all the sound that needs to be played? I can see that happening in Protected Mode, but how do you do it with EMS? I don't think there's memory available onboard the sound card, so it has to exist somewhere in RAM... but new sound cannot be loaded as already precached sound are being played, otherwise it would trash currently playing effects, right? I know it's possible, I'm pretty sure I've heard it done in QBasic before, but I'm still puzzled about it all. I'll figure it out eventually.
Minimal system requirements would be 3000 cycles in DOSBox but I'd recommend at least the equivalent of a 486 (the code could use some cleanup and optimizations,) a VGA card with 256Kb of memory, 4 pages (64Kb) of expanded memory (EMS drivers version 4.0 required - you may reduce the number of reserved pages via the INI file, but that would prevent some audio files from loading.) Gamepad, mouse and SoundBlaster-compatible audio cards are supported, but not required.