don't change anything in this game except for when you unlock the green sprite frog secret, when you are the frog, make it so you can move any tile individually, put it all in one code -- constants for block size and sprites
block_size = 8
grid_width = 15
grid_height = 13 -- adjusted grid height to fit the screen
green_sprite_idx = 2 -- sprite index for green block
gray_sprite_idx = 3 -- sprite index for gray blocks
golden_snowflake_sprite_idx = 4 -- sprite index for golden snowflakes
red_snowflake_sprite_idx = 5 -- sprite index for red snowflakes
rare_sprite_idx = 6 -- sprite index for rare block
special_sprite_idx = 7 -- sprite index for special block (appears infrequently)
large_sprite_idx = 8 -- sprite index for controllable large sprite

-- calculate ui and game offsets
ui_height = 8 -- height reserved for ui
game_offset_x = (128 - grid_width * block_size) / 2 -- center grid horizontally
game_offset_y = ui_height -- start grid beneath the ui
ui_margin = 4 -- margin for ui elements

-- player position (cursor)
player_x = 0
player_y = 0

-- grid to store blocks
blocks = {}
large_sprite = nil -- to store the position of the controllable large sprite
large_sprite_start_time = nil -- to store the start time of large sprite control

-- player score and game state
score = 0
time_limit = 60 * 5 -- 5 minutes in seconds
start_time = time()
game_over = false
game_won = false
final_time_str = "" -- to store final time string

-- scoring bubbles
scoring_bubbles = {}

-- bubble display time
bubble_display_time = 1.5 -- 1.5 seconds
points_per_second = 300 -- points rewarded per second for controlling the large sprite
large_sprite_active_time = 20 -- duration large sprite is controllable in seconds
last_score_time = 0 -- track the last time scored when controlling large sprite

-- initialize blocks
function init_blocks()
    for x=1, grid_width do
        blocks[x] = {}
        for y=1, grid_height do
            blocks[x][y] = 0
        end
    end
end

-- initialize game state
function _init()
    init_blocks()
end

-- update game state
function _update()
    if game_over or game_won then
        return
    end

    -- check time
    local elapsed_time = time() - start_time
    if elapsed_time > time_limit then
        if score < 8000 then
            game_over = true
        end
    end

    -- check win condition
    if score >= 8000 then
        game_won = true
        final_time_str = "final time: " .. to_minutes(time_limit - elapsed_time) -- capture final time
    end
  
    -- control logic for large sprite
    if large_sprite then
        local elapsed_control_time = time() - large_sprite_start_time
        if elapsed_control_time >= large_sprite_active_time then
            large_sprite = nil -- stop controlling the large sprite
        else
            -- generate points per second
            if time() - last_score_time >= 1 then
                score += points_per_second
                last_score_time = time()
            end
            
            -- move large sprite
            local moved = false
            if (btnp(0)) then large_sprite.x = max(0, large_sprite.x - block_size); moved = true end
            if (btnp(1)) then large_sprite.x = min(128 - block_size * 2, large_sprite.x + block_size); moved = true end
            if (btnp(2)) then large_sprite.y = max(ui_height, large_sprite.y - block_size); moved = true end
            if (btnp(3)) then large_sprite.y = min(128 - block_size * 2, large_sprite.y + block_size); moved = true end

            -- move sprites
            if moved then
                move_sprites_with_large_sprite()
            end

            -- update bubbles but do not do other updates when large sprite is active
            update_scoring_bubbles()
            return
        end
    end

    -- move cursor
    if (btnp(0)) then player_x = max(0, player_x - 1) end
    if (btnp(1)) then player_x = min(grid_width - 1, player_x + 1) end
    if (btnp(2)) then player_y = max(0, player_y - 1) end
    if (btnp(3)) then player_y = min(grid_height - 1, player_y + 1) end

    -- place block
    if (btnp(4)) then
        if blocks[player_x + 1][player_y + 1] == 0 then
            local rand = rnd()

            if rand < 0.01 then
                blocks[player_x + 1][player_y + 1] = special_sprite_idx -- special block with 1% chance
            elseif rand < 0.06 then
                blocks[player_x + 1][player_y + 1] = rare_sprite_idx -- rare block with 5% chance
            elseif rand < 0.31 then
                blocks[player_x + 1][player_y + 1] = green_sprite_idx -- green block with 25% chance
            else
                blocks[player_x + 1][player_y + 1] = gray_sprite_idx -- gray block with 69% chance
            end

            check_for_scoring()
        end
    end

    -- erase block
    if (btnp(5)) then
        if blocks[player_x + 1][player_y + 1] ~= 0 then
            blocks[player_x + 1][player_y + 1] = 0
        end
    end

    -- update scoring bubbles
    update_scoring_bubbles()
