TkBomb

Here's another silly game (with a slight twist on the original) - Bruce Hartweg


uniquename 2013aug01

This unusual grid deserves an image --- along with an image of a help window.

hartweg_TkBomb_hexgrid_screenshot_267x366.jpg hartweg_TkBomb_helpWindow_screenshot_499x193.jpg

#########################################################################
##
## TkBomb - Hexagonal Variant of Bomb Finding game 
##
#########################################################################
## Change Log: (please add entry and update the TkBombVersion)
##
##  Date       Name           Notes
##  ---------  -------------  --------------------------------------------
##  25Nov2001  B. Hartweg     Initial Release
##  09May2004  MG             (0) Small change to allow it to work as-is with 8.4+
##  28Aug2004  MG             (1) Already "opened" hex's could previously be marked
##
#########################################################################
package require Tk
if { [package vsatisfies [package present Tk] 8.4] } {
     interp alias {} doDarken {} ::tk::Darken
   } else {
     interp alias {} doDarken {} tkDarken
   };# (0)
namespace eval tkbomb {
   variable TkBombVersion 0.3
   variable Bombs
   variable Open ;# (1)
   variable Color
   variable Cos
   variable Sin
   variable Vals
   variable Marks
   variable Scores
   variable Settings
   array set Bombs {}
   array set Marks {}
   array set Scores {}
   array set Settings {
      HexSize 12
      BoardSize 7
      BombPercent 15
      Colors {black blue orange green yellow purple cyan}
   }
   set Deg2Rad [expr {acos(-1) / 180.0}]
   foreach a {0 60 120 180 240 300 360} {
      set ang [expr {$a * $Deg2Rad}]
      set Cos($a) [expr {cos($ang)}]
      set Sin($a) [expr {sin($ang)}]
   }
   set Color(main) [. cget -background]
   set Color(dark) [doDarken $Color(main) 50]
   set Color(light) [doDarken $Color(main) 125]
   font create FNT -family helvetica -weight bold -size -16
}

proc ::tkbomb::GUI {} {
   variable Settings
   variable BombsLeft
   
   wm title . "TkBomb"
   menu .mbar -type menubar
   . config -menu .mbar

   # File Menu
   .mbar add cascade -label File -menu [set m [menu .mbar.f -tearoff 0]]
   $m add command -label "New Game" -command \
   [namespace code newGame]
   $m add command -label Quit -command exit

   # Options Menu
   .mbar add cascade -label Options -menu [set m [menu .mbar.o -tearoff 0]]
   foreach {s n} {small 12 medium 16 large 20} {
      $m add radiobutton -label "$s hexes" -value $n \
      -variable "[namespace which -variable Settings](HexSize)"
   }
   $m add separator
   foreach {s n} {small 5 medium 10 large 15 huge 20} {
      $m add radiobutton -label "$s board" -value $n \
      -variable "[namespace which -variable Settings](BoardSize)"
   }
   $m add separator
   foreach {s n} {easy 10 medium 15 hard 20 difficult 30} {
      $m add radiobutton -label "$s" -value $n \
      -variable "[namespace which -variable Settings](BombPercent)"
   }

   # Help Menu
   .mbar add cascade -label Help -menu [set m [menu .mbar.h -tearoff 0]]
   $m add command -label "About..." -command \
   [namespace code about]

   # widgets
   pack [label .bomb -textvar [namespace which -variable BombsLeft]]
   pack [canvas .c]
   wm resizable . 0 0
}

proc ::tkbomb::hexPoints {x y r} {
   variable Cos
   variable Sin
   foreach a {120 180 240 300 0 60} {
      lappend points [expr {$x+$r*$Cos($a)}] [expr {$y+$r*$Sin($a)}]
   }
   return $points
}

