See also tkmines.
package require Tk package require Tktable package require math # Widget names/Commands set rstBtn .f.b set cntLbl .f.c set timLbl .f.tm set tbl .f.t set initMines 40 set nMines 0 set gameOn 0 variable tab; # tab($r,$c) contains revealed mine counts variable map; # map($r,$c) contains "M" or number of mines in adj. cells variable cntVar; # Number of flags to place variable timer; # Elapsed time. Not used yet. # table info array set table { name .f.t cmd .f.t rows 16 cols 16 cellSize 18 xp -1 yp -1 x -1 y -1 offsets {-1 -1 -1 0 -1 1 0 -1 0 1 1 -1 1 0 1 1 } valid 0 } frame .f pack .f -side top set tbl $table(name) table $tbl -rows $table(rows) -cols $table(cols) \ -roworigin 0 -colorigin 0 -selectmode none \ -colwidth -$table(cellSize) -rowheight -$table(cellSize) \ -width 0 -height 0 \ -variable tab \ -flashmode off \ -cursor top_left_arrow \ -borderwidth 2 \ -state disabled \ -resizeborders none set f [font create -family "arial black" -size 15 ] label $cntLbl -font $f -textvariable cntVar label $timLbl -font $f -textvariable timer button $rstBtn grid $cntLbl $rstBtn $timLbl grid $tbl -columnspan 3 # set up tags for the various cell states: # NC Not clicked yet # C0 Empty no neighbours # C1..C8 Empty n neighbours # FLAG Flag Symbol # MINE Mine # NOMINE bad flag # BANG exploded mine set f [font create -family "arial black" -size 9 ] $tbl tag configure NC -bg gray75 -relief raised -showtext 1 $tbl tag configure C0 -bg gray -relief flat foreach {n c} {1 blue 2 "dark green" 3 red 4 "dark blue" 5 "dark red" 6 brown 7 black 8 navy } { $tbl tag configure C$n -bg gray -fg $c -relief flat -font $f } # $tbl tag configure FLAG / MINE / NOMINE / BOOM (tags with images, see later) # Win. All mines flagged. All other squares clicked at least once proc checkWin {} { if { ! $::cntVar } { if { ! [llength [$::tbl tag cell NC]]} { tk_messageBox -message "Woo hoo!" -type ok set ::gameOn 0 after cancel [after info] ;# Stop the timer } } } # When placing a flag: if mine decrease remainder count. proc flag rc { $::tbl tag celltag FLAG $rc incr ::cntVar -1 } proc isFlagged rc { $::tbl tag includes FLAG $rc } proc isntClicked rc { $::tbl tag includes NC $rc } # Remove a flag. If was correctly place, have one more to find again proc deflag rc { incr ::cntVar } proc getCell {x y} { global table set c [expr ($x - 2) /$table(cellSize) ] set r [expr ($y - 2) /$table(cellSize) ] set table(col) $c; set table(row) $r set table(rc) $r,$c puts "$x,$y" } proc showMines {} { global map tbl table for {set r 0} {$r < $table(rows)} {incr r} { for {set c 0} {$c < $table(cols)} {incr c} { set rc $r,$c if {[isFlagged $rc]} { if { $map($rc) ne "M" } { $tbl tag celltag NOMINE $rc } } elseif { $map($rc) eq "M"} { $tbl tag celltag MINE $rc } } } } proc showZero {r c} { global tbl table map offsets tab lappend toDo $r $c set inList($r,$c) 1 while {[llength $toDo] } { set r [lindex $toDo 0] set c [lindex $toDo 1] set rc $r,$c $tbl tag celltag C0 $rc set toDo [lreplace $toDo 0 1] foreach cell [neighbors $r $c] { if {[isFlagged $cell]} {deflag $cell } if { ![info exists inList($cell)] } { if { $map($cell) eq "0" } { foreach {nr nc} [split $cell ,] {} lappend toDo $nr $nc set inList($cell) 1 } else { set tab($cell) $map($cell) $tbl tag celltag "C$map($cell)" $cell $tbl tag celltag "C$map($rc)" $rc } } } } } # Pressing down left button makes us worry bind $tbl <ButtonPress-1> { if { $::gameOn } { $::rstBtn configure -image $::images(worry) } break } # Releasing left button reveals our fate bind $tbl <ButtonRelease-1> { getCell %x %y click $table(row),$table(col) break } proc click {rc} { global tbl table map images foreach {r c} [split $rc ,] {} if {$::gameOn && ! [isFlagged $rc]} { if { $map($rc) eq "M" } { showMines $::rstBtn configure -image $images(dead) $tbl tag celltag BOOM $rc set ::gameOn 0 after cancel [after info] ;# Stop the timer } elseif { $map($rc) > 0 } { set tab($rc) $map($rc) $tbl tag celltag "C$tab($rc)" $rc $::rstBtn configure -image $images(smiley) } else { showZero $table(row) $table(col) $::rstBtn configure -image $images(smiley) } checkWin } } # Pressing right button, toggles NC<->FLAG bind $tbl <3> { global tbl table tab if $::gameOn { getCell %x %y set rc $table(rc) if {[isFlagged $rc ]} { deflag $rc $tbl tag celltag NC $rc } elseif { [isntClicked $rc ] } { flag $rc } } checkWin break ;# Avoid hiliting cell } # Middle button sweeps bind $tbl <2> { getCell %x %y sweep $table(row) $table(col) break } proc sweep {r c} { global table map if {!$::gameOn || [isFlagged $r,$c] || [isntClicked $r,$c]} return set flags 0 set notClicked [list] foreach cell [neighbors $r $c] { if {[isFlagged $cell]} { incr flags } elseif {[isntClicked $cell]} { lappend notClicked $cell } } if {$flags != $map($r,$c)} return foreach neighbor $notClicked { click $neighbor } } proc neighbors {r c} { global table set result [list] foreach {dr dc} $table(offsets) { set nr [expr $r + $dr] if {$nr < 0 || $nr >= $table(rows) } continue set nc [expr $c + $dc] if {$nc < 0 || $nc >= $table(cols) } continue lappend result $nr,$nc } return $result } proc getImage {tag imageData} { if {[catch {image create photo [list gif $tag] -data $imageData} image]} return return $image } set im [getImage FLAG { R0lGODlhEAAQAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8EAgAGAgAIAgAKAgAMAg AOAgAABAACBAAEBAAGBAAIBAAKBAAMBAAOBAAABgACBgAEBgAGBgAIBgAKBgAMBgAOBgAACAACCA AECAAGCAAICAAKCAAMCAAOCAAACgACCgAECgAGCgAICgAKCgAMCgAOCgAADAACDAAEDAAGDAAIDA AKDAAMDAAODAAADgACDgAEDgAGDgAIDgAKDgAMDgAODgAAAAQCAAQEAAQGAAQIAAQKAAQMAAQOAA QAAgQCAgQEAgQGAgQIAgQKAgQMAgQOAgQABAQCBAQEBAQGBAQIBAQKBAQMBAQOBAQABgQCBgQEBg QGBgQIBgQKBgQMBgQOBgQACAQCCAQECAQGCAQICAQKCAQMCAQOCAQACgQCCgQECgQGCgQICgQKCg QMCgQOCgQADAQCDAQEDAQGDAQIDAQKDAQMDAQODAQADgQCDgQEDgQGDgQIDgQKDgQMDgQODgQAAA gCAAgEAAgGAAgIAAgKAAgMAAgOAAgAAggCAggEAggGAggIAggKAggMAggOAggABAgCBAgEBAgGBA gIBAgKBAgMBAgOBAgABggCBggEBggGBggIBggKBggMBggOBggACAgCCAgECAgGCAgICAgKCAgMCA gOCAgACggCCggECggGCggICggKCggMCggOCggADAgCDAgEDAgGDAgIDAgKDAgMDAgODAgADggCDg gEDggGDggIDggKDggMDggODggAAAwCAAwEAAwGAAwIAAwKAAwMAAwOAAwAAgwCAgwEAgwGAgwIAg wKAgwMAgwOAgwABAwCBAwEBAwGBAwIBAwKBAwMBAwOBAwABgwCBgwEBgwGBgwIBgwKBgwMBgwOBg wACAwCCAwECAwGCAwICAwKCAwMCAwOCAwACgwCCgwECgwGCgwICgwKCgwMCgwOCgwADAwCDAwEDA wGDAwIDAwKDAwP/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAPkALAAAAAAQABAA AAhBAAEIHEiwoMF/CBEaJJiw4UKB/yBKfBgRQMWKCxsmfGhR4UaKGjFmHCgyo0OOKBmWhLiyo0aO Gz8WVHiwZUoAAQEAOw==}] $tbl tag configure FLAG -image $im set im [ getImage MINE { R0lGODlhEAAQAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8EAgAGAgAIAgAKAgAMAg AOAgAABAACBAAEBAAGBAAIBAAKBAAMBAAOBAAABgACBgAEBgAGBgAIBgAKBgAMBgAOBgAACAACCA AECAAGCAAICAAKCAAMCAAOCAAACgACCgAECgAGCgAICgAKCgAMCgAOCgAADAACDAAEDAAGDAAIDA AKDAAMDAAODAAADgACDgAEDgAGDgAIDgAKDgAMDgAODgAAAAQCAAQEAAQGAAQIAAQKAAQMAAQOAA QAAgQCAgQEAgQGAgQIAgQKAgQMAgQOAgQABAQCBAQEBAQGBAQIBAQKBAQMBAQOBAQABgQCBgQEBg QGBgQIBgQKBgQMBgQOBgQACAQCCAQECAQGCAQICAQKCAQMCAQOCAQACgQCCgQECgQGCgQICgQKCg QMCgQOCgQADAQCDAQEDAQGDAQIDAQKDAQMDAQODAQADgQCDgQEDgQGDgQIDgQKDgQMDgQODgQAAA gCAAgEAAgGAAgIAAgKAAgMAAgOAAgAAggCAggEAggGAggIAggKAggMAggOAggABAgCBAgEBAgGBA gIBAgKBAgMBAgOBAgABggCBggEBggGBggIBggKBggMBggOBggACAgCCAgECAgGCAgICAgKCAgMCA gOCAgACggCCggECggGCggICggKCggMCggOCggADAgCDAgEDAgGDAgIDAgKDAgMDAgODAgADggCDg gEDggGDggIDggKDggMDggODggAAAwCAAwEAAwGAAwIAAwKAAwMAAwOAAwAAgwCAgwEAgwGAgwIAg wKAgwMAgwOAgwABAwCBAwEBAwGBAwIBAwKBAwMBAwOBAwABgwCBgwEBgwGBgwIBgwKBgwMBgwOBg wACAwCCAwECAwGCAwICAwKCAwMCAwOCAwACgwCCgwECgwGCgwICgwKCgwMCgwOCgwADAwCDAwEDA wGDAwIDAwKDAwP/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAPkALAAAAAAQABAA AAg9APMJHEiwoMGDCBMqXGgQgEKHAyEKlBgxH4CLFy0iBPAPY8aNHj8WDEnyIEmKI0uatOhR40iC FFG+ZKgwIAA7}] $tbl tag configure MINE -image $im $tbl tag configure BOOM -image $im -bg red set im [ getImage NOMINE { R0lGODdhEAAQAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/ /////4iIiIiIiIiIiIiIiIgIiICICICAiAgIAACIiIgAAACIiIgAAACAiAgAAACAiAgAAACAiAgA AACAiAgAAA+IiIgAAACIiIgAAICAiAgIAIgIiICICIiIiIiIiIiIiIiIiP8AAAAAAB8ACwgBALXI MCPzMAAAAAEEfgAAAAUEvoUADwAAAAAAAAAAAAAAAAAAEAAAEAAAAQAAEAAAAP///wAAAAAAEAAA DwsBZv8QALXcACMBeAAAAAAA//8AAAAAAAAAAAAA//8AAAAAAAAAAP///////wAAAAAAAP//AAD/ /wAAAAAAAAAAAAAAAAAAAAAAAAAAAKcBWQAQALXcALUm2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAQABAA AAhOABEIHEiwoMGDBgEQBKBwYMOBCRIwnKgwosGIDyhGTJAw4kSLCRl69PjQ4UeSJQVqRBkSwEiJ KVWiBHkR5smaFBkioImgpE6HCIMKPRgQADs=}] $tbl tag configure NOMINE -image $im set images(smiley) [getImage SMILEY { R0lGODlhIAAgAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8EAgAGAgAIAgAKAgAMAg AOAgAABAACBAAEBAAGBAAIBAAKBAAMBAAOBAAABgACBgAEBgAGBgAIBgAKBgAMBgAOBgAACAACCA AECAAGCAAICAAKCAAMCAAOCAAACgACCgAECgAGCgAICgAKCgAMCgAOCgAADAACDAAEDAAGDAAIDA AKDAAMDAAODAAADgACDgAEDgAGDgAIDgAKDgAMDgAODgAAAAQCAAQEAAQGAAQIAAQKAAQMAAQOAA QAAgQCAgQEAgQGAgQIAgQKAgQMAgQOAgQABAQCBAQEBAQGBAQIBAQKBAQMBAQOBAQABgQCBgQEBg QGBgQIBgQKBgQMBgQOBgQACAQCCAQECAQGCAQICAQKCAQMCAQOCAQACgQCCgQECgQGCgQICgQKCg QMCgQOCgQADAQCDAQEDAQGDAQIDAQKDAQMDAQODAQADgQCDgQEDgQGDgQIDgQKDgQMDgQODgQAAA gCAAgEAAgGAAgIAAgKAAgMAAgOAAgAAggCAggEAggGAggIAggKAggMAggOAggABAgCBAgEBAgGBA gIBAgKBAgMBAgOBAgABggCBggEBggGBggIBggKBggMBggOBggACAgCCAgECAgGCAgICAgKCAgMCA gOCAgACggCCggECggGCggICggKCggMCggOCggADAgCDAgEDAgGDAgIDAgKDAgMDAgODAgADggCDg gEDggGDggIDggKDggMDggODggAAAwCAAwEAAwGAAwIAAwKAAwMAAwOAAwAAgwCAgwEAgwGAgwIAg wKAgwMAgwOAgwABAwCBAwEBAwGBAwIBAwKBAwMBAwOBAwABgwCBgwEBgwGBgwIBgwKBgwMBgwOBg wACAwCCAwECAwGCAwICAwKCAwMCAwOCAwACgwCCgwECgwGCgwICgwKCgwMCgwOCgwADAwCDAwEDA wGDAwIDAwKDAwP/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAPkALAAAAAAgACAA AAiSAPMJHEiwoMGDCBMqXMiwocOHEAkCmEgxokGK+zJqrGgRgMaPIAFE9AiyZEaRDkmaNIlyocqV LF3CnLmv5cGXNV9OjJkQ586NOE/2pEnTpkSiM40ODIp0I0KmTWsO3QjTp0KdVUNeDcmypNKCPlX+ /Pj1otexZFNCPQtxrdOOaE9OtAiW4ly6ePPq3cu3r9++AQEAOw==}] set images(worry) [getImage WORRY { R0lGODlhIAAgAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8EAgAGAgAIAgAKAgAMAg AOAgAABAACBAAEBAAGBAAIBAAKBAAMBAAOBAAABgACBgAEBgAGBgAIBgAKBgAMBgAOBgAACAACCA AECAAGCAAICAAKCAAMCAAOCAAACgACCgAECgAGCgAICgAKCgAMCgAOCgAADAACDAAEDAAGDAAIDA AKDAAMDAAODAAADgACDgAEDgAGDgAIDgAKDgAMDgAODgAAAAQCAAQEAAQGAAQIAAQKAAQMAAQOAA QAAgQCAgQEAgQGAgQIAgQKAgQMAgQOAgQABAQCBAQEBAQGBAQIBAQKBAQMBAQOBAQABgQCBgQEBg QGBgQIBgQKBgQMBgQOBgQACAQCCAQECAQGCAQICAQKCAQMCAQOCAQACgQCCgQECgQGCgQICgQKCg QMCgQOCgQADAQCDAQEDAQGDAQIDAQKDAQMDAQODAQADgQCDgQEDgQGDgQIDgQKDgQMDgQODgQAAA gCAAgEAAgGAAgIAAgKAAgMAAgOAAgAAggCAggEAggGAggIAggKAggMAggOAggABAgCBAgEBAgGBA gIBAgKBAgMBAgOBAgABggCBggEBggGBggIBggKBggMBggOBggACAgCCAgECAgGCAgICAgKCAgMCA gOCAgACggCCggECggGCggICggKCggMCggOCggADAgCDAgEDAgGDAgIDAgKDAgMDAgODAgADggCDg gEDggGDggIDggKDggMDggODggAAAwCAAwEAAwGAAwIAAwKAAwMAAwOAAwAAgwCAgwEAgwGAgwIAg wKAgwMAgwOAgwABAwCBAwEBAwGBAwIBAwKBAwMBAwOBAwABgwCBgwEBgwGBgwIBgwKBgwMBgwOBg wACAwCCAwECAwGCAwICAwKCAwMCAwOCAwACgwCCgwECgwGCgwICgwKCgwMCgwOCgwADAwCDAwEDA wGDAwIDAwKDAwP/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAPkALAAAAAAgACAA AAiPAPMJHEiwoMGDCBMqXMiwocOHEAkCmEgxokGK+zJqrGgRgMaPIAFE9AiyZEaRDkmaNIlyocp9 L0+WbInwZcyNMxWq9DjxY8+YNC+GtHkTZsKiK1nWTMp049KmTIMORAr15NGqSq9iDalzJsWvObtu tcqQatayZn1KFRvVYj6MQ9dC/DrRrd27ePPq3cuXb0AAOw== }] set images(dead) [getImage DEAD { R0lGODlhIAAgAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8EAgAGAgAIAgAKAgAMAg AOAgAABAACBAAEBAAGBAAIBAAKBAAMBAAOBAAABgACBgAEBgAGBgAIBgAKBgAMBgAOBgAACAACCA AECAAGCAAICAAKCAAMCAAOCAAACgACCgAECgAGCgAICgAKCgAMCgAOCgAADAACDAAEDAAGDAAIDA AKDAAMDAAODAAADgACDgAEDgAGDgAIDgAKDgAMDgAODgAAAAQCAAQEAAQGAAQIAAQKAAQMAAQOAA QAAgQCAgQEAgQGAgQIAgQKAgQMAgQOAgQABAQCBAQEBAQGBAQIBAQKBAQMBAQOBAQABgQCBgQEBg QGBgQIBgQKBgQMBgQOBgQACAQCCAQECAQGCAQICAQKCAQMCAQOCAQACgQCCgQECgQGCgQICgQKCg QMCgQOCgQADAQCDAQEDAQGDAQIDAQKDAQMDAQODAQADgQCDgQEDgQGDgQIDgQKDgQMDgQODgQAAA gCAAgEAAgGAAgIAAgKAAgMAAgOAAgAAggCAggEAggGAggIAggKAggMAggOAggABAgCBAgEBAgGBA gIBAgKBAgMBAgOBAgABggCBggEBggGBggIBggKBggMBggOBggACAgCCAgECAgGCAgICAgKCAgMCA gOCAgACggCCggECggGCggICggKCggMCggOCggADAgCDAgEDAgGDAgIDAgKDAgMDAgODAgADggCDg gEDggGDggIDggKDggMDggODggAAAwCAAwEAAwGAAwIAAwKAAwMAAwOAAwAAgwCAgwEAgwGAgwIAg wKAgwMAgwOAgwABAwCBAwEBAwGBAwIBAwKBAwMBAwOBAwABgwCBgwEBgwGBgwIBgwKBgwMBgwOBg wACAwCCAwECAwGCAwICAwKCAwMCAwOCAwACgwCCgwECgwGCgwICgwKCgwMCgwOCgwADAwCDAwEDA wGDAwIDAwKDAwP/78KCgpICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAPkALAAAAAAgACAA AAiVAPMJHEiwoMGDCBMqXMiwocOHEAkCmEgxokGK+zJqrGgRgMaPIAFE9AiyZEaRDkmaNIlyocqV LF2G3KfS48uTCm/SnFmy5cGbNjfuDJlQJ8yYP48qpYnQ6NKNTZ/C9FnQKcaVVKv2rGk0q0SeYKHm FIr1o9eLUnE2nCh1IkSnZi3mu2rWrdyBFDne3cu3r9+/gANDDAgAOw== }] proc initGame {} { global tbl images table map tab array set map {} set t $tbl # initialise mine array with extra 'guard' cells around entire area. for { set r -1 } { $r <= $table(rows) } { incr r } { for { set c -1 } { $c <= $table(cols) } { incr c } { set map($r,$c) "0" set tab($r,$c) "" } } set mxr [expr $table(rows) - 1] set mxc [expr $table(cols) - 1] set ::cntVar 0 while { $::cntVar < $::initMines } { set r [::math::random 0 $mxr ] set c [::math::random 0 $mxc ] if { $map($r,$c) ne "M"} { incr ::cntVar set map($r,$c) "M" # increase adj. mine count for all neighboring cells which don't contain mines foreach {dr dc} $table(offsets) { set nr [expr $r + $dr] set nc [expr $c + $dc] if { $map($nr,$nc) ne "M" } { incr map($nr,$nc)} } } } # inititialize the array, titles, and celltags for {set r 0} {$r < $table(rows)} {incr r} { for {set c 0} {$c < $table(cols)} {incr c} { $tbl tag celltag NC $r,$c # set tab($r,$c) $map($r,$c); # Uncomment for cheat mode } } set ::gameOn 1 set ::timer 0 $::rstBtn configure -image $images(smiley) -command initGame after 1000 tick } proc tick {} { incr ::timer after 1000 tick } initGame