end

-- move other sprites with the large sprite
function move_sprites_with_large_sprite()
    local lx, ly = large_sprite.x, large_sprite.y
    local ls_col = flr((lx - game_offset_x) / block_size) + 1
    local ls_row = flr((ly - game_offset_y) / block_size) + 1

    for x=1, grid_width do
        for y=1, grid_height do
            if blocks[x][y] > 0 then
                -- calculate the effect of the large sprite
                local dx = x - ls_col
                local dy = y - ls_row

                if abs(dx) <= 1 and abs(dy) <= 1 then
                    local target_x = x + (dx > 0 and 1 or (dx < 0 and -1 or 0))
                    local target_y = y + (dy > 0 and 1 or (dy < 0 and -1 or 0))

                    -- ensure target stays within grid
                    if target_x >= 1 and target_x <= grid_width and 
                       target_y >= 1 and target_y <= grid_height and 
                       blocks[target_x][target_y] == 0 then

                        blocks[target_x][target_y] = blocks[x][y]
                        blocks[x][y] = 0
                    end
                end
            end
        end
    end
end

-- check for 3-in-a-row greens, rares, or specials to score points and play sound
function check_for_scoring()
    -- horizontal
    for y=1, grid_height do
        for x=1, grid_width-2 do
            if blocks[x][y] == green_sprite_idx and blocks[x+1][y] == green_sprite_idx and blocks[x+2][y] == green_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y], blocks[x+2][y] = 0, 0, 0
            end
            if blocks[x][y] == rare_sprite_idx and blocks[x+1][y] == rare_sprite_idx and blocks[x+2][y] == rare_sprite_idx then
                add_scoring_bubble(x, y, 800)
                sfx(2)
                blocks[x][y], blocks[x+1][y], blocks[x+2][y] = 0, 0, 0
            end
            if blocks[x][y] == special_sprite_idx and blocks[x+1][y] == special_sprite_idx and blocks[x+2][y] == special_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y], blocks[x+2][y] = 0, 0, 0
                -- create a controllable large sprite
                large_sprite = {x = game_offset_x + x * block_size, y = game_offset_y + y * block_size}
                large_sprite_start_time = time() -- record the start time for controlling the large sprite
                last_score_time = time() -- reset scoring time for large sprite
            end
        end
    end

    -- vertical
    for x=1, grid_width do
        for y=1, grid_height-2 do
            if blocks[x][y] == green_sprite_idx and blocks[x][y+1] == green_sprite_idx and blocks[x][y+2] == green_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x][y+1], blocks[x][y+2] = 0, 0, 0
            end
            if blocks[x][y] == rare_sprite_idx and blocks[x][y+1] == rare_sprite_idx and blocks[x][y+2] == rare_sprite_idx then
                add_scoring_bubble(x, y, 800)
                sfx(2)
                blocks[x][y], blocks[x][y+1], blocks[x][y+2] = 0, 0, 0
            end
            if blocks[x][y] == special_sprite_idx and blocks[x][y+1] == special_sprite_idx and blocks[x][y+2] == special_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x][y+1], blocks[x][y+2] = 0, 0, 0
                -- create a controllable large sprite
                large_sprite = {x = game_offset_x + x * block_size, y = game_offset_y + y * block_size}
                large_sprite_start_time = time() -- record the start time for controlling the large sprite
                last_score_time = time() -- reset scoring time for large sprite
            end
        end
    end

    -- diagonal (down-right)
    for x=1, grid_width-2 do
        for y=1, grid_height-2 do
            if blocks[x][y] == green_sprite_idx and blocks[x+1][y+1] == green_sprite_idx and blocks[x+2][y+2] == green_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y+1], blocks[x+2][y+2] = 0, 0, 0
            end
            if blocks[x][y] == rare_sprite_idx and blocks[x+1][y+1] == rare_sprite_idx and blocks[x+2][y+2] == rare_sprite_idx then
                add_scoring_bubble(x, y, 800)
                sfx(2)
                blocks[x][y], blocks[x+1][y+1], blocks[x+2][y+2] = 0, 0, 0
            end
            if blocks[x][y] == special_sprite_idx and blocks[x+1][y+1] == special_sprite_idx and blocks[x+2][y+2] == special_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y+1], blocks[x+2][y+2] = 0, 0, 0
                -- create a controllable large sprite
                large_sprite = {x = game_offset_x + x * block_size, y = game_offset_y + y * block_size}
                large_sprite_start_time = time() -- record the start time for controlling the large sprite
                last_score_time = time() -- reset scoring time for large sprite
            end
        end
    end

    -- diagonal (up-right)
    for x=1, grid_width-2 do
        for y=3, grid_height do
            if blocks[x][y] == green_sprite_idx and blocks[x+1][y-1] == green_sprite_idx and blocks[x+2][y-2] == green_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y-1], blocks[x+2][y-2] = 0, 0, 0
            end
            if blocks[x][y] == rare_sprite_idx and blocks[x+1][y-1] == rare_sprite_idx and blocks[x+2][y-2] == rare_sprite_idx then
                add_scoring_bubble(x, y, 800)
                sfx(2)
                blocks[x][y], blocks[x+1][y-1], blocks[x+2][y-2] = 0, 0, 0
            end
            if blocks[x][y] == special_sprite_idx and blocks[x+1][y-1] == special_sprite_idx and blocks[x+2][y-2] == special_sprite_idx then
                add_scoring_bubble(x, y, 300)
                sfx(2)
                blocks[x][y], blocks[x+1][y-1], blocks[x+2][y-2] = 0, 0, 0
                -- create a controllable large sprite
                large_sprite = {x = game_offset_x + x * block_size, y = game_offset_y + y * block_size}
                large_sprite_start_time = time() -- record the start time for controlling the large sprite
                last_score_time = time() -- reset scoring time for large sprite
            end
        end
    end
