got billiard ball collision detection almost working (still wonky)

This commit is contained in:
Richard Harrington 2013-09-02 11:12:30 -04:00
parent a7cb6a58da
commit 8e70c42e3d

View File

@ -25,8 +25,14 @@
:shot-timer 0.0 :shot-timer 0.0
:brain (brain/init-brain src-code (register/init-registers idx))}) :brain (brain/init-brain src-code (register/init-registers idx))})
(defn update-robots
"takes a world and a function, and returns a world
with its robots updated by passing them through the function"
[world f]
(update-in world [:robots] f))
(defn update-robot (defn update-robot
"takes a robot, a world, and a function, and returns a world "takes a robot index, a world, and a function, and returns a world
with the robot updated by passing it through the function" with the robot updated by passing it through the function"
[robot-idx world f] [robot-idx world f]
(update-in world [:robots robot-idx] f)) (update-in world [:robots robot-idx] f))
@ -60,56 +66,67 @@
:v-x new-v-x :v-x new-v-x
:v-y new-v-y}))) :v-y new-v-y})))
(defn collide-or-not (defn collide-two-robots
"takes a robot and a world and returns the world, with the "takes a vector of robots, two robot-indexes (an acting robot
velocities of robots altered if they have collided with and a target robot), and returns a vector of robots with those
each other. Does not currently calculate damage to robots." two altered if the actor has collided with the target.
; TODO: This is terrible and needs to be rewritten soon. The whole last Right now they're just behaving like square billiard balls --
; two-thirds consists of code that would be a lot shorter even in JavaScript, all momentum from one is transferred to the other when they collide.
; for Christ's sake. And it's really inefficient -- calculates To account for overshoot during the tick, the position of the actor
; a lot of x and y stuff twice. is set to but up against the target.
[robot-idx {robots :robots :as world}] Does not currently calculate damage. when it does, it will
(let [robot (get-in world [:robots robot-idx]) need to only assign each robot half the damage, because the other
other-robot-idxs (filter #(not= robot-idx %) (range (count robots))) half will be assigned when the other robot it ticks through its own turn."
enemy-dist-x (fn [other-robot-idx] [robots actor-idx target-idx]
(let [other-robot (get-in world [:robots other-robot-idx])] (let [actor (get robots actor-idx)
(Math/abs (- (:pos-x robot) (:pos-x other-robot))))) target (get robots target-idx)
enemy-dist-y (fn [other-robot-idx] dist-x (- (:pos-x target) (:pos-x actor))
(let [other-robot (get-in world [:robots other-robot-idx])] dist-y (- (:pos-y target) (:pos-y actor))
(Math/abs (- (:pos-y robot) (:pos-y other-robot))))) min-dist (* ROBOT-RADIUS 2)
close? (fn [dist] colliding (and (< dist-x min-dist)
(< dist (* ROBOT-RADIUS 2))) (< dist-y min-dist)
enemy-colliding (fn [other-robot-idx] (if (> dist-x dist-y) :x :y))]
(let [dist-x (enemy-dist-x other-robot-idx) (if colliding
dist-y (enemy-dist-y other-robot-idx)] (let [new-actor (case colliding
(and (close? dist-x) :x (assoc
(close? dist-y) actor
(if (> dist-x dist-y) :v-x (:v-x target)
:x :pos-x (- (:pos-x target)
:y)))) (Math/copySign min-dist (:v-x actor))))
enemy-colliding-x? (fn [other-robot-idx] :y (assoc
(= (enemy-colliding other-robot-idx) :x)) actor
enemy-colliding-y? (fn [other-robot-idx] :v-y (:v-y target)
(= (enemy-colliding other-robot-idx) :y)) :pos-y (- (:pos-y target)
colliding-enemy-idxs-x (set (filter enemy-colliding-x? other-robot-idxs)) (Math/copySign min-dist (:v-y actor)))))
colliding-enemy-idxs-y (set (filter enemy-colliding-y? other-robot-idxs)) new-target (case colliding
total-colliding-idxs-x (if (not-empty colliding-enemy-idxs-x) :x (assoc target :v-x (:v-x actor))
(conj colliding-enemy-idxs-x robot-idx) :y (assoc target :v-y (:v-y actor)))]
#{}) {colliding (assoc robots actor-idx new-actor, target-idx new-target)})
total-colliding-idxs-y (if (not-empty colliding-enemy-idxs-y) {nil robots})))
(conj colliding-enemy-idxs-y robot-idx)
#{}) (defn collide-all-robots
new-robots-v-x (mapv (fn [{rob-idx :idx :as rob}] "takes a vector of robots and an actor-idx,
(if (get total-colliding-idxs-x rob-idx) and returns a vector of robots with any collisions that have occurred
(assoc rob :v-x 0.0) (may be at most one x-collision and at most one y-collision)."
rob)) ; TODO: this is remarkably inefficient, and checks the collisions
robots) ; twice in a lot of cases. Sort this out when we sort out the whole :x and :y issue.
new-robots-v-y (mapv (fn [{rob-idx :idx :as rob}] [robots actor-idx]
(if (get total-colliding-idxs-y rob-idx) (let [target-idxs (filter #(not= actor-idx %) (range (count robots)))
(assoc rob :v-y 0.0) collided-robots-x (or (some (fn [target-idx]
rob)) (:x (collide-two-robots
new-robots-v-x)] robots
(assoc world :robots new-robots-v-y))) actor-idx
target-idx)))
target-idxs)
robots)
collided-robots-y (or (some (fn [target-idx]
(:y (collide-two-robots
collided-robots-x
actor-idx
target-idx)))
target-idxs)
collided-robots-x)]
collided-robots-y))
(defn tick-robot (defn tick-robot
"takes a robot and a world and returns the new state of the world "takes a robot and a world and returns the new state of the world
@ -120,12 +137,21 @@
[{robot-idx :idx :as robot} world] [{robot-idx :idx :as robot} world]
(if (<= (:damage robot) 0) (if (<= (:damage robot) 0)
world world
(let [ticked-world (brain/tick-brain (let [ticked-world (brain/tick-brain
robot robot
world world
register/read-register register/read-register
register/write-register) register/write-register)
shot-timer-updated-world (update-robot robot-idx ticked-world update-shot-timer) shot-timer-updated-world (update-robot
collision-detected-world (collide-or-not robot-idx shot-timer-updated-world)] robot-idx
(update-robot robot-idx collision-detected-world move-robot)))) ticked-world
update-shot-timer)
moved-world (update-robot
robot-idx
shot-timer-updated-world
move-robot)
collision-detected-world (update-robots
moved-world
#(collide-all-robots % robot-idx))]
collision-detected-world)))