17 people like it.

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
Raw view Test code New version

More information

Link:http://fssnip.net/7W
Posted:12 years ago
Author:nagat01
Tags: game , puzzle