Browse Source

Reimplement manager

Thomas Dy 8 years ago
parent
commit
87858dadf5
4 changed files with 131 additions and 74 deletions
  1. 1 0
      project.clj
  2. 4 0
      src/bombnet/game.clj
  3. 87 41
      src/bombnet/manager.clj
  4. 39 33
      test/bombnet/manager_test.clj

+ 1 - 0
project.clj

@@ -4,6 +4,7 @@
   :license {:name "WTFPL"
             :url "http://www.wtfpl.net/txt/copying/"}
   :dependencies [[org.clojure/clojure "1.7.0"]
+                 [org.clojure/core.async "0.2.374"]
                  [org.clojure/data.json "0.2.6"]
                  [com.stuartsierra/component "0.3.1"]
                  [compojure "1.4.0"]

+ 4 - 0
src/bombnet/game.clj

@@ -199,3 +199,7 @@
       perform-bomb-placement
       perform-movement
       explode-bombs))
+
+(defn finished? [state]
+  (let [alive (remove :dead? (:players state))]
+    (<= (count alive) 1)))

+ 87 - 41
src/bombnet/manager.clj

@@ -1,61 +1,107 @@
 (ns bombnet.manager
   (:require [bombnet.game :refer :all]
-            [clojure.set :refer [difference]]))
+            [clojure.set :refer [difference]]
+            [clojure.core.async :refer [>! >!! <! <!! alts! alts!! chan go go-loop timeout]]))
 
-(defrecord Room [clients players state])
+(defrecord Room [clients players state msg-ch ready-ch])
 
 (defrecord Manager [rooms])
 
 (defn new-manager []
   (->Manager (atom {})))
 
