refact
This commit is contained in:
220
bomberman.lua
220
bomberman.lua
@@ -33,6 +33,7 @@ local SPLASH_DURATION = 90 -- 1.5 seconds at 60fps
|
||||
local WIN_SCREEN_DURATION = 60
|
||||
local AI_MOVE_DELAY = 20
|
||||
local AI_BOMB_COOLDOWN = 90
|
||||
local BOMB_DANGER_THRESHOLD = 30 -- AI considers bomb dangerous below this timer
|
||||
|
||||
-- Movement
|
||||
local MOVE_SPEED = 2
|
||||
@@ -44,6 +45,7 @@ local DIRECTIONS = {
|
||||
{-1, 0},
|
||||
{1, 0}
|
||||
}
|
||||
local SPREAD_DIRS = {-1, 1} -- negative and positive spread directions
|
||||
|
||||
-- Sprite indices (SPRITES section loads at 256+)
|
||||
local PLAYER_BLUE = 256
|
||||
@@ -433,52 +435,41 @@ end
|
||||
-- Menu module
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local MENU_ITEMS = {
|
||||
{label = "1 Player Game", action = function() State.two_player_mode = false; State.game_state = GAME_STATE_PLAYING; Game.init() end},
|
||||
{label = "2 Player Game", action = function() State.two_player_mode = true; State.game_state = GAME_STATE_PLAYING; Game.init() end},
|
||||
{label = "Help", action = function() State.game_state = GAME_STATE_HELP end},
|
||||
{label = "Credits", action = function() State.game_state = GAME_STATE_CREDITS end},
|
||||
{label = "Exit", action = exit},
|
||||
}
|
||||
|
||||
local function get_menu_color(index)
|
||||
return (State.menu_selection == index) and COLOR_CYAN or COLOR_GRAY_LIGHT
|
||||
end
|
||||
|
||||
function Menu.update()
|
||||
cls(COLOR_BLACK)
|
||||
|
||||
UI.print_shadow("Bomberman", 85, 20, COLOR_BLUE, false, 2)
|
||||
UI.print_shadow("Clone", 100, 40, COLOR_BLUE, false, 2)
|
||||
|
||||
local unselected = COLOR_GRAY_LIGHT
|
||||
local p1_color = (State.menu_selection == 1) and COLOR_CYAN or unselected
|
||||
local p2_color = (State.menu_selection == 2) and COLOR_CYAN or unselected
|
||||
local help_color = (State.menu_selection == 3) and COLOR_CYAN or unselected
|
||||
local credits_color = (State.menu_selection == 4) and COLOR_CYAN or unselected
|
||||
local exit_color = (State.menu_selection == 5) and COLOR_CYAN or unselected
|
||||
|
||||
local cursor_y = 60 + (State.menu_selection - 1) * 14
|
||||
UI.print_shadow(">", 60, cursor_y, COLOR_CYAN)
|
||||
|
||||
UI.print_shadow("1 Player Game", 70, 60, p1_color)
|
||||
UI.print_shadow("2 Player Game", 70, 74, p2_color)
|
||||
UI.print_shadow("Help", 70, 88, help_color)
|
||||
UI.print_shadow("Credits", 70, 102, credits_color)
|
||||
UI.print_shadow("Exit", 70, 116, exit_color)
|
||||
for i, item in ipairs(MENU_ITEMS) do
|
||||
UI.print_shadow(item.label, 70, 60 + (i - 1) * 14, get_menu_color(i))
|
||||
end
|
||||
|
||||
if Input.back_pressed() then
|
||||
exit()
|
||||
elseif Input.up_pressed() then
|
||||
State.menu_selection = State.menu_selection - 1
|
||||
if State.menu_selection < 1 then State.menu_selection = 5 end
|
||||
if State.menu_selection < 1 then State.menu_selection = #MENU_ITEMS end
|
||||
elseif Input.down_pressed() then
|
||||
State.menu_selection = State.menu_selection + 1
|
||||
if State.menu_selection > 5 then State.menu_selection = 1 end
|
||||
if State.menu_selection > #MENU_ITEMS then State.menu_selection = 1 end
|
||||
elseif Input.action_pressed() then
|
||||
if State.menu_selection == 1 then
|
||||
State.two_player_mode = false
|
||||
State.game_state = GAME_STATE_PLAYING
|
||||
Game.init()
|
||||
elseif State.menu_selection == 2 then
|
||||
State.two_player_mode = true
|
||||
State.game_state = GAME_STATE_PLAYING
|
||||
Game.init()
|
||||
elseif State.menu_selection == 3 then
|
||||
State.game_state = GAME_STATE_HELP
|
||||
elseif State.menu_selection == 4 then
|
||||
State.game_state = GAME_STATE_CREDITS
|
||||
else
|
||||
exit()
|
||||
end
|
||||
MENU_ITEMS[State.menu_selection].action()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -638,6 +629,45 @@ function Bomb.place(player)
|
||||
player.activeBombs = player.activeBombs + 1
|
||||
end
|
||||
|
||||
local function spread_explosion(bombX, bombY, gridX, gridY, power, is_horizontal)
|
||||
for _, dir in ipairs(SPREAD_DIRS) do
|
||||
for dist = 1, power do
|
||||
local explX, explY, eGridX, eGridY
|
||||
if is_horizontal then
|
||||
explX = bombX + dir * dist * TILE_SIZE
|
||||
explY = bombY
|
||||
eGridX = gridX + dir * dist
|
||||
eGridY = gridY
|
||||
if eGridX < 1 or eGridX > MAP_WIDTH then break end
|
||||
else
|
||||
explX = bombX
|
||||
explY = bombY + dir * dist * TILE_SIZE
|
||||
eGridX = gridX
|
||||
eGridY = gridY + dir * dist
|
||||
if eGridY < 1 or eGridY > MAP_HEIGHT then break end
|
||||
end
|
||||
|
||||
local tile = State.map[eGridY][eGridX]
|
||||
if tile == SOLID_WALL then break end
|
||||
|
||||
local is_breakable = tile == BREAKABLE_WALL
|
||||
if is_breakable then
|
||||
State.map[eGridY][eGridX] = EMPTY
|
||||
end
|
||||
|
||||
table.insert(State.explosions, {
|
||||
x = explX,
|
||||
y = explY,
|
||||
timer = EXPLOSION_TIMER,
|
||||
dist = dist,
|
||||
spread = dist * SPREAD_DELAY
|
||||
})
|
||||
|
||||
if is_breakable then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Bomb.explode(bombX, bombY, power)
|
||||
power = power or 1
|
||||
sfx(0, nil, 30)
|
||||
@@ -652,63 +682,8 @@ function Bomb.explode(bombX, bombY, power)
|
||||
local gridX = math.floor(bombX / TILE_SIZE) + 1
|
||||
local gridY = math.floor(bombY / TILE_SIZE) + 1
|
||||
|
||||
-- horizontal explosion
|
||||
for _, dir in ipairs({-1, 1}) do
|
||||
for dist = 1, power do
|
||||
local explX = bombX + dir * dist * TILE_SIZE
|
||||
local eGridX = gridX + dir * dist
|
||||
if eGridX < 1 or eGridX > MAP_WIDTH then break end
|
||||
local tile = State.map[gridY][eGridX]
|
||||
if tile == SOLID_WALL then break end
|
||||
if tile == BREAKABLE_WALL then
|
||||
State.map[gridY][eGridX] = EMPTY
|
||||
table.insert(State.explosions, {
|
||||
x = explX,
|
||||
y = bombY,
|
||||
timer = EXPLOSION_TIMER,
|
||||
dist = dist,
|
||||
spread = dist * SPREAD_DELAY
|
||||
})
|
||||
break
|
||||
end
|
||||
table.insert(State.explosions, {
|
||||
x = explX,
|
||||
y = bombY,
|
||||
timer = EXPLOSION_TIMER,
|
||||
dist = dist,
|
||||
spread = dist * SPREAD_DELAY
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- vertical explosion
|
||||
for _, dir in ipairs({-1, 1}) do
|
||||
for dist = 1, power do
|
||||
local explY = bombY + dir * dist * TILE_SIZE
|
||||
local eGridY = gridY + dir * dist
|
||||
if eGridY < 1 or eGridY > MAP_HEIGHT then break end
|
||||
local tile = State.map[eGridY][gridX]
|
||||
if tile == SOLID_WALL then break end
|
||||
if tile == BREAKABLE_WALL then
|
||||
State.map[eGridY][gridX] = EMPTY
|
||||
table.insert(State.explosions, {
|
||||
x = bombX,
|
||||
y = explY,
|
||||
timer = EXPLOSION_TIMER,
|
||||
dist = dist,
|
||||
spread = dist * SPREAD_DELAY
|
||||
})
|
||||
break
|
||||
end
|
||||
table.insert(State.explosions, {
|
||||
x = bombX,
|
||||
y = explY,
|
||||
timer = EXPLOSION_TIMER,
|
||||
dist = dist,
|
||||
spread = dist * SPREAD_DELAY
|
||||
})
|
||||
end
|
||||
end
|
||||
spread_explosion(bombX, bombY, gridX, gridY, power, true) -- horizontal
|
||||
spread_explosion(bombX, bombY, gridX, gridY, power, false) -- vertical
|
||||
end
|
||||
|
||||
function Bomb.update_all()
|
||||
@@ -748,6 +723,18 @@ end
|
||||
-- AI module
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function is_blast_line_blocked(pos1, pos2, fixedCoord, is_horizontal)
|
||||
local minPos = math.min(pos1, pos2)
|
||||
local maxPos = math.max(pos1, pos2)
|
||||
for i = minPos + 1, maxPos - 1 do
|
||||
local tile = is_horizontal and State.map[fixedCoord][i] or State.map[i][fixedCoord]
|
||||
if tile == SOLID_WALL then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function AI.is_dangerous(gridX, gridY)
|
||||
-- Check active explosions
|
||||
for _, expl in ipairs(State.explosions) do
|
||||
@@ -758,44 +745,31 @@ function AI.is_dangerous(gridX, gridY)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check bombs about to explode (timer < 30) - need to escape!
|
||||
-- Check bombs about to explode - need to escape!
|
||||
for _, bomb in ipairs(State.bombs) do
|
||||
local bombGridX = math.floor(bomb.x / TILE_SIZE) + 1
|
||||
local bombGridY = math.floor(bomb.y / TILE_SIZE) + 1
|
||||
local power = bomb.power or 1
|
||||
|
||||
-- Only urgent if bomb is about to explode
|
||||
if bomb.timer < 30 then
|
||||
if bomb.timer < BOMB_DANGER_THRESHOLD then
|
||||
if gridX == bombGridX and gridY == bombGridY then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check blast radius only for soon-to-explode bombs
|
||||
-- Check horizontal blast radius
|
||||
if gridY == bombGridY and math.abs(gridX - bombGridX) <= power then
|
||||
local blocked = false
|
||||
local minX = math.min(gridX, bombGridX)
|
||||
local maxX = math.max(gridX, bombGridX)
|
||||
for x = minX + 1, maxX - 1 do
|
||||
if State.map[gridY][x] == SOLID_WALL then
|
||||
blocked = true
|
||||
break
|
||||
if not is_blast_line_blocked(gridX, bombGridX, gridY, true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if not blocked then return true end
|
||||
end
|
||||
|
||||
-- Check vertical blast radius
|
||||
if gridX == bombGridX and math.abs(gridY - bombGridY) <= power then
|
||||
local blocked = false
|
||||
local minY = math.min(gridY, bombGridY)
|
||||
local maxY = math.max(gridY, bombGridY)
|
||||
for y = minY + 1, maxY - 1 do
|
||||
if State.map[y][gridX] == SOLID_WALL then
|
||||
blocked = true
|
||||
break
|
||||
if not is_blast_line_blocked(gridY, bombGridY, gridX, false) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if not blocked then return true end
|
||||
end
|
||||
else
|
||||
-- For bombs with more time, just avoid the bomb cell itself
|
||||
if gridX == bombGridX and gridY == bombGridY then
|
||||
@@ -1205,22 +1179,7 @@ end
|
||||
-- Main game loop
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function TIC()
|
||||
if State.game_state == GAME_STATE_SPLASH then
|
||||
Splash.update()
|
||||
return
|
||||
elseif State.game_state == GAME_STATE_MENU then
|
||||
Menu.update()
|
||||
return
|
||||
elseif State.game_state == GAME_STATE_HELP then
|
||||
Help.update()
|
||||
return
|
||||
elseif State.game_state == GAME_STATE_CREDITS then
|
||||
Credits.update()
|
||||
return
|
||||
end
|
||||
|
||||
-- GAME_STATE_PLAYING
|
||||
local function update_playing()
|
||||
cls(COLOR_GREEN)
|
||||
|
||||
-- ESC to return to menu
|
||||
@@ -1243,6 +1202,21 @@ function TIC()
|
||||
GameBoard.draw()
|
||||
end
|
||||
|
||||
local STATE_HANDLERS = {
|
||||
[GAME_STATE_SPLASH] = Splash.update,
|
||||
[GAME_STATE_MENU] = Menu.update,
|
||||
[GAME_STATE_HELP] = Help.update,
|
||||
[GAME_STATE_CREDITS] = Credits.update,
|
||||
[GAME_STATE_PLAYING] = update_playing,
|
||||
}
|
||||
|
||||
function TIC()
|
||||
local handler = STATE_HANDLERS[State.game_state]
|
||||
if handler then
|
||||
handler()
|
||||
end
|
||||
end
|
||||
|
||||
-- <TILES>
|
||||
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
|
||||
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
|
||||
|
||||
Reference in New Issue
Block a user