package ch.zhaw.pm2.racetrack; import ch.zhaw.pm2.racetrack.given.GameSpecification; import ch.zhaw.pm2.racetrack.strategy.*; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import static ch.zhaw.pm2.racetrack.PositionVector.Direction; /** * Game controller class, performing all actions to modify the game state. * It contains the logic to move the cars, detect if they are crashed * and if we have a winner. */ public class Game implements GameSpecification { public static final int NO_WINNER = -1; private Track track; private int currentCarIndex; UserInterface userInterface; public Game(UserInterface userInterface) { this.userInterface = userInterface; } /** * This method will initialize the game. Therefore it interacts with the user via UserInterface * @return true if the initialization is completed. Returns false if there is an error. */ public boolean initPhase() { File folder = new File("tracks"); File[] listOfFiles = folder.listFiles(); if (listOfFiles.length > 0) { List tracks = new ArrayList<>(); for (File file : listOfFiles) { tracks.add(file.getName()); } File selectedTrack = listOfFiles[userInterface.selectOption("Select Track file", tracks)]; try { selectTrack(selectedTrack); } catch (FileNotFoundException e) { userInterface.printInformation("There is an unexpected Error with the trackfile Path. Add trackfiles only to tracks path. Exit the Game and Fix the Problem"); return false; } catch (InvalidTrackFormatException e) { userInterface.printInformation("There is an unexpected Error with the trackfile. Format does not match specifications! Exit the Game and Fix the Problem"); return false; } List moveStrategies = new ArrayList<>(); moveStrategies.add("Do not move Strategy"); moveStrategies.add("User Move Strategy"); moveStrategies.add("Move List Strategy"); moveStrategies.add("Path Follow Move Strategy"); moveStrategies.add("Path Finder Move Strategy"); for (int i = 0; i < track.getCarCount(); i++) { Car car = track.getCar(i); MoveStrategy moveStrategy = null; while (moveStrategy == null) { String filePath; int moveStrategie = userInterface.selectOption( "Select Strategy for Car " + i + " (" + track.getCarId(i) + ")", moveStrategies); switch (moveStrategie) { case 0: moveStrategy = new DoNotMoveStrategy(); break; case 1: moveStrategy = new UserMoveStrategy(userInterface, i, track.getCarId(i)); break; case 2: filePath = ".\\moves\\" + selectedTrack.getName().split("\\.")[0] + "-car-" + track.getCar(i).getID() + ".txt"; try { moveStrategy = new MoveListStrategy(filePath); } catch (FileNotFoundException e) { userInterface.printInformation("There is no Move-List implemented. Choose another Strategy!"); } break; case 3: filePath = ".\\follower\\" + selectedTrack.getName().split("\\.")[0] + "_points.txt"; try { moveStrategy = new PathFollowerMoveStrategy(filePath, track.getCarPos(i)); } catch (FileNotFoundException e) { userInterface.printInformation("There is no Point-List implemented. Choose another Strategy!"); } break; case 4: moveStrategy = new PathFinderMoveStrategy(track, i); break; } } selectMoveStrategy(car, moveStrategy); } return true; } else { userInterface.printInformation("No Trackfile found!"); return false; } } /** * The functionality was taken out of init to automate testing * @param selectedTrack */ Track selectTrack(File selectedTrack) throws InvalidTrackFormatException,FileNotFoundException { track = new Track(selectedTrack); return track; } /** * The functionality was taken out of init to automate testing * * @param car to set the MoveStrategy * @param strategy The movestrategy to set */ void selectMoveStrategy(Car car, MoveStrategy strategy) { car.setMoveStrategy(strategy); } /** * Return the index of the current active car. * Car indexes are zero-based, so the first car is 0, and the last car is getCarCount() - 1. * * @return The zero-based number of the current car */ @Override public int getCurrentCarIndex() { return currentCarIndex; } /** * 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 track.getCarId(carIndex); } /** * Get the position of the specified car. * * @param carIndex The zero-based carIndex number * @return A PositionVector containing the car's current position */ @Override public PositionVector getCarPosition(int carIndex) { return track.getCarPos(carIndex); } /** * 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 track.getCarVelocity(carIndex); } /** * Return the winner of the game. If the game is still in progress, returns NO_WINNER. * * @return The winning car's index (zero-based, see getCurrentCar()), or NO_WINNER if the game is still in progress */ @Override public int getWinner() { for (int i = 0; i < track.getCarCount(); i++) { if (track.getCar(i).getWinPoints() == 1) { return i; } } if (onlyOneCarLeft()) { return currentCarIndex; } return NO_WINNER; } /** * Execute the next turn for the current active car. *

This method changes the current car's velocity and checks on the path to the next position, * if it crashes (car state to crashed) or passes the finish line in the right direction (set winner state).

*

The steps are as follows

*
    *
  1. Accelerate the current car
  2. *
  3. Calculate the path from current (start) to next (end) position * (see {@link Game#calculatePath(PositionVector, PositionVector)})
  4. *
  5. Verify for each step what space type it hits: *
      *
    • TRACK: check for collision with other car (crashed & don't continue), otherwise do nothing
    • *
    • WALL: car did collide with the wall - crashed & don't continue
    • *
    • FINISH_*: car hits the finish line - wins only if it crosses the line in the correct direction
    • *
    *
  6. *
  7. If the car crashed or wins, set its position to the crash/win coordinates
  8. *
  9. If the car crashed, also detect if there is only one car remaining, remaining car is the winner
  10. *
  11. Otherwise move the car to the end position
  12. *
*

The calling method must check the winner state and decide how to go on. If the winner is different * than {@link Game#NO_WINNER}, or the current car is already marked as crashed the method returns immediately.

* * @param acceleration A Direction containing the current cars acceleration vector (-1,0,1) in x and y direction * for this turn */ @Override public void doCarTurn(Direction acceleration) { track.getCar(currentCarIndex).accelerate(acceleration); PositionVector crashPosition = null; List positionList = calculatePath(track.getCarPos(currentCarIndex), track.getCar(currentCarIndex).nextPosition()); for (int i = 0; i < positionList.size(); i++) { if (willCarCrash(currentCarIndex, positionList.get(i))) { if (i == 0) { crashPosition = track.getCarPos(currentCarIndex); } else { crashPosition = positionList.get(i - 1); } break; } } int newWinPoints = track.calculateNewWinPoints(track.getCarPos(currentCarIndex), track.getCar(currentCarIndex).nextPosition()); if(newWinPoints == 1){ track.getCar(currentCarIndex).increaseWinPoints(); }else if(newWinPoints == -1){ track.getCar(currentCarIndex).deductWinPoints(); } if (crashPosition != null) { track.carDoesCrash(currentCarIndex, crashPosition); } else { track.moveCar(currentCarIndex); } } /** * This method implements the gameflow in a while loop. If there is a winner. The method will return its carid. * * @return the ID of the winning car return null if there is no winner. */ public String gamePhase() { while (carsMoving() && getWinner() == NO_WINNER) { userInterface.printTrack(track); Direction direction; direction = track.getCar(currentCarIndex).getMoveStrategy().nextMove(); if (direction == null) { track.getCar(currentCarIndex).setMoveStrategy(new DoNotMoveStrategy()); direction = track.getCar(currentCarIndex).getMoveStrategy().nextMove(); } doCarTurn(direction); switchToNextActiveCar(); } userInterface.printTrack(track); int indexWinner = getWinner(); if (indexWinner == NO_WINNER) { return null; } return String.valueOf(track.getCar(indexWinner).getID()); } /** * Switches to the next car who is still in the game. Skips crashed cars. */ @Override public void switchToNextActiveCar() { do { if ((currentCarIndex + 1) == track.getCarCount()) { currentCarIndex = 0; } else { currentCarIndex++; } } while (track.getCar(currentCarIndex).isCrashed()); // TODO: evtl andere Kapselung } /** * Returns all of the grid positions in the path between two positions, for use in determining line of sight. * Determine the 'pixels/positions' on a raster/grid using Bresenham's line algorithm. * (https://de.wikipedia.org/wiki/Bresenham-Algorithmus) * Basic steps are * - Detect which axis of the distance vector is longer (faster movement) * - for each pixel on the 'faster' axis calculate the position on the 'slower' axis. * Direction of the movement has to correctly considered * * @param startPosition Starting position as a PositionVector * @param endPosition Ending position as a PositionVector * @return Intervening grid positions as a List of PositionVector's, including the starting and ending positions. */ @Override public List calculatePath(PositionVector startPosition, PositionVector endPosition) { return track.calculatePointsOnPath(startPosition, endPosition); } /** * Does indicate if a car would have a crash with a WALL space or another car at the given position. * * @param carIndex The zero-based carIndex number * @param position A PositionVector of the possible crash position * @return A boolean indicator if the car would crash with a WALL or another car. */ @Override public boolean willCarCrash(int carIndex, PositionVector position) { return track.willCrashAtPosition(carIndex, position); } public boolean onlyOneCarLeft() { int carsLeft = 0; for (int carIndex = 0; carIndex < track.getCarCount(); carIndex++) { if (!track.getCar(carIndex).isCrashed()) { carsLeft++; } } return !(carsLeft > 1); } public boolean carsMoving() { for (int carIndex = 0; carIndex < track.getCarCount(); carIndex++) { if (!(track.getCar(carIndex).isCrashed() || track.getCar(carIndex).getMoveStrategy().getClass() == DoNotMoveStrategy.class)) { return true; } } return false; } }