knightstour.tcl 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. # Copyright (C) 2008 Pat Thoyts <patthoyts@users.sourceforge.net>
  2. #
  3. # Calculate a Knight's tour of a chessboard.
  4. #
  5. # This uses Warnsdorff's rule to calculate the next square each
  6. # time. This specifies that the next square should be the one that
  7. # has the least number of available moves.
  8. #
  9. # Using this rule it is possible to get to a position where
  10. # there are no squares available to move into. In this implementation
  11. # this occurs when the starting square is d6.
  12. #
  13. # To solve this fault an enhancement to the rule is that if we
  14. # have a choice of squares with an equal score, we should choose
  15. # the one nearest the edge of the board.
  16. #
  17. # If the call to the Edgemost function is commented out you can see
  18. # this occur.
  19. #
  20. # You can drag the knight to a specific square to start if you wish.
  21. # If you let it repeat then it will choose random start positions
  22. # for each new tour.
  23. package require Tk 8.5
  24. # Return a list of accessible squares from a given square
  25. proc ValidMoves {square} {
  26. set moves {}
  27. foreach pair {{-1 -2} {-2 -1} {-2 1} {-1 2} {1 2} {2 1} {2 -1} {1 -2}} {
  28. set col [expr {($square % 8) + [lindex $pair 0]}]
  29. set row [expr {($square / 8) + [lindex $pair 1]}]
  30. if {$row > -1 && $row < 8 && $col > -1 && $col < 8} {
  31. lappend moves [expr {$row * 8 + $col}]
  32. }
  33. }
  34. return $moves
  35. }
  36. # Return the number of available moves for this square
  37. proc CheckSquare {square} {
  38. variable visited
  39. set moves 0
  40. foreach test [ValidMoves $square] {
  41. if {[lsearch -exact -integer $visited $test] == -1} {
  42. incr moves
  43. }
  44. }
  45. return $moves
  46. }
  47. # Select the next square to move to. Returns -1 if there are no available
  48. # squares remaining that we can move to.
  49. proc Next {square} {
  50. variable visited
  51. set minimum 9
  52. set nextSquare -1
  53. foreach testSquare [ValidMoves $square] {
  54. if {[lsearch -exact -integer $visited $testSquare] == -1} {
  55. set count [CheckSquare $testSquare]
  56. if {$count < $minimum} {
  57. set minimum $count
  58. set nextSquare $testSquare
  59. } elseif {$count == $minimum} {
  60. set nextSquare [Edgemost $nextSquare $testSquare]
  61. }
  62. }
  63. }
  64. return $nextSquare
  65. }
  66. # Select the square nearest the edge of the board
  67. proc Edgemost {a b} {
  68. set colA [expr {3-int(abs(3.5-($a%8)))}]
  69. set colB [expr {3-int(abs(3.5-($b%8)))}]
  70. set rowA [expr {3-int(abs(3.5-($a/8)))}]
  71. set rowB [expr {3-int(abs(3.5-($b/8)))}]
  72. return [expr {($colA * $rowA) < ($colB * $rowB) ? $a : $b}]
  73. }
  74. # Display a square number as a standard chess square notation.
  75. proc N {square} {
  76. return [format %c%d [expr {97 + $square % 8}] \
  77. [expr {$square / 8 + 1}]]
  78. }
  79. # Perform a Knight's move and schedule the next move.
  80. proc MovePiece {dlg last square} {
  81. variable visited
  82. variable delay
  83. variable continuous
  84. $dlg.f.txt insert end "[llength $visited]. [N $last] .. [N $square]\n" {}
  85. $dlg.f.txt see end
  86. $dlg.f.c itemconfigure [expr {1+$last}] -state normal -outline black
  87. $dlg.f.c itemconfigure [expr {1+$square}] -state normal -outline red
  88. $dlg.f.c coords knight [lrange [$dlg.f.c coords [expr {1+$square}]] 0 1]
  89. lappend visited $square
  90. set next [Next $square]
  91. if {$next ne -1} {
  92. variable aid [after $delay [list MovePiece $dlg $square $next]]
  93. } else {
  94. $dlg.tf.b1 configure -state normal
  95. if {[llength $visited] == 64} {
  96. variable initial
  97. if {$initial == $square} {
  98. $dlg.f.txt insert end "Closed tour!"
  99. } else {
  100. $dlg.f.txt insert end "Success\n" {}
  101. if {$continuous} {
  102. after [expr {$delay * 2}] [namespace code \
  103. [list Tour $dlg [expr {int(rand() * 64)}]]]
  104. }
  105. }
  106. } else {
  107. $dlg.f.txt insert end "FAILED!\n" {}
  108. }
  109. }
  110. }
  111. # Begin a new tour of the board given a random start position
  112. proc Tour {dlg {square {}}} {
  113. variable visited {}
  114. $dlg.f.txt delete 1.0 end
  115. $dlg.tf.b1 configure -state disabled
  116. for {set n 0} {$n < 64} {incr n} {
  117. $dlg.f.c itemconfigure $n -state disabled -outline black
  118. }
  119. if {$square eq {}} {
  120. set square [expr {[$dlg.f.c find closest \
  121. {*}[$dlg.f.c coords knight] 0 65]-1}]
  122. }
  123. variable initial $square
  124. after idle [list MovePiece $dlg $initial $initial]
  125. }
  126. proc Stop {} {
  127. variable aid
  128. catch {after cancel $aid}
  129. }
  130. proc Exit {dlg} {
  131. Stop
  132. destroy $dlg
  133. }
  134. proc SetDelay {new} {
  135. variable delay [expr {int($new)}]
  136. }
  137. proc DragStart {w x y} {
  138. $w dtag selected
  139. $w addtag selected withtag current
  140. variable dragging [list $x $y]
  141. }
  142. proc DragMotion {w x y} {
  143. variable dragging
  144. if {[info exists dragging]} {
  145. $w move selected [expr {$x - [lindex $dragging 0]}] \
  146. [expr {$y - [lindex $dragging 1]}]
  147. variable dragging [list $x $y]
  148. }
  149. }
  150. proc DragEnd {w x y} {
  151. set square [$w find closest $x $y 0 65]
  152. $w coords selected [lrange [$w coords $square] 0 1]
  153. $w dtag selected
  154. variable dragging ; unset dragging
  155. }
  156. proc CreateGUI {} {
  157. catch {destroy .knightstour}
  158. set dlg [toplevel .knightstour]
  159. wm title $dlg "Knights tour"
  160. wm withdraw $dlg
  161. set f [ttk::frame $dlg.f]
  162. set c [canvas $f.c -width 240 -height 240]
  163. text $f.txt -width 10 -height 1 -background white \
  164. -yscrollcommand [list $f.vs set] -font {Arial 8}
  165. ttk::scrollbar $f.vs -command [list $f.txt yview]
  166. variable delay 600
  167. variable continuous 0
  168. ttk::frame $dlg.tf
  169. ttk::label $dlg.tf.ls -text Speed
  170. ttk::scale $dlg.tf.sc -from 8 -to 2000 -command [list SetDelay] \
  171. -variable [namespace which -variable delay]
  172. ttk::checkbutton $dlg.tf.cc -text Repeat \
  173. -variable [namespace which -variable continuous]
  174. ttk::button $dlg.tf.b1 -text Start -command [list Tour $dlg]
  175. ttk::button $dlg.tf.b2 -text Exit -command [list Exit $dlg]
  176. set square 0
  177. for {set row 7} {$row != -1} {incr row -1} {
  178. for {set col 0} {$col < 8} {incr col} {
  179. if {(($col & 1) ^ ($row & 1))} {
  180. set fill tan3 ; set dfill tan4
  181. } else {
  182. set fill bisque ; set dfill bisque3
  183. }
  184. set coords [list [expr {$col * 30 + 4}] [expr {$row * 30 + 4}] \
  185. [expr {$col * 30 + 30}] [expr {$row * 30 + 30}]]
  186. $c create rectangle $coords -fill $fill -disabledfill $dfill \
  187. -width 2 -state disabled
  188. }
  189. }
  190. catch {eval font create KnightFont -size -24}
  191. $c create text 0 0 -font KnightFont -text "\u265e" \
  192. -anchor nw -tags knight -fill black -activefill "#600000"
  193. $c coords knight [lrange [$c coords [expr {1 + int(rand() * 64)}]] 0 1]
  194. $c bind knight <ButtonPress-1> [namespace code [list DragStart %W %x %y]]
  195. $c bind knight <Motion> [namespace code [list DragMotion %W %x %y]]
  196. $c bind knight <ButtonRelease-1> [namespace code [list DragEnd %W %x %y]]
  197. grid $c $f.txt $f.vs -sticky news
  198. grid rowconfigure $f 0 -weight 1
  199. grid columnconfigure $f 1 -weight 1
  200. grid $f - - - - - -sticky news
  201. set things [list $dlg.tf.ls $dlg.tf.sc $dlg.tf.cc $dlg.tf.b1]
  202. if {![info exists ::widgetDemo]} {
  203. lappend things $dlg.tf.b2
  204. if {[tk windowingsystem] ne "aqua"} {
  205. set things [linsert $things 0 [ttk::sizegrip $dlg.tf.sg]]
  206. }
  207. }
  208. pack {*}$things -side right
  209. if {[tk windowingsystem] eq "aqua"} {
  210. pack configure {*}$things -padx {4 4} -pady {12 12}
  211. pack configure [lindex $things 0] -padx {4 24}
  212. pack configure [lindex $things end] -padx {16 4}
  213. }
  214. grid $dlg.tf - - - - - -sticky ew
  215. if {[info exists ::widgetDemo]} {
  216. grid [addSeeDismiss $dlg.buttons $dlg] - - - - - -sticky ew
  217. }
  218. grid rowconfigure $dlg 0 -weight 1
  219. grid columnconfigure $dlg 0 -weight 1
  220. bind $dlg <Control-F2> {console show}
  221. bind $dlg <Return> [list $dlg.tf.b1 invoke]
  222. bind $dlg <Escape> [list $dlg.tf.b2 invoke]
  223. bind $dlg <Destroy> [namespace code [list Stop]]
  224. wm protocol $dlg WM_DELETE_WINDOW [namespace code [list Exit $dlg]]
  225. wm deiconify $dlg
  226. tkwait window $dlg
  227. }
  228. if {![winfo exists .knightstour]} {
  229. if {![info exists widgetDemo]} { wm withdraw . }
  230. set r [catch [linsert $argv 0 CreateGUI] err]
  231. if {$r} {
  232. tk_messageBox -icon error -title "Error" -message $err
  233. }
  234. if {![info exists widgetDemo]} { exit $r }
  235. }