First define the namespace and require the necessary libraries.
(ns oo-clj.rock-paper-scissors)
The rules are pretty simple: Rock beats scissors, scissors beats paper, and paper beats rock.
Write an idiomatic Clojure function `dominates` that returns the thing that dominates the argument passed in.
(def dominates
{:rock :paper
:scissors :rock
:paper :scissors})
Create a `choices` set that contains the possible choices. Don’t Repeat Yourself.
(def choices (set (keys dominates)))
Create a `winner` function that takes two players’ choices, and returns the winner, or `nil` for a tie.
(defn winner
[choice1 choice2]
(cond
(= choice1 choice2) nil
(= (dominates choice1) choice2) choice2
:else choice1))
Create a `draw?` predicate that takes two players’ choices and returns true if they are a draw
(defn draw?
[me you]
(= me you))
Create an `iwon?` predicate that takes two players’ choices and returns true if the first player won.
(defn iwon?
[me you]
(= (winner me you) me))
All the players will conform to a `Player` protocol. It should have two methods:
- `choose`: takes a player and returns that player’s choice
- `update-strategy`: takes a player, that player’s last choice, and the other player’s last choice, returning the `Player` for the next round.
(defprotocol Player
(choose [this])
(update-strategy [this me you]))
The random player always picks at random, and never changes strategy based on what the other player is doing:
(defn random-choice []
"Random choice"
(nth (vec choices) (rand-int (count choices))))
(defrecord Random []
Player
(choose [_] (random-choice))
(update-strategy [this me you] this))
The stubborn player is initialized with a choice and never changes it:
(defrecord Stubborn [choice]
Player
(choose [_] choice)
(update-strategy [this me you] this))
The mean player sticks with what worked last time, or plays at random following a loss:
(defrecord Mean [last-winner]
Player
(choose [_] (or last-winner (random-choice)))
(update-strategy [_ me you] (Mean. (when (iwon? me you) me))))
The copycat player picks the opponents last choice or plays at random:
(defrecord CopycatPlayer [choice]
Player
(choose [_]
(if choice choice (random-choice)))
(update-strategy [_ player-last-choice opponent-last-choice]
(CopycatPlayer. opponent-last-choice)))
The cycle player simply cycles through the available choices:
(defrecord CyclePlayer [choices-seq]
Player
(choose [_] (first choices-seq))
(update-strategy [_ _ _] (CyclePlayer. (rest choices-seq))))
To play, create a `game` function with three arguments: two players and a number of rounds.
It reads nicely as a loop with five arguments:”
- Player 1
- Player 2
- Player 1’s current score
- Player 2’s current score
- Number of rounds remaining
Have the `game` return the two player’s scores in a map.
(defn game
[p1 p2 rounds]
(loop [p1 p1
p2 p2
p1-score 0
p2-score 0
rounds rounds]
(if (pos? rounds)
(let [p1-choice (choose p1)
p2-choice (choose p2)
result (winner p1-choice p2-choice)]
(recur
(update-strategy p1 p1-choice p2-choice)
(update-strategy p2 p2-choice p1-choice)
(+ p1-score (if (= result p1-choice) 1 0))
(+ p2-score (if (= result p2-choice) 1 0))
(dec rounds)))
{:p1 p1-score :p2 p2-score})))