proc ::tkbomb::makeHex {x y r tag} {
   variable Color
   set spts  [hexPoints $x $y [expr {$r-2}]] 
   set lpts  [hexPoints $x $y $r]
   # make shadows for 3-D effect
   .c create polygon $lpts -fill $Color(dark) -tag "Shadow$tag"
   .c create polygon [lrange $lpts 0 7] -fill $Color(light) -tag "Shadow$tag"
   .c create polygon $spts -fill $Color(main) -tag "Top$tag"
   .c create text $x $y -anchor center -text "" -font FNT -tag "Text$tag"
   foreach tg "Top$tag Text$tag" {
      .c bind $tg <Enter> ".c itemconfig Top$tag -fill $Color(light)"
      .c bind $tg <Leave> ".c itemconfig Top$tag -fill $Color(main)"
      .c bind $tg <1> [namespace code "openHex $tag"]
      .c bind $tg <Shift-1> [namespace code "markHex $tag"]
      .c bind $tg <2> [namespace code "markHex $tag"]
      .c bind $tg <3> [namespace code "markHex $tag"]
   }
}

proc ::tkbomb::unsetBindings {} {
   .c bind all <Enter> {  break }
   .c bind all <Leave> {  break }
   .c bind all <1> { break }
   .c bind all <Shift-1> {  break }
   .c bind all <2> {  break }
   .c bind all <3> {  break }
}
proc ::tkbomb::clearBindings {} {
   .c bind all <Enter> {}
   .c bind all <Leave> {}
   .c bind all <1> {}
   .c bind all <Shift-1> {}
   .c bind all <2> {}
   .c bind all <3> {}
}

proc ::tkbomb::openHex {tag} {
   variable Bombs
   variable Vals
   variable Marks
   variable Settings
   variable Open

   if {[info exists Open($tag)]} {
   return; # (1)
      }
   if {[info exists Marks($tag)]} {
      # can't open this
   } elseif {[info exists Bombs($tag)]} {
      .c delete Shadow$tag
      foreach t [array names Marks] {
     if {![info exists Bombs($t)]} {
        .c itemconfig Text$t -text "X" -fill red
     }
      }
      foreach t [array names Bombs] {
     if {![info exists Marks($t)]} {
        .c itemconfig Text$t -text "\u263c" -fill red
     }
      }
      .c itemconfig Top$tag -fill red
      .c itemconfig Text$tag -text "\u263c" -fill black
      unsetBindings
      tk_messageBox -message "You LOSE!"
   } elseif {[info exists Vals($tag)]} {
      .c delete Shadow$tag
      .c delete Top$tag
      set v $Vals($tag)
      unset Vals($tag)
      set Open($tag) 1 ;# (1)
      if {$v>0} {
     .c itemconfig Text$tag -text $v -fill [lindex $Settings(Colors) $v]
      } else {
     .c itemconfig text$tag -text ""
     foreach t [neighbors $tag] {
        openHex $t
     }
      }
      checkWin
   }
}

proc ::tkbomb::markHex {tag} {
   variable Marks
   variable BombsLeft
   variable Open
   
   if {[info exists Open($tag)]} {
   return; # (1)
      }

   if {[info exists Marks($tag)]} {
      unset Marks($tag)
      .c itemconfig Text$tag -text ""
      incr BombsLeft 1
   } else {
      set Marks($tag) 1
      .c itemconfig Text$tag -text "\u263c"
      incr BombsLeft -1
      checkWin
   }
}

proc ::tkbomb::checkWin {} {
   variable Bombs
   variable Marks
   variable Vals
   if {[array size Bombs] == [array size Marks] &&
   [array size Bombs] == [array size Vals] } {
      unsetBindings
      tk_messageBox -message "You WIN!"
   }
}

#
# Given a hex tag - return list of 6 touching hexes
#   NOTE: some of these may NOT be valid (i.e. outside of game boundaries)
#
proc ::tkbomb::neighbors {tag} {
   foreach {c r} [split $tag :] break
   set rm [expr $r-1]
   set rp [expr $r+1]
   set cm [expr $c-1]
   set cp [expr $c+1]
   set res {}
   foreach {v1 v2} {c rm c rp cm rm cm r cp r cp rp} {
      lappend res "[set $v1]:[set $v2]"
   }
   set res
}

