/
rps_async_test.clj
114 lines (96 loc) · 3.46 KB
/
rps_async_test.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
; Adapted from: http://tech.puredanger.com/2013/07/10/rps-core-async/
(ns typed-test.lib.clojure.core.async.rps-async-test
(:require [typed.clojure :as t]
[clojure.core.async :as a]
[typed.lib.clojure.core.async :as ta]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(t/defalias Move
"A legal move in rock-paper-scissors"
(t/U ':rock ':paper ':scissors))
(t/defalias PlayerName
"A player's name in rock-paper-scissors"
t/Str)
(t/defalias PlayerMove
"A move in rock-paper-scissors. A Tuple of player name and move"
'[PlayerName Move])
(t/defalias RPSResult
"The result of a rock-paper-scissors match.
A 3 place vector of the two player moves, and the winner"
'[PlayerMove PlayerMove PlayerName])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(t/ann MOVES (t/Vec Move))
(def MOVES [:rock :paper :scissors])
(t/ann BEATS (t/Map Move Move))
(def BEATS {:rock :scissors, :paper :rock, :scissors :paper})
(t/ann rand-player [PlayerName -> (ta/Chan PlayerMove)])
(defn rand-player
"Create a named player and return a channel to report moves."
[name]
(let [out (ta/chan :- PlayerMove)]
(a/go (while true (a/>! out [name (rand-nth MOVES)])))
out))
(t/ann winner [PlayerMove PlayerMove -> PlayerName])
(defn winner
"Based on two moves, return the name of the winner."
[[name1 move1] [name2 move2]]
(cond
(= move1 move2) "no one"
(= move2 (BEATS move1)) name1
:else name2))
(t/ann judge [(ta/Chan PlayerMove) (ta/Chan PlayerMove) -> (ta/Chan RPSResult)])
(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]."
[p1 p2]
(let [out (ta/chan :- RPSResult)]
(a/go
(while true
(let [m1 (a/<! p1)
m2 (a/<! p2)]
(assert m1)
(assert m2)
(a/>! out (t/ann-form [m1 m2 (winner m1 m2)]
RPSResult)))))
out))
(t/ann init (t/IFn [PlayerName PlayerName -> (ta/Chan RPSResult)]
[-> (ta/Chan RPSResult)]))
(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))))
(t/ann report [PlayerMove PlayerMove PlayerName -> nil])
(defn report
"Report results of a match to the console."
[[name1 move1] [name2 move2] winner]
(println)
(println name1 "throws" move1)
(println name2 "throws" move2)
(println winner "wins!"))
(t/ann play [(ta/Chan RPSResult) -> nil])
(defn play
"Play by taking a match reporting channel and reporting the results of the latest match."
[out-chan]
(let [[move1 move2 winner :as c] (a/<!! out-chan)]
(assert c)
(report move1 move2 winner)))
(t/ann play-many [(ta/Chan RPSResult) t/Int -> (t/Map t/Any t/Any)])
(defn play-many
"Play n matches from out-chan and report a summary of the results."
[out-chan n]
(t/loop [remaining :- t/Int, n
results :- (t/Map PlayerName t/Int), {}]
(if (zero? remaining)
results
(let [[m1 m2 winner :as c] (a/<!! out-chan)]
(assert c)
(recur (dec remaining)
(merge-with (t/fn [i :- t/Num, j :- t/Num]
(int (+ i j)))
results {winner 1}))))))
(fn []
(t/ann-form (a/<!! (init))
(t/U nil RPSResult)))