-- title:  blockbattle
-- author: pixelbath
-- desc:   a falling blocks game totally not inspired by anything previous
-- script: lua

local spriteIndexStart = 256
local blockSize = 5

local t = 0
local t_move = 0
local is_move_pressed = false
local is_das_triggered = false

-- i, o, j, l, t, s, z
local pieces = {
    {
        {
            {0, 0, 0, 0},
            {1, 1, 1, 1},
            {0, 0, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 1, 0, 0},
            {0, 1, 0, 0},
            {0, 1, 0, 0},
            {0, 1, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {0, 4, 4, 0},
            {0, 4, 4, 0},
            {0, 0, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {2, 2, 2, 0},
            {0, 0, 2, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 2, 0, 0},
            {0, 2, 0, 0},
            {2, 2, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {2, 0, 0, 0},
            {2, 2, 2, 0},
            {0, 0, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 2, 2, 0},
            {0, 2, 0, 0},
            {0, 2, 0, 0},
            {0, 0, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {3, 3, 3, 0},
            {3, 0, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 3, 0, 0},
            {0, 3, 0, 0},
            {0, 3, 3, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 0, 3, 0},
            {3, 3, 3, 0},
            {0, 0, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {3, 3, 0, 0},
            {0, 3, 0, 0},
            {0, 3, 0, 0},
            {0, 0, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {5, 5, 5, 0},
            {0, 5, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 5, 0, 0},
            {0, 5, 5, 0},
            {0, 5, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 5, 0, 0},
            {5, 5, 5, 0},
            {0, 0, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 5, 0, 0},
            {5, 5, 0, 0},
            {0, 5, 0, 0},
            {0, 0, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {0, 6, 6, 0},
            {6, 6, 0, 0},
            {0, 0, 0, 0},
        },
        {
            {6, 0, 0, 0},
            {6, 6, 0, 0},
            {0, 6, 0, 0},
            {0, 0, 0, 0},
        },
    },
    {
        {
            {0, 0, 0, 0},
            {7, 7, 0, 0},
            {0, 7, 7, 0},
            {0, 0, 0, 0},
        },
        {
            {0, 7, 0, 0},
            {7, 7, 0, 0},
            {7, 0, 0, 0},
            {0, 0, 0, 0},
        },
    },
}
local piece = {}

local board = {}

local game_state = {
    board = {},
    level = 1,
    piece_queue = {},
    piece_bag = {},
    update = nil,
}

local active_piece = {
    type = nil,
    rotation = 1,
    x = 3, y = 0,
}

local rules = {
    keyrepeat = 5,
    das = 20,
    are = 4,
}

-- gaiden, vs, single
local options = {
    gametype = "gaiden",
    shared_queue = true,
}

function shuffle(x)
    for i = #x, 2, -1 do
		local j = math.random(i)
		x[i], x[j] = x[j], x[i]
	end
end

function fill_bag()
    -- just throw a bag of randomized pieces in it
    local new_bag = { 1, 2, 3, 4, 5, 6, 7 }
    shuffle(new_bag)
    for i = 1,7 do
        table.insert(game_state.piece_bag, new_bag[i])
    end
end

function init_board()
	for y = 1, 20 do
		board[y] = {}
		for x = 1, 10 do
			board[y][x] = 0
		end
	end
end

function spawn_piece()
    trace("spawn")
    if #game_state.piece_bag <= 4 then
        fill_bag()
    end
    active_piece = {
        type = game_state.piece_bag[1],
        rotation = 1,
        x = 3, y = 0,
    }
    table.remove(game_state.piece_bag, 1)
end

function hard_drop()
    for i=20,0,-1 do
        local tempy = active_piece.y + i
        if test_can_fit(active_piece.type, active_piece.x, tempy, active_piece.rotation) then
            return i
        end
    end
    return 0

    -- active_piece.rotation = 1
    -- active_piece.type = active_piece.type + 1
    -- if active_piece.type > 7 then active_piece.type = 1 end
    -- trace("hard_drop: new type: "..tostring(active_piece.type)..", rot="..tostring(active_piece.rotation))
end
function soft_drop()
    active_piece.y = active_piece.y + 1
end
function try_move_right()
    active_piece.x = active_piece.x + 1
end
function try_move_left()
    active_piece.x = active_piece.x - 1
end

function handle_input()
    is_move_pressed = false
    if btnp(0) then
        -- spawn_piece()
        hard_drop()
	end
	if btn(1) then
        is_move_pressed = true
        if t_move <= 0 then
            t_move = rules.das
            soft_drop()
        end
	end
	if btn(2) then
        is_move_pressed = true
        if t_move <= 0 then
            if not is_das_triggered then
                t_move = rules.das
            else
                t_move = rules.keyrepeat
            end
            is_das_triggered = true
            try_move_left()
        end
	end
	if btn(3) then
        is_move_pressed = true
        if t_move <= 0 then
            if not is_das_triggered then
                t_move = rules.das
            else
                t_move = rules.keyrepeat
            end
            is_das_triggered = true
            try_move_right()
        end
	end
    if btnp(4) then
        active_piece.rotation = active_piece.rotation - 1
        if active_piece.rotation < 1 then
            active_piece.rotation = #pieces[active_piece.type]
        end
    end
    if btnp(5) then
        active_piece.rotation = active_piece.rotation + 1
        if active_piece.rotation > #pieces[active_piece.type] then
            active_piece.rotation = 1
        end
    end

    if is_move_pressed then
        t_move = t_move - 1
    else
        t_move = -1
        is_das_triggered = false
    end
end

function draw_board(xpos, ypos)
    -- draw next pieces
    for i=1,3 do
        local ptype = game_state.piece_bag[i]
        draw_piece(200, 20 + (i * 20), ptype, 1)
    end
end

function draw_piece(xpos, ypos, ptype, rotation)
	for y = 1, 4 do
        for x = 1, 4 do
            local block = pieces[ptype][rotation][y][x]
            if block ~= 0 then
                spr(
                    spriteIndexStart + block, 
                    (x - 1) * blockSize + xpos,
                    (y - 1) * blockSize + ypos
                )
            end
        end
    end
end

-- BEGIN state updates
function update_intro()
    if t == 30 then
        game_state.update = update_menu
    else
        print("intro " .. tostring(30-t))
    end
end

function update_menu()
    print("Press X to start")
    if btnp(5) then
        game_state.update = update_game
        init_board()
        fill_bag()
        spawn_piece()
    end
end

function update_game()
    handle_input()
    draw_board()
	draw_piece(active_piece.x * blockSize, active_piece.y * blockSize, active_piece.type, active_piece.rotation)
end
-- END state updates

function test_can_fit(ptype, xpos, ypos, rotation)
	for y = 1, 4 do
        for x = 1, 4 do
            local block = pieces[ptype][rotation][y][x]
            if block ~= 0 then
                if ypos + y > 21 or ypos + y < 0 then return false end
                trace("yooo "..tostring(ypos))
                -- TODO: some nil reference here idk
                local cell = game_state.board[ypos + y - 1][xpos + x - 1]
                if cell ~= 0 then
                    return false
                end
            end
        end
    end
    return true
end

function piece.move_left()
    local tempx = active_piece.x - 1
    if piece.test_can_fit(active_piece.type, tempx, active_piece.y, active_piece.rotation) then
        active_piece.x = tempx
        return true
    end
    return false
end

function piece.move_right()
    local tempx = active_piece.x + 1
    if piece.test_can_fit(active_piece.type, tempx, active_piece.y, active_piece.rotation) then
        active_piece.x = tempx
        return true
    end
    return false
end

function piece.soft_drop()
    local tempy = active_piece.y + 1
    if piece.test_can_fit(active_piece.type, active_piece.x, tempy, active_piece.rotation) then
        active_piece.y = tempy
        return true
    end
    return false
end

-- if return value is 0, piece should lock immediately
function piece.test_hard_drop()
    for i=20,0,-1 do
        local tempy = active_piece.y + i
        if piece.test_can_fit(active_piece.type, active_piece.x, tempy, active_piece.rotation) then
            return i
        end
    end
    return 0
end


game_state.update = update_intro


function TIC()
	cls(0)
    
    -- call our current state's update
    game_state.update()
    t = t + 1
end

-- <SPRITES>
-- 001:bbbbc0009aaab0009aaab0009aaab00089999000000000000000000000000000
-- 002:8aaab0008999a0008999a0008999a00088888000000000000000000000000000
-- 003:4444c00023334000233340002333400012222000000000000000000000000000
-- 004:3cccc0002444c0002444c0002444c00012222000000000000000000000000000
-- 005:55554000766650007666500076665000f7777000000000000000000000000000
-- 006:ddddc000feeed000feeed000feeed000fffff000000000000000000000000000
-- 007:3333400012223000122230001222300081111000000000000000000000000000
-- 016:8bbbb0008abba0008a99a0008999900088888000000000000000000000000000
-- 017:f5555000f6556000f6776000f7777000fffff000000000000000000000000000
-- 018:0aaa0000a000a000a000a000a000a0000aaa0000000000000000000000000000
-- 019:0666000060006000600060006000600006660000000000000000000000000000
-- 020:000000000bc000000abbc0000aaabbc00aaaaff00aaff0000ff0000000000000
-- 021:0000000005400000065540000666554006666ff0066ff0000ff0000000000000
-- 022:0000000000044000004cc4000444cc4004444440004444000004400000000000
-- </SPRITES>

-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>

