--[[
    Название: пол это лава - генерация
    Автор: Avondale, дискорд - avonda
    Описание механики: 
        игра пол это лава, но со случайно сгенерироваными уровнями
        в конфиге можно задать сид генерации, один и тот же сид = одна и та же серия уровней

    Идеи по доработке: 
]]
math.randomseed(os.time())
require("avonlib")

local CLog = require("log")
local CInspect = require("inspect")
local CHelp = require("help")
local CJson = require("json")
local CTime = require("time")
local CAudio = require("audio")
local CColors = require("colors")

local tGame = {
    Cols = 24,
    Rows = 15, 
    Buttons = {}, 
}
local tConfig = {}

-- стейты или этапы игры
local GAMESTATE_SETUP = 1
local GAMESTATE_GAME = 2
local GAMESTATE_POSTGAME = 3
local GAMESTATE_FINISH = 4

local bGamePaused = false
local iGameState = GAMESTATE_SETUP
local iPrevTickTime = 0

local tGameStats = {
    StageLeftDuration = 0, 
    StageTotalDuration = 0, 
    CurrentStars = 0,
    TotalStars = 0,
    CurrentLives = 0,
    TotalLives = 0,
    Players = { -- максимум 6 игроков
        { Score = 0, Lives = 0, Color = CColors.GREEN },
        { Score = 0, Lives = 0, Color = CColors.NONE },
        { Score = 0, Lives = 0, Color = CColors.NONE },
        { Score = 0, Lives = 0, Color = CColors.NONE },
        { Score = 0, Lives = 0, Color = CColors.NONE },
        { Score = 0, Lives = 0, Color = CColors.NONE },
    },
    TargetScore = 0,
    StageNum = 1,
    TotalStages = 0,
    TargetColor = CColors.NONE,
    ScoreboardVariant = 1,
}

local tGameResults = {
    Won = false,
    AfterDelay = false,
    PlayersCount = 0,
    Score = 0,
    Color = CColors.NONE,
}

local tFloor = {} 
local tButtons = {}

local tFloorStruct = { 
    iColor = CColors.NONE,
    iBright = CColors.BRIGHT0,
    bClick = false,
    bDefect = false,
    iWeight = 0,
    bAnimated = false,
    bProtectedFromLava = false,
    iCoinId = 0,
}
local tButtonStruct = { 
    bClick = false,
    bDefect = false,
}

function StartGame(gameJson, gameConfigJson)
    tGame = CJson.decode(gameJson)
    tConfig = CJson.decode(gameConfigJson)

    for iX = 1, tGame.Cols do
        tFloor[iX] = {}    
        for iY = 1, tGame.Rows do
            tFloor[iX][iY] = CHelp.ShallowCopy(tFloorStruct) 
        end
    end

    for _, iId in pairs(tGame.Buttons) do
        tButtons[iId] = CHelp.ShallowCopy(tButtonStruct)
    end    

    iPrevTickTime = CTime.unix()

    tGame.iMinX = 1
    tGame.iMinY = 1
    tGame.iMaxX = tGame.Cols
    tGame.iMaxY = tGame.Rows
    if AL.NFZ.bLoaded then
        tGame.iMinX = AL.NFZ.iMinX
        tGame.iMinY = AL.NFZ.iMinY
        tGame.iMaxX = AL.NFZ.iMaxX
        tGame.iMaxY = AL.NFZ.iMaxY
    end
    tGame.CenterX = math.floor((tGame.iMaxX-tGame.iMinX+1)/2)
    tGame.CenterY = math.ceil((tGame.iMaxY-tGame.iMinY+1)/2)

    CGameMode.InitGameMode()
    CGameMode.Announcer()
end

function NextTick()
    if iGameState == GAMESTATE_SETUP then
        GameSetupTick()
    end

    if iGameState == GAMESTATE_GAME then
        GameTick()
    end

    if iGameState == GAMESTATE_POSTGAME then
        PostGameTick()

        if not tGameResults.AfterDelay then
            tGameResults.AfterDelay = true
            return tGameResults
        end
    end

    if iGameState == GAMESTATE_FINISH then
        tGameResults.AfterDelay = false
        return tGameResults
    end     

    AL.CountTimers((CTime.unix() - iPrevTickTime) * 1000)
    iPrevTickTime = CTime.unix()
end

function GameSetupTick()
    SetGlobalColorBright(CColors.NONE, tConfig.Bright)
    --SetAllButtonColorBright(CColors.BLUE, tConfig.Bright)
    CPaint.GameSetup()
end

function GameTick()
    SetGlobalColorBright(CColors.NONE, tConfig.Bright)
    CPaint.GameField()
end

function PostGameTick()
    
end

function RangeFloor(setPixel, setButton)
    for iX = 1, tGame.Cols do
        for iY = 1, tGame.Rows do
            setPixel(iX , iY, tFloor[iX][iY].iColor, tFloor[iX][iY].iBright)
        end
    end

    for i, tButton in pairs(tButtons) do
        setButton(i, tButton.iColor, tButton.iBright)
    end
end

function SwitchStage()
    if iGameState == GAMESTATE_GAME then
        if CGameMode.bRoundStarted then
            CGameMode.EndRound()
        else
            CGameMode.iCountdown = 0
        end
    end
end

--GAMEMODE
CGameMode = {}
CGameMode.iCountdown = 0
CGameMode.bRoundStarted = false

CGameMode.iMapCoinCount = 0
CGameMode.iMapCoinReq = 0
CGameMode.iMapCoinCollected = 0

CGameMode.tGameSetupCoins = {}
CGameMode.iGameSetupCollectedCoins = 0

CGameMode.iRoundTimeLimit = 60

CGameMode.bCanStart = false

CGameMode.InitGameMode = function()
    if tConfig.Seed ~= 0 then 
        math.randomseed(tonumber(tConfig.Seed))
    else
        local iSeed = math.random(1, tConfig.Seed_Max)
        math.randomseed(iSeed)
        --CLog.print("Seed: "..iSeed)
    end

    CGameMode.tGameSetupCoins[1] = {}
    CGameMode.tGameSetupCoins[1].bCollected = false
    repeat
        CGameMode.tGameSetupCoins[1].iX = math.random(math.floor(tGame.Cols/2)-3,math.floor(tGame.Cols/2)+3)
        CGameMode.tGameSetupCoins[1].iY = math.random(math.floor(tGame.Rows/2)-3,math.floor(tGame.Rows/2)+3)
    until not tFloor[CGameMode.tGameSetupCoins[1].iX][CGameMode.tGameSetupCoins[1].iY].bDefect 
    tFloor[CGameMode.tGameSetupCoins[1].iX][CGameMode.tGameSetupCoins[1].iY].iCoinId = 1
    tGameStats.TotalStars = 1

    tGameStats.TotalStages = tConfig.RoundCount
end

CGameMode.Announcer = function()
    CAudio.PlayVoicesSync("quest/quest-game.mp3")
    CAudio.PlayVoicesSync("press-center-for-start.mp3")

    AL.NewTimer((CAudio.GetVoicesDuration("quest/quest-game.mp3"))*1000, function()
        CGameMode.bCanStart = true
    end)
end

CGameMode.GameSetupRandomCoins = function()
    for iCoin = 1, math.random(5,6) do
        local iX = math.random(1, tGame.Cols)
        local iY = math.random(1, tGame.Rows)

        if tFloor[iX] and tFloor[iX][iY] and not tFloor[iX][iY].bDefect and tFloor[iX][iY].iCoinId == 0 then
            local iCoinId = #CGameMode.tGameSetupCoins+1
            CGameMode.tGameSetupCoins[iCoinId] = {}
            CGameMode.tGameSetupCoins[iCoinId].iX = iX
            CGameMode.tGameSetupCoins[iCoinId].iY = iY
            CGameMode.tGameSetupCoins[iCoinId].bCollected = false
            tFloor[iX][iY].iCoinId = iCoinId
        end
    end

    tGameStats.TotalStars = #CGameMode.tGameSetupCoins
end

CGameMode.PlayerCollectGameSetupCoin = function(iCoinId)
    CGameMode.tGameSetupCoins[iCoinId].bCollected = true
    CGameMode.iGameSetupCollectedCoins = CGameMode.iGameSetupCollectedCoins + 1

    tGameStats.CurrentStars = tGameStats.CurrentStars + 1

    if CGameMode.iGameSetupCollectedCoins == #CGameMode.tGameSetupCoins then
        CGameMode.EndGameSetup()
        CAudio.PlaySystemSync(CAudio.STAGE_DONE)
    else
        CAudio.PlaySystemAsync(CAudio.CLICK)
    end