proc ::tkbomb::newGame {} {
   variable Bombs
   variable Open ;# (1)
   variable Color
   variable Cos
   variable Sin
   variable Vals
   variable Marks
   variable Settings
   variable NumHexes
   variable Settings
   variable BombsLeft
   
   set xm [expr {$Settings(HexSize)*1.5}]
   set ym [expr {$Settings(HexSize)*$Sin(60)}]
   set sz [expr {round($ym*1.5)}]
   font configure FNT -size -$sz
   
   # reset data
   .c delete all
   clearBindings
   foreach v {Bombs Vals Marks Open} {
      unset -nocomplain $v
      array set $v {}
   } ;# (1) Open added to foreach

   # build hexes
   for {set c 0} {$c < $Settings(BoardSize)} {incr c} {
      set x [expr $c*$xm]
      set R [expr {2*$Settings(BoardSize) - 1 - $c}]
      set Y [expr {$c*$ym}]
      for {set r 0} {$r < $R} {incr r} {
     set y [expr {$Y+$r*$ym*2}]
     set tag  "$c:[expr {$r+$c}]"
     makeHex $x $y $Settings(HexSize) $tag
     set Vals($tag) 0
     if {$c==0} continue
     set tag "-$c:$r"
     makeHex -$x $y $Settings(HexSize) $tag
     set Vals($tag) 0
      }
   }

   # center data & size canvas 
   foreach {x1 y1 x2 y2} [.c bbox all] break
   .c move all [expr {-1*$x1 + 10}] [expr {-1*$y1 + 10}]
   .c config -width [expr {$x2 - $x1 + 20}] -height [expr {$y2 - $y1 + 20}]

   # get # of bombs
   set NumHexes  [array size Vals]
   set BombsLeft [expr {round($Settings(BombPercent)*$NumHexes/100.0)}]
   
   # randomly place bombs
   set l [array names Vals]
   set N 0
   while {$N<$BombsLeft} {
      set i [expr {int(rand()*[llength $l])}]
      set tag [lindex $l $i]
      if {![info exists Bombs($tag)]} {
     set Bombs($tag) 1
     incr N
      }
   }

   # calc numbers around bombs
   foreach tag [array names Vals] {
      if {![info exists Bombs($tag)]} {
     foreach tt [neighbors $tag] {
        catch {incr Vals($tag) $Bombs($tt)}
     }
      }
   }
}

proc ::tkbomb::about {} {
     variable TkBombVersion
     if {[winfo exists .about]} {
     wm deiconify .about
     } else {
     set w [toplevel .about -class dialog]
     wm withdraw $w
     wm transient $w .
     wm title $w "About TkBomb $TkBombVersion"
     button $w.b -text Dismiss -command [list wm withdraw $w]
     text $w.text -height 9 -bd 1 -width 70
     pack $w.b -fill x -side bottom
     pack $w.text -fill both -side left -expand 1
     $w.text tag config center -justify center
     $w.text tag config title -justify center -font {Courier -18 bold}
     $w.text insert 1.0 "About TkBomb $TkBombVersion" title \
           "\n\nCopyright (C) 2001 Bruce B Hartweg <[email protected]>\n" \
           center {
              Find all the hidden Bombs!
              Button 1 click will open a hex & show number of adjacent bombs
              Button 2 or 3 click (or Shift 1) will mark/unmark a hex
           } left
     $w.text config -state disabled
     wm deiconify $w
     }
}

####################################################################################

::tkbomb::GUI
::tkbomb::newGame

Search and replace tkDarken to ::tk::Darken in order to run under Tk8.4 -- Svenn Are Bjerkem

MG May 9th 2004 - Nice game. I made a small change so that Svenn's comment above is no longer necessary; it should work fine under any version (from the POV of ::tk::Darken) now (marked as (0) in the code).


MG August 28th 2004 - Changed so that you can't mark an "opened" hex (one displaying a number) as a possible bomb (marked as (1) in the code)


Stu 2008-5-13 - Fixed the check for Tk 8.4; package present returns a string, not a boolean. (NEM: Removed trailing - from version number, which is an 8.5ism).