Last active
November 19, 2023 06:40
-
-
Save camdez/6c9366ca7b8944c5b23548b0a678e063 to your computer and use it in GitHub Desktop.
Example code shape for a functional card game in Clojure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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} | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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