
use vectors

use ./player.tm
use ./color.tm
use ./box.tm

# Return a displacement relative to `a` that will push it out of `b`
func solve_overlap(a_pos:Vec2, a_size:Vec2, b_pos:Vec2, b_size:Vec2 -> Vec2):
    a_left := a_pos.x
    a_right := a_pos.x + a_size.x
    a_top := a_pos.y
    a_bottom := a_pos.y + a_size.y

    b_left := b_pos.x
    b_right := b_pos.x + b_size.x
    b_top := b_pos.y
    b_bottom := b_pos.y + b_size.y

    # Calculate the overlap in each dimension
    overlap_x := (a_right _min_ b_right) - (a_left _max_ b_left)
    overlap_y := (a_bottom _min_ b_bottom) - (a_top _max_ b_top)

    # If either axis is not overlapping, then there is no collision:
    if overlap_x <= 0 or overlap_y <= 0:
        return Vec2(0, 0)

    if overlap_x < overlap_y:
        if a_right > b_left and a_right < b_right:
            return Vec2(-(overlap_x), 0)
        else if a_left < b_right and a_left > b_left:
            return Vec2(overlap_x, 0)
    else:
        if a_top < b_bottom and a_top > b_top:
            return Vec2(0, overlap_y)
        else if a_bottom > b_top and a_bottom < b_bottom:
            return Vec2(0, -overlap_y)

    return Vec2(0, 0)

struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=0.0, won=no):
    DT := 1./60.
    CURRENT := @World(@Player(Vec2(0,0), Vec2(0,0)), @Box(Vec2(0,0), Vec2(0,0), Color.GOAL), @[:@Box])
    STIFFNESS := 0.3

    func update(w:@World, dt:Num):
        w.dt_accum += dt
        while w.dt_accum > 0:
            w:update_once()
            w.dt_accum -= World.DT

    func update_once(w:@World):
        w.player:update()

        if solve_overlap(w.player.pos, Player.SIZE, w.goal.pos, w.goal.size) != Vec2(0,0):
            w.won = yes

        # Resolve player overlapping with any boxes:
        for i in 3:
            for b in w.boxes:
                w.player.pos += STIFFNESS * solve_overlap(w.player.pos, Player.SIZE, b.pos, b.size)

    func draw(w:@World):
        for b in w.boxes:
            b:draw()
        w.goal:draw()
        w.player:draw()

        if w.won:
            inline C {
                DrawText("WINNER", GetScreenWidth()/2-48*3, GetScreenHeight()/2-24, 48, (Color){0,0,0,0xFF});
            }

    func load_map(w:@World, map:Text):
        if map:has($/[]/):
            map = map:replace_all({$/[]/: "#", $/@{1..}/: "@", $/  /: " "})
        w.boxes = @[:@Box]
        box_size := Vec2(50., 50.)
        for y,line in map:lines():
            for x,cell in line:split():
                if cell == "#":
                    pos := Vec2((Num(x)-1) * box_size.x, (Num(y)-1) * box_size.y)
                    box := @Box(pos, size=box_size, color=Color.GRAY)
                    w.boxes:insert(box)
                else if cell == "@":
                    pos := Vec2((Num(x)-1) * box_size.x, (Num(y)-1) * box_size.y)
                    pos += box_size/2. - Player.SIZE/2.
                    w.player = @Player(pos,pos)
                else if cell == "?":
                    pos := Vec2((Num(x)-1) * box_size.x, (Num(y)-1) * box_size.y)
                    w.goal = @Box(pos, size=box_size, color=Color.GOAL)

