0,]']>Saup8d%qy)6o;]AsVl3= x and px < x2 and py >= y and py < y2 then return true end return false end function isIntersectRects(a, b) local a_x2 = a.x + a.w local a_y2 = a.y + a.h local b_x2 = b.x + b.w local b_y2 = b.y + b.h -- Y not intersects if a_y2 < b.y or b_y2 < a.y then return false end -- X not intersects if a_x2 < b.x or b_x2 < a.x then return false end return true end Timer = { new = function(self) local props = { startTime = time(), func = nil, delay = nil, isEnabled = false } self.__index = self return setmetatable(props, self) end, start = function(self, delay, func) self.isEnabled = true self.startTime = time() self.delay = delay self.func = func end, reset = function(self) self.isEnabled = true self.startTime = time() end, update = function(self) if self.isEnabled then local nowTime = time() if self.startTime + self.delay < nowTime then self.isEnabled = false self.func() end end end, } TimerStore = { new = function(self) local config = { _timeoutList = {}, _intervalList = {} } self.__index = self return setmetatable(config, self) end, delay = function(self, ms, callback) -- запускает колбэк после паузы, указанной в милисекундах -- срабатывает один раз local t = Timer:new() t:start(ms, callback) table.insert(self._timeoutList, t) -- возвращаем ID созданного таймаута return #self._timeoutList end, interval = function(self, ms, callback) -- запускает колбэк после паузы, указанной в милисекундах local t = Timer:new() t:start(ms, function() callback() t:reset() end) table.insert(self._intervalList, t) -- возвращаем ID созданного интервала return #self._intervalList end, stopInterval = function(self, intervalId) table.remove(self._intervalList, intervalId) end, update = function(self) for k,v in pairs(self._intervalList) do v:update() end for k,v in pairs(self._timeoutList) do v:update() end end } Animation = { -- Animation v1.0.1 new = function(self, frames) local struct = { frame = 0, frameIdx = 1, frames = frames or {}, state = 'stop', endAnimFunc = function() end } struct.frame = struct.frames[struct.frameIdx] self.__index = self return setmetatable(struct, self) end, play = function(self) self.state = 'play' end, stop = function(self) self.state = 'stop' end, reset = function(self) self.state = 'stop' self.frameIdx = 1 self.frame = self.frames[self.frameIdx] end, getNextFrame = function(self) if self.state == 'play' then if self.frameIdx == #self.frames then self:endAnimFunc() self.frameIdx = 1 else self.frameIdx = self.frameIdx + 1 end self.frame = self.frames[self.frameIdx] end return self.frame end } WalkEnemy = { new = function(self, x, y) local config = { x = x or 0, y = y or FLOOR_Y, vx = 0, vy = 0, speed = 0.1, dir = 0, isDead = false, curFrame = 320, state = 'idle', animWalk = Animation:new({328, 330, 332, 334}), intervalId = nil } self.__index = self return setmetatable(config, self) end, init = function(self) sfx(4, 'C-2') gameTimer:delay(350, function() self.curFrame = 322 end) gameTimer:delay(700, function() self.curFrame = 324 end) gameTimer:delay(1200, function() self.curFrame = 326 end) gameTimer:delay(2000, function() self:setState("walk") self.animWalk:play() self:updateFrame() self.intervalId = gameTimer:interval(500, function() self:updateFrame() end) end) end, setState = function(self, state) self.state = state if state == 'dead' then self.animWalk:stop() gameTimer:stopInterval(self.intervalId) self.curFrame = 352 gameTimer:delay(300, function() self.isDead = true end) end end, isDangerous = function(self) return self.state ~= 'idle' and self.state ~= 'dead' end, getRect = function(self) return { w = 11, h = 16, x = self.x + 3, y = self.y } end, goRight = function(self) self.vx = -self.speed self.dir = 1 end, goLeft = function(self) self.vx = self.speed self.dir = 0 end, move = function(self) if self.state == 'walk' then self.x = self.x + self.vx end self.y = self.y + self.vy end, updateFrame = function(self) if self.state == 'dead' then self.curFrame = 352 else self.curFrame = self.animWalk:getNextFrame() end end, getFrame = function(self) return self.curFrame end, update = function(self) self:move() spr( self:getFrame(), self.x, self.y, 0, 1, self.dir, 0, 2, 2 ) end } Player = { new = function(self) local config = { x = 240/2, y = FLOOR_Y, vx = 0, vy = 0, dir = 0, speed = 0.56, lives = 3, maxAmmo = 2, ammo = 2, state = 'idle', curFrame = 258, reloadTimer = Timer:new(), anim = { walk = Animation:new({260, 262, 264, 266}) }, walkFrame = nil } self.__index = self return setmetatable(config, self) end, init = function(self) self.reloadTimer:start(1000, function() self:reload() end) self.anim.walk:play() gameTimer:interval(250, function() self.walkFrame = self.anim.walk:getNextFrame() end) end, updateFrame = function(self) self.curFrame = self.walkFrame end, setState = function(self, state) if (self.state ~= state and state ~= 'idle' and state ~= 'reload') or (self.state == state and self.state == 'move') then self.reloadTimer:reset() end if state ~= 'move' then self.anim.walk:stop() self.anim.walk:reset() end if state == 'shot' then self.curFrame = 268 elseif state == 'idle' then self.curFrame = 258 end self.state = state end, getRect = function(self) return { w = 10, h = 16, x = self.x + 4, y = self.y } end, reload = function(self) if self.state == 'idle' or self.state == 'reload' then if self.ammo < self.maxAmmo then self:setState('reload') self.ammo = self.ammo + 1 self.curFrame = 288 sfx(2, 'C-6') else self:setState("idle") end self.reloadTimer:reset() end end, shot = function(self) if self.state ~= 'shot' and self.state ~= 'reload' then self.reloadTimer:reset() if self.ammo > 0 then self:setState('shot') sfx(0, "d-2") self.vx = 0 self.ammo = self.ammo - 1 gameTimer:delay(300, function() self:setState('idle') end) return true else sfx(3, "d-2") end end return false end, goRight = function(self) self.vx = self.speed end, goLeft = function(self) self.vx = -self.speed end, move = function(self) if self.state ~= "reload" then self.x = self.x + self.vx self.y = self.y + self.vy end if self.vx ~= 0 then self:setState('move') self.anim.walk:play() elseif self.state ~= 'shot' and self.state ~= 'reload' then self:setState('idle') end end, getFrame = function(self) if self.state == 'idle' then self.curFrame = 258 elseif self.state == 'move' then self.curFrame = self.walkFrame end return self.curFrame end, update = function(self) self.reloadTimer:update() spr( self:getFrame(), self.x, self.y, 0, 1, self.dir, 0, 2, 2 ) end } TitleScene = { new = function(self, parent) local config = { parent = parent, blinkColor = YELLOW } self.__index = self return setmetatable(config, self) end, init = function(self) gameTimer:interval(500, function() if self.blinkColor == YELLOW then self.blinkColor = GRAY else self.blinkColor = YELLOW end end) end, input = function(self) if btnp(GAMEPAD_1.A) then self.parent:setScene("RUN") end end, update = function(self) self:input() cls() print("Dangerous grave", 31, 21, DARKGRAY, false, 2) print("Dangerous grave", 30, 20, RED, false, 2) print("- press (A) to play -", 70, 120, self.blinkColor) end } WinScene = { new = function(self, parent) local config = { parent = parent, blinkColor = YELLOW } self.__index = self return setmetatable(config, self) end, init = function(self) gameTimer:interval(400, function() if self.blinkColor == YELLOW then self.blinkColor = GRAY else self.blinkColor = YELLOW end end) end, input = function(self) if btnp(GAMEPAD_1.A) then self.parent:nextLevel() end end, update = function(self) self:input() cls() print("Next night", 91, 66, DARKGRAY) print("Next night", 90, 65, GRAY) print("- press (A) to play -", 70, 120, self.blinkColor) end } GameoverScene = { new = function(self, parent) local config = { parent = parent, blinkColor = YELLOW } self.__index = self return setmetatable(config, self) end, init = function(self) gameTimer:interval(500, function() if self.blinkColor == YELLOW then self.blinkColor = GRAY else self.blinkColor = YELLOW end end) self.anim = Animation:new({243, 244, 245, 246, 247, 248, 249}) self.anim:play() self.curFrame = self.anim:getNextFrame() gameTimer:interval(300, function() self.curFrame = self.anim:getNextFrame() end) end, input = function(self) if btnp(GAMEPAD_1.A) then self.parent:reset() end end, update = function(self) self:input() cls() spr(208, 105, 80, 0, 1, 0, 0, 3, 3) spr(self.curFrame, 121, 48) print("GAME OVER", 90, 45, WHITE) print("V", 124, 45, RED) print("- press (A) to play again -", 50, 120, self.blinkColor) end } RunScene = { new = function(self, parent) config = { parent = parent, player = Player:new(), enemies = {}, zombieSpeed = 0.1, enemiesCount = 3, enemyTimer = Timer:new() } self.__index = self config.player.lives = lives or 3 return setmetatable(config, self) end, init = function(self, options) local enemyCount = 1 local enemySpeed = 1 local ammo = 2 if options ~= nil then if options.enemyCount ~= nil then self.enemiesCount = options.enemyCount end if options.enemySpeed ~= nil then self.zombieSpeed = options.enemySpeed end if options.ammo ~= nil then ammo = options.ammo end end self.player:init() self.player.ammo = ammo self.player.maxAmmo = ammo self:addEnemy() gameTimer:interval(3000, function() self:addEnemy() end) end, addEnemy = function(self) local newEnemy = function(x) local enemy = WalkEnemy:new(x, FLOOR_Y) math.randomseed(time()) enemy.speed = self.zombieSpeed enemy:init() return enemy end if self.enemiesCount > 0 then local startArea = 0 local endArea = 220 local x = math.random( math.floor(startArea), math.floor(endArea) ) table.insert(config.enemies, newEnemy(x)) self.enemiesCount = self.enemiesCount - 1 end end, input = function(self) if btn(GAMEPAD_1.LEFT) then if self.player.x <= 0 then self.player.vx = 0 else self.player:goLeft() end self.player.dir = DIR_LEFT elseif btn(GAMEPAD_1.RIGHT) then if self.player.x >= 224 then self.player.vx = 0 else self.player:goRight() end self.player.dir = DIR_RIGHT else self.player.vx = 0 end if btnp(GAMEPAD_1.A) then if self.player:shot() then local nearEnemyValue = nil local nearEnemyKey = nil for k, v in pairs(self.enemies) do if v:isDangerous() then if self.player.dir == DIR_RIGHT and v.x > self.player.x then if nearEnemyValue == nil or nearEnemyValue.x > v.x then nearEnemyValue = v nearEnemyKey = k end elseif self.player.dir == DIR_LEFT and v.x < self.player.x then if nearEnemyValue == nil or nearEnemyValue.x < v.x then nearEnemyValue = v nearEnemyKey = k end end end end if nearEnemyValue ~= nil then nearEnemyValue:setState("dead") nearEnemyValue = nil end end end end, updateUi = function(self) local sprite = 497 for i = 1, self.player.maxAmmo do if i > self.player.ammo then sprite = 496 end spr( sprite, (i-1) * 8, 0 ) end end, update = function(self) self.player:move() self:input() cls() map() self:updateUi() for k, v in pairs(self.enemies) do if v.x > self.player.x then v:goRight() elseif v.x < self.player.x then v:goLeft() end if v:isDangerous() and isIntersectRects(self.player:getRect(), v:getRect()) then self.parent:setScene("GAMEOVER") end if v.isDead then table.remove(self.enemies, k) trace(v) trace(#self.enemies) trace(self.enemiesCount) if #self.enemies == 0 and self.enemiesCount == 0 then self.parent:setScene('WIN') end end v:update() end self.player:update() end } gameTimer = TimerStore:new() Game = { new = function(self) local config = { scene = nil, enemyCount = 1, enemySpeed = 0.1, _sceneList = { RUN = RunScene, TITLE = TitleScene, WIN = WinScene, GAMEOVER = GameoverScene } } self.__index = self return setmetatable(config, self) end, setScene = function(self, sceneName, ...) self.scene = self._sceneList[sceneName]:new(self) self.scene:init(...) end, nextLevel = function(self) self:setEnemyCount(self.enemyCount + 10) self.ammo = math.floor(self.enemyCount / 15) + 2 self:setScene('RUN', { enemyCount = self.enemyCount, ammo = self.ammo }) end, reset = function(self) self:setEnemyCount(1) self.ammo = math.floor(self.enemyCount / 15) + 2 self:setScene('RUN', { enemyCount = self.enemyCount, ammo = self.ammo }) end, setEnemyCount = function(self, count) self.enemyCount = count if self.enemyCount > 10 then self.enemySpeed = 0.3 end end, update = function(self) self.scene:update() end } theGame = Game:new() theGame:setScene('TITLE') function TIC() theGame:update() gameTimer:update() end