end

CGameMode.EndGameSetup = function()
    iGameState = GAMESTATE_GAME
    CGameMode.StartNextRoundCountDown(tConfig.RoundCountdown)
end

CGameMode.StartNextRoundCountDown = function(iCountDownTime)
    CMap.GenerateRandomMap()

    CGameMode.iCountdown = iCountDownTime

    CAudio.PlayVoicesSync("stand_on_green_zone_and_wait.mp3")

    AL.NewTimer(1000, function()
        CAudio.ResetSync()
        tGameStats.StageLeftDuration = CGameMode.iCountdown

        if CGameMode.iCountdown <= 0 then
            if tGameStats.StageNum == 0 then
                CGameMode.StartGame()
            end

            CGameMode.StartRound()
            return nil
        else
            CAudio.PlayLeftAudio(CGameMode.iCountdown)
            CGameMode.iCountdown = CGameMode.iCountdown - 1

            return 1000
        end
    end)
end

CGameMode.StartGame = function()
    CAudio.PlayVoicesSync(CAudio.START_GAME)
end

CGameMode.EndGame = function(bVictory)
    CAudio.StopBackground()

    if bVictory then
        tGameResults.Won = true
        CAudio.PlayVoicesSync(CAudio.VICTORY)
        SetGlobalColorBright(CColors.GREEN, tConfig.Bright)
        tGameResults.Color = CColors.GREEN
    else
        tGameResults.Won = false
        CAudio.PlayVoicesSync(CAudio.DEFEAT)
        SetGlobalColorBright(CColors.RED, tConfig.Bright)
        tGameResults.Color = CColors.RED
    end

    iGameState = GAMESTATE_POSTGAME
    AL.NewTimer(10000, function()
        iGameState = GAMESTATE_FINISH
    end)        
end

CGameMode.StartRound = function()
    CAudio.PlayRandomBackground()
    CGameMode.bRoundStarted = true

    tGameStats.CurrentStars = 0
    tGameStats.TotalStars = CGameMode.iMapCoinReq

    tGameStats.StageLeftDuration = CGameMode.iRoundTimeLimit
    AL.NewTimer(1000, function()
        if CGameMode.bRoundStarted then
            tGameStats.StageLeftDuration = tGameStats.StageLeftDuration - 1
        
            if tGameStats.StageLeftDuration <= 0 then
                if CGameMode.iMapCoinCollected >= CGameMode.iMapCoinReq then
                    CGameMode.EndRound()
                else
                    CGameMode.EndGame(false)
                end

                return nil;
            end

            return 1000
        end
    end)
    AL.NewTimer(150, function()
        if not CGameMode.bRoundStarted then return nil; end

        CBlock.CalculateMovableObjects()

        return 150
    end)
end

CGameMode.EndRound = function()
    CAudio.StopBackground()
    CGameMode.bRoundStarted = false

    tGameResults.Score = tGameResults.Score + (tGameStats.StageLeftDuration*10) --+ ((tConfig.RoundTimeLimit_Max - tConfig.RoundTimeLimit)*4)

    tGameStats.StageLeftDuration = 0

    CBlock.Clear()

    tGameStats.CurrentStars = tGameResults.Score
    tGameStats.TotalStars = tGameResults.Score

    if tGameStats.StageNum == tGameStats.TotalStages then
        CGameMode.EndGame(true)
    else
        tGameStats.StageNum = tGameStats.StageNum + 1
        CGameMode.StartNextRoundCountDown(tConfig.RoundCountdown)
    end
end

CGameMode.PlayerCollectCoin = function()
    CAudio.PlaySystemAsync(CAudio.CLICK)

    CGameMode.iMapCoinCollected = CGameMode.iMapCoinCollected + 1
    tGameStats.CurrentStars = tGameStats.CurrentStars + 1
    tGameResults.Score = tGameResults.Score + 25
    tGameStats.CurrentStars = CGameMode.iMapCoinCollected

    if CGameMode.iMapCoinCollected == CGameMode.iMapCoinReq then
        CAudio.PlaySystemAsync(CAudio.STAGE_DONE)
        tGameStats.TotalStars = CGameMode.iMapCoinCount
    elseif CGameMode.iMapCoinCollected == CGameMode.iMapCoinCount then
        CAudio.PlaySystemAsync(CAudio.STAGE_DONE)
        CGameMode.EndRound()
    end
end

CGameMode.PlayerCollectLava = function(iX, iY)
    if not CBlock.tBlocks[CBlock.LAYER_GROUND][iX][iY].bCooldown then
        CAudio.PlaySystemAsync(CAudio.MISCLICK)
        tGameResults.Score = tGameResults.Score - 10 

        CPaint.AnimatePixelFlicker(iX, iY, 5, CColors.NONE)

        CBlock.tBlocks[CBlock.LAYER_GROUND][iX][iY].bCooldown = true
        AL.NewTimer(1000, function()
            CBlock.tBlocks[CBlock.LAYER_GROUND][iX][iY].bCooldown = false
        end)
    end
end
--//

--MAP GENERATION
CMap = {}

CMap.GEN_TYPE_RANDOM = 0
CMap.GEN_TYPE_SAFEEDGES = 1
CMap.GEN_TYPE_SQUAREFIELD = 2
CMap.GEN_TYPE_LL = 3
CMap.GEN_TYPE_DOORS = 4
CMap.GEN_TYPE_CROSSHAIR = 5
CMap.GEN_TYPE_CENTERSAFE = 6
CMap.GEN_TYPE_ARROWS = 7
CMap.GEN_TYPE_MAX = 7

CMap.iGenType = 0

--CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)

CMap.GenerateRandomMap = function()
    CMap.iGenType = CMap.NewGenType(CMap.iGenType)

    CMap.GenerateMapWithType[CMap.iGenType]();

    CGameMode.iMapCoinCount = CBlock.tBlocksCountPerType[CBlock.BLOCK_TYPE_COIN]
    CGameMode.iMapCoinReq = math.floor(CGameMode.iMapCoinCount/2)   
    CGameMode.iRoundTimeLimit = math.floor(CBlock.tBlocksCountPerType[CBlock.BLOCK_TYPE_COIN] * tConfig.RoundTimePerCoin) 
end

CMap.NewGenType = function(iOldType)
    local iNewType = 0

    repeat iNewType = math.random(1, CMap.GEN_TYPE_MAX)
    until iNewType ~= iOldType and CMap.CheckRoomForGenType(iNewType)

    return iNewType 
end

CMap.CheckRoomForGenType = function(iGenType)
    if iGenType == CMap.GEN_TYPE_ARROWS and tGame.iMaxX-5 <= tGame.iMaxY then return false end

    return true
end

CMap.CreateCoin = function(iX, iY)
    if CBlock.IsEmpty(CBlock.LAYER_SAFEGROUND, iX, iY) and CBlock.tBlocks[CBlock.LAYER_GROUND][iX][iY].iBlockType == CBlock.BLOCK_TYPE_GROUND then
        CBlock.NewBlock(CBlock.LAYER_COINS, iX, iY, CBlock.BLOCK_TYPE_COIN)
        return true
    end
    return false
end

CMap.GenerateMapWithType = {}

---GEN TYPES

