Skip to content

Instantly share code, notes, and snippets.

@thickey
Forked from puredanger/rps.clj
Last active December 19, 2015 14:29
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 thickey/5969833 to your computer and use it in GitHub Desktop.
Save thickey/5969833 to your computer and use it in GitHub Desktop.
;; An implementation of the rock-paper-scissors variant -
;; rock-paper-scissors-lizard-spock - based on Alex Miller's core.async
;; implementation.
;; - http://tech.puredanger.com/2013/07/10/rps-core-async/
;; - https://gist.github.com/puredanger/5965883
;; - https://github.com/relevance/labrepl/blob/master/src/solutions/rock_paper_scissors.clj
;; - http://www.imdb.com/title/tt1256039/quotes?item=qt0493730
(require 'clojure.core.async :refer :all)
(def rules
[[:scissors :cuts :paper]
[:paper :covers :rock]
[:rock :crushes :lizard]
[:lizard :poisons :spock]
[:spock :smashes :scissors]
[:scissors :decapitates :lizard]
[:lizard :eats :paper]
[:paper :disproves :spock]
[:spock :vaporizes :rock]
[:rock :crushes :scissors]])
(def moves (seq (set (map first rules))))
(def dominates
^{:doc "Includes two mappings: a set of moves that each moves beats, and the
descriptive reason why one move beats another (for reporting)."}
(reduce (fn [m [i beat u]]
(-> m
(update-in [:beats i] (fn [s] (set (conj s u))))
(assoc-in [:reasons i u] beat)))
{}
rules))
(defn rand-player
"Create a named player and return a channel to report moves."
[name]
(let [out (chan)]
(go (while true (>! out [name (rand-nth moves)])))
out))
(defn winner
"Based on two moves, return the name of the winner."
[p1 p2]
(let [[name1 move1] p1
[name2 move2] p2]
(cond
(= move1 move2) ["no one"]
(= move2 (-> dominates :beats move1 move2)) p1
:else p2)))
(defn judge
"Given two channels on which players report moves, create and return an
output channel to report the results of each match as [move1 move2 winner]."
[c1 c2]
(let [out (chan)]
(go
(while true
(let [m1 (<! c1)
m2 (<! c2)]
(>! out [m1 m2 (winner m1 m2)]))))
out))
(defn init
"Create 2 players (by default Alice and Bob) and return an output channel of match results."
([] (init "Alice" "Bob"))
([n1 n2] (judge (rand-player n1) (rand-player n2))))
(defn report
"Report results of a match to the console."
[p1 p2 winner]
(let [[name1 move1] p1
[name2 move2] p2
[wname wmove] winner]
(println name1 "throws" move1)
(println name2 "throws" move2)
(if wmove
(let [[lname lmove] (if (= p1 winner) p2 p1)]
(println wmove (get-in dominates [:reasons wmove lmove]) lmove)
(println wname "wins!"))
(println "No one wins!"))))
(defn play
"Play by taking a match reporting channel and reporting the results of the latest match."
[out-chan]
(apply report (<!! out-chan)))
(defn play-many
"Play n matches from out-chan and report a summary of the results."
[out-chan n]
(loop [remaining n
results {}]
(if (zero? remaining)
results
(let [[_ _ [winner]] (<!! out-chan)]
(recur (dec remaining)
(merge-with + results {winner 1}))))))
(comment
(def game (init))
(play game)
(time (play-many game 10000)))
;; user> (play game)
;; Alice throws :scissors
;; Bob throws :lizard
;; :scissors :decapitates :lizard
;; Alice wins!
;; nil
;; user> (play game)
;; Alice throws :rock
;; Bob throws :spock
;; :spock :vaporizes :rock
;; Bob wins!
;; nil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment