package ch.zhaw.pm2.racetrack; import ch.zhaw.pm2.racetrack.given.GameSpecification; import ch.zhaw.pm2.racetrack.strategy.DoNotMoveStrategy; import ch.zhaw.pm2.racetrack.strategy.MoveListStrategy; import ch.zhaw.pm2.racetrack.strategy.PathFollowerMoveStrategy; import ch.zhaw.pm2.racetrack.strategy.UserMoveStrategy; 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; int currentCarIndex; UserInterface userInterface; public Game(UserInterface userInterface) { this.userInterface = userInterface; } public boolean initPhase() throws InvalidTrackFormatException, FileNotFoundException { 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 { track = new Track(selectedTrack); } catch (FileNotFoundException | PositionVectorNotValid e) { e.printStackTrace(); } 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"); for(int i = 0; i < track.getCarCount() ; i++ ) { int moveStrategie = userInterface.selectOption( "Select Strategy for Car " + i + " (" + track.getCarId(i) + ")", moveStrategies); switch (moveStrategie + 1) { //TODO: set Movestrategy with method in Track case 1: track.getCar(i).setMoveStrategy(new DoNotMoveStrategy()); //TODO: add Arguments break; case 2: track.getCar(i).setMoveStrategy(new UserMoveStrategy(userInterface, i, track.getCarId(i))); //TODO: add Arguments break; case 3: track.getCar(i).setMoveStrategy(new MoveListStrategy()); //TODO: add Arguments break; case 4: track.getCar(i).setMoveStrategy(new PathFollowerMoveStrategy()); //TODO: add Arguments break; } } return true; } else{ userInterface.printInformation("No Trackfile found!"); return false; } } /** * 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() { List cars = track.getCars(); for (Car car: cars) { if(car.getWinPoints() == 1){ return car.getID(); } } 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) throws PositionVectorNotValid { // TODO: implementation track.getCar(currentCarIndex).accelerate(acceleration); PositionVector crashPosition = null; List positionList = calculatePath(track.getCarPos(currentCarIndex),track.getCar(currentCarIndex).nextPosition()); //TODO: check if Method calculatePath contains endposition for(PositionVector location : positionList) { //todo: check if order must be reversed if(willCarCrash(currentCarIndex, location)) { crashPosition = location; } } if(crashPosition != null) { track.carDoesCrash(currentCarIndex, crashPosition); } else { track.moveCar(currentCarIndex); calculateWinner(track.getCarPos(currentCarIndex), track.getCar(currentCarIndex).nextPosition(), currentCarIndex); } } public int gamePhase() throws PositionVectorNotValid { do{ userInterface.printTrack(track); Direction direction = track.getCar(currentCarIndex).getMoveStrategy().nextMove(); doCarTurn(direction); int winner = getWinner(); if(winner != NO_WINNER) { return winner; } } while (!allCarsCrashed()); return NO_WINNER; } /** * Switches to the next car who is still in the game. Skips crashed cars. */ @Override public void switchToNextActiveCar() { do { if (currentCarIndex++ > track.getCarCount()) { currentCarIndex = 0; } } 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) { ArrayList 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; } private void calculateWinner(PositionVector start, PositionVector finish, int carIndex ) { List path = calculatePath(start, finish); for (PositionVector point : path){ switch (track.getSpaceType(point)) { case FINISH_UP: if(start.getY() < finish.getY()) { track.getCar(carIndex).increaseWinPoints(); } else if(start.getY() < finish.getY()) { track.getCar(carIndex).deductWinPoints(); } break; case FINISH_DOWN: if(start.getY() > finish.getY()){ track.getCar(carIndex).increaseWinPoints(); } else if (start.getY() < finish.getY()){ track.getCar(carIndex).deductWinPoints(); } break; case FINISH_RIGHT: if(start.getX() < finish.getX()){ track.getCar(carIndex).increaseWinPoints(); } else if (start.getX() < finish.getX()){ track.getCar(carIndex).deductWinPoints(); } break; case FINISH_LEFT: if(start.getX() > finish.getX()){ track.getCar(carIndex).increaseWinPoints(); } else if (start.getX() < finish.getX()){ track.getCar(carIndex).increaseWinPoints(); } break; } } } /** * 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) throws PositionVectorNotValid { return track.willCrashAtPosition(carIndex, position); } public boolean allCarsCrashed() { for(int carIndex = 0; carIndex < track.getCarCount(); carIndex ++) { if(! track.getCar(carIndex).isCrashed()) { return false; } } return true; } }