package ch.epfl.maze.physical; import ch.epfl.maze.util.Direction; import ch.epfl.maze.util.Vector2D; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * World that is represented by a labyrinth of tiles in which an {@code Animal} * can move. * * @author EPFL * @author Pacien TRAN-GIRARD */ public abstract class World { /* tiles constants */ public static final int FREE = 0; public static final int WALL = 1; public static final int START = 2; public static final int EXIT = 3; public static final int NOTHING = -1; /** * Structure of the labyrinth, an NxM array of tiles */ private final int[][] labyrinth; private final Vector2D start; private final Vector2D exit; /** * Constructs a new world with a labyrinth. The labyrinth must be rectangle. * * @param labyrinth Structure of the labyrinth, an NxM array of tiles */ public World(int[][] labyrinth) { this.labyrinth = labyrinth; this.start = this.findFirstTileOfType(World.START); this.exit = this.findFirstTileOfType(World.EXIT); } /** * Finds the coordinates of the first occurrence of the given tile type. * * @param tileType Type of the tile * @return A Vector2D of the first occurrence of the given tile type */ private Vector2D findFirstTileOfType(int tileType) { for (int x = 0; x < this.getWidth(); ++x) for (int y = 0; y < this.getHeight(); ++y) if (this.getTile(x, y) == tileType) return new Vector2D(x, y); return null; } /** * Determines whether the labyrinth has been solved by every animal. * * @return true if no more moves can be made, false otherwise */ abstract public boolean isSolved(); /** * Resets the world as when it was instantiated. */ abstract public void reset(); /** * Returns a copy of the set of all current animals in the world. * * @return A set of all animals in the world */ public Set getAnimalSet() { return null; } /** * Returns a copy of the list of all current animals in the world. * * @return A list of all animals in the world * @implNote Not abstract for compatibility purpose (in order not to break tests) * @deprecated Use getAnimalSet() instead */ public List getAnimals() { return new ArrayList<>(this.getAnimalSet()); } /** * Checks in a safe way the tile number at position (x, y) in the labyrinth. * * @param x Horizontal coordinate * @param y Vertical coordinate * @return The tile number at position (x, y), or the NOTHING tile if x or y is * incorrect. */ public final int getTile(int x, int y) { if (x < 0 || x >= this.getWidth()) return World.NOTHING; if (y < 0 || y >= this.getHeight()) return World.NOTHING; return this.labyrinth[y][x]; } /** * Determines if coordinates are free to walk on. * * @param x Horizontal coordinate * @param y Vertical coordinate * @return true if an animal can walk on tile, false otherwise */ public final boolean isFree(int x, int y) { int tile = this.getTile(x, y); return !(tile == World.WALL || tile == World.NOTHING); } /** * Determines if coordinates are free to walk on. * * @param position The position vector * @return true if an animal can walk on tile, false otherwise */ public final boolean isFree(Vector2D position) { return this.isFree(position.getX(), position.getY()); } /** * Computes and returns the available choices for a position in the * labyrinth. The result will be typically used by {@code Animal} in * {@link ch.epfl.maze.physical.Animal#move(Set) move(Set)} * * @param position A position in the maze * @return A set of all available choices at a position */ public final Set getChoiceSet(Vector2D position) { Set freeDirections = Direction.MOVING_DIRECTIONS .stream() .filter(dir -> this.isFree(position.addDirectionTo(dir))) .collect(Collectors.toSet()); return freeDirections.isEmpty() ? EnumSet.of(Direction.NONE) : freeDirections; } /** * Computes and returns the available choices for a position in the * labyrinth. The result will be typically used by {@code Animal} in * {@link ch.epfl.maze.physical.Animal#move(Direction[]) move(Direction[])} * * @param position A position in the maze * @return An array of all available choices at a position * @deprecated Use @code{Set getChoiceSet(Vector2D position)} instead */ public final Direction[] getChoices(Vector2D position) { Set choiceSet = this.getChoiceSet(position); return choiceSet.toArray(new Direction[choiceSet.size()]); } /** * Returns horizontal length of labyrinth. * * @return The horizontal length of the labyrinth */ public final int getWidth() { return this.labyrinth[0].length; } /** * Returns vertical length of labyrinth. * * @return The vertical length of the labyrinth */ public final int getHeight() { return this.labyrinth.length; } /** * Returns the entrance of the labyrinth at which animals should begin when * added. * * @return Start position of the labyrinth, null if none. */ public final Vector2D getStart() { return this.start; } /** * Returns the exit of the labyrinth at which animals should be removed. * * @return Exit position of the labyrinth, null if none. */ public final Vector2D getExit() { return this.exit; } }