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 WIN_SCREEN_DURATION = 60
|
||||||
local AI_MOVE_DELAY = 20
|
local AI_MOVE_DELAY = 20
|
||||||
local AI_BOMB_COOLDOWN = 90
|
local AI_BOMB_COOLDOWN = 90
|
||||||
|
local BOMB_DANGER_THRESHOLD = 30 -- AI considers bomb dangerous below this timer
|
||||||
|
|
||||||
-- Movement
|
-- Movement
|
||||||
local MOVE_SPEED = 2
|
local MOVE_SPEED = 2
|
||||||
@@ -44,6 +45,7 @@ local DIRECTIONS = {
|
|||||||
{-1, 0},
|
{-1, 0},
|
||||||
{1, 0}
|
{1, 0}
|
||||||
}
|
}
|
||||||
|
local SPREAD_DIRS = {-1, 1} -- negative and positive spread directions
|
||||||
|
|
||||||
-- Sprite indices (SPRITES section loads at 256+)
|
-- Sprite indices (SPRITES section loads at 256+)
|
||||||
local PLAYER_BLUE = 256
|
local PLAYER_BLUE = 256
|
||||||
@@ -433,52 +435,41 @@ end
|
|||||||
-- Menu module
|
-- 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()
|
function Menu.update()
|
||||||
cls(COLOR_BLACK)
|
cls(COLOR_BLACK)
|
||||||
|
|
||||||
UI.print_shadow("Bomberman", 85, 20, COLOR_BLUE, false, 2)
|
UI.print_shadow("Bomberman", 85, 20, COLOR_BLUE, false, 2)
|
||||||
UI.print_shadow("Clone", 100, 40, 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
|
local cursor_y = 60 + (State.menu_selection - 1) * 14
|
||||||
UI.print_shadow(">", 60, cursor_y, COLOR_CYAN)
|
UI.print_shadow(">", 60, cursor_y, COLOR_CYAN)
|
||||||
|
|
||||||
UI.print_shadow("1 Player Game", 70, 60, p1_color)
|
for i, item in ipairs(MENU_ITEMS) do
|
||||||
UI.print_shadow("2 Player Game", 70, 74, p2_color)
|
UI.print_shadow(item.label, 70, 60 + (i - 1) * 14, get_menu_color(i))
|
||||||
UI.print_shadow("Help", 70, 88, help_color)
|
end
|
||||||
UI.print_shadow("Credits", 70, 102, credits_color)
|
|
||||||
UI.print_shadow("Exit", 70, 116, exit_color)
|
|
||||||
|
|
||||||
if Input.back_pressed() then
|
if Input.back_pressed() then
|
||||||
exit()
|
exit()
|
||||||
elseif Input.up_pressed() then
|
elseif Input.up_pressed() then
|
||||||
State.menu_selection = State.menu_selection - 1
|
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
|
elseif Input.down_pressed() then
|
||||||
State.menu_selection = State.menu_selection + 1
|
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
|
elseif Input.action_pressed() then
|
||||||
if State.menu_selection == 1 then
|
MENU_ITEMS[State.menu_selection].action()
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -638,6 +629,45 @@ function Bomb.place(player)
|
|||||||
player.activeBombs = player.activeBombs + 1
|
player.activeBombs = player.activeBombs + 1
|
||||||
end
|
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)
|
function Bomb.explode(bombX, bombY, power)
|
||||||
power = power or 1
|
power = power or 1
|
||||||
sfx(0, nil, 30)
|
sfx(0, nil, 30)
|
||||||
@@ -652,63 +682,8 @@ function Bomb.explode(bombX, bombY, power)
|
|||||||
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
|
||||||
|
|
||||||
-- horizontal explosion
|
spread_explosion(bombX, bombY, gridX, gridY, power, true) -- horizontal
|
||||||
for _, dir in ipairs({-1, 1}) do
|
spread_explosion(bombX, bombY, gridX, gridY, power, false) -- vertical
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Bomb.update_all()
|
function Bomb.update_all()
|
||||||
@@ -748,6 +723,18 @@ end
|
|||||||
-- AI module
|
-- 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)
|
function AI.is_dangerous(gridX, gridY)
|
||||||
-- Check active explosions
|
-- Check active explosions
|
||||||
for _, expl in ipairs(State.explosions) do
|
for _, expl in ipairs(State.explosions) do
|
||||||
@@ -758,43 +745,30 @@ function AI.is_dangerous(gridX, gridY)
|
|||||||
end
|
end
|
||||||
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
|
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
|
||||||
local power = bomb.power or 1
|
local power = bomb.power or 1
|
||||||
|
|
||||||
-- Only urgent if bomb is about to explode
|
-- 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
|
if gridX == bombGridX and gridY == bombGridY then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check blast radius only for soon-to-explode bombs
|
-- Check horizontal blast radius
|
||||||
if gridY == bombGridY and math.abs(gridX - bombGridX) <= power then
|
if gridY == bombGridY and math.abs(gridX - bombGridX) <= power then
|
||||||
local blocked = false
|
if not is_blast_line_blocked(gridX, bombGridX, gridY, true) then
|
||||||
local minX = math.min(gridX, bombGridX)
|
return true
|
||||||
local maxX = math.max(gridX, bombGridX)
|
|
||||||
for x = minX + 1, maxX - 1 do
|
|
||||||
if State.map[gridY][x] == SOLID_WALL then
|
|
||||||
blocked = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if not blocked then return true end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check vertical blast radius
|
||||||
if gridX == bombGridX and math.abs(gridY - bombGridY) <= power then
|
if gridX == bombGridX and math.abs(gridY - bombGridY) <= power then
|
||||||
local blocked = false
|
if not is_blast_line_blocked(gridY, bombGridY, gridX, false) then
|
||||||
local minY = math.min(gridY, bombGridY)
|
return true
|
||||||
local maxY = math.max(gridY, bombGridY)
|
|
||||||
for y = minY + 1, maxY - 1 do
|
|
||||||
if State.map[y][gridX] == SOLID_WALL then
|
|
||||||
blocked = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if not blocked then return true end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- For bombs with more time, just avoid the bomb cell itself
|
-- For bombs with more time, just avoid the bomb cell itself
|
||||||
@@ -1205,22 +1179,7 @@ end
|
|||||||
-- Main game loop
|
-- Main game loop
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
function TIC()
|
local function update_playing()
|
||||||
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
|
|
||||||
cls(COLOR_GREEN)
|
cls(COLOR_GREEN)
|
||||||
|
|
||||||
-- ESC to return to menu
|
-- ESC to return to menu
|
||||||
@@ -1243,6 +1202,21 @@ function TIC()
|
|||||||
GameBoard.draw()
|
GameBoard.draw()
|
||||||
end
|
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>
|
-- <TILES>
|
||||||
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
|
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
|
||||||
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
|
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
|
||||||
|
|||||||
Reference in New Issue
Block a user