game.clj 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. (ns bombnet.game
  2. (:require [clojure.test :refer [is with-test]])
  3. (:require [clojure.set :refer :all]))
  4. (defn new-board [w h]
  5. (let [should-wall (fn [row col]
  6. (or
  7. (= row 0)
  8. (= row (- h 1))
  9. (= col 0)
  10. (= col (- w 1))
  11. (= 0 (mod row 2) (mod col 2))))]
  12. (vec (map (fn [row]
  13. (vec (map (fn [col]
  14. (if (should-wall row col) "#" " "))
  15. (range 0 w))))
  16. (range 0 h )))))
  17. (with-test
  18. (defn get-pos [entity]
  19. (cond
  20. (vector? entity) entity
  21. (:pos entity) (:pos entity)))
  22. (is [2 3] (get-pos [2 3]))
  23. (is [2 3] (get-pos {:pos [2 3]})))
  24. (defn get-cell [board entity]
  25. (let [[x y] (get-pos entity)] (get-in board [y x])))
  26. (defn move-player
  27. ([p x y] (assoc p :pos [x y]))
  28. ([p dir]
  29. (let [[x y] (:pos p)]
  30. (condp = dir
  31. "up" (move-player p x (- y 1))
  32. "down" (move-player p x (+ y 1))
  33. "left" (move-player p (- x 1) y)
  34. "right" (move-player p (+ x 1) y)))))
  35. (defn new-game [players]
  36. (let [w 21 h 17
  37. colors ["red" "blue" "green" "yellow"]
  38. positions [[1 1] [1 (- h 2)] [(- w 2) 1] [(- w 2) (- h 2)]]
  39. positioned-players (mapv #(assoc % :pos %2 :color %3) players positions colors)]
  40. {:board (new-board 21 17)
  41. :players positioned-players
  42. :bombs []
  43. :explosion #{}}))
  44. (defn with-action [typ]
  45. (fn [p] (= typ (get-in p [:action :type]))))
  46. (defn ^:private mapv-if [pred f coll]
  47. (mapv (fn [e] (if (pred e) (f e) e)) coll))
  48. (defn check-positions [state]
  49. "Returns a vector of valid new player positions"
  50. (let [{:keys [board players bombs]} state
  51. wall-clear (fn [p] (= " " (get-cell board p)))
  52. collides (fn [a b] (and (not= a b) (= (get-pos a) (get-pos b))))
  53. swap-clear (fn [p] (every? (fn [e]
  54. (not (and
  55. (= (:prev-pos p) (:pos e))
  56. (= (:prev-pos e) (:pos p)))))
  57. players))
  58. entity-clear (fn [entities] (fn [p] (every? #(not (collides p %)) entities)))
  59. xf (comp
  60. (filter wall-clear)
  61. (filter (entity-clear players))
  62. (filter (entity-clear bombs))
  63. (filter swap-clear)
  64. (map :id))]
  65. (into #{} xf players)))
  66. (defn perform-movement [initial-state]
  67. (loop [state initial-state]
  68. (let [{players :players} state
  69. to-move (mapv-if
  70. (with-action "move")
  71. (fn [p] (-> p
  72. (assoc :prev-pos (:pos p))
  73. (move-player (get-in p [:action :dir]))
  74. (dissoc :action)))
  75. players)
  76. proposed-state (assoc state :players to-move)
  77. valid-moves (check-positions proposed-state)
  78. valid-players (mapv-if
  79. #(and
  80. (not (valid-moves (:id %)))
  81. ((with-action "move") %))
  82. #(dissoc % :action)
  83. players)]
  84. (if (= players valid-players)
  85. proposed-state
  86. (recur (assoc state :players valid-players))))))
  87. (def ^:const min-bomb-timer 1)
  88. (def ^:const max-bomb-timer 5)
  89. (def ^:const bomb-power 99)
  90. (defn ^:private clamp [a lbound ubound]
  91. (max lbound (min ubound a)))
  92. (defn ^:private merge-by-key [k coll1 coll2]
  93. (->> (merge
  94. (group-by k coll1)
  95. (group-by k coll2))
  96. vals
  97. (apply concat)))
  98. (defn perform-bomb-placement [state]
  99. (let [{bombs :bombs players :players} state
  100. get-bomb (fn [p]
  101. (let [pos (:pos p)
  102. power bomb-power
  103. params (:action p)
  104. timer (clamp
  105. (:timer params)
  106. min-bomb-timer
  107. max-bomb-timer)
  108. diagonal? (boolean (:diagonal? params))]
  109. {:pos pos :power power :counter timer :diagonal? diagonal?}))
  110. bombs-to-place (->> players
  111. (filter (with-action "bomb"))
  112. (map get-bomb))
  113. new-bombs (merge-by-key :pos bombs-to-place bombs)
  114. new-players (mapv-if
  115. (with-action "bomb")
  116. #(dissoc % :action)
  117. players)]
  118. (-> state
  119. (assoc :bombs new-bombs)
  120. (assoc :players new-players))))
  121. (let [state {:board (new-board 5 5)
  122. :players [{:pos [1 1] :action {:type "bomb" :timer 5}}]
  123. :bombs []}]
  124. (perform-bomb-placement state))
  125. (defn ^:private offset [pos dir] (mapv + pos dir))
  126. (with-test
  127. (defn explode-bomb-helper [board start dir power acc]
  128. (let [pos (offset start dir)]
  129. (if (or (zero? power) (= (get-cell board pos) "#"))
  130. acc
  131. (recur board pos dir (- power 1) (conj acc pos)))))
  132. (let [board (new-board 7 7)]
  133. (is (= #{[1 2] [1 3]}
  134. (explode-bomb-helper board [1 1] [0 1] 2 #{})))
  135. (is (= #{[1 2] [1 3] [1 4] [1 5]}
  136. (explode-bomb-helper board [1 1] [0 1] 99 #{})))))
  137. (defn explode-bomb [board bomb]
  138. (let [{:keys [power pos]} bomb
  139. dirs (if (:diagonal? bomb)
  140. [[1 1] [-1 -1] [1 -1] [-1 1]]
  141. [[1 0] [0 1] [-1 0] [0 -1]])]
  142. (reduce #(explode-bomb-helper board pos %2 power %) #{pos} dirs)))
  143. (defn explode-bombs
  144. ([state] (explode-bombs state #{}))
  145. ([state prev-explosion]
  146. (if-let [exploding (seq (filter #(zero? (:counter %)) (:bombs state)))]
  147. (let [{:keys [board players bombs]} state
  148. bomb (first exploding)
  149. other-bombs (filter #(not= bomb %) bombs)
  150. explosion (explode-bomb board bomb)
  151. new-bombs (mapv-if
  152. #(explosion (:pos %))
  153. #(assoc % :counter 0)
  154. other-bombs)
  155. new-players (mapv-if
  156. #(explosion (:pos %))
  157. #(assoc % :dead? true)
  158. players)]
  159. (recur
  160. (-> state
  161. (assoc :players new-players)
  162. (assoc :bombs new-bombs))
  163. (union prev-explosion explosion)))
  164. (assoc state :explosion prev-explosion))))
  165. (defn perform-bomb-tick [state]
  166. (let [{bombs :bombs} state
  167. new-bombs (mapv #(update-in % [:counter] dec) bombs)]
  168. (assoc state :bombs new-bombs)))
  169. (defn queue-action [state id action]
  170. (update-in state [:players]
  171. (partial
  172. mapv-if
  173. #(= id (:id %))
  174. #(assoc % :action action))))
  175. (defn update-state [state]
  176. (-> state
  177. perform-bomb-tick
  178. perform-bomb-placement
  179. perform-movement
  180. explode-bombs))
  181. (defn finished? [state]
  182. (let [alive (remove :dead? (:players state))]
  183. (<= (count alive) 1)))