4
0

enemy as player

This commit is contained in:
Zsolt Tasnadi
2025-12-04 10:27:01 +01:00
parent 79bfa3c8bd
commit d197289a06

View File

@@ -16,16 +16,29 @@ EMPTY = 0
SOLID_WALL = 1 SOLID_WALL = 1
BREAKABLE_WALL = 2 BREAKABLE_WALL = 2
# player (grid position and pixel position for animation) # create a new player/enemy entity
$player = { def create_player(gridX, gridY, color, is_ai = false)
gridX: 1, {
gridY: 1, gridX: gridX,
pixelX: 16, gridY: gridY,
pixelY: 16, pixelX: gridX * TILE_SIZE,
moving: false, pixelY: gridY * TILE_SIZE,
maxBombs: 1, moving: false,
activeBombs: 0 maxBombs: 1,
} activeBombs: 0,
color: color,
is_ai: is_ai,
moveTimer: 0,
bombCooldown: 0,
spawnX: gridX,
spawnY: gridY
}
end
# players array (first is human, rest are AI)
$players = []
$players << create_player(1, 1, 12, false) # human player (blue)
$players << create_player(13, 7, 2, true) # AI enemy (red)
# powerups (extra bombs hidden under breakable walls) # powerups (extra bombs hidden under breakable walls)
$powerups = [] $powerups = []
@@ -34,16 +47,6 @@ $powerups = []
$bombs = [] $bombs = []
$explosions = [] $explosions = []
# enemy (grid position and pixel position for animation)
$enemy = {
gridX: 13,
gridY: 7,
pixelX: 208,
pixelY: 112,
moving: false,
moveTimer: 0
}
# animation speed (pixels per frame) # animation speed (pixels per frame)
MOVE_SPEED = 2 MOVE_SPEED = 2
@@ -77,18 +80,14 @@ init_powerups
def TIC def TIC
cls(0) cls(0)
# handle player movement # update all players
update_player $players.each do |player|
update_player_movement(player)
# place bomb (only if we have available bombs) if player[:is_ai]
if btnp(4) && $player[:activeBombs] < $player[:maxBombs] update_ai(player)
bombX = $player[:gridX] * TILE_SIZE else
bombY = $player[:gridY] * TILE_SIZE handle_human_input(player)
# check if there's already a bomb at this position
already_bomb = $bombs.any? { |b| b[:x] == bombX && b[:y] == bombY }
unless already_bomb
$bombs << { x: bombX, y: bombY, timer: BOMB_TIMER }
$player[:activeBombs] += 1
end end
end end
@@ -98,7 +97,7 @@ def TIC
if bomb[:timer] <= 0 if bomb[:timer] <= 0
explode(bomb[:x], bomb[:y]) explode(bomb[:x], bomb[:y])
$bombs.delete(bomb) $bombs.delete(bomb)
$player[:activeBombs] -= 1 bomb[:owner][:activeBombs] -= 1 if bomb[:owner]
end end
end end
@@ -127,18 +126,19 @@ def TIC
if $map[pw[:gridY]][pw[:gridX]] == EMPTY if $map[pw[:gridY]][pw[:gridX]] == EMPTY
drawX = pw[:gridX] * TILE_SIZE drawX = pw[:gridX] * TILE_SIZE
drawY = pw[:gridY] * TILE_SIZE drawY = pw[:gridY] * TILE_SIZE
# draw bomb powerup as small circle with B
rect(drawX + 3, drawY + 3, 10, 10, 6) rect(drawX + 3, drawY + 3, 10, 10, 6)
print("B", drawX + 5, drawY + 5, 0) print("B", drawX + 5, drawY + 5, 0)
end end
end end
# check powerup pickup # check powerup pickup for all players
$powerups.reverse_each do |pw| $players.each do |player|
if $map[pw[:gridY]][pw[:gridX]] == EMPTY && $powerups.reverse_each do |pw|
$player[:gridX] == pw[:gridX] && $player[:gridY] == pw[:gridY] if $map[pw[:gridY]][pw[:gridX]] == EMPTY &&
$player[:maxBombs] += 1 player[:gridX] == pw[:gridX] && player[:gridY] == pw[:gridY]
$powerups.delete(pw) player[:maxBombs] += 1
$powerups.delete(pw)
end
end end
end end
@@ -152,125 +152,310 @@ def TIC
rect(expl[:x], expl[:y], TILE_SIZE, TILE_SIZE, 6) rect(expl[:x], expl[:y], TILE_SIZE, TILE_SIZE, 6)
end end
# update enemy # draw all players
update_enemy $players.each do |player|
rect(player[:pixelX] + 2, player[:pixelY] + 2, PLAYER_SIZE, PLAYER_SIZE, player[:color])
end
# draw player (centered in tile) # check death by explosion for all players
rect($player[:pixelX] + 2, $player[:pixelY] + 2, PLAYER_SIZE, PLAYER_SIZE, 12) $players.each do |player|
$explosions.each do |expl|
# draw enemy (centered in tile) explGridX = (expl[:x] / TILE_SIZE).floor
rect($enemy[:pixelX] + 2, $enemy[:pixelY] + 2, PLAYER_SIZE, PLAYER_SIZE, 2) explGridY = (expl[:y] / TILE_SIZE).floor
if player[:gridX] == explGridX && player[:gridY] == explGridY
# check player death by explosion (grid-based) reset_player_entity(player)
$explosions.each do |expl| end
explGridX = (expl[:x] / TILE_SIZE).floor
explGridY = (expl[:y] / TILE_SIZE).floor
if $player[:gridX] == explGridX && $player[:gridY] == explGridY
reset_player
end end
end end
# check player death by enemy (grid-based) # check human player death by touching AI enemy
if $player[:gridX] == $enemy[:gridX] && $player[:gridY] == $enemy[:gridY] human = $players[0]
reset_player $players.each do |player|
end if player[:is_ai] && human[:gridX] == player[:gridX] && human[:gridY] == player[:gridY]
reset_player_entity(human)
# check enemy death by explosion (grid-based)
$explosions.each do |expl|
explGridX = (expl[:x] / TILE_SIZE).floor
explGridY = (expl[:y] / TILE_SIZE).floor
if $enemy[:gridX] == explGridX && $enemy[:gridY] == explGridY
reset_enemy
end end
end end
print("ARROWS:MOVE A:BOMB", 50, 2, 15) print("ARROWS:MOVE A:BOMB", 50, 2, 15)
available = $player[:maxBombs] - $player[:activeBombs] human = $players[0]
print("BOMBS:#{available}/#{$player[:maxBombs]}", 170, 2, 11) available = human[:maxBombs] - human[:activeBombs]
print("BOMBS:#{available}/#{human[:maxBombs]}", 170, 2, 11)
end end
def update_player # common movement animation for all players
targetX = $player[:gridX] * TILE_SIZE def update_player_movement(player)
targetY = $player[:gridY] * TILE_SIZE targetX = player[:gridX] * TILE_SIZE
targetY = player[:gridY] * TILE_SIZE
# animate toward target position if player[:pixelX] < targetX
if $player[:pixelX] < targetX player[:pixelX] = [player[:pixelX] + MOVE_SPEED, targetX].min
$player[:pixelX] = [$player[:pixelX] + MOVE_SPEED, targetX].min player[:moving] = true
$player[:moving] = true elsif player[:pixelX] > targetX
elsif $player[:pixelX] > targetX player[:pixelX] = [player[:pixelX] - MOVE_SPEED, targetX].max
$player[:pixelX] = [$player[:pixelX] - MOVE_SPEED, targetX].max player[:moving] = true
$player[:moving] = true elsif player[:pixelY] < targetY
elsif $player[:pixelY] < targetY player[:pixelY] = [player[:pixelY] + MOVE_SPEED, targetY].min
$player[:pixelY] = [$player[:pixelY] + MOVE_SPEED, targetY].min player[:moving] = true
$player[:moving] = true elsif player[:pixelY] > targetY
elsif $player[:pixelY] > targetY player[:pixelY] = [player[:pixelY] - MOVE_SPEED, targetY].max
$player[:pixelY] = [$player[:pixelY] - MOVE_SPEED, targetY].max player[:moving] = true
$player[:moving] = true
else else
$player[:moving] = false player[:moving] = false
end end
# only accept input when not moving player[:bombCooldown] -= 1 if player[:bombCooldown] > 0
return if $player[:moving] end
newGridX = $player[:gridX] # handle human player input
newGridY = $player[:gridY] def handle_human_input(player)
return if player[:moving]
newGridX = player[:gridX]
newGridY = player[:gridY]
if btn(0) if btn(0)
newGridY = $player[:gridY] - 1 newGridY = player[:gridY] - 1
elsif btn(1) elsif btn(1)
newGridY = $player[:gridY] + 1 newGridY = player[:gridY] + 1
elsif btn(2) elsif btn(2)
newGridX = $player[:gridX] - 1 newGridX = player[:gridX] - 1
elsif btn(3) elsif btn(3)
newGridX = $player[:gridX] + 1 newGridX = player[:gridX] + 1
end end
# check if new grid position is valid
if can_move_to?(newGridX, newGridY) if can_move_to?(newGridX, newGridY)
$player[:gridX] = newGridX player[:gridX] = newGridX
$player[:gridY] = newGridY player[:gridY] = newGridY
end
# place bomb
if btnp(4)
place_bomb(player)
end end
end end
def update_enemy # AI player logic
targetX = $enemy[:gridX] * TILE_SIZE def update_ai(player)
targetY = $enemy[:gridY] * TILE_SIZE return if player[:moving]
# animate toward target position player[:moveTimer] += 1
if $enemy[:pixelX] < targetX return if player[:moveTimer] < 20
$enemy[:pixelX] = [$enemy[:pixelX] + MOVE_SPEED, targetX].min
$enemy[:moving] = true player[:moveTimer] = 0
elsif $enemy[:pixelX] > targetX
$enemy[:pixelX] = [$enemy[:pixelX] - MOVE_SPEED, targetX].max # check if in danger
$enemy[:moving] = true danger_dir = get_escape_direction(player[:gridX], player[:gridY])
elsif $enemy[:pixelY] < targetY
$enemy[:pixelY] = [$enemy[:pixelY] + MOVE_SPEED, targetY].min if danger_dir
$enemy[:moving] = true newGridX = player[:gridX] + danger_dir[0]
elsif $enemy[:pixelY] > targetY newGridY = player[:gridY] + danger_dir[1]
$enemy[:pixelY] = [$enemy[:pixelY] - MOVE_SPEED, targetY].max if can_move_to?(newGridX, newGridY) && !is_dangerous?(newGridX, newGridY)
$enemy[:moving] = true player[:gridX] = newGridX
player[:gridY] = newGridY
else
try_escape_any_direction(player)
end
else else
$enemy[:moving] = false ai_move_and_bomb(player)
end
end
# check if a position is dangerous (bomb blast zone or explosion)
def is_dangerous?(gridX, gridY)
# check explosions
$explosions.each do |expl|
explGridX = (expl[:x] / TILE_SIZE).floor
explGridY = (expl[:y] / TILE_SIZE).floor
return true if gridX == explGridX && gridY == explGridY
end end
# only move when animation is complete # check bombs and their blast zones
return if $enemy[:moving] $bombs.each do |bomb|
bombGridX = (bomb[:x] / TILE_SIZE).floor
bombGridY = (bomb[:y] / TILE_SIZE).floor
$enemy[:moveTimer] += 1 # bomb position
return if $enemy[:moveTimer] < 30 return true if gridX == bombGridX && gridY == bombGridY
$enemy[:moveTimer] = 0 # horizontal blast zone
if gridY == bombGridY
if (gridX - bombGridX).abs <= 1
# check if wall blocks the blast
if gridX < bombGridX
return true if $map[gridY][gridX + 1] != SOLID_WALL
elsif gridX > bombGridX
return true if $map[gridY][gridX - 1] != SOLID_WALL
end
end
end
# pick random direction # vertical blast zone
if gridX == bombGridX
if (gridY - bombGridY).abs <= 1
# check if wall blocks the blast
if gridY < bombGridY
return true if $map[gridY + 1][gridX] != SOLID_WALL
elsif gridY > bombGridY
return true if $map[gridY - 1][gridX] != SOLID_WALL
end
end
end
end
false
end
# find escape direction away from danger
def get_escape_direction(gridX, gridY)
return nil unless is_dangerous?(gridX, gridY)
# find direction away from nearest bomb
$bombs.each do |bomb|
bombGridX = (bomb[:x] / TILE_SIZE).floor
bombGridY = (bomb[:y] / TILE_SIZE).floor
# if on same row as bomb, move vertically
if gridY == bombGridY && (gridX - bombGridX).abs <= 1
return [0, -1] if can_move_to?(gridX, gridY - 1) && !is_dangerous?(gridX, gridY - 1)
return [0, 1] if can_move_to?(gridX, gridY + 1) && !is_dangerous?(gridX, gridY + 1)
end
# if on same column as bomb, move horizontally
if gridX == bombGridX && (gridY - bombGridY).abs <= 1
return [-1, 0] if can_move_to?(gridX - 1, gridY) && !is_dangerous?(gridX - 1, gridY)
return [1, 0] if can_move_to?(gridX + 1, gridY) && !is_dangerous?(gridX + 1, gridY)
end
end
nil
end
# try to escape in any safe direction
def try_escape_any_direction(player)
dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]].shuffle
dirs.each do |dir|
newX = player[:gridX] + dir[0]
newY = player[:gridY] + dir[1]
if can_move_to?(newX, newY) && !is_dangerous?(newX, newY)
player[:gridX] = newX
player[:gridY] = newY
return
end
end
end
# escape immediately after placing bomb
def escape_from_own_bomb(player)
dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]].shuffle
dirs.each do |dir|
newX = player[:gridX] + dir[0]
newY = player[:gridY] + dir[1]
if can_move_to?(newX, newY)
player[:gridX] = newX
player[:gridY] = newY
return
end
end
end
# check if there's a breakable wall adjacent to position
def has_adjacent_breakable_wall?(gridX, gridY)
dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]] dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]
dir = rand(4) dirs.each do |dir|
newGridX = $enemy[:gridX] + dirs[dir][0] checkX = gridX + dir[0]
newGridY = $enemy[:gridY] + dirs[dir][1] checkY = gridY + dir[1]
if checkX >= 0 && checkX <= 14 && checkY >= 0 && checkY <= 8
return true if $map[checkY][checkX] == BREAKABLE_WALL
end
end
false
end
if can_move_to?(newGridX, newGridY) # check if enemy has a safe escape route after placing bomb
$enemy[:gridX] = newGridX def has_escape_route?(gridX, gridY)
$enemy[:gridY] = newGridY dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]
dirs.each do |dir|
newX = gridX + dir[0]
newY = gridY + dir[1]
# check if can move there and it's not in bomb blast zone
if can_move_to?(newX, newY)
# check one more step for safety
dirs.each do |dir2|
safeX = newX + dir2[0]
safeY = newY + dir2[1]
if can_move_to?(safeX, safeY)
return true
end
end
end
end
false
end
# place bomb for any player
def place_bomb(player)
return if player[:activeBombs] >= player[:maxBombs]
bombX = player[:gridX] * TILE_SIZE
bombY = player[:gridY] * TILE_SIZE
already_bomb = $bombs.any? { |b| b[:x] == bombX && b[:y] == bombY }
unless already_bomb
$bombs << { x: bombX, y: bombY, timer: BOMB_TIMER, owner: player }
player[:activeBombs] += 1
end
end
# AI movement and bombing logic
def ai_move_and_bomb(player)
# find nearest human player to chase
human = $players.find { |p| !p[:is_ai] }
return unless human
dx = human[:gridX] - player[:gridX]
dy = human[:gridY] - player[:gridY]
dist = dx.abs + dy.abs
# decide if should place bomb
should_bomb = false
should_bomb = true if dist <= 2
should_bomb = true if has_adjacent_breakable_wall?(player[:gridX], player[:gridY])
# place bomb if should and can
if should_bomb && player[:activeBombs] < player[:maxBombs] && player[:bombCooldown] <= 0
if has_escape_route?(player[:gridX], player[:gridY])
place_bomb(player)
player[:bombCooldown] = 90
escape_from_own_bomb(player)
return
end
end
# move toward human player
dirs = []
if dx > 0
dirs << [1, 0]
elsif dx < 0
dirs << [-1, 0]
end
if dy > 0
dirs << [0, 1]
elsif dy < 0
dirs << [0, -1]
end
dirs += [[0, -1], [0, 1], [-1, 0], [1, 0]].shuffle
dirs.each do |dir|
newGridX = player[:gridX] + dir[0]
newGridY = player[:gridY] + dir[1]
if can_move_to?(newGridX, newGridY) && !is_dangerous?(newGridX, newGridY)
player[:gridX] = newGridX
player[:gridY] = newGridY
return
end
end end
end end
@@ -280,22 +465,16 @@ def can_move_to?(gridX, gridY)
true true
end end
def reset_player # reset any player to spawn position
$player[:gridX] = 1 def reset_player_entity(player)
$player[:gridY] = 1 player[:gridX] = player[:spawnX]
$player[:pixelX] = 16 player[:gridY] = player[:spawnY]
$player[:pixelY] = 16 player[:pixelX] = player[:spawnX] * TILE_SIZE
$player[:moving] = false player[:pixelY] = player[:spawnY] * TILE_SIZE
$player[:maxBombs] = 1 player[:moving] = false
$player[:activeBombs] = 0 player[:maxBombs] = 1
end player[:activeBombs] = 0
player[:bombCooldown] = 0
def reset_enemy
$enemy[:gridX] = 13
$enemy[:gridY] = 7
$enemy[:pixelX] = 208
$enemy[:pixelY] = 112
$enemy[:moving] = false
end end
def explode(bombX, bombY) def explode(bombX, bombY)