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;
}
}