import { moveAndWait, goAway, getNearTiles } from "../actions/movement.js";
import DeliverooClient from "../api/deliverooClient.js";
import { MapStore } from "../models/mapStore.js";
import { Me } from "../models/me.js";
import { ParcelsStore } from "../models/parcelsStore.js";
import { INTENTIONS } from "../utils/intentions.js";
import { goingTowardsParcel } from "../utils/geometry.js";
import { astarSearch, direction } from "../utils/astar.js";
import { coord2Key, key2Coord } from "../utils/hashMap.js";
import { LOG_LEVELS } from "../utils/log.js";
import { log } from "../utils/log.js";
import { AgentStore } from "../models/agentStore.js";
import { TILE_TYPES } from "../utils/tile.js";
import { ServerConfig } from "../models/serverConfig.js";
import { Communication } from "../models/communication.js";
import { getPickupScore } from "../utils/misc.js";
import config from "../utils/gameConfig.js";
import gameConfig from "../utils/gameConfig.js";
/**
* MultiAgent class for managing multi-agent interactions in the Deliveroo game.
* It handles desires, intentions, and actions of agents, including pathfinding and communication.
* It supports both master and slave agents, allowing them to coordinate actions and share information.
* The class implements a BDI (Belief-Desire-Intention) architecture to manage agent behavior.
* It includes methods for generating desires, filtering intentions, and executing actions based on the current state of the game.
* The agent can perform actions such as picking up parcels, depositing them, exploring the map, and avoiding collisions with other agents.
* It also manages the agent's state, including movement, camping, and collision detection.
* The class is designed to be extensible, allowing for the addition of new intentions and actions as needed.
* @class
* @param {DeliverooClient} client - The client instance for API communication.
* @param {Me} me - The agent's own state.
* @param {Me} mate - The mate agent's state (for master agents, this is the slave agent).
* @param {ParcelsStore} parcels - The store for managing parcels.
* @param {MapStore} mapStore - The store for managing the game map.
* @param {AgentStore} agentStore - The store for managing agents.
* @param {Communication} communication - The communication model for the agent.
* @param {ServerConfig} serverConfig - The server configuration for the game.
* @param {boolean} isMaster - Flag to check if the agent is a master agent.
* @property {DeliverooClient} client - The client instance for API communication.
* @description
* The MultiAgent class implements a BDI architecture to manage agent behavior in the Deliveroo game.
* It allows agents to generate desires based on the current state of the game, filter those desires into intentions, and execute actions accordingly.
* The class supports both master and slave agents, enabling them to coordinate actions and share information.
* It includes methods for pathfinding, collision detection, and managing the agent's state, such as movement, camping, and exploring the map.
* The agent can perform actions such as picking up parcels, depositing them, exploring the map, and avoiding collisions with other agents.
* The class is designed to be extensible, allowing for the addition of new intentions and actions as needed.
* It also includes logging functionality to track agent actions and intentions
*/
export class MultiAgent {
constructor(client, me, mate, parcels, mapStore, agentStore, communication, serverConfig, isMaster) {
this.client = client;
this.me = me;
this.mate = mate; // For master agent, the mate is the slave agent
this.parcels = parcels;
this.mapStore = mapStore;
this.agentStore = agentStore;
this.serverConfig = serverConfig;
this.isMaster = isMaster; // Flag to check if it's a master agent
this.communication = communication; // Communication model for the agent
// BDI structures
this.desires = [];
this.intentions = [];
this.lastIntention = { // Out last intentions
type: null,
};
// Pathfinding
this.pathIndex = 0; // Move to perform (from path)
this.path = []; // Path got from A*
this.oldTime = null; // Keep track of last loop time
this.isMoving = false; // flag to check if it's already perfoming an action -> to not get penalties
// Explore timers
this.isExploring = false;
this.isCamping = false;
this.campingStartTime = 0;
// Agent collision timers
this.isColliding = false;
this.agentCollisionStartTime = 0;
// Log section
this.logLevels = [];
}
/**
* Logs messages based on the log level.
* @param {string} logLevel - The log level to filter messages.
* @param {...any} args - The messages to log.
* @description
* This method logs messages to the console based on the specified log level.
* It uses the `log` utility function to filter and format the messages according to the log levels defined in the class.
* The log levels can include master, slave, and action logs, allowing for flexible logging of agent actions and intentions.
* @example
* // Log a master level message
* agent.log(LOG_LEVELS.MASTER, "This is a master level message");
* @example
* // Log a slave level message
* agent.log(LOG_LEVELS.SLAVE, "This is a slave level message");
*/
log(logLevel, ...args) {
log(this.logLevels, logLevel, ...args);
}
/**
* Updates the agent's beliefs based on the current state of the game.
* It calculates the frame difference and updates the parcels data accordingly.
* @description
* This method is called to update the agent's beliefs about the game state.
* It calculates the time difference since the last update and uses it to update the parcels data.
* The parcels data is updated based on the current frame and the server configuration.
* This method is essential for keeping the agent's beliefs up-to-date, allowing it to make informed decisions based on the latest game state.
* @example
* // Update the agent's beliefs
* agent.updateBeliefs();
*/
updateBeliefs() {
// Get frame difference
if (this.oldTime === null) {
this.oldTime = this.me.ms;
}
const timeDiff = this.me.ms - this.oldTime;
this.oldTime = this.me.ms;
// Update parcels
this.parcels.updateData(timeDiff / 1000, this.me.frame, this.agentStore.map.size, this.serverConfig);
}
/**
* Generates desires based on the current state of the game.
* It considers the agent's carried parcels, the distance to the nearest base, and the presence of other agents.
* It calculates potential rewards for picking up parcels and decides on actions such as picking up, depositing, or exploring.
* @description
* This method is responsible for generating the agent's desires based on the current game state.
* It evaluates the agent's carried parcels, calculates potential rewards for picking up new parcels, and considers the distance to the nearest base.
* It also checks for the presence of other agents and their actions, allowing the agent to make informed decisions about its next actions.
* The desires are sorted by score, with higher scores indicating more desirable actions.
* The method also handles special cases, such as dropping parcels when close to a mate agent or moving away if another agent is stuck.
* The generated desires are stored in the `this.desires` array, which is later filtered to create intentions for the agent to act upon.
* @example
* // Generate desires based on the current game state
* agent.generateDesires();
* @returns {void}
*/
generateDesires() {
this.desires = [];
let myParcels = this.parcels.carried(this.me.id);
let carried_value = myParcels.reduce((sum, parcel) => sum + parcel.reward, 0);
let carried_count = myParcels.length;
const clockPenalty = this.serverConfig.clock / 1000;
const roundedMe = {x : Math.round(this.me.x), y : Math.round(this.me.y)};
const roundedMate = {x : Math.round(this.mate.x), y : Math.round(this.mate.y)};
//If we have a mate close to us, drop carried parcels
if (this.mapStore.distance(roundedMe, roundedMate) <= 1 && carried_count > 0) {
let [base, minDist] = this.mapStore.nearestBase(this.me);
let [baseMate, minDistMate] = this.mapStore.nearestBase(this.mate);
if (minDist > minDistMate) {
this.desires.push({ type: INTENTIONS.DROP_AND_GO_AWAY, score: gameConfig.EXCHANGE_PARCELS });
return;
}
}
// Move away if other agent is stuck
if (this.communication.moveAwayAgentId === this.me.id) {
this.desires.push({ type: INTENTIONS.GO_AWAY, score: gameConfig.FREE_MATE });
return;
}
// For all parcels available, calculate potential reward
for (const p of this.parcels.available) {
let pickUpScoreMaster = -1;
let pickUpScoreSlave = -1;
// If is the parcel dropped, do not consider (score is -1)
if (p.x !== this.communication.droppedCoord.x || p.y !== this.communication.droppedCoord.y
|| this.communication.agentToPickup === this.me.id
|| (p.x === this.me.x && p.y === this.me.y)) {
// Calculate distance
const distanceToParcel = this.mapStore.distance(roundedMe, p);
// If parcel is below us, pickup (might see some other parcel that is more valuable and leave that on the ground)
if (distanceToParcel === 0) {
this.desires.push({ type: INTENTIONS.GO_PICKUP, parcel: p, score: gameConfig.PICKUP_NEAR_PARCEL });
continue;
}
p.calculatePotentialPickUpReward(roundedMe, this.isMaster, carried_value, carried_count, this.mapStore, clockPenalty, this.serverConfig);
p.calculatePotentialPickUpReward(roundedMate, !this.isMaster, carried_value, carried_count, this.mapStore, clockPenalty, this.serverConfig);
pickUpScoreMaster = p.potentialPickUpReward;
pickUpScoreSlave = p.potentialPickUpRewardSlave;
}
if (this.isMaster === true) {
if (pickUpScoreMaster >= pickUpScoreSlave) {
this.desires.push({ type: INTENTIONS.GO_PICKUP, parcel: p, score: pickUpScoreMaster });
}
}
else {
if (pickUpScoreSlave > pickUpScoreMaster) {
this.desires.push({ type: INTENTIONS.GO_PICKUP, parcel: p, score: pickUpScoreSlave });
}
}
}
// Dropped parcels
if (this.communication.droppedValue > 0 && this.communication.agentToPickup === this.me.id) {
const dropPickupReward = getPickupScore(this.me, this.communication.droppedCoord, carried_value, carried_count,
this.communication.droppedValue, this.communication.droppedQuantity,
this.communication.droppedBaseDistance, clockPenalty, this.mapStore, this.serverConfig);
this.desires.push({ type: INTENTIONS.GO_PICKUP, parcel: this.communication.droppedCoord, score: dropPickupReward, isFromDropped : true });
}
//If we have parcels, consider deposit option
if (carried_count > 0) {
let [base, minDist] = this.mapStore.nearestBase(this.me);
// If we are on a base, drop instantly (might see some other parcel that is more valuable and not drop the parcels)
if (minDist === 0) {
this.desires.push({ type: INTENTIONS.GO_DEPOSIT, score: gameConfig.DEPOSIT_INSTANTLY });
}
else {
let deposit_score = carried_value - minDist * carried_count * clockPenalty / this.serverConfig.parcels_decaying_interval;
this.desires.push({ type: INTENTIONS.GO_DEPOSIT, score: deposit_score });
}
}
// Explore
this.desires.push({ type: INTENTIONS.EXPLORE, score: 0.0001 });
}
/**
* Filters the desires into intentions based on their scores.
* It sorts the desires in descending order of score and updates the intentions array.
* @description
* This method filters the agent's desires into intentions by sorting them based on their scores.
* It ensures that the most desirable actions are prioritized for execution.
* The intentions are sorted in descending order, with higher scores indicating more desirable actions.
* The filtered intentions are stored in the `this.intentions` array, which is later used to determine the agent's actions.
* @example
* // Filter the desires into intentions
* agent.filterIntentions();
* @returns {void}
*/
filterIntentions() {
this.intentions = this.desires.sort((a, b) => { return b.score - a.score });
}
/**
* Executes the agent's actions based on the current intentions.
* It iterates through the intentions and performs actions such as picking up parcels, depositing them, exploring, or moving away.
* It checks for conditions such as whether the intention is equal to the last intention and handles agent collisions.
* @description
* This method is responsible for executing the agent's actions based on its current intentions.
* It iterates through the intentions and performs actions such as picking up parcels, depositing them, exploring the map, or moving away from other agents.
* It checks for conditions such as whether the current intention is equal to the last intention and handles agent collisions.
* The method uses helper methods to achieve specific actions, such as `achievePickup`, `achieveDeposit`, `achieveDropAndGoAway`, `achieveGoAway`, and `achieveExplore`.
* The agent's actions are performed in a sequential manner, ensuring that it follows its intentions and updates its state accordingly.
*/
async act() {
for (let intentionIndex = 0; intentionIndex < this.intentions.length; intentionIndex++) {
// Get current intention
const intention = this.intentions[intentionIndex];
let isEqualToLastIntention = intention.type === this.lastIntention.type;
switch (intention.type) {
// If pickup -> check if there are other agents
case INTENTIONS.GO_PICKUP:
isEqualToLastIntention = isEqualToLastIntention && intention.parcel.id === this.lastIntention.parcel.id;
let p = intention.parcel;
let visibleAgents = this.agentStore.visible(this.me, this.serverConfig);
if (visibleAgents.length > 0) {
let canPickup = true;
const myDist = this.mapStore.distance(this.me, p)
for (let a of visibleAgents) {
const agentDist = this.mapStore.distance(a, p);
if (agentDist < myDist && (agentDist <= 1 || goingTowardsParcel(a, p))) {
canPickup = false;
break;
}
}
if (!canPickup) {
// Drop the parcel and pick the next intention in order
continue;
}
}
this.lastIntention = intention;
// If program is here, the parcel can be pickup by us
const isFromDropped = intention.hasOwnProperty("isFromDropped") && intention.isFromDropped;
return this.achievePickup(p, isEqualToLastIntention, isFromDropped);
case INTENTIONS.GO_DEPOSIT:
this.lastIntention = intention;
return this.achieveDeposit(isEqualToLastIntention);
case INTENTIONS.DROP_AND_GO_AWAY:
this.lastIntention = intention;
return this.achieveDropAndGoAway();
case INTENTIONS.GO_AWAY:
this.lastIntention = intention;
return this.achieveGoAway();
case INTENTIONS.EXPLORE:
this.lastIntention = intention;
return this.achieveExplore(isEqualToLastIntention);
default:
this.lastIntention = intention;
return;
}
}
}
/**
* Achieves the pickup action by moving towards the parcel and picking it up.
* It checks if the intention is equal to the last intention and handles the pickup accordingly.
* @param {Object} p - The parcel to be picked up.
* @param {boolean} isEqualToLastIntention - Flag to check if the intention is equal to the last intention.
* @param {boolean} isFromDropped - Flag to check if the pickup is from a dropped parcel.
* @description
* This method is responsible for achieving the pickup action by moving towards the specified parcel and picking it up.
* It checks if the current intention is equal to the last intention and updates the path accordingly.
* If the agent is already at the parcel's location, it emits a pickup event to the server.
* If the pickup is from a dropped parcel, it resets the drop communication.
* The method uses the `oneStepCheckAgents` method to check for collisions with other agents before performing the pickup action.
* The agent's state is updated to reflect the pickup action, and it logs the action based on whether it is a master or slave agent.
*/
async achievePickup(p, isEqualToLastIntention, isFromDropped) {
if (this.isMaster) {
this.log(LOG_LEVELS.MASTER, "GO_PICKUP");
}
else {
this.log(LOG_LEVELS.SLAVE, "GO_PICKUP");
}
// Move towards the parcel and pick up
if (!isEqualToLastIntention) {
this.getNewPath(p);
}
this.oneStepCheckAgents(p);
// this.oneStep();
if (this.me.x === p.x && this.me.y === p.y) {
await this.client.emitPickup();
if (isFromDropped) {
this.communication.resetDrop();
}
}
}
/**
* Achieves the deposit action by moving to the nearest base and depositing carried parcels.
* It checks if the intention is equal to the last intention and handles the deposit accordingly.
* @param {boolean} isEqualToLastIntention - Flag to check if the intention is equal to the last intention.
* @description
* This method is responsible for achieving the deposit action by moving to the nearest base and depositing the carried parcels.
* It checks if the current intention is equal to the last intention and updates the path to the nearest base accordingly.
* If the agent is already at the base's location, it emits a putdown event to the server.
* The method uses the `oneStepCheckAgents` method to check for collisions with other agents before performing the deposit action.
* The agent's state is updated to reflect the deposit action, and it logs the action based on whether it is a master or slave agent.
*/
async achieveDeposit(isEqualToLastIntention) {
if (this.isMaster) {
this.log(LOG_LEVELS.MASTER, "GO_DEPOSIT");}
else {
this.log(LOG_LEVELS.SLAVE, "GO_DEPOSIT");
}
// Use helper to move to nearest base
if (!isEqualToLastIntention) {
let [base, minDist] = this.mapStore.nearestBase(this.me);
this.getBasePath(base);
this.currentNearestBase = base;
}
this.oneStepCheckAgents(null);
if (this.me.x === this.currentNearestBase.x && this.me.y === this.currentNearestBase.y) {
this.isMoving = true;
this.log(LOG_LEVELS.ACTION, "PUTDOWN");
await this.client.emitPutdown(this.parcels, this.me.id);
this.isMoving = false;
}
}
/**
* Achieves the drop and go away action by dropping carried parcels and moving away.
* It checks if there are possible moves and handles the drop and go away action accordingly.
* @description
* This method is responsible for achieving the drop and go away action by dropping the carried parcels and moving away from the current location.
* It checks if there are possible moves available and, if so, sets the dropped state, puts down the parcels, and moves away.
* If there are no possible moves, it communicates with the mate agent to move away.
* The method updates the agent's state to reflect the drop and go away action, and it logs the action based on whether it is a master or slave agent.
*/
async achieveDropAndGoAway() {
const myParcels = this.parcels.carried(this.me.id);
const carried_value = myParcels.reduce((sum, parcel) => sum + parcel.reward, 0);
// Check if there's a possibility to move
const possibleMoves = getNearTiles(this.mapStore, this.me, this.mate, {x : undefined, y : undefined});
if (possibleMoves.length === 0) {
// Tell the other agent to move away
this.communication.moveAwayAgentId = this.mate.id;
return;
}
// 1. Set dropped
// 2. Putdown parcels
// 3. Go away
this.isMoving = true;
this.communication.setDropped(this.me, carried_value, myParcels.length, this.mate.id, this.mapStore);
await this.client.emitPutdown(this.parcels, this.me.id);
await goAway(this.client, this.me, this.mate, this.mapStore);
this.isMoving = false;
}
/**
* Achieves the go away action by moving away from the current location.
* It checks if there are possible moves and handles the go away action accordingly.
* @description
* This method is responsible for achieving the go away action by moving away from the current location.
* It checks if there are possible moves available and, if so, moves away from the current location.
* If there are no possible moves, it communicates with the mate agent to move away.
* The method updates the agent's state to reflect the go away action, and it logs the action based on whether it is a master or slave agent.
*/
async achieveGoAway() {
this.isMoving = true;
await goAway(this.client, this.me, this.mate, this.mapStore);
this.isMoving = false;
this.communication.moveAwayAgentId = null;
}
/**
* Achieves the explore action by moving to a random spawn tile or camping on the spawn.
* It checks if the intention is equal to the last intention and handles the explore action accordingly.
* @param {boolean} isEqualToLastIntention - Flag to check if the intention is equal to the last intention.
* @description
* This method is responsible for achieving the explore action by moving to a random spawn tile or camping on the spawn.
* It checks if the current intention is equal to the last intention and updates the path to a random spawn tile accordingly.
* If the agent is camping, it saves the camping start time and checks if the camping time has expired.
* If the agent is not camping, it moves to a random spawn tile and checks for collisions with other agents.
* The method updates the agent's state to reflect the explore action, and it logs the action based on whether it is a master or slave agent.
*/
async achieveExplore(isEqualToLastIntention) {
if (this.isMaster === true) {
this.log(LOG_LEVELS.MASTER, "EXPLORE");
}
else {
this.log(LOG_LEVELS.SLAVE, "EXPLORE");
}
const wasCamping = this.isCamping;
const isOnSpawn = this.mapStore.map.get(coord2Key(this.me)) === TILE_TYPES.SPAWN;
const spawnIsSparse = this.mapStore.isSpawnSparse;
if (wasCamping) {
const secondsElapsed = (Date.now() - this.campingStartTime) / 1000;
this.isCamping = spawnIsSparse && (secondsElapsed < config.CAMP_TIME);
}
else {
this.isCamping = !this.isExploring && spawnIsSparse && isOnSpawn;
}
// If spawn is not sparse OR we are not on a green tile
if (!this.isCamping) {
if (!isEqualToLastIntention || wasCamping) {
let spawnTileCoord = this.mapStore.randomSpawnTile(this.me);
this.isExploring = true;
this.getNewPath(spawnTileCoord);
}
this.oneStepCheckAgents(null);
if (this.pathIndex >= this.path.length) {
this.isExploring = false;
}
}
// Camping behaviour --> save time but do nothing
else if (!wasCamping) {
this.campingStartTime = Date.now();
}
}
/**
* Performs a one-step check for agents in the next tile of the path.
* It checks if any agent is in the tile the agent wants to go to and handles collisions accordingly.
* If a collision is detected, it sets the colliding state and starts a timer.
* If the timer expires, it gets a new path or base path based on the last intention.
* If everything is clear, it performs one step of the agent path.
* @param {{x : number, y : number}} newPathTile - The new path tile to check for agents.
* @description
* This method performs a one-step check for agents in the next tile of the path.
* It checks if any agent is in the tile the agent wants to go to and handles collisions accordingly.
* If a collision is detected, it sets the colliding state and starts a timer.
* If the timer expires, it gets a new path or base path based on the last intention.
* If everything is clear, it performs one step of the agent path.
* The method updates the agent's state to reflect the one-step check, and it logs the action based on whether it is a master or slave agent.
*/
async oneStepCheckAgents(newPathTile) {
if (this.pathIndex >= this.path.length) {
this.lastIntention = { type: null };
return;
}
const visibleAgents = this.agentStore.visible(this.me, this.serverConfig);
// Check if any agent is in the tile i want to go
const nextTile = this.path[this.pathIndex];
for (let a of visibleAgents) {
// If there's an agent in the tile i want to go
if (a.x === nextTile.x && a.y === nextTile.y) {
// Set colliding (only first time)
if (!this.isColliding) {
this.isColliding = true;
this.agentCollisionStartTime = Date.now();
}
// After timer expires -> get new path
const secondsElapsed = (Date.now() - this.agentCollisionStartTime) / 1000;
if (secondsElapsed > config.AGENT_TIME) {
this.isColliding = false;
switch (this.lastIntention.type) {
case INTENTIONS.GO_PICKUP :
break;
case INTENTIONS.GO_DEPOSIT :
const [base, minDist] = this.mapStore.nearestBase(this.me);
this.currentNearestBase = base;
newPathTile = base;
break;
case INTENTIONS.EXPLORE :
const spawnTileCoord = this.mapStore.randomSpawnTile(this.me);
this.isExploring = true;
newPathTile = spawnTileCoord;
break;
default :
break;
}
if (this.lastIntention.type === INTENTIONS.GO_DEPOSIT) {
this.getBasePath(newPathTile);
}
else {
this.getNewPath(newPathTile);
}
}
return;
}
}
this.isColliding = false;
// If everything is clear -> move
await this.oneStep();
}
/**
* Performs one step of the agent path.
* It moves the agent towards the next tile in the path and updates the path index.
* If the agent is already at the destination tile, it does nothing.
* @description
* This method performs one step of the agent path by moving the agent towards the next tile in the path.
* It checks if the agent is already at the destination tile and does nothing if it is.
* If the agent is not at the destination tile, it calculates the direction to move and performs the move action.
* The method updates the agent's state to reflect the movement and increments the path index.
* It also sets the moving state to false after the move is completed.
* The method logs the action based on whether it is a master or slave agent.
* @example
* agent.oneStep();
*/
async oneStep() {
if (this.pathIndex >= this.path.length) {
this.lastIntention = { type: null };
return;
}
this.isMoving = true;
const dir = direction(this.me, this.path[this.pathIndex]);
if (dir) {
this.log(LOG_LEVELS.ACTION, "Moving ", dir);
await moveAndWait(this.client, this.me, dir);
}
this.isMoving = false;
this.pathIndex++;
}
/**
* Gets the A* path to the target tile.
* It initializes the path index and calculates the path using the astarSearch function.
* @param {{x : number, y : number}} target - The target tile to get the path to.
* @description
* This method is responsible for getting the A* path to the specified target tile.
* It initializes the path index to 0 and calculates the path using the astarSearch function.
* The calculated path is stored in the `this.path` variable, which is later used for movement actions.
* The method ensures that the agent has a valid path to follow towards the target tile.
* @example
* agent.getPath({ x: 5, y: 10 });
*/
getPath(target) {
this.pathIndex = 0;
this.path = astarSearch({ x: Math.round(this.me.x), y: Math.round(this.me.y) }, target, this.mapStore);
}
/**
* Gets a new A* path to the target tile, removing visible agents from the map.
* It initializes the path index and calculates the path using the astarSearch function.
* @param {{x : number, y : number}} target - The target tile to get the path to.
* @description
* This method is responsible for getting a new A* path to the specified target tile.
* It removes visible agents from the map to ensure that the pathfinding algorithm does not consider them as obstacles.
* The calculated path is stored in the `this.path` variable, which is later used for movement actions.
* The method ensures that the agent has a valid path to follow towards the target tile, even in the presence of other agents.
* @example
* agent.getNewPath({ x: 5, y: 10 });
*/
getNewPath(target) {
this.pathIndex = 0;
let tileMapTemp = new Map();
// Remove tiles with agents
for (const a of this.agentStore.visible(this.me, this.serverConfig)) {
let type = this.mapStore.setType(a, TILE_TYPES.EMPTY);
tileMapTemp.set(coord2Key(a), type);
}
this.path = astarSearch({ x: Math.round(this.me.x), y: Math.round(this.me.y) }, target, this.mapStore);
// Re-add tiles
for (const [key, value] of tileMapTemp) {
let tile = key2Coord(key);
this.mapStore.setType(tile, value);
}
}
/**
* Gets the base path to the nearest base tile.
* It checks if the agent is already at the base tile and returns if so.
* If the agent is not at the base tile, it gets a new path to the nearest base using the getNewPath method.
* If the base tile is not found, it removes the base from the map and sets a timer to restore it later.
* @param {{x : number, y : number}} target - The target tile to get the base path to.
* @description
* This method is responsible for getting the base path to the nearest base tile.
* It checks if the agent is already at the base tile and returns if so.
* If the agent is not at the base tile, it gets a new path to the nearest base using the getNewPath method.
* If the base tile is not found, it removes the base from the map and sets a timer to restore it later.
* The method ensures that the agent has a valid path to follow towards the nearest base tile, even if the base is temporarily removed from the map.
* @example
* agent.getBasePath({ x: 5, y: 10 });
*/
getBasePath(target) {
let tries = 0;
do {
// Check if #tries expired
if (tries % config.BASE_TRIES === 0 && tries > 0) {
// Delete base from map
this.mapStore.setType(target, TILE_TYPES.EMPTY);
// Re-add seconds later
const restoreTarget = { x: target.x, y: target.y }; // snapshot
setTimeout(() => {
this.mapStore.setType(restoreTarget, TILE_TYPES.BASE);
}, config.BASE_REMOVAL_TIME);
// Find new target
const [base, minDist] = this.mapStore.nearestBase(this.me);
if (base === null || base === undefined) {
return;
}
this.currentNearestBase = base;
target = base;
}
if (this.me.x === target.x && this.me.y === target.y)
return;
// Check if it exists a path to the nearest base (without the one removed from the map)
this.getNewPath(target);
tries++;
} while (this.path.length === 0 && tries < config.BASE_SWITCH_MAX_TRIES);
}
}