end

-- add a scoring bubble
function add_scoring_bubble(x, y, points)
    score += points
    local bx = game_offset_x + (x - 1) * block_size
    local by = game_offset_y + (y - 1) * block_size
    add(scoring_bubbles, {x=bx, y=by, points=points, time=time()})
end

-- update scoring bubbles
function update_scoring_bubbles()
    for bubble in all(scoring_bubbles) do
        if time() - bubble.time > bubble_display_time then
            del(scoring_bubbles, bubble)
        end
    end
end

-- draw the game
function _draw()
    cls()

    if game_over then
        print_centered("game over", 64, 64, 8)
        scatter_red_snowflakes() -- show red snowflakes
        return
    elseif game_won then
        print_centered("you win", 64, 64, 10)
        print_centered(final_time_str, 64, 76, 7) -- display captured final time below
        scatter_snowflakes()
        return
    end

    -- prepare score and time strings
    local score_str = "score: "..tostr(score)
    local time_str = "time: "..to_minutes(time_limit - (time() - start_time))
    
    -- calculate positions for score and time
    local ui_total_text = score_str.."  "..time_str
    local ui_x = 128 - ui_margin - (#ui_total_text * 4)

    -- draw score and time side by side in the top right corner
    print(score_str, ui_x, ui_margin, 7)
    print(time_str, ui_x + (#score_str * 4) + 8, ui_margin, 7)

    -- draw game grid below the ui
    for x=1, grid_width do
        for y=1, grid_height do
            local bx = game_offset_x + (x - 1) * block_size
            local by = game_offset_y + (y - 1) * block_size
            if blocks[x][y] > 0 then
                spr(blocks[x][y], bx, by)
            end
        end
    end

    -- draw large sprite if it exists
    if large_sprite then
        spr(large_sprite_idx, large_sprite.x, large_sprite.y, 2, 2) -- large sprite with a scale factor of 2x2
        
        -- display the text "froggin' fantastic" at the bottom of the screen
        print_centered("froggin' fantastic", 64, 120, 11)
    end

    -- draw scoring bubbles
    for bubble in all(scoring_bubbles) do
        print("+"..bubble.points, bubble.x, bubble.y, 10)
    end

    -- draw cursor (outline of the currently selected block)
    local cx = game_offset_x + player_x * block_size
    local cy = game_offset_y + player_y * block_size
    rect(cx, cy, cx + block_size - 1, cy + block_size - 1, 7)
end

-- print centered text
function print_centered(text, x, y, color)
    local length = #text * 4
    print(text, x - length / 2, y, color)
end

-- convert seconds to a mm:ss format
function to_minutes(sec)
    return flr(sec/60)..":"..sub("0"..flr(sec%60), -2)
end

-- scatter golden snowflakes using sprite 004
function scatter_snowflakes()
    for i = 1, 50 do
        -- randomize x and y positions across the screen
        local x = rnd(128 - block_size)
        local y = rnd(128 - block_size)
        spr(golden_snowflake_sprite_idx, x, y)
    end
end

-- scatter slow-moving red snowflakes using sprite 005
function scatter_red_snowflakes()
    for i = 1, 50 do
        -- randomize x and y positions and move them slowly
        local x = rnd(128 - block_size)
        local y = rnd(128 - block_size)
        spr(red_snowflake_sprite_idx, x, y)
    end
end