minesweeper.ps1

<#PSScriptInfo
.DESCRIPTION
    This is a port of the minesweeper game into powershell using new powershell 5 classes and .net framework forms.
.AUTHOR
    Robert Weber
.VERSION
    1.1
.GUID
    cfa53e66-abe8-4584-a3cd-9398162f5a0f
#>


<#
.SYNOPSIS
    This is a port of the minesweeper game into powershell...using new powershell 5 classes and .net framework forms.
.PARAMETER Side
    The length of the sides of the arena
.PARAMETER Mines
    The number of mines to hide
.PARAMETER Easy
    A Switch to use the easy layout
.PARAMETER Medium
    A Switch to use the medium layout
.PARAMETER Hard
    A Switch to use the hard layout
.PARAMETER Expert
    A Switch to use the expert layout
.EXAMPLE
    .\minesweeper.ps1
.INPUTS
    This script expects no inputs
.OUTPUTS
    This script only outputs the minesweeper GUI
.NOTES
    Tags - Games, PSClasses, WinForms, MineSweeper, BPE
    Author - Robert Weber
    Version - 1.1
    Changes - Fixed a bug with the scope of the button event 'this' item
#>

[CmdletBinding()]
param( 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][int]$side = 16, 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][int]$mines = 40, 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][switch]$easy, 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][switch]$medium, 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][switch]$hard, 
    [Parameter(Mandatory = $false, ValueFromPipeLine = $false,ValueFromPipelineByPropertyName = $false)][switch]$expert
)
Begin{
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    #this is here because you can't call .net types from within the new classes.
    function getControls{
        return ( [System.Windows.Forms.Control[]] @() )
    }    
}

