diff --git a/src/main/java/inf112/fiasko/roborally/objects/Board.java b/src/main/java/inf112/fiasko/roborally/objects/Board.java index 8453e59..ea63450 100644 --- a/src/main/java/inf112/fiasko/roborally/objects/Board.java +++ b/src/main/java/inf112/fiasko/roborally/objects/Board.java @@ -1,36 +1,211 @@ package inf112.fiasko.roborally.objects; +import inf112.fiasko.roborally.element_properties.Direction; +import inf112.fiasko.roborally.element_properties.Position; +import inf112.fiasko.roborally.element_properties.RobotID; +import inf112.fiasko.roborally.element_properties.TileType; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * This class represents a board */ - public class Board { - private Grid walls; - private Grid otherObjects; - private ArrayList deadRobots = new ArrayList<>(); - private ArrayList aliveRobots; + private int boardHeight; + private int boardWidth; + private IGrid walls; + private IGrid tiles; + private Map robots; + private List deadRobots; /** * Initializes the board - * @param walls a grid containing all the walls - * @param otherObjects a grid containing all the other Objects like flags and conveyor belts - * @param aliveRobots a list of all the robots that are currently alive + * @param tiles A grid containing all tiles + * @param walls A grid containing all walls + * @param robots A list of all robots in the game */ - public Board(Grid walls, Grid otherObjects, ArrayList aliveRobots){ - this.walls=walls; - this.otherObjects=otherObjects; - this.aliveRobots=aliveRobots; + public Board(IGrid tiles, IGrid walls, List robots) { + if (walls.getWidth() != tiles.getWidth() || walls.getHeight() != tiles.getHeight()) { + throw new IllegalArgumentException("The grids in the input don't have the same dimensions."); + } + this.robots = new HashMap<>(); + for (Robot robot : robots) { + if (this.robots.get(robot.getRobotId()) != null) { + throw new IllegalArgumentException("There can't be two robots with the same robot id."); + } + this.robots.put(robot.getRobotId(), robot); + } + this.deadRobots = new ArrayList<>(); + this.boardWidth = tiles.getWidth(); + this.boardHeight = tiles.getHeight(); + this.walls = walls; + this.tiles = tiles; } /** - * Removes a dead robot from the board over to the dead robot list. + * Gets the height of the board + * @return The height of the board + */ + public int getBoardHeight() { + return boardHeight; + } + + /** + * Gets the width of the board + * @return The width of the board + */ + public int getBoardWidth() { + return boardWidth; + } + + /** + * Moves all dead robots to their backups and makes them part of the board again + */ + public void respawnRobots() { + //TODO: Account for several robots re-spawning at same backup + for (Robot robot : deadRobots) { + robot.setPosition(robot.getBackupPosition()); + robots.put(robot.getRobotId(), robot); + } + deadRobots = new ArrayList<>(); + } + + /** + * Removes a dead robot from the board over to the dead robot list * @param robot the dead robot */ - public void removeDeadRobotFromBoard(Robot robot){ - aliveRobots.remove(robot); + public void removeDeadRobotFromBoard(Robot robot) { + robots.remove(robot.getRobotId()); deadRobots.add(robot); } + + /** + * Moves a robot one unit in a specified direction + * @param robotID ID of the robot to move + * @param direction The direction to move the robot + * @return True if the robot moved away from its old position + */ + public boolean moveRobot(RobotID robotID, Direction direction) { + Robot robot = robots.get(robotID); + Position robotPosition = robot.getPosition(); + Position newPosition = getNewPosition(robotPosition, direction); + //Robot tried to go outside of the map. Kill it. + if (newPosition.getXCoordinate() < 0 + || newPosition.getXCoordinate() >= boardWidth + || newPosition.getYCoordinate() < 0 + || newPosition.getYCoordinate() >= boardHeight) { + killRobot(robot); + return true; + } + //There is a wall blocking the robot. It can't proceed. + if (hasWallFacing(robotPosition, direction) || + hasWallFacing(newPosition, Direction.getReverseDirection(direction))) { + return false; + } + //If another robot is blocking this robot's path, try to shove it. + if (hasRobotOnPosition(newPosition)) { + RobotID otherRobotID = getRobotOnPosition(newPosition); + if (otherRobotID != null && !moveRobot(otherRobotID, direction)) { + return false; + } + } + //Some tiles may kill the robot if stepped on. + Tile tileRobotStepsOn = tiles.getElement(newPosition.getXCoordinate(), newPosition.getYCoordinate()); + if (tileRobotStepsOn == null) { + throw new IllegalArgumentException("The game board is missing a tile. This should not happen."); + } + TileType tileTypeRobotStepsOn = tileRobotStepsOn.getTileType(); + if (tileTypeRobotStepsOn == TileType.HOLE || tileTypeRobotStepsOn == TileType.DEATH_TILE) { + killRobot(robot); + return true; + } + robot.setPosition(newPosition); + return true; + } + + /** + * Kills the robot + * + * If the robot steps outside of the board, steps on a hole or takes too much damage, this method should be used to + * properly dispose of the robot until the next round. + * + * @param robot The robot to kill + */ + private void killRobot(Robot robot) { + //TODO: Must remove a life from the robot/player + removeDeadRobotFromBoard(robot); + } + + /** + * Returns a robot id for a robot on a specific position if such a robot exists + * @param position The position to check + * @return The robot id of the robot on the position or null if there is no robot there + */ + private RobotID getRobotOnPosition(Position position) { + for (RobotID robotID : robots.keySet()) { + Robot robot = robots.get(robotID); + if (position.equals(robot.getPosition())) { + return robotID; + } + } + return null; + } + + /** + * Checks whether there exists a robot on a specific position + * @param position The position to check + * @return True if there is a robot on the specified position + */ + private boolean hasRobotOnPosition(Position position) { + return getRobotOnPosition(position) != null; + } + + /** + * Checks whether a position has a wall facing a specific direction + * @param position The position to check + * @param direction The direction of the wall to check for + * @return True if there is a wall on the position facing the input direction + */ + private boolean hasWallFacing(Position position, Direction direction) { + Wall wall = walls.getElement(position.getXCoordinate(), position.getYCoordinate()); + if (wall == null) { + return false; + } + switch (wall.getDirection()) { + case NORTH_EAST: + return direction == Direction.NORTH || direction == Direction.EAST; + case NORTH_WEST: + return direction == Direction.NORTH || direction == Direction.WEST; + case SOUTH_WEST: + return direction == Direction.SOUTH || direction == Direction.WEST; + case SOUTH_EAST: + return direction == Direction.SOUTH || direction == Direction.EAST; + } + return wall.getDirection() == direction; + } + + /** + * Gets the position 1 unit in a specific direction from another position + * @param oldPosition The old/current position of the element + * @param direction The direction to move the element + * @return The new position of the element + */ + private Position getNewPosition(Position oldPosition, Direction direction) { + //TODO: Make sure we're accounting for the flipped y axis in libgdx + switch (direction) { + case NORTH: + return new Position(oldPosition.getXCoordinate(), oldPosition.getYCoordinate() + 1); + case SOUTH: + return new Position(oldPosition.getXCoordinate(), oldPosition.getYCoordinate() - 1); + case EAST: + return new Position(oldPosition.getXCoordinate() + 1, oldPosition.getYCoordinate()); + case WEST: + return new Position(oldPosition.getXCoordinate() - 1, oldPosition.getYCoordinate()); + default: + throw new IllegalArgumentException("It's not possible to move in that direction."); + } + } } diff --git a/src/test/java/inf112/fiasko/roborally/objects/BoardTest.java b/src/test/java/inf112/fiasko/roborally/objects/BoardTest.java new file mode 100644 index 0000000..02c3aad --- /dev/null +++ b/src/test/java/inf112/fiasko/roborally/objects/BoardTest.java @@ -0,0 +1,65 @@ +package inf112.fiasko.roborally.objects; + +import inf112.fiasko.roborally.element_properties.*; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BoardTest { + private Grid tileGrid; + private Grid wallGrid; + private Position someValidPosition; + private List robotList; + private Board board; + + @Before + public void setUp() { + tileGrid = new Grid<>(5, 5, new Tile(TileType.TILE, Direction.NORTH)); + wallGrid = new Grid<>(5, 5); + someValidPosition = new Position(2, 2); + robotList = new ArrayList<>(); + robotList.add(new Robot(RobotID.ROBOT_1, someValidPosition)); + wallGrid.setElement(2, 3, new Wall(WallType.WALL_NORMAL, Direction.SOUTH)); + wallGrid.setElement(2, 2, new Wall(WallType.WALL_NORMAL, Direction.EAST)); + wallGrid.setElement(1, 2, new Wall(WallType.WALL_CORNER, Direction.NORTH_EAST)); + board = new Board(tileGrid, wallGrid, robotList); + } + + @Test + public void robotCanMove() { + assertTrue(board.moveRobot(RobotID.ROBOT_1, Direction.SOUTH)); + } + + @Test + public void robotIsStoppedByWallOnSameTile() { + assertFalse(board.moveRobot(RobotID.ROBOT_1, Direction.EAST)); + } + + @Test + public void robotIsStoppedByWallOnAdjacentTile() { + assertFalse(board.moveRobot(RobotID.ROBOT_1, Direction.NORTH)); + } + + @Test + public void robotIsStoppedByCornerWall() { + assertFalse(board.moveRobot(RobotID.ROBOT_1, Direction.WEST)); + } + + @Test (expected = IllegalArgumentException.class) + public void gridsOfDifferentSizeThrowsError() { + IGrid wallGrid = new Grid<>(1, 1); + new Board(tileGrid, wallGrid, robotList); + } + + @Test (expected = IllegalArgumentException.class) + public void multipleRobotsWithSameIDThrowsError() { + Robot robot = new Robot(RobotID.ROBOT_1, someValidPosition); + robotList.add(robot); + new Board(tileGrid, wallGrid, robotList); + } +}