I am pretty new to Clojure & am loving it so far. After going through a few resources, I decided to make a hangman game.

I'm wondering how idiomatic is my code & where can I improve it stylistically & reduce complexity. It is divided into two files. Both of them are shown below.


(ns hangman.core
  (:require [clojure.string :as string])
  (:use hangman.play))

(defn exit-screen []
  (println "Exit?\n->(Y)eah\n->(N)ope")
  (case (string/upper-case (read-line))
    "Y" :null-screen
    "N" :menu-screen

(defn play-screen []
  (println "Mode:\n->(1)p\n->(2)p")
  (case (read-line)
    "1" (play-turn (get-cipher :1p) #{} #{})
    "2" (play-turn (get-cipher :2p) #{} #{})
    (println "Enter a valid mode."))

(defn menu-screen []
  (println "Menu:\n->(P)lay\n->(E)xit")
  (case (string/upper-case (read-line))
    "P" :play-screen
    "E" :exit-screen

(defn run-screen [screen]
  (case screen
    :menu-screen (recur (menu-screen))
    :play-screen (recur (play-screen))
    :exit-screen (recur (exit-screen))
    (println "Exiting...")))

(defn -main [& args]
  (run-screen :menu-screen))


(ns hangman.play
  (:require [clojure.string :as str]))

(defn game-over? [cipher right wrong]
    (> (count wrong) 5)
    (= (count (set cipher)) (count right))))

(defn get-move [right wrong]
  (let [move (str/upper-case (read-line))]
    (if (or (contains? right move) (contains? wrong move))
        (println "Already Tried.")
        (recur right wrong))

(defn show-ui [cipher right wrong]
  (println (case (count wrong)
             0 " O \n/|\\\n/ \\"
             1 " O \n/|\\\n  \\"
             2 " O \n/|\\\n    "
             3 " O \n |\\\n    "
             4 " O \n | \n     "
             5 " O \n   \n     "
             6 " X \n   \n     "))
  (println (str "Right: " right))
  (println (str "Wrong: " wrong))
  (doseq [letter cipher]
    (if (contains? right letter)
      (print letter)
      (print " _")))
  (println ""))

(defn play-turn [cipher right wrong]
  (show-ui cipher right wrong)
  (let [move (get-move right wrong)]
    (let [[right wrong]
          (if (.contains cipher move)
            [(conj right move) wrong]
            [right (conj wrong move)])]
      (if-not (game-over? cipher right wrong)
        (recur cipher right wrong)
        (println "Game Over!")))))

(defmulti get-cipher (fn [mode] mode))

(defmethod get-cipher :1p [mode]
  (let [ciphers (str/split (str/trim-newline (slurp "resources/ciphers.txt")) #", ")]
    (str/split (str/upper-case (rand-nth ciphers)) #"")))

(defmethod get-cipher :2p [mode]
  (println "Enter a cipher")
  (str/split (str/upper-case (read-line)) #""))

There is a file called cipher.txt in my resources folder. It contains various ", " separated words that can be used for the game.


apple, ball, cat, dog, elephant, fish, hangman

I feel that my play.clj file is needlessly complicated & is the main source of inelegance.

Playing the game: To select an option, press whatever character is in (). For eg: To select the (P)lay option, press p or P.

PS: Do you know any resource where I can learn good style in Clojure. Preferably something to do with games?


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.