Mastering Development

Elixir: Looping through a map, comparing it to itself, and updating it

I have a map of a billiard ball table, as follows:

ball_map = %{
    "cue" => {"x":-15.0, "z": 0.0, "velocity_x": 0.0, "velocity_z": 0.0, "is_idle": true},
    "ball_1" => {"x":15.0, "z": 0.0, "velocity_x": 0.0, "velocity_z": 0.0, "is_idle": true},
    "ball_2" => {"x":17.0, "z": 1.1, "velocity_x": 0.0, "velocity_z": 0.0, "is_idle": true},
    "ball_3" => {"x":17.0, "z": -1.1, "velocity_x": 0.0, "velocity_z": 0.0, "is_idle": true}

I need to apply collisions to it. For that to work properly, each ball must be checked against the others, and the ball locations must be updated as it checks. In my previous attempts, I made a "collision map" and then applied the new locations after, but this had problems with subsequent collisions.

So what I’m trying to do is

  • Loop through each item in the map.
  • Compare that item with every other item, and run a check for collision on it (I have a function for that).
  • Update both items upon collision, while keeping the format of ball_map.

The code I’ve come up with for that is this:

collision_result = Enum.reduce(ball_map, fn {ball_a, body_a}, acc -> 
    new_result =, fn {ball_b, body_b} ->

        has_collided = checkStaticCollisions(ball_a, body_a, ball_b, body_b, const_physics["billiard_ball_radius"])
        # Takes in parameters (Key of Ball A, Value of Ball A, Key of Ball B, Value of Ball B, radius)
        # Returns true or false depending if Ball A != Ball B and collisions happen

        if has_collided == true do
            calc = calculatePosAfterCollision(ball_a, body_a, ball_b, body_b, const_physics["billiard_ball_radius"])
            # Takes in parameters (Key of Ball A, Value of Ball A, Key of Ball B, Value of Ball B, radius)
            # Returns {"id_of_ball_a" => {new X and Z pos}, "id_of_ball_b" => {new X and Z pos}}

            new_a_pos = calc[ball_a]
            new_b_pos = calc[ball_b]
            new_a = %{"x" => new_a_pos["x"], "z" => new_a_pos["z"], "velocity_x" => body_a["velocity_x"], "velocity_z" => body_a["velocity_z"], "is_idle" => body_a["is_idle"]}
            new_b = %{"x" => new_b_pos["x"], "z" => new_b_pos["z"], "velocity_x" => body_b["velocity_x"], "velocity_z" => body_b["velocity_z"], "is_idle" => body_b["is_idle"]}

            # Supposedly, Update both ball A and ball B, keep the rest
            Map.put(acc, ball_a, new_a) #?
            Map.put(acc, ball_b, new_b) #?

            # Supposedly, No change for ball A or ball B
            %{ball_a => body_a, ball_b => body_b}
    # Not sure what to output here, if any

Now this doesn’t work, as I’m getting a BadMapError (I assume something to do with the accs on else’s), but I’m also positive this will result in a list within a list.

I’m still rather new to Elixir, and I’m not sure what else I can do. Is there anything else I can try to make this work? Are there non enum-approaches (does recursion count?)?

EDIT: here are the other functions in-use:

def checkStaticCollisions(key_a, body_a, key_b, body_b, ball_radius) do
    if (key_a == key_b) do
        pos_a =["x"], 0.0, body_a["z"])
        pos_b =["x"], 0.0, body_b["z"])
        circle_a =, ball_radius)
        circle_b =, ball_radius)
        checkCircleCollision(circle_a, circle_b)

def checkCircleCollision(circleA, circleB) do 
    posA = circleA.position
    posB = circleB.position
    radiusA = circleA.radius
    radiusB = circleB.radius

    distance = PGS.Vector.distance(posA, posB)
    circleDistance = radiusA + radiusB

    if distance < circleDistance do 
        # collision occured

def calculatePosAfterCollision(id_a, ball_a, id_b, ball_b, radius) do
    pos_a =["x"], 0.0, ball_a["z"])
    pos_b =["x"], 0.0, ball_b["z"])
    radius_a = radius
    radius_b = radius       
    distance = PGS.Vector.distance(pos_a, pos_b)
    pos_delta = PGS.Vector.sub(pos_a, pos_b)
    overlap_dist = (distance - radius_a - radius_b) * 0.5
    vector_difference = * pos_delta.x / distance, overlap_dist * pos_delta.y / distance, overlap_dist * pos_delta.z / distance)

    new_pos_a = PGS.Vector.sub(pos_a, vector_difference)
    new_pos_b = PGS.Vector.add(pos_b, vector_difference)

    %{id_a => new_pos_a, id_b => new_pos_b}

For the Circle and Vector functions, please refer to this.

Leave a Reply

Your email address will not be published. Required fields are marked *