dofile('Data/Scripts/match3/M3Helpers.lua')
dofile('Data/Scripts/match3/field/M3Charge.lua')
dofile('Data/Scripts/match3/field/M3Cell.lua')
dofile('Data/Scripts/match3/field/M3Item.lua')
dofile('Data/Scripts/match3/field/M3AimItem.lua')
dofile('Data/Scripts/match3/field/M3AbilityItem.lua')

dofile('Data/Scripts/match3/field/ability/M3AbilityLogicBase.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicExplosive.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicPick.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicMatch.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicKnife.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicMirror.lua')
dofile('Data/Scripts/match3/field/ability/M3AbilityLogicTorch.lua')
-------------------------------------------------------------------------------

class 'M3Field' (Node)
-------------------------------------------------------------------------------

M3Field.SWAP_DISTANCE   = 10
M3Field.SWAPPING_TIME   = 0.3
M3Field.CHARGE_FLY_TIME = 2
M3Field.SHUFFLE_TIME    = 2.5
M3Field.MAX_AIMS_COUNT  = 3
M3Field.DEFAULT_MAX_ABILITIES_COUNT = 5

M3Field.CELL_SYMBOL_NONE     = '-'
M3Field.CELL_SYMBOL_SIMPLE   = '8'
M3Field.CELL_SYMBOL_PASSABLE = 'P'
M3Field.ITEM_SYMBOL_NONE     = '-'
M3Field.LOCKED_SYMBOL        = 'L'
M3Field.CHARGED_SYMBOL       = 'C'

M3Field.CELLS_Z       = 1
M3Field.CHARGES_Z     = 20
M3Field.ABILITY_Z     = 22
M3Field.HINT_Z        = 19
M3Field.COMBO_TEXT_Z  = 21
M3Field.EXPLODE_Z     = 18

M3Field.MOVEABLE_CELLS_MODE = true
-------------------------------------------------------------------------------

function M3Field:__init()   
    Node.__init(self, current_scene:getPlayField():getMainEntity(), 'M3Field', 42)
    self.ed = EventDispatcher(self)   
    
    self:getMainEntity():setHandlingChildEvents(true)

    self.torchAnimUniqueId   = 0
    self.explodeAnimUniqueId = 0
    
    self.timer  = Timer()
    self.anchor = nil               -- якорь поля, к чему аттачатся все ячейки
    
    self.cols  = 0                  -- количество столбцов в поле
    self.rows  = 0                  -- количество строк в поле
    self.cells = {}                 -- двумерный массив, хранящий ячейки
    self.outFieldItems = {}
    self.sounds = {}
    
    self.comboTexts       = {}
    self.comboTextsFreeId = 0
    
    self.swappingCells = {}         -- массив, хранящий пары менающихся ячеек (добавлен для реализации возврата итемов, если не возникло комбинаций при их перемещении)
    self.selectCell    = nil        -- выделеная ячейка
    self.startDragPos  = nil        -- позиция клика по ячейке при начале перетаскивания (необходима для определения направления перемещения и соответствующей ячейки)
    self.comboCount    = 0          -- количество комбинаций, убраных до полного устаканивания поля
    self.comboShownCount = 1
    
    
    self.charges   = {}
    self.itemsInfo = {}
    self.abilityMaxCounts    = {}
    self.abilityCountBalance = {}
    self.abilityAffectIDs    = {}
    
    self.lastCreateItemId = nil
    self.sameItemsIdCount = 0
    
    self.replaceArrowsPool  = {}
    self.aimArrowsPool      = {}
    self.arrowEffectors     = {}
    self.resetHintAction    = nil
    
    self.abilityMinInterval = 0
    self.abilityMaxInterval = 0
    self.abilityCurInterval = 0     -- количество итемов, которые необходимо создать до создания следующего создания бонусного итема
    
    self.aimMinInterval = 0
    self.aimMaxInterval = 0
    self.aimCurInterval = 0         -- количество итемов, которые необходимо создать до создания следующего создания целевого итема
    
    self.aimId          = nil
    self.aimsLeft       = 0         -- количество целевых итемов (должно соответствовать количеству объектов для нахождения в HiddenObject)
    self.aimsCount      = 0         -- количество целевых итемов, отображаемых в данный момент на поле
    
    self.lockedMaxCount = 0         -- максимальное количество заблокированых итемов, одновременно отображаемых на поле
    self.lockedCount    = 0         -- текущее количество заблокированых итемов, отображаемых на поле
    self.lockRate       = 0         -- пероятность появления заблокированого итема, про следующем создании 0..1
    
    self.explodeAnims = {}
    self.torchAnims   = {}
    
    self.abilityLogic = nil
    
    self.bActive   = false          -- активность поля
    self.bShuffle  = false          -- перемешивание поля
    self.bDragging = false          -- перетаскивается ли в данный момент какой-либо итем
    self.bChanged  = false
    
    self.bNeedCrashSound  = false
    self.bNeedIceSound    = false
    
    self:createAnchor()
    self:initSounds()
    local object = current_scene:getPlayField():getEntityNamed('fade_in_zoom_zone')
    object.onClickBegin:add( bind(self.onObjectMouseDown, self) )
end
-------------------------------------------------------------------------------

function M3Field:destroy()
    self.timer:clear()
    
    --ADD 5.12.2012
    self:clearCharges()
    
    local row, col
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            
            if cell then
                cell:destroy()
            end
        end
    end
    
    self.cells = nil
    
    EventDispatcher.destroy(self)
end
-------------------------------------------------------------------------------

--ADD 5.12.2012
function M3Field:clearCharges()
    do
        --TODO: uncoment to clear charges
        --return
    end
    
    for chargeId, info in pairs(self.charges) do
        info.charge:destroy()
        info.action:kill()
    end
    
    self.charges = {}
end
-------------------------------------------------------------------------------

function M3Field:registerEvents(dispatcher)   
    self:getMainEntity().onUpdate:add( bind(self.update, self) )
end
-------------------------------------------------------------------------------

function M3Field:unregisterEvents()
    self:getMainEntity().onUpdate:removeAll()
end
-------------------------------------------------------------------------------

function M3Field:getAnchor()
    return self.anchor
end
-------------------------------------------------------------------------------

function M3Field:getCell(col, row)
    return self.cells[row] and self.cells[row][col] or nil
end
-------------------------------------------------------------------------------

function M3Field:getCellByTile(objectName)
    local row, col
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            local obj_name = 'tile' .. string.sub(objectName, 11)
            if cell and cell:isTile(obj_name) then
                return cell
            end
        end
    end
    
    return nil
end
-------------------------------------------------------------------------------

function M3Field:getItem(col, row)
    local cell = self:getCell(col, row)
    return (cell ~= nil) and cell:getItem() or nil
end
-------------------------------------------------------------------------------

function M3Field:getWidth()
    return self.cols * M3Cell.CELL_SIZE
end
-------------------------------------------------------------------------------

function M3Field:getHeight()
    return self.rows * M3Cell.CELL_SIZE
end
-------------------------------------------------------------------------------

