using Colors

WIDTH = 600
HEIGHT = 600
BACKGROUND=colorant"black"
BALL_SIZE = 10
MARGIN = 50
BRICKS_X = 10
BRICKS_Y = 5
BRICK_W = (WIDTH - 2 * MARGIN) ÷ BRICKS_X
BRICK_H = 25

ball = Circle(WIDTH / 2, HEIGHT / 2, BALL_SIZE/2)
ball_vel = (0,0)
bat = Rect(WIDTH / 2, HEIGHT - 50, 120, 12)

bricks = []

struct Brick
    brick::Rect
    brick_color
    highlight_color
end

function reset()
    deleteat!(bricks, 1:length(bricks))
    for x in 1:BRICKS_X
        for y in 1:BRICKS_Y
            hue = (x + y - 2) / BRICKS_X
            saturation = ( (y-1) / BRICKS_Y) * 0.5 + 0.5
            brick_color = HSV(hue*360, saturation, 0.8)
            highlight_color = HSV(hue*360, saturation * 0.7, 1.0)
            brick = Brick( Rect(
                ((x-1) * BRICK_W + MARGIN, (y-1) * BRICK_H + MARGIN),
                (BRICK_W - 1, BRICK_H - 1)
            ), brick_color, highlight_color )
            push!(bricks, brick)
        end
    end

    ball.center = (WIDTH / 2, HEIGHT / 3)  #should be centre
    global ball_vel = (rand(-200:200), 400)
end

reset()

function draw(g::Game)
    clear()
    for b in bricks
        draw(b.brick, b.brick_color, fill=true)
        draw(Line(b.brick.bottomleft, b.brick.topleft), b.highlight_color)
        draw(Line(b.brick.topleft, b.brick.topright), b.highlight_color)
    end
    draw(bat, colorant"pink", fill=true)
    draw(ball, colorant"white", fill=true)
end

function update(g::Game)

When you have fast moving objects, like the ball, a good trick is to run the update step several times per frame with tiny time steps. This makes it more likely that collisions will be handled correctly.

    for _ in 1:3
        update_step(1 / 180)
    end
    update_bat_vx()
end

function update_step(dt)
    x, y = ball.center  #should be centre
    global ball_vel
    vx, vy = ball_vel
    @show
    if ball.top > HEIGHT
        reset()
        return
    end
    x += vx * dt
    y += vy * dt
    ball.center = (x, y)  #should be centre
    if ball.left < 0
        vx = -vx
        ball.left = -ball.left
    elseif ball.right > WIDTH
        vx = -vx
        ball.right += -(2 * (ball.right - WIDTH))
    end

    if ball.top < 0
        vy = -vy
        ball.top = ball.top * -1
    end
    if collide(ball, bat)
        vy = -abs(vy)
        vx += -30 * bat_vx
    else
        collisions = [collide(ball, b.brick) for b in bricks]
        idx = findfirst(x->x==true, collisions)
        if idx ≠ nothing
            b = bricks[idx]
            dx = (ball.centerx - b.brick.centerx) / BRICK_W
            dy = (ball.centery - b.brick.centery) / BRICK_H
            if abs(dx) > abs(dy)
                vx = copysign(abs(vx), dx)
            else
                vy = copysign(abs(vy), dy)
            end
            deleteat!(bricks, idx)
        end
    end
    ball_vel = (vx, vy)
end

bat_recent_vxs = []
bat_vx = 0
bat_prev_centerx = bat.centerx

function update_bat_vx()
    global bat_prev_centerx
    x = bat.centerx
    dx = x - bat_prev_centerx
    bat_prev_centerx = x
    history = bat_recent_vxs
    if length(history) >= 5
        pop!(history)
    end
    push!(history, dx)
    vx = sum(history) / length(history)
    global bat_vx = min(10, max(-10, vx))
end

function on_mouse_move(g::Game, pos)
    x, y = pos
    bat.centerx = x
    if bat.left < 0
        bat.left = 0
    elseif bat.right > WIDTH
        bat.right = WIDTH
    end
end