17 people like it.
Like the snippet!
Minesweeper in 99 lines of code
This program is written in only 99 lines of actual code and remains enough readability.
I used few short-coding technics.
1. no XAML.
2. pre-calculate every useful data for the purpose of eliminating useless states
3. using record type with set property as an alternative of view-model
4. initialize everything in one place.
5. encapsulate all states in one place.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
|
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
let defaultSize = 10 , 10
let bombRate = 0.11
// functions
let rand = let rand = new Random() in rand.NextDouble
let neighbours (width,height) (pos:int) = seq {
let range x bound = [max 0 (x-1) .. min bound (x+1)]
for x in range (pos%width) (width-1) do
for y in range (pos/width) (height-1) do
let p = x + y * width in if p <> pos then yield p }
// WPF
let appendTo (panel:Panel) uie =
panel.Children.Add uie |> ignore
uie
let dockTo(panel:DockPanel) dock (uie:#UIElement) =
DockPanel.SetDock(uie,dock)
appendTo panel uie
type MyComboBox(text) as this =
inherit StackPanel(Orientation=Orientation.Vertical)
let textBlock = new TextBlock(Text=text,Foreground=Brushes.White) |> appendTo this
let listBox = new ListBox() |> appendTo this
do for x in 4 .. 2 .. 30 do listBox.Items.Add x |> ignore
listBox.SelectedItem <- 10
member __.Value = listBox.SelectedItem :?> int
// view
let window = Window(Background=Brushes.BlueViolet,ResizeMode=ResizeMode.NoResize)
window.Title <- "FineSweeper"
let dpMain = new DockPanel()
window.Content <- dpMain
let spsize = new StackPanel(Orientation=Orientation.Horizontal) |> dockTo dpMain Dock.Left
let comboBoxWidth = new MyComboBox("X") |> appendTo spsize
let comboBoxHeight = new MyComboBox("Y") |> appendTo spsize
let buttonInit = new Button(Content="Replay") |> dockTo dpMain Dock.Top
let canvas = new Canvas() |> appendTo dpMain
let initButtons(lenX,lenY) =
let f(pos:int) =
let b = new Button(Width=20. , Height=20.)
Canvas.SetLeft(b, float(pos%lenX*20))
Canvas.SetTop(b, float(pos/lenX*20))
canvas.Children.Add b|> ignore
b
Array.init (lenX*lenY) f
// model
type State = Normal | Pushed | Flaged
type Square =
{Button:Button; mutable state:State; IsBomb:bool; BombNum:int; Neighbours:seq<Square> } with
member this.State
with get() = this.state
and set value =
this.state <- value
let pushedText = if this.IsBomb then "※" else string this.BombNum
match value with
|Normal->true,"" |Pushed->false,pushedText |Flaged->true, "¶"
|> fun (isEnabled,text) ->
this.Button.IsEnabled <- isEnabled
this.Button.Content <- text
#nowarn "40"
let initSquares (lenX,lenY) : Square [] =
let buttons = initButtons (lenX,lenY)
let bombs : bool[] = Array.init (lenX*lenY) (fun _ -> rand() < bombRate)
let rec squares =
Array.init (lenX*lenY) (fun p->
let neighbours = neighbours(lenX,lenY) p
let bombNum = neighbours |> Seq.filter(fun p -> bombs.[p]) |> Seq.length
let neighbours = neighbours |> Seq.map(fun p -> squares.[p])
{Button=buttons.[p]; state=Normal; IsBomb=bombs.[p]; BombNum=bombNum; Neighbours=neighbours})
squares
let changeSqs sq =
let examined = ResizeArray()
let rec f(s:Square) =
if not(examined.Contains s) && not(s.IsBomb) then
examined.Add s
if s.BombNum = 0 then Seq.iter f s.Neighbours
f sq ; examined
// controller
let onInit () =
canvas.Children.Clear()
let lenX,lenY = comboBoxWidth.Value , comboBoxHeight.Value
let squares = initSquares(lenX,lenY)
let onGameEnd text =
MessageBox.Show text |> ignore
for sq in squares do sq.State <- Pushed
let onCheck () =
if squares |> Seq.forall(fun sq->sq.State<>Normal || sq.IsBomb) then onGameEnd "You win!!"
let onClick(sq:Square) =
if sq.State = Normal then
if sq.IsBomb then onGameEnd "You lose!!" else
for sq in changeSqs sq do sq.State <- Pushed
onCheck()
let onRightClick (sq:Square) =
sq.State <- if sq.State = Normal then Flaged else Normal
for {Button=b} as sq in squares do
b.Click.Add(fun _ -> onClick sq )
b.MouseRightButtonDown.Add(fun _ -> onRightClick sq ; onCheck())
window.Width <- lenX * 20 + 55 |> float
window.Height <- lenY * 20 + 60 |> float
buttonInit.Click.Add(fun _-> onInit())
[<EntryPoint>][<STAThread>]
let main _ =
onInit()
(Application()).Run window
|
namespace System
namespace System.Windows
namespace System.Media
val defaultSize : int * int
Full name: Script.defaultSize
val bombRate : float
Full name: Script.bombRate
val rand : (unit -> float)
Full name: Script.rand
val rand : Random
Multiple items
type Random =
new : unit -> Random + 1 overload
member Next : unit -> int + 2 overloads
member NextBytes : buffer:byte[] -> unit
member NextDouble : unit -> float
Full name: System.Random
--------------------
Random() : unit
Random(Seed: int) : unit
Random.NextDouble() : float
val neighbours : width:int * height:int -> pos:int -> seq<int>
Full name: Script.neighbours
val width : int
val height : int
val pos : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
val range : (int -> int -> int list)
val x : int
val bound : int
val max : e1:'T -> e2:'T -> 'T (requires comparison)
Full name: Microsoft.FSharp.Core.Operators.max
val min : e1:'T -> e2:'T -> 'T (requires comparison)
Full name: Microsoft.FSharp.Core.Operators.min
val y : int
val p : int
val appendTo : panel:'a -> uie:'b -> 'b
Full name: Script.appendTo
val panel : 'a
val uie : 'b
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
val dockTo : panel:'a -> dock:'b -> uie:'c -> 'c
Full name: Script.dockTo
val dock : 'b
val uie : 'c
Multiple items
type MyComboBox =
inherit obj
new : text:string -> MyComboBox
member Value : int
Full name: Script.MyComboBox
--------------------
new : text:string -> MyComboBox
val text : string
val this : MyComboBox
namespace System.Text
member MyComboBox.Value : int
Full name: Script.MyComboBox.Value
val window : obj
Full name: Script.window
val dpMain : obj
Full name: Script.dpMain
val spsize : obj
Full name: Script.spsize
val comboBoxWidth : MyComboBox
Full name: Script.comboBoxWidth
val comboBoxHeight : MyComboBox
Full name: Script.comboBoxHeight
val buttonInit : obj
Full name: Script.buttonInit
val canvas : obj
Full name: Script.canvas
val initButtons : lenX:int * lenY:int -> 'a []
Full name: Script.initButtons
val lenX : int
val lenY : int
val f : (int -> 'b)
val b : 'b
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
type Array =
member Clone : unit -> obj
member CopyTo : array:Array * index:int -> unit + 1 overload
member GetEnumerator : unit -> IEnumerator
member GetLength : dimension:int -> int
member GetLongLength : dimension:int -> int64
member GetLowerBound : dimension:int -> int
member GetUpperBound : dimension:int -> int
member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
member Initialize : unit -> unit
member IsFixedSize : bool
...
Full name: System.Array
val init : count:int -> initializer:(int -> 'T) -> 'T []
Full name: Microsoft.FSharp.Collections.Array.init
type State =
| Normal
| Pushed
| Flaged
Full name: Script.State
union case State.Normal: State
union case State.Pushed: State
union case State.Flaged: State
type Square =
{Button: obj;
mutable state: State;
IsBomb: bool;
BombNum: int;
Neighbours: seq<Square>;}
member State : State
member State : State with set
Full name: Script.Square
Square.Button: obj
Square.state: State
Square.IsBomb: bool
type bool = Boolean
Full name: Microsoft.FSharp.Core.bool
Square.BombNum: int
Square.Neighbours: seq<Square>
val this : Square
Multiple items
member Square.State : State with set
Full name: Script.Square.State
--------------------
type State =
| Normal
| Pushed
| Flaged
Full name: Script.State
val set : elements:seq<'T> -> Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
val value : State
val pushedText : string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
val isEnabled : bool
val initSquares : lenX:int * lenY:int -> Square []
Full name: Script.initSquares
val buttons : obj []
val bombs : bool []
val squares : Square []
val neighbours : seq<int>
val bombNum : int
module Seq
from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Collections.Seq.filter
val length : source:seq<'T> -> int
Full name: Microsoft.FSharp.Collections.Seq.length
val neighbours : seq<Square>
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>
Full name: Microsoft.FSharp.Collections.Seq.map
val changeSqs : sq:Square -> Collections.Generic.List<Square>
Full name: Script.changeSqs
val sq : Square
val examined : Collections.Generic.List<Square>
type ResizeArray<'T> = Collections.Generic.List<'T>
Full name: Microsoft.FSharp.Collections.ResizeArray<_>
val f : (Square -> unit)
val s : Square
val not : value:bool -> bool
Full name: Microsoft.FSharp.Core.Operators.not
Collections.Generic.List.Contains(item: Square) : bool
Collections.Generic.List.Add(item: Square) : unit
val iter : action:('T -> unit) -> source:seq<'T> -> unit
Full name: Microsoft.FSharp.Collections.Seq.iter
val onInit : unit -> 'a
Full name: Script.onInit
property MyComboBox.Value: int
val onGameEnd : ('b -> unit)
val text : 'b
property Square.State: State
val onCheck : (unit -> unit)
val forall : predicate:('T -> bool) -> source:seq<'T> -> bool
Full name: Microsoft.FSharp.Collections.Seq.forall
val onClick : (Square -> unit)
val onRightClick : (Square -> unit)
val b : obj
Multiple items
type EntryPointAttribute =
inherit Attribute
new : unit -> EntryPointAttribute
Full name: Microsoft.FSharp.Core.EntryPointAttribute
--------------------
new : unit -> EntryPointAttribute
Multiple items
type STAThreadAttribute =
inherit Attribute
new : unit -> STAThreadAttribute
Full name: System.STAThreadAttribute
--------------------
STAThreadAttribute() : unit
val main : string [] -> int
Full name: Script.main
More information