Minesweeper game: Difference between revisions
m
→{{header|EasyLang}}
(38 intermediate revisions by 10 users not shown) | |||
Line 25:
=={{header|Ada}}==
<
with Ada.Text_IO;
Line 392:
Put (The_Grid);
end;
end Minesweeper;</
=={{header|AutoHotkey}}==
Gui clone w/o graphic files but with cheats.
<
; (c) Dec 28, 2008 by derRaphael
; Licensed under the Terms of EUPL 1.0
Line 951:
- Add middle-button shortcut
- Re-license as GPL 1.3
*/</
=={{header|
===
==== Mouse version ====
[[File:Minesweeper game BASIC-256 won.png|thumb|right|Game won]]
[[File:Minesweeper game BASIC-256 lost.png|thumb|right|Game lost (green mines are those that were flagged before explosion)]]
<
fastgraphics
Line 1,066 ⟶ 1,067:
end if
if f[ai,aj]&2 then over = -1
return</
=={{header|BCPL}}==
<syntaxhighlight lang="bcpl">get "libhdr"
static $( randstate = 0 $)
manifest $(
nummask = #XF
flagmask = #X10
bombmask = #X20
revealed = #X40
MAXINT = (~0)>>1
MININT = ~MAXINT
$)
let min(x,y) = x<y -> x, y
and max(x,y) = x>y -> x, y
let rand() = valof
$( randstate := random(randstate)
resultis randstate >> 7
$)
let randto(x) = valof
$( let r, mask = ?, 1
while mask<x do mask := (mask << 1) | 1
r := rand() & mask repeatuntil r < x
resultis r
$)
// Place a bomb on the field (if not already a bomb)
let placebomb(field, xsize, ysize, x, y) =
(field!(y*xsize+x) & bombmask) ~= 0 -> false,
valof
$( for xa = max(x-1, 0) to min(x+1, xsize-1)
for ya = max(y-1, 0) to min(y+1, ysize-1)
$( let loc = ya*xsize+xa
let n = field!loc & nummask
field!loc := (field!loc & ~nummask) | (n + 1)
$)
field!(y*xsize+x) := field!(y*xsize+x) | bombmask
resultis true
$)
// Populate the field with N bombs
let populate(field, xsize, ysize, nbombs) be
$( for i=0 to xsize*ysize-1 do field!i := 0
while nbombs > 0
$( let x, y = randto(xsize), randto(ysize)
if placebomb(field, xsize, ysize, x, y) then
nbombs := nbombs - 1
$)
$)
// Reveal field (X,Y) - returns true if stepped on a bomb
let reveal(field, xsize, ysize, x, y) =
(field!(y*xsize+x) & bombmask) ~= 0 -> true,
valof
$( let loc = y*xsize+x
field!loc := field!loc | revealed
if (field!loc & nummask) = 0 then
for xa = max(x-1, 0) to min(x+1, xsize-1)
for ya = max(y-1, 0) to min(y+1, ysize-1)
if (field!(ya*xsize+xa) &
(bombmask | flagmask | revealed)) = 0 do
reveal(field, xsize, ysize, xa, ya)
resultis false
$)
// Toggle flag
let toggleflag(field, xsize, ysize, x, y) be
$( let loc = y*xsize+x
field!loc := field!loc neqv flagmask
$)
// Show the field. Returns true if won.
let showfield(field, xsize, ysize, kaboom) = valof
$( let bombs, flags, hidden, found = 0, 0, 0, 0
for i=0 to xsize*ysize-1
$( if (field!i & revealed) = 0 do hidden := hidden + 1
unless (field!i & bombmask) = 0 do bombs := bombs + 1
unless (field!i & flagmask) = 0 do flags := flags + 1
if (field!i & bombmask) ~= 0 & (field!i & flagmask) ~= 0
do found := found + 1
$)
writef("Bombs: %N - Flagged: %N - Hidden: %N*N", bombs, flags, hidden)
wrch('+')
for x=0 to xsize-1 do wrch('-')
writes("+*N")
for y=0 to ysize-1
$( wrch('|')
for x=0 to xsize-1
$( let loc = y*xsize+x
test kaboom & (field!loc & bombmask) ~= 0 do
wrch('**')
or test (field!loc & (flagmask | revealed)) = flagmask do
wrch('?')
or test (field!loc & revealed) = 0 do
wrch('.')
or test (field!loc & nummask) = 0 do
wrch(' ')
or
wrch('0' + (field!loc & nummask))
$)
writes("|*N")
$)
wrch('+')
for x=0 to xsize-1 do wrch('-')
writes("+*N")
resultis found = bombs
$)
// Ask a question, get number
let ask(q, min, max) = valof
$( let n = ?
$( writes(q)
n := readn()
$) repeatuntil min <= n <= max
resultis n
$)
// Read string
let reads(v) = valof
$( let ch = ?
v%0 := 0
$( ch := rdch()
if ch = endstreamch then resultis false
v%0 := v%0 + 1
v%(v%0) := ch
$) repeatuntil ch = '*N'
resultis true
$)
// Play game given field
let play(field, xsize, ysize) be
$( let x = ?
let y = ?
let ans = vec 80
if showfield(field, xsize, ysize, false)
$( writes("*NYou win!*N")
finish
$)
$( writes("*NR)eveal, F)lag, Q)uit? ")
unless reads(ans) finish
unless ans%0 = 2 & ans%2='*N' loop
ans%1 := ans%1 | 32
if ans%1 = 'q' then finish
$) repeatuntil ans%1='r' | ans%1='f'
y := ask("Row? ", 1, ysize)-1
x := ask("Column? ", 1, xsize)-1
switchon ans%1 into
$( case 'r':
unless (field!(y*xsize+x) & flagmask) = 0
$( writes("*NError: that field is flagged, unflag it first.*N")
endcase
$)
unless (field!(y*xsize+x) & revealed) = 0
$( writes("*NError: that field is already revealed.*N")
endcase
$)
if reveal(field, xsize, ysize, x, y)
$( writes("*N K A B O O M *N*N")
showfield(field, xsize, ysize, true)
finish
$)
endcase
case 'f':
test (field!(y*xsize+x) & revealed) = 0
do toggleflag(field, xsize, ysize, x, y)
or writes("*NError: that field is already revealed.*N")
endcase
$)
wrch('*N')
$) repeat
let start() be
$( let field, xsize, ysize, bombs = ?, ?, ?, ?
writes("Minesweeper*N-----------*N*N")
randstate := ask("Random seed? ", MININT, MAXINT)
xsize := ask("Width (4-64)? ", 4, 64)
ysize := ask("Height (4-22)? ", 4, 22)
// 10 to 20% bombs
bombs := muldiv(xsize,ysize,10) + randto(muldiv(xsize,ysize,10)+1)
field := getvec(xsize*ysize)
populate(field, xsize, ysize, bombs)
play(field, xsize, ysize)
$)</syntaxhighlight>
{{out}}
<pre style='height:70ex;'>Minesweeper
-----------
Random seed? 50
Width (4-64)? 6
Height (4-22)? 4
Bombs: 4 - Flagged: 0 - Hidden: 24
+------+
|......|
|......|
|......|
|......|
+------+
R)eveal, F)lag, Q)uit? r
Row? 1
Column? 1
Bombs: 4 - Flagged: 0 - Hidden: 16
+------+
| 1....|
| 2....|
| 2....|
| 1....|
+------+
R)eveal, F)lag, Q)uit? r
Row? 4
Column? 6
Bombs: 4 - Flagged: 0 - Hidden: 10
+------+
| 1....|
| 2....|
| 2.311|
| 1.1 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 1
Column? 3
Bombs: 4 - Flagged: 0 - Hidden: 9
+------+
| 11...|
| 2....|
| 2.311|
| 1.1 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 4
Column? 3
Bombs: 4 - Flagged: 0 - Hidden: 8
+------+
| 11...|
| 2....|
| 2.311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? f
Row? 2
Column? 3
Bombs: 4 - Flagged: 1 - Hidden: 8
+------+
| 11...|
| 2?...|
| 2.311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? f
Row? 3
Column? 3
Bombs: 4 - Flagged: 2 - Hidden: 8
+------+
| 11...|
| 2?...|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 1
Column? 4
Bombs: 4 - Flagged: 2 - Hidden: 7
+------+
| 112..|
| 2?...|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 2
Column? 4
Bombs: 4 - Flagged: 2 - Hidden: 6
+------+
| 112..|
| 2?3..|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? f
Row? 2
Column? 5
Bombs: 4 - Flagged: 3 - Hidden: 6
+------+
| 112..|
| 2?3?.|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 2
Column? 6
Bombs: 4 - Flagged: 3 - Hidden: 5
+------+
| 112..|
| 2?3?2|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? r
Row? 1
Column? 5
Bombs: 4 - Flagged: 3 - Hidden: 4
+------+
| 1122.|
| 2?3?2|
| 2?311|
| 111 |
+------+
R)eveal, F)lag, Q)uit? f
Row? 1
Column? 6
Bombs: 4 - Flagged: 4 - Hidden: 4
+------+
| 1122?|
| 2?3?2|
| 2?311|
| 111 |
+------+
You win!</pre>
=={{header|C}}==
Line 1,073 ⟶ 1,426:
{{libheader|curses}}
<
Unix build:
Line 1,488 ⟶ 1,841:
# endif
return 0;
}</
===Mouse version===
Using ncurses and mouse input. Compiled with <code>gcc -lncurses -Wall -std=c99</code>. Run as <code>a.out [height] [width]</code>; your terminal needs to support mouse input, and at least 2*width + 2 columns wide. Left button clears a cell, right button toggles mine mark, middle button on a cleared cell clears all neighboring cells (or blow up if there are unmarked mines). When mine count drops to zero, click "claim victory" to win the game or blow up.
<
#include <locale.h>
#include <stdlib.h>
Line 1,675 ⟶ 2,028:
endwin();
return 0;
}</
=={{header|C sharp|C#}}==
The following solution implements the required task providing GUI (Windows Forms).
<
using System.Drawing;
using System.Windows.Forms;
Line 1,945 ⟶ 2,298:
Application.Run(new MineSweepForm());
}
}</
=={{header|C++}}==
This solution implements the required task and one more command: unknown. It is represented by a '?' that's why the flag in this solution is represented as a '!'
<
#include <iostream>
#include <string>
Line 2,173 ⟶ 2,526:
return system( "pause" );
}
</syntaxhighlight>
{{out}}
<pre>
Line 2,221 ⟶ 2,574:
=={{header|Ceylon}}==
Be sure to import ceylon.random in your module.ceylon file.
<
DefaultRandom
Line 2,354 ⟶ 2,707:
}
}
}</
=={{header|Clojure}}==
<
(->> (repeatedly #(rand-nth coll))
distinct
Line 2,434 ⟶ 2,787:
(let [[cmd & xy] (.split #" " (read-line))
[x y] (map #(Integer. %) xy)]
(recur ((if (= cmd "mark") mark open) board x y)))))))</
=={{header|Common Lisp}}==
<
((mines :initform (make-hash-table :test #'equal))
(width :initarg :width)
Line 2,523 ⟶ 2,876:
(return-from play-game T))))))
(play-game)</
=={{header|D}}==
Line 2,529 ⟶ 2,882:
=={{header|EasyLang}}==
[https://easylang.dev/apps/mine-sweeper.html Run it]
<syntaxhighlight>
len cell[] 56
len cnt[] 56
len flag[] 56
#
subr initvars
state = 0
ticks = 0
indx = -1
no_time = 0
.
func getind r c
ind = -1
if r >= 0 and r <= 6 and c >= 0 and c <= 7
ind = r * 8 + c + 1
.
return ind
.
y = r * 12 + 2.5
if
move x + 3 y + 3
elif
line x + 2 y + 7
elif
.
move x + 5 y + 5
circle 3
line x + 8 y + 9
.
.
if ind <> -1 and cell[ind] = 0
flag[ind] = 0
color 686
if cnt[ind]
ind -= 1
r0 = ind div 8
c0 = ind mod 8
for r = r0 - 1 to r0 + 1
for c = c0 - 1 to c0 + 1
if r <> r0 or c <> c0
open getind r c
.
.
.
.
.
.
for ind to 56
if cell[ind] = 1
color 686
if m = -1
color 353
draw_cell ind m
.
.
.
color 000
move
text
.
for i to 56
.
if nc = 8
outp 484 "Well done"
show_mines -1
state = 1
.
.
if cell[ind] < 2 and flag[ind] = 0
if cell[ind] = 1
show_mines -1
color
draw_cell ind -2
outp 844 "B O O M !"
upd_info
.
background 676
proc start . .
clear
for ind to 56
cnt[ind] = 0
cell[ind] = 0
flag[ind] = 0
draw_cell ind 0
.
n = 8
while n > 0
c = randint 8 - 1
r = randint 7 - 1
ind = r * 8 + c + 1
if cell[ind] = 0
n -= 1
cell[ind] = 1
for rx = r - 1 to r + 1
for cx = c - 1 to c + 1
ind = getind rx cx
if ind > -1
cnt[ind] += 1
.
.
.
.
.
initvars
outp 464 ""
textsize 4
move 5 93
text "Minesweeper - 8 mines"
move 5 88.2
text "Long-press for flagging"
textsize 6
timer 0
.
on mouse_down
if state = 0
move 64.5 87
rect 33 11
.
indx = getind ((mouse_y - 2) div 12) ((mouse_x - 2) div 12)
ticks0 = ticks
elif state = 3
start
.
.
on mouse_up
if state = 0 and indx <> -1
.
indx = -1
.
on timer
if state = 1
state = 2
timer 1
elif state = 2
state = 3
elif no_time = 0 and ticks > 3000
state = 2
timer 1
else
if indx >
if cell[indx] < 2
color 353
flag[indx] = 1 - flag[indx]
opt = 0
if flag[indx] = 1
opt = -3
.
.
indx = -1
.
move 64.5 87
color
color 000
move 66 90
text "Time:" & 300 - ticks / 10
.
.
</syntaxhighlight>
=={{header|FreeBASIC}}==
===Mouse version===
Based On BeBeliOuS's original code (www.bebelious.fr)
<syntaxhighlight lang="vb">'--- Declaration of global variables ---
Type mina
Dim mina As Byte
Dim flag As Byte
Dim ok As Byte
Dim numero As Byte
End Type
Dim Shared As Integer size = 16, NX = 20, NY = 20
Dim Shared As Double mina = 0.10
Dim Shared tablero(NX+1,NY+1) As mina
Dim Shared As Integer GameOver, ddx, ddy, kolor, nbDESCANSO
Dim Shared As Double temps
Dim As String tecla
'--- SUBroutines and FUNCtions ---
Sub VerTodo
Dim As Integer x, y
For x = 1 To NX
For y = 1 To NY
With tablero(x,y)
If .mina = 1 Or .flag > 0 Then .ok = 1
End With
Next y
Next x
End Sub
Sub ShowGrid
Dim As Integer x, y
Line(ddx-1,ddy-1)-(1+ddx+size*NX,1+ddy+size*NY),&hFF0000,B
For x = 1 To NX
For y = 1 To NY
With tablero(x,y)
'Si la casilla no se hace click
If .ok = 0 Then
Line(ddx+x*size,ddy+y*size)-(ddx+(x-1)*size,ddy+(y-1)*size),&h888888,BF
Line(ddx+x*size,ddy+y*size)-(ddx+(x-1)*size,ddy+(y-1)*size),&h444444,B
'bandera verde
If .flag = 1 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h00FF00,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h0
End If
'bandera azul
If .flag = 2 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h0000FF,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h0
End If
'Si se hace clic
Else
If .mina = 0 Then
If .numero > 0 Then
Select Case .numero
Case 1: kolor = &h3333FF
Case 2: kolor = &h33FF33
Case 3: kolor = &hFF3333
Case 4: kolor = &hFFFF33
Case 5: kolor = &h33FFFF
Case 6: kolor = &hFF33FF
Case 7: kolor = &h999999
Case 8: kolor = &hFFFFFF
End Select
Draw String(ddx+x*size-size/1.5,ddy+y*size-size/1.5),Str(.numero),kolor
End If
If GameOver = 1 Then
'Si no hay Mina y una bandera verde >> rojo completo
If .flag = 1 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h330000,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h555555
End If
'Si no hay Mina y una bandera azul >> azul oscuro
If .flag = 2 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h000033,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h555555
End If
End If
Else
'Si hay una Mina sin bandera >> rojo
If .flag = 0 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&hFF0000,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&hFFFF00
Else
'Si hay una Mina con bandera verde >>> verde
If .flag = 1 Then
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h00FF00,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&hFFFF00
End If
If .flag = 2 Then
'Si hay una Mina con bandera azul >>>> verde oscuro
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&h003300,,,,F
Circle (ddx+x*size-size/2,ddy+y*size-size/2),size/4,&hFFFF00
End If
End If
End If
End If
End With
Next y
Next x
End Sub
Sub Calcul
Dim As Integer x, y
For x = 1 To NX
For y = 1 To NY
tablero(x,y).numero = _
Iif(tablero(x+1,y).mina=1,1,0) + Iif(tablero(x-1,y).mina=1,1,0) + _
Iif(tablero(x,y+1).mina=1,1,0) + Iif(tablero(x,y-1).mina=1,1,0) + _
Iif(tablero(x+1,y-1).mina=1,1,0) + Iif(tablero(x+1,y+1).mina=1,1,0) + _
Iif(tablero(x-1,y-1).mina=1,1,0) + Iif(tablero(x-1,y+1).mina=1,1,0)
Next y
Next x
End Sub
Sub Inicio
size = 20
If NX > 720/size Then size = 720/NX
If NY > 520/size Then size = 520/NY
Redim tablero(NX+1,NY+1) As mina
Dim As Integer x, y
For x = 1 To NX
For y = 1 To NY
With tablero(x,y)
.mina = Iif(Rnd > (1-mina), 1, 0)
.ok = 0
.flag = 0
.numero = 0
End With
Next y
Next x
ddx = (((800/size)-NX)/2)*size
ddy = (((600/size)-NY)/2)*size
Calcul
End Sub
Function isGameOver As Integer
Dim As Integer x, y, nbMINA, nbOK, nbAZUL, nbVERT
For x = 1 To NX
For y = 1 To NY
If tablero(x,y).ok = 1 And tablero(X,y).mina = 1 Then
Return -1
End If
If tablero(x,y).ok = 0 And tablero(x,y).flag = 1 And tablero(X,y).mina = 1 Then
nbOK += 1
End If
If tablero(X,y).mina = 1 Then
nbMINA + =1
End If
If tablero(X,y).flag = 2 Then
nbAZUL += 1
'End If
Elseif tablero(X,y).flag = 1 Then
nbVERT += 1
End If
Next y
Next x
If nbMINA = nbOK And nbAZUL = 0 Then Return 1
nbDESCANSO = nbMINA - nbVERT
End Function
Sub ClicRecursivo(ZX As Integer, ZY As Integer)
If tablero(ZX,ZY).ok = 1 Then Exit Sub
If tablero(ZX,ZY).flag > 0 Then Exit Sub
'CLICK
tablero(ZX,ZY).ok = 1
If tablero(ZX,ZY).mina = 1 Then Exit Sub
If tablero(ZX,ZY).numero > 0 Then Exit Sub
If ZX > 0 And ZX <= NX And ZY > 0 And ZY <= NY Then
ClicRecursivo(ZX+1,ZY)
ClicRecursivo(ZX-1,ZY)
ClicRecursivo(ZX,ZY+1)
ClicRecursivo(ZX,ZY-1)
ClicRecursivo(ZX+1,ZY+1)
ClicRecursivo(ZX+1,ZY-1)
ClicRecursivo(ZX-1,ZY+1)
ClicRecursivo(ZX-1,ZY-1)
End If
End Sub
'--- Main Program ---
Screenres 800,600,24
Windowtitle"Minesweeper game"
Randomize Timer
Cls
Dim As Integer mx, my, mb, fmb, ZX, ZY, r, tt
Dim As Double t
Inicio
GameOver = 1
Do
tecla = Inkey
If GameOver = 1 Then
Select Case Ucase(tecla)
Case Chr(Asc("X"))
NX += 5
If NX > 80 Then NX = 10
Inicio
Case Chr(Asc("Y"))
NY += 5
If NY > 60 Then NY = 10
Inicio
Case Chr(Asc("M"))
mina += 0.01
If mina > 0.26 Then mina = 0.05
Inicio
Case Chr(Asc("S"))
Inicio
GameOver = 0
temps = Timer
End Select
End If
Getmouse mx,my,,mb
mx -= ddx-size
my -= ddy-size
ZX = (MX-size/2)/size
ZY = (MY-size/2)/size
If GameOver = 0 And zx > 0 And zx <= nx And zy > 0 And zy <= ny Then
If MB=1 And fmb=0 Then ClicRecursivo(ZX,ZY)
If MB=2 And fmb=0 Then
tablero(ZX,ZY).flag += 1
If tablero(ZX,ZY).flag > 2 Then tablero(ZX,ZY).flag = 0
End If
fmb = mb
End If
r = isGameOver
If r = -1 And GameOver = 0 Then
VerTodo
GameOver = 1
End If
If r = 1 And GameOver = 0 Then GameOver = 1
If GameOver = 0 Then tt = Timer-temps
Screenlock
Cls
ShowGrid
If GameOver = 0 Then
Draw String (210,4), "X:" & NX & " Y:" & NY & " MINA:" & Int(mina*100) & "% TIMER : " & Int(TT) & " S REMAINING:" & nbDESCANSO,&hFFFF00
Else
Draw String (260,4), "PRESS: 'S' TO START X,Y,M TO SIZE" ,&hFFFF00
Draw String (330,17), "X:" & NX & " Y:" & NY & " MINA:" & Int(mina*100) & "%" ,&hFFFF00
If r = 1 Then
t += 0.01
If t > 1000 Then t = 0
Draw String (320,280+Cos(t)*100),"!! CONGRATULATION !!",&hFFFF00
End If
End If
Screenunlock
Loop Until tecla = Chr(27)
Bsave "Minesweeper.bmp",0
End</syntaxhighlight>
{{out}}
[https://www.dropbox.com/s/vj7cjdz7dp3wtk3/MinesweeperFB.bmp?dl=0 Minesweeper FreeBasic image]
=={{header|Go}}==
{{trans|PureBasic}}
... though altered somewhat.
<
import (
Line 2,999 ⟶ 3,624:
}
}
}</
{{out}}
Line 3,112 ⟶ 3,737:
=={{header|Icon}} and {{header|Unicon}}==
The following solution implements the required task and additionally error checking, and several additional commands.
<
record minefield(mask,grid,rows,cols,mines,density,marked)
Line 3,292 ⟶ 3,917:
MF.mask := m
write(tag) & showgrid()
end</
Sample output:
Line 3,324 ⟶ 3,949:
6 : ....
0 marked mines and 9 mines left to be marked.</pre>
=={{header|Haskell}}==
Game Module
<syntaxhighlight lang="haskell">{-# LANGUAGE TemplateHaskell #-}
module MineSweeper
( Board
, Cell(..)
, CellState(..)
, Pos
-- lenses / prisms
, pos
, coveredLens
, coveredFlaggedLens
, coveredMinedLens
, xCoordLens
, yCoordLens
-- Functions
, emptyBoard
, groupedByRows
, displayCell
, isLoss
, isWin
, exposeMines
, openCell
, flagCell
, mineBoard
, totalRows
, totalCols )
where
import Control.Lens ((%~), (&), (.~), (^.), (^?), Lens', Traversal', _1, _2,
anyOf, filtered, folded, lengthOf, makeLenses, makePrisms,
preview, to, view)
import Data.List (find, groupBy, nub, delete, sortBy)
import Data.Maybe (isJust)
import System.Random (getStdGen, getStdRandom, randomR, randomRs)
type Pos = (Int, Int)
type Board = [Cell]
data CellState = Covered { _mined :: Bool, _flagged :: Bool }
| UnCovered { _mined :: Bool }
deriving (Show, Eq)
data Cell = Cell
{ _pos :: Pos
, _state :: CellState
, _cellId :: Int
, _adjacentMines :: Int }
deriving (Show, Eq)
makePrisms ''CellState
makeLenses ''CellState
makeLenses ''Cell
-- Re-useable lens.
coveredLens :: Traversal' Cell (Bool, Bool) --'
coveredLens = state . _Covered
coveredMinedLens, coveredFlaggedLens, unCoveredLens :: Traversal' Cell Bool --'
coveredMinedLens = coveredLens . _1
coveredFlaggedLens = coveredLens . _2
unCoveredLens = state . _UnCovered
xCoordLens, yCoordLens :: Lens' Cell Int --'
xCoordLens = pos . _1
yCoordLens = pos . _2
-- Adjust row and column size to your preference.
totalRows, totalCols :: Int
totalRows = 4
totalCols = 6
emptyBoard :: Board
emptyBoard = (\(n, p) -> Cell { _pos = p
, _state = Covered False False
, _adjacentMines = 0
, _cellId = n }) <$> zip [1..] positions
where
positions = (,) <$> [1..totalCols] <*> [1..totalRows]
updateCell :: Cell -> Board -> Board
updateCell cell = fmap (\c -> if cell ^. cellId == c ^. cellId then cell else c)
updateBoard :: Board -> [Cell] -> Board
updateBoard = foldr updateCell
okToOpen :: [Cell] -> [Cell]
okToOpen = filter (\c -> c ^? coveredLens == Just (False, False))
openUnMined :: Cell -> Cell
openUnMined = state .~ UnCovered False
openCell :: Pos -> Board -> Board
openCell p b = f $ find (\c -> c ^. pos == p) b
where
f (Just c)
| c ^? coveredFlaggedLens == Just True = b
| c ^? coveredMinedLens == Just True = updateCell
(c & state .~ UnCovered True)
b
| isCovered c = if c ^. adjacentMines == 0 && not (isFirstMove b)
then updateCell (openUnMined c) $ expandEmptyCells b c
else updateCell (openUnMined c) b
| otherwise = b
f Nothing = b
isCovered = isJust . preview coveredLens
expandEmptyCells :: Board -> Cell -> Board
expandEmptyCells board cell
| null openedCells = board
| otherwise = foldr (flip expandEmptyCells) updatedBoard (zeroAdjacent openedCells)
where
findMore _ [] = []
findMore exclude (c : xs)
| c `elem` exclude = findMore exclude xs
| c ^. adjacentMines == 0 = c : adjacent c <>
findMore (c : exclude <> adjacent c) xs
| otherwise = c : findMore (c : exclude) xs
adjacent = okToOpen . flip adjacentCells board
openedCells = openUnMined <$> nub (findMore [cell] (adjacent cell))
zeroAdjacent = filter (view (adjacentMines . to (== 0)))
updatedBoard = updateBoard board openedCells
flagCell :: Pos -> Board -> Board
flagCell p board = case find ((== p) . view pos) board of
Just c -> updateCell (c & state . flagged %~ not) board
Nothing -> board
adjacentCells :: Cell -> Board -> [Cell]
adjacentCells Cell {_pos = c@(x1, y1)} = filter (\c -> c ^. pos `elem` positions)
where
f n = [pred n, n, succ n]
positions = delete c $ [(x, y) | x <- f x1, x > 0, y <- f y1, y > 0]
isLoss, isWin, allUnMinedOpen, allMinesFlagged, isFirstMove :: Board -> Bool
isLoss = anyOf (traverse . unCoveredLens) (== True)
isWin b = allUnMinedOpen b || allMinesFlagged b
allUnMinedOpen = (== 0) . lengthOf (traverse . coveredMinedLens . filtered (== False))
allMinesFlagged b = minedCount b == flaggedMineCount b
where
minedCount = lengthOf (traverse . coveredMinedLens . filtered (== True))
flaggedMineCount = lengthOf (traverse . coveredLens . filtered (== (True, True)))
isFirstMove = (== totalCols * totalRows) . lengthOf (folded . coveredFlaggedLens . filtered (== False))
groupedByRows :: Board -> [Board]
groupedByRows = groupBy (\c1 c2 -> yAxis c1 == yAxis c2)
. sortBy (\c1 c2 -> yAxis c1 `compare` yAxis c2)
where
yAxis = view yCoordLens
displayCell :: Cell -> String
displayCell c
| c ^? unCoveredLens == Just True = "X"
| c ^? coveredFlaggedLens == Just True = "?"
| c ^? (unCoveredLens . to not) == Just True =
if c ^. adjacentMines > 0 then show $ c ^. adjacentMines else "▢"
| otherwise = "."
exposeMines :: Board -> Board
exposeMines = fmap (\c -> c & state . filtered (\s -> s ^? _Covered . _1 == Just True) .~ UnCovered True)
updateMineCount :: Board -> Board
updateMineCount b = go b
where
go [] = []
go (x : xs) = (x & adjacentMines .~ totalAdjacentMines b) : go xs
where
totalAdjacentMines =
foldr (\c acc -> if c ^. (state . mined) then succ acc else acc) 0 . adjacentCells x
-- IO
mineBoard :: Pos -> Board -> IO Board
mineBoard p board = do
totalMines <- randomMinedCount
minedBoard totalMines >>= \mb -> pure $ updateMineCount mb
where
mines n = take n <$> randomCellIds
minedBoard n = (\m ->
fmap (\c -> if c ^. cellId `elem` m then c & state . mined .~ True else c)
board) . filter (\c -> openedCell ^. cellId /= c)
<$> mines n
openedCell = head $ filter (\c -> c ^. pos == p) board
randomCellIds :: IO [Int]
randomCellIds = randomRs (1, totalCols * totalRows) <$> getStdGen
randomMinedCount :: IO Int
randomMinedCount = getStdRandom $ randomR (minMinedCells, maxMinedCells)
where
maxMinedCells = floor $ realToFrac (totalCols * totalRows) * 0.2
minMinedCells = floor $ realToFrac (totalCols * totalRows) * 0.1</syntaxhighlight>
Text Play
<syntaxhighlight lang="haskell">module Main
where
import MineSweeper (Board, Cell (..), Pos, coveredLens, coveredMinedLens,
coveredFlaggedLens, xCoordLens, yCoordLens, pos, emptyBoard,
groupedByRows, displayCell, isLoss, isWin, exposeMines,
openCell, flagCell, mineBoard, totalRows, totalCols)
import Text.Printf (printf)
import Text.Read (readMaybe)
import System.IO (hSetBuffering, stdout, BufferMode(..))
import Control.Monad (join, guard)
import Control.Lens (lengthOf, filtered, view, preview)
data Command = Open Pos | Flag Pos | Invalid
parseInput :: String -> Command
parseInput s | length input /= 3 = Invalid
| otherwise = maybe Invalid command parsedPos
where
input = words s
parsedPos = do
x <- readMaybe (input !! 1)
y <- readMaybe (input !! 2)
guard (x <= totalCols && y <= totalRows)
pure (x, y)
command p = case head input of
"o" -> Open p
"f" -> Flag p
_ -> Invalid
cheat :: Board -> String
cheat = show . fmap (view pos) . filter ((== Just True) . preview coveredMinedLens)
totalMines, totalFlagged, totalCovered :: Board -> Int
totalMines = lengthOf (traverse . coveredMinedLens . filtered (==True))
totalFlagged = lengthOf (traverse . coveredFlaggedLens . filtered (==True))
totalCovered = lengthOf (traverse . coveredLens)
-- IO
drawBoard :: Board -> IO ()
drawBoard b = do
-- printf "Cheat: %s\n" $ cheat b
printf " Mines: %d Covered: %d Flagged: %d\n\n"
(totalMines b) (totalCovered b) (totalFlagged b)
printf "%3s" ""
mapM_ (printf "%3d") $ view xCoordLens <$> head rows
printf "\n"
mapM_ (\row -> do
printf "%3d" $ yCoord row
mapM_ (printf "%3s" . displayCell) row
printf "\n" ) rows
where
rows = groupedByRows b
yCoord = view yCoordLens . head
gameLoop :: Board -> IO ()
gameLoop b
| isLoss b = drawBoard (exposeMines b) >> printf "\nYou Lose.\n"
| isWin b = drawBoard b >> printf "\nYou Win.\n"
| otherwise = do
drawBoard b
putStr "\nPick a cell: "
c <- getLine
case parseInput c of
Open p -> gameLoop $ openCell p b
Flag p -> gameLoop $ flagCell p b
Invalid -> gameLoop b
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
drawBoard emptyBoard
putStr "\nPick a cell: "
c <- getLine
case parseInput c of
Open p -> join $ startGame p emptyBoard
_ -> main
where
startGame p b = gameLoop . openCell p <$> mineBoard p b</syntaxhighlight>
{{out}}
<pre> Mines: 0 Covered: 24 Flagged: 0
1 2 3 4 5 6
1 . . . . . .
2 . . . . . .
3 . . . . . .
4 . . . . . .
Pick a cell: o 1 1
Mines: 2 Covered: 23 Flagged: 0
1 2 3 4 5 6
1 ▢ . . . . .
2 . . . . . .
3 . . . . . .
4 . . . . . .
Pick a cell: f 1 2
Mines: 2 Covered: 23 Flagged: 1
1 2 3 4 5 6
1 ▢ . . . . .
2 ? . . . . .
3 . . . . . .
4 . . . . . .
Pick a cell: f 6 6
Mines: 2 Covered: 23 Flagged: 1
1 2 3 4 5 6
1 ▢ . . . . .
2 ? . . . . .
3 . . . . . .
4 . . . . . .
Pick a cell: f 6 4
Mines: 2 Covered: 23 Flagged: 2
1 2 3 4 5 6
1 ▢ . . . . .
2 ? . . . . .
3 . . . . . .
4 . . . . . ?
Pick a cell: f 1 2
Mines: 2 Covered: 23 Flagged: 1
1 2 3 4 5 6
1 ▢ . . . . .
2 . . . . . .
3 . . . . . .
4 . . . . . ?
Pick a cell: o 1 2
Mines: 2 Covered: 14 Flagged: 1
1 2 3 4 5 6
1 ▢ 1 . . . .
2 ▢ 1 . . . .
3 ▢ 1 2 . . .
4 ▢ ▢ 1 . . ?
Pick a cell: o 6 1
Mines: 2 Covered: 4 Flagged: 1
1 2 3 4 5 6
1 ▢ 1 . 1 ▢ ▢
2 ▢ 1 . 1 ▢ ▢
3 ▢ 1 2 2 1 ▢
4 ▢ ▢ 1 . 1 ?
Pick a cell: f 4 4
Mines: 2 Covered: 4 Flagged: 2
1 2 3 4 5 6
1 ▢ 1 . 1 ▢ ▢
2 ▢ 1 . 1 ▢ ▢
3 ▢ 1 2 2 1 ▢
4 ▢ ▢ 1 ? 1 ?
Pick a cell: o 3 2
Mines: 0 Covered: 2 Flagged: 1
1 2 3 4 5 6
1 ▢ 1 . 1 ▢ ▢
2 ▢ 1 X 1 ▢ ▢
3 ▢ 1 2 2 1 ▢
4 ▢ ▢ 1 X 1 ?
You Lose.</pre>
=={{header|J}}==
'''Solution'''
<
NB. =========================================================
NB. Game engine
Line 3,429 ⟶ 4,422:
- start a new game using the command:
fld=: MineSweeper <num columns> <num rows>
)</
'''Example Usage'''
<
fld=: Minesweeper 6 4
=== MineSweeper ===
Line 3,530 ⟶ 4,523:
│***1 1*12*312*1 1*1│
└────────────────────┘
You won! Try again?</
=={{header|Java}}==
<
--------------------------------- START of Main.java ---------------------------------
Line 3,557 ⟶ 4,550:
int input;
boolean loopEnd;
do {
Line 3,594 ⟶ 4,586:
{
loopEnd = false;
System.out.println("Input is invalid.");
return 0;
Line 3,626 ⟶ 4,618:
public class Cell{ //This entire class is quite self-explanatory. All we're really doing is setting booleans.
Line 3,886 ⟶ 4,876:
mine.select(x, y);
}
//Right click - marks mine
if (e.getButton() == 3)
Line 4,181 ⟶ 5,163:
</syntaxhighlight>
=={{header|Julia}}==
Save the following code to a file (for example "minesweeper.jl") and include it in the Julia REPL to play the game.
<syntaxhighlight lang="julia">
# Minesweeper:
Line 4,304 ⟶ 5,286:
play()
</syntaxhighlight>
typical game:
<syntaxhighlight lang="julia">
julia> include("minesweeper.jl")
Line 4,391 ⟶ 5,373:
ERROR: You closed the game.
</syntaxhighlight>
=={{header|Locomotive Basic}}==
<
20 defint a-z
30 boardx=6:boardy=4
Line 4,508 ⟶ 5,490:
1120 next
1130 if mm=nmines then locate 5,22:print "Congratulations, you've won!":end
1140 return</
=={{header|M2000 Interpreter}}==
Line 4,522 ⟶ 5,504:
<syntaxhighlight lang="m2000 interpreter">
Module Minesweeper {
Font "Arial Black"
Line 4,790 ⟶ 5,772:
}
Minesweeper
</syntaxhighlight>
=={{header|Mathematica}}/{{header|Wolfram Language}}==
===Mouse version===
<
checkwin},
reset[] :=
Line 4,839 ⟶ 5,821:
minecount--, "?", grid[[i, j, 2]] = "."; minecount++];
checkwin[]]}]], {i, 1, m}, {j, 1, n}], Spacings -> {0, 0}],
Dynamic[win]}]</
=={{header|MATLAB}}==
Line 4,845 ⟶ 5,827:
===Built-In Graphical Version===
<syntaxhighlight lang
===Original Graphical Version===
Line 4,861 ⟶ 5,843:
*Code uses <code>gca</code> and <code>gcf</code> frequently, which could possibly cause problems if user is working with multiple figure windows at once.
<
% Game parameters (should be modified by user)
Line 5,103 ⟶ 6,085:
delete(stopwatch)
delete(obj)
end</
=={{header|Nim}}==
{{trans|Go}}
<syntaxhighlight lang="nim">import random, sequtils, strformat, strscans, strutils
const LMargin = 4
type
Cell = object
isMine: bool
display: char
Grid = seq[seq[Cell]]
Game = object
grid: Grid
mineCount: Natural
minesMarked: Natural
isOver: bool
proc initGame(m, n: Positive): Game =
result.grid = newSeqWith(m, repeat(Cell(isMine: false, display: '.'), n))
let min = (float(m * n) * 0.1).toInt
let max = (float(m * n) * 0.2).toInt
result.mineCount = min + rand(max - min)
var rm = result.mineCount
while rm > 0:
let x = rand(m - 1)
let y = rand(n - 1)
if not result.grid[x][y].isMine:
dec rm
result.grid[x][y].isMine = true
result.minesMarked = 0
template `[]`(grid: Grid; x, y: int): Cell = grid[x][y]
iterator cells(grid: var Grid): var Cell =
for y in 0..grid[0].high:
for x in 0..grid.high:
yield grid[x, y]
proc display(game: Game; endOfGame: bool) =
if not endOfGame:
echo &"Grid has {game.mineCount} mine(s), {game.minesMarked} mine(s) marked."
let margin = repeat(' ', LMargin)
echo margin, toSeq(1..game.grid.len).join()
echo margin, repeat('-', game.grid.len)
for y in 0..game.grid[0].high:
stdout.write align($(y + 1), LMargin)
for x in 0..game.grid.high:
stdout.write game.grid[x][y].display
stdout.write '\n'
proc terminate(game: var Game; msg: string) =
game.isOver = true
echo msg
var answer = ""
while answer notin ["y", "n"]:
stdout.write "Another game (y/n)? "
answer = try: stdin.readLine().toLowerAscii
except EOFError: "n"
if answer == "y":
game = initGame(6, 4)
game.display(false)
proc resign(game: var Game) =
var found = 0
for cell in game.grid.cells:
if cell.isMine:
if cell.display == '?':
cell.display = 'Y'
inc found
elif cell.display == 'x':
cell.display = 'N'
game.display(true)
let msg = &"You found {found} out of {game.mineCount} mine(s)."
game.terminate(msg)
proc markCell(game: var Game; x, y: int) =
if game.grid[x, y].display == '?':
dec game.minesMarked
game.grid[x, y].display = '.'
elif game.grid[x, y].display == '.':
inc game.minesMarked
game.grid[x, y].display = '?'
proc countAdjMines(game: Game; x, y: Natural): int =
for j in (y - 1)..(y + 1):
if j in 0..game.grid[0].high:
for i in (x - 1)..(x + 1):
if i in 0..game.grid.high:
if game.grid[i, j].isMine:
inc result
proc clearCell(game: var Game; x, y: int): bool =
if x in 0..game.grid.high and y in 0..game.grid[0].high:
if game.grid[x, y].display == '.':
if game.grid[x, y].isMine:
game.grid[x][y].display = 'x'
echo "Kaboom! You lost!"
return false
let count = game.countAdjMines(x, y)
if count > 0:
game.grid[x][y].display = chr(ord('0') + count)
else:
game.grid[x][y].display = ' '
for dx in -1..1:
for dy in -1..1:
if dx != 0 or dy != 0:
discard game.clearCell(x + dx, y + dy)
result = true
proc testForWin(game: var Game): bool =
if game.minesMarked != game.mineCount: return false
for cell in game.grid.cells:
if cell.display == '.': return false
result = true
echo "You won!"
proc splitAction(game: Game; action: string): tuple[x, y: int; ok: bool] =
var command: string
if not action.scanf("$w $s$i $s$i$s$.", command, result.x, result.y): return
if command.len != 1: return
if result.x notin 1..game.grid.len or result.y notin 1..game.grid.len: return
result.ok = true
proc printUsage() =
echo "h or ? - this help,"
echo "c x y - clear cell (x,y),"
echo "m x y - marks (toggles) cell (x,y),"
echo "n - start a new game,"
echo "q - quit/resign the game,"
echo "where 'x' is the (horizontal) column number and 'y' is the (vertical) row number.\n"
randomize()
printUsage()
var game = initGame(6, 4)
game.display(false)
while not game.isOver:
stdout.write "\n>"
let action = try: stdin.readLine().toLowerAscii
except EOFError: "q"
case action[0]
of 'h', '?':
printUsage()
of 'n':
game = initGame(6, 4)
game.display(false)
of 'c':
let (x, y, ok) = game.splitAction(action)
if not ok: continue
if game.clearCell(x - 1, y - 1):
game.display(false)
if game.testForwin(): game.resign()
else:
game.resign()
of 'm':
let (x, y, ok) = game.splitAction(action)
if not ok: continue
game.markCell(x - 1, y - 1)
game.display(false)
if game.testForWin(): game.resign()
of 'q':
game.resign()
else:
continue</syntaxhighlight>
{{out}}
Session example.
<pre>h or ? - this help,
c x y - clear cell (x,y),
m x y - marks (toggles) cell (x,y),
n - start a new game,
q - quit/resign the game,
where 'x' is the (horizontal) column number and 'y' is the (vertical) row number.
Grid has 2 mine(s), 0 mine(s) marked.
123456
------
1......
2......
3......
4......
>c 3 3
Grid has 2 mine(s), 0 mine(s) marked.
123456
------
1.1 1..
211 111
3
4
>m 1 1
Grid has 2 mine(s), 1 mine(s) marked.
123456
------
1?1 1..
211 111
3
4
>m 5 1
Grid has 2 mine(s), 2 mine(s) marked.
123456
------
1?1 1?.
211 111
3
4
>c 6 1
Grid has 2 mine(s), 2 mine(s) marked.
123456
------
1?1 1?1
211 111
3
4
You won!
123456
------
1Y1 1Y1
211 111
3
4
You found 2 out of 2 mine(s).
Another game (y/n)? n</pre>
=={{header|OCaml}}==
<
exception Won
Line 5,251 ⟶ 6,486:
in
minesweeper n m percent;
;;</
Sample output:
Line 5,294 ⟶ 6,529:
=={{header|Perl}}==
<
use warnings;
use strict;
Line 5,460 ⟶ 6,695:
}
print "You won!\n";
</syntaxhighlight>
=={{header|Phix}}==
Simple text-based console version
<
Minesweeper
Line 5,635 ⟶ 6,870:
disclose()
puts(1,board&"game over\n\n")
{} = wait_key()</
{{out}}
<pre>
Line 5,667 ⟶ 6,902:
=={{header|PHP}}==
This example generates webpage. It also uses JavaScript to have right-click events. Use it as CGI script.
<
define('MINEGRID_WIDTH', 6);
define('MINEGRID_HEIGHT', 4);
Line 5,920 ⟶ 7,155:
unset($_SESSION['minesweeper']);
echo '<p>Congratulations. You won :).';
}</
=={{header|PicoLisp}}==
<
# T Hidden: Mine
# 0-8 Marked: Empty field
Line 5,978 ⟶ 7,213:
(showMines)
(unless (fish =T *Field)
"Congratulations! You won!!" ) )</
Output:
<pre>: (minesweeper 6 4)
Line 6,025 ⟶ 7,260:
Works with SWI-Prolog
<
% Play - run the minesweeper game with a specified width and height
Line 6,231 ⟶ 7,466:
grid_set_xy(G, H, cell(0,0), Ng1),
expose_grid(H, Ng1, Ng2),
expose_grid_(T, Ng2, Ng).</
<pre>
Line 6,247 ⟶ 7,482:
=={{header|PureBasic}}==
<
isMine.i
display.c ;character to displays for cell, one of these {'.', '?', ' ', #)
Line 6,424 ⟶ 7,659:
Print(#CRLF$ + #CRLF$ + "Press ENTER to exit"): Input()
CloseConsole()
EndIf</
Sample output:
<pre>h or ? - this help,
Line 6,475 ⟶ 7,710:
=={{header|Python}}==
<
'''
Minesweeper game.
Line 6,611 ⟶ 7,846:
% (len(mines.intersection(markedmines)),
len(markedmines.difference(mines)),
len(mines)) )</
'''Sample output'''
Line 6,691 ⟶ 7,926:
=={{header|Racket}}==
<syntaxhighlight lang="racket">
#lang racket
(require math/array)
Line 6,836 ⟶ 8,071:
;parse failed
(run))))))
</syntaxhighlight>
=={{header|Raku}}==
(formerly Perl 6)
{{works with|rakudo|2015-09-23}}
<syntaxhighlight lang="raku"
class Tile {
Line 6,951 ⟶ 8,186:
}
say ~$f;</
=={{header|Ruby}}==
<
Minesweeper game.
Line 7,118 ⟶ 8,353:
show_grid
end
end</
=={{header|Rust}}==
<
use std::io;
Line 7,436 ⟶ 8,671:
}
}
}</
'''Sample output'''
Line 7,483 ⟶ 8,718:
=={{header|Tcl}}==
<
fconfigure stdout -buffering none
Line 7,632 ⟶ 8,867:
}
play 6 4</
Sample output:
<pre>
Line 7,688 ⟶ 8,923:
- 1 Class Module (Name it cMinesweeper)</pre>
===Code Module : ===
<syntaxhighlight lang="vb">
Option Explicit
Line 7,701 ⟶ 8,936:
Userf.Show 0, True
End Sub
</syntaxhighlight>
===Code Class Module===
Line 7,710 ⟶ 8,945:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</pre>
<syntaxhighlight lang="vb">
Option Explicit
Line 7,952 ⟶ 9,187:
End If
End Sub
</syntaxhighlight>
=={{header|Wren}}==
{{trans|Go}}
{{libheader|Wren-dynamic}}
{{libheader|Wren-fmt}}
{{libheader|Wren-ioutil}}
{{libheader|Wren-str}}
<syntaxhighlight lang="wren">import "./dynamic" for Struct
import "./fmt" for Fmt
import "random" for Random
import "./ioutil" for Input
import "./str" for Str
var Cell = Struct.create("Cell", ["isMine", "display"])
var lMargin = 4
var grid = []
var mineCount = 0
var minesMarked = 0
var isGameOver = false
var rand = Random.new()
var makeGrid = Fn.new { |n, m|
if ( n <= 0 || m <= 0) Fiber.abort("Grid dimensions must be positive.")
grid = List.filled(n, null)
for (i in 0...n) {
grid[i] = List.filled(m, null)
for (j in 0...m) grid[i][j] = Cell.new(false, ".")
}
var min = (n * m * 0.1).round // 10% of tiles
var max = (n * m * 0.2).round // 20% of tiles
mineCount = min + rand.int(max - min + 1)
var rm = mineCount
while (rm > 0) {
var x = rand.int(n)
var y = rand.int(m)
if (!grid[x][y].isMine) {
rm = rm - 1
grid[x][y].isMine = true
}
}
minesMarked = 0
isGameOver = false
}
var displayGrid = Fn.new { |isEndOfGame|
if (!isEndOfGame) {
System.print("Grid has %(mineCount) mine(s), %(minesMarked) mine(s) marked.")
}
var margin = " " * lMargin
System.write("%(margin) ")
for (i in 1..grid.count) System.write(i)
System.print()
System.print("%(margin) %("-" * grid.count)")
for (y in 0...grid[0].count) {
Fmt.write("$*d:", lMargin, y+1)
for (x in 0...grid.count) System.write(grid[x][y].display)
System.print()
}
}
var endGame = Fn.new { |msg|
isGameOver = true
System.print(msg)
var ans = Input.option("Another game (y/n)? : ", "ynYN")
if (ans == "n" || ans == "N") return
makeGrid.call(6, 4)
displayGrid.call(false)
}
var resign = Fn.new {
var found = 0
for (y in 0...grid[0].count) {
for (x in 0...grid.count) {
if (grid[x][y].isMine) {
if (grid[x][y].display == "?") {
grid[x][y].display = "Y"
found = found + 1
} else if (grid[x][y].display != "x") {
grid[x][y].display = "N"
}
}
}
}
displayGrid.call(true)
var msg = "You found %(found), out of %(mineCount) mine(s)."
endGame.call(msg)
}
var usage = Fn.new {
System.print("h or ? - this help,")
System.print("c x y - clear cell (x,y),")
System.print("m x y - marks (toggles) cell (x,y),")
System.print("n - start a new game,")
System.print("q - quit/resign the game,")
System.print("where x is the (horizontal) column number and y is the (vertical) row number.\n")
}
var markCell = Fn.new { |x, y|
if (grid[x][y].display == "?") {
minesMarked = minesMarked - 1
grid[x][y].display = "."
} else if (grid[x][y].display == ".") {
minesMarked = minesMarked + 1
grid[x][y].display = "?"
}
}
var countAdjMines = Fn.new { |x, y|
var count = 0
for (j in y-1..y+1) {
if (j >= 0 && j < grid[0].count) {
for (i in x-1..x+1) {
if (i >= 0 && i < grid.count) {
if (grid[i][j].isMine) count = count + 1
}
}
}
}
return count
}
var clearCell // recursive function
clearCell = Fn.new { |x, y|
if (x >= 0 && x < grid.count && y >= 0 && y < grid[0].count) {
if (grid[x][y].display == ".") {
if (!grid[x][y].isMine) {
var count = countAdjMines.call(x, y)
if (count > 0) {
grid[x][y].display = String.fromByte(48 + count)
} else {
grid[x][y].display = " "
clearCell.call(x+1, y)
clearCell.call(x+1, y+1)
clearCell.call(x, y+1)
clearCell.call(x-1, y+1)
clearCell.call(x-1, y)
clearCell.call(x-1, y-1)
clearCell.call(x, y-1)
clearCell.call(x+1, y-1)
}
} else {
grid[x][y].display = "x"
System.print("Kaboom! You lost!")
return false
}
}
}
return true
}
var testForWin = Fn.new {
var isCleared = false
if (minesMarked == mineCount) {
isCleared = true
for (x in 0...grid.count) {
for (y in 0...grid[0].count) {
if (grid[x][y].display == ".") isCleared = false
}
}
}
if (isCleared) System.print("You won!")
return isCleared
}
var splitAction = Fn.new { |action|
var fields = action.split(" ").where{ |s| s != "" }.toList
if (fields.count != 3) return [0, 0, false]
var x = Num.fromString(fields[1])
if (x < 1 || x > grid.count) return [0, 0, false]
var y = Num.fromString(fields[2])
if (y < 1 || y > grid[0].count) return [0, 0, false]
return [x, y, true]
}
usage.call()
makeGrid.call(6, 4)
displayGrid.call(false)
while (!isGameOver) {
var action = Str.lower(Input.text("\n>", 1))
var first = action[0]
if (first == "h" || first == "?") {
usage.call()
} else if (first == "n") {
makeGrid.call(6, 4)
displayGrid.call(false)
} else if (first == "c") {
var res = splitAction.call(action)
if (!res[2]) continue
var x = res[0]
var y = res[1]
if (clearCell.call(x-1, y-1)) {
displayGrid.call(false)
if (testForWin.call()) resign.call()
} else {
resign.call()
}
} else if (first == "m") {
var res = splitAction.call(action)
if (!res[2]) continue
var x = res[0]
var y = res[1]
markCell.call(x-1, y-1)
displayGrid.call(false)
if (testForWin.call()) resign.call()
} else if (first == "q") {
resign.call()
} else {
System.print("Invalid option, try again")
}
}</syntaxhighlight>
{{out}}
Sample game:
<pre>
h or ? - this help,
c x y - clear cell (x,y),
m x y - marks (toggles) cell (x,y),
n - start a new game,
q - quit/resign the game,
where x is the (horizontal) column number and y is the (vertical) row number.
Grid has 2 mine(s), 0 mine(s) marked.
123456
------
1:......
2:......
3:......
4:......
>c 1 1
Grid has 2 mine(s), 0 mine(s) marked.
123456
------
1: 1....
2: 11211
3:
4:
>m 3 1
Grid has 2 mine(s), 1 mine(s) marked.
123456
------
1: 1?...
2: 11211
3:
4:
>c 6 1
Grid has 2 mine(s), 1 mine(s) marked.
123456
------
1: 1?..1
2: 11211
3:
4:
>m 5 1
Grid has 2 mine(s), 2 mine(s) marked.
123456
------
1: 1?.?1
2: 11211
3:
4:
>c 4 1
Grid has 2 mine(s), 2 mine(s) marked.
123456
------
1: 1?2?1
2: 11211
3:
4:
You won!
123456
------
1: 1Y2Y1
2: 11211
3:
4:
You found 2, out of 2 mine(s).
Another game (y/n)? : n
</pre>
|