Process{

    Class MineSweeper{
        $gui = @{
                'form' = new-object "System.Windows.Forms.form"
                'controls' = @{}
                'vars' = @{}
            }
        [int] $rows = $script:side
        [int] $cols = $script:side
        [int] $mines = $script:mines
        $map = (getControls)
        $cleared = (new-object collections.arraylist)
        
        [void] ToggleFlag( $num ){
            <#
            .DESCRIPTION
                This function toggles the placement of flags
            .PARAMETER num
                This is the index of the button being toggled
            #>

            if( $this.map[$num].text -ne "M"){
                $this.map[$num].text = "M" 
                $this.map[$num].BackColor = "lightblue"
                $this.gui.controls.txtFlags.text = $([int]$this.gui.controls.txtFlags.text) - 1
            }else{
                $this.map[$num].text = "" 
                $this.map[$num].BackColor = "lightGray"
                $this.gui.controls.txtFlags.text = $([int]$this.gui.controls.txtFlags.text) + 1
            }
        }
        
        [void] clearSpace($num) {
            <#
            .DESCRIPTION
                This function clears the flag in a space
            .PARAMETER num
                This is the index of the button being toggled
            #>

            $this.cleared.add( $num )
     
            while($this.cleared.count -gt 0){
                $n = $this.cleared[0]
                $this.ExecClear($n)
                $this.cleared.remove($n)
            }
        }

        [void] DoRaise($num){ 
            <#
            .DESCRIPTION
                This function counts the number of 'mines' surrounding the current cell
            .PARAMETER num
                This is the index of the button being toggled
            #>

            if ($this.map[$num].tag -ne "x"){ 
                [int]$this.map[$num].tag = [int]$this.map[$num].tag + 1
            } 
        } 
        
        [void] ExecClear($num) {
            <#
            .DESCRIPTION
                This function updates the gui with the toggles to the flags
            .PARAMETER num
                This is the index of the button being toggled
            #>

            $C = $num % $this.rows 
            $R = [math]::truncate($num/$this.rows) 
            If ($this.map[$num].enabled -eq $true) { 
                if ($this.map[$num].tag -eq "x") { 
                    $this.map[$num].text = "x" 
                    $this.map[$num].BackColor = "red"
                    $this.map | % {
                        $_.enabled = $false

                    }
                } Else {
                    $this.map[$num].BackColor = "#FFC0C0C0" 
                    $this.map[$num].FlatStyle = 'Flat' 
                    
                    if ($this.map[$num].tag -ne 0) {
                        $this.map[$num].BackColor = "#FFC0C0C0"
                        $this.map[$num].text = $this.map[$num].tag
                        $this.map[$num].Enabled = $true
                        switch($this.map[$num].tag){
                            1 { $this.map[$num].backcolor = "#FF9999FF"; }
                            2 { $this.map[$num].backcolor = "#FF99FF99"; }
                            3 { $this.map[$num].backcolor = "#FFFF9999"; }
                            
                            4 { $this.map[$num].backcolor = "#FF9999CC"; }
                            5 { $this.map[$num].backcolor = "#FF99CC99"; }
                            6 { $this.map[$num].backcolor = "#FFCC9999"; }
                            
                            7 { $this.map[$num].backcolor = "#FF6666ff"; }
                            8 { $this.map[$num].backcolor = "#FF66ff66"; }
                            9 { $this.map[$num].backcolor = "#FFff6666"; }
                        }
                    } Else {
                        $this.map[$num].BackColor = "#FFEEEEEE"
                        if ($C -gt 0) { 
                            $this.cleared.add(  $( $num - 1 ) ) 
     
                            if ($R -gt 0) { 
                                $this.cleared.add( $( $num - $this.Cols) - 1 ) 
                            } 
                        } 
                        if ($C -lt ($this.Cols - 1)) { 
                            $this.cleared.add($num + 1 ) 
                            if ($R -gt 0) { 
                                $this.cleared.add( ( $num - $this.cols) + 1 )
                            } 
                        } 
                        if ($R -gt 0) { 
                            $this.cleared.add( $num - $this.cols) 
                        } 
                        if ($R -lt ($this.rows -1)) { 
                            $this.cleared.add($num + $this.cols) 
                            if ($C -gt 0) { 
                                $this.cleared.add( ($num + $this.Cols) - 1 ) 
                            }
                            if ($C -lt ($this.Cols - 1)) { 
                                $this.cleared.add( ( $num + $this.Cols ) + 1 )
                            } 
                        } 
                    } 
                    $this.map[$num].Enabled = $false
                } 
            } 
        }

        [void] generateForm(){
            <#
            .DESCRIPTION
                This function generates the GUI using winforms
            #>
                
            $this.gui.form.Width = ($this.cols * 25) + 35 
            $this.gui.form.Height = ($this.rows * 25) + 90   
            
            $this.gui.controls.add('txtFlags', (New-Object "System.windows.forms.textBox" ) )
            $this.gui.controls.txtFlags.location = New-Object System.Drawing.Size(10,10)
            $this.gui.controls.txtFlags.size = New-Object System.Drawing.Size(50,25)
            $this.gui.controls.txtFlags.backColor = '#FFFFFFFF'
            $this.gui.controls.txtFlags.text = $this.mines
            $this.gui.form.Controls.Add($this.gui.controls.txtFlags)
     
            for ($i = 0;$i -lt $this.rows;$i++){ 
                $row = @() 
                for ($j = 0;$j -lt $this.Cols;$j++){ 

                    $Button = new-object System.Windows.Forms.Button 
                    $Button.width = 25 
                    $Button.Height = 25 
                    $button.top = ($i * 25) + 40 
                    $button.Left = $j * 25 + 10 
                    $button.Name = $( ($i * $this.cols) + $j)
                    $button.tabstop = $false
                    $button.FlatStyle = 'Flat'
                    $button.FlatAppearance.Bordersize = 1
                    $Button.FlatAppearance.BorderColor = "#80808080"
                    $button.backColor = "#FF999999"
     
                    $Button.tag = "0" 
                    
                    $script:self = $this
                    
                    $button.add_mouseDown({
                        switch($_.Button){
                            "Left" {
                                [int]$num = $this.name 
                                $script:self.cleared = new-object collections.arraylist
                                $script:self.clearSpace($num) 
                            }
                            "Right" {
                                [int]$num = $this.name 
                                $script:self.ToggleFlag($num)
                            }
                        }
                    }) 
                    $row += $Button 
                }    
                $this.Map += $row 
            }
        }
        
        [void] fillMines(){
            <#
            .DESCRIPTION
                This function places mines in the field
            #>

            $Random = new-object system.random([datetime]::now.Millisecond) 
            for ($i = 0 ; $i -lt $this.Mines) { 
                $num = $random.next($this.map.count) 
                if ( $this.map[$num].tag -ne "x") { 
                    $this.map[$num].tag = "x" 
                    $C = $num % $this.rows 
                    $R = [math]::truncate($num/$this.rows) 

                    if ($C -gt 0) { 
                        $this.doRaise($num - 1) 
                        if ($R -gt 0) { 
                            $this.doRaise(($num - $this.Cols) - 1) 
                        } 
                    } 
                    if ($C -lt ($this.Cols - 1)) { 
                        $this.doRaise($num + 1) 
                        if ($R -gt 0) { 
                            $this.doRaise(($num - $this.Cols) + 1) 
                        } 
                    } 
                    if ($R -gt 0) { 
                        $this.doRaise($num - $this.cols) 
                    } 
                    if ($R -lt ($this.rows -1)) { 
                        $this.doRaise($num + $this.cols) 
                        if ($C -gt 0) { 
                            $this.doRaise(($num + $this.Cols) - 1) 
                        } 
                        if ($C -lt ($this.Cols -1)) { 
                            $this.doRaise(($num + $this.Cols) + 1) 
                        } 
                    }
                    $i++ 
                } 
            }
            $this.gui.Form.controls.addrange($this.map)
        }
        
        [void] Dispose(){
            <#
            .DESCRIPTION
                This function disposes the objects and variables created for this script
            #>

            $this = $null
        }
        
        MineSweeper(){
        <#
        .DESCRIPTION
            This is the constructor for the MineSweeper object
        #>

            switch($true){
                $script:easy{ $this.cols, $this.rows, $this.mines = @(8,8,10) }
                $script:medium{ $this.cols, $this.rows, $this.mines = @(16,16,40) }
                $script:hard{ $this.cols, $this.rows, $this.mines = @(24,24,99) }
                $script:expert{ $this.cols, $this.rows, $this.mines = @(32,32,150) }
            }
        
            $this.generateForm()
            $this.fillMines()
            $this.gui.Form.ShowDialog()| Out-Null
        }    
    }
    
    $mineSweeper = [MineSweeper]::new()
}
End{
    $mineSweeper.Dispose()
    [system.gc]::Collect()
}