Skip to content

Instantly share code, notes, and snippets.

@camdez
Last active November 19, 2023 06:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save camdez/6c9366ca7b8944c5b23548b0a678e063 to your computer and use it in GitHub Desktop.
Save camdez/6c9366ca7b8944c5b23548b0a678e063 to your computer and use it in GitHub Desktop.
Example code shape for a functional card game in Clojure
(ns card-game
(:require [clojure.string :as str]))
(def deck
(for [suit ["♣" "♦" "♥" "♠"]
rank ["1" "2" "3" "4" "5" "6" "7" "8" "9" "J" "Q" "K" "A"]]
(str rank suit)))
(defn new-game [player-count hand-size]
{:deck deck
:player-count player-count
:hand-size hand-size
:hands (vec (repeat player-count []))
:cur-player nil
:last-play nil
:round nil
:over? false})
(defn draw! [{:keys [cur-player hands last-play player-count over? round]}]
(if round
(println "Round:" round)
(println "Initial Deal:"))
(dotimes [i player-count]
(let [hand (nth hands i)
cur? (= i cur-player)]
(println (if cur?
"-->"
" ")
(str "Player " i ":")
(str/join " " hand))))
(when last-play
(println "\n Played" (second last-play)))
(when over?
(println "\nGAME OVER!"))
(newline))
;;; Helpers
(defn game-over? [{:keys [hands]}]
(every? empty? hands))
(defn deal-one [s player-num]
(let [[card & rest] (:deck s)]
(if card
(-> s
(update-in [:hands player-num] conj card)
(assoc :deck rest))
(throw (ex-info "No cards to deal" {:s s})))))
(defn play-one [{:keys [hands] :as s} player-num]
(let [hand (nth hands player-num)
card (first hand)]
(if (seq hand)
(-> s
(update-in [:hands player-num] rest)
(assoc :last-play [player-num card]))
(throw (ex-info "No cards to play" {:s s})))))
;;; Actions (state -> state')
(defn shuffle-deck [s]
(update s :deck shuffle))
(defn deal [{:keys [hand-size player-count] :as s}]
(->> (cycle (range player-count))
(take (* player-count hand-size))
(reduce deal-one s)))
(defn play-turn [{:keys [cur-player] :as s}]
(play-one s cur-player))
(defn next-turn [{:keys [cur-player player-count round] :as s}]
(merge s
(cond
(nil? round)
{:round 0, :cur-player 0}
(= (dec player-count) cur-player)
{:round (inc round) :cur-player 0}
:else
{:cur-player (inc cur-player)})))
(defn check-over [s]
(assoc s :over? (game-over? s)))
(defn take-turn [s]
(-> s
(next-turn)
(play-turn)
(check-over)))
;;;
(defn play! []
(loop [s (-> (new-game 2 3)
(shuffle-deck)
(deal))]
(draw! s)
(when-not (:over? s)
(recur (take-turn s)))))
;;;
(defn ddraw
"Debugging version of `draw!` that returns game state."
[s]
(draw! s)
s)
(comment
(play!)
(-> (new-game 2 3)
(shuffle-deck)
(deal)
(ddraw)
(take-turn)
(ddraw)
(take-turn)
(ddraw)
(dissoc :deck)) ; (too long)
;; =>
'{:player-count 2
:hand-size 3
:hands [("A♣" "Q♣") ("Q♦" "3♥")]
:cur-player 1
:last-play [1 "A♠"]
:round 0
:over? false}
)
Initial Deal:
Player 0: J♦ A♦ J♣
Player 1: K♠ K♣ 6♠
Round: 0
--> Player 0: A♦ J♣
Player 1: K♠ K♣ 6♠
Played J♦
Round: 0
Player 0: A♦ J♣
--> Player 1: K♣ 6♠
Played K♠
Round: 1
--> Player 0: J♣
Player 1: K♣ 6♠
Played A♦
Round: 1
Player 0: J♣
--> Player 1: 6♠
Played K♣
Round: 2
--> Player 0:
Player 1: 6♠
Played J♣
Round: 2
Player 0:
--> Player 1:
Played 6♠
GAME OVER!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment