[Keith Vetter] 2005-11-18 : Somehow my family acquired the board game ''Junior Labyrinth''. We have a lot of fun playing it despite not having any instructions and lacking a few pieces. I thought I'd try writing a tcl version of the game. This was one of those fun projects that started off small and incrementally grew bigger as I added ''just one more feature''. Initially it was just the sliding tiles (see also [Shifting Maze]), then stippling for the brick look, then moving players, then.... The next thing I knew I had a complete game. Except that I still don't know how the game is officially played, so I used the rules that we use in our house. The objective is to be the first player to collect 15 gems. The players rotate taking turns. A players turn consists of two parts, first sliding a tile to change the maze and then moving the piece to capture the gem. A player's turn is over when he either captures the gem or he presses the DONE button. ---- ##+########################################################################## # # Labyrinth.tcl -- Plays Junior Labyrinth # by Keith Vetter, Nov 2005 # package require Tk set S(title) "Junior Labyrinth" set S(version) "1.0" set S(sz) 100 ;# Tile size set S(wall) [expr {$S(sz) / 4.0}] ;# Wall thickness set S(pad) 2 ;# Space between tiles set S(m) $S(sz) ;# Margin set S(n) 5 ;# How many rows and columns set S(nn) [expr {$S(n)-1}] set S(bsize) [expr {$S(n)*$S(sz) + $S(nn)*$S(pad)}] set S(blink,on) 2000 set S(blink,off) 500 set S(delay) 10 ;# Time between animation steps set S(step) 2 ;# Animation step size set S(goal) 15 ;# Winning total set S(players) 2 ;# How many players set S(state) pick set S(key) "" set S(turn) [expr {$S(players)-1}] array set COLORS { board yellow . saddlebrown bg green4 arrow yellow txt deepskyblue gem skyblue brick red mortar black score,bg black score,fg white player,0 magenta player,1 green player,2 cyan player,3 red } array set TILES {corner 8 tee 7 line 2} set FIXED {0 0 rb 0 2 lbr 0 4 lb 2 0 trb 2 2 lr 2 4 tlb 4 0 tr 4 2 trl 4 4 tl} array set RAND {c {tr tl rb lb} t {trl trb rlb tlb} l {lr tb}} array set DIR {Up {-1 0} Down {1 0} Left {0 -1} Right {0 1}} array set DIR2 {1,0 Down -1,0 Up 0,1 Right 0,-1 Left} array set SCORE {0 0 1 0 2 0 3 0} set PI [expr {acos(-1)}] proc DoDisplay {} { global S COLORS option add *Canvas.highlightThickness 0 wm title . $S(title) . config -bg $COLORS(.) DoMenus GetBoxesBMP frame .s -bg $COLORS(score,bg) -bd 2 -relief ridge -padx 5 set w [expr {$S(m) + $S(bsize) + $S(m)}] set h [expr {$S(m) + $S(bsize) + $S(m)}] canvas .title -width $w -bd 0 -bg $COLORS(bg) ShadedText .title [expr {$w/2}] 10 $COLORS(txt) black -text $S(title) \ -font {Times 42 bold} -anchor n -tag title .title config -height [lindex [.title bbox title] 3] canvas .c -width $w -height $h -bd 0 -bg $COLORS(bg) .c create rect -10 -10 10000 10000 -fill $COLORS(bg) -tag bg image create photo ::img::rot -data $::rotImage button .rot -image ::img::rot -command RotateTile .c create window [LocateTile rotate rotate 1] -tag rotate -window .rot button .done -text "Done" -font {Helvetica 12 bold} \ -command {NewState done} -height 2 .c create window [LocateTile extra extra 1] -tag done -window .done MakeScoreArea label .msg -textvariable S(msg) -font {Times 32 bold} -bg $COLORS(bg) foreach {x0 y0} [LocateTile 0 0] break foreach {. . x1 y1} [LocateTile $S(nn) $S(nn)] break .c create rect $x0 $y0 $x1 $y1 -tag board -fill $COLORS(board) \ -outline $COLORS(board) foreach {r c d} {-1 1 s -1 3 s 5 1 n 5 3 n 1 -1 e 3 -1 e 1 5 w 3 5 w} { MakeArrow $r $c $d } NewBoard pack .s -side right -fill y pack .title -side top -fill x pack .c -side top -fill both -expand 1 -pady .2i -padx .2i \ -ipadx 5 -ipady 5 pack .msg -side bottom -fill x foreach key {Up Down Left Right} { bind .c [list KeyPress %K press] bind .c [list KeyPress %K release] } bind all {console show} focus .c wm geom . +5+5 } proc DoMenus {} { option add *Menu.tearOff 0 menu .menu . config -menu .menu menu .menu.game .menu add cascade -label "Game" -menu .menu.game -underline 0 .menu.game add command -label "New Game" -command NewGame set m .menu.game.players menu $m .menu.game add cascade -label "Players" -menu $m -underline 0 foreach n {2 3 4} { $m add radio -label "$n Players" \ -variable S(players) \ -value $n \ -underline 0 \ -command NewGame } .menu.game add separator .menu.game add command -label "Exit" -command exit menu .menu.help .menu add cascade -label "Help" -menu .menu.help -underline 0 .menu.help add command -label "Help" -command Help .menu.help add command -label "About" -command About } proc MakePlayers {} { foreach {who row col} {0 0 0 1 0 4 2 4 0 3 4 4} { .c delete player,$who if {$who >= $::S(players)} continue DrawPlayer $who $row $col set ::PLAYERS($who) [list $row $col] .c bind player,$who [list BDown $who] .c bind player,$who [list BMotion $who %x %y] .c bind player,$who [list BUp $who] } } proc MakeScoreArea {} { global S COLORS SCORE eval destroy [winfo child .s] set csize 75 label .s.title -text Score -font {Times 42 bold underline} \ -bg $COLORS(score,bg) -fg $COLORS(score,fg) grid .s.title - -sticky ew -row 1 for {set who 0} {$who < $S(players)} {incr who} { canvas .s.$who -width $csize -height $csize \ -bg $COLORS(score,bg) -bd 5 -relief flat DrawPlayerAt 10 10 $csize $csize $COLORS(player,$who) tag .s.$who label .s.l$who -textvariable SCORE($who) -font {Times 36 bold} \ -bg $COLORS(score,bg) -fg $COLORS(score,fg) -width 3 grid .s.$who .s.l$who -sticky news -pady 20 } grid rowconfigure .s 60 -weight 1 } proc ShadedText {w x y fg bg args} { set cbg [ $w cget -bg ] eval [list $w create text $x $y -fill $bg] $args eval [list $w create text [incr x -2] [incr y -2] -fill $cbg] $args eval [list $w create text [incr x -1] [incr y -1] -fill $fg] $args } proc FillBoard {} { global S FIXED BOARD TILES RAND .c delete win unset -nocomplain BOARD set id -1 foreach {row col doors} $FIXED { MakeTile "fixed,[incr id]" [LocateTile $row $col] $doors set BOARD(doors,$row,$col) $doors } set S(deck) [Shuffle [concat [string repeat "c " $TILES(corner)] \ [string repeat "t " $TILES(tee)] \ [string repeat "l " $TILES(line)]]] set idx -1 for {set row 0} {$row < $S(n)} {incr row} { for {set col 0} {$col < $S(n)} {incr col} { if {[info exists BOARD(doors,$row,$col)]} continue set type [lindex $S(deck) [incr idx]] set doors [lindex $RAND($type) \ [expr {int(rand() * [llength $RAND($type)])}]] MakeTile "tile,$idx" [LocateTile $row $col] $doors set BOARD(doors,$row,$col) $doors set BOARD(tag,$row,$col) "tile,$idx" } } set type [lindex $S(deck) [incr idx]] set doors [lindex $RAND($type) \ [expr {int(rand() * [llength $RAND($type)])}]] MakeTile "tile,$idx" [LocateTile extra extra] $doors set BOARD(doors,extra) $doors set BOARD(tag,extra) "tile,$idx" } proc LocateTile {row col {mid 0}} { global S if {$row eq "extra"} { return [LocateTile $S(n) $S(n) $mid] } if {$row eq "rotate"} { return [LocateTile $S(n) $S(nn) $mid] } set x0 [expr {$S(m) + $col*($S(sz)+$S(pad))}] set y0 [expr {$S(m) + $row*($S(sz)+$S(pad))}] if {$mid} { return [list [expr {$x0 + $S(sz)/2}] [expr {$y0 + $S(sz)/2}]] } set x1 [expr {$x0 + $S(sz)}] set y1 [expr {$y0 + $S(sz)}] return [list $x0 $y0 $x1 $y1] } proc Canvas2Tile {x y} { global S set sz [expr {$S(sz) + $S(pad)}] set row [expr {int(($y - $S(m) + $S(pad)/2 - 1) / $sz)}] set col [expr {int(($x - $S(m) + $S(pad)/2 - 1) / $sz)}] return [list $row $col] } proc MakeArrow {row col dir} { array set D { s {2 1 2 4} n {2 3 2 0} e {1 2 4 2} w {3 2 0 2} } foreach {x(0) y(0) x(4) y(4)} [LocateTile $row $col] break set x(1) [expr {$x(0) + ($x(4)-$x(0))/4}] set x(2) [expr {($x(0) + $x(4))/2}] set x(3) [expr {$x(4) - ($x(4)-$x(0))/4}] set y(1) [expr {$y(0) + ($y(4)-$y(0))/4}] set y(2) [expr {($y(0) + $y(4))/2}] set y(3) [expr {$y(4) - ($y(4)-$y(0))/4}] set xy {} foreach {dx dy} $D($dir) { lappend xy $x($dx) $y($dy) } set id [.c create line $xy -tag arrow -width 10 -capstyle round \ -fill $::COLORS(arrow) -arrow last -arrowshape {16 24 11}] .c bind $id <1> [list Shift $row $col] } proc MakeTile {tag rect doors} { global S COLORS array set PARTS { lr {n s} bt {e w} br {Lnw se} bl {Lne sw} rt {Lsw ne} lt {Lse nw} lrt {s nw ne} brt {w ne se} blr {n se sw} blt {e nw sw} } .c delete $tag .c create rect $rect -width 0 -fill $COLORS(board) -tag $tag set doors [join [lsort [split $doors ""]] ""] foreach part $PARTS($doors) { set xy [GetSubCoords $rect $part] .c create poly $xy -tag $tag -fill $COLORS(brick) -outline $COLORS(mortar) .c create poly $xy -tag $tag -fill $COLORS(mortar) -stipple @$S(bmp) } } proc GetSubCoords {rect what} { array set XY { n {$x0 $y0 $x1 $y0 $x1 $yq1 $x0 $yq1} s {$x0 $yq2 $x1 $yq2 $x1 $y1 $x0 $y1} w {$x0 $y0 $xq1 $y0 $xq1 $y1 $x0 $y1} e {$xq2 $y0 $x1 $y0 $x1 $y1 $xq2 $y1} ne {$xq2 $y0 $x1 $y0 $x1 $yq1 $xq2 $yq1} nw {$x0 $y0 $xq1 $y0 $xq1 $yq1 $x0 $yq1} se {$xq2 $yq2 $x1 $yq2 $x1 $y1 $xq2 $y1} sw {$x0 $yq2 $xq1 $yq2 $xq1 $y1 $x0 $y1} Lsw {$x0 $y0 $xq1 $y0 $xq1 $yq2 $x1 $yq2 $x1 $y1 $x0 $y1} Lnw {$x0 $y0 $x1 $y0 $x1 $yq1 $xq1 $yq1 $xq1 $y1 $x0 $y1} Lne {$x0 $y0 $x1 $y0 $x1 $y1 $xq2 $y1 $xq2 $yq1 $x0 $yq1} Lse {$xq2 $y0 $x1 $y0 $x1 $y1 $x0 $y1 $x0 $yq2 $xq2 $yq2} } foreach {x0 y0 x1 y1} $rect break set xq1 [expr {$x0+$::S(wall)}] set xq2 [expr {$x1-$::S(wall)}] set yq1 [expr {$y0+$::S(wall)}] set yq2 [expr {$y1-$::S(wall)}] set xy [subst -nocommands -nobackslashes $XY($what)] return $xy } proc Shuffle { l } { set len [llength $l] set len2 $len for {set i 0} {$i < $len-1} {incr i} { set n [expr {int($i + $len2 * rand())}] incr len2 -1 # Swap elements at i & n set temp [lindex $l $i] lset l $i [lindex $l $n] lset l $n $temp } return $l } proc NewBoard {} { FillBoard MakePlayers RandomGem } proc NewState {new} { global S COLORS BOARD SCORE if {$new eq "gem"} { BUp $S(turn) KillGem incr SCORE($S(turn)) if {$SCORE($S(turn)) >= $S(goal)} { Winner $S(turn) set S(state) win .c itemconfig done -window {} return } RandomGem set new done } if {$new eq "done"} { .s.$S(turn) config -relief flat set S(turn) [expr {($S(turn)+1) % $S(players)}] .s.$S(turn) config -relief ridge #.s.cturn itemconfig player -fill $COLORS(player,$S(turn)) \ -outline $COLORS(player,$S(turn)) set S(msg) "Click Arrow to Slide Tiles" .c raise arrow bg .c raise $BOARD(tag,extra) bg .c raise player,$S(turn) .c raise gem .c itemconfig rotate -window .rot .c itemconfig done -window {} set S(state) pick BlinkArrows 0 } else { set S(state) $new .c lower arrow bg .c lower $BOARD(tag,extra) bg .c itemconfig rotate -window {} if {$S(state) eq "move"} { set S(msg) "Move Player to Capture Gem" .c itemconfig done -window .done } } } proc Shift {row col} { if {$::S(state) ne "pick"} return NewState shift if {$row == -1} { ShiftCol $col 1 } if {$row == $::S(n)} { ShiftCol $col -1 } if {$col == -1} { ShiftRow $row 1 } if {$col == $::S(n)} { ShiftRow $row -1 } NewState move } proc ShiftRow {row dir} { if {$dir == 1} { MoveTileTo $::BOARD(tag,extra) $row -1 set u {extra save 4 extra 3 4 2 3 1 2 0 1 save 0} } else { MoveTileTo $::BOARD(tag,extra) $row 5 set u {0 save 1 0 2 1 3 2 4 3 extra 4 save extra} } set tags [GetRowColTags row $row] set players [PlayersOnRowCol row $row] foreach player $players { lappend tags "player,$player" } set gems [GemsOnRowCol row $row] foreach tag $gems { lappend tags $tag } update; after 500 DoShift $tags $dir 0 vwait ::S(vwait) foreach {from to} $u { set from [Index $row $from] set to [Index $row $to] UpdateBoard $from $to } MoveTileTo $::BOARD(tag,extra) extra extra UpdatePlayers $players $dir 0 UpdateGem $gems $dir 0 } proc ShiftCol {col dir} { if {$dir == 1} { MoveTileTo $::BOARD(tag,extra) -1 $col set u {extra save 4 extra 3 4 2 3 1 2 0 1 save 0} } else { MoveTileTo $::BOARD(tag,extra) $::S(n) $col set u {0 save 1 0 2 1 3 2 4 3 extra 4 save extra} } set tags [GetRowColTags col $col] set players [PlayersOnRowCol col $col] foreach player $players { lappend tags "player,$player" } set gems [GemsOnRowCol col $col] foreach tag $gems { lappend tags $tag } update ; after 500 DoShift $tags 0 $dir vwait ::S(vwait) foreach {from to} $u { set from [Index $from $col] set to [Index $to $col] UpdateBoard $from $to } MoveTileTo $::BOARD(tag,extra) extra extra UpdatePlayers $players 0 $dir UpdateGem $gems 0 $dir } proc UpdateGem {who dx dy} { if {$who eq {}} return foreach {r c} [split $::GEM ","] break incr r $dy incr c $dx set off 0 if {$r < 0} { set off 1 ; set r $::S(nn)} if {$r > $::S(nn)} { set off 1 ; set r 0} if {$c < 0} { set off 1 ; set c $::S(nn)} if {$c > $::S(nn)} { set off 1 ; set c 0} set ::GEM "$r,$c" if {$off} { DrawGem $r $c } } proc UpdatePlayers {who dx dy} { foreach player $who { foreach {r c} $::PLAYERS($player) break incr r $dy incr c $dx set off 0 if {$r < 0} { set off 1 ; set r $::S(nn)} if {$r > $::S(nn)} { set off 1 ; set r 0} if {$c < 0} { set off 1 ; set c $::S(nn)} if {$c > $::S(nn)} { set off 1 ; set c 0} set ::PLAYERS($player) [list $r $c] if {$off} { DrawPlayer $player $r $c } } } proc PlayersOnRowCol {what which} { set cells [CellsOnRowCol $what $which] set result {} for {set player 0} {$player < $::S(players)} {incr player} { foreach {r c} $::PLAYERS($player) break set n [lsearch $cells "$r,$c"] if {$n != -1} { lappend result $player } } return $result } proc GemsOnRowCol {what which} { set cells [CellsOnRowCol $what $which] if {[lsearch $cells $::GEM] != -1} { return gem} return {} } proc CellsOnRowCol {what which} { set cells {} for {set idx 0} {$idx < $::S(n)} {incr idx} { if {$what eq "row"} { lappend cells $which,$idx } else { lappend cells $idx,$which } } return $cells } proc GetRowColTags {what who} { set tags $::BOARD(tag,extra) for {set idx 0} {$idx < $::S(n)} {incr idx} { if {$what eq "row"} { lappend tags $::BOARD(tag,$who,$idx) } else { lappend tags $::BOARD(tag,$idx,$who) } } return $tags } proc UpdateBoard {from to} { global BOARD set BOARD(doors,$to) $BOARD(doors,$from) set BOARD(tag,$to) $BOARD(tag,$from) } proc Index {row col} { if {$row eq "extra" || $col eq "extra"} { return "extra"} if {$row eq "save" || $col eq "save"} { return "save"} return "$row,$col" } proc DoShift {tags dx dy {fast 0} {soFar 0}} { set dd [expr {$fast ? 3*$::S(step) : $::S(step)}] set max [expr {$::S(sz) + $::S(pad)}] if {$soFar >= $max} { set ::S(vwait) 1 ; return} incr soFar $dd if {$soFar > $max} { set dd [expr {$dd + $max - $soFar}]} set dxx [expr {$dd*$dx}] set dyy [expr {$dd*$dy}] foreach tag $tags { .c move $tag $dxx $dyy } after $::S(delay) [list DoShift $tags $dx $dy $fast $soFar] } proc MoveTileTo {id row col} { foreach {x1 y1} [.c coords $id] break foreach {x2 y2} [LocateTile $row $col] break set dx [expr {$x2 - $x1}] set dy [expr {$y2 - $y1}] .c move $id $dx $dy .c raise $id board } proc DrawPlayer {who row col} { global S COLORS .c delete player,$who set pad [expr {-$S(wall)-2}] foreach {x0 y0 x1 y1} [Expand [LocateTile $row $col] $pad] break DrawPlayerAt $x0 $y0 $x1 $y1 $COLORS(player,$who) player,$who .c move player,$who [expr {2*($who-1)}] 0 } proc DrawPlayerAt {x0 y0 x1 y1 color tag {W .c}} { set w [expr {$x1 - $x0}] set h [expr {$y1 - $y0}] set xm [expr {($x1 + $x0)/2}] set ym [expr {($y1 + $y0)/2}] set w8 [expr {$h/8}] set cy [expr {$y0 + $w8}] set cxy [Expand [list $xm $cy $xm $cy] $w8] set mxy [list $xm $cy \ [expr {$xm-1*$w/4}] $ym \ [expr {$xm-1*$w/8}] $ym \ [expr {$xm-3*$w/8}] $y1 \ [expr {$xm+3*$w/8}] $y1 \ [expr {$xm+1*$w/8}] $ym \ [expr {$xm+1*$w/4}] $ym \ $xm $cy] $W create poly $mxy -tag $tag -fill $color -outline $color $W create oval $cxy -tag $tag -fill $color -outline $color } proc DrawGem {row col} { global S COLORS .c delete gem set pad [expr {-$S(wall)-2}] foreach {x0 y0 x1 y1} [Expand [LocateTile $row $col] $pad] break DrawGemAt ? $x0 $y0 $x1 $y1 $COLORS(gem) gem } proc DrawGemAt {which x0 y0 x1 y1 color tag {W .c}} { set D(0) { {3 0 3 3 0 3} {3 0 3 3 6 3} {3 6 3 3 0 3} {3 6 3 3 6 3} } set D(1) { {2 1 4 1 5 2 5 4 4 5 2 5 1 4 1 2} {0 1 1 0 2 1 1 2} {1 0 5 0 4 1 2 1} {0 1 1 2 1 4 0 5} {5 0 6 1 5 2 4 1} {1 4 2 5 1 6 0 5} {2 5 4 5 5 6 1 6} {6 1 6 5 5 4 5 2} {5 4 6 5 5 6 4 5} } set D(2) { {1 0 2 0 2 1 0 1} {3 6 0 1 2 1} {2 0 4 0 4 1 2 1} {3 6 2 1 4 1} {4 0 5 0 6 1 4 1} {3 6 4 1 6 1} } set D(3) { {1 0 2 2 0 1} {1 0 5 0 4 2 2 2} {0 1 2 2 2 4 0 5} {5 0 6 1 4 2} {2 2 4 2 4 4 2 4} {2 4 1 6 0 5} {6 1 6 5 4 4 4 2} {2 4 4 4 5 6 1 6} {4 4 6 5 5 6} } if {$which eq "?"} { set which [expr {int(rand() * [llength [array names D]])}] } if {$which != 0} { foreach {x0 y0 x1 y1} [Expand [list $x0 $y0 $x1 $y1] -2] break } for {set i 0} {$i < 7} {incr i} { ;# Get every 1/6 interval set x($i) [expr {$x0 + $i * ($x1-$x0)/6}] set y($i) [expr {$y0 + $i * ($y1-$y0)/6}] } set idx -1 set darken [expr {70 / [llength $D($which)]}] foreach coords $D($which) { incr idx set xy(x,$idx) {} foreach {a b} $coords { lappend xy(x,$idx) $x($a) $y($b) } set c [::tk::Darken $color [expr {110-$darken*$idx}]] $W create poly $xy(x,$idx) -fill $c -tag [list $tag gem$idx] \ -outline black } } proc KillGem {} { foreach {x0 y0 x1 y1} [.c bbox gem] break set xrad [expr {($x1 - $x0)/2}] set yrad [expr {($y1 - $y0)/2}] set xm [expr {($x1 + $x0)/2}] set ym [expr {($y1 + $y0)/2}] while {1} { .c scale gem $xm $ym .95 .95 update foreach {l . r} [.c bbox gem] break if {$r - $l < 15} break after 30 } .c delete gem foreach step {.25 .5 .75} rad {1 2 3} { for {set theta 0} {$theta < 360} {incr theta 60} { set x [expr {$xm + $step*$xrad*cos($theta * $::PI/180)}] set y [expr {$ym + $step*$yrad*sin($theta * $::PI/180)}] set xy [Expand [list $x $y] $rad] .c create oval $xy -tag gem -fill $::COLORS(gem) } update after 30 .c delete gem } } proc RandomGem {} { global S GEM PLAYERS COLORS set bad {} for {set who 0} {$who < $S(players)} {incr who} { lappend bad [join $PLAYERS($who) ","] } while {1} { set row [expr {int(rand() * $S(n))}] set col [expr {int(rand() * $S(n))}] set n [lsearch $bad "$row,$col"] if {$n == -1} break } set COLORS(gem) [LightColor] DrawGem $row $col set GEM "$row,$col" } proc Expand {xy d} { foreach {x0 y0 x1 y1} [concat $xy $xy] break return [list [expr {$x0-$d}] [expr {$y0-$d}] \ [expr {$x1+$d}] [expr {$y1+$d}]] } proc MovePlayer {who dir {fast 0}} { global S PLAYERS BOARD DIR GEM if {$S(state) ne "move"} return NewState "moving" while {1} { foreach {row col} $PLAYERS($who) break foreach {drow dcol} $DIR($dir) break set row2 [expr {$row + $drow}] set col2 [expr {$col + $dcol}] # Check legal move: on board w/o a wall if {$row2 < 0 || $row2 >= $S(n)|| $col2 < 0 || $col2 >= $S(n)} break set door [string map {U t D b R r L l} [string range $dir 0 0]] if {[string first $door $BOARD(doors,$row,$col)] == -1} break set door [string map {t b b t r l l r} $door] if {[string first $door $BOARD(doors,$row2,$col2)] == -1} break DoShift player,$who $dcol $drow $fast vwait ::S(vwait) set PLAYERS($who) [list $row2 $col2] if {$GEM eq "$row2,$col2"} { NewState gem return } if {$S(key) eq "" || $S(key) eq "mouse"} break set dir $S(key) } NewState "move" } proc KeyPress {who how} { global S if {$how eq "release" && $S(key) eq $who} { set S(key) "" } elseif {$how eq "press" && $S(key) ne $who && $S(key) ne "mouse"} { set S(key) $who if {$S(state) eq "move"} { after 1 MovePlayer $S(turn) $who } } } # # Stippling w/ custom bitmaps seems to require the bmp to be saved in # the file system. Here we write the bmp file to the tmp directory. # proc GetBoxesBMP {} { global S set boxesBMP { #define boxes_width 11 #define boxes_height 9 static char boxes_bits = { 0xff, 0x07, 0xff, 0x07, 0x60, 0x00, 0x60, 0x00, 0xff, 0x07, 0xff, 0x07, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00 } } set bmpName "JLBoxes.bmp" if {[file exists $bmpName]} { set S(bmp) $bmpName return } switch $::tcl_platform(platform) { unix { set tmpdir /tmp # or even $::env(TMPDIR), at times. } macintosh { set tmpdir $::env(TRASH_FOLDER) ;# a better place? } default { set tmpdir [pwd] catch {set tmpdir $::env(TMP)} catch {set tmpdir $::env(TEMP)} } } set fname [file join $tmpdir $bmpName] if {[file exists $fname]} { set S(bmp) $fname return } catch { set fout [open $fname w] puts $fout $boxesBMP close $fout } if {[file exists $fname]} { set S(bmp) $fname return } set emsg "ERROR: cannot create brick bitmap" tk_messageBox -title $S(title) -icon error -message $emgs exit } proc BlinkArrows {cnt} { global S COLORS if {$S(state) ne "pick"} return if {[incr cnt] > 31} return set col [expr {$cnt & 1 ? $COLORS(arrow) : $COLORS(bg)}] .c itemconfig arrow -fill $col after $S(blink,[expr {$cnt & 1 ? "on" : "off"}]) [list BlinkArrows $cnt] } ##+########################################################################## # # LightColor -- returns a "light" color. A light color is one in which # the V value in the HSV color model is greater than .7. Since the V # value is the maximum of R,G,B we simply need at least one of R,G,B # must be greater than .7. # proc LightColor {} { set light [expr {255 * .7}] ;# Value threshold while {1} { set r [expr {int (255 * rand())}] set g [expr {int (255 * rand())}] set b [expr {int (255 * rand())}] if {$r > $light || $g > $light || $b > $light} break } return [format "\#%02x%02x%02x" $r $g $b] } proc RotateTile {} { global BOARD set BOARD(doors,extra) [string map {r b b l l t t r} $BOARD(doors,extra)] MakeTile $BOARD(tag,extra) [LocateTile extra extra] $BOARD(doors,extra) } set rotImage { R0lGODlhLgAqALMAABQWjJCQmMvMy0tKfwQDyayprO7t7nRzdCQmdHR2jwQC+wkHqi0rmdnb2ba4 tvz+/CH5BAAAAAAALAAAAAAuACoAAwT/8MlJq7046827/2CYIchwBIL4OGzrvmwjGYtiKwsTyN+x EMCgcJiY1W43QMLwCRCQUGjx0ThGFQjHp2C93qaNpxcX+DgA469EIB4Tph3BIK0AI35pwqEJaEfh DwYCCQNdSARlHAZ9eFeAFA5zVwspG5I4hjaPFQd+NwMbAl0LmZsVAZkEWhmXN6RSHE5RoBiLUQgF aGocrTYLPBYOnqoPAgi7G8JRiRYJUQATDZKmF8ewF70KexMGA28ezlC0FtnMEwfUFmxQDBgMUQUX THGeC+5QBPEqElVQ9tjL9q3x1A7gtX3hkBRs9myeimzjKhQYpk+EqIDydCFBsC/brwwHeKJ8CzFx loZ1/ip2cJBKpcGUHs5ciYihgcZDB4BdaJAgE45KG0pOGlCgwTwDBgocuHnI3IYEnvwxYDCAQSMv NHnR2frJoQcDULm6GeAVRACmYnGk8yDN51igAtck6EMHwAG4cSkYcBCgqtUcVBMUKJu3sOHDiPNG AAA7 } proc NewGame {} { foreach aid [after info] { after cancel $aid} MakeScoreArea NewBoard array set ::SCORE {0 0 1 0 2 0 3 0} set ::S(turn) [expr {$::S(players)-1}] NewState done } proc About {} { set msg "$::S(title) v$::S(version)\n\nby Keith Vetter\nNovember 2005\n" tk_messageBox -title "About $::S(title)" -message $msg } proc Help {} { global S catch {destroy .help} toplevel .help wm title .help "$S(title) Help" set t .help.t text $t -relief raised -wrap word -width 60 -height 23 \ -padx 10 -pady 10 -cursor {} button .help.ok -text OK -width 8 -command {destroy .help} pack .help.ok -side bottom -pady 10 pack $t -side top -expand 1 -fill both set bold "[font actual [$t cget -font]] -weight bold" set italic "[font actual [$t cget -font]] -slant italic" $t tag config title -justify center -foregr red -font "Arial 20 bold" $t tag configure title2 -justify center -font "Arial 12 bold" $t tag configure heading -font $bold $t tag configure n -lmargin1 10 -lmargin2 10 $t tag configure bullet -lmargin1 20 -lmargin2 30 $t insert end "$S(title)\n" title $t insert end "by Keith Vetter\n\n" title2 $t insert end "Based on a children's game by Ravensburger.\n\n" set h "Ojective\n" set m "To be the first player to collect $S(goal) gems.\n\n" $t insert end $h heading $m n set h "Starting a New Game\n" set b "o Select Game->New Game\n" append b "o Select Game->Players to change the number of players\n\n" $t insert end $h heading $b bullet #Playing set h "Playing the Game\n" set m "The players rotate taking turns. A player's turn consists " append m "of two parts:\n" set b "1. Sliding a tile to change the maze.\n" append b "2. Moving the player to try to capture the gem.\n\n" set m2 "A players turn ends when:\n" set b2 "o The gem is captured.\n" append b2 "o The player presses the DONE button.\n\n" $t insert end $h heading $m n $b bullet $m2 n $b2 bullet $t config -state disabled focus $t } proc Winner {who} { global S COLORS foreach {x0 y0 x1 y1} [LocateTile [expr {$S(n)/2}] [expr {$S(n)/2-1}]] break DrawPlayerAt $x0 $y0 $x1 $y1 $COLORS(player,$who) win .c set ym [expr {($y1 + $y0)/2}] .c create text $x1 $ym -tag win -text "Wins!" -font {Times 42 bold} \ -fill white -anchor w set xy [Expand [.c bbox win] 30] .c create rect $xy -fill black -outline white -width 10 -tag {win x} .c lower x win set S(msg) "" } proc BDown {who} { if {$::S(turn) != $who} return if {$::S(state) ne "move"} return set color [::tk::Darken $::COLORS(player,$who) 80] .c itemconfig player,$who -width 5 -outline $color } proc BMotion {who x y} { global S PLAYERS DIR2 if {$S(turn) != $who} return if {$S(state) ne "move"} return foreach {row0 col0} $PLAYERS($who) break foreach {row1 col1} [Canvas2Tile [.c canvasx $x] [.c canvasy $y]] break set drow [expr {$row1-$row0}] set dcol [expr {$col1-$col0}] set drow [expr {$drow > 0 ? 1 : $drow < 0 ? -1 : 0}] set dcol [expr {$dcol > 0 ? 1 : $dcol < 0 ? -1 : 0}] if {$drow > 1 || $drow < -1} return if {$dcol > 1 || $dcol < -1} return if {$drow == 0 && $dcol == 0} return if {$drow != 0 && $dcol != 0} return set S(key) "mouse" MovePlayer $who $DIR2($drow,$dcol) 1 set S(key) "" } proc BUp {who} { .c itemconfig player,$who -width 1 -outline $::COLORS(player,$who) } DoDisplay NewGame ---- [JM] I could not see the "Done" button, looks like it is in the bottom of the GUI, just out of sight, and of reach )-: ---- [Category Games]