summary history branches tags files
commit:cb7c84aded4418ab0977ef53d5428c7b2b45d546
author:mrmekon
committer:mrmekon
date:Mon Apr 9 17:36:23 2012 -0400
parents:5967dcc97c9bf7ca9f96b2e15193e3daa3f72f0b
Tankers fully implemented
diff --git a/src/tempest_cljs/views/welcome.clj b/src/tempest_cljs/views/welcome.clj
line changes: +1/-0
index b616b5d..f6a8772
--- a/src/tempest_cljs/views/welcome.clj
+++ b/src/tempest_cljs/views/welcome.clj
@@ -10,6 +10,7 @@
 
 (defpage "/tempest/:level" {:keys [level]}
   (common/site-layout
+   (include-js "/analytics.js")
    (include-js "/tempest.js")
    [:div#links {:style "color: #FFFFFF; position: absolute; z-index: 2;"}
     (link-to {:style (str "color: #FFFFFF; position:absolute;"

diff --git a/tempest/tempest/core.cljs b/tempest/tempest/core.cljs
line changes: +77/-86
index 9cc0457..7806740
--- a/tempest/tempest/core.cljs
+++ b/tempest/tempest/core.cljs
@@ -13,7 +13,8 @@ the player's ship, enemies, and projectiles.
             [goog.dom :as dom]
             [goog.events :as events]
             [goog.events.KeyCodes :as key-codes]
-            [clojure.browser.repl :as repl]))
+            [clojure.browser.repl :as repl])
+  (:require-macros [tempest.macros :as macros]))
 
 (repl/connect "http://localhost:9000/repl")
 
@@ -58,6 +59,8 @@ after passing through all the other functions.  This implements the game loop.
        remove-collided-bullets
        update-projectile-locations
        update-enemy-locations
+       maybe-split-tankers
+       handle-dead-enemies
        maybe-enemies-shoot
        maybe-make-enemy
        check-if-player-captured
@@ -196,6 +199,7 @@ after passing through all the other functions.  This implements the game loop.
    })
 
 (defn build-tanker
+  "Returns a new tanker enemy.  Tankers move slowly and do not shoot or flip."
   [level seg-idx & {:keys [step] :or {step 0}}]
   (assoc (build-enemy level seg-idx :step step)
     :type (EnemyEnum "TANKER")
@@ -251,22 +255,13 @@ after passing through all the other functions.  This implements the game loop.
    If zero enemies are on the board, probability of placing one is increased
    two-fold to avoid long gaps with nothing to do."
   [game-state]
-  (let [level (:level game-state)
-        segments (count (:segments level))
-        remaining (:remaining level)
-        enemy-list (:enemy-list game-state)
-        r (if (pos? (count enemy-list)) (rand) (/ (rand) 2))]
-    (cond
-     (and (<= r (:flipper (:probability level))) (pos? (:flipper remaining)))
-     (do
-       (assoc game-state
-         :enemy-list (cons (build-flipper level (rand-int segments)) enemy-list)
-         :level (assoc level
-                  :remaining (assoc remaining
-                               :flipper (dec (:flipper remaining))))))
-     :else game-state)))
+  (let [;;flipper-fn (macros/dumbtest flipper)
+        flipper-fn (macros/random-enemy-fn flipper)
+        tanker-fn (macros/random-enemy-fn tanker)]
+    (->> game-state
+         flipper-fn
+         tanker-fn)))
 
-;;(count (:segments level))
 
 (defn flip-angle-stride
   "Returns the angle stride of a flipper, which is how many radians to
@@ -364,6 +359,17 @@ flipper appears to flip 'inside' the level:
                   (DirectionEnum "CW"))]
     (assoc flipper :flip-permanent-dir new-dir)))
 
