package ch.zhaw.pm2.racetrack;
import ch.zhaw.pm2.racetrack.given.ConfigSpecification;
import ch.zhaw.pm2.racetrack.given.TrackSpecification;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* This class represents the racetrack board.
*
*
The racetrack board consists of a rectangular grid of 'width' columns and 'height' rows.
* The zero point of he grid is at the top left. The x-axis points to the right and the y-axis points downwards.
* Positions on the track grid are specified using {@link PositionVector} objects. These are vectors containing an
* x/y coordinate pair, pointing from the zero-point (top-left) to the addressed space in the grid.
*
* Each position in the grid represents a space which can hold an enum object of type {@link Config.SpaceType}.
* Possible Space types are:
*
* - WALL : road boundary or off track space
* - TRACK: road or open track space
* - FINISH_LEFT, FINISH_RIGHT, FINISH_UP, FINISH_DOWN : finish line spaces which have to be crossed
* in the indicated direction to winn the race.
*
* Beside the board the track contains the list of cars, with their current state (position, velocity, crashed,...)
*
* At initialization the track grid data is read from the given track file. The track data must be a
* rectangular block of text. Empty lines at the start are ignored. Processing stops at the first empty line
* following a non-empty line, or at the end of the file.
* Characters in the line represent SpaceTypes. The mapping of the Characters is as follows:
*
* - WALL : '#'
* - TRACK: ' '
* - FINISH_LEFT : '<'
* - FINISH_RIGHT: '>'
* - FINISH_UP : '^;'
* - FINISH_DOWN: 'v'
* - Any other character indicates the starting position of a car.
* The character acts as the id for the car and must be unique.
* There are 1 to {@link Config#MAX_CARS} allowed.
*
*
* All lines must have the same length, used to initialize the grid width).
* Beginning empty lines are skipped.
* The the tracks ends with the first empty line or the file end.
* An {@link InvalidTrackFormatException} is thrown, if
*
* - not all track lines have the same length
* - the file contains no track lines (grid height is 0)
* - the file contains more than {@link Config#MAX_CARS} cars
*
*
* The Track can return a String representing the current state of the race (including car positons)
*/
public class Track implements TrackSpecification {
public static final char CRASH_INDICATOR = 'X';
private List track;
private List cars;
private final List finishLine;
/**
* Initialize a Track from the given track file.
*
* @param trackFile Reference to a file containing the track data
* @throws FileNotFoundException if the given track file could not be found
* @throws InvalidTrackFormatException if the track file contains invalid data (no tracklines, ...)
*/
public Track(File trackFile) throws FileNotFoundException, InvalidTrackFormatException, PositionVectorNotValid {
track = new ArrayList<>();
cars = new ArrayList<>();
finishLine = new ArrayList<>();
readFile(trackFile);
findFinish();
addCars();
}
/**
* This method reads the File and saves it to the track ArrayList Line by Line
*
* @param trackFile the File where the track has been documented
* @throws FileNotFoundException if the FilePath is invalid.
*/
private void readFile(File trackFile) throws FileNotFoundException {
Scanner scanner = new Scanner(trackFile);
while (scanner.hasNextLine()) {
track.add(scanner.nextLine());
}
}
private void addCars() throws InvalidTrackFormatException {
ConfigSpecification.SpaceType[] spaceTypes = ConfigSpecification.SpaceType.values();
List allSpaceTypesAsChar = new ArrayList<>();
List usedSymbolForCar = new ArrayList<>();
for (ConfigSpecification.SpaceType spaceType : spaceTypes) {
allSpaceTypesAsChar.add(spaceType.getValue());
}
for (int j = 0; j < track.size(); j++) {
String line = track.get(j);
for (int i = 0; i < line.length(); i++) {
char possibleCarChar = line.charAt(i);
if (!allSpaceTypesAsChar.contains(possibleCarChar)) {
if (usedSymbolForCar.contains(possibleCarChar)) {
throw new InvalidTrackFormatException();
}
usedSymbolForCar.add(possibleCarChar);
cars.add(new Car(possibleCarChar, new PositionVector(i, j)));
}
}
}
}
private void findFinish() throws InvalidTrackFormatException {
for (int i = 0; i < track.size(); i++) {
String line = track.get(i);
for (int j = 0; j < line.length(); j++) {
if (line.charAt(j) == ConfigSpecification.SpaceType.FINISH_LEFT.getValue() ||
line.charAt(j) == ConfigSpecification.SpaceType.FINISH_RIGHT.getValue() ||
line.charAt(j) == ConfigSpecification.SpaceType.FINISH_DOWN.getValue() ||
line.charAt(j) == ConfigSpecification.SpaceType.FINISH_UP.getValue()) {
finishLine.add(new PositionVector(j, i));
}
}
}
if (finishLine.size() == 0) {
throw new InvalidTrackFormatException();
}
ConfigSpecification.SpaceType finishTyp = getSpaceType(finishLine.get(0));
for (PositionVector positionVector : finishLine) {
if (getSpaceType(positionVector) != finishTyp) {
throw new InvalidTrackFormatException();
}
}
}
private PositionVector findChar(char symbol) {
PositionVector vector = null;
for (int i = 0; i < track.size(); i++) {
String line = track.get(i);
for (int j = 0; j < line.length(); j++) {
if (line.charAt(j) == symbol) {
vector = new PositionVector(j, i);
}
}
}
return vector;
}
private void drawCharOnTrackIndicator(PositionVector positionVector, char symbol) {
String line = track.get(positionVector.getY());
line = line.substring(0, positionVector.getX()) + symbol + line.substring(positionVector.getX() + 1);
track.remove(positionVector.getY());
track.add(positionVector.getY(), line);
}
private void isPositionVectorOnTrack(PositionVector positionVector) throws PositionVectorNotValid {
try{
track.get(positionVector.getY()).charAt(positionVector.getX());
}catch (IndexOutOfBoundsException e) {
throw new PositionVectorNotValid();
}
}
/**
* @return all Cars
*/
public List getCars() {
return cars;
}
/**
* @return finishLine
*/
public List getFinishLine() {
return finishLine;
}
/**
* @return the track
*/
public List getTrack() {
return track;
}
/**
* This Method will update the Car on the track
* and will make the Car move to the next position
*
* @param carIndex representing the current Car
*/
public void moveCar(int carIndex) {
makeCarMoveInTrack(carIndex);
//Change position of car
getCar(carIndex).move();
}
/**
* This class does change the Position of the car only in the track.
*
* @param carIndex of the current car
*/
private void makeCarMoveInTrack(int carIndex) {
PositionVector positionVector = findChar(getCarId(carIndex));
//Removes the Car at Current Pos
drawCharOnTrackIndicator(positionVector, ConfigSpecification.SpaceType.TRACK.getValue());
//Adds Car at new Position
positionVector = cars.get(carIndex).nextPosition();
drawCharOnTrackIndicator(positionVector, cars.get(carIndex).getID());
}
/**
* This Method will check if the Car could crash at the specific position
*
* @param positionVector the position to check if the car could crash
* @return true if car would crash. Else false.
*/
public boolean willCrashAtPosition(int carIndex, PositionVector positionVector) throws PositionVectorNotValid {
isPositionVectorOnTrack(positionVector);
char charAtPosition = track.get(positionVector.getY()).charAt(positionVector.getX());
if (getCarId(carIndex) == charAtPosition) return false;
return (charAtPosition == ConfigSpecification.SpaceType.WALL.value);
}
/**
* This Method will make the Car Crash. In Track and in the Car Object
*
* @param carIndex representing current Car
* @param positionVector where the Crash did happen
*/
public void carDoesCrash(int carIndex, PositionVector positionVector) throws PositionVectorNotValid{
isPositionVectorOnTrack(positionVector);
Car car = cars.get(carIndex);
car.crash();
car.setPosition(positionVector);
drawCharOnTrackIndicator(new PositionVector(positionVector.getX(), positionVector.getY()), CRASH_INDICATOR);
}
/**
* Return the type of space at the given position.
* If the location is outside the track bounds, it is considered a wall.
*
* @param position The coordinates of the position to examine
* @return The type of track position at the given location
*/
@Override
public Config.SpaceType getSpaceType(PositionVector position) {
//isPositionVectorOnTrack(position); Should be used but we are not allowed to change method head. We don't use function anyway
char charAtPosition = track.get(position.getY()).charAt(position.getX());
ConfigSpecification.SpaceType[] spaceTypes = ConfigSpecification.SpaceType.values();
for (ConfigSpecification.SpaceType spaceType : spaceTypes) {
if (spaceType.getValue() == charAtPosition) {
return spaceType;
}
}
return null;
}
/**
* Return the number of cars.
*
* @return Number of cars
*/
@Override
public int getCarCount() {
return cars.size();
}
/**
* Get instance of specified car.
*
* @param carIndex The zero-based carIndex number
* @return The car instance at the given index
*/
@Override
public Car getCar(int carIndex) {
return cars.get(carIndex);
}
/**
* Get the id of the specified car.
*
* @param carIndex The zero-based carIndex number
* @return A char containing the id of the car
*/
@Override
public char getCarId(int carIndex) {
return cars.get(carIndex).getID();
}
/**
* Get the position of the specified car.
* Returns Null if carIndex not valid
*
* @param carIndex The zero-based carIndex number
* @return A PositionVector containing the car's current position
*/
@Override
public PositionVector getCarPos(int carIndex) {
return findChar(cars.get(carIndex).getID());
}
/**
* Get the velocity of the specified car.
*
* @param carIndex The zero-based carIndex number
* @return A PositionVector containing the car's current velocity
*/
@Override
public PositionVector getCarVelocity(int carIndex) {
return cars.get(carIndex).getVelocity();
}
/**
* Gets character at the given position.
* If there is a crashed car at the position, {@link #CRASH_INDICATOR} is returned.
*
* @param y position Y-value
* @param x position X-vlaue
* @param currentSpace char to return if no car is at position (x,y)
* @return character representing position (x,y) on the track
*/
@Override
public char getCharAtPosition(int y, int x, Config.SpaceType currentSpace) {
char charAtPos = track.get(y).charAt(x);
PositionVector positionVector = new PositionVector(x, y);
for (Car car : cars) {
if (charAtPos == car.getID()) {
return charAtPos;
}
}
if (positionVector.equals(findChar(CRASH_INDICATOR))) return CRASH_INDICATOR;
return currentSpace.getValue();
}
/**
* Return a String representation of the track, including the car locations.
*
* @return A String representation of the track
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (String line : track) str.append(line).append("\n");
return str.toString();
}
}