4
\$\begingroup\$

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.

src/core.clj

(ns hangman.core
  (:gen-class)
  (: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
    :exit-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."))
  :menu-screen)

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

src/play.clj

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

(defn game-over? [cipher right wrong]
  (or
    (> (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))
      (do
        (println "Already Tried.")
        (recur right wrong))
      move)))

(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.

resources/cipher.txt

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?

\$\endgroup\$

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.