function M3Field:getRandomItemInfo()
    local totalItemRate = 0
    local lastRate = 0
    local rate
    
    for i, info in ipairs(self.itemsInfo) do
        totalItemRate = totalItemRate + info.rate
    end

	rate = math.random() * totalItemRate
    
    for i, info in ipairs(self.itemsInfo) do
        if info.rate > 0 and rate <= (lastRate + info.rate) then
            return info
        else
            lastRate = lastRate + info.rate
        end
    end
    
	return self.itemsInfo[math.random(#self.itemsInfo)]
end
-------------------------------------------------------------------------------

function M3Field:getRandomCalmCell(bIgnoreMovingCheck)
    local col, row
    local cells = {}
    
    for row = self.rows-1, 0, -1 do
        for col = self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            
            if cell and
               cell.swapId == nil and 
               cell:getComboId() == 0 and
               self:getCellsCharge(cell) == nil
            then
                local item = cell:getItem()
                
                if  item and
                    item:isChargeable()   and
                    (bIgnoreMovingCheck or not item:isMoving()) and
                    not item:isAnimated() and
                    not item:isLocked()   and
                    not item:isCharged()  and
                    not item:isKilled()   and
                    not item:isFalling()
                then
                    table.insert(cells, cell)
                end
            end
        end
    end
    
    
    return (#cells > 0) and cells[math.random(#cells)] or nil
end
-------------------------------------------------------------------------------

function M3Field:getCellsCharge(cell)
    for chargeId, info in pairs(self.charges) do
        if info.targetCell.col == cell.col and info.targetCell.row == cell.row then
            return info
        end
    end
    
    return nil
end
-------------------------------------------------------------------------------

function M3Field:getItemsInfo()
    return self.itemsInfo
end
-------------------------------------------------------------------------------

function M3Field:getItemInfo(id)
    for i, info in ipairs(self.itemsInfo) do
        if id == info.id then
            return info
        end
    end
    
    return nil
end
-------------------------------------------------------------------------------

function M3Field:getAbilitiesInfo()
    local result = {}
    
    for i, info in ipairs(self.itemsInfo) do
        if info.ability then
            table.insert(result, info)
        end
    end
    
    return result
end
-------------------------------------------------------------------------------

function M3Field:getAbilityMaxCount(ability)
    local maxCount
    
    if self.abilityMaxCounts[ability] then
        maxCount = self.abilityMaxCounts[ability]
    else
        maxCount = M3Field.DEFAULT_MAX_ABILITIES_COUNT
    end
    
    return maxCount
end
-------------------------------------------------------------------------------

function M3Field:getAvailableAbilitiesInfo()
    local abilities = {}
    
    for i, info in ipairs(self.itemsInfo) do
        local ability = info.ability
        
        if ability and self.abilityCountBalance[ability] < self:getAbilityMaxCount(ability) then
            table.insert(abilities, info)
        end
    end
    
    return abilities
end
-------------------------------------------------------------------------------

function M3Field:getCellsString()
    local str = ''
    local col, row
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                if cell:isPassable() then
                    str = str..M3Field.CELL_SYMBOL_PASSABLE
                else
                    str = str..M3Field.CELL_SYMBOL_SIMPLE
                end
            else
                str = str..M3Field.CELL_SYMBOL_NONE
            end
        end
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Field:getItemsString()
    local str = ''
    local col, row
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local item = self:getItem(col, row)
            
            if item then
                str = str..item.id
            else
                str = str..M3Field.ITEM_SYMBOL_NONE
            end
        end
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Field:getLocksString()
    local str = ''
    local col, row
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local item = self:getItem(col, row)
            
            if item and item:isLocked() then
                str = str..M3Field.LOCKED_SYMBOL
            else
                str = str..M3Field.ITEM_SYMBOL_NONE
            end
        end
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Field:getChargesString()
    local str = ''
    local col, row
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local item = self:getItem(col, row)
            
            if item and item:isCharged() then
                str = str..M3Field.CHARGED_SYMBOL
            else
                str = str..M3Field.ITEM_SYMBOL_NONE
            end
        end
    end
    
    for chargeId, info in pairs(self.charges) do
        local index   = info.targetCell.row * self.cols + info.targetCell.col + 1
        local strHead = (index > 1)               and string.sub(str, 1, index-1) or ''
        local strTail = (index < string.len(str)) and string.sub(str, index+1)    or ''
        
        str = strHead..M3Field.CHARGED_SYMBOL..strTail
    end
    
    return str
end
-------------------------------------------------------------------------------

function M3Field:getSelectCell()
    return self.selectCell
end
-------------------------------------------------------------------------------

function M3Field:setItemLocked(item, bLocked, type, bFatality)
    if bLocked then
        item:lock()
        self.lockedCount = self.lockedCount + 1
    else
        item:unlock(type, bFatality)
        self.lockedCount = self.lockedCount - 1
    end
end
-------------------------------------------------------------------------------

function M3Field:setItemsInfo(itemsInfo)
    self.itemsInfo = table.copy(itemsInfo)
    
    for i, info in ipairs(self.itemsInfo) do
        assert(string.len(info.id) == 1)
        
        if not info.rate then
            info.rate = 0
        end
        
        if info.aim == 'true' then
            self.aimId = info.id
        end
        
        if info.ability then
            self.abilityCountBalance[info.ability] = 0
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:setAbilityCountBalance(abilityCountBalance)
    for ability, count in pairs(abilityCountBalance) do
        self.abilityCountBalance[ability] = count
    end
end
-------------------------------------------------------------------------------

function M3Field:setAbilityMaxCounts(abilityMaxCounts)
    if abilityMaxCounts then
        for ability, count in pairs(abilityMaxCounts) do
           self.abilityMaxCounts[ability] = count
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:setAbilityAffectIDs(abilityAffectIDs)
    self.abilityAffectIDs = table.copy(abilityAffectIDs)
end
-------------------------------------------------------------------------------

function M3Field:setChanged()
    self.bChanged = true
end
-------------------------------------------------------------------------------

function M3Field:setAbility(ability, spriteInfo, time)
    if self.abilityLogic then
        self.abilityLogic:stop(time)
        self.abilityLogic = nil
    end
    
    if ability then
        local affectIDs = self.abilityAffectIDs[ability]
        local className = 'M3AbilityLogic'..
                            string.upper(string.sub(ability, 1, 1))..
                            string.lower(string.sub(ability, 2))
        
        self.abilityLogic = Helpers.CallFunctionByName(className, ability, self)
        self.abilityLogic:setAffectIDs(affectIDs)
        self.abilityLogic:run(spriteInfo, time)
    end
end
-------------------------------------------------------------------------------

function M3Field:dropAbility(time, changeSlotActivityTime)
    if self.abilityLogic then
        local event = { eventName = 'AbilityDropped',
                        ability = self.abilityLogic.ability,
                        time = (time or 0),
                        changeSlotActivityTime = changeSlotActivityTime
                      }
        
        self:setAbility(nil, nil, time)
        self.ed:dispatchEvent(event)
    end
end
-------------------------------------------------------------------------------

function M3Field:DecAimsLeft()
    self.aimsLeft = math.max(0, self.aimsLeft - 1)
end
-------------------------------------------------------------------------------

function M3Field:createAnchor()
    local objectName = 'm3fieldAnchor'
    local anim = CreateSpriteEntity('')
    anim:attachTo(self:getMainEntity())
    anim:setAnimationSize(8, 8) 
	anim:setHotSpot(4, 4)
    anim:setName(objectName) 
    anim:setPosition(Vector2f(0, 0)) 
    anim:setOrder(1)
    anim:setColor(Colorf(1, 0, 0, 0.01))  	
	self.anchor = anim
end
-------------------------------------------------------------------------------

function M3Field:createCell(col, row, bPassable)
    assert(col>=0 and col<self.cols)
    assert(row>=0 and row<self.rows)
    
    local cell = M3Cell(col, row)
    cell:setPassable(bPassable)
    
    cell:getAnchor():attachTo(self.anchor)
    cell:getAnchor():setOrder(M3Field.CELLS_Z)
    cell:getAnchor():setOpacity(0)

    cell:getAnchor():setHandlingChildEvents(true)

    -- events
   
    cell:getAnchor().onClickBegin:add( bind(self.onObjectMouseDown, self) )
    cell:getAnchor().onClickEnd:add( bind(self.onObjectMouseUp, self) )
    cell:getAnchor().onFocusIn:add( bind(self.onObjectMouseOver, self) )
    cell:getAnchor().onFocusOut:add( bind(self.onObjectMouseOut, self) )
    
    self.cells[row][col] = cell
    return cell
end
-------------------------------------------------------------------------------

function M3Field:createItem(col, row, info)
    local item

    if info.ability then
        item = M3AbilityItem(info.id, info.name, self.timer, info.ability)
        self.abilityCountBalance[info.ability] = self.abilityCountBalance[info.ability] + 1
    else
        item = M3Item(info.id, info.name, self.timer)
    end
    
    if info.crashHeight then
        item:setCrashHeight(info.crashHeight)
    end
    
    self:getCell(col, row):setItem(item)
    return item
end
-------------------------------------------------------------------------------

function M3Field:createAimItem(col, row)
    local info = self:getItemInfo(self.aimId)
    local item = M3AimItem(info.id, info.name, self.timer)
    
    self:getCell(col, row):setItem(item)
    
    self.aimsCount = self.aimsCount + 1
    return item
end
-------------------------------------------------------------------------------

function M3Field:createRandomAbilityItem(col, row)
    local infos   = self:getAvailableAbilitiesInfo()
    local info    = infos[math.random(#infos)]
    
    return self:createItem(col, row, info)
end
-------------------------------------------------------------------------------

function M3Field:createBorder()
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local cell_L  = self:getCell(col-1,row)
                local cell_R  = self:getCell(col+1,row)
                local cell_T  = self:getCell(col,row-1)
                local cell_B  = self:getCell(col,row+1)
                local cell_LT = self:getCell(col-1,row-1)
                local cell_LB = self:getCell(col-1,row+1)
                local cell_RT = self:getCell(col+1,row-1)
                local cell_RB = self:getCell(col+1,row+1)
                
                if not cell_L then
                    cell:createBorder(M3Cell.EBorderId.LBORDER)
                    
                    if cell_LT then
                        cell:createBorder(M3Cell.EBorderId.LTCORNERIN)
                    elseif not cell_T then
                        cell:createBorder(M3Cell.EBorderId.LTCORNEROUT)
                    end
                    
                    if cell_LB then
                        cell:createBorder(M3Cell.EBorderId.LBCORNERIN)
                    elseif not cell_B then
                        cell:createBorder(M3Cell.EBorderId.LBCORNEROUT)
                    end
                end
                
                if not cell_R then
                    cell:createBorder(M3Cell.EBorderId.RBORDER)
                    
                    if cell_RB then
                        cell:createBorder(M3Cell.EBorderId.RBCORNERIN)
                    elseif not cell_B then
                        cell:createBorder(M3Cell.EBorderId.RBCORNEROUT)
                    end
                    
                    if cell_RT then
                        cell:createBorder(M3Cell.EBorderId.RTCORNERIN)
                    elseif not cell_T then
                        cell:createBorder(M3Cell.EBorderId.RTCORNEROUT)
                    end
                end
                
                if not cell_T then
                    cell:createBorder(M3Cell.EBorderId.TBORDER)
                end
                
                if not cell_B then
                    cell:createBorder(M3Cell.EBorderId.BBORDER)
                end
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:build(cols, rows, cellsString)
    self.cols = cols
    self.rows = rows
    
    for row=0, self.rows-1 do
        if not self.cells[row] then
            self.cells[row] = {}
        end
        
        for col=0, self.cols-1 do 
            local index = col + row * self.cols + 1
            local cellType = string.sub(cellsString, index, index)
            
            if cellType ~= M3Field.CELL_SYMBOL_NONE then 
                local bPassable = (cellType == M3Field.CELL_SYMBOL_PASSABLE)
                self:createCell(col, row, bPassable)
            end
        end
    end
    
    self:createBorder()
    
    if self.aimCurInterval == nil then
        self.aimCurInterval = self.aimMinInterval +
                              math.random(self.aimMaxInterval - self.aimMinInterval)
    end
    
    if self.abilityCurInterval == nil then
        self.abilityCurInterval = self.abilityMinInterval +
                                  math.random(self.abilityMaxInterval - self.abilityMinInterval)
    end
end
-------------------------------------------------------------------------------

function M3Field:open(time)
    local pescent  = 0.6
    local delay    = time * pescent / (self.cols * self.rows)
    local maxDelay = delay * (self.cols * self.rows)
    local cellTime = math.max(0, time - maxDelay)
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local cellDelay = (col * row) * delay
                cell:show(cellTime, cellDelay)
            end
        end
    end
    
    local effector = CreateAnimationEffector()
    effector:setDestroysOnEnd(true)
    effector:attachTo(self.anchor)
    effector:setKey(_TRACK_O, 0, 0)
    effector:setKey(_TRACK_O, time * 0.2, 1)
    effector:play()
end
-------------------------------------------------------------------------------

function M3Field:close(time)
    local pescent  = 0.6
    local delay    = time * pescent / (self.cols * self.rows)
    local maxDelay = delay * (self.cols * self.rows)
    local cellTime = math.max(0, time - maxDelay)
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local cellDelay = (col * row) * delay
                cell:hide(cellTime, cellDelay)
            end
        end
    end
    
    local effector = CreateAnimationEffector()
    effector:setDestroysOnEnd(true)
    effector:attachTo(self.anchor)
    effector:setKey(_TRACK_O, 0,          1)
    effector:setKey(_TRACK_O, time * 0.8, 1)
    effector:setKey(_TRACK_O, time,       0)
    effector:play()
    
    self:stopHintTimerRequest()
end
-------------------------------------------------------------------------------

function M3Field:update(target, dt)
    self.bNeedCrashSound  = false
    self.bNeedIceSound    = false
    
    self.timer:update(dt)
    
    if self.bActive then
        if self.abilityLogic then
            self.abilityLogic:update(dt)
        end
        
        if not self.bShuffle then
            self:checkOutFieldItems()
            
            self:updateMoveLogic()
            self:updateReplaseLogic()
        end
        
        if self.bChanged then
            local event = {eventName = 'Change'}
            self.ed:dispatchEvent(event)
            self.bChanged = false
        end
    end
    
    if self.bNeedCrashSound then
        self:playSound('m3_stone', true)
    end
    
    if self.bNeedIceSound then
        self:playSound('m3_ice', true)
    end
end
-------------------------------------------------------------------------------

function M3Field:checkOutFieldItems()
    for i=#self.outFieldItems, 1, -1 do
        local item = self.outFieldItems[i]
        
        if item:isKilled() and not item:isAnimated() then
            item:destroy()
            table.remove(self.outFieldItems, i)
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:updateMoveLogic()
    local col, row
    local bWasFallingComplete = false
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local item = cell:getItem()
                
                if item and self:checkItemDestroying(col, row) then
                    item = nil
                end
                
                if item and self:checkItemFalling(col, row) then
                    item = nil
                end
                
                if item then
                    if self:checkPassingItem(col, row) then
                        if item.id == self.aimId then
                            self.aimsCount = self.aimsCount - 1
                            
                            local time  = M3Item.MOVE_TIME
                            local event = {
                                            eventName = 'AimPassed',
                                            item      = item,
                                            delay     = time
                                          }
                            self.ed:dispatchEvent(event)
                            self:playSound('m3_aim', true)
                        elseif item.ability then
                            local time  = M3Item.MOVE_TIME
                            local event = {
                                            eventName = 'AbilityPassed',
                                            item      = item,
                                            delay     = time
                                          }
                            self.ed:dispatchEvent(event)
                            self:playSound('m3_ability', true)
                        end
                        
                        item = nil
                        self:setChanged()
                    else
                        if item:isFalling() and not item:isMoving() then
                            bWasFallingComplete = true
                            
                            if item:canCrash() and not item:isLocked() then
                                item:crash()
                                
                                self.bNeedCrashSound = true
                            else
                                item:bounce()
                            end
                            self:setChanged()
                        end
                    end
                end
                
                if not item and self:isCellSpawner(col, row) then
                    if self:canCreateAim(col, row) then
                        self.aimCurInterval = self.aimMinInterval + math.random(self.aimMaxInterval - self.aimMinInterval)
                        self.aimsLeft       = self.aimsLeft - 1
                        
                        item = self:createAimItem(col, row)
                    elseif self:canCreateAbility(col, row) then
                        self.abilityCurInterval = self.abilityMinInterval + math.random(self.abilityMaxInterval - self.abilityMinInterval)
                        
                        item = self:createRandomAbilityItem(col, row)
                    else
                        if self.aimsCount < M3Field.MAX_AIMS_COUNT then
                            self.aimCurInterval = self.aimCurInterval - 1
                        end
                        
                        self.abilityCurInterval = math.max(0, self.abilityCurInterval - 1)
                        
                        local info
                        
                        repeat
                            info = self:getRandomItemInfo()
                        until self.sameItemsIdCount < 2 or self.lastCreateItemId ~= info.id
                        
                        if self.lastCreateItemId == info.id then
                            self.sameItemsIdCount = self.sameItemsIdCount + 1
                        else
                            self.lastCreateItemId = info.id
                            self.sameItemsIdCount = 1
                        end
                        
                        local bLocked = (self.lockedCount < self.lockedMaxCount) and (math.random() < self.lockRate)
                        item = self:createItem(col, row, info)
                        
                        if bLocked then
                            self:setItemLocked(item, true)
                        end
                    end
                    
                    item:setPos(0, -M3Cell.CELL_SIZE)
                    item:appear()
                    item:fall()
                    
                    self:setChanged()
                end
                
                if item then
                    if item:isCalm() then
                        local bCellFocused = cell:isFocused()
                        item:setFocused(bCellFocused)
                    else
                        item:setFocused(false)
                    end
                end
            end
        end
    end
    
    if bWasFallingComplete then
        self:stopSound('m3_item_fell')
        self:playSound('m3_item_fell')
    end
end
-------------------------------------------------------------------------------

function M3Field:updateReplaseLogic()
    self:checkCombination()
    self:checkSwapBackCells()
    
    if self:isCalm() then
        self.comboCount = 0
        self.comboShownCount = 1
        
        if self:findExchangeItem(true) then
            self:startHintTimerRequest()
        else
            self:shuffle()
        end
    else
        self:stopHintTimerRequest()
    end
end
-------------------------------------------------------------------------------

function M3Field:fill(itemsString)
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local index  = col + row * self.cols + 1
                local itemID = itemsString:sub(index, index)
                
                if itemID ~= M3Field.ITEM_SYMBOL_NONE then
                    local info = self:getItemInfo(itemID)
                    local item
                    
                    if info.id == self.aimId then
                        item = self:createAimItem(col, row)
                    else
                        item = self:createItem(col, row, info)
                    end
                    
                    item:setPos(0,0)
                end
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:fillRandom()
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell and not cell:getItem() then
                local bCorrect
                local info
                
                local checksItems = {}
                table.insert(checksItems, {self:getItem(col,row+1), self:getItem(col,row+2)})
                table.insert(checksItems, {self:getItem(col,row-1), self:getItem(col,row-2)})
                table.insert(checksItems, {self:getItem(col,row-1), self:getItem(col,row+1)})
                table.insert(checksItems, {self:getItem(col+1,row), self:getItem(col+2,row)})
                table.insert(checksItems, {self:getItem(col-1,row), self:getItem(col-2,row)})
                table.insert(checksItems, {self:getItem(col-1,row), self:getItem(col+1,row)})
                
                repeat
                    bCorrect = true
                    info = self:getRandomItemInfo()
                    
                    for i, items in ipairs(checksItems) do
                        if items[1] and items[2] then 
                            if items[1].id == info.id and items[2].id == info.id then
                                bCorrect = false
                                break
                            end
                        end
                    end
                until bCorrect
                
                local item = self:createItem(col, row, info)
                item:setPos(0,0)
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:fillLocks(locksString)
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local item = self:getItem(col, row)
            
            if item then
                local index = col + row * self.cols + 1
                local bLock = (locksString:sub(index, index) == M3Field.LOCKED_SYMBOL)
                
                if bLock then
                    self:setItemLocked(item, true)
                end
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:fillCharges(chargedString)
    if not chargedString then
        return
    end
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local item = self:getItem(col, row)
            
            if item then
                local index = col + row * self.cols + 1
                local bCharged = (chargedString:sub(index, index) == M3Field.CHARGED_SYMBOL)
                
                if bCharged then
                    item:charge()
                end
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:shuffle()
    local col, row
    local items = {}
    
    repeat
        for row=0, self.rows-1 do
            for col=0, self.cols-1 do
                local cell = self:getCell(col, row)
                
                if cell then
                    local item = cell:getItem()
                    assert(item)
                    
                    if item:isShufflable() then
                        table.insert(items, item)
                        cell:setItem(nil)
                    end
                end
            end
        end
        
        table.shuffle(items)
        
        for row=self.rows-1, 0, -1 do
            for col=self.cols-1, 0, -1 do
                local cell = self:getCell(col, row)
                
                if cell and cell:getItem() == nil then
                    local item = table.remove(items)
                    cell:setItem(item)
                end
            end
        end
    until self:findExchangeItem(true)
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local item = cell:getItem()
            
                if item and item:isShufflable() then
                    local pos = cell:getCenterPos()
                    
                    local rect = {  x = -pos.x,
                                    y = -pos.y,
                                    w = self.cols * M3Cell.CELL_SIZE,
                                    h = self.rows * M3Cell.CELL_SIZE
                                 }
                    item:flyHome(rect, M3Field.SHUFFLE_TIME)
                end
            end
        end
    end
    
    self:resetAllFlyingCharges()
    self:setChanged()
    
    self.bShuffle = true
    self.timer:add( function()
                        self.bShuffle = false
                    end,
                    M3Field.SHUFFLE_TIME)
end
-------------------------------------------------------------------------------

function M3Field:explode(col, row, radius, bWithAnim)
    radius = radius or 1
    
    local checked = {}
    local marcked = {}
    
    local explodeWave = function(col, row, depth)
        depth = (depth ~= nil) and (depth + 1) or 1
        
        local nearby = {}
        table.insert(nearby, {col, row})
        table.insert(nearby, {col + 1, row})
        table.insert(nearby, {col + 1, row + 1})
        table.insert(nearby, {col,     row + 1})
        table.insert(nearby, {col - 1, row + 1})
        table.insert(nearby, {col - 1, row})
        table.insert(nearby, {col - 1, row - 1})
        table.insert(nearby, {col,     row - 1})
        table.insert(nearby, {col + 1, row - 1})
        
        for i, pos in ipairs(nearby) do
            local c = pos[1]
            local r = pos[2]
            local item = self:getItem(c, r)
            
            if item then
                local index = r * self.cols + c
                
                if not marcked[index] then
                    if item:isExplodable() and item:isCalm() then
                        marcked[index] = true
                    end
                end
                
                if c == col and r == row then
                    checked[index] = true
                else
                    if depth < radius and not checked[index] then
                        explodeWave(c, r, depth)
                    end
                end
            end
        end
    end
    
    explodeWave(col, row)
    
    for index, bMarcked in pairs(marcked) do
        local r    = math.floor(index / self.cols)
        local c    = index - (r * self.cols)
        local cell = self:getCell(c, r)
        local item = cell:getItem()
        
        self:resetSelect(cell)
        self:resetFlyingCharge(cell)
        self:resetSwap(cell.swapId)
        
        if item:isLocked() then
            self:setItemLocked(item, false, 1)
            self.bNeedIceSound = true
        elseif item:isCharged() then
            item:kill()
        else
            local pos = Vector2f(c - col, r - row)
            local a = math.atan((c - col)/(r - row))
        
            item:explode(a)
            
            table.insert(self.outFieldItems, item)
            cell:setItem(nil)
        end
    end
    
    if bWithAnim then
        self:_getExplodeAnim(col, row)
    end
    self:playSound('m3_explode', true)
end
-------------------------------------------------------------------------------

function M3Field:canCreateAbility(col, row)
    if self.abilityCurInterval > 0 then
        return false
    end
    
    for iRow = 0, self.rows-1 do
        local item = self:getItem(col, iRow)
        
        if item and (item.ability or item.id == self.aimId) then
            return false
        end
    end
    
    local availableAbilities = self:getAvailableAbilitiesInfo()
    return #availableAbilities > 0
end
-------------------------------------------------------------------------------

function M3Field:canCreateAim(col, row)
    if self.aimsLeft <= 0 or self.aimCurInterval > 0 then
        return false
    end
    
    for iRow = 0, self.rows-1 do
        local item = self:getItem(col, iRow)
        
        if item and (item.ability or item.id == self.aimId) then
            return false
        end
    end
    
    return true
end
-------------------------------------------------------------------------------

function M3Field:isCellSpawner(col, row)
    for _row = row-1, 0, -1 do
        if self:getCell(col, _row) then
            return false
        end
    end
    
    return not (self:getCell(col-1, row-1) or
                self:getCell(col+1, row-1))
end
-------------------------------------------------------------------------------

function M3Field:isTopLineEmpty(col, row)
    local upperRow = row-1
    
    for upperRow = row-1, 0, -1 do
        local cell = self:getCell(col, upperRow)
        
        if not cell then
            return true
        end
        
        if cell:getItem() then
            return false
        end
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:isCellSwapping(col, row)
    local cell = self:getCell(col, row)
    return cell and cell.swapId
end
-------------------------------------------------------------------------------

function M3Field:isCalm()
    local col, row
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local item = cell:getItem()
                
                if not item or item:isAnimated() or item:isMoving() or item:isKilled() then
                    return false
                end
            end
        end
    end
    
    return true
end
-------------------------------------------------------------------------------

function M3Field:isExchangableItem(col, row)
    local item = self:getItem(col, row)
    return item and not item:isKilled() and not item:isLocked()
end
-------------------------------------------------------------------------------

function M3Field:isCheckCombination(items, col, row)
    local id = items[row][col].id
    local count
    local c, r
    
    if not items[row][col]:isCombinable() then
        return false
    end
    
    count = 1
    for c=col-1, 0, -1 do
        local item = items[row][c]
        if item and not item:isKilled() and item.id == id then
            count = count + 1
        else
            break
        end
    end
    
    for c=col+1, self.cols-1 do
        local item = items[row][c]
        if item and not item:isKilled() and item.id == id then
            count = count + 1
        else
            break
        end
    end
    
    if count >= 3 then
        return true
    end
    
    count = 1
    for r=row-1, 0, -1 do
        local item = items[r][col]
        if item and not item:isKilled() and item.id == id then
            count = count + 1
        else
            break
        end
    end
    
    for r=row+1, self.rows-1 do
        local item = items[r][col]
        if item and not item:isKilled() and item.id == id then
            count = count + 1
        else
            break
        end
    end
    
    if count >= 3 then
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:isAbilityDragging(ability)
    return self.abilityLogic and self.abilityLogic.ability == ability
end
-------------------------------------------------------------------------------

function M3Field:resetSelect(cell)
    cell = cell or self.selectCell
    
    if self.selectCell and
        self.selectCell.col == cell.col and
        self.selectCell.row == cell.row
    then
        cell:setSelected(false)
        self.selectCell = nil
        
        if self.bDragging then
            self.bDragging = false
        end
        return true
    end
    
	return false
end
-------------------------------------------------------------------------------

function M3Field:resetSwap(swapId)
    local cells = self.swappingCells[swapId]
    
    if cells then
        cells[1].swapId = nil
        cells[2].swapId = nil
        
        self.swappingCells[swapId] = nil
    end
end
-------------------------------------------------------------------------------

function M3Field:resetFlyingCharge(cell)
    local info = self:getCellsCharge(cell)
    
    if info then
        local targetCell = self:getRandomCalmCell()
        info.action:kill()
        
        if targetCell then
            local targetPos = targetCell:getCenterPos()
            
            info.charge:fly(targetPos, info.rect, M3Field.CHARGE_FLY_TIME)
            info.action     = self.timer:add(info.callback, M3Field.CHARGE_FLY_TIME)
            info.targetCell = targetCell
        else
            info.charge:destroy()
            self.charges[info.charge.id] = nil
        end
        
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:resetAllFlyingCharges()
    for chargeId, info in pairs(self.charges) do
        local targetCell = self:getRandomCalmCell(true)
        
        if targetCell then
            local targetPos = targetCell:getCenterPos()
                
            info.charge:fly(targetPos, info.rect, M3Field.CHARGE_FLY_TIME)
            info.action:kill()
            info.action     = self.timer:add(info.callback, M3Field.CHARGE_FLY_TIME)
            info.targetCell = targetCell
        else
            info.charge:destroy()
            self.charges[chargeId] = nil
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:checkItemDestroying(col, row)
    local cell = self:getCell(col, row)
    local item = cell:getItem()
    
    if item and item:isKilled() and not item:isAnimated() then
        cell:setItem(nil)
        
        if item:isCharged() then
            self:explode(col, row)
        end
        
        item:destroy()
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:checkItemFalling(col, row)
    local cell = self:getCell(col, row)
    local item = cell:getItem()
    
    if item:isAnimated() or item:isMoving() then
        return false
    end
    
    local targets = {}
    
    if M3Field.MOVEABLE_CELLS_MODE then
        if cell.bFallable then
            for _row = row + 1, self.rows-1 do
                local _cell = self:getCell(col, _row)
                
                if _cell then
                    table.insert(targets, {cell=_cell, checkUpperCells=false})
                    break
                end
            end
        end
    else
        table.insert(targets, {cell=self:getCell(col,   row+1), checkUpperCells=false})
    end
    
    table.insert(targets, {cell=self:getCell(col-1, row+1), checkUpperCells=true})
    table.insert(targets, {cell=self:getCell(col+1, row+1), checkUpperCells=true})
    
    for i, target in ipairs(targets) do
        local trgtCell = target.cell
        
        if trgtCell and not trgtCell:getItem() and
           (not target.checkUpperCells or self:isTopLineEmpty(trgtCell.col, trgtCell.row))
        then
            self:resetSelect(cell)
            self:resetFlyingCharge(cell)
            self:resetSwap(cell.swapId)
            
          --  item:setFocused(false, nil, true)
            
            local fallHeight = trgtCell.row - cell.row
            
            cell:setItem(nil)
            trgtCell:setItem(item)
            
            item:fall(fallHeight)
            return true
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:checkSwapCells(cell1, cell2, bSwapBack, bAnyPositions)
    if not cell1 or not cell2 then
        return false
    end
    
    local item1 = cell1:getItem()
    local item2 = cell2:getItem()
    
    if not item1 or item1:isMoving() or item1:isAnimated() or item1:isLocked() or
       not item2 or item2:isMoving() or item2:isAnimated() or item2:isLocked()
    then
        return false
    end
    
    if (bAnyPositions and (cell1.col ~= cell2.col or cell1.row ~= cell2.row)) or
       (cell1.col == cell2.col and math.abs(cell1.row - cell2.row) == 1) or
       (cell1.row == cell2.row and math.abs(cell1.col - cell2.col) == 1)
    then
        self:resetSelect(cell1)
        self:resetSelect(cell2)
        
        cell1:setItem(item2)
        cell2:setItem(item1)
        
        item1:moveHome(M3Field.SWAPPING_TIME)
        item2:moveHome(M3Field.SWAPPING_TIME)
        
        self:resetFlyingCharge(cell1)
        self:resetFlyingCharge(cell2)
        
        self:setChanged()
        
        item2:addAnchorZ(2)
        self.timer:add( function()
                            item2:addAnchorZ(-2)
                        end,
                        M3Field.SWAPPING_TIME)
        
        if not bSwapBack then
            local swapId = 'R'..tostring(cell1.row)..'C'..tostring(cell1.col)..'_'..
                           'R'..tostring(cell2.row)..'C'..tostring(cell2.col)
            
            self.swappingCells[swapId] = {cell1, cell2}
            cell1.swapId = swapId
            cell2.swapId = swapId
        end
        
        self:playSound('m3_move', true)
        
        return true
    end
end
-------------------------------------------------------------------------------

function M3Field:checkSwapBackCells()
    for swapId, cells in pairs(self.swappingCells) do
        local cell1 = cells[1]
        local cell2 = cells[2]
        
        if self:checkSwapCells(cell1, cell2, true) then
            self:resetSwap(swapId)
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:checkPassingItem(col, row)
    local cell = self:getCell(col, row)
    
    if cell and cell:isPassable() and not self:isCellSwapping(col, row) then
        local item = cell:getItem()
        
        if item and item:isPassable() and not item:isAnimated() and not item:isMoving() then
            cell:setItem(nil)
            
            item:pass()
            return true
        end
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:checkCharges(comboInfo)
    local ids = {}
    for comboId, _ in pairs(comboInfo) do
        table.insert(ids, comboId)
    end
    
    if #ids == 0 then
        return
    end
    
    local randomId = ids[math.random(#ids)]
    local info     = comboInfo[randomId]
    
    if info then
        local targetCell = self:getRandomCalmCell()
        
        -- TODO
        if targetCell then
            local col, row = info.getCenter()
            local pos      = self:getCell(col, row):getCenterPos()
            local charge   = M3Charge(self.timer)
            
            self.comboShownCount = self.comboShownCount + 1
            local text = 'x'..self.comboShownCount
            self:_showText(pos, text)
            self:playSound('m3_bonus', true)
            
            charge:getAnchor():attachTo(self.anchor)
            charge:getAnchor():setOrder(M3Field.CHARGES_Z)
            charge:getAnchor():setXY(pos.x, pos.y)
            
            local targetPos  = targetCell:getCenterPos()
            local rect       = {x=0,
                                y=0,
                                w=self.cols*M3Cell.CELL_SIZE,
                                h=self.rows*M3Cell.CELL_SIZE}
            
            
            assert(not targetCell:getItem():isCharged())
            
            charge:fly(targetPos, rect, M3Field.CHARGE_FLY_TIME)
            
            local callback = function()
                local info = self.charges[charge.id]
                
                if info then
                    local item = info.targetCell:getItem()
                    
                    -- ADD 05.12
                    --TODO: раскоментить для перенапрявления, если итем  == nil
                    if item then
                        local ch = info.charge
                        local _ps = ch.particle
                        
                        -- if _ps then
                        --     local ps = _ps:GetParticleSystem()
                            
                        --     if ps then
                        --         ps:Stop(false)
                        --     end
                        -- end
                        
                        self.timer:add( function()
                                            ch:destroy()
                                        end,
                                        1)
                        if item then
                            item:charge()
                        else
                            Log('MATCH3: Charge arrived to an empty cell.')
                        end
                        
                        self.charges[charge.id] = nil
                        self:resetFlyingCharge(info.targetCell)
                        
                        self:setChanged()
                    
                    --TODO: раскоментить для перенапрявления, если итем  == nil
                    ---[[
                    else
                        Log('MATCH3: Charge arrived to an empty cell.')
                        self:resetFlyingCharge(info.targetCell)
                    end
                    --]]
                    
                end
            end
            
            local action = self.timer:add(callback, M3Field.CHARGE_FLY_TIME)
            
            self.charges[charge.id] = {charge     = charge,
                                       targetCell = targetCell,
                                       callback   = callback,
                                       action     = action,
                                       rect       = rect}
            self:setChanged()
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:checkCombination()
    local col, row
    local comboInfo = {}
    
    for row=self.rows-1, 0, -1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell then
                local item = cell:getItem()
                
                if item and item:isCombinable() and not item:isMoving() and not item:isAnimated() then
                    local id = item.id
                    
                    local item1 = self:getItem(col, row-1)
                    local item2 = self:getItem(col, row-2)
                    
                    if item1 and item1.id == id and not item1:isMoving() and not item1:isAnimated() and (cell:getComboId() == 0 or self:getCell(col, row-1):getComboId() ~= cell:getComboId()) and
                       item2 and item2.id == id and not item2:isMoving() and not item2:isAnimated()
                    then
                        local comboCells = {}
                        local comboId    = 0
                        
                        for nextRow=row, 0, -1 do
                            local nextCell = self:getCell(col, nextRow)
                            
                            if nextCell then
                                local nextItem = nextCell:getItem()
                                
                                if nextItem and nextItem.id == id and not nextItem:isMoving() and not nextItem:isAnimated() then
                                    table.insert(comboCells, nextCell)
                                    
                                    if nextCell:getComboId() ~= 0 then
                                        comboId = nextCell:getComboId()
                                    end
                                else
                                    break
                                end
                            else
                                break
                            end
                        end
                        
                        if comboId == 0 then
                            self.comboCount = self.comboCount + 1
                            comboId = self.comboCount
                            
                            if comboId > 1 and not comboInfo[comboId] then
                                comboInfo[comboId] = M3Helpers.createComboInfo()
                            end
                        end
                        
                        for i, cell in ipairs(comboCells) do
                            cell:setComboId(comboId)
                                
                            if comboInfo[comboId] then
                                comboInfo[comboId].add(cell.col, cell.row)
                            end
                        end
                    end
                    
                    item1 = self:getItem(col+1, row)
                    item2 = self:getItem(col+2, row)
                    
                    if item1 and item1.id == id and not item1:isMoving() and not item1:isAnimated() and (cell:getComboId() == 0 or self:getCell(col+1, row):getComboId() ~= cell:getComboId()) and
                       item2 and item2.id == id and not item2:isMoving() and not item2:isAnimated()
                    then
                        local comboCells = {}
                        local comboId    = 0
                        
                        for nextCol=col, self.cols-1 do
                            local nextCell = self:getCell(nextCol, row)
                            
                            if nextCell then
                                local nextItem = nextCell:getItem()
                                
                                if nextItem and nextItem.id == id and not nextItem:isMoving() and not nextItem:isAnimated() then
                                    table.insert(comboCells, nextCell)
                                    
                                    if nextCell:getComboId() ~= 0 then
                                        comboId = nextCell:getComboId()
                                    end
                                else
                                    break
                                end
                            else
                                break
                            end
                        end
                        
                        if comboId == 0 then
                            self.comboCount = self.comboCount + 1
                            comboId = self.comboCount
                            
                            if comboId > 1 and not comboInfo[comboId] then
                                comboInfo[comboId] = M3Helpers.createComboInfo()
                            end
                        end
                        
                        for i, cell in ipairs(comboCells) do
                            cell:setComboId(comboId)
                                
                            if comboInfo[comboId] then
                                comboInfo[comboId].add(cell.col, cell.row)
                            end
                        end
                    end
                end
            end
        end
    end
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            local cell = self:getCell(col, row)
            
            if cell and cell:getComboId() ~= 0 then
                self:resetSelect(cell)
                self:resetFlyingCharge(cell)
                self:resetSwap(cell.swapId)
                
                local item = cell:getItem()
                
                if item:isLocked() then
                    self:setItemLocked(item, false, 1)
                    self.bNeedIceSound = true
                else
                    item:kill()
                end
                
                cell:setComboId(0)
                self:setChanged()
            end
        end
    end
    
    self:checkCharges(comboInfo)
end
-------------------------------------------------------------------------------

function M3Field:findExchangeItem(bSimpleCheck)
    local col, row
    local items = {}
    local candidates = {}
    
    for row=0, self.rows-1 do
        items[row] = {}
        for col=0, self.cols-1 do
            items[row][col] = self:getItem(col, row)
        end
    end
    
    for row=0, self.rows-1 do
        for col=0, self.cols-1 do
            if self:isExchangableItem(col, row) then
                local col2 = col + 1
                
                if self:isExchangableItem(col2, row) then
                    items[row][col],
                    items[row][col2] = items[row][col2],
                                       items[row][col]
                    
                    if self:isCheckCombination(items, col,  row) or 
                       self:isCheckCombination(items, col2, row)
                    then
                        if bSimpleCheck then
                            return true
                        end
                        
                        table.insert(candidates, {c1 = col,
                                                  c2 = col2,
                                                  r1 = row,
                                                  r2 = row})
                    end
                    
                    items[row][col],
                    items[row][col2] = items[row][col2],
                                       items[row][col]
                end
                
                local row2 = row + 1
                
                if self:isExchangableItem(col, row2) then
                    items[row][col],
                    items[row2][col] = items[row2][col],
                                       items[row][col]
                    
                    if self:isCheckCombination(items, col, row) or 
                       self:isCheckCombination(items, col, row2)
                    then
                        if bSimpleCheck then
                            return true
                        end
                        
                        table.insert(candidates, {c1 = col,
                                                  c2 = col,
                                                  r1 = row,
                                                  r2 = row2})
                    end
                    
                    items[row][col],
                    items[row2][col] = items[row2][col],
                                       items[row][col]
                end
            end
        end
    end
    
    
    if #candidates > 0 then
        local index = math.random(#candidates)
        return true, candidates[index]
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:hintReplace()
    local bExist, cells = self:findExchangeItem()
    local cell1 = self:getCell(cells.c1, cells.r1)
    local cell2 = self:getCell(cells.c2, cells.r2)
    
    local arrow1 = self:_getArrow('replace')
    local arrow2 = self:_getArrow('replace')
    
    local time       = 3
    local iterations = 20
    local dAngle     = M_PI / iterations
    
    local effector1 = CreateAnimationEffector()
    effector1:setDestroysOnEnd(false)
    effector1:attachTo(arrow1)
    effector1:setKey(_TRACK_O, 0,           0)
    effector1:setKey(_TRACK_O, time * 0.25, 1)
    effector1:setKey(_TRACK_O, time * 0.75, 1)
    effector1:setKey(_TRACK_O, time,        0)
    
    local effector2 = CreateAnimationEffector()
    effector2:setDestroysOnEnd(false)
    effector2:attachTo(arrow2)
    effector2:setKey(_TRACK_O, 0,           0)
    effector2:setKey(_TRACK_O, time * 0.25, 1)
    effector2:setKey(_TRACK_O, time * 0.75, 1)
    effector2:setKey(_TRACK_O, time,        0)
    
    if cell1.row == cell2.row then
        local minX = math.min(cell1:getCenterPos().x, cell2:getCenterPos().x)
        local maxX = math.max(cell1:getCenterPos().x, cell2:getCenterPos().x)
        local dx   = maxX - minX
        local dy   = 10
        
        arrow1:setRotationRad(0)
        arrow2:setRotationRad(M_PI)
        
        arrow1:setY(cell1:getCenterPos().y - dy)
        arrow2:setY(cell1:getCenterPos().y + dy)
        
        for i=0, iterations do
            local percent      = math.sin(i*dAngle)
            local currTime     = time * i/iterations
            
            effector1:setKey(_TRACK_X, currTime, minX + dx * percent)
            effector2:setKey(_TRACK_X, currTime, maxX - dx * percent)
        end
    else
        local minY = math.min(cell1:getCenterPos().y, cell2:getCenterPos().y)
        local maxY = math.max(cell1:getCenterPos().y, cell2:getCenterPos().y)
        local dy   = maxY - minY
        local dx   = 10
        local x    = cell1:getCenterPos().x
        
        arrow1:setRotationRad( M_PI_OVER_2)
        arrow2:setRotationRad(-M_PI_OVER_2)
        
        arrow1:setX(x - dx)
        arrow2:setX(x + dx)
        
        for i=0, iterations do
            local percent  = math.sin(i*dAngle)
            local currTime = time * i/iterations
            
            effector1:setKey(_TRACK_Y, currTime, minY + dy * percent)
            effector2:setKey(_TRACK_Y, currTime, maxY - dy * percent)
        end
    end
    
    effector1:play()
    effector2:play()
    
    table.insert(self.arrowEffectors, effector1)
    table.insert(self.arrowEffectors, effector2)
    
    self.resetHintAction = self.timer:add(bind(self.resetHint, self), time)
    
    return time
end
-------------------------------------------------------------------------------

function M3Field:hintAims()
    local cells = {}
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local item = self:getItem(col, row)
            
            if item and item.id == self.aimId then
                table.insert(cells, self:getCell(col, row))
            end
        end
    end
    
    local moveCount  = 3
    local moveTime   = 3
    local delayTime  = 1
    local iterations = 20
    local dAngle     = M_PI_OVER_2 / iterations
    local dScale     = 0.1
    local dY         = 20
    local time       = moveTime * moveCount + delayTime * (moveCount - 1)
    
    for i, cell in ipairs(cells) do
        local pos        = cell:getCenterPos()
        local arrow      = self:_getArrow('aim')
        
        arrow:setXY(pos.x, pos.y)
        
        local effector = CreateAnimationEffector()
        effector:attachTo(arrow)
        effector:setDestroysOnEnd(false)
        
        for i=1, moveCount do
            local startMoveTime = (moveTime + delayTime) * (i-1)
            
            effector:setKey(_TRACK_O, startMoveTime,                        0)
            effector:setKey(_TRACK_O, startMoveTime + moveTime * 0.25,      1)
            effector:setKey(_TRACK_O, startMoveTime + moveTime * 0.75,      1)
            effector:setKey(_TRACK_O, startMoveTime + moveTime,             0)
            effector:setKey(_TRACK_O, startMoveTime + moveTime + delayTime, 0)
            
            for i=0, iterations do
                local percent  = math.sin(i*dAngle)
                local currTime = startMoveTime + moveTime * i/iterations
                
                effector:setKey(_TRACK_Y, currTime, pos.y + percent * dY)
                effector:setKey(_TRACK_S, currTime, 1     + percent * dScale)
            end
            
            effector:setKey(_TRACK_Y, startMoveTime + moveTime + delayTime, pos.y)
            effector:setKey(_TRACK_S, startMoveTime + moveTime + delayTime, 1)
        end
        
        effector:play()
        cell:getItem():helpAnimate(time)
        
        table.insert(self.arrowEffectors, effector)
    end
    
    self.resetHintAction = self.timer:add(bind(self.resetHint, self), time)
    return time
end
-------------------------------------------------------------------------------

function M3Field:getExplodeAimCell()
    local aimCells    = {}
    local resultCells = {}
    
    for row=self.rows-1, 0, -1 do
        for col=self.cols-1, 0, -1 do
            local item = self:getItem(col, row)
            
            if item and item.id == self.aimId then
                table.insert(aimCells, self:getCell(col, row))
            end
        end
    end
    
    for i, cell in ipairs(aimCells) do
        local col = cell.col
        local row = cell.row + 1
        
        while self:getCell(col, row) do
            local underCell = self:getCell(col, row)
            local item = underCell:getItem()
            
            if not item or item.id == self.aimId or not item:isCalm() then
                break
            end
            
            if underCell:isPassable() then
                table.insert(resultCells, cell)
                break
            end
            
            row = row + 1
        end
    end
    
    table.shuffle(resultCells)
    
    return (#resultCells > 0) and resultCells[1] or nil
end
-------------------------------------------------------------------------------

function M3Field:hintExplodeAimCol(cell)
    local col = cell.col
    local row = cell.row + 1
    
    while self:getCell(col, row) do
        local curCell = self:getCell(col, row)
        local item = curCell:getItem()
        
        if item and
            item.id ~= self.aimId and
            not item.ability and
            item:isCalm()
        then
            if item:isLocked() then
                self:setItemLocked(item, false, 1, true)
                self.bNeedIceSound = true
            else
                item:kill()
            end
        end
        
        row = row + 1
    end
    
    return 1
end
-------------------------------------------------------------------------------

function M3Field:hintExplodeRandomCol()
    local candidates = {}
    local col, row
    
    for col=self.cols-1, 0, -1 do
        local count = 0
        
        for row=self.rows-1, 0, -1 do
            local item = self:getItem(col, row)
            
            if item and item.id ~= self.aimId and not item.ability then
                count = count + 1
            end
        end
        
        if count > 0 then
            table.insert(candidates, {col=col, count=count})
        end
    end
    
    table.shuffle(candidates)
    
    col = candidates[1].col
    
    for row=0, self.rows-1 do
        local cell = self:getCell(col, row)
        
        if cell then
            local item = cell:getItem()
            
            if item and
                item.id ~= self.aimId and
                not item.ability and
                item:isCalm()
            then
                if item:isLocked() then
                    self:setItemLocked(item, false, 1, true)
                    self.bNeedIceSound = true
                else
                    item:kill()
                end
            end
        end
    end
    
    return 1
end
-------------------------------------------------------------------------------

function M3Field:resetHint()
    if self.resetHintAction then
        self.resetHintAction:kill()
        self.resetHintAction = nil
    end
    
    for i, effector in ipairs(self.arrowEffectors) do
        effector:destroy()
    end
    
    self.arrowEffectors = {}
    
    for arrow, bEnable in pairs(self.replaceArrowsPool) do
        if not bEnable then
            arrow:setOpacity(0)
            self.replaceArrowsPool[arrow] = true
        end
    end
    
    for arrow, bEnable in pairs(self.aimArrowsPool) do
        if not bEnable then
            arrow:setOpacity(0)
            self.aimArrowsPool[arrow] = true
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:hint(bOnHintClicked)
    self:stopHintTimerRequest()
    local hintTime
    
    local hints = {}
    
    if bOnHintClicked then
        local explodeCell = self:getExplodeAimCell()
        
        if explodeCell then
            table.insert(hints, {type = 'ExplodeAimCol',
                                 cell = explodeCell
                                })
        else
            table.insert(hints, {type = 'ExplodeRandomCol'})
        end
    else
        table.insert(hints, {type = 'Replace'})
        table.insert(hints, {type = 'Aims'})
    end
    
    local index = math.random(#hints)
    local type = hints[index].type
    
    if type == 'Replace' then
        hintTime = self:hintReplace()
    elseif type == 'ExplodeAimCol' then
        hintTime = self:hintExplodeAimCol(hints[index].cell)
    elseif type == 'ExplodeRandomCol' then
        hintTime = self:hintExplodeRandomCol()
    elseif type == 'Aims' then
        hintTime = self:hintAims()
    end
    
    return hintTime
end
-------------------------------------------------------------------------------

function M3Field:startHintTimerRequest(delay)
    if self.hintAction then
        return
    end
    
    delay = delay or 0
    local time = 5
    
    self.hintAction = self.timer:add(function()
                                         local hintTime = self:hint()
                                         
                                         self.hintAction = nil
                                         self:startHintTimerRequest(hintTime)
                                     end,
                                     delay + time)
end
-------------------------------------------------------------------------------

function M3Field:stopHintTimerRequest()
    self:resetHint()
    
    if self.hintAction then
        self.hintAction:kill()
        self.hintAction = nil
    end
end
-------------------------------------------------------------------------------

function M3Field:checkCellAffect(cell)
    if self.abilityLogic then
        if cell then
            if self.abilityLogic:canAffect(cell) then
                self.abilityLogic:affect(cell)
                
                if self.abilityLogic:isComplete() then
                    local ability = self.abilityLogic.ability
                    local event = { eventName = 'AbilityAffected',
                                    ability   = ability}
                    
                    self.abilityCountBalance[ability] = self.abilityCountBalance[ability] - 1
                    
                    self:setAbility(nil)
                    self:resetSelect()
                    self.ed:dispatchEvent(event)
                end
            end
        else
            self:dropAbility()
        end
        
        return true
    end
    
    return false
end
-------------------------------------------------------------------------------

function M3Field:cellPress(cell)
    local item = cell:getItem()
    if not item or not item:isCalm() then
        return
    end
    
    if item:isLocked() then
        item:shakeLock()
        self:resetSelect()
        
        -- if not HasGameState(Match3HelpScreenSmall.DONT_SHOW_STATE_ICE, 'true') then
        --     CreateMatch3HelpScreenSmall('Ice')
        -- end
    else
        if not self:resetSelect(cell) then
            if not self:checkSwapCells(cell, self.selectCell) then
                if item.id == self.aimId and not HasGameState(Match3HelpScreenSmall.DONT_SHOW_STATE_EYE, 'true') then
                    -- CreateMatch3HelpScreenSmall('Eye')
                elseif item.ability and CanShowMatch3AbilityInfoScreen(item.ability) then
--                    CreateMatch3AbilityInfoScreen(item.ability, true)
                else
                    self:resetSelect()
                    
                    self.selectCell = cell
                    cell:setSelected(true)
                    self.bDragging = true
                    self.startDragPos = getHandledCursor():getPosition()
                    --Engine->AudioManager->PlaySound("ItemSlct");
                end
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:cellOver(cell)
    if not self.bActive or self.bShuffle then
        return
    end
    
    cell:setFocused(true)
end
-------------------------------------------------------------------------------

function M3Field:cellOut(cell)
    if cell:isFocused() then
        cell:setFocused(false)
    end
end
-------------------------------------------------------------------------------

function M3Field:playSound(soundName, bStopIfPlaing, bLoop)
    local info = self.sounds[soundName]
    
    if info.sound:isPlaying() and not bStopIfPlaing then
        return
    end
    
    if info.action then
        info.action:kill()
        info.action = nil
        info.sound:stop()
    end
    
    if bLoop then
        info.sound:play(true)
    else
        info.sound:play()
        info.action = self.timer:add(function()
                                        info.action = nil
                                        info.sound:stop()
                                     end,
                                     info.duration)
    end
end
-------------------------------------------------------------------------------

function M3Field:stopSound(soundName)
    local info = self.sounds[soundName]
    
    if info then
        if info.action then
            info.action:kill()
            info.action = nil
            info.sound:stop()
        end
        
        info.sound:stop()
    end
end
--m3_mirror_loop
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------
-- EVENTS
-------------------------------------------------------------------------------

function M3Field:onObjectMouseOver(entity, state)  
    local objectName = entity:getName()
    local cell = self:getCellByTile(objectName)
    
    if cell then
        self:cellOver(cell)
    end
end
-------------------------------------------------------------------------------

function M3Field:onObjectMouseOut(entity, state) 
    local objectName = entity:getName()
    local cell = self:getCellByTile(objectName)
    
    if cell then
        self:cellOut(cell)
    end
end
-------------------------------------------------------------------------------

function M3Field:onObjectMouseDown(entity)  
    if self.bActive then
       -- e.checkExecuted = true
        
        local objectName = entity:getName()
        local cell       = self:getCellByTile(objectName)
        
        if not self:checkCellAffect(cell) then
            if not self.bShuffle and cell then
                self:cellPress(cell)
            end
        end
    end
end
-------------------------------------------------------------------------------

function M3Field:onObjectMouseUp(entity)
    self.bDragging = false
end
-------------------------------------------------------------------------------

function M3Field:onMouseMove(e)
    if not self.bActive or self.bShuffle then
        return
    end
    
    if self.bDragging then
        local diffPos = e.currPos - self.startDragPos
        
        if diffPos:Length() > M3Field.SWAP_DISTANCE then
            local col = self.selectCell.col
            local row = self.selectCell.row
            local a   = math.deg(diffPos:Angle())
            
            if a < 45 and a > -45 then
                col = col + 1
            elseif a < 135 and a >= 45 then
                row = row + 1
            elseif a > -135 and a <= -45 then
                row = row - 1
            else
                col = col - 1
            end
            
            if not self:checkSwapCells(self:getCell(col, row), self.selectCell) then
                self:resetSelect()
            end
            self.bDragging = false
        end
    end
end
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------
-- PRIVATE
-------------------------------------------------------------------------------

function M3Field:initSounds()
    local tracks = parseSounds('Data/Sounds/Match3')
    
    for name, track in pairs(tracks) do
        local info    = {}
        info.sound    = CreateSoundTrack(track.path)
        info.sound:attachTo(self:getMainEntity())
        info.duration = info.sound:getDuration()
        
        self.sounds[name] = info
    end
end
-------------------------------------------------------------------------------

function M3Field:_getArrow(type)
    local pool = self[type..'ArrowsPool']
    local arrow
    
    for arrow, bEnable in pairs(pool) do
        if bEnable then
            pool[arrow] = false
            return arrow
        end
    end
    
    local textureName = M3Game.TEXTURE_PATH..type..'Arrow.png'
    local objectName  = type..'Arrow'..table.size(pool)
  --  local anim        = CreateAnimationByTextureName(textureName)
    local z           = M3Field.HINT_Z

    local anim        = CreateSpriteEntity(textureName)  
    
    if type == 'aim' then
        anim:setHotSpot(18, -10)
    end
    anim:setOrder(z)
    anim:setXY(0, 0)
   -- local arrow = play_field:CreateObject(objectName,anim,0,0,z,0,1,1)
    
    anim:attachTo(self.anchor)
    anim:setOpacity(0)
    anim:setRespondable(false)
    
    pool[anim] = false
    return anim
end
-------------------------------------------------------------------------------

function M3Field:_showText(pos, text)
    local time   = 2
    local deltaY = -50
    
    local field = CreateTextEntity()

    field:setFont('Data/Fonts/dialog.fnt') 
    field:setText(text)
    field:attachTo(self.anchor)
    field:setXY(pos.x, pos.y)
    field:setOrder(M3Field.COMBO_TEXT_Z)
    field:setTextAlignment(ETextHorizontalAlignmentCenter)
    field:setColor(1, 1, 1, 1)
    field:setScale(0.6)
    
    local effector = CreateAnimationEffector()
    effector:attachTo(field)
    effector:setDestroysOnEnd(false)
    effector:setKey(_TRACK_O, 0,          0)
    effector:setKey(_TRACK_O, time * 0.1, 1)
    effector:setKey(_TRACK_O, time * 0.9, 1)
    effector:setKey(_TRACK_O, time,       0)
    effector:setKey(_TRACK_Y, 0,    pos.y)
    effector:setKey(_TRACK_Y, time, pos.y + deltaY)
    effector:play()
    
    self.timer:add( function()
                        effector:destroy()
                        field:destroy()
                    end,
                    time)
end
-------------------------------------------------------------------------------

function M3Field:_getExplodeAnim(col, row)
    local object
    local count = 0
    
    for object, enable in pairs(self.explodeAnims) do
        if enable then
            break
        end
        
        object = nil
        count = count + 1
    end
    
    
    if not object then
        self.explodeAnimUniqueId = self.explodeAnimUniqueId + 1
        
        local texture    = M3Game.TEXTURE_PATH..'animExplode.png'
        local objectName = 'explodeAnimation'..tostring(self.explodeAnimUniqueId)
        
        local anim = CreateSpriteEntity(texture)
        anim:attachTo(self.anchor)
        anim:setAnimationSize(80, 76) 
        anim:setHotSpot(40,38)
        anim:setFramesCount(20)
        anim:setFPS(24)
        anim:setName(objectName) 
        anim:setPosition(Vector2f(0, 0)) 
        anim:setOrder(M3Field.EXPLODE_Z)
        anim:setScale(1) 
        anim:setRespondable(false)
        object = anim
    end
    
    object:setFrame(0)
    object:play()
    
    local time = 0.8
    local cell = self:getCell(col, row)
    local pos = cell:getCenterPos()
    
    object:setX(pos.x)
    object:setY(pos.y)
    object:setVisible(true)
    
    self.explodeAnims[object] = false
    self.timer:add( function()
                        object:setVisible(false)
                        self.explodeAnims[object] = true
                    end,
                    time)
end
-------------------------------------------------------------------------------

function M3Field:_getTorchAnim(col, row)
    local torch
    local count = 0
    
    for torch, enable in pairs(self.torchAnims) do
        if enable then
            break
        end
        
        torch = nil
        count = count + 1
    end
    
    if not torch then
        torch = {}
        
        self.torchAnimUniqueId = self.torchAnimUniqueId + 1
        
        do
            local texture    = M3Game.TEXTURE_PATH..'/items/itemTorch.png'
            local objectName = 'torchAnimBg'..tostring(self.torchAnimUniqueId)
            
            local anim   = CreateSpriteEntity(texture)  
            anim:setName(objectName)
            anim:setXY(0, 0)
            anim:attachTo(self.anchor)
            anim:setOrder(M3Field.EXPLODE_Z)
            anim:setRespondable(false)
            
            torch.bg = anim          
        end
        
        do
            local texture    = M3Game.TEXTURE_PATH..'/animTorchFire.png'
            local objectName = 'torchAnimFire'..tostring(self.torchAnimUniqueId)
            
            -- local anim = CreateAnimation(texture,0,0,50,12,15,24)
            local anim = CreateSpriteEntity(texture) 
            anim:attachTo(torch.bg)
            anim:setHotSpot(50,6)
            anim:setAnimationSize(50, 12) 
            anim:setFPS(24)
            anim:setFramesCount(15)
            anim:setName(objectName)
            anim:setXY(-20, -12)
            anim:setOrder(1)  
            anim:setRespondable(false)       
            
            torch.fire = anim     
        end
        
        do
            local texture    = M3Game.TEXTURE_PATH..'/torchGlow.png'
            local objectName = 'torchAnimGlowBottom'..tostring(self.torchAnimUniqueId)
            
            local anim = CreateSpriteEntity(texture)
            anim:attachTo(torch.bg)
            anim:setName(objectName)
            anim:setXY(-37, -9)
            anim:setOrder(2)  
            anim:setScaleX(9.78)  
            anim:setScaleX(3.63)  
            anim:setRespondable(false)  
            
            torch.glowBottom = anim
        end
        
        do
            local texture    = M3Game.TEXTURE_PATH..'/torchGlow.png'
            local objectName = 'torchAnimGlowTop'..tostring(self.torchAnimUniqueId)       

            local anim = CreateSpriteEntity(texture)
            anim:attachTo(torch.bg)
            anim:setName(objectName)
            anim:setXY(-37, -9)
            anim:setOrder(3)  
            anim:setScaleX(9.78)  
            anim:setScaleX(3.63)  
            anim:setRespondable(false)

            torch.glowTop = anim            
        end
        
        do
            local effector = CreateAnimationEffector()
            effector:setMode(_ANIM_FWD + _ANIM_LOOP)
            effector:attachTo(torch.glowTop)
            effector:setDestroysOnEnd(false)
            
            effector:setKey(_TRACK_O, 0, 0.24)
            effector:setKey(_TRACK_O, 0.04573567708333, 0.84)
            effector:setKey(_TRACK_O, 0.09147135416667, 0.61)
            effector:setKey(_TRACK_O, 0.13720703125, 0.72)
            effector:setKey(_TRACK_O, 0.18294270833333, 0.44)
            effector:setKey(_TRACK_O, 0.22867838541667, 0.74)
            effector:setKey(_TRACK_O, 0.27437337239583, 0.57)
            effector:setKey(_TRACK_O, 0.32010904947917, 0.24)
            effector:setKey(_TRACK_O, 0.3658447265625, 0.84)
            effector:setKey(_TRACK_O, 0.41158040364583, 0.61)
            effector:setKey(_TRACK_O, 0.45731608072917, 0.72)
            effector:setKey(_TRACK_O, 0.5030517578125, 0.44)
            effector:setKey(_TRACK_O, 0.54878743489583, 0.74)
            effector:setKey(_TRACK_O, 0.59452311197917, 0.57)
            effector:setKey(_TRACK_O, 0.6402587890625, 0.24)
            effector:setKey(_TRACK_O, 0.68599446614583, 0.84)
            effector:setKey(_TRACK_O, 0.731689453125, 0.61)
            effector:setKey(_TRACK_O, 0.77742513020833, 0.72)
            effector:setKey(_TRACK_O, 0.82316080729167, 0.44)
            effector:setKey(_TRACK_O, 0.868896484375, 0.74)
            effector:setKey(_TRACK_O, 0.91463216145833, 0.57)
            effector:setKey(_TRACK_O, 0.96036783854167, 0.24)
            effector:setKey(_TRACK_O, 1.006103515625, 0.84)
            effector:setKey(_TRACK_O, 1.05183919270833, 0.61)
            effector:setKey(_TRACK_O, 1.09757486979167, 0.72)
            effector:setKey(_TRACK_O, 1.143310546875, 0.44)
            effector:setKey(_TRACK_O, 1.18900553385417, 0.74)
            effector:setKey(_TRACK_O, 1.2347412109375, 0.57)
            effector:setKey(_TRACK_O, 1.28047688802083, 0.24)
            effector:setKey(_TRACK_O, 1.32621256510417, 0.84)
            effector:setKey(_TRACK_O, 1.3719482421875, 0.61)
            effector:setKey(_TRACK_O, 1.41768391927083, 0.72)
            effector:setKey(_TRACK_O, 1.46341959635417, 0.44)
            effector:setKey(_TRACK_O, 1.5091552734375, 0.74)
            effector:setKey(_TRACK_O, 1.55489095052083, 0.57)
            effector:setKey(_TRACK_O, 1.60062662760417, 0.24)
            effector:setKey(_TRACK_O, 1.64632161458333, 0.84)
            effector:setKey(_TRACK_O, 1.69205729166667, 0.61)
            effector:setKey(_TRACK_O, 1.73779296875, 0.72)
            effector:setKey(_TRACK_O, 1.78352864583333, 0.44)
            effector:setKey(_TRACK_O, 1.82926432291667, 0.74)
            effector:setKey(_TRACK_O, 1.875, 0.57)
            
            torch.eff = effector
        end
        
        torch.stop = function()
            torch.bg:setVisible(false)
            torch.fire:setVisible(false)
            torch.glowBottom:setVisible(false)
            torch.glowTop:setVisible(false)
            
            torch.fire:stop()
            torch.eff:stop()
        end
        
        torch.play = function()
            torch.bg:setVisible(true)
            torch.fire:setVisible(true)
            torch.glowBottom:setVisible(true)
            torch.glowTop:setVisible(true)
            
            torch.fire:play()
            torch.eff:play()
        end
    end
    
    torch.play()
    
    local cell = self:getCell(col, row)
    local pos  = cell:getCenterPos()
    local offsetX = 50
    local offsetY = 15
    
    torch.bg:setX(pos.x + offsetX)
    torch.bg:setY(pos.y + offsetY)
    
    local time = 1
    local eff = CreateAnimationEffector()
    eff:attachTo(torch.bg)
    eff:setDestroysOnEnd(true)
    eff:setMode(_ANIM_NOLOOP)
    eff:setKey(_TRACK_O, 0,          0)
    eff:setKey(_TRACK_O, time * 0.5, 1)
    eff:setKey(_TRACK_O, time,       0)
    eff:Play()
    
    self.torchAnims[torch] = false
    self.timer:add( function()
                        torch.stop()
                        self.torchAnims[torch] = true
                    end,
                    time)
end
-------------------------------------------------------------------------------