498 lines
19 KiB
Java
498 lines
19 KiB
Java
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.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Scanner;
|
|
|
|
/**
|
|
* This class represents the racetrack board.
|
|
*
|
|
* <p>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.</p>
|
|
* <p>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.</p>
|
|
*
|
|
* <p>Each position in the grid represents a space which can hold an enum object of type {@link Config.SpaceType}.<br>
|
|
* Possible Space types are:
|
|
* <ul>
|
|
* <li>WALL : road boundary or off track space</li>
|
|
* <li>TRACK: road or open track space</li>
|
|
* <li>FINISH_LEFT, FINISH_RIGHT, FINISH_UP, FINISH_DOWN : finish line spaces which have to be crossed
|
|
* in the indicated direction to winn the race.</li>
|
|
* </ul>
|
|
* <p>Beside the board the track contains the list of cars, with their current state (position, velocity, crashed,...)</p>
|
|
*
|
|
* <p>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.</p>
|
|
* <p>Characters in the line represent SpaceTypes. The mapping of the Characters is as follows:</p>
|
|
* <ul>
|
|
* <li>WALL : '#'</li>
|
|
* <li>TRACK: ' '</li>
|
|
* <li>FINISH_LEFT : '<'</li>
|
|
* <li>FINISH_RIGHT: '>'</li>
|
|
* <li>FINISH_UP : '^;'</li>
|
|
* <li>FINISH_DOWN: 'v'</li>
|
|
* <li>Any other character indicates the starting position of a car.<br>
|
|
* The character acts as the id for the car and must be unique.<br>
|
|
* There are 1 to {@link Config#MAX_CARS} allowed. </li>
|
|
* </ul>
|
|
*
|
|
* <p>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.<br>
|
|
* An {@link InvalidTrackFormatException} is thrown, if
|
|
* <ul>
|
|
* <li>not all track lines have the same length</li>
|
|
* <li>the file contains no track lines (grid height is 0)</li>
|
|
* <li>the file contains more than {@link Config#MAX_CARS} cars</li>
|
|
* </ul>
|
|
*
|
|
* <p>The Track can return a String representing the current state of the race (including car positons)</p>
|
|
*/
|
|
public class Track implements TrackSpecification {
|
|
|
|
public static final char CRASH_INDICATOR = 'X';
|
|
private final List<String> track;
|
|
private final List<Car> cars;
|
|
private final List<PositionVector> finishLine;
|
|
private ConfigSpecification.SpaceType finishTyp;
|
|
|
|
/**
|
|
* 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 {
|
|
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(new FileInputStream(trackFile), StandardCharsets.UTF_8);
|
|
while (scanner.hasNextLine()) {
|
|
track.add(scanner.nextLine());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Goes through the track ArrayList and determines the locations of each cars and initializes them at the location.
|
|
*
|
|
* @throws InvalidTrackFormatException is thrown if a car is found more than once inside the track.
|
|
*/
|
|
private void addCars() throws InvalidTrackFormatException {
|
|
ConfigSpecification.SpaceType[] spaceTypes = ConfigSpecification.SpaceType.values();
|
|
List<Character> allSpaceTypesAsChar = new ArrayList<>();
|
|
List<Character> usedSymbolForCar = new ArrayList<>();
|
|
|
|
for (ConfigSpecification.SpaceType spaceType : spaceTypes) {
|
|
allSpaceTypesAsChar.add(spaceType.getValue());
|
|
}
|
|
|
|
for (int yPosition = 0; yPosition < track.size(); yPosition++) {
|
|
String line = track.get(yPosition);
|
|
for (int xPosition = 0; xPosition < line.length(); xPosition++) {
|
|
char possibleCarChar = line.charAt(xPosition);
|
|
if (!allSpaceTypesAsChar.contains(possibleCarChar)) {
|
|
if (usedSymbolForCar.contains(possibleCarChar)) {
|
|
throw new InvalidTrackFormatException();
|
|
}
|
|
usedSymbolForCar.add(possibleCarChar);
|
|
cars.add(new Car(possibleCarChar, new PositionVector(xPosition, yPosition)));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
//TODO: THIS
|
|
|
|
/**
|
|
* @throws InvalidTrackFormatException
|
|
*/
|
|
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();
|
|
}
|
|
finishTyp = getSpaceType(finishLine.get(0));
|
|
for (PositionVector positionVector : finishLine) {
|
|
if (getSpaceType(positionVector) != finishTyp) {
|
|
throw new InvalidTrackFormatException();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to find the PositionVector of a chosen character
|
|
*
|
|
* @param symbol char that we are looking for on the track
|
|
* @return the PositionVector of the desired char
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Method that places a character at a chosen position
|
|
*
|
|
* @param positionVector position where char will be placed
|
|
* @param symbol char that should be placed at desired position
|
|
*/
|
|
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);
|
|
}
|
|
//TODO: check if this method is okay and needed
|
|
|
|
/**
|
|
* Determines if a location is valid PositionVector inside the track
|
|
*
|
|
* @param positionVector of location that has to be checked
|
|
* @throws PositionVectorNotValidException if the PositionVector does not lie on the track.
|
|
*/
|
|
private void isPositionVectorOnTrack(PositionVector positionVector) throws PositionVectorNotValidException {
|
|
try {
|
|
track.get(positionVector.getY()).charAt(positionVector.getX());
|
|
} catch (IndexOutOfBoundsException e) {
|
|
throw new PositionVectorNotValidException();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Method that returns the finishline as a List
|
|
*
|
|
* @return finishLine List
|
|
*/
|
|
public List<PositionVector> getFinishLine() {
|
|
return finishLine;
|
|
}
|
|
|
|
/**
|
|
* Returns the whole Track as List of Strings
|
|
*
|
|
* @return track as List of Strings
|
|
*/
|
|
public List<String> 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 Method does change the Position of the car inside the track object.
|
|
*
|
|
* @param carIndex of the current car
|
|
*/
|
|
private void makeCarMoveInTrack(int carIndex) {
|
|
PositionVector carPositionVector = findChar(getCarId(carIndex));
|
|
//Removes the Car at Current Pos
|
|
drawCharOnTrackIndicator(carPositionVector, ConfigSpecification.SpaceType.TRACK.getValue());
|
|
|
|
//Redraw finishline if Car was on finish-line Position
|
|
for (PositionVector finishLinePositionVector : finishLine) {
|
|
if (finishLinePositionVector.equals(carPositionVector)) {
|
|
drawCharOnTrackIndicator(carPositionVector, finishTyp.getValue());
|
|
}
|
|
}
|
|
|
|
//Adds Car at new Position
|
|
carPositionVector = cars.get(carIndex).nextPosition();
|
|
drawCharOnTrackIndicator(carPositionVector, cars.get(carIndex).getID());
|
|
}
|
|
|
|
/**
|
|
* This Method will check if the Car would 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 PositionVectorNotValidException {
|
|
isPositionVectorOnTrack(positionVector); //TODO: remove this line? Or Method?
|
|
char charAtPosition = track.get(positionVector.getY()).charAt(positionVector.getX());
|
|
if (getCarId(carIndex) == charAtPosition) return false;
|
|
return !(charAtPosition == ConfigSpecification.SpaceType.TRACK.value ||
|
|
charAtPosition == ConfigSpecification.SpaceType.FINISH_RIGHT.value ||
|
|
charAtPosition == ConfigSpecification.SpaceType.FINISH_LEFT.value ||
|
|
charAtPosition == ConfigSpecification.SpaceType.FINISH_UP.value ||
|
|
charAtPosition == ConfigSpecification.SpaceType.FINISH_DOWN.value);
|
|
}
|
|
|
|
/**
|
|
* This Method will make the Car Crash. In Track and in the Car Object
|
|
*
|
|
* @param carIndex representing current Car
|
|
* @param crashPositionVector where the Crash did happen
|
|
*/
|
|
public void carDoesCrash(int carIndex, PositionVector crashPositionVector) throws PositionVectorNotValidException {
|
|
isPositionVectorOnTrack(crashPositionVector); //TODO: remove this line? and Method?
|
|
PositionVector currentCarPosition = getCarPos(carIndex);
|
|
drawCharOnTrackIndicator(new PositionVector(currentCarPosition.getX(), currentCarPosition.getY()), ConfigSpecification.SpaceType.TRACK.getValue());
|
|
Car car = cars.get(carIndex);
|
|
car.crash();
|
|
car.setPosition(crashPositionVector);
|
|
drawCharOnTrackIndicator(new PositionVector(crashPositionVector.getX(), crashPositionVector.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();
|
|
}
|
|
|
|
public ArrayList<PositionVector> calculatePointsOnPath(PositionVector startPosition, PositionVector endPosition) {
|
|
ArrayList<PositionVector> pathList = new ArrayList<>();
|
|
// Use Bresenham's algorithm to determine positions.
|
|
int x = startPosition.getX();
|
|
int y = startPosition.getY();
|
|
|
|
// Relative Distance (x & y axis) between end- and starting position
|
|
int diffX = endPosition.getX() - startPosition.getX();
|
|
int diffY = endPosition.getY() - startPosition.getY();
|
|
|
|
// Absolute distance (x & y axis) between end- and starting position
|
|
int distX = Math.abs(diffX);
|
|
int distY = Math.abs(diffY);
|
|
|
|
// Direction of vector on x & y axis (-1: to left/down, 0: none, +1 : to right/up)
|
|
int dirX = Integer.signum(diffX);
|
|
int dirY = Integer.signum(diffY);
|
|
|
|
// Determine which axis is the fast direction and set parallel/diagonal step values
|
|
int parallelStepX, parallelStepY;
|
|
int diagonalStepX, diagonalStepY;
|
|
int distanceSlowAxis, distanceFastAxis;
|
|
if (distX > distY) {
|
|
// x axis is the 'fast' direction
|
|
parallelStepX = dirX;
|
|
parallelStepY = 0; // parallel step only moves in x direction
|
|
diagonalStepX = dirX;
|
|
diagonalStepY = dirY; // diagonal step moves in both directions
|
|
distanceSlowAxis = distY;
|
|
distanceFastAxis = distX;
|
|
} else {
|
|
// y axis is the 'fast' direction
|
|
parallelStepX = 0;
|
|
parallelStepY = dirY; // parallel step only moves in y direction
|
|
diagonalStepX = dirX;
|
|
diagonalStepY = dirY; // diagonal step moves in both directions
|
|
distanceSlowAxis = distX;
|
|
distanceFastAxis = distY;
|
|
}
|
|
|
|
int error = distanceFastAxis / 2;
|
|
for (int step = 0; step < distanceFastAxis; step++) {
|
|
error -= distanceSlowAxis;
|
|
if (error < 0) {
|
|
error += distanceFastAxis; // correct error value to be positive again
|
|
// step into slow direction; diagonal step
|
|
x += diagonalStepX;
|
|
y += diagonalStepY;
|
|
} else {
|
|
// step into fast direction; parallel step
|
|
x += parallelStepX;
|
|
y += parallelStepY;
|
|
}
|
|
|
|
pathList.add(new PositionVector(x, y));
|
|
}
|
|
return pathList;
|
|
}
|
|
|
|
public int calculateNewWinPoints(PositionVector start, PositionVector finish) {
|
|
List<PositionVector> path = calculatePointsOnPath(start, finish);
|
|
for (PositionVector point : path) {
|
|
if (getSpaceType(point) != null) {
|
|
switch (getSpaceType(point)) {
|
|
case FINISH_UP:
|
|
if (start.getY() < finish.getY()) {
|
|
return 1;
|
|
} else if (start.getY() > finish.getY()) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case FINISH_DOWN:
|
|
if (start.getY() > finish.getY()) {
|
|
return 1;
|
|
} else if (start.getY() < finish.getY()) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case FINISH_RIGHT:
|
|
if (start.getX() < finish.getX()) {
|
|
return 1;
|
|
} else if (start.getX() > finish.getX()) {
|
|
return -1;
|
|
}
|
|
break;
|
|
case FINISH_LEFT:
|
|
if (start.getX() > finish.getX()) {
|
|
return 1;
|
|
} else if (start.getX() < finish.getX()) {
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|