[Keith Vetter] 2006-02-09 : Here's a fun litle game where you have to rotate nodes
to connect up wires to light up every node. I wrote a somewhat similar game
earlier called [Lights Out].
Fun project for several reasons: 1) implemented a minimum spanning
tree algorithm (Prim's) 2) implemented a depth-first search
and 3) did a poor man's anti-aliasing to get nicer looking lines.
----
[ABU] Really nice. Just for the blockheads like me, could you provide a "solve" command with a slow animation ?
[KPV] There's already a built it cheat command.
Just hold the control key down and click on a node. That will
cause that node to orient itself correctly.
----
[GS] 2010-10-23 : A slightly modified version [http://wfr.tcl.tk/fichiers/pub/lightson-winmo.tcl] for touchscreen Windows Mobile device with [eTcl]. Lights bulbs are larger and expert mod has been disabled because it is too larger to fit in smartphone screen resolution.
[http://wfr.tcl.tk/fichiers/images/lightson.gif]----
[Jeff Smith] 2019-04-23 : Below is an online demo using [CloudTk]
2017-12-03 : A<<in online dehtmo ol>>
<if the rabovme heisght="650" avawilable adth="650" [src="https://172cloudtk-app.104tcl-lang.5.86:5443org/cloudtk/VNC?session=new&Tk=LightsOn]" allowfullscreen></iframe>
<<inlinehtml>>
----
[uniquename] 2013aug01
Here is a right-clipped image of the 'desktop' version --- along with a (partly-clipped) image of the help popup.
[vetter_LightsOn_screenshot_647x461.jpg]
This is the initial setting of the GUI. The little rods rotate to 'hexagonal-angles' --- and lights come on as more segments are attached,
to complete the circuit to the central light.
======
##+##########################################################################
#
# lightsOn.tcl -- based on http://pyva.net/eng/pc/lights.html
# by Keith Vetter
#
package require Tk
set G(n) 7
array set S {title "Lights On" w 600 h 600 vdist 50 hdist 28}
array set DRC {0 {0 2} 1 {-1 1} 2 {-1 -1} 3 {0 -2} 4 {1 -1} 5 {1 1}}
array set COLORS {ray1 \#4C526C ray2 \#8C96B4}
proc DoDisplay {} {
global S
wm title . $S(title)
canvas .c -bg black -width $S(w) -height $S(h) -highlightthickness 0
label .t -textvariable ::G(tmsg) -font {Times 18 bold} \
-fg cyan -bg black -anchor w -padx 10
pack .t -side top -fill x
pack .c -side top -fill both -expand 1
bind all <F2> NewGame
bind all <F3> {console show}
DoMenus
bind .c <Configure> {ReCenter %W %h %w} ;# Force 0,0 to be in center
update
}
proc DoMenus {} {
menu .m -tearoff 0
. configure -menu .m ;# Attach menu to main window
.m add cascade -menu .m.game -label "Game" -underline 0
.m add cascade -menu .m.help -label "Help" -underline 0
menu .m.game -tearoff 0
.m.game add command -label "New Game" -under 0 -command NewGame -acc "F2"
.m.game add separator
.m.game add radiobutton -label "Easy" -under 0 -variable ::G(n) -value 5 -command NewGame
.m.game add radiobutton -label "Normal" -under 0 -variable ::G(n) -value 7 -command NewGame
.m.game add radiobutton -label "Hard" -under 0 -variable ::G(n) -value 9 -command NewGame
.m.game add radiobutton -label "Expert" -under 0 -variable ::G(n) -value 11 -command NewGame
.m.game add separator
.m.game add command -label "Exit" -under 1 -command exit
menu .m.help -tearoff 0
.m.help add command -label "About" -under 0 -command About
}
proc DrawBoard {} {
global B
.c delete all
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
DrawCellRays $row $col
.c create image [Cell2XY $row $col] -image ::img::ball \
-tag b$row,$col
.c bind b$row,$col <1> [list Click $row $col 1]
.c bind r$row,$col <1> [list Click $row $col 1]
.c bind b$row,$col <3> [list Click $row $col -1]
.c bind r$row,$col <3> [list Click $row $col -1]
.c bind b$row,$col <Control-1> [list Cheat $row $col]
.c bind r$row,$col <Control-1> [list Cheat $row $col]
}
}
LightUp
}
proc DrawRay {row col dir} {
foreach {r1 c1} [MoveDir $row $col $dir] break
foreach {x0 y0} [Cell2XY $row $col] break
foreach {x1 y1} [Cell2XY $r1 $c1] break
set x2 [expr {$x0 + ($x1-$x0)/2}] ;# Halfway point
set y2 [expr {$y0 + ($y1-$y0)/2}]
set tag r$row,$col
.c create line $x0 $y0 $x2 $y2 -tag [list r1 $tag] -width 4 -fil $::COLORS(ray1)
.c create line $x0 $y0 $x2 $y2 -tag [list r2 $tag] -width 2 -fil $::COLORS(ray2)
.c lower r$row,$col
}
proc DrawCellRays {row col} {
.c delete r$row,$col
foreach dir $::B(r,$row,$col) {
DrawRay $row $col $dir
}
}
proc Cell2XY {row col} {
set x [expr {$col * $::S(hdist)}]
set y [expr {-$row * $::S(vdist)}]
return [list $x $y]
}
##+##########################################################################
#
# Recenter -- keeps 0,0 at the center of the canvas during resizing
#
proc ReCenter {W h w} { ;# Called by configure event
set h2 [expr {$h / 2}]
set w2 [expr {$w / 2}]
$W config -scrollregion [list -$w2 -$h2 $w2 $h2]
}
proc Timer {{restart 0}} {
global G
foreach aid [after info] { after cancel $aid }
if {$restart} {
set G(start) [clock seconds]
}
if {$G(state) ne "play"} return
set tlen [expr {[clock seconds] - $G(start)}]
set G(tmsg) [clock format $tlen -format "%M:%S"]
after 1000 Timer
}
proc NewGame {} {
MakeBoard
DrawBoard
set ::G(state) play
Timer 1
}
##+##########################################################################
#
# MakeBoard -- figures out all the nodes and all edges, then deletes
# edges leaving a minimum spanning tree and finally randomly rotates
# all nodes.
#
proc MakeBoard {} {
global B G EDGES
unset -nocomplain B
set EDGES {}
set G(n2) [expr {($G(n)+1)/2}] ;# Handy constants
set G(-n2) [expr {1-$G(n2)}]
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set B(c,$row,$col) 1
}
}
# Compute all legal edges
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set B(r,$row,$col) [FindNeighbors $row $col]
foreach dir {0 1 2} {
if {[lsearch $B(r,$row,$col) $dir] > -1} {
lappend EDGES [list $row $col $dir]
}
}
}
}
set G(cnt) [llength [array names B c*]]
# Now convert full graph into minimum spanning tree
set mst [MST]
foreach e $EDGES {
if {[lsearch $mst $e] == -1} { ;# Is edge not in MST???
eval RemoveEdge $e ;# ...then remove it
}
}
# Now rotate randomly every node
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set B(rr,$row,$col) $B(r,$row,$col)
RotateCell $row $col [expr {int(rand()*6)}]
}
}
}
##+##########################################################################
#
# FindNeighbors -- returns list of all legal directions from this node
#
proc FindNeighbors {row col} {
global B
set dirs {}
foreach dir {0 1 2 3 4 5} {
foreach {r c} [MoveDir $row $col $dir] break
if {[info exists B(c,$r,$c)]} { lappend dirs $dir }
}
return $dirs
}
proc MoveDir {row col dir} {
foreach {dr dc} $::DRC($dir) break
set r1 [expr {$row + $dr}]
set c1 [expr {$col + $dc}]
return [list $r1 $c1]
}
##+##########################################################################
#
# Click -- handles clicking on a node
#
proc Click {row col rdir} {
RotateCell $row $col $rdir
DrawCellRays $row $col
LightUp
}
proc Cheat {row col} {
set ::B(r,$row,$col) $::B(rr,$row,$col)
DrawCellRays $row $col
LightUp
}
proc RotateCell {row col rdir} {
global B
set dirs {}
foreach dir $B(r,$row,$col) {
lappend dirs [expr {($dir + $rdir) % 6}]
}
set B(r,$row,$col) $dirs
}
##+##########################################################################
#
# LightUp -- does a depth-first-search to find all connected components
#
proc LightUp {} {
global DFS
DFS
set solved 1
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set img "::img::ball2"
if {! $DFS($row,$col)} {
set solved 0
set img "::img::ball"
}
.c itemconfig b$row,$col -image $img
}
}
if {$solved} Victory
}
proc Victory {} {
global G
if {$G(state) ne "play"} return
set G(state) solved
Flash
}
proc Flash {{cnt 3} {delay 200}} {
for {set i 0} {$i < $cnt} {incr i} {
.c config -bg red
.t config -bg red
update
after $delay
.c config -bg black
.t config -bg black
update
after $delay
}
}
##+##########################################################################
#
# RemoveEdge -- removes an edge for a given node and the reverse node
#
proc RemoveEdge {row col dir} {
global B
foreach {r c} [MoveDir $row $col $dir] break
set opp [expr {($dir + 3) % 6}]
set n [lsearch $B(r,$row,$col) $dir]
set B(r,$row,$col) [lreplace $B(r,$row,$col) $n $n]
set n [lsearch $B(r,$r,$c) $opp]
set B(r,$r,$c) [lreplace $B(r,$r,$c) $n $n]
}
##+##########################################################################
#
# DFS -- does a depth-first-search from the origin. This can blow out
# the recursion limit for big board sizes.
#
proc DFS {} {
global DFS
unset -nocomplain DFS
set DFS(cnt) 0
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set DFS($row,$col) 0
}
}
_DFS 0 0
}
##+##########################################################################
#
# _DFS -- recursive caller for DFS
#
proc _DFS {row col} {
global B DFS
set DFS($row,$col) 1
incr DFS(cnt)
foreach dir $B(r,$row,$col) {
if {! [IsPath $row $col $dir]} continue
foreach {r c} [MoveDir $row $col $dir] break
if {$DFS($r,$c) != 0} continue
_DFS $r $c
}
}
##+##########################################################################
#
# IsPath -- return true if there is a path from row,col in direction dir
# assumes path exists out of row,col so it checks for off the board
# and is there a matching opposite path from destination
#
proc IsPath {row col dir} {
global B
foreach {r c} [MoveDir $row $col $dir] break
if {! [info exists B(c,$r,$c)]} { return 0 };# Destination off the board
set opp [expr {($dir + 3) % 6}]
set n [lsearch $B(r,$r,$c) $opp]
return [expr {$n > -1 ? 1 : 0}]
}
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 About {} {
set txt "$::S(title)\nby Keith Vetter, February 2006\n\n"
append txt "Turn on all the lights!\n\n"
append txt "Left click: rotate clockwise\n"
append txt "Right click: rotate counter-clockwise\n"
tk_messageBox -message $txt -title About
}
##+##########################################################################
#
# MST -- computes a random minimum spanning tree using Prim's algorithm
#
proc MST {} {
global B EDGES
set mst {}
# Mark all nodes as unvisited
for {set row $::G(-n2)} {$row < $::G(n2)} {incr row} {
set cmax [expr {$::G(n)-1-abs($row)}]
for {set col -$cmax} {$col <= $cmax} {incr col 2} {
set visited($row,$col) 0
}
}
set edges [Shuffle $EDGES]
foreach {r c} [lindex $edges 0] break ;# Start with random node
set visited($r,$c) 1
while {[llength $mst] < $::G(cnt)-1} {
# Find edge out of visited nodes (inefficient but who cares)
for {set i 0} {$i < [llength $edges]} {incr i} {
foreach {r0 c0 dir} [lindex $edges $i] break ;# Start point
foreach {r1 c1} [MoveDir $r0 $c0 $dir] break ;# End point
if {$visited($r0,$c0) != $visited($r1,$c1)} break
}
set edges [lreplace $edges $i $i] ;# Remove from edge list
lappend mst [list $r0 $c0 $dir] ;# Add to our mst
set visited($r0,$c0) 1 ;# Mark nodes as visited
set visited($r1,$c1) 1
}
return $mst
}
################################################################
image create photo ::img::ball -data {
R0lGODlhCgAKALMAACQmJHd3d6SmpAQC/ExKTJGRkcTCxFxeXDk3Oby6vISGhJyenNDQ0GZnZlRW
VKyurCH5BAEAAAMALAAAAAAKAAoAAwQ5cIyWGHtOjmJNekszHN4jLIUSIAWYBkHTEEUBN0d+ODHu
/I4dIQgMHggAo3EHGCB+uR9CAxg6kJIIADs=}
image create photo ::img::ball2 -data {
R0lGODlhCgAKALMAAGRXBJSKBN7MBOzYXLyyBOjkBMq/BN3XBAQC/Pz2fJR6BJSWBPPwBL6aBGRm
BN23BCH5BAEAAAgALAAAAAAKAAoAAwQzECFnzjFAomWtEMFkFQxTCBghkKX5NMaQzPTwPDI9283D
loUX4MYKPjKKm/Kh0AB6QkkEADs=}
DoDisplay
NewGame
return
======
[lights_on android IMG]
Lights On is available for Android on Playstore [https://play.google.com/store/apps/details?id=com.game.lightson]
<<categories>> Games | Application