-(defn ^:private get-room [rooms room]
-  (if-let [found-room (get @rooms room)]
-    found-room
-    (let
-      [new-room (->Room {} #{} nil)]
-      (swap! rooms assoc room new-room)
-      new-room)))
-
-(defmacro defn-room [name params & body]
-  (let [actual-params (vec (concat ['{:keys [rooms]} 'room-id]
-                                   params))
-        actual-body `(let [~'room (get-room ~'rooms ~'room-id)
-                           ~'update-room #(swap! ~'rooms assoc ~'room-id %)]
-                       ~@body)]
-    `(defn ~name ~actual-params
-       ~actual-body)))
-
-(defn-room client-join [id channel]
-  (update-room (assoc-in room [:clients id] {:id id :channel channel}))
-  true)
+(defn client-join [room {:keys [id] :as client}]
+  (assoc-in room [:clients id] client))
 
-(defn-room client-leave [id]
+(defn client-leave [room id]
   (let [new-room (-> room
                      (update :clients dissoc id)
                      (update :players difference #{id}))]
-    (update-room (if (not-empty (:clients new-room)) new-room))
-    true))
+    (if (not-empty (:clients new-room)) new-room)))
 
-(defn-room player-join [id]
-  (let [player-count (count (:players room))]
+(defn player-join [room id]
+  (let [player-ch (get-in room [:clients id :channel])
+        player-count (count (:players room))]
     (if (>= player-count 4)
-      false
       (do
-        (update-room (update room :players conj id))
-        true))))
+        (>!! player-ch {:message "Could not join game. Lobby is full."})
+        room)
+      (do
+        (>!! player-ch {:message "Joined game."})
+        (update room :players conj id)))))
+
+(defn player-leave [room id]
+  (update room :players difference #{id}))
 
-(defn-room player-leave [id]
-  (update-room (update room :players difference #{id}))
-  true)
+(defn player-action [room id action]
+  (if (some? (:state room))
+    (let [new-room (update room :state queue-action id action)
+          ready-ch (:ready-ch new-room)
+          ready (every? :action (get-in new-room [:state :players]))]
+      (if ready
+        (>!! ready-ch "ready"))
+      new-room)))
 
-(defn-room player-action [id action]
-  (update-room (update room :state bombnet.game/queue-action action id)))
+(defn schedule-tick [room]
+  (let [{:keys [msg-ch ready-ch]} room]
+    (go
+      (alts! [ready-ch (timeout 5000)])
+      (>! msg-ch [:game-tick "tick"]))))
+
+(defn publish-state [{:keys [clients state]}]
+  (doseq [ch (map :channel (vals clients))]
+    (>!! ch {:game state})))
+
+(defn game-start [room]
+  (let [clients (map #(dissoc % :channel) (vals (:clients room)))
+        players (filter #((:players room) (:id %)) clients)
+        new-state (new-game players)
+        new-room (assoc room :state new-state)]
+    (schedule-tick new-room)
+    (publish-state new-room)
+    new-room))
+
+(defn game-tick [room]
+  (let [new-state (update-state (:state room))
+        new-room (assoc room :state new-state)]
+    (if (not (finished? new-state))
+      (schedule-tick new-room))
+    (publish-state new-room)
+    new-room))
+
+(defn message [room {:keys [id message]}]
+  (let [type (:type message)]
+    (condp = type
+      "start" (game-start room)
+      "join" (player-join room id)
+      "leave" (player-leave room id)
+      "action" (player-action room id (:action message))
+      (do
+        (println "Unknown message")
+        room))))
 
-(defn-room start-game []
-  (let [players (filter (:players room) (:clients room))
-        new-state (new-game players)]
-    (update-room (assoc room :state new-state))))
+(defn start-room [in-ch close-ch]
+  (go-loop
+    [room (->Room {} #{} nil in-ch (chan))]
+    (let [[message-type payload] (<! in-ch)
+          new-room (condp = message-type
+                     :client-join (client-join room payload)
+                     :client-leave (client-leave room payload)
+                     :game-start (game-start room)
+                     :game-tick (game-tick room)
+                     :message (message room payload))]
+      (if (some? new-room)
+        (recur new-room)
+        (>! close-ch :kill-me-now)))))
 
-(defn room-status [{:keys [rooms]} room-id]
-  (get @rooms room-id))
+(defn get-room [{:keys [rooms]} room]
+  (if-let [room-chan (get @rooms room)]
+    room-chan
+    (let [room-chan (chan)
+          close-chan (chan)]
+      (start-room room-chan close-chan)
+      (go
+        (<! close-chan)
+        (swap! rooms dissoc room))
+      (swap! rooms assoc room room-chan)
+      room-chan)))

+ 39 - 33
test/bombnet/manager_test.clj

@@ -1,40 +1,46 @@
 (ns bombnet.manager-test
   (:require [clojure.test :refer :all]
+            [clojure.core.async :refer [<! chan close! go]]
             [bombnet.manager :refer :all]))
 
-(deftest client-test
+(defn new-room []
+  (->Room {} #{} nil nil nil))
+
+(defn closed-chan []
+  (let [ch (chan)]
+    (close! ch)
+    ch))
+
+(deftest room-test
   (testing "Clients can join"
-    (let [manager (new-manager)]
-      (is (nil? (room-status manager 12)))
-      (client-join manager 12 100 "chan")
-      (is (= (->Room {100 {:id 100 :channel "chan"}} #{} nil)
-             (room-status manager 12)))))
-  (testing "An empty room is removed"
-    (let [manager (new-manager)]
-      (client-join manager 12 100 "chan")
-      (client-leave manager 12 100)
-      (is (nil? (room-status manager 12)))))
+    (let [room (new-room)]
+      (is (= (->Room {100 {:id 100 :channel "chan"}} #{} nil nil nil)
+             (client-join room {:id 100 :channel "chan"})))))
   (testing "Players leave when the client leaves"
-    (let [manager (new-manager)]
-      (client-join manager 12 100 "chan")
-      (player-join manager 12 100)
-      (is (= (->Room {100 {:id 100 :channel "chan"}} #{100} nil)
-             (room-status manager 12)))
-      (client-join manager 12 101 "chan")
-      (client-leave manager 12 100)
-      (is (= (->Room {101 {:id 101 :channel "chan"}} #{} nil)
-             (room-status manager 12)))))
+    (let [ch (closed-chan)
+          room (-> (new-room)
+                   (client-join {:id 100 :channel ch})
+                   (player-join 100))
+          left-room (-> room
+                        (client-join {:id 101 :channel "chan"})
+                        (client-leave 100) )]
+      (is (= (->Room {100 {:id 100 :channel ch}} #{100} nil nil nil)
+             room))
+      (is (= (->Room {101 {:id 101 :channel "chan"}} #{} nil nil nil)
+             left-room))))
   (testing "Only up to 4 players can join"
-    (let [manager (new-manager)]
-      (client-join manager 12 1 "p1")
-      (client-join manager 12 2 "p2")
-      (client-join manager 12 3 "p3")
-      (client-join manager 12 4 "p4")
-      (client-join manager 12 5 "p5")
-      (is (player-join manager 12 1))
-      (is (player-join manager 12 2))
-      (is (player-join manager 12 3))
-      (is (player-join manager 12 4))
-      (is (not (player-join manager 12 5)))
-      (is (player-leave manager 12 4))
-      (is (player-join manager 12 5)))))
+    (let [chan-1 (chan)
+          chan-5 (chan)
+          empty-room (->Room {1 {:id 1 :channel chan-1}}
+                             #{} nil nil nil)
+          full-room (->Room {1 {:id 1}
+                             2 {:id 2}
+                             3 {:id 3}
+                             4 {:id 4}
+                             5 {:id 5 :channel chan-5}}
+                            #{1 2 3 4}
+                            nil nil nil)]
+      (go (is (= (<! chan-1) {:message "Joined game."})))
+      (player-join empty-room 1)
+      (go (is (= (<! chan-5) {:message "Could not join game. Lobby is full."})))
+      (player-join full-room 5))))