Copyright © 2023 by Víctor Parada
This is a little game for the 2023 NOMAM's BASIC 10-liners Contest. This program fits in the EXTREM-256 category, and it was written using FastBasic 4.6 for the 8-bits ATARI XL/XE computers line. Development started on 2023-03-05, and it took 2+5 days. The final version's date is 2023-03-15.
UPDATE: It obtained the 7th place of 19 entries in the category.
Clean the floor with your broom. Use the hints of surrounding tiles to avoid mines.
Move the joystick to select the difficulty level from EASY, COOL and HARD. | |
Press the button to start the game. A pointer will appear over the tiles. You will see the number of tiles to clean at the top left and timer showing the seconds at the top right. | |
Use the joystick to move around. Press the button to clean a tile. If a number appears, it says how many mines are adjacent to it, horizontally, vertically or diagonally. | |
Clean some other tiles to get an idea about where are the mines. Sometimes, you could find a danger-free area that will be cleaned immediatelly by a single click. | |
When you are sure that a tile is hiding a mine, press and hold the button until the broom appears, then move the joystick to get a flag, then release the button. | |
To remove a misplaced flag, hold the button over it and move the joystick until a tile appears, and then release the button. | |
When you clean all the tiles that are not hiding a mine, the game is complete and the timer shows how many second were required. Try to be quick!!! All unflagged tiles will get their flag. | |
If you clean a tile that is hidding a mine, this will explode. Then, the location of every mine will be shown. If you misplaced a flag, it will remain in its place. |
A couple of years ago, I wrote a FastBasic demo using DLI to enable a customized GRAPHICS 2 text mode with 8 colors and no flickering. Every paired colors could also be combined to create 4 aditional colors. Using this trick, 13 colors including background were available in a 20x12 text mode or tiles. I was thinking about using that trick in my The Children puzzle game like in PitKat for the 2600, but then I had another idea about DLI support that fitted better in that game and didn't use too many source listing space.
12 colors demo.
PitKat for Atari 2600.
The Children.
Some days ago, I thought that the trick from the demo could be used in a Minesweeper clone, as this game requires 8 numbers matching 8 different colors, 4 tiles (unknown, flag, doubt "?" and mine) matching 4 mixed colors, and the empty space matching the background. I was sure that game logic would be simple to develope, and could fit in the space left by the setting up for the trick (too much listing space for a tenliner). I started to write a prototype over the demo to measure if it was feasible.
Old demo with redefined charset.
Visualization of hint numbers being computed.
During the development, I defined and redefined the charset and the colors many times as I was adding more features. Of course, I run out of space soon and I had to simplify things. But I also got unexpected glitches, being the most important the one that appeared when I turned P/M graphics on. The HBLANK time was partially used by the ANTIC chip's management of P/M, and I got less CPU cycles to manage PF color registers. The solution was to manage only 3 PF registers, so the number of colors for the trick were reduced from 8 to 7, and the number of mixed colors from 4 to 3. It was not that bad, because I could repeat any of the other colors for the 8 and it couldn't be noticed just because the probability of its appearance is almost 0. Also, instead of modifying colors for all 16 scan lines, I had to leave the 16th intact, because it was running when the next interrupt was triggered.
Adding a pointer using P/M.
A glitch was detected!!!
Less colors in the palette.
In order to manage multiple difficulty levels, I had to change data management and built a single DATA byte array with a simple loop to proccess it, dropping blocks or bytes directly into fixed memory areas or system registers like the color palette. Also, in that block I stored some indexed tables that are accessed at certain points of the game. With the extra space I got, I could add sound FXs, a timer and, later, an animated Smiley face.
New pointer and a timer was added.
A classic Smiley and a better pointer.
The levels have a similar difficulty than the original Minesweeper from Windows. This is a comparison of both games:
Difficulty Level | Sweeper | Minesweeper | |
---|---|---|---|
1 | Name | EASY | Beginner |
Size | 9x9 | 9x9 | |
Mines | 10 | 10 | |
Probability | 0.12345 | 0.12345 | |
2 | Name | COOL | Intermediate |
Size | 13x10 | 16x16 | |
Mines | 20 | 40 | |
Probability | 0.15384 | 0.15625 | |
3 | Name | HARD | Expert |
Size | 19x11 | 30x16 | |
Mines | 40 | 99 | |
Probability | 0.19138 | 0.20625 |
There are differences between PAL and NTSC computers: the color palette is shifted for the same register values, the screen refresh rate is not the same, affecting the speed of jiffies (Hertz), and the aspect ratio changes. As this game was developed for PAL machines, I had to build a NTSC version of it to match the original.
Get the SWEEPER.ATR file and set it as drive 1 in a real Atari (or emulator). Turn the computer on and the game should start after the loading completes. A joystick in port 1 is required.
NOTE: This game is for PAL computers. For NTSC computers, the ATR contains a file called SWEENTSC.XEX with minor changes in color palette, timer and Smiley.
The abbreviated BASIC code is the following:
The full and expanded BASIC listing is:
|
Sweeper (Minesweeper) (c) 2023 Víctor Parada |
|
$8000-$81FF: Top half of charset $8200-$83FF: Bottom half of charset $8400-$844D: Custom display list $8500-$8607: Secret screen data $8700-$88FF: Screen data |
|
This game uses 2 blocks of memory for data: screen (visible board) and secret (internal area with hidden mines and hint numbers). Both blocks are paired byte by byte. Board has a visible size of PxQ, while data size goes from 0 to P+1 and 0 to Q+1 respectively, leaving one row and one column free on each side for simplicity in the hint number computation in the internal area. Rows start every 20 bytes matching screen width. This means that the max visible size of the board should be 18x11, but left and right borders of the internal area could be merged (they are never displayed in the visible board), so the effective max size is 19x11. The total data size is 260 bytes for both areas, but top line in screen can be reused to display messages, timer and counter without conflict, and the bottom line is never displayed. |
graphics 31 |
Clears top 8K of RAM, where P/M graphics will be stored |
data d() byte="{binary data}", data byte="{binary data}" |
Data block 29 bytes: Smileys 60 bytes: Configs 3 level x 10 words 12 bytes: Tile color mappings 44 bytes: 4 pointer bitmaps (11 bytes each) 85+3 bytes: Top half of the charset 0+3 bytes: Fix for splitted DATA in string format 86+3 bytes: Bottom half of the charset 75+3 bytes: Custom Display List 6+3 bytes: Color palette 2+3 bytes: Enable Display List (560) 2+3 bytes: Redirects screen data (88) 1+3 bytes: Sets horizontal position of P3 (Smiley) |
dli set in = {color and charset assignment} |
Display List Interrupt (DLI) for the board |
dim f(12) byte,w(40),g(240),s,t,p,q,p2, q2,m,x,y,z |
Arrays: F(): Fonts mapping to bitmaps: 0-8,mine,tile,flag W(): Relative location of each mine in the board (x+y*20) G(): Queue for area cleaning Difficulty level configuration: S: Pointer to screen position of the first board cell (minus 21) T: Pointer to memory storing the secret data of the board (minus 21) P: Width of the board Q: Height of the board P2: Horizontal offset of the board in terms of P/M pixels Q2: Vertical offset of the board in terms of P/M pixels M: Number of mines to hide X: Initial horizontal position of the pointer X: Initial vertical position of the pointer Z: Initial relative position of the pointer in the board (x+y*20) |
mset $8000,$800,0 |
Clears data area |
graphics 18 |
Graphics mode 2+16 (text 20x12) |
pmgraphics 2 |
Double height P/M graphics |
move adr(d)+90,adr(f),12 |
Sets up tile mapping |
e=adr(d)+146 while peek(e) move e+3,dpeek(e+1),peek(e) e=e+3+peek(e) wend |
Loads data into memory |
c=pmadr(2) |
Gets P/M address of the pointer |
dli in |
Activates DLI |
exec _n 0 |
Defaults to EASY level |
do |
MAIN LOOP |
mset c,256,0 |
Removes pointer and smiley |
poke 708,$28 |
Restores dynamic text color |
exec _q 0 |
Prints default smiley |
repeat |
MENU LOOP |
v=10 |
Current tile value |
r=p*q-m |
Number of required tiles to clean |
mset $8500,$300,0 |
Cleans hidden data and screen |
position 6,0 print #6,"{binary data}"; |
Prints title |
exec _m e+1 |
Displays current difficulty name |
mset s+21,p,f(10) move s+21,s+41,q*20-20 |
Draws the board |
repeat until stick(0)=15 |
Waits for a released stick |
sound |
Silence... |
repeat |
Waits for next difficulty selection or confirmation of the current one |
a=stick(0)<15 |
Was joystick moved? |
b=strig(0)=0 |
Was trigger pressed? |
if a |
If joystick was moved |
e=(e+1) mod 3 |
Selects next difficulty |
exec _n e endif |
Loads new level configuration |
sound 0,72+7*e*a-62*b,10-4*b, a*8+b*2 |
Beep? Different tones based on action and level |
until a+b |
Action was taken |
exec _a |
Disables atract mode |
until b |
Start? |
exec _m 4 |
Prints WAIT message |
n=0 while n<m |
Hides mines |
b=rand(p)+rand(q)*20+21 |
Selects a random cell |
a=t+b if peek(a)=0 |
Is that cell in the board empty? |
poke a,9 |
Yes, hide a mine there |
w(n)=b |
Saves its position to simplify the hint numbers computation and to reveal them after loosing the game |
inc n endif wend |
Next mine |
sound |
Turns of the level selection beep |
for n=0 to m-1 for j=-20 to 20 step 20 for i=-1 to 1 |
Computes hint numbers surrounding each mine |
a=t+w(n)+j+i b=peek(a) |
Picks a cell and its content |
if b<9 |
Is it not a mine? |
poke a,b+1 endif next i next j next n |
Increase the counter for that cell |
exec _m 0 |
Prints the number of remaining blocks |
exec _c 0 |
Enables standard pointer |
timer k=0 |
Resets timer |
repeat |
GAME LOOP |
exec _q 0 |
Prints default smiley |
a=stick(0) |
Gets stick position |
if a<15 |
If the joystick was moved |
sound 0,200,10,6 |
Sound FX |
exec _a |
Disables attract mode |
x=x+(a&8=0)*(x<p)-(a&4=0)*(x>1) y=y+(a&2=0)*(y<q)-(a&1=0)*(y>1) |
Computes new coordinates within the limits |
z=x+y*20 v=peek(s+z)&$f |
Gets the selected cell and its current type |
exec _c 0 |
Moves the pointer to the new position |
sound |
Enough FX |
pause 5 |
Delay to avoid quick move |
elif strig(0)=0 and v>9 |
If the trigger was pressed over a covered cell |
exec _q 1 |
Displays a gessing smiley |
exec _a |
Disables attract mode |
o=0 exec _c 1 |
Selects the broom as the action pointer |
repeat if stick(0)<15 |
While the trigger is being pressed, check if the joystick was moved |
sound 0,9,10,8 |
Selection sound |
o=(o+1) mod 3 exec _c o+1 |
Next pointer: (0=broom, 1=flag, 2=clear) |
sound |
Silence... |
repeat |
Waits for the joystick to be released |
exec _t until stick(0)=15 endif |
Updates the onscreen timer |
exec _t |
Updates the onscreen timer |
until strig(0) |
Repeat until the trigger is released |
if o poke s+z,f(12-o) |
If the selected action is not to uncover, puts the corresponding tile (moves between flag and clear) |
else v=peek(t+z) poke s+z,f(v) |
The action is to reveal the cell content, copying it from the hidden area to the screen |
if v=9 |
If a mine was revealed |
exec _q 2 |
Display a sad Smiley |
for a = 30 to 150 sound 0,a,8,15-a/10 next a |
Plays and explosion FX |
else |
A hint was revealed |
sound 0,9,8,2 |
Plays a cleaning sound FX |
dec r |
Decreases the remaining cells counter |
if v=0 |
If the cell was clear, reveal all surrounding hints |
h=0 n=0 mset adr(g),480,0 |
Initializes queue and pointers |
g(n)=z |
Stores the current cell at the start of the queue |
while g(h) |
While the queue has pending cells |
for i=-1 to 1 for j=-20 to 20 step 20 |
Checks for unrevealed cells surrounding it |
a=g(h)+i+j |
Checks surrounding cell's content |
if peek(s+a)=f(10) dec r b=peek(t+a) poke s+a,f(b) |
If that cell is still unrevealed, copy its content from the hidden board |
if b=0 inc n g(n)=a endif endif next j next i |
If that cell was empty, add it to the queue |
inc h wend else |
Updates pointer for next queue item |
pause 2 endif |
Delay to allow cleaning FX to be listened |
exec _m 0 |
Displays the final number of remaining cells |
sound endif endif |
Silence |
exec _c 0 endif |
Recover the pointer shape |
exec _t |
Updates the onscreen timer |
l=v=9 |
Was a mine revealed? |
until r=0 or l |
Game is over when there are no more required blocks or a mine was revealed |
exec _m 5+l |
Prints DONE or FAIL message |
for n=0 to m-1 poke s+w(n),f(11-l-l) next n |
In case of success, completes missing flags, otherwise reveal position of each mine, replacing all the flags, except those not over a real mine |
if l=0 |
If last cell was not a mine |
exec _q 3 |
Displays a cool smiley |
for a=63 to 0 step -1 sound 0,a,10,a mod 8 pause next a endif |
Congratulations sound FX |
while strig(0) |
The game is over. Waits for the trigger to be pressed and then released |
poke 708,peek(20)&$3C*4+12 wend repeat until strig(0) |
Continuously changes the color of the game over message |
exec _a loop |
Disables the attract mode |
proc _t |
Updates the timer |
if k<999 |
If the timer has not reached its limit, update it |
pause |
Waits for VBLANK to avoid glitches with system timer |
k=(time/2)&$7FFF/25 |
Converts jiffies into seconds |
u$="00" u$=+str$(k) u3$=u$[peek(adr(u$))-2] |
Converts a decimal number into a 3 digits string |
position 16,0 print #6,color(32) u3$ endif endproc |
Prints the updated timer |
proc _c a mset c,128,0 pmhpos 2,p2+x*8+2 move adr(d)+102+a*11,c+q2+y*8-1,11 endproc |
Updates pointer position and shape |
proc _a poke 77,0 endproc |
Disables attract mode |
proc _m a position 1,0 if a print #6, "EASYCOOLHARDWAITDONEFAIL"[a*4-3,4] else print #6,rtab(5) r endif endproc |
Prints message or required blocks |
proc _n e |
Gets level config |
move adr(d)+30+e*20,adr(s),20 endproc |
This MOVE sets up 10 integer variables in a single statement, plus the level as an argument |
proc _q a move adr(d)+7*a+1,pmadr(3)+15,8 endproc |
Updates Smiley |
Return to my 10-liners page.
© 2023 by Víctor Parada - 2023-03-14 (updated: 2023-04-03)