|
@@ -1,61 +1,107 @@
|
|
(ns bombnet.manager
|
|
(ns bombnet.manager
|
|
(:require [bombnet.game :refer :all]
|
|
(: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])
|
|
(defrecord Manager [rooms])
|
|
|
|
|
|
(defn new-manager []
|
|
(defn new-manager []
|
|
(->Manager (atom {})))
|
|
(->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
|
|
(let [new-room (-> room
|
|
(update :clients dissoc id)
|
|
(update :clients dissoc id)
|
|
(update :players difference #{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)
|
|
(if (>= player-count 4)
|
|
- false
|
|
|
|
(do
|
|
(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)))
|