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?