----GEN TYPE 1 - SAFEEDGES
CMap.GenerateMapWithType[CMap.GEN_TYPE_SAFEEDGES] = function()
    local iCenterType = math.random(1, 3)

    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if (iX <= tGame.iMinX+1 or iX >= tGame.iMaxX-1) or (iY <= tGame.iMinY+1 or iY >= tGame.iMaxY-1) then
                CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)
            elseif iCenterType == 3 and ((iX <= tGame.iMinX+3 or iX >= tGame.iMaxX-3) or (iY <= tGame.iMinY+3 or iY >= tGame.iMaxY-3)) then
                if math.random(1,3) == 2 then
                    CMap.CreateCoin(iX, iY)
                end
            elseif iCenterType == 3 then
                CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_LAVA)
            end
        end
    end

    if iCenterType == 1 then
        if math.random(1,2) == 2 then
            CMap.CreateMovingRect(tGame.iMinX+2, tGame.iMinY+2, math.random(1,3), tGame.iMaxY-tGame.iMinY-3, 1, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        else
            CMap.CreateMovingRect(tGame.iMinX+2, tGame.iMinY+2, tGame.iMaxX-tGame.iMinX-3, math.random(1,3), 0, 1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        end

        local iShapesCount = math.random(1,2)
        local iShapeSizeX = math.ceil((tGame.iMaxX-tGame.iMinX-3)/iShapesCount)
        local iShapeSizeY = tGame.iMaxY-tGame.iMinY-3
        for i = 1, iShapesCount do
            local tShape = CMap.CreatePatternShape(iShapeSizeX, iShapeSizeY)
            local iXOffset = (iShapeSizeX * i) - iShapeSizeX
            CBlock.NewBlockFormationFromShape(CBlock.LAYER_COINS, tGame.iMinX+2+iXOffset, tGame.iMinY+2, CBlock.BLOCK_TYPE_COIN, tShape)
        end
    elseif iCenterType == 2 or iCenterType == 3 then
        local tPoints = {}
        tPoints[1] = {iX = tGame.iMinX+2, iY = tGame.iMinY+2}
        tPoints[2] = {iX = tGame.iMaxX-3, iY = tGame.iMinY+2}
        tPoints[3] = {iX = tGame.iMaxX-3, iY = tGame.iMaxY-3}
        tPoints[4] = {iX = tGame.iMinX+2, iY = tGame.iMaxY-3}
        if math.random(1,2) == 2 then tPoints = ReverseTable(tPoints); end
        local iPathId = CBlock.NewPath(tPoints)

        local iObjectCount = 4
        if iCenterType == 3 then iObjectCount = 3; end
        for iObject = 1, iObjectCount do
            local iObjectId = CMap.CreateMovingRect(tPoints[iObject].iX, tPoints[iObject].iY, 2, 2, 0, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathId = iPathId
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = iObject
        end

        if iCenterType == 2 then
            local tShape = CMap.CreatePatternShape(tGame.iMaxX-tGame.iMinX-7, tGame.iMaxY-tGame.iMinY-7)
            CBlock.NewBlockFormationFromShape(CBlock.LAYER_COINS, tGame.iMinX+4, tGame.iMinY+4, CBlock.BLOCK_TYPE_COIN, tShape)        
        end
    end
end
----//

---- GENTYPE 2 - SQUAREFIELD
CMap.GenerateMapWithType[CMap.GEN_TYPE_SQUAREFIELD] = function()
    local tShape = CMap.CreatePatternShape(tGame.iMaxX-tGame.iMinX, tGame.iMaxY-tGame.iMinY+1, 10)
    CBlock.NewBlockFormationFromShape(CBlock.LAYER_SAFEGROUND, tGame.iMinX+1, tGame.iMinY, CBlock.BLOCK_TYPE_SAFEGROUND, tShape)      

    local iLavaType = math.random(1, 2)

    if iLavaType == 1 then
    local iRand = math.random(1,5)
    if iRand <= 3 then
        local iObjectId = CMap.CreateMovingRect(tGame.iMinX, tGame.iMinY, math.random(1,3), tGame.iMaxY-tGame.iMinY+1, 1, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
    end
    if iRand >= 3 then
        local iObjectId = CMap.CreateMovingRect(tGame.iMinX, tGame.iMinY, tGame.iMaxX-tGame.iMinX+1, math.random(1,3), 0, 1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
    end    
    else
        for iPath = 0, math.floor(tGame.iMaxX/4)-1 do
            local iSpawnX = ((iPath+1)*4)
            local tPoints = {}
            tPoints[1] = {iX = iSpawnX, iY = tGame.iMinY}
            tPoints[2] = {iX = iSpawnX, iY = tGame.iMaxY-1}
            local iPathId = CBlock.NewPath(tPoints)
            local iObjectId = CMap.CreateMovingRect(iSpawnX, math.random(tGame.iMinY, tGame.iMaxY), 2, 2, 0, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathId = iPathId
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = math.random(1,2)
        end
    end
    
    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if math.random(1,4) == 2 then
                CMap.CreateCoin(iX, iY)
            end
        end
    end
end
----//

---- GENTYPE 3 - LL
CMap.GenerateMapWithType[CMap.GEN_TYPE_LL] = function()
    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if ((iX >= tGame.iMinX+3 and iX <= tGame.iMaxX-3) and (iY >= tGame.iMinY+3 and iY <= tGame.iMaxY-3)) 
            and ((iX == tGame.iMinX+3 or iX == tGame.iMinX+4) or (iX == tGame.iMaxX-3 or iX == tGame.iMaxX-4) or (iY == tGame.iMinY+3 or iY == tGame.iMinY+4) or (iY == tGame.iMaxY-3 or iY == tGame.iMaxY-4)) then
                if not (iY >= tGame.iMaxY-5 and iX >= tGame.iMinX+5 and iX < tGame.CenterX) and not (iY <= tGame.iMinY+5 and iX >= tGame.CenterX+2 and iX <= tGame.iMaxX-5) then
                    CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)
                end
            elseif math.random(1,4) == 2 then
                CMap.CreateCoin(iX, iY)
            end
        end
    end

    local tPoints = {}
    tPoints[1] = {iX = tGame.iMinX, iY = tGame.iMinY}
    tPoints[2] = {iX = tGame.iMaxX-2, iY = tGame.iMinY}
    tPoints[3] = {iX = tGame.iMaxX-2, iY = tGame.iMaxY-2}
    tPoints[4] = {iX = tGame.iMinX, iY = tGame.iMaxY-2}
    if math.random(1,2) == 2 then tPoints = ReverseTable(tPoints); end
    local iPathId = CBlock.NewPath(tPoints)

    for iObject = 1, 4 do
        local iObjectId = CMap.CreateMovingRect(tPoints[iObject].iX, tPoints[iObject].iY, 3, 3, 0, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathId = iPathId
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = iObject
    end

    if math.random(1,3) == 2 then
        tPoints = {}
        tPoints[1] = {iX = tGame.iMinX+5, iY = tGame.iMinY+5}
        tPoints[2] = {iX = tGame.iMaxX-6, iY = tGame.iMinY+5}
        tPoints[3] = {iX = tGame.iMaxX-6, iY = tGame.iMaxY-6}
        tPoints[4] = {iX = tGame.iMinX+5, iY = tGame.iMaxY-6}
        if math.random(1,2) == 2 then tPoints = ReverseTable(tPoints); end
        iPathId = CBlock.NewPath(tPoints)

        local iObjectId = CMap.CreateMovingRect(tPoints[1].iX, tPoints[1].iY, 2, 2, 0, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathId = iPathId
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = 1
    end
end
----//

---- GENTYPE 4 - DOORS
CMap.GenerateMapWithType[CMap.GEN_TYPE_DOORS] = function()
    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if (iX == tGame.CenterX or iX == tGame.CenterX+1) or (iY >= tGame.CenterY-1 and iY <= tGame.CenterY+1) then
                CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)
            end
        end
    end    

    local iDoorSizeX = tGame.CenterX-1
    local iDoorSizeY = tGame.CenterY-2

    local function makeADoor(iX, iY, bOpen)
        CBlock.NewBlockFormationFromShape(CBlock.LAYER_COINS, iX, iY, CBlock.BLOCK_TYPE_COIN, CMap.CreatePatternShape(iDoorSizeX, iDoorSizeY))

        local tPoints = {}
        tPoints[1] = {iX = iX, iY = iY, iWaitTime = 10}
        tPoints[2] = {iX = iX-iDoorSizeX, iY = iY, iWaitTime = 10}
        if iX > tGame.CenterX then tPoints[2].iX = iX+iDoorSizeX; end
        local iPathId = CBlock.NewPath(tPoints)

        local iObjectId = CMap.CreateMovingRect(iX, iY, iDoorSizeX, iDoorSizeY, 0, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathId = iPathId
        CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = 1   
        if bOpen then 
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iPathPoint = 2
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].iX = tPoints[2].iX
        end
    end

    makeADoor(tGame.iMinX, tGame.iMinY, true)
    makeADoor(tGame.CenterX+2, tGame.iMinY, false)
    makeADoor(tGame.iMinX, tGame.CenterY+2, false)
    makeADoor(tGame.CenterX+2, tGame.CenterY+2, true)
end
----//

---- GENTYPE 5 - CROSSHAIR
CMap.GenerateMapWithType[CMap.GEN_TYPE_CROSSHAIR] = function()
    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if ((iX == tGame.CenterX or iX == tGame.CenterX+1) and (iY < tGame.CenterY-1 or iY > tGame.CenterY+1)) or ((iX < tGame.iMinX+6 or iX > tGame.iMaxX-6) and (iY >= tGame.CenterY-1 and iY <= tGame.CenterY+1)) then
                CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)
            elseif math.random(1,5) == 3 then
                CMap.CreateCoin(iX, iY)
            end
        end
    end      

    local iObjectId = 0
    if math.random(1,2) == 2 then
        iObjectId = CMap.CreateMovingRect(tGame.iMinX, tGame.iMinY, 1, (tGame.iMaxY-tGame.iMinY+1), 1, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
    else
        iObjectId = CMap.CreateMovingRect(tGame.iMinX, tGame.iMinY, (tGame.iMaxX-tGame.iMinX+1), 1, 0, 1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)  
    end  

    CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
end
----//

---- GENTYPE 6 - CENTERSAFE
CMap.GenerateMapWithType[CMap.GEN_TYPE_CENTERSAFE] = function()
    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            if (iX > tGame.CenterX-5 and iX <= tGame.CenterX+5) and (iY >= tGame.CenterY-1 and iY <= tGame.CenterY+1) then
                CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, iY, CBlock.BLOCK_TYPE_SAFEGROUND)
            elseif math.random(1,5) == 3 then
                CMap.CreateCoin(iX, iY)
            end
        end
    end    
    if math.random(1,2) == 1 then 
        for iX = 1, tGame.CenterY do
            local iObjectId = CMap.CreateMovingRect(tGame.CenterX+iX, tGame.CenterY-iX, 1, 1, 1, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA) 
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
            iObjectId = CMap.CreateMovingRect(tGame.CenterX+iX, tGame.CenterY+iX, 1, 1, 1, 0, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA) 
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
        end  
    else
        for iY = 1, tGame.CenterX do
            local iObjectId = CMap.CreateMovingRect(tGame.CenterX-iY, iY, 1, 1, 0, 1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA) 
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
            iObjectId = CMap.CreateMovingRect(tGame.CenterX+iY, iY, 1, 1, 0, 1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA) 
            CBlock.tObjects[CBlock.LAYER_MOVING_LAVA][iObjectId].bCollision = false
        end  
    end
end
----//

---- GENTYPE 7 - ARROWS
CMap.GenerateMapWithType[CMap.GEN_TYPE_ARROWS] = function()
    for iX = 0, tGame.CenterY do
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, tGame.CenterY-iX, CBlock.BLOCK_TYPE_SAFEGROUND)
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+1, tGame.CenterY-iX, CBlock.BLOCK_TYPE_SAFEGROUND)
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+2, tGame.CenterY-iX, CBlock.BLOCK_TYPE_SAFEGROUND)
        CBlock.NewBlock(CBlock.LAYER_GROUND, iX+3, tGame.CenterY-iX, CBlock.BLOCK_TYPE_LAVA)

        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, tGame.CenterY+iX, CBlock.BLOCK_TYPE_SAFEGROUND)
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+1, tGame.CenterY+iX, CBlock.BLOCK_TYPE_SAFEGROUND)
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+2, tGame.CenterY+iX, CBlock.BLOCK_TYPE_SAFEGROUND)
    end     

    for iX = tGame.iMaxX+1, tGame.CenterY, -1 do
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+1, tGame.CenterY-(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)     
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, tGame.CenterY-(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)     
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX-1, tGame.CenterY-(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)     

        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX+1, tGame.CenterY+(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)     
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX, tGame.CenterY+(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)     
        CBlock.NewBlock(CBlock.LAYER_SAFEGROUND, iX-1, tGame.CenterY+(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_SAFEGROUND)   
        CBlock.NewBlock(CBlock.LAYER_GROUND, iX-2, tGame.CenterY+(tGame.iMaxX-iX), CBlock.BLOCK_TYPE_LAVA)  
    end       

    for iX = tGame.iMinX, tGame.iMaxX do
        for iY = tGame.iMinY, tGame.iMaxY do
            if CBlock.IsEmpty(CBlock.LAYER_GROUND, iX, iY) then
                CBlock.NewBlock(CBlock.LAYER_GROUND, iX, iY, CBlock.BLOCK_TYPE_GROUND)
            
                if math.random(1,5) == 3 then
                    CMap.CreateCoin(iX, iY)
                end
            end
        end
    end    

    CMap.CreateMovingRect(tGame.CenterY+2, tGame.iMaxY, (tGame.iMaxX-tGame.CenterY*2)-2, 1 , 0, -1, CBlock.LAYER_MOVING_LAVA, CBlock.BLOCK_TYPE_LAVA)
end
----//

---//
---GENSHAPES

----RECT SHAPE
CMap.BuildRect = function(iStartX, iStartY, iSizeX, iSizeY, iLayer, iBlockType)
    for iX = iStartX, iStartX + iSizeX-1 do
        for iY = iStartY, iStartY + iSizeY-1 do
            CBlock.NewBlock(iLayer, iX, iY, iBlockType)
        end
    end
end
----//

----RANDOM PATTERN SHAPE
CMap.CreatePatternShape = function(iSizeX, iSizeY, iShapeRule)
    local tShape = {}

    if not iShapeRule or iShapeRule == 0 then
        iShapeRule = math.random(1,12)
    end

    local function checkShapeRule(iX, iY)
        if iShapeRule == 1 then -- horizontal stripes
            if iY % 2 == 0 and iX > 1 and iX < iSizeX and iY > 1 and iY < iSizeY then return 1; end
            return 0 
        elseif iShapeRule == 2 then -- vertical stripes
            if iX % 2 == 0 and iX > 1 and iX < iSizeX and iY > 1 and iY < iSizeY then return 1; end
            return 0 
        elseif iShapeRule == 3 then -- dots
            if iX % 2 == 0 and iY % 2 ~= 0 then return 1; end
            return 0
        elseif iShapeRule == 4 then -- square with horizontal line
            if (iX == 1 or iY == 1 or iX == iSizeX or iY == iSizeY) or iY == math.ceil(iSizeY/2) then return 1; end
            return 0
        elseif iShapeRule == 5 then -- square with dots
            if (iX == 1 or iY == 1 or iX == iSizeX or iY == iSizeY) or (iX > 1 and iX < iSizeX and iY > 1 and iY < iSizeY and iX % 2 ~= 0 and iY % 2 == 0) then return 1; end
            return 0
        elseif iShapeRule == 6 then -- square with vertical line
            if (iX == 1 or iY == 1 or iX == iSizeX or iY == iSizeY) or iX == math.floor(iSizeX/2) then return 1; end
            return 0
        elseif iShapeRule == 7 then -- chess
            if (iX > 2 and iX < iSizeX - 1 and iY > 2 and iY < iSizeY - 1) and ((iX % 2 == 0 and iY % 2 ~= 0) or (iX % 2 ~= 0 and iY % 2 == 0)) then return 1; end
            return 0
        elseif iShapeRule == 8 then -- V shape
            if (iX <= iSizeY and iX == iY) or (iX > iSizeY - 2 and (iSizeY - (iX - iSizeY) - 2) == iY) then return 1; end
            return 0
        elseif iShapeRule == 9 then -- big squares
            if (iX % 6 == 0 or (iX+1) % 6 == 0 or (iX-1) % 6 == 0) and (iY % 6 == 0 or (iY+1) % 6 == 0 or (iY-1) % 6 == 0) then return 1; end
            return 0           
        elseif iShapeRule == 10 then -- small squares
            if ((iX+2) % 4 == 0 or (iX+3) % 4 == 0) and ((iY+2) % 4 == 0 or (iY+3) % 4 == 0) then return 1; end
            return 0     
        elseif iShapeRule == 11 then -- random X lines
            if iY % 2 == 0 and math.random(1,3) == 2 then return 1; end
            return 0
        elseif iShapeRule == 12 then -- random Y lines
            if iX % 2 == 0 and math.random(1,3) == 2 then return 1; end
            return 0            
        end
    end

    for iX = 1, iSizeX do
        tShape[iX] = {}
        for iY = 1, iSizeY do
           tShape[iX][iY] = checkShapeRule(iX, iY)
        end
    end

    return tShape
end
----//

----MOVING RECT OBJECT
CMap.CreateMovingRect = function(iX, iY, iSizeX, iSizeY, iVelX, iVelY, iLayer, iBlockType)
    local tShape = {}
    for iX = 1, iSizeX do
        tShape[iX] = {}
        for iY = 1, iSizeY do
           tShape[iX][iY] = 1 
        end
    end

    local iObjectId = CBlock.NewObject(iLayer, iX, iY, iBlockType, tShape)
    CBlock.tObjects[iLayer][iObjectId].iVelX = iVelX
    CBlock.tObjects[iLayer][iObjectId].iVelY = iVelY

    return iObjectId
end
----//

---//
--//

--BLOCK
CBlock = {}

CBlock.tBlocks = {}
CBlock.tBlockStructure = {
    iBlockType = 0,
    bCollected = false,
    iBright = 0,
}

CBlock.tObjects = {}
CBlock.tObjectStructure = {
    iX = 0,
    iY = 0,
    iBlockType = 0,
    iBright = 0,
    tShape = {},
    iPathId = 0,
    iPathPoint = 1,
    iVelX = 0,
    iVelY = 0,
    bCollision = true,
    bCooldown = false,
    iPathWaitLeft = 0,
}

CBlock.tPaths = {}

CBlock.tBlockMovement = {}

CBlock.LAYER_GROUND = 1
CBlock.LAYER_COINS = 2
CBlock.LAYER_MOVING_LAVA = 3
CBlock.LAYER_SAFEGROUND = 4
CBlock.LAYER_MOVING_SAFEGROUND = 5

CBlock.MAX_LAYER = 5

CBlock.BLOCK_TYPE_GROUND = 1
CBlock.BLOCK_TYPE_LAVA = 2
CBlock.BLOCK_TYPE_COIN = 3
CBlock.BLOCK_TYPE_SAFEGROUND = 4

CBlock.tBLOCK_TYPE_TO_COLOR = {}
CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_GROUND]                   = CColors.NONE
CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_LAVA]                     = CColors.RED
CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_COIN]                     = CColors.BLUE
CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_SAFEGROUND]               = CColors.GREEN
CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.LAYER_MOVING_SAFEGROUND]             = CColors.CYAN

