PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/hangman/desktop.clj

http://github.com/wagjo/hangman
Clojure | 162 lines | 101 code | 17 blank | 44 comment | 3 complexity | ca814bdcca1efbe8d3b6cfd0b352739e MD5 | raw file
  1. ;; Copyright (C) 2012, Jozef Wagner.
  2. ;;
  3. ;; The use and distribution terms for this software are covered by the
  4. ;; Eclipse Public License 1.0
  5. ;; (http://opensource.org/licenses/eclipse-1.0.php) which can be found
  6. ;; in the file epl-v10.html at the root of this distribution.
  7. ;;
  8. ;; By using this software in any fashion, you are agreeing to be bound
  9. ;; by the terms of this license.
  10. ;;
  11. ;; You must not remove this notice, or any other, from this software.
  12. (ns hangman.desktop
  13. "Desktop GUI for hangman."
  14. (:require [hangman.core :as hangman]
  15. [seesaw.core :as ss]
  16. [clojure.string :as cs]))
  17. ;; There is no official library or API for desktop GUI.
  18. ;; For this tutorial, Seesaw library was chosen.
  19. ;; Forward declarations. Sometimes we need to refer to functions
  20. ;; which are defined later in source code. Use declare for such cases.
  21. (declare form update-state!)
  22. ;; NOTE: By using declare, you can solve forward declarations problem
  23. ;; within one source file. If you have a forward declaration
  24. ;; problem across namespaces, you have a wrong design. Rethink
  25. ;; your namespaces.
  26. ;;;; STEP 1: define event handlers
  27. ;; This function will be called when player clicks on New Game button
  28. (defn on-new-game
  29. "Begins a new game.
  30. Called when new game button is pressed."
  31. [_]
  32. (hangman/new-game!)
  33. (update-state!))
  34. ;; This function will be called when player presses a key
  35. (defn on-next-guess
  36. "Guess next character.
  37. Called when player presses a key."
  38. [_]
  39. (let [guess-widget (ss/select form [:#guess])
  40. value (cs/lower-case (ss/value guess-widget))]
  41. ;; using regular expressions to determine valid input
  42. (when (re-matches #"[a-z]+" (str value))
  43. ;; only try to guess if it is a letter from a to z
  44. (hangman/guess! (first value)))
  45. (ss/value! guess-widget ""))
  46. (update-state!))
  47. ;;;; STEP 2: update game state
  48. (defn get-wrong-guesses
  49. "Returns string informing about wrong guesses."
  50. [info]
  51. (str (if (empty? (:wrong-guesses info))
  52. "no wrong guesses"
  53. ;; also transform to uppercase and sort
  54. (cs/join ", " (->> info
  55. :wrong-guesses
  56. (map cs/upper-case)
  57. sort)))
  58. ;; also print number of wrong guesses left
  59. (when-not (= :lost (:state info))
  60. (str " (" (:wrong-guesses-left info) " left)"))))
  61. (defn update-state!
  62. "Updates game UI, to reflect a current state."
  63. []
  64. ;; find widgets and collect information about current game
  65. (let [guess-widget (ss/select form [:#guess])
  66. new-game-widget (ss/select form [:#new-game])
  67. game-state-widget (ss/select form [:#game-state])
  68. info (hangman/get-game-info)]
  69. ;; print word to guess
  70. (ss/value! (ss/select form [:#word])
  71. (cs/upper-case (cs/join " " (seq (:hint info)))))
  72. ;; print wrong guesses
  73. (ss/value! (ss/select form [:#wrong]) (get-wrong-guesses info))
  74. ;; update controls and game state message
  75. (condp = (:state info)
  76. :won (do
  77. (ss/value! game-state-widget "You have won!")
  78. (ss/config! guess-widget :enabled? false)
  79. ;; low level java interop to change focus
  80. (.requestFocus (ss/to-widget new-game-widget)))
  81. :lost (do
  82. (ss/value! game-state-widget "You have lost!")
  83. (ss/config! guess-widget :enabled? false)
  84. (.requestFocus (ss/to-widget new-game-widget)))
  85. ;; game is still playing
  86. (do
  87. (ss/value! game-state-widget "Make your guess.")
  88. (ss/config! guess-widget :enabled? true)
  89. (.requestFocus (ss/to-widget guess-widget))))))
  90. ;;;; STEP 3: Define form
  91. ;; form defines how the UI will look
  92. (def form
  93. (ss/frame
  94. :title "Hangman in Clojure"
  95. :on-close :exit
  96. :content
  97. (ss/grid-panel
  98. :border 15 :vgap 10 :columns 1
  99. ;; list of widgets
  100. :items [;; write current state of game here
  101. (ss/label :id :game-state
  102. :text "N/A"
  103. :font "ARIAL-16")
  104. ;; word to guess goes here
  105. (ss/label :id :word
  106. :text "N/A"
  107. :font "ARIAL-BOLD-26"
  108. :halign :center)
  109. ;; list of wrong guesses
  110. (ss/label :id :wrong
  111. :text "N/A"
  112. :font "ARIAL-18"
  113. :halign :center)
  114. ;; two widgets in one line
  115. (ss/horizontal-panel
  116. :items [(ss/label :text "Type next guess:"
  117. :font "ARIAL-16")
  118. [:fill-h 20]
  119. ;; input widget, to capture key events
  120. (ss/text :id :guess
  121. :listen [:key-released on-next-guess]
  122. :font "ARIAL-BOLD-16"
  123. :size [30 :by 30])])
  124. ;; New Game button
  125. (ss/button :id :new-game
  126. :text "New Game"
  127. :listen [:action on-new-game]
  128. :font "ARIAL-16")
  129. ;; credits
  130. (ss/label :text "https://github.com/wagjo/hangman"
  131. :font "ARIAL-16"
  132. :halign :right)])))
  133. ;; main desktop functions
  134. (defn start-game!
  135. "Starts a desktop game."
  136. []
  137. (ss/native!)
  138. (update-state!)
  139. (-> form
  140. ss/pack!
  141. ss/show!))
  142. (comment
  143. ;; WARNING: swank will terminate after game window is closed!
  144. (start-game!)
  145. )