refact
This commit is contained in:
621
bomberman.lua
621
bomberman.lua
@@ -3,37 +3,69 @@
|
|||||||
-- desc: Simple Bomberman clone for TIC-80
|
-- desc: Simple Bomberman clone for TIC-80
|
||||||
-- site: http://teletype.hu
|
-- site: http://teletype.hu
|
||||||
-- license: MIT License
|
-- license: MIT License
|
||||||
-- version: 0.1
|
-- version: 0.2
|
||||||
-- script: lua
|
-- script: lua
|
||||||
|
|
||||||
-- luacheck: globals TIC btn btnp cls rect spr print exit sfx keyp
|
-- luacheck: globals TIC btn btnp cls rect spr print exit sfx keyp
|
||||||
-- luacheck: max line length 150
|
-- luacheck: max line length 150
|
||||||
|
|
||||||
-- constants
|
--------------------------------------------------------------------------------
|
||||||
local TILE_SIZE = 16
|
-- Constants
|
||||||
local BOMB_TIMER = 90
|
--------------------------------------------------------------------------------
|
||||||
local EXPLOSION_TIMER = 30
|
|
||||||
local SPREAD_DELAY = 6 -- ticks per cell spread (0.1 sec at 60fps)
|
|
||||||
|
|
||||||
|
-- Tile constants
|
||||||
|
local TILE_SIZE = 16
|
||||||
|
local MAP_WIDTH = 15
|
||||||
|
local MAP_HEIGHT = 9
|
||||||
|
|
||||||
|
-- Tile types
|
||||||
local EMPTY = 0
|
local EMPTY = 0
|
||||||
local SOLID_WALL = 1
|
local SOLID_WALL = 1
|
||||||
local BREAKABLE_WALL = 2
|
local BREAKABLE_WALL = 2
|
||||||
|
|
||||||
|
-- Timing constants
|
||||||
|
local BOMB_TIMER = 90
|
||||||
|
local EXPLOSION_TIMER = 30
|
||||||
|
local SPREAD_DELAY = 6 -- ticks per cell spread
|
||||||
|
local SPLASH_DURATION = 90 -- 1.5 seconds at 60fps
|
||||||
|
local WIN_SCREEN_DURATION = 60
|
||||||
|
local AI_MOVE_DELAY = 20
|
||||||
|
local AI_BOMB_COOLDOWN = 90
|
||||||
|
|
||||||
|
-- Movement
|
||||||
local MOVE_SPEED = 2
|
local MOVE_SPEED = 2
|
||||||
|
|
||||||
-- sprite indices (in SPRITES section, starts at 256)
|
-- Sprite indices (in SPRITES section, starts at 256)
|
||||||
local ASTRONAUT_BLUE = 256
|
local ASTRONAUT_BLUE = 256
|
||||||
local ASTRONAUT_RED = 257
|
local ASTRONAUT_RED = 257
|
||||||
local BOMB_SPRITE = 258
|
local BOMB_SPRITE = 258
|
||||||
local BREAKABLE_WALL_SPRITE = 259
|
local BREAKABLE_WALL_SPRITE = 259
|
||||||
local SOLID_WALL_SPRITE = 260
|
local SOLID_WALL_SPRITE = 260
|
||||||
|
|
||||||
-- game state constants
|
-- Colors
|
||||||
|
local COLOR_BLACK = 0
|
||||||
|
local COLOR_SHADOW = 1
|
||||||
|
local COLOR_RED = 2
|
||||||
|
local COLOR_ORANGE = 3
|
||||||
|
local COLOR_YELLOW = 4
|
||||||
|
local COLOR_GREEN = 6
|
||||||
|
local COLOR_GREEN_LIGHT = 11
|
||||||
|
local COLOR_BLUE = 12
|
||||||
|
local COLOR_WHITE = 15
|
||||||
|
|
||||||
|
-- Game states
|
||||||
local GAME_STATE_SPLASH = 0
|
local GAME_STATE_SPLASH = 0
|
||||||
local GAME_STATE_MENU = 1
|
local GAME_STATE_MENU = 1
|
||||||
local GAME_STATE_PLAYING = 2
|
local GAME_STATE_PLAYING = 2
|
||||||
|
|
||||||
-- modules
|
-- Powerup spawn chance
|
||||||
|
local POWERUP_SPAWN_CHANCE = 0.3
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Modules
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local Input = {}
|
||||||
local Map = {}
|
local Map = {}
|
||||||
local UI = {}
|
local UI = {}
|
||||||
local Bomb = {}
|
local Bomb = {}
|
||||||
@@ -41,65 +73,73 @@ local AI = {}
|
|||||||
local Player = {}
|
local Player = {}
|
||||||
local Game = {}
|
local Game = {}
|
||||||
|
|
||||||
-- game state variables
|
|
||||||
local game_state = GAME_STATE_SPLASH
|
|
||||||
local splash_timer = 90 -- 1.5 seconds at 60fps
|
|
||||||
local menu_selection = 1 -- 1 = Play, 2 = Exit
|
|
||||||
|
|
||||||
local players = {}
|
|
||||||
local powerups = {}
|
|
||||||
local bombs = {}
|
|
||||||
local explosions = {}
|
|
||||||
local winner = nil
|
|
||||||
local win_timer = 0
|
|
||||||
local score = {0, 0}
|
|
||||||
|
|
||||||
-- map (1=solid wall, 2=breakable wall)
|
|
||||||
local map = {
|
|
||||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
|
|
||||||
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
|
||||||
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
|
||||||
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
|
||||||
{1,2,1,0,1,0,1,0,1,0,1,0,1,2,1},
|
|
||||||
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
|
||||||
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
|
||||||
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
|
||||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- initial map for reset
|
|
||||||
local initial_map = {
|
|
||||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
|
|
||||||
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
|
||||||
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
|
||||||
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
|
||||||
{1,2,1,0,1,0,1,0,1,0,1,0,1,2,1},
|
|
||||||
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
|
||||||
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
|
||||||
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
|
||||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
|
|
||||||
}
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Map module
|
-- Game State
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function Map.can_move_to(gridX, gridY)
|
local State = {
|
||||||
if gridX < 1 or gridY < 1 or gridX > 15 or gridY > 9 then
|
game_state = GAME_STATE_SPLASH,
|
||||||
return false
|
splash_timer = SPLASH_DURATION,
|
||||||
|
menu_selection = 1,
|
||||||
|
players = {},
|
||||||
|
powerups = {},
|
||||||
|
bombs = {},
|
||||||
|
explosions = {},
|
||||||
|
winner = nil,
|
||||||
|
win_timer = 0,
|
||||||
|
score = {0, 0},
|
||||||
|
map = {},
|
||||||
|
initial_map = {
|
||||||
|
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
|
||||||
|
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
||||||
|
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
||||||
|
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
||||||
|
{1,2,1,0,1,0,1,0,1,0,1,0,1,2,1},
|
||||||
|
{1,2,2,2,0,2,2,0,2,2,0,2,2,2,1},
|
||||||
|
{1,0,1,2,1,2,1,2,1,2,1,2,1,0,1},
|
||||||
|
{1,0,0,2,2,2,0,2,0,2,2,2,0,0,1},
|
||||||
|
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Copy initial map to working map
|
||||||
|
for row = 1, MAP_HEIGHT do
|
||||||
|
State.map[row] = {}
|
||||||
|
for col = 1, MAP_WIDTH do
|
||||||
|
State.map[row][col] = State.initial_map[row][col]
|
||||||
end
|
end
|
||||||
if map[gridY][gridX] >= SOLID_WALL then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- powerup type definitions (extensible)
|
--------------------------------------------------------------------------------
|
||||||
|
-- Powerup System (extensible)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
local POWERUP_TYPES = {
|
local POWERUP_TYPES = {
|
||||||
{type = "bomb", weight = 50}, -- extra bomb
|
{
|
||||||
{type = "power", weight = 50}, -- explosion range +1
|
type = "bomb",
|
||||||
|
weight = 50,
|
||||||
|
color = COLOR_YELLOW,
|
||||||
|
label = "B",
|
||||||
|
apply = function(player) player.maxBombs = player.maxBombs + 1 end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "power",
|
||||||
|
weight = 50,
|
||||||
|
color = COLOR_ORANGE,
|
||||||
|
label = "P",
|
||||||
|
apply = function(player) player.bombPower = player.bombPower + 1 end
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function get_powerup_config(type_name)
|
||||||
|
for _, p in ipairs(POWERUP_TYPES) do
|
||||||
|
if p.type == type_name then
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return POWERUP_TYPES[1]
|
||||||
|
end
|
||||||
|
|
||||||
local function get_random_powerup_type()
|
local function get_random_powerup_type()
|
||||||
local total_weight = 0
|
local total_weight = 0
|
||||||
for _, p in ipairs(POWERUP_TYPES) do
|
for _, p in ipairs(POWERUP_TYPES) do
|
||||||
@@ -116,24 +156,78 @@ local function get_random_powerup_type()
|
|||||||
return POWERUP_TYPES[1].type
|
return POWERUP_TYPES[1].type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Input module
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Input.action_pressed()
|
||||||
|
return btnp(4) or keyp(48) -- A button or Space
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.up()
|
||||||
|
return btn(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.down()
|
||||||
|
return btn(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.left()
|
||||||
|
return btn(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.right()
|
||||||
|
return btn(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.up_pressed()
|
||||||
|
return btnp(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Input.down_pressed()
|
||||||
|
return btnp(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Map module
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function Map.can_move_to(gridX, gridY)
|
||||||
|
if gridX < 1 or gridY < 1 or gridX > MAP_WIDTH or gridY > MAP_HEIGHT then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if State.map[gridY][gridX] >= SOLID_WALL then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function Map.init_powerups()
|
function Map.init_powerups()
|
||||||
powerups = {}
|
State.powerups = {}
|
||||||
for row = 1, 9 do
|
for row = 1, MAP_HEIGHT do
|
||||||
for col = 1, 15 do
|
for col = 1, MAP_WIDTH do
|
||||||
if map[row][col] == BREAKABLE_WALL and math.random() < 0.3 then
|
if State.map[row][col] == BREAKABLE_WALL and math.random() < POWERUP_SPAWN_CHANCE then
|
||||||
table.insert(powerups, {gridX = col, gridY = row, type = get_random_powerup_type()})
|
table.insert(State.powerups, {gridX = col, gridY = row, type = get_random_powerup_type()})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Map.reset()
|
||||||
|
for row = 1, MAP_HEIGHT do
|
||||||
|
for col = 1, MAP_WIDTH do
|
||||||
|
State.map[row][col] = State.initial_map[row][col]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- UI module
|
-- UI module
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function UI.print_shadow(text, x, y, color, fixed, scale)
|
function UI.print_shadow(text, x, y, color, fixed, scale)
|
||||||
scale = scale or 1
|
scale = scale or 1
|
||||||
print(text, x + 1, y + 1, 1, fixed, scale)
|
print(text, x + 1, y + 1, COLOR_SHADOW, fixed, scale)
|
||||||
print(text, x, y, color, fixed, scale)
|
print(text, x, y, color, fixed, scale)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -147,48 +241,46 @@ function UI.draw_bomb_sprite(x, y)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.draw_win_screen()
|
function UI.draw_win_screen()
|
||||||
cls(0)
|
cls(COLOR_BLACK)
|
||||||
rect(20, 30, 200, 80, 12)
|
rect(20, 30, 200, 80, COLOR_BLUE)
|
||||||
rect(22, 32, 196, 76, 0)
|
rect(22, 32, 196, 76, COLOR_BLACK)
|
||||||
UI.print_shadow("PLAYER "..winner.." WON!", 70, 55, 12, false, 2)
|
UI.print_shadow("PLAYER "..State.winner.." WON!", 70, 55, COLOR_BLUE, false, 2)
|
||||||
if win_timer <= 0 or math.floor(win_timer / 15) % 2 == 0 then
|
if State.win_timer <= 0 or math.floor(State.win_timer / 15) % 2 == 0 then
|
||||||
UI.print_shadow("Press A to restart", 70, 80, 12)
|
UI.print_shadow("Press A to restart", 70, 80, COLOR_BLUE)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.draw_game()
|
function UI.draw_game()
|
||||||
-- draw wall shadows first
|
-- draw wall shadows first
|
||||||
for row = 1, 9 do
|
for row = 1, MAP_HEIGHT do
|
||||||
for col = 1, 15 do
|
for col = 1, MAP_WIDTH do
|
||||||
local tile = map[row][col]
|
local tile = State.map[row][col]
|
||||||
if tile == SOLID_WALL or tile == BREAKABLE_WALL then
|
if tile == SOLID_WALL or tile == BREAKABLE_WALL then
|
||||||
local drawX = (col - 1) * TILE_SIZE
|
local drawX = (col - 1) * TILE_SIZE
|
||||||
local drawY = (row - 1) * TILE_SIZE
|
local drawY = (row - 1) * TILE_SIZE
|
||||||
rect(drawX + 2, drawY + 2, TILE_SIZE, TILE_SIZE, 1)
|
rect(drawX + 2, drawY + 2, TILE_SIZE, TILE_SIZE, COLOR_SHADOW)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw explosions (after shadows, before walls)
|
-- draw explosions (after shadows, before walls)
|
||||||
for _, expl in ipairs(explosions) do
|
for _, expl in ipairs(State.explosions) do
|
||||||
if expl.spread <= 0 then
|
if expl.spread <= 0 then
|
||||||
-- fully spread - draw full cell
|
rect(expl.x, expl.y, TILE_SIZE, TILE_SIZE, COLOR_RED)
|
||||||
rect(expl.x, expl.y, TILE_SIZE, TILE_SIZE, 2) -- red
|
|
||||||
else
|
else
|
||||||
-- still spreading - draw partial from center
|
|
||||||
local progress = 1 - (expl.spread / (expl.dist * SPREAD_DELAY))
|
local progress = 1 - (expl.spread / (expl.dist * SPREAD_DELAY))
|
||||||
if progress > 0 then
|
if progress > 0 then
|
||||||
local size = math.floor(TILE_SIZE * progress)
|
local size = math.floor(TILE_SIZE * progress)
|
||||||
local offset = math.floor((TILE_SIZE - size) / 2)
|
local offset = math.floor((TILE_SIZE - size) / 2)
|
||||||
rect(expl.x + offset, expl.y + offset, size, size, 2)
|
rect(expl.x + offset, expl.y + offset, size, size, COLOR_RED)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw map
|
-- draw map
|
||||||
for row = 1, 9 do
|
for row = 1, MAP_HEIGHT do
|
||||||
for col = 1, 15 do
|
for col = 1, MAP_WIDTH do
|
||||||
local tile = map[row][col]
|
local tile = State.map[row][col]
|
||||||
local drawX = (col - 1) * TILE_SIZE
|
local drawX = (col - 1) * TILE_SIZE
|
||||||
local drawY = (row - 1) * TILE_SIZE
|
local drawY = (row - 1) * TILE_SIZE
|
||||||
if tile == SOLID_WALL then
|
if tile == SOLID_WALL then
|
||||||
@@ -200,82 +292,74 @@ function UI.draw_game()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- draw powerups
|
-- draw powerups
|
||||||
for _, pw in ipairs(powerups) do
|
for _, pw in ipairs(State.powerups) do
|
||||||
if map[pw.gridY][pw.gridX] == EMPTY then
|
if State.map[pw.gridY][pw.gridX] == EMPTY then
|
||||||
local drawX = (pw.gridX - 1) * TILE_SIZE
|
local drawX = (pw.gridX - 1) * TILE_SIZE
|
||||||
local drawY = (pw.gridY - 1) * TILE_SIZE
|
local drawY = (pw.gridY - 1) * TILE_SIZE
|
||||||
rect(drawX + 5, drawY + 5, 10, 10, 1) -- shadow
|
local config = get_powerup_config(pw.type)
|
||||||
if pw.type == "bomb" then
|
rect(drawX + 5, drawY + 5, 10, 10, COLOR_SHADOW)
|
||||||
rect(drawX + 3, drawY + 3, 10, 10, 4) -- yellow background
|
rect(drawX + 3, drawY + 3, 10, 10, config.color)
|
||||||
print("B", drawX + 5, drawY + 5, 0)
|
print(config.label, drawX + 5, drawY + 5, COLOR_BLACK)
|
||||||
elseif pw.type == "power" then
|
|
||||||
rect(drawX + 3, drawY + 3, 10, 10, 3) -- orange background
|
|
||||||
print("P", drawX + 5, drawY + 5, 0)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw bombs
|
-- draw bombs
|
||||||
for _, bomb in ipairs(bombs) do
|
for _, bomb in ipairs(State.bombs) do
|
||||||
UI.draw_bomb_sprite(bomb.x, bomb.y)
|
UI.draw_bomb_sprite(bomb.x, bomb.y)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw players
|
-- draw players
|
||||||
for idx, player in ipairs(players) do
|
for idx, player in ipairs(State.players) do
|
||||||
UI.draw_player_sprite(player.pixelX, player.pixelY, idx == 1)
|
UI.draw_player_sprite(player.pixelX, player.pixelY, idx == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- score display
|
-- score display
|
||||||
print(score[1]..":"..score[2], 5, 2, 12)
|
print(State.score[1]..":"..State.score[2], 5, 2, COLOR_BLUE)
|
||||||
print("ARROWS:MOVE SPACE:BOMB", 50, 2, 15)
|
print("ARROWS:MOVE SPACE:BOMB", 50, 2, COLOR_WHITE)
|
||||||
local human = players[1]
|
local human = State.players[1]
|
||||||
local available = human.maxBombs - human.activeBombs
|
if human then
|
||||||
print("BOMBS:"..available.."/"..human.maxBombs, 180, 2, 11)
|
local available = human.maxBombs - human.activeBombs
|
||||||
|
print("BOMBS:"..available.."/"..human.maxBombs, 180, 2, COLOR_GREEN_LIGHT)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.update_splash()
|
function UI.update_splash()
|
||||||
cls(0)
|
cls(COLOR_BLACK)
|
||||||
|
|
||||||
-- title with line break
|
UI.print_shadow("Bomberman", 85, 50, COLOR_BLUE, false, 2)
|
||||||
UI.print_shadow("Bomberman", 85, 50, 12, false, 2)
|
UI.print_shadow("Clone", 100, 70, COLOR_BLUE, false, 2)
|
||||||
UI.print_shadow("Clone", 100, 70, 12, false, 2)
|
|
||||||
|
|
||||||
splash_timer = splash_timer - 1
|
State.splash_timer = State.splash_timer - 1
|
||||||
if splash_timer <= 0 then
|
if State.splash_timer <= 0 then
|
||||||
game_state = GAME_STATE_MENU
|
State.game_state = GAME_STATE_MENU
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.update_menu()
|
function UI.update_menu()
|
||||||
cls(0)
|
cls(COLOR_BLACK)
|
||||||
|
|
||||||
-- title
|
UI.print_shadow("Bomberman", 85, 30, COLOR_BLUE, false, 2)
|
||||||
UI.print_shadow("Bomberman", 85, 30, 12, false, 2)
|
UI.print_shadow("Clone", 100, 50, COLOR_BLUE, false, 2)
|
||||||
UI.print_shadow("Clone", 100, 50, 12, false, 2)
|
|
||||||
|
|
||||||
-- menu options
|
local play_color = (State.menu_selection == 1) and COLOR_GREEN_LIGHT or COLOR_WHITE
|
||||||
local play_color = (menu_selection == 1) and 11 or 15
|
local exit_color = (State.menu_selection == 2) and COLOR_GREEN_LIGHT or COLOR_WHITE
|
||||||
local exit_color = (menu_selection == 2) and 11 or 15
|
|
||||||
|
|
||||||
-- selection indicator
|
if State.menu_selection == 1 then
|
||||||
if menu_selection == 1 then
|
UI.print_shadow(">", 85, 90, COLOR_GREEN_LIGHT)
|
||||||
UI.print_shadow(">", 85, 90, 11)
|
|
||||||
else
|
else
|
||||||
UI.print_shadow(">", 85, 110, 11)
|
UI.print_shadow(">", 85, 110, COLOR_GREEN_LIGHT)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- menu items
|
|
||||||
UI.print_shadow("Play", 95, 90, play_color)
|
UI.print_shadow("Play", 95, 90, play_color)
|
||||||
UI.print_shadow("Exit", 95, 110, exit_color)
|
UI.print_shadow("Exit", 95, 110, exit_color)
|
||||||
|
|
||||||
-- handle input
|
if Input.up_pressed() then
|
||||||
if btnp(0) then -- up
|
State.menu_selection = 1
|
||||||
menu_selection = 1
|
elseif Input.down_pressed() then
|
||||||
elseif btnp(1) then -- down
|
State.menu_selection = 2
|
||||||
menu_selection = 2
|
elseif Input.action_pressed() then
|
||||||
elseif btnp(4) or keyp(48) then -- A button or Space (select)
|
if State.menu_selection == 1 then
|
||||||
if menu_selection == 1 then
|
State.game_state = GAME_STATE_PLAYING
|
||||||
game_state = GAME_STATE_PLAYING
|
|
||||||
Game.init()
|
Game.init()
|
||||||
else
|
else
|
||||||
exit()
|
exit()
|
||||||
@@ -284,7 +368,7 @@ function UI.update_menu()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Bomb module
|
-- Bomb module (includes explosions)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function Bomb.place(player)
|
function Bomb.place(player)
|
||||||
@@ -293,20 +377,20 @@ function Bomb.place(player)
|
|||||||
local bombX = (player.gridX - 1) * TILE_SIZE
|
local bombX = (player.gridX - 1) * TILE_SIZE
|
||||||
local bombY = (player.gridY - 1) * TILE_SIZE
|
local bombY = (player.gridY - 1) * TILE_SIZE
|
||||||
|
|
||||||
for _, b in ipairs(bombs) do
|
for _, b in ipairs(State.bombs) do
|
||||||
if b.x == bombX and b.y == bombY then
|
if b.x == bombX and b.y == bombY then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(bombs, {x = bombX, y = bombY, timer = BOMB_TIMER, owner = player, power = player.bombPower})
|
table.insert(State.bombs, {x = bombX, y = bombY, timer = BOMB_TIMER, owner = player, power = player.bombPower})
|
||||||
player.activeBombs = player.activeBombs + 1
|
player.activeBombs = player.activeBombs + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function Bomb.explode(bombX, bombY, power)
|
function Bomb.explode(bombX, bombY, power)
|
||||||
power = power or 1
|
power = power or 1
|
||||||
sfx(0, nil, 30) -- explosion sound, 30 ticks = 0.5 sec
|
sfx(0, nil, 30)
|
||||||
table.insert(explosions, {x = bombX, y = bombY, timer = EXPLOSION_TIMER, dist = 0, spread = 0})
|
table.insert(State.explosions, {x = bombX, y = bombY, timer = EXPLOSION_TIMER, dist = 0, spread = 0})
|
||||||
|
|
||||||
local gridX = math.floor(bombX / TILE_SIZE) + 1
|
local gridX = math.floor(bombX / TILE_SIZE) + 1
|
||||||
local gridY = math.floor(bombY / TILE_SIZE) + 1
|
local gridY = math.floor(bombY / TILE_SIZE) + 1
|
||||||
@@ -316,15 +400,15 @@ function Bomb.explode(bombX, bombY, power)
|
|||||||
for dist = 1, power do
|
for dist = 1, power do
|
||||||
local explX = bombX + dir * dist * TILE_SIZE
|
local explX = bombX + dir * dist * TILE_SIZE
|
||||||
local eGridX = gridX + dir * dist
|
local eGridX = gridX + dir * dist
|
||||||
if eGridX < 1 or eGridX > 15 then break end
|
if eGridX < 1 or eGridX > MAP_WIDTH then break end
|
||||||
local tile = map[gridY][eGridX]
|
local tile = State.map[gridY][eGridX]
|
||||||
if tile == SOLID_WALL then break end
|
if tile == SOLID_WALL then break end
|
||||||
if tile == BREAKABLE_WALL then
|
if tile == BREAKABLE_WALL then
|
||||||
map[gridY][eGridX] = EMPTY
|
State.map[gridY][eGridX] = EMPTY
|
||||||
table.insert(explosions, {x = explX, y = bombY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
table.insert(State.explosions, {x = explX, y = bombY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
||||||
break -- stop at breakable wall
|
break
|
||||||
end
|
end
|
||||||
table.insert(explosions, {x = explX, y = bombY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
table.insert(State.explosions, {x = explX, y = bombY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -333,25 +417,58 @@ function Bomb.explode(bombX, bombY, power)
|
|||||||
for dist = 1, power do
|
for dist = 1, power do
|
||||||
local explY = bombY + dir * dist * TILE_SIZE
|
local explY = bombY + dir * dist * TILE_SIZE
|
||||||
local eGridY = gridY + dir * dist
|
local eGridY = gridY + dir * dist
|
||||||
if eGridY < 1 or eGridY > 9 then break end
|
if eGridY < 1 or eGridY > MAP_HEIGHT then break end
|
||||||
local tile = map[eGridY][gridX]
|
local tile = State.map[eGridY][gridX]
|
||||||
if tile == SOLID_WALL then break end
|
if tile == SOLID_WALL then break end
|
||||||
if tile == BREAKABLE_WALL then
|
if tile == BREAKABLE_WALL then
|
||||||
map[eGridY][gridX] = EMPTY
|
State.map[eGridY][gridX] = EMPTY
|
||||||
table.insert(explosions, {x = bombX, y = explY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
table.insert(State.explosions, {x = bombX, y = explY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
||||||
break -- stop at breakable wall
|
break
|
||||||
end
|
end
|
||||||
table.insert(explosions, {x = bombX, y = explY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
table.insert(State.explosions, {x = bombX, y = explY, timer = EXPLOSION_TIMER, dist = dist, spread = dist * SPREAD_DELAY})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Bomb.update_all()
|
||||||
|
-- update bombs
|
||||||
|
for i = #State.bombs, 1, -1 do
|
||||||
|
local bomb = State.bombs[i]
|
||||||
|
bomb.timer = bomb.timer - 1
|
||||||
|
if bomb.timer <= 0 then
|
||||||
|
Bomb.explode(bomb.x, bomb.y, bomb.power)
|
||||||
|
if bomb.owner then
|
||||||
|
bomb.owner.activeBombs = bomb.owner.activeBombs - 1
|
||||||
|
end
|
||||||
|
table.remove(State.bombs, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update explosions
|
||||||
|
for i = #State.explosions, 1, -1 do
|
||||||
|
local expl = State.explosions[i]
|
||||||
|
if expl.spread > 0 then
|
||||||
|
expl.spread = expl.spread - 1
|
||||||
|
else
|
||||||
|
expl.timer = expl.timer - 1
|
||||||
|
if expl.timer <= 0 then
|
||||||
|
table.remove(State.explosions, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Bomb.clear_all()
|
||||||
|
State.bombs = {}
|
||||||
|
State.explosions = {}
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- AI module
|
-- AI module
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function AI.is_dangerous(gridX, gridY)
|
function AI.is_dangerous(gridX, gridY)
|
||||||
for _, expl in ipairs(explosions) do
|
for _, expl in ipairs(State.explosions) do
|
||||||
local explGridX = math.floor(expl.x / TILE_SIZE) + 1
|
local explGridX = math.floor(expl.x / TILE_SIZE) + 1
|
||||||
local explGridY = math.floor(expl.y / TILE_SIZE) + 1
|
local explGridY = math.floor(expl.y / TILE_SIZE) + 1
|
||||||
if gridX == explGridX and gridY == explGridY then
|
if gridX == explGridX and gridY == explGridY then
|
||||||
@@ -359,7 +476,7 @@ function AI.is_dangerous(gridX, gridY)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, bomb in ipairs(bombs) do
|
for _, bomb in ipairs(State.bombs) do
|
||||||
local bombGridX = math.floor(bomb.x / TILE_SIZE) + 1
|
local bombGridX = math.floor(bomb.x / TILE_SIZE) + 1
|
||||||
local bombGridY = math.floor(bomb.y / TILE_SIZE) + 1
|
local bombGridY = math.floor(bomb.y / TILE_SIZE) + 1
|
||||||
|
|
||||||
@@ -370,9 +487,9 @@ function AI.is_dangerous(gridX, gridY)
|
|||||||
if gridY == bombGridY then
|
if gridY == bombGridY then
|
||||||
if math.abs(gridX - bombGridX) <= 1 then
|
if math.abs(gridX - bombGridX) <= 1 then
|
||||||
if gridX < bombGridX then
|
if gridX < bombGridX then
|
||||||
if map[gridY][gridX + 1] ~= SOLID_WALL then return true end
|
if State.map[gridY][gridX + 1] ~= SOLID_WALL then return true end
|
||||||
elseif gridX > bombGridX then
|
elseif gridX > bombGridX then
|
||||||
if map[gridY][gridX - 1] ~= SOLID_WALL then return true end
|
if State.map[gridY][gridX - 1] ~= SOLID_WALL then return true end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -380,9 +497,9 @@ function AI.is_dangerous(gridX, gridY)
|
|||||||
if gridX == bombGridX then
|
if gridX == bombGridX then
|
||||||
if math.abs(gridY - bombGridY) <= 1 then
|
if math.abs(gridY - bombGridY) <= 1 then
|
||||||
if gridY < bombGridY then
|
if gridY < bombGridY then
|
||||||
if map[gridY + 1][gridX] ~= SOLID_WALL then return true end
|
if State.map[gridY + 1][gridX] ~= SOLID_WALL then return true end
|
||||||
elseif gridY > bombGridY then
|
elseif gridY > bombGridY then
|
||||||
if map[gridY - 1][gridX] ~= SOLID_WALL then return true end
|
if State.map[gridY - 1][gridX] ~= SOLID_WALL then return true end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -398,17 +515,17 @@ function AI.in_blast_zone(gridX, gridY, bombGridX, bombGridY)
|
|||||||
|
|
||||||
if gridY == bombGridY and math.abs(gridX - bombGridX) <= 1 then
|
if gridY == bombGridY and math.abs(gridX - bombGridX) <= 1 then
|
||||||
if gridX < bombGridX then
|
if gridX < bombGridX then
|
||||||
return map[gridY][gridX + 1] ~= SOLID_WALL
|
return State.map[gridY][gridX + 1] ~= SOLID_WALL
|
||||||
elseif gridX > bombGridX then
|
elseif gridX > bombGridX then
|
||||||
return map[gridY][gridX - 1] ~= SOLID_WALL
|
return State.map[gridY][gridX - 1] ~= SOLID_WALL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if gridX == bombGridX and math.abs(gridY - bombGridY) <= 1 then
|
if gridX == bombGridX and math.abs(gridY - bombGridY) <= 1 then
|
||||||
if gridY < bombGridY then
|
if gridY < bombGridY then
|
||||||
return map[gridY + 1][gridX] ~= SOLID_WALL
|
return State.map[gridY + 1][gridX] ~= SOLID_WALL
|
||||||
elseif gridY > bombGridY then
|
elseif gridY > bombGridY then
|
||||||
return map[gridY - 1][gridX] ~= SOLID_WALL
|
return State.map[gridY - 1][gridX] ~= SOLID_WALL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -420,8 +537,8 @@ function AI.has_adjacent_breakable_wall(gridX, gridY)
|
|||||||
for _, dir in ipairs(dirs) do
|
for _, dir in ipairs(dirs) do
|
||||||
local checkX = gridX + dir[1]
|
local checkX = gridX + dir[1]
|
||||||
local checkY = gridY + dir[2]
|
local checkY = gridY + dir[2]
|
||||||
if checkX >= 1 and checkX <= 15 and checkY >= 1 and checkY <= 9 then
|
if checkX >= 1 and checkX <= MAP_WIDTH and checkY >= 1 and checkY <= MAP_HEIGHT then
|
||||||
if map[checkY][checkX] == BREAKABLE_WALL then
|
if State.map[checkY][checkX] == BREAKABLE_WALL then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -492,12 +609,11 @@ function AI.escape_from_bomb(player)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function AI.move_and_bomb(player)
|
function AI.move_and_bomb(player, target)
|
||||||
local human = players[1]
|
if not target then return end
|
||||||
if not human then return end
|
|
||||||
|
|
||||||
local dx = human.gridX - player.gridX
|
local dx = target.gridX - player.gridX
|
||||||
local dy = human.gridY - player.gridY
|
local dy = target.gridY - player.gridY
|
||||||
local dist = math.abs(dx) + math.abs(dy)
|
local dist = math.abs(dx) + math.abs(dy)
|
||||||
|
|
||||||
local should_bomb = false
|
local should_bomb = false
|
||||||
@@ -509,7 +625,7 @@ function AI.move_and_bomb(player)
|
|||||||
if should_bomb and player.activeBombs < player.maxBombs and player.bombCooldown <= 0 then
|
if should_bomb and player.activeBombs < player.maxBombs and player.bombCooldown <= 0 then
|
||||||
if AI.has_escape_route(player.gridX, player.gridY) then
|
if AI.has_escape_route(player.gridX, player.gridY) then
|
||||||
Bomb.place(player)
|
Bomb.place(player)
|
||||||
player.bombCooldown = 90
|
player.bombCooldown = AI_BOMB_COOLDOWN
|
||||||
AI.escape_from_bomb(player)
|
AI.escape_from_bomb(player)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -539,7 +655,7 @@ function AI.move_and_bomb(player)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function AI.update(player)
|
function AI.update(player, target)
|
||||||
if player.moving then return end
|
if player.moving then return end
|
||||||
|
|
||||||
local in_danger = AI.is_dangerous(player.gridX, player.gridY)
|
local in_danger = AI.is_dangerous(player.gridX, player.gridY)
|
||||||
@@ -572,10 +688,10 @@ function AI.update(player)
|
|||||||
end
|
end
|
||||||
|
|
||||||
player.moveTimer = player.moveTimer + 1
|
player.moveTimer = player.moveTimer + 1
|
||||||
if player.moveTimer < 20 then return end
|
if player.moveTimer < AI_MOVE_DELAY then return end
|
||||||
|
|
||||||
player.moveTimer = 0
|
player.moveTimer = 0
|
||||||
AI.move_and_bomb(player)
|
AI.move_and_bomb(player, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@@ -591,7 +707,7 @@ function Player.create(gridX, gridY, color, is_ai)
|
|||||||
moving = false,
|
moving = false,
|
||||||
maxBombs = 1,
|
maxBombs = 1,
|
||||||
activeBombs = 0,
|
activeBombs = 0,
|
||||||
bombPower = 1, -- explosion range
|
bombPower = 1,
|
||||||
color = color,
|
color = color,
|
||||||
is_ai = is_ai,
|
is_ai = is_ai,
|
||||||
moveTimer = 0,
|
moveTimer = 0,
|
||||||
@@ -632,13 +748,13 @@ function Player.handle_input(player)
|
|||||||
local newGridX = player.gridX
|
local newGridX = player.gridX
|
||||||
local newGridY = player.gridY
|
local newGridY = player.gridY
|
||||||
|
|
||||||
if btn(0) then
|
if Input.up() then
|
||||||
newGridY = player.gridY - 1
|
newGridY = player.gridY - 1
|
||||||
elseif btn(1) then
|
elseif Input.down() then
|
||||||
newGridY = player.gridY + 1
|
newGridY = player.gridY + 1
|
||||||
elseif btn(2) then
|
elseif Input.left() then
|
||||||
newGridX = player.gridX - 1
|
newGridX = player.gridX - 1
|
||||||
elseif btn(3) then
|
elseif Input.right() then
|
||||||
newGridX = player.gridX + 1
|
newGridX = player.gridX + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -647,7 +763,7 @@ function Player.handle_input(player)
|
|||||||
player.gridY = newGridY
|
player.gridY = newGridY
|
||||||
end
|
end
|
||||||
|
|
||||||
if btnp(4) or keyp(48) then -- A button or Space
|
if Input.action_pressed() then
|
||||||
Bomb.place(player)
|
Bomb.place(player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -669,139 +785,118 @@ end
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function Game.init()
|
function Game.init()
|
||||||
players = {}
|
State.players = {}
|
||||||
table.insert(players, Player.create(2, 2, 12, false)) -- human player (blue)
|
table.insert(State.players, Player.create(2, 2, COLOR_BLUE, false))
|
||||||
table.insert(players, Player.create(14, 8, 2, true)) -- AI enemy (red)
|
table.insert(State.players, Player.create(14, 8, COLOR_RED, true))
|
||||||
Map.init_powerups()
|
Map.init_powerups()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Game.set_winner(player_num)
|
function Game.set_winner(player_num)
|
||||||
winner = player_num
|
State.winner = player_num
|
||||||
win_timer = 60
|
State.win_timer = WIN_SCREEN_DURATION
|
||||||
score[player_num] = score[player_num] + 1
|
State.score[player_num] = State.score[player_num] + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function Game.restart()
|
function Game.restart()
|
||||||
winner = nil
|
State.winner = nil
|
||||||
win_timer = 0
|
State.win_timer = 0
|
||||||
bombs = {}
|
Bomb.clear_all()
|
||||||
explosions = {}
|
Map.reset()
|
||||||
|
|
||||||
-- reset map from initial
|
for _, p in ipairs(State.players) do
|
||||||
for row = 1, 9 do
|
|
||||||
for col = 1, 15 do
|
|
||||||
map[row][col] = initial_map[row][col]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, p in ipairs(players) do
|
|
||||||
Player.reset(p)
|
Player.reset(p)
|
||||||
end
|
end
|
||||||
Map.init_powerups()
|
Map.init_powerups()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Game.check_powerup_pickup()
|
||||||
|
for _, player in ipairs(State.players) do
|
||||||
|
for i = #State.powerups, 1, -1 do
|
||||||
|
local pw = State.powerups[i]
|
||||||
|
if State.map[pw.gridY][pw.gridX] == EMPTY and
|
||||||
|
player.gridX == pw.gridX and player.gridY == pw.gridY then
|
||||||
|
local config = get_powerup_config(pw.type)
|
||||||
|
config.apply(player)
|
||||||
|
table.remove(State.powerups, i)
|
||||||
|
sfx(1, nil, 8)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Game.check_death_by_explosion()
|
||||||
|
for idx, player in ipairs(State.players) do
|
||||||
|
for _, expl in ipairs(State.explosions) do
|
||||||
|
if expl.spread <= 0 then
|
||||||
|
local explGridX = math.floor(expl.x / TILE_SIZE) + 1
|
||||||
|
local explGridY = math.floor(expl.y / TILE_SIZE) + 1
|
||||||
|
if player.gridX == explGridX and player.gridY == explGridY then
|
||||||
|
local winner_idx = (idx == 1) and 2 or 1
|
||||||
|
Game.set_winner(winner_idx)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Game.check_death_by_collision()
|
||||||
|
local human = State.players[1]
|
||||||
|
if not human then return false end
|
||||||
|
|
||||||
|
for _, player in ipairs(State.players) do
|
||||||
|
if player.is_ai and human.gridX == player.gridX and human.gridY == player.gridY then
|
||||||
|
Game.set_winner(2)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Main game loop
|
-- Main game loop
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function TIC()
|
function TIC()
|
||||||
if game_state == GAME_STATE_SPLASH then
|
if State.game_state == GAME_STATE_SPLASH then
|
||||||
UI.update_splash()
|
UI.update_splash()
|
||||||
return
|
return
|
||||||
elseif game_state == GAME_STATE_MENU then
|
elseif State.game_state == GAME_STATE_MENU then
|
||||||
UI.update_menu()
|
UI.update_menu()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- GAME_STATE_PLAYING
|
-- GAME_STATE_PLAYING
|
||||||
cls(6) -- green background
|
cls(COLOR_GREEN)
|
||||||
|
|
||||||
if winner then
|
if State.winner then
|
||||||
win_timer = win_timer - 1
|
State.win_timer = State.win_timer - 1
|
||||||
UI.draw_win_screen()
|
UI.draw_win_screen()
|
||||||
if (btnp(4) or keyp(48)) and win_timer <= 0 then
|
if Input.action_pressed() and State.win_timer <= 0 then
|
||||||
Game.restart()
|
Game.restart()
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Get human player as target for AI
|
||||||
|
local human_player = State.players[1]
|
||||||
|
|
||||||
-- update all players
|
-- update all players
|
||||||
for _, player in ipairs(players) do
|
for _, player in ipairs(State.players) do
|
||||||
Player.update_movement(player)
|
Player.update_movement(player)
|
||||||
if player.is_ai then
|
if player.is_ai then
|
||||||
AI.update(player)
|
AI.update(player, human_player)
|
||||||
else
|
else
|
||||||
Player.handle_input(player)
|
Player.handle_input(player)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update bombs
|
Bomb.update_all()
|
||||||
for i = #bombs, 1, -1 do
|
Game.check_powerup_pickup()
|
||||||
local bomb = bombs[i]
|
|
||||||
bomb.timer = bomb.timer - 1
|
|
||||||
if bomb.timer <= 0 then
|
|
||||||
Bomb.explode(bomb.x, bomb.y, bomb.power)
|
|
||||||
if bomb.owner then
|
|
||||||
bomb.owner.activeBombs = bomb.owner.activeBombs - 1
|
|
||||||
end
|
|
||||||
table.remove(bombs, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update explosions
|
if Game.check_death_by_explosion() then return end
|
||||||
for i = #explosions, 1, -1 do
|
if Game.check_death_by_collision() then return end
|
||||||
local expl = explosions[i]
|
|
||||||
if expl.spread > 0 then
|
|
||||||
expl.spread = expl.spread - 1
|
|
||||||
else
|
|
||||||
expl.timer = expl.timer - 1
|
|
||||||
if expl.timer <= 0 then
|
|
||||||
table.remove(explosions, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check powerup pickup
|
|
||||||
for _, player in ipairs(players) do
|
|
||||||
for i = #powerups, 1, -1 do
|
|
||||||
local pw = powerups[i]
|
|
||||||
if map[pw.gridY][pw.gridX] == EMPTY and
|
|
||||||
player.gridX == pw.gridX and player.gridY == pw.gridY then
|
|
||||||
-- apply powerup effect based on type
|
|
||||||
if pw.type == "bomb" then
|
|
||||||
player.maxBombs = player.maxBombs + 1
|
|
||||||
elseif pw.type == "power" then
|
|
||||||
player.bombPower = player.bombPower + 1
|
|
||||||
end
|
|
||||||
table.remove(powerups, i)
|
|
||||||
sfx(1, nil, 8) -- short pickup beep
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check death by explosion (only fully spread explosions)
|
|
||||||
for idx, player in ipairs(players) do
|
|
||||||
for _, expl in ipairs(explosions) do
|
|
||||||
if expl.spread <= 0 then
|
|
||||||
local explGridX = math.floor(expl.x / TILE_SIZE) + 1
|
|
||||||
local explGridY = math.floor(expl.y / TILE_SIZE) + 1
|
|
||||||
if player.gridX == explGridX and player.gridY == explGridY then
|
|
||||||
local winner_idx = (idx == 1) and 2 or 1
|
|
||||||
Game.set_winner(winner_idx)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check human death by touching AI
|
|
||||||
local human = players[1]
|
|
||||||
for _, player in ipairs(players) do
|
|
||||||
if player.is_ai and human.gridX == player.gridX and human.gridY == player.gridY then
|
|
||||||
Game.set_winner(2)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
UI.draw_game()
|
UI.draw_game()
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user