CBlock.tBlocksCountPerType = {}

CBlock.NewBlock = function(iLayer, iX, iY, iBlockType)
    if CBlock.tBlocks[iLayer] == nil then CBlock.tBlocks[iLayer] = {} end
    if CBlock.tBlocks[iLayer][iX] == nil then CBlock.tBlocks[iLayer][iX] = {} end

    if iBlockType == CBlock.BLOCK_TYPE_COIN and not CBlock.IsEmptyOnAllLayers(iX, iY) then return false; end

    CBlock.tBlocks[iLayer][iX][iY] = CHelp.ShallowCopy(CBlock.tBlockStructure)
    CBlock.tBlocks[iLayer][iX][iY].iBlockType = iBlockType
    CBlock.tBlocks[iLayer][iX][iY].iBright = tConfig.Bright

    CBlock.tBlocksCountPerType[iBlockType] = (CBlock.tBlocksCountPerType[iBlockType] or 0) + 1

    return true;
end

CBlock.NewObject = function(iLayer, iX, iY, iBlockType, tShape)
    if CBlock.tObjects[iLayer] == nil then CBlock.tObjects[iLayer] = {} end

    local iObjectId = #CBlock.tObjects[iLayer]+1
    CBlock.tObjects[iLayer][iObjectId] = CHelp.ShallowCopy(CBlock.tObjectStructure)
    CBlock.tObjects[iLayer][iObjectId].iBlockType = iBlockType
    CBlock.tObjects[iLayer][iObjectId].iX = iX
    CBlock.tObjects[iLayer][iObjectId].iY = iY
    CBlock.tObjects[iLayer][iObjectId].iBright = tConfig.Bright
    CBlock.tObjects[iLayer][iObjectId].tShape = tShape

    return iObjectId