+(defn engage-flipping
+  "Mark flipper as flipping in given direction, unless no segment is in
+   that direction."
+  [flipper flip-dir]
+  (let [flip-seg-idx (segment-for-flip-direction flipper flip-dir)
+        cw? (= flip-dir (DirectionEnum "CW"))]
+    (if (not= flip-seg-idx (:segment flipper))
+      (mark-flipper-for-flipping flipper flip-dir
+                                 flip-seg-idx cw?)
+      flipper)))
+
 (defn maybe-engage-flipping
   "Given a flipper, returns the flipper possibly modified to be in a state
    of flipping to another segment.  This will always be true if the flipper
@@ -515,18 +521,6 @@ flipper appears to flip 'inside' the level:
   [entity]
   (assoc entity :step (entity-next-step entity)))
 
-(defn test-update-entity-position! []
-  (and 
-   (= 11 (:step (update-entity-position! (build-enemy level 0 :step 10))))
-   (= 5 (:step (update-entity-position!
-                (build-projectile level 0 -5 :step 10))))
-   (= 15 (:step (update-entity-position!
-                (build-projectile level 0 5 :step 10))))
-   (= 0 (:step (update-entity-position!
-                (build-projectile level 0 -5 :step 0))))
-   (= 100 (:step (update-entity-position!
-                  (build-projectile level 0 5 :step 100))))))
-  
 (defn update-entity-list
   "Call update-entity-position! on all entities in list."
   [entity-list]
@@ -543,16 +537,6 @@ flipper appears to flip 'inside' the level:
      (>= (:step entity) min)
      (<= (:step entity) max))))
 
-(defn test-entity-between-steps []
-  (and
-   (true? (entity-between-steps 0 0 10 (build-enemy level 0 :step 5)))
-   (false? (entity-between-steps 0 0 10 (build-enemy level 0 :step 15)))
-   (false? (entity-between-steps 0 0 10 (build-enemy level 1 :step 5)))
-   (false? (entity-between-steps 5 10 20 (build-enemy level 5 :step 5)))
-   (true? (entity-between-steps 5 10 20 (build-enemy level 5 :step 15)))
-  ))
-
-
 (defn projectiles-after-collision
   "Given an entity and a list of projectiles, returns the entity and updated
    list of projectiles after collisions.  The entity's hits-remaining counter
@@ -578,25 +562,6 @@ flipper appears to flip 'inside' the level:
                   was-hit?)))))
    entity projectile-list '() false))
 
