src_actions_movement.js

import DeliverooClient from "../api/deliverooClient.js";
import { MapStore } from "../models/mapStore.js";
import { Me } from "../models/me.js";
import { astarSearch, direction } from "../utils/astar.js";
import { DIRECTIONS, oppositeDirection } from "../utils/directions.js";
import { coord2Key } from "../utils/hashMap.js";
import { isWalkableTile, TILE_TYPES } from "../utils/tile.js";
import gameConfig from "../utils/gameConfig.js";
import { ParcelsStore } from "../models/parcelsStore.js";

/**
 * Moves the agent in the specified direction and waits for the move to complete.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {string} dir - The direction to move in (e.g., 'UP', 'DOWN', 'LEFT', 'RIGHT').
 * @returns {Promise<void>} - A promise that resolves when the move is complete.
 * @description
 * This function emits a move command to the Deliveroo client and waits for the
 * player's state to update with the new position. It listens for the 'onYou' event
 * to confirm that the move has been processed and the player's position has been updated.
 */
export async function moveAndWait(client, me, dir) {
  const moved = new Promise(resolve => {
    client.onYou((state, time) => {
      if (Number.isInteger(state.x) && Number.isInteger(state.y)) {
        me.update(state, time);
        resolve();
      }
    });
  });
  await client.emitMove(dir);
  await moved;
}

/**
 * Moves the agent towards the target position using A* search algorithm.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {{x: number, y: number}} target - The target position to move towards.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<void>} - A promise that resolves when the movement is complete.
 * @description
 * This function calculates the path from the current position to the target position
 * using the A* search algorithm. It then iterates through each step in the path,
 * determines the direction to move in, and calls the `moveAndWait` function to 
 * move the agent step by step.
 */
export async function smartMove(client, me, target, mapStore) {
  const path = astarSearch( me, target, mapStore);
  for (const step of path) {
    const dir = direction(me, step);
    if (!dir) continue;
    
    await moveAndWait(client, me, dir);
  }
}

/**
 * Moves the agent to the nearest base using smart movement.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<void>} - A promise that resolves when the movement is complete.
 * @description
 * This function finds the nearest base on the map and moves the agent towards it
 * using the `smartMove` function. If a base is found, it will navigate to that base
 * efficiently, taking into account the current map state and obstacles.
 */
export async function smartMoveToNearestBase(client, me, mapStore) {
  const [base] = mapStore.nearestBase(me);
  if (base) {
    await smartMove(client, me, base, mapStore);
  }

}
/**
 * Moves the agent to the nearest base and puts down all carried parcels.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @param {ParcelsStore} parcels - The list of parcels to be put down.
 * @returns {Promise<void>} - A promise that resolves when the movement and put down action are complete.
 * @description
 * This function finds the nearest base on the map and moves the agent towards it
 * using the `smartMove` function. Once the agent reaches the base, it emits a put down
 * command for all carried parcels. This is useful for efficiently delivering parcels
 * to the nearest base, ensuring that the agent can quickly return to the delivery process.
 */
export async function smartMoveToNearestBaseAndPutDown(client, me, mapStore, parcels) {
  const [base] = mapStore.nearestBase(me);
  if (base) {
    await smartMove(client, me, base, mapStore);

    // drop off all carried parcels
    if (me.x === base.x && me.y === base.y) {
      client.emitPutdown(parcels, me.id);
    }
  }
}

/**
 * Makes a random move in one direction and then returns to the original position.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<void>} - A promise that resolves when the random move and return are complete.
 * @description
 * This function checks the surrounding tiles for walkable paths and randomly selects one to move in.
 * After moving in the random direction, it immediately moves back to the original position.
 * This is useful for testing movement logic or simulating random behavior in the game.
 */
export async function randomMoveAndBack(client, me, mapStore) {
  let possibleMoves = [];

  const rightCell = mapStore.map.get(coord2Key({x : me.x + 1, y : me.y}))
  const leftCell = mapStore.map.get(coord2Key({x : me.x - 1, y : me.y}))
  const upCell = mapStore.map.get(coord2Key({x : me.x, y : me.y + 1}))
  const downCell = mapStore.map.get(coord2Key({x : me.x, y : me.y - 1}))

  if (isWalkableTile(rightCell))
    possibleMoves.push(DIRECTIONS.RIGHT);
  if (isWalkableTile(leftCell))
    possibleMoves.push(DIRECTIONS.LEFT);
  if (isWalkableTile(upCell))
    possibleMoves.push(DIRECTIONS.UP);
  if (isWalkableTile(downCell))
    possibleMoves.push(DIRECTIONS.DOWN);
  
  const randomDir = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
  const secondDir = oppositeDirection(randomDir);

  await client.emitMove(randomDir);
  await client.emitMove(secondDir);
}