end

CBlock.NewBlockFormationFromShape = function(iLayer, iStartX, iStartY, iBlockType, tShapeIn)
    local tShape = tShapeIn

    local iShapeX = 0
    local iShapeY = 0
    local iShapeSizeX = #tShape
    local iShapeSizeY = #tShape[1]

    for iX = iStartX, iStartX+iShapeSizeX-1 do
        iShapeX = iShapeX + 1
        for iY = iStartY, iStartY+iShapeSizeY-1 do
            iShapeY = iShapeY + 1
            if tShape[iShapeX][iShapeY] == 1 and CBlock.IsValidPosition(iX, iY) then
                CBlock.NewBlock(iLayer, iX, iY, iBlockType)
            end
        end
        iShapeY = 0
    end
end

CBlock.NewPath = function(tPoints)
    local iPathId = #CBlock.tPaths+1
    CBlock.tPaths[iPathId] = {}

    for iPathPoint = 1, #tPoints do
        CBlock.tPaths[iPathId][iPathPoint] = {}
        CBlock.tPaths[iPathId][iPathPoint].iX = tPoints[iPathPoint].iX
        CBlock.tPaths[iPathId][iPathPoint].iY = tPoints[iPathPoint].iY
        CBlock.tPaths[iPathId][iPathPoint].iWaitTime = tPoints[iPathPoint].iWaitTime or 0
    end

    return iPathId
end

CBlock.RegisterBlockClick = function(iX, iY)
    if iGameState ~= GAMESTATE_GAME or bGamePaused or not CGameMode.bRoundStarted then return; end

    for iLayer = CBlock.MAX_LAYER, 1, -1 do 
        if CBlock.tBlocks[iLayer] and CBlock.tBlocks[iLayer][iX] and CBlock.tBlocks[iLayer][iX][iY] and not CBlock.tBlocks[iLayer][iX][iY].bCollected then
            if CBlock.tBlocks[iLayer][iX][iY].iBlockType == CBlock.BLOCK_TYPE_LAVA and not tFloor[iX][iY].bProtectedFromLava and tFloor[iX][iY].iColor == CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_LAVA] then
                CBlock.tBlocks[iLayer][iX][iY].bCollected = true
                CGameMode.PlayerCollectLava(iX, iY)

                break;
            elseif CBlock.tBlocks[iLayer][iX][iY].iBlockType == CBlock.BLOCK_TYPE_COIN then
                CBlock.tBlocks[iLayer][iX][iY].bCollected = true
                CGameMode.PlayerCollectCoin()

                --[[tFloor[iX][iY].bProtectedFromLava = true
                AL.NewTimer(CPaint.ANIMATION_DELAY*10, function()
                    tFloor[iX][iY].bProtectedFromLava = false  
                end)]]
                break;
            elseif CBlock.tBlocks[iLayer][iX][iY].iBlockType == CBlock.BLOCK_TYPE_SAFEGROUND then
                break;
            end
        end
    end
end

CBlock.LavaObjectClick = function(iX, iY)
    if iGameState ~= GAMESTATE_GAME or bGamePaused or not CGameMode.bRoundStarted or not CBlock.IsEmpty(CBlock.LAYER_SAFEGROUND, iX, iY) or tFloor[iX][iY].bProtectedFromLava --[[or (not CBlock.IsEmpty(CBlock.LAYER_COINS, iX, iY) and not CBlock.tBlocks[CBlock.LAYER_COINS][iX][iY].bCollected)]] then return; end

    CGameMode.PlayerCollectLava(iX, iY)
end

