/** * @author João Campos */ import java.util.Scanner; //constants final char[] CARS = {'P','a','b','c','d','e','f','g','h','i'}; final char START = 'S'; final char BOOST = '+'; final char DRAG = '-'; final char OIL = '!'; final String ACCEL = "accel"; final String SHOW = "show"; final String STATUS = "status"; final String QUIT = "quit"; final String INVALID_CMD = "Invalid command"; final String RACE_ONGOING = "The race is not over yet!"; final String ONGOING = " (ongoing)"; final String ENDED = " (ended)"; //global variables int[] carPositions; int[] carSpeeds; int[] carLaps; int totalLaps; int maxSpeed; int numOpponents; int numCars; String track; boolean[] hasCrossedS; boolean isRaceOver; boolean isRaceQuit; boolean yellowFlag; char winner; //domain methods /** * Initializes the race state with starting positions and values */ void initState() { numCars = numOpponents + 1; carSpeeds = new int[numCars]; carLaps = new int[numCars]; carPositions = new int[numCars]; hasCrossedS = new boolean[numCars]; isRaceOver = false; isRaceQuit = false; yellowFlag = false; winner = ' '; for (int i = 0; i < numCars; i++) { carSpeeds[i] = 1; carLaps[i] = 0; carPositions[i] = 0; hasCrossedS[i] = false; } setupInitialGrid(); } /** * Sets up initial positions for all cars on starting grid */ void setupInitialGrid() { int startPosition = findStartPosition(); for (int i = 0; i < numCars; i++) { carPositions[i] = getPosition(startPosition - 1 - i); } } /** * Calculates valid position on circular track with wrap-around * @param position raw position to normalize * @return normalized position within track bounds */ int getPosition(int position) { int trackLength = track.length(); while (position < 0) { position += trackLength; } return position % trackLength; } /** * Finds the start line position on the track * @return index of start line position */ int findStartPosition() { for (int i = 0; i < track.length(); i++) { if (track.charAt(i) == START) { return i; } } return 0; } /** * Applies track effects (boost, drag, oil) to car at given index * @param carIndex index of the car to apply effects to */ void applyTrackEffect(int carIndex) { int position = carPositions[carIndex]; char cell = track.charAt(position); switch (cell) { case BOOST -> carSpeeds[carIndex] = Math.min(maxSpeed, carSpeeds[carIndex] + 1); case DRAG -> carSpeeds[carIndex] = Math.max(0, carSpeeds[carIndex] - 1); case OIL -> carSpeeds[carIndex] = 0; } } /** * Calculates AI acceleration decisions based on track conditions */ void calculateAIAcceleration() { for (int i = 1; i < numCars; i++) { int currentSpeed = carSpeeds[i]; int currentPos = carPositions[i]; boolean hasBoost = false; boolean hasOil = false; for (int j = 1; j <= 3; j++) { int lookAhead = getPosition(currentPos + j); char cell = track.charAt(lookAhead); if (cell == BOOST) hasBoost = true; if (cell == OIL) hasOil = true; } if (hasBoost && currentSpeed < maxSpeed) carSpeeds[i] = Math.min(maxSpeed, carSpeeds[i] + 1); else if (hasOil && currentSpeed > 0) carSpeeds[i] = Math.max(0, carSpeeds[i] - 1); } } /** * Checks if car crossed start line during movement * @param startPosition starting position of movement * @param endPosition ending position of movement * @param startLinePosition position of start line * @return true if start line was crossed during movement */ boolean hasCrossedStart(int startPosition, int endPosition, int startLinePosition) { if (startPosition <= endPosition) { return startPosition < startLinePosition && endPosition >= startLinePosition; } else { return startPosition < startLinePosition || endPosition >= startLinePosition; } } /** * Updates lap count if car crossed start line * @param startPosition starting position of movement * @param endPosition ending position of movement * @param carIndex index of car to check */ void checkLapCompletion(int startPosition, int endPosition, int carIndex) { int startLine = findStartPosition(); if (hasCrossedStart(startPosition, endPosition, startLine)) { //doesn't increment carLaps as the first time the car passes S, it hasn't done a lap. if (!hasCrossedS[carIndex]) hasCrossedS[carIndex] = true; else carLaps[carIndex]++; } } /** * Moves car according to current speed and resolves collisions * @param carIndex index of car to move */ void moveCar(int carIndex) { int currentSpeed = carSpeeds[carIndex]; if (yellowFlag) currentSpeed = Math.min(currentSpeed, 1); int oldPosition = carPositions[carIndex]; int targetPosition = getPosition(oldPosition + currentSpeed); int finalPosition = resolveCollision(targetPosition, CARS[carIndex]); carPositions[carIndex] = finalPosition; applyTrackEffect(carIndex); checkLapCompletion(oldPosition, finalPosition, carIndex); if (carLaps[carIndex] >= totalLaps && !isRaceOver) { isRaceOver = true; winner = CARS[carIndex]; } } /** * Executes one race round with all car movements */ void race() { yellowFlag = false; calculateAIAcceleration(); int i = 0; while (!isRaceOver && i < numCars) { moveCar(i); i++; } } /** * Resolves collisions during car movement * @param targetPosition intended target position * @param carChar character identifier of moving car * @return final position after collision resolution */ int resolveCollision(int targetPosition, char carChar) { if (isPositionOccupied(targetPosition, carChar)) { if (!yellowFlag) { yellowFlag = true; } // Move to position before the collision if desired position is occupied return getPosition(targetPosition - 1); } return targetPosition; } /** * Checks if position is occupied by any other car * @param position position to check * @param excludeCar character of car to exclude from check * @return true if position is occupied by another car */ boolean isPositionOccupied(int position, char excludeCar) { int i = 0; while (i < numCars && !(CARS[i] != excludeCar && carPositions[i] == position)) { i++; } return i < numCars; } //interaction methods /** * Reads track configuration from input */ String readTrack(Scanner in) { return in.nextLine(); } /** * Reads integer input values for race parameters */ int readInput(Scanner in) { return in.nextInt(); } /** * Processes acceleration command for player car * @param acceleration acceleration value (-1, 0, or +1) */ void accel(int acceleration) { if (isRaceOver) { System.out.println("Race ended: " + winner + " won the race!"); return; } carSpeeds[0] = Math.max(0, Math.min(maxSpeed, carSpeeds[0] + acceleration)); race(); if (isRaceOver) System.out.println("Player " + winner + " won the race!"); else System.out.println("Player P: cell "+ carPositions[0] +", laps "+ carLaps[0] +"!"); } /** * Displays current track state with car positions */ void show() { char[] displayTrack = track.toCharArray(); for (int i = 0; i < numCars; i++) { displayTrack[carPositions[i]] = CARS[i]; } if (!isRaceOver) System.out.println(new String(displayTrack) + ONGOING); else System.out.println(new String(displayTrack) + ENDED); } /** * Shows status of specified player * @param playerId identifier of player to check */ void status(String playerId) { char requestedCar = playerId.charAt(0); if (isRaceOver && requestedCar == winner) { System.out.println("Race ended: " + winner + " won the race!"); return; } int carIndex = -1; if (requestedCar == 'P') carIndex = 0; else if (requestedCar >= 'a' && requestedCar < 'a' + numOpponents) carIndex = requestedCar - 'a' + 1; if (carIndex != -1 && carIndex < numCars) { System.out.print("Player " + CARS[carIndex] + ": cell "); System.out.println(carPositions[carIndex] + ", laps " + carLaps[carIndex] + "!"); } else { System.out.println("Player " + requestedCar + " does not exist!"); } } /** * Processes quit command and displays race result */ void quit() { if (isRaceOver) System.out.println("Race ended: " + winner + " won the race!"); else System.out.println(RACE_ONGOING); isRaceQuit = true; } /** * Processes user commands from input * @param in scanner for reading user input */ void processCommand(Scanner in) { String command = in.next(); switch (command) { case ACCEL -> { accel(in.nextInt()); in.nextLine(); } case SHOW -> { show(); in.nextLine(); } case STATUS -> { status(in.next()); in.nextLine(); } case QUIT -> { quit(); in.nextLine(); } default -> { System.out.println(INVALID_CMD); in.nextLine(); } } } void main() { Scanner input = new Scanner(System.in); track = readTrack(input); totalLaps = readInput(input); maxSpeed = readInput(input); numOpponents = readInput(input); input.nextLine(); initState(); do { processCommand(input); } while (!isRaceQuit); input.close(); }