-(defn test-projectiles-after-collision []
-  (let [level (get levels/*levels* 4)
-        projectiles (list (build-projectile level 0 -1 :step 9)
-                          (build-projectile level 0 -5 :step 9)
-                          (build-projectile level 0 1 :step 9)
-                          (build-projectile level 0 2 :step 9)
-                          (build-projectile level 0 5 :step 20)
-                          (build-projectile level 5 2 :step 9))
-        result (projectiles-after-collision
-                (build-enemy level 0 :step 10)
-                projectiles)]
-    (println (str "Projectiles: "
-                  (pr-str (count (:projectiles result)))
-                  " of "
-                  (pr-str (count projectiles))))
-    (println (str "Hits left: " (pr-str (:hits-remaining (:entity result)))))
-    (println (str "Was hit: " (pr-str (:was-hit? result))))
-     ))
-
 (defn entities-after-collisions
   "Given a list of entities and a list of projectiles, returns the lists
    with entity hit counts updated, entities removed if they have no hits
@@ -611,40 +576,66 @@ flipper appears to flip 'inside' the level:
        (let [{entity :entity projectiles :projectiles was-hit? :was-hit?}
              (projectiles-after-collision (first entities-in)
                                           projectiles-in)]
-         (if (and was-hit? (<= (:hits-remaining entity) 0))
-           (recur (rest entities-in)
-                  entities-out
-                  projectiles)
            (recur (rest entities-in)
                   (cons entity entities-out)
-                  projectiles)))))
+                  projectiles))))
    entity-list '() projectile-list))
 
-(defn test-entities-after-collisions []
-  (let [level (get levels/*levels* 4)
-        enemies (list (build-enemy level 0 :step 10)
-                      (build-enemy level 1 :step 20)
-                      (build-enemy level 2 :step 30)
-                      (build-enemy level 3 :step 40)
-                      (build-enemy level 4 :step 50)
-                      (build-enemy level 5 :step 60))
-        projectiles (list (build-projectile level 0 -5 :step 9)
-                          (build-projectile level 0 5 :step 9)
-                          (build-projectile level 1 -5 :step 19)
-                          (build-projectile level 1 5 :step 19)
-                          (build-projectile level 3 -5 :step 35)
-                          (build-projectile level 3 5 :step 35))
-        result (entities-after-collisions enemies projectiles)]
-    (println (str "Entities: "
-                  (pr-str (count (:entities result)))
-                  " of "
-                  (pr-str (count enemies))))
-    (println (str "Projectiles: "
-                  (pr-str (count (:projectiles result)))
-                  " of "
-                  (pr-str (count projectiles))))
-    ))
 
+(defn new-flippers-from-tanker
+  "Spawns two new flippers from one tanker.  These flippers are automatically
+   set to be flipping to the segments surround the tanker, unless one of the
+   directions is blocked, in which case that flipper just stays on the tanker's
+   segment."
+  [enemy]
+  (let [{:keys [segment level step]} enemy]
+    (list
+     (engage-flipping
+      (build-flipper level segment :step step)
+      (DirectionEnum "CW"))
+     (engage-flipping
+      (build-flipper level segment :step step)
+      (DirectionEnum "CCW")))))
+
+(defn enemy-list-after-deaths
+  "Returns the enemy list updated for deaths.  This means removing enemies
+   that died, and possibly adding new enemies for those that spawn children
+   on death."
+  [enemy-list]
+  (let [{live-enemies false dead-enemies true}
+        (group-by #(zero? (:hits-remaining %)) enemy-list)]
+    (loop [[enemy & enemies] dead-enemies
+           enemies-out '()]
+      (cond
+       (nil? enemy) (concat live-enemies enemies-out)
+       (= (:type enemy) (EnemyEnum "TANKER"))
+       (recur enemies (concat (new-flippers-from-tanker enemy) enemies-out))
+       :else (recur enemies enemies-out)))))
+
+(defn handle-dead-enemies
+  "Return game state after handling dead enemies, by removing them and possibly
+   replacing them with children."
+  [game-state]
+  (let [enemy-list (:enemy-list game-state)]
+    (assoc game-state :enemy-list (enemy-list-after-deaths enemy-list))))
+
+(defn kill-tanker-at-top
+  "If the given tanker is at the top of a level, mark it as dead."
+  [tanker]
+  (let [step (:step tanker)
+        maxstep (:steps (:level tanker))]
+    (if (= step maxstep)
+      (assoc tanker :hits-remaining 0)
+      tanker)))
+
+(defn maybe-split-tankers
+  "Marks tankers at the top of the level as ready to split into flippers."
+  [game-state]
+  (let [enemy-list (:enemy-list game-state)
+        {tankers true others false}
+        (group-by #(= (:type %) (EnemyEnum "TANKER")) enemy-list)]
+    (assoc game-state
+      :enemy-list (concat (map kill-tanker-at-top tankers) others))))
 
 (defn animate-player-capture
   "Updates player's position on board while player is in the process of being

diff --git a/tempest/tempest/levels.cljs b/tempest/tempest/levels.cljs
line changes: +5/-5
index 0b3beb1..353a6dc
--- a/tempest/tempest/levels.cljs
+++ b/tempest/tempest/levels.cljs
@@ -291,18 +291,18 @@ Functions related to generating paths representing levels.
 
 (def *levels*
   [ (make-level-entry *level1_lines* false
-                      {:flipper 5}
+                      {:flipper 5 :tanker 0}
                       {:flipper 0.01})
     (make-level-entry *level2_lines* true
                       {:flipper 20}
                       {:flipper 0.01}
                       :length-fn #(* 9 %))
     (make-level-entry *level3_lines* false
-                      {:flipper 20}
-                      {:flipper 0.01})
+                      {:flipper 20 :tanker 5}
+                      {:flipper 0.01 :tanker 0.005})
     (make-level-entry *level4_lines* false
-                      {:flipper 20}
-                      {:flipper 0.01})
+                      {:flipper 20 :tanker 10}
+                      {:flipper 0.01 :tanker 0.005})
     (make-level-entry *level5_lines* false
                       {:flipper 20}
                       {:flipper 0.01})

diff --git a/tempest/tempest/macros.clj b/tempest/tempest/macros.clj
line changes: +28/-1
index 5fe9533..c43bc9d
--- a/tempest/tempest/macros.clj
+++ b/tempest/tempest/macros.clj
@@ -11,4 +11,31 @@
        ~@body
        (.log js/console
              (str "Fn time: " (pr-str (- (goog.now) starttime#)) " ms")))))
-     
+
+(defmacro random-enemy-fn
+  "Macro that returns a function that generates an enemy of type 'type'
+   randomly.  The returned function should be given the game-state,
+   and it will generate a random number, and create a new enemy of the given
+   type if the random number is under the enemy type's probability and more
+   of the given type of enemy are permitted on the level.
+
+   Calls 'build-TYPE' function to make an enemy, with 'TYPE' replaced by
+   whatever random-enemy-fn was called with.
+
+   Example usage: (let [ffn (random-enemy-fn flipper)] (ffn game-state))"
+  [type]
+  `(fn [game-state#]
+    (let [level# (:level game-state#)
+          enemy-list# (:enemy-list game-state#)
+          r# (if (empty? enemy-list#) (/ (rand) 2) (rand))
+          {{more?# (keyword ~(name type))} :remaining
+           {prob# (keyword ~(name type))} :probability
+           segments# :segments} level#]
+      (if (and (<= r# prob#) (pos? more?#))
+        (assoc game-state#
+          :enemy-list (cons (~(symbol (str "build-" (name type)))
+                             level#
+                             (rand-int (count segments#))) enemy-list#)
+          :level (assoc-in level#
+                           [:remaining (keyword ~(name type))] (dec more?#)))
+        game-state#))))

diff --git a/tempest/tempest/path.cljs b/tempest/tempest/path.cljs
line changes: +14/-11
index dc0e291..458578f
--- a/tempest/tempest/path.cljs
+++ b/tempest/tempest/path.cljs
@@ -372,18 +372,21 @@ given level."
      (projectile-path-with-width (* 0.3 (entity-desired-width projectile))))))
 
 (defn tanker-path-with-width
-  "Returns a path to draw a 'flipper' enemy with given width."
+  "Returns a path to draw a 'tanker' enemy with given width.
+   Tanker is a diamond with an angular 'cat-eye' inside."
   [width]
-  (let [r (/ width (js/Math.cos (util/deg-to-rad 16)))]
-    [[0 0]
-     [(/ r 2) 16]
-     [(/ r 4) 214]
-     [(/ r 4) 326]
-     [r 164]
-     [(/ r 4) 326]
-     [(/ r 4) 214]
-     [(/ r 2) 16]]))
-
+  (let [r (* .85 (/ width (* 2 (js/Math.cos (util/deg-to-rad 45)))))
+        midheight (* .85 (* r (js/Math.sin (util/deg-to-rad 45))))
+        r2 (* .85 (/ (/ width 2) (* 2 (js/Math.cos (util/deg-to-rad 65)))))]
+    [[midheight 270]
+     [r 45]
+     [r 135]
+     [r 225]
+     [r 315]
+     [r2 65]
+     [r2 115]
+     [r2 245]
+     [r2 295]]))
 
 (defn flipper-path-with-width
   "Returns a path to draw a 'flipper' enemy with given width."