4
0

AI improvements

This commit is contained in:
Zsolt Tasnadi
2025-12-04 16:33:43 +01:00
parent c28e896f3a
commit 730fddb52e

View File

@@ -43,7 +43,6 @@ local PLAYER_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
local FLOOR_SPRITE = 261
-- Colors -- Colors
local COLOR_BLACK = 0 local COLOR_BLACK = 0
@@ -651,6 +650,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function AI.is_dangerous(gridX, gridY) function AI.is_dangerous(gridX, gridY)
-- Check active explosions
for _, expl in ipairs(State.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
@@ -659,32 +659,49 @@ function AI.is_dangerous(gridX, gridY)
end end
end end
-- Check bombs about to explode (timer < 30) - 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
-- Only urgent if bomb is about to explode
if bomb.timer < 30 then
if gridX == bombGridX and gridY == bombGridY then if gridX == bombGridX and gridY == bombGridY then
return true return true
end end
if gridY == bombGridY then -- Check blast radius only for soon-to-explode bombs
if math.abs(gridX - bombGridX) <= 1 then if gridY == bombGridY and math.abs(gridX - bombGridX) <= power then
if gridX < bombGridX then local blocked = false
if State.map[gridY][gridX + 1] ~= SOLID_WALL then return true end local minX = math.min(gridX, bombGridX)
elseif gridX > bombGridX then local maxX = math.max(gridX, bombGridX)
if State.map[gridY][gridX - 1] ~= SOLID_WALL then return true end for x = minX + 1, maxX - 1 do
if State.map[gridY][x] == SOLID_WALL then
blocked = true
break
end end
end end
if not blocked then return true end
end end
if gridX == bombGridX then if gridX == bombGridX and math.abs(gridY - bombGridY) <= power then
if math.abs(gridY - bombGridY) <= 1 then local blocked = false
if gridY < bombGridY then local minY = math.min(gridY, bombGridY)
if State.map[gridY + 1][gridX] ~= SOLID_WALL then return true end local maxY = math.max(gridY, bombGridY)
elseif gridY > bombGridY then for y = minY + 1, maxY - 1 do
if State.map[gridY - 1][gridX] ~= SOLID_WALL then return true end if State.map[y][gridX] == SOLID_WALL then
blocked = true
break
end end
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
return true
end
end end
end end
@@ -734,32 +751,35 @@ function AI.has_adjacent_breakable_wall(gridX, gridY)
return false return false
end end
function AI.has_escape_route(gridX, gridY, player) function AI.find_nearest_powerup(gridX, gridY)
local dirs = { local nearest = nil
{0, -1}, local nearestDist = 9999
{0, 1}, for _, pw in ipairs(State.powerups) do
{-1, 0}, if State.map[pw.gridY][pw.gridX] == EMPTY then
{1, 0} local dist = math.abs(pw.gridX - gridX) + math.abs(pw.gridY - gridY)
} if dist < nearestDist then
for _, dir in ipairs(dirs) do nearestDist = dist
local newX = gridX + dir[1] nearest = pw
local newY = gridY + dir[2] end
if Map.can_move_to(newX, newY, player) and not AI.is_dangerous(newX, newY) then end
for _, dir2 in ipairs(dirs) do end
local safeX = newX + dir2[1] return nearest
local safeY = newY + dir2[2] end
if Map.can_move_to(safeX, safeY, player) then
function AI.is_in_blast_line(cellX, cellY, bombX, bombY, power)
-- Check if cell is in same row or column as bomb and within power range
if cellY == bombY and math.abs(cellX - bombX) <= power then
return true return true
end end
end if cellX == bombX and math.abs(cellY - bombY) <= power then
end return true
end end
return false return false
end end
function AI.escape_from_bomb(player) function AI.find_safe_cell(gridX, gridY, player)
local bombGridX = player.gridX -- Find a cell to escape to that's OUTSIDE the bomb's blast line
local bombGridY = player.gridY local power = player.bombPower
local dirs = { local dirs = {
{0, -1}, {0, -1},
{0, 1}, {0, 1},
@@ -767,61 +787,72 @@ function AI.escape_from_bomb(player)
{1, 0} {1, 0}
} }
local best_dir = nil -- First try: find a path that gets us completely out of blast line
local best_score = -999
for _, dir in ipairs(dirs) do for _, dir in ipairs(dirs) do
local newX = player.gridX + dir[1] local newX = gridX + dir[1]
local newY = player.gridY + dir[2] local newY = gridY + dir[2]
if Map.can_move_to(newX, newY, player) and not AI.is_dangerous(newX, newY) then
if Map.can_move_to(newX, newY, player) then -- Check if this first step gets us out of blast line
local sc = 0 if not AI.is_in_blast_line(newX, newY, gridX, gridY, power) then
return {newX, newY}
if not AI.in_blast_zone(newX, newY, bombGridX, bombGridY) then
sc = sc + 100
end end
-- If not, check if we can turn corner to get out
for _, dir2 in ipairs(dirs) do for _, dir2 in ipairs(dirs) do
local checkX = newX + dir2[1] local safeX = newX + dir2[1]
local checkY = newY + dir2[2] local safeY = newY + dir2[2]
if not (checkX == bombGridX and checkY == bombGridY) then if Map.can_move_to(safeX, safeY, player) and not AI.is_dangerous(safeX, safeY) then
if Map.can_move_to(checkX, checkY, player) then if not AI.is_in_blast_line(safeX, safeY, gridX, gridY, power) then
sc = sc + 10 return {newX, newY}
if not AI.in_blast_zone(checkX, checkY, bombGridX, bombGridY) then
sc = sc + 20
end end
end end
end end
end end
end
return nil
end
if sc > best_score then function AI.has_escape_route(gridX, gridY, player)
best_score = sc return AI.find_safe_cell(gridX, gridY, player) ~= nil
best_dir = dir
end
end
end end
if best_dir then function AI.escape_from_bomb(player)
player.gridX = player.gridX + best_dir[1] local safe = AI.find_safe_cell(player.gridX, player.gridY, player)
player.gridY = player.gridY + best_dir[2] if safe then
player.gridX = safe[1]
player.gridY = safe[2]
end end
end end
function AI.move_and_bomb(player, target) function AI.move_and_bomb(player, target)
if not target then return end if not target then return end
local dx = target.gridX - player.gridX -- Check for nearby powerup first
local dy = target.gridY - player.gridY local powerup = AI.find_nearest_powerup(player.gridX, player.gridY)
local actualTarget = target
-- If powerup is closer than target, go for powerup
if powerup then
local pwDist = math.abs(powerup.gridX - player.gridX) + math.abs(powerup.gridY - player.gridY)
local targetDist = math.abs(target.gridX - player.gridX) + math.abs(target.gridY - player.gridY)
if pwDist < targetDist or pwDist <= 5 then
actualTarget = {gridX = powerup.gridX, gridY = powerup.gridY}
end
end
local dx = actualTarget.gridX - player.gridX
local dy = actualTarget.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
if dist <= 2 then should_bomb = true end if dist <= 2 and actualTarget == target then should_bomb = true end
if AI.has_adjacent_breakable_wall(player.gridX, player.gridY) then if AI.has_adjacent_breakable_wall(player.gridX, player.gridY) then
should_bomb = true should_bomb = true
end end
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, player) then if AI.has_escape_route(player.gridX, player.gridY, player) then
player.lastGridX = player.gridX
player.lastGridY = player.gridY
Bomb.place(player) Bomb.place(player)
player.bombCooldown = AI_BOMB_COOLDOWN player.bombCooldown = AI_BOMB_COOLDOWN
AI.escape_from_bomb(player) AI.escape_from_bomb(player)
@@ -829,6 +860,7 @@ function AI.move_and_bomb(player, target)
end end
end end
-- Build direction list
local dirs = {} local dirs = {}
if dx > 0 then table.insert(dirs, {1, 0}) if dx > 0 then table.insert(dirs, {1, 0})
elseif dx < 0 then table.insert(dirs, {-1, 0}) elseif dx < 0 then table.insert(dirs, {-1, 0})
@@ -847,10 +879,18 @@ function AI.move_and_bomb(player, target)
table.insert(dirs, d) table.insert(dirs, d)
end end
-- Try to move, avoiding going back to last position unless necessary
local fallback = nil
for _, dir in ipairs(dirs) do for _, dir in ipairs(dirs) do
local newGridX = player.gridX + dir[1] local newGridX = player.gridX + dir[1]
local newGridY = player.gridY + dir[2] local newGridY = player.gridY + dir[2]
if Map.can_move_to(newGridX, newGridY, player) and not AI.is_dangerous(newGridX, newGridY) then if Map.can_move_to(newGridX, newGridY, player) and not AI.is_dangerous(newGridX, newGridY) then
-- Avoid going back unless it's the only option
if newGridX == player.lastGridX and newGridY == player.lastGridY then
if not fallback then fallback = {newGridX, newGridY} end
else
player.lastGridX = player.gridX
player.lastGridY = player.gridY
player.gridX = newGridX player.gridX = newGridX
player.gridY = newGridY player.gridY = newGridY
return return
@@ -858,8 +898,30 @@ function AI.move_and_bomb(player, target)
end end
end end
-- Use fallback if no other option
if fallback then
player.lastGridX = player.gridX
player.lastGridY = player.gridY
player.gridX = fallback[1]
player.gridY = fallback[2]
end
end
function AI.update(player, target) function AI.update(player, target)
if player.moving then return end -- Even while moving, check if destination becomes dangerous
if player.moving then
if AI.is_dangerous(player.gridX, player.gridY) then
-- Destination is dangerous! Try to stop or reverse
local currentGridX = math.floor(player.pixelX / TILE_SIZE) + 1
local currentGridY = math.floor(player.pixelY / TILE_SIZE) + 1
if not AI.is_dangerous(currentGridX, currentGridY) then
-- Stay at current position
player.gridX = currentGridX
player.gridY = currentGridY
end
end
return
end
local in_danger = AI.is_dangerous(player.gridX, player.gridY) local in_danger = AI.is_dangerous(player.gridX, player.gridY)
@@ -915,6 +977,8 @@ function Player.create(gridX, gridY, color, is_ai)
return { return {
gridX = gridX, gridX = gridX,
gridY = gridY, gridY = gridY,
lastGridX = gridX,
lastGridY = gridY,
pixelX = (gridX - 1) * TILE_SIZE, pixelX = (gridX - 1) * TILE_SIZE,
pixelY = (gridY - 1) * TILE_SIZE, pixelY = (gridY - 1) * TILE_SIZE,
moving = false, moving = false,
@@ -1158,7 +1222,6 @@ end
-- 002:00043000001111000111111001111110011111100011110000011000000000000 -- 002:00043000001111000111111001111110011111100011110000011000000000000
-- 003:ddd1ddddddd1dddd1111111ddddd1dddddddd1dd1111111ddd1ddddddd1ddddd -- 003:ddd1ddddddd1dddd1111111ddddd1dddddddd1dd1111111ddd1ddddddd1ddddd
-- 004:8888888888888888888888888888888888888888888888888888888888888888 -- 004:8888888888888888888888888888888888888888888888888888888888888888
-- 005:6666666666566656666666665666566666666666665656666666666665666566
-- </SPRITES> -- </SPRITES>
-- <PALETTE> -- <PALETTE>