CBlock.CalculateMovableObjects = function()
    for iLayer = 1, CBlock.MAX_LAYER do
        if CBlock.tObjects[iLayer] then
            for iObjectId = 1, #CBlock.tObjects[iLayer] do
                if CBlock.tObjects[iLayer][iObjectId] then
                    if CBlock.tObjects[iLayer][iObjectId].iPathId > 0 then
                        if CBlock.tObjects[iLayer][iObjectId].iPathWaitLeft > 0 then
                            CBlock.tObjects[iLayer][iObjectId].iPathWaitLeft = CBlock.tObjects[iLayer][iObjectId].iPathWaitLeft - 1
                        else
                            local tPath = CBlock.tPaths[CBlock.tObjects[iLayer][iObjectId].iPathId]
                            local tPathCurrentPoint = tPath[CBlock.tObjects[iLayer][iObjectId].iPathPoint]

                            local iXPlus = 0
                            if CBlock.tObjects[iLayer][iObjectId].iX < tPathCurrentPoint.iX then
                                iXPlus = 1
                            elseif CBlock.tObjects[iLayer][iObjectId].iX > tPathCurrentPoint.iX then
                                iXPlus = -1
                            end 
                            local iYPlus = 0
                            if CBlock.tObjects[iLayer][iObjectId].iY < tPathCurrentPoint.iY then
                                iYPlus = 1
                            elseif CBlock.tObjects[iLayer][iObjectId].iY > tPathCurrentPoint.iY then
                                iYPlus = -1
                            end 

                            if iXPlus == 0 and iYPlus == 0 then
                                if tPath[CBlock.tObjects[iLayer][iObjectId].iPathPoint].iWaitTime then CBlock.tObjects[iLayer][iObjectId].iPathWaitLeft = tPath[CBlock.tObjects[iLayer][iObjectId].iPathPoint].iWaitTime; end
                                CBlock.tObjects[iLayer][iObjectId].iPathPoint = CBlock.tObjects[iLayer][iObjectId].iPathPoint + 1
                                if not tPath[CBlock.tObjects[iLayer][iObjectId].iPathPoint] then CBlock.tObjects[iLayer][iObjectId].iPathPoint = 1 end
                            else
                                CBlock.tObjects[iLayer][iObjectId].iX = CBlock.tObjects[iLayer][iObjectId].iX + iXPlus
                                CBlock.tObjects[iLayer][iObjectId].iY = CBlock.tObjects[iLayer][iObjectId].iY + iYPlus
                            end
                        end
                    elseif (CBlock.tObjects[iLayer][iObjectId].iVelX ~= 0 or CBlock.tObjects[iLayer][iObjectId].iVelY ~= 0) then
                        CBlock.tObjects[iLayer][iObjectId].iX = CBlock.tObjects[iLayer][iObjectId].iX + CBlock.tObjects[iLayer][iObjectId].iVelX
                        CBlock.tObjects[iLayer][iObjectId].iY = CBlock.tObjects[iLayer][iObjectId].iY + CBlock.tObjects[iLayer][iObjectId].iVelY

                        local iNextX = CBlock.tObjects[iLayer][iObjectId].iX + CBlock.tObjects[iLayer][iObjectId].iVelX
                        local iNextY = CBlock.tObjects[iLayer][iObjectId].iY + CBlock.tObjects[iLayer][iObjectId].iVelY

                        if CBlock.tObjects[iLayer][iObjectId].bCollision and ((not CBlock.IsEmpty(CBlock.LAYER_SAFEGROUND, iNextX, iNextY) or not CBlock.IsEmpty(CBlock.LAYER_SAFEGROUND, iNextX + #CBlock.tObjects[iLayer][iObjectId].tShape-1, iNextY)
                        or not CBlock.IsEmpty(CBlock.LAYER_SAFEGROUND, iNextX, iNextY + #CBlock.tObjects[iLayer][iObjectId].tShape[1]-1))
                        or CBlock.CheckObjectCollision(iLayer, iNextX, iNextY, iObjectId)) then
                            CBlock.tObjects[iLayer][iObjectId].iVelX = -CBlock.tObjects[iLayer][iObjectId].iVelX
                            CBlock.tObjects[iLayer][iObjectId].iVelY = -CBlock.tObjects[iLayer][iObjectId].iVelY
                        else
                            if iNextX < 1 or iNextX + #CBlock.tObjects[iLayer][iObjectId].tShape-1 > tGame.Cols or (CBlock.tBlockMovement[iNextX] and CBlock.tBlockMovement[iNextX][iNextY]) then
                                CBlock.tObjects[iLayer][iObjectId].iVelX = -CBlock.tObjects[iLayer][iObjectId].iVelX
                            end
                            if iNextY < 1 or iNextY + #CBlock.tObjects[iLayer][iObjectId].tShape[1]-1 > tGame.Rows or (CBlock.tBlockMovement[iNextX] and CBlock.tBlockMovement[iNextX][iNextY]) then
                                CBlock.tObjects[iLayer][iObjectId].iVelY = -CBlock.tObjects[iLayer][iObjectId].iVelY
                            end   
                        end
                    end
                end
            end
        end
    end
end

CBlock.IsValidPosition = function(iX, iY)
    return iX >= 1 and iX <= tGame.Cols and iY >= 1 and iY <= tGame.Rows
end

CBlock.IsEmpty = function(iLayer, iX, iY)
    return not CBlock.tBlocks[iLayer] or not CBlock.tBlocks[iLayer][iX] or not CBlock.tBlocks[iLayer][iX][iY]
end

CBlock.IsEmptyOnAllLayers = function(iX, iY)
    for iLayer = 1, CBlock.MAX_LAYER do
        if CBlock.tBlocks[iLayer] and CBlock.tBlocks[iLayer][iX] and CBlock.tBlocks[iLayer][iX][iY] then
            if (not CBlock.IsEmpty(iLayer, iX, iY)) and (iLayer ~= CBlock.LAYER_GROUND or CBlock.tBlocks[iLayer][iX][iY].iBlockType ~= CBlock.BLOCK_TYPE_GROUND) then return false; end
        end
    end

    return true
end

CBlock.CheckObjectCollision = function(iLayer, iX, iY, iSelfObjectId)
    local iSizeX = #CBlock.tObjects[iLayer][iSelfObjectId].tShape
    local iSizeY = #CBlock.tObjects[iLayer][iSelfObjectId].tShape[1]

    for iObjectId = 1, #CBlock.tObjects[iLayer] do
        if iObjectId ~= iSelfObjectId then
            if CShapes.Intersects(iX, iY, iSizeX, iSizeY, CBlock.tObjects[iLayer][iObjectId].iX, CBlock.tObjects[iLayer][iObjectId].iY, #CBlock.tObjects[iLayer][iObjectId].tShape, #CBlock.tObjects[iLayer][iObjectId].tShape[1]) then
                return true
            end
        end
    end

    return false
end

CBlock.Clear = function()
    CGameMode.iMapCoinCount = 0
    CGameMode.iMapCoinReq = 0
    CGameMode.iMapCoinCollected = 0

    CBlock.tBlocks = {}   
    CBlock.tObjects = {} 
    CBlock.tPaths = {}
    CBlock.tBlocksCountPerType = {}
    CBlock.tBlockMovement = {}
end
--//

--PAINT
CPaint = {}
CPaint.ANIMATION_DELAY = 100

CPaint.GameField = function()
    if CGameMode.bRoundStarted then
        CPaint.BlocksAndObjects()
    else
        CPaint.SafeZonePreview()
        CPaint.CountDown()   
    end
end

CPaint.GameSetup = function()
    if CGameMode.bCanStart then
        for iCoinId = 1, #CGameMode.tGameSetupCoins do
            if not CGameMode.tGameSetupCoins[iCoinId].bCollected then
                tFloor[CGameMode.tGameSetupCoins[iCoinId].iX][CGameMode.tGameSetupCoins[iCoinId].iY].iColor = CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_COIN]
                tFloor[CGameMode.tGameSetupCoins[iCoinId].iX][CGameMode.tGameSetupCoins[iCoinId].iY].iBright = tConfig.Bright
                tFloor[CGameMode.tGameSetupCoins[iCoinId].iX][CGameMode.tGameSetupCoins[iCoinId].iY].iCoinId = iCoinId
            end
        end
    end
end

CPaint.CountDown = function()
    if CGameMode.iCountdown >= 0 and tGameStats.StageLeftDuration >= 0 then
        local tShape = CShapes.tNumbers[tGameStats.StageLeftDuration]
        if tShape then
            local iShapeX = 0
            local iShapeY = 0
            local iShapeSizeX = #tShape[1]
            local iShapeSizeY = #tShape

            local iXStart = math.ceil(tGame.Cols/2 - iShapeSizeX/2)
            local iYStart = math.floor(tGame.Rows/3)

            for iY = iYStart, iYStart+iShapeSizeY-1 do
                iShapeY = iShapeY + 1
                for iX = iXStart, iXStart+iShapeSizeX-1 do
                    iShapeX = iShapeX+1
                    if tShape[iShapeY][iShapeX] > 0 then
                        tFloor[iX][iY].iColor = CColors.WHITE
                        tFloor[iX][iY].iBright = tConfig.Bright + 2
                    end
                end
                iShapeX = 0
            end
        end
    end
end

CPaint.BlocksAndObjects = function()
    for iLayer = 1, CBlock.MAX_LAYER do
        if CBlock.tBlocks[iLayer] then
            CPaint.BlocksLayer(iLayer)
        end
        if CBlock.tObjects[iLayer] then
            CPaint.ObjectsLayer(iLayer)
        end
    end
end

CPaint.BlocksLayer = function(iLayer, iBrightOffset)
    for iX = 1, tGame.Cols do
        if CBlock.tBlocks[iLayer][iX] then
            for iY = 1, tGame.Rows do
                if not tFloor[iX][iY].bAnimated and CBlock.IsValidPosition(iX, iY) and CBlock.tBlocks[iLayer][iX][iY] and not CBlock.tBlocks[iLayer][iX][iY].bCollected then
                    local iBright = CBlock.tBlocks[iLayer][iX][iY].iBright + (iBrightOffset or 0)
                    --if tFloor[iX][iY].iColor == CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.BLOCK_TYPE_LAVA] and CBlock.tBlocks[CBlock.LAYER_GROUND][iX][iY].iBlockType ~= CBlock.BLOCK_TYPE_LAVA then iBright = iBright - 2 end

                    tFloor[iX][iY].iColor = CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.tBlocks[iLayer][iX][iY].iBlockType]
                    tFloor[iX][iY].iBright = iBright
                end
            end
        end
    end
end

CPaint.ObjectsLayer = function(iLayer, iBrightOffset)
    for iObjectId = 1, #CBlock.tObjects[iLayer] do
        local tObject = CBlock.tObjects[iLayer][iObjectId]
        local tShape = tObject.tShape
        local iShapeSizeX = #tShape
        local iShapeSizeY = #tShape[1]
        local iShapeX = 0
        local iShapeY = 0

        for iX = tObject.iX, tObject.iX+iShapeSizeX-1 do
            iShapeX = iShapeX + 1
            for iY = tObject.iY, tObject.iY+iShapeSizeY-1 do
                iShapeY = iShapeY + 1
                if CBlock.IsValidPosition(iX, iY) and not tFloor[iX][iY].bAnimated and tShape[iShapeX][iShapeY] == 1 then
                    tFloor[iX][iY].iColor = CBlock.tBLOCK_TYPE_TO_COLOR[tObject.iBlockType]
                    tFloor[iX][iY].iBright = tObject.iBright + (iBrightOffset or 0)

                    if tFloor[iX][iY].bClick and not tFloor[iX][iY].bDefect then
                        if tObject.iBlockType == CBlock.BLOCK_TYPE_LAVA then
                            if tFloor[iX][iY].iWeight > 10 then
                                AL.NewTimer(300, function()
                                    if tFloor[iX][iY].bClick and tFloor[iX][iY].iWeight > 10 then
                                        CBlock.LavaObjectClick(iX, iY)
                                    end
                                end)
                            end
                        end
                    end

                    if tObject.iBlockType == CBlock.BLOCK_TYPE_SAFEGROUND then
                        tFloor[iX][iY].iColor = CBlock.tBLOCK_TYPE_TO_COLOR[CBlock.LAYER_MOVING_SAFEGROUND]
                        tFloor[iX][iY].bProtectedFromLava = true
                        AL.NewTimer(CPaint.ANIMATION_DELAY*4, function()
                            tFloor[iX][iY].bProtectedFromLava = false  
                        end)
                    else
                        tFloor[iX][iY].bProtectedFromLava = false
                    end
                end
            end
            iShapeY = 0
        end
    end
end

CPaint.SafeZonePreview = function()
    local function checkandpaintblockslayer(iLayer)
        if CBlock.tBlocks[iLayer] then
            CPaint.BlocksLayer(iLayer, -2)
        end
    end
    local function checkandpaintobjectslayer(iLayer)
        if CBlock.tObjects[iLayer] then
            CPaint.ObjectsLayer(iLayer, -2)
        end
    end

    checkandpaintblockslayer(CBlock.LAYER_SAFEGROUND)
    checkandpaintblockslayer(CBlock.LAYER_MOVING_SAFEGROUND)
    checkandpaintobjectslayer(CBlock.LAYER_SAFEGROUND)
    checkandpaintobjectslayer(CBlock.LAYER_MOVING_SAFEGROUND)
end

CPaint.AnimatePixelFlicker = function(iX, iY, iFlickerCount, iColor)
    if tFloor[iX][iY].bAnimated then return; end
    tFloor[iX][iY].bAnimated = true

    local iCount = 0
    AL.NewTimer(30, function()
        if not tFloor[iX][iY].bAnimated then return; end

        if tFloor[iX][iY].iColor == iColor then
            tFloor[iX][iY].iBright = tConfig.Bright + 1
            tFloor[iX][iY].iColor = CColors.MAGENTA
            iCount = iCount + 1
        else
            tFloor[iX][iY].iBright = tConfig.Bright
            tFloor[iX][iY].iColor = iColor
            iCount = iCount + 1
        end
        
        if iCount <= iFlickerCount then
            return 200
        end

        tFloor[iX][iY].iBright = tConfig.Bright
        tFloor[iX][iY].iColor = iColor
        tFloor[iX][iY].bAnimated = false

        return nil
    end)
end
--//

--SHAPES
CShapes = {}

CShapes.RotateShape = function(tShapeIn)
    local tShapeOut = tShapeIn

    return tShapeOut
end

function CShapes.Intersects(iX1, iY1, iSizeX1, iSizeY1, iX2, iY2, iSizeX2, iSizeY2)
    if iSizeX1 == 0 or iSizeX2 == 0 then return false; end
    if iSizeY1 == 0 or iSizeY2 == 0 then return false; end

    if iX1 > iX2+iSizeX2-1 or iX2 > iX1+iSizeX1-1 then return false; end

    if iY1+iSizeY1-1 < iY2 or iY2+iSizeY2-1 < iY1 then return false; end

    return true
end

CShapes.GetRandomShapeFromTable = function(tTable, iFrom, iTo)
    local iLimit = iLimitIn
    if iFrom == nil or iFrom > #tTable or iFrom < 1 then iFrom = 1 end
    if iTo == nil or iTo > #tTable or iTo < 1 then iTo = #tTable end
    if iFrom > iTo then iFrom = 1 end
    return tTable[math.random(iFrom, iTo)]
end

CShapes.GetRandomSmallShape = function(iFrom, iTo)
    return CShapes.GetRandomShapeFromTable(CShapes.tSmall, iFrom, iTo)
end

CShapes.GetRandomMediumShape = function(iFrom, iTo)
    return CShapes.GetRandomShapeFromTable(CShapes.tMedium, iFrom, iTo)
end

CShapes.GetRandomPattern = function(iLimit)
    return CShapes.GetRandomShapeFromTable(CShapes.tPatterns, iFrom, iTo)
end

CShapes.tSmall = {}
CShapes.tSmall[1] =
{
    {1,1},
    {1,1},
}
CShapes.tSmall[2] =
{
    {1,1,1},
    {1,1,1},
    {1,1,1},
}
CShapes.tSmall[3] =
{
    {1,1},
    {1,1},
    {1,1},
}
CShapes.tSmall[4] =
{
    {1,1,1},
    {0,1,0},
    {1,1,1},
}
CShapes.tSmall[5] =
{
    {1,0,1},
    {1,1,1},
    {1,0,1},
}
CShapes.tSmall[6] =
{
    {0,1,0},
    {1,1,1},
    {0,1,0},
}
CShapes.tSmall[7] =
{
    {1,1,1},
}
CShapes.tSmall[8] =
{
    {1,1,1,1},
}
CShapes.tSmall[9] =
{
    {1,},
    {1,},
    {1,},
}
CShapes.tSmall[10] =
{
    {1,},
    {1,},
    {1,},
    {1,},
}

CShapes.tMedium = {}
CShapes.tMedium[1] = 
{
    {1,1,1,1,1},
    {1,1,1,1,1},
    {1,1,1,1,1},
    {1,1,1,1,1},
    {1,1,1,1,1},
}
CShapes.tMedium[2] = 
{
    {0,0,1,0,0},
    {0,1,1,1,0},
    {1,1,1,1,1},
    {0,1,1,1,0},
    {0,0,1,0,0},
}

CShapes.tMedium[3] =
{
    {0,0,1,0,0},
    {0,1,1,1,0},
    {1,1,1,1,1},
}
CShapes.tMedium[4] =
{
    {1,1,1,1,1},
    {0,1,1,1,0},
    {0,0,1,0,0},
}
CShapes.tMedium[5] =
{
    {0,0,1},
    {0,1,1},
    {1,1,1},
    {0,1,1},
    {0,0,1},
}
CShapes.tMedium[6] =
{
    {1,0,0},
    {1,1,0},
    {1,1,1},
    {1,1,0},
    {1,0,0},
}

CShapes.tPatterns = {}
CShapes.tPatterns[1] =
{
    {1,0,1},
    {0,1,0},
    {1,0,1},
}
CShapes.tPatterns[2] =
{
    {0,1,0},
    {1,0,1},
    {0,1,0},
}
CShapes.tPatterns[3] =
{
    {1,1},
    {1,1},
}
CShapes.tPatterns[4] =
{
    {0,1},
    {1,1},
}
CShapes.tPatterns[5] =
{
    {1,1},
    {1,0},
}
CShapes.tPatterns[6] =
{
    {0,1},
    {1,0},
}
CShapes.tPatterns[7] =
{
    {1,0,0,1},
    {0,1,1,0},
    {0,1,1,0},
    {1,0,0,1},
}

CShapes.tNumbers = {}
CShapes.tNumbers[0] = 
{
    {0,0,0,0,0},
    {0,0,0,0,0},
    {0,0,0,0,0},
    {0,0,0,0,0},
    {0,0,0,0,0},
    {0,0,0,0,0},
    {0,0,0,0,0},
    --[[{0,1,1,1,0,0,0,1,1,1,0},
    {1,0,0,0,1,0,1,0,0,0,1},
    {1,0,0,0,0,0,1,0,0,0,1},
    {1,0,0,1,1,0,1,0,0,0,1},
    {1,0,0,0,1,0,1,0,0,0,1},
    {1,0,0,0,1,0,1,0,0,0,1},
    {0,1,1,1,0,0,0,1,1,1,0},]]
}
CShapes.tNumbers[1] = 
{
    {0,0,1,0,0},
    {0,1,1,0,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
    {0,1,1,1,0},
}
CShapes.tNumbers[2] = 
{
    {0,1,1,1,0},
    {1,0,0,0,1},
    {0,0,0,0,1},
    {0,0,0,1,0},
    {0,0,1,0,0},
    {0,1,0,0,0},
    {1,1,1,1,1},
}
CShapes.tNumbers[3] = 
{
    {0,1,1,1,0},
    {1,0,0,0,1},
    {0,0,0,0,1},
    {0,1,1,1,0},
    {0,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
}
CShapes.tNumbers[4] = 
{
    {0,0,1,1,0},
    {0,1,0,1,0},
    {1,0,0,1,0},
    {1,1,1,1,1},
    {0,0,0,1,0},
    {0,0,0,1,0},
    {0,0,0,1,0},
}
CShapes.tNumbers[5] = 
{
    {1,1,1,1,1},
    {1,0,0,0,0},
    {1,1,1,1,0},
    {0,0,0,0,1},
    {0,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
}
CShapes.tNumbers[6] = 
{
    {0,1,1,1,0},
    {1,0,0,0,1},
    {1,0,0,0,0},
    {1,1,1,1,0},
    {1,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
}
CShapes.tNumbers[7] = 
{
    {1,1,1,1,1},
    {0,0,0,0,1},
    {0,0,0,1,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
    {0,0,1,0,0},
}
CShapes.tNumbers[8] = 
{
    {0,1,1,1,0},
    {1,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
    {1,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
}
CShapes.tNumbers[9] = 
{
    {0,1,1,1,0},
    {1,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,1},
    {0,0,0,0,1},
    {1,0,0,0,1},
    {0,1,1,1,0},
}
--//

--UTIL прочие утилиты
function CheckPositionClick(tStart, iSizeX, iSizeY)
    for iX = tStart.X, tStart.X + iSizeX - 1 do
        for iY = tStart.Y, tStart.Y + iSizeY - 1 do
            if tFloor[iX] and tFloor[iX][iY] then
                if tFloor[iX][iY].bClick then
                    return true
                end 
            end
        end
    end

    return false
end

function SetPositionColorBright(tStart, iSize, iColor, iBright)
    for i = 0, iSize * iSize - 1 do
        local iX = tStart.X + i % iSize
        local iY = tStart.Y + math.floor(i / iSize)

        if not (iX < 1 or iX > tGame.Cols or iY < 1 or iY > tGame.Rows) then     
            tFloor[iX][iY].iColor = iColor
            tFloor[iX][iY].iBright = iBright            
        end
    end
end

function SetRectColorBright(iX, iY, iSizeX, iSizeY, iColor, iBright)
    for i = iX, iX + iSizeX do
        for j = iY, iY + iSizeY do
            if not (i < 1 or i > tGame.Cols or j < 1 or j > tGame.Rows) and not tFloor[i][j].bAnimated then     
                tFloor[i][j].iColor = iColor
                tFloor[i][j].iBright = iBright            
            end            
        end
    end
end

function SetGlobalColorBright(iColor, iBright)
    for iX = 1, tGame.Cols do
        for iY = 1, tGame.Rows do
            if not tFloor[iX][iY].bAnimated then
                tFloor[iX][iY].iColor = iColor
                tFloor[iX][iY].iBright = iBright
                --tFloor[iX][iY].bProtectedFromLava = false
            end
        end
    end

    for i, tButton in pairs(tButtons) do
        tButtons[i].iColor = iColor
        tButtons[i].iBright = iBright
    end
end

function SetAllButtonColorBright(iColor, iBright, bCheckDefect)
    for i, tButton in pairs(tButtons) do
        if not bCheckDefect or not tButtons[i].bDefect then
            tButtons[i].iColor = iColor
            tButtons[i].iBright = iBright
        end
    end
end

function ReverseTable(t)
    for i = 1, #t/2, 1 do
        t[i], t[#t-i+1] = t[#t-i+1], t[i]
    end
    return t
end
--//

--//
function GetStats()
    return tGameStats
end

function PauseGame()
    bGamePaused = true
end

function ResumeGame()
    bGamePaused = false
	iPrevTickTime = CTime.unix()
end

function PixelClick(click)
    if tFloor[click.X] and tFloor[click.X][click.Y] then
        if bGamePaused then
            tFloor[click.X][click.Y].bClick = false
            return;
        end
        
        tFloor[click.X][click.Y].bClick = click.Click
        tFloor[click.X][click.Y].iWeight = click.Weight

        if click.Click and not tFloor[click.X][click.Y].bDefect and click.Weight > 10 then
            if iGameState == GAMESTATE_SETUP then
                if tFloor[click.X][click.Y].iCoinId > 0 and not CGameMode.tGameSetupCoins[tFloor[click.X][click.Y].iCoinId].bCollected then
                    CGameMode.PlayerCollectGameSetupCoin(tFloor[click.X][click.Y].iCoinId)
                end
            else
                CBlock.RegisterBlockClick(click.X, click.Y)
            end
        end

    end
end

function DefectPixel(defect)
    if tFloor[defect.X] and tFloor[defect.X][defect.Y] then
        tFloor[defect.X][defect.Y].bDefect = defect.Defect
        tFloor[defect.X][defect.Y].bProtectedFromLava = true

        if iGameState == GAMESTATE_SETUP then
            if tFloor[click.X][click.Y].iCoinId > 0 and not CGameMode.tGameSetupCoins[tFloor[click.X][click.Y].iCoinId].bCollected then
                CGameMode.PlayerCollectGameSetupCoin(tFloor[click.X][click.Y].iCoinId)
            end
        else
            CBlock.RegisterBlockClick(defect.X, defect.Y)
        end
    end
end

function ButtonClick(click)
    if tButtons[click.Button] == nil or bGamePaused or tButtons[click.Button].bDefect then return end
    tButtons[click.Button].bClick = click.Click

    if iGameState == GAMESTATE_SETUP then
        --CGameMode.EndGameSetup()
    end
end

function DefectButton(defect)
    if tButtons[defect.Button] == nil then return end
    tButtons[defect.Button].bDefect = defect.Defect

    if defect.Defect then
        tButtons[defect.Button].iColor = CColors.NONE
        tButtons[defect.Button].iBright = CColors.BRIGHT0
    end    
end