/**
  * Gets the possible near tiles for movement based on the current position of the agent and its mate.
  * @param {MapStore} mapStore - The MapStore instance containing the current map state.
  * @param {Me} me - The current player instance.
  * @param {Me} mate - The mate player instance.
  * @param {{x: number, y: number}} myPos - The current position of the agent.
  * @returns {string[]} - An array of possible movement directions (e.g., ['UP', 'DOWN']).
 */
export function getNearTiles(mapStore, me, mate, myPos) {
  let possibleMoves = [];

  const rightCell = { x: me.x + 1, y: me.y }
  const leftCell = { x: me.x - 1, y: me.y }
  const upCell = { x: me.x, y: me.y + 1 }
  const downCell = { x: me.x, y: me.y - 1 }

  const rightCellType = mapStore.map.get(coord2Key(rightCell))
  const leftCellType = mapStore.map.get(coord2Key(leftCell))
  const upCellType = mapStore.map.get(coord2Key(upCell))
  const downCellType = mapStore.map.get(coord2Key(downCell))

  if (isWalkableTile(rightCellType)
      && !(rightCell.x === mate.x && rightCell.y === mate.y)
      && !(rightCell.x === myPos.x && rightCell.y === myPos.y))
    possibleMoves.push(DIRECTIONS.RIGHT);

  if (isWalkableTile(leftCellType)
      && !(leftCell.x === mate.x && leftCell.y === mate.y)
      && !(leftCell.x === myPos.x && leftCell.y === myPos.y))
    possibleMoves.push(DIRECTIONS.LEFT);

  if (isWalkableTile(upCellType)
      && !(upCell.x === mate.x && upCell.y === mate.y) 
      && !(upCell.x === myPos.x && upCell.y === myPos.y))
    possibleMoves.push(DIRECTIONS.UP);

  if (isWalkableTile(downCellType)
      && !(downCell.x === mate.x && downCell.y === mate.y)
      && !(downCell.x === myPos.x && downCell.y === myPos.y))
    possibleMoves.push(DIRECTIONS.DOWN);

  return possibleMoves;
}

/**
 * Moves the agent away from the current position in a random direction for a specified number of moves.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {Me} mate - The mate player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<void>} - A promise that resolves when the movement is complete.
 * @description
 * This function iterates a specified number of times, each time selecting a random direction
 * from the possible near tiles around the agent's current position. It moves the agent in that
 * random direction and updates the agent's position for the next iteration. This is useful for
 * simulating random movement away from the current position, which can be useful in various game scenarios.
 */
export async function goAway(client, me, mate, mapStore) {
  let myPos = {x : undefined, y : undefined};

  for (let i = 0; i < gameConfig.GO_AWAY_MOVES; i++) {
    let possibleMoves = getNearTiles(mapStore, me, mate, myPos);

    const randomDir = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
    
    if (randomDir) {
      myPos = { x: me.x, y: me.y }; // Set my position for the next iteration
      await client.emitMove(randomDir);
    }
  }
}

/**
 * Moves the agent to the nearest base using A* search algorithm.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<boolean>} - Returns true if successfully moved to a new base, false otherwise.
 * @description
 * This function finds the nearest base on the map and uses the A* search algorithm to calculate
 * the path to that base. It then moves the agent in the direction of the first step in the path.
 * If a valid path is found, it moves the agent and returns true. If no base is found or no valid
 * path exists, it logs an error message and returns false.
 */
export async function moveToNearestBase(client, me, mapStore) {
    const [newBase] = mapStore.nearestBase(me);
    if (!newBase) {
        console.log("No bases found");
        return false;
    }

    const fullPath = astarSearch(me, newBase, mapStore);
    if (!fullPath?.length) {
        console.log("No valid path to base");
        return false;
    }

    for (const step of fullPath)
    {
      const dir = direction(me, step);

      if (!dir) {
        console.log("Couldn't determine direction");
        return false;
      }

      await moveAndWait(client, me, dir);
    }

    return true;
}


/**
 * Moves the agent to a random spawn tile using A* search algorithm.
 * @param {DeliverooClient} client - The Deliveroo client instance.
 * @param {Me} me - The current player instance.
 * @param {MapStore} mapStore - The MapStore instance containing the current map state.
 * @returns {Promise<boolean>} - Returns true if successfully moved to a spawn tile, false otherwise.
 * @description
 * This function finds a random spawn tile on the map and uses the A* search algorithm to calculate
 * the path to that tile. It then moves the agent in the direction of each step in the path.
 * If a valid path is found, it moves the agent step by step. If no spawn tile is found or no valid
 * path exists, it logs an error message and returns false. 
 */
export async function easyExplore(client, me, mapStore) {
  let spawnTileCoord = mapStore.randomSpawnTile(me);
  const fullPath = astarSearch(me, spawnTileCoord, mapStore);

  if (!fullPath?.length) {
    console.log("No valid path to spawn tile");
    return false;
  }

  for (const step of fullPath)
  {
    const dir = direction(me, step);

    if (!dir) {
      console.log("Couldn't determine direction");
      return false;
    }

    await moveAndWait(client, me, dir);
  }

  return true;
}