mirror of
https://github.com/zoriya/Bomberman.git
synced 2025-12-06 06:26:13 +00:00
Merge pull request #262 from AnonymusRaccoon/ai_inprovements
Ai inprovements
This commit is contained in:
74
Bot.md
Normal file
74
Bot.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Bot documentation
|
||||
|
||||
A bot in this bomberman is using a lua script as way to choose what to do
|
||||
So you can make your own with some helper functions given from C++
|
||||
|
||||
## Update function
|
||||
|
||||
Each frame, the game will call the "Update" function in the script.
|
||||
This is the same lua state as the last call so this means you can set global variables to keep data between frames
|
||||
|
||||
Update function should take no arguments.
|
||||
|
||||
## Map Blocks
|
||||
|
||||
```lua
|
||||
nothing = 0
|
||||
breakable = 1
|
||||
hole = 2
|
||||
bumper = 4
|
||||
unbreakable = 7
|
||||
bomb = 10
|
||||
```
|
||||
|
||||
## Registered functions
|
||||
|
||||
```lua
|
||||
-- getMap returns a table of the blocks of the map
|
||||
-- From 1 to 17 {{0, 1, 2 ..}, ..}
|
||||
function getMap();
|
||||
|
||||
-- getDanger returns a table of the danger zone on the map
|
||||
-- From 1 to 17 {{0, 1, 2 ..}, ..}
|
||||
-- value is number of seconds before explosion
|
||||
function getDanger();
|
||||
|
||||
-- getPath returns a table of nodes of a path from A to B {{x = X, y = Y}, ...}
|
||||
-- @param x1 should be int
|
||||
-- @param y1 should be int
|
||||
-- @param x2 should be int
|
||||
-- @param y2 should be int
|
||||
-- @param throughBreakable bool path is going through breakables blocks or not
|
||||
function getPath(x1, y1, x2, y2, throughBreakable);
|
||||
|
||||
-- getPlayer returns player pos as {x = xPlayer, y = yPlayer}
|
||||
function getPlayer();
|
||||
|
||||
-- getPlayerRound returns player pos with rounded value {x = xPlayer, y = yPlayer}
|
||||
function getPlayerRound();
|
||||
|
||||
-- getDangerLevel returns danger level at [xpos, ypos]
|
||||
function getDangerLevel(xpos, ypos);
|
||||
|
||||
-- getDangerLevelPlayer get danger level on the position of the player
|
||||
function getDangerLevelPlayer();
|
||||
|
||||
-- getBlockType returns block type at [xpos, ypos]
|
||||
function getBlockType(xpos, ypos);
|
||||
|
||||
-- getClosestSafeSpace returns the block next to player where the player should go to to the closest safe space
|
||||
-- returns player pos if no path is found
|
||||
function getClosestSafeSpace();
|
||||
|
||||
-- canPutBombSafe returns true if player can put a bomb and find a path to safe space if bomb is put
|
||||
function canPutBombSafe();
|
||||
|
||||
-- getRadius returns the explosion radius of the current player
|
||||
function getRadius();
|
||||
|
||||
-- getEnemies returns a table with enemies position {{x = X, y = Y}, ...}
|
||||
function getEnemies();
|
||||
|
||||
-- getEnemies returns a table with enemies position rounded {{x = X, y = Y}, ...}
|
||||
function getEnemiesRound();
|
||||
```
|
||||
@@ -41,71 +41,6 @@ function PrintMap(map, MaxX, maxZ)
|
||||
end
|
||||
end
|
||||
|
||||
function getNeighborsDefend(node)
|
||||
local neighbors = {}
|
||||
for _, dir in ipairs(Dirs) do
|
||||
local neighborX = node.x + dir.x
|
||||
local neighborY = node.y + dir.y
|
||||
if neighborY <= MaxY and neighborX <= MaxX then
|
||||
if neighborY >= 0 and neighborX >= 0 then
|
||||
if Map[neighborX][neighborY] == 0 and Danger[neighborX][neighborY] ~= 1 then
|
||||
table.insert(neighbors, {x = neighborX, y = neighborY})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #neighbors == 0 and Danger[node.x][node.y] <= 1 then
|
||||
for _, dir in ipairs(Dirs) do
|
||||
local neighborX = node.x + dir.x
|
||||
local neighborY = node.y + dir.y
|
||||
if neighborY <= MaxY and neighborX <= MaxX then
|
||||
if neighborY >= 0 and neighborX >= 0 then
|
||||
if Map[neighborX][neighborY] == 0 then
|
||||
table.insert(neighbors, {x = neighborX, y = neighborY})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return neighbors
|
||||
end
|
||||
|
||||
function dist(nodeA, nodeB)
|
||||
return math.sqrt(math.pow(nodeB.x - nodeA.x, 2) + math.pow(nodeB.y - nodeA.y, 2))
|
||||
end
|
||||
|
||||
function getNeighborAttack(node)
|
||||
log("atta")
|
||||
local neighbors = {}
|
||||
for _, dir in ipairs(Dirs) do
|
||||
local neighborX = node.x + dir.x
|
||||
local neighborY = node.y + dir.y
|
||||
if neighborY <= MaxY and neighborX <= MaxX then
|
||||
if neighborY >= 0 and neighborX >= 0 then
|
||||
if Map[neighborX][neighborY] <= 1 and Danger[neighborX][neighborY] ~= 1 then
|
||||
table.insert(neighbors, {x = neighborX, y = neighborY})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return neighbors
|
||||
end
|
||||
|
||||
function getPathToEnemy(player, enemies)
|
||||
local minDist = 100000
|
||||
local res = {}
|
||||
for _, enemy in ipairs(enemies) do
|
||||
local currDist = dist(player, enemy)
|
||||
if currDist < minDist and enemy.x ~= player.x and enemy.y ~= player.y then
|
||||
minDist, res = currDist, enemy
|
||||
end
|
||||
end
|
||||
local path = pathfind(player, res, getNeighborAttack)
|
||||
return path
|
||||
end
|
||||
|
||||
|
||||
|
||||
function getPathToSafeSpace(player)
|
||||
local res = getClosestSafeSpace()
|
||||
log("run to")
|
||||
@@ -123,7 +58,6 @@ LastTarget = nil
|
||||
math.randomseed(os.time())
|
||||
function Update()
|
||||
log("NEW FRAME")
|
||||
--local path = getPath(0, 0, 16, 16);
|
||||
|
||||
local player = getPlayer()
|
||||
if LastTarget ~= nil then
|
||||
|
||||
@@ -70,6 +70,11 @@ namespace LuaG
|
||||
return res;
|
||||
}
|
||||
|
||||
bool State::getBool(int idx)
|
||||
{
|
||||
return lua_toboolean(_state, idx);
|
||||
}
|
||||
|
||||
float State::getNumber(int idx)
|
||||
{
|
||||
return lua_tonumber(_state, idx);
|
||||
|
||||
@@ -52,6 +52,9 @@ namespace LuaG
|
||||
//! @brief Get return Number
|
||||
bool getReturnBool(void);
|
||||
|
||||
//! @brief Get bool at index in the stack
|
||||
bool getBool(int index);
|
||||
|
||||
//! @brief Get Number at index in the stack
|
||||
float getNumber(int index);
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace BBM
|
||||
{
|
||||
if (std::filesystem::exists(scriptPath)) {
|
||||
_state.dofile(scriptPath);
|
||||
_state.getGlobal("Update");
|
||||
if (lua_isfunction(_state.getState(), -1)) {
|
||||
_state.popLast();
|
||||
} else {
|
||||
throw Error("Lua script doesn't have Update function");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw Error("Couldn't load lua script: " + scriptPath);
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace BBM
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<Vector2f> LuaMap::getNeighbors(Vector2f node) const
|
||||
std::vector<Vector2f> LuaMap::getNeighbors(Vector2f node, bool throughBreakable) const
|
||||
{
|
||||
std::vector<Vector2f> neighbors;
|
||||
for (auto &dir : _dirs) {
|
||||
@@ -65,7 +65,8 @@ namespace BBM
|
||||
continue;
|
||||
if (neighbor.y >= 17 || neighbor.x >= 17)
|
||||
continue;
|
||||
if (_map[neighbor.y][neighbor.x] == 0 &&
|
||||
if ((_map[neighbor.y][neighbor.x] == 0 ||
|
||||
_map[neighbor.y][neighbor.x] == throughBreakable) &&
|
||||
_danger[neighbor.y][neighbor.x] != 1)
|
||||
neighbors.push_back(neighbor);
|
||||
}
|
||||
@@ -83,7 +84,7 @@ namespace BBM
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
std::vector<Vector2f> LuaMap::pathfind(Vector2f root, Vector2f target) const
|
||||
std::vector<Vector2f> LuaMap::pathfind(Vector2f root, Vector2f target, bool throughBreakable) const
|
||||
{
|
||||
std::vector<Vector2f> closed;
|
||||
std::vector<Vector2f> open;
|
||||
@@ -117,7 +118,7 @@ namespace BBM
|
||||
}
|
||||
open.erase(std::remove(open.begin(), open.end(), current), open.end());
|
||||
closed.push_back(current);
|
||||
auto neighbors = getNeighbors(current);
|
||||
auto neighbors = getNeighbors(current, throughBreakable);
|
||||
for (auto &neighbor : neighbors) {
|
||||
if (std::find(closed.begin(), closed.end(), neighbor) != closed.end())
|
||||
continue;
|
||||
@@ -224,14 +225,16 @@ namespace BBM
|
||||
int LuaMap::getPath(lua_State *L)
|
||||
{
|
||||
LuaG::State state(L);
|
||||
auto y2 = state.getNumber(-1);
|
||||
auto x2 = state.getNumber(-2);
|
||||
auto y1 = state.getNumber(-3);
|
||||
auto x1 = state.getNumber(-4);
|
||||
const LuaMap *map = reinterpret_cast<const LuaMap *>(state.getPointer(state.getFirstUpValueIdx()));
|
||||
auto throughBreakable = state.getBool(-1);
|
||||
auto y2 = state.getNumber(-2);
|
||||
auto x2 = state.getNumber(-3);
|
||||
auto y1 = state.getNumber(-4);
|
||||
auto x1 = state.getNumber(-5);
|
||||
|
||||
const LuaMap *map = reinterpret_cast<const LuaMap *>(state.getPointer(state.getFirstUpValueIdx()));
|
||||
Vector2f fst(x1, y1);
|
||||
Vector2f snd(x2, y2);
|
||||
auto path = map->pathfind(fst, snd);
|
||||
auto path = map->pathfind(fst, snd, throughBreakable);
|
||||
int index = 1;
|
||||
state.newTable();
|
||||
for (auto &r : path) {
|
||||
@@ -319,6 +322,54 @@ namespace BBM
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaMap::getRadius(lua_State *L)
|
||||
{
|
||||
LuaG::State state(L);
|
||||
const LuaMap *map = reinterpret_cast<const LuaMap *>(state.getPointer(state.getFirstUpValueIdx()));
|
||||
state.push(map->currRadius);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaMap::getEnemies(lua_State *L)
|
||||
{
|
||||
LuaG::State state(L);
|
||||
const LuaMap *map = reinterpret_cast<const LuaMap *>(state.getPointer(state.getFirstUpValueIdx()));
|
||||
int index = 1;
|
||||
state.newTable();
|
||||
for (auto &r : map->_enemies) {
|
||||
state.push(index++);
|
||||
state.newTable();
|
||||
state.push("x");
|
||||
state.push(r.x);
|
||||
state.setTable();
|
||||
state.push("y");
|
||||
state.push(r.y);
|
||||
state.setTable();
|
||||
state.setTable();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaMap::getEnemiesRound(lua_State *L)
|
||||
{
|
||||
LuaG::State state(L);
|
||||
const LuaMap *map = reinterpret_cast<const LuaMap *>(state.getPointer(state.getFirstUpValueIdx()));
|
||||
int index = 1;
|
||||
state.newTable();
|
||||
for (auto &r : map->_enemies) {
|
||||
state.push(index++);
|
||||
state.newTable();
|
||||
state.push("x");
|
||||
state.push(std::round(r.x));
|
||||
state.setTable();
|
||||
state.push("y");
|
||||
state.push(std::round(r.y));
|
||||
state.setTable();
|
||||
state.setTable();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaMap::canPutBomb(lua_State *L)
|
||||
{
|
||||
LuaG::State state(L);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace BBM
|
||||
void setPlayer(Vector3f pos);
|
||||
|
||||
//! @brief A star pathfinding between two points
|
||||
std::vector<Vector2f> pathfind(Vector2f, Vector2f) const;
|
||||
std::vector<Vector2f> pathfind(Vector2f root, Vector2f target, bool throughBreakable) const;
|
||||
|
||||
//! @brief find a safe space for current player
|
||||
Vector2f findSafeSpace(const std::vector<std::vector<int>> &dangerMap) const;
|
||||
@@ -63,6 +63,15 @@ namespace BBM
|
||||
//! @brief Check if current player can put a bomb with an escape
|
||||
static int canPutBomb(lua_State *L);
|
||||
|
||||
//! @brief Get current explosion radius of the player
|
||||
static int getRadius(lua_State *L);
|
||||
|
||||
//! @brief Get enemies position
|
||||
static int getEnemies(lua_State *L);
|
||||
|
||||
//! @brief Get enemies position rounded
|
||||
static int getEnemiesRound(lua_State *L);
|
||||
|
||||
//! @brief map blocks in 2D grid
|
||||
std::vector<std::vector<int>> _map;
|
||||
|
||||
@@ -72,6 +81,9 @@ namespace BBM
|
||||
//! @brief player position
|
||||
Vector2f _player;
|
||||
|
||||
//! @brief other players position
|
||||
std::vector<Vector2f> _enemies;
|
||||
|
||||
//! @brief rounded player position
|
||||
Vector2f _roundedPlayer;
|
||||
|
||||
@@ -83,7 +95,7 @@ namespace BBM
|
||||
std::unordered_map<Vector2f, Vector2f> &cameFrom, Vector2f node) const;
|
||||
|
||||
//! @brief get neighbors of node for a_star
|
||||
std::vector<Vector2f> getNeighbors(Vector2f node) const;
|
||||
std::vector<Vector2f> getNeighbors(Vector2f node, bool throughBreakable) const;
|
||||
|
||||
std::vector<Vector2f> _dirs = {
|
||||
Vector2f(1, 0), Vector2f(-1, 0), Vector2f(0, 1), Vector2f(0, -1)
|
||||
|
||||
@@ -19,11 +19,13 @@ namespace BBM
|
||||
|
||||
void IAControllableSystem::UpdateMapInfos(WAL::ViewEntity<PositionComponent, ControllableComponent, IAControllableComponent, BombHolderComponent> &entity)
|
||||
{
|
||||
_players.clear();
|
||||
_luamap._enemies.clear();
|
||||
if (!_wal.getScene())
|
||||
return;
|
||||
for (auto &[other, pos, _] : _wal.getScene()->view<PositionComponent, ScoreComponent>()) {
|
||||
_players.push_back(MapInfo(pos.position, MapGenerator::NOTHING));
|
||||
if (other == entity)
|
||||
continue;
|
||||
_luamap._enemies.push_back(Vector2f(pos.position.x, pos.position.z));
|
||||
}
|
||||
if (_cached)
|
||||
return;
|
||||
@@ -86,6 +88,9 @@ namespace BBM
|
||||
state.registerClosure(&_luamap, "getBlockType", LuaMap::getBlockType);
|
||||
state.registerClosure(&_luamap, "getClosestSafeSpace", LuaMap::getClosestSafeSpace);
|
||||
state.registerClosure(&_luamap, "canPutBombSafe", LuaMap::canPutBomb);
|
||||
state.registerClosure(&_luamap, "getRadius", LuaMap::getRadius);
|
||||
state.registerClosure(&_luamap, "getEnemies", LuaMap::getEnemies);
|
||||
state.registerClosure(&_luamap, "getEnemiesRound", LuaMap::getEnemiesRound);
|
||||
}
|
||||
|
||||
void IAControllableSystem::onFixedUpdate(WAL::ViewEntity<PositionComponent, ControllableComponent, IAControllableComponent, BombHolderComponent> &entity)
|
||||
|
||||
Reference in New Issue
Block a user