1
0

Initial Commit

This commit is contained in:
2025-07-16 18:06:02 +02:00
commit 2358585522
33 changed files with 1523 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea/*
build/*
*.iml
out/*
.gradle/*
target/*

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# Kata Tondeuse
Simulate the movement of Tondeuses (mowers) on a rectangular Pelouse (lawn), following a set of instructions.
## Requirements
- Java 8 or later
- Maven
## Setup
Clone this repository
```bash
git clone https://github.com/yourusername/katatondeuse.git
cd katatondeuse
```
## Running the Application
To run the application:
```bash
mvn compile exec:java -Dexec.mainClass="com.qrouland.katatondeuse.Main" -Dexec.args="examples/input.txt"
```
## Running Tests
To run all tests :
```bash
mvn test
```
## License
This project is licensed under the WTFPL - Do What The F*** You Want To Public License.

5
examples/input.txt Normal file
View File

@ -0,0 +1,5 @@
5 5
1 2 N
GAGAGAGAA
3 3 E
AADAADADDA

90
pom.xml Normal file
View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qrouland.katatondeuse</groupId>
<artifactId>kata-tondeuse</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Kata Tondeuse</name>
<licenses>
<license>
<name>WTFPL - Do What The F*** You Want To Public License</name>
<url>http://www.wtfpl.net/txt/copying/</url>
</license>
</licenses>
<properties>
<mockito.version>2.26.0</mockito.version>
<junit.version>5.5.2</junit.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javadoc.plugin.version>3.1.1</javadoc.plugin.version>
<source.plugin.version>3.2.0</source.plugin.version>
<surefire.plugin.version>2.22.2</surefire.plugin.version>
<org.apache.commons.version>3.14.0</org.apache.commons.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${org.apache.commons.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${source.plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,87 @@
package com.qrouland.katatondeuse;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class MainIntegrationTest {
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private ByteArrayOutputStream outContent;
private ByteArrayOutputStream errContent;
@BeforeEach
public void setUpStreams() {
outContent = new ByteArrayOutputStream();
errContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@AfterEach
public void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
@Test
public void testMain_ValidInputFile() throws IOException {
Path tempFile = Files.createTempFile("test-input", ".txt");
Files.writeString(tempFile,
"5 5\n" +
"1 2 N\n" +
"GAGAGAGAA\n" +
"3 3 E\n" +
"AADAADADDA\n"
);
int code = Main.run(new String[]{tempFile.toString()});
String output = outContent.toString();
assertEquals(0, code);
assertTrue(output.contains("Pelouse 5 x 5"));
assertTrue(output.contains("Moved"));
Files.deleteIfExists(tempFile);
}
@Test
public void testMain_MissingFileArg() {
int code = Main.run(new String[]{});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("Error: Missing input file argument."));
assertTrue(errorOutput.contains("Usage: java Main <inputfile>"));
}
@Test
public void testMain_NonexistentFile() {
int code = Main.run(new String[]{"nonexistent-file.txt"});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("File does not exist"));
}
@Test
public void testMain_withMalformedInputFile() throws IOException {
Path tempFile = Files.createTempFile("malformed-input", ".txt");
Files.writeString(tempFile,
"invalid pelouse line\n"
);
int code = Main.run(new String[]{tempFile.toString()});
assertEquals(1, code);
String errorOutput = errContent.toString();
assertTrue(errorOutput.contains("Invalid Pelouse size format"));
Files.deleteIfExists(tempFile);
}
}

View File

@ -0,0 +1,34 @@
package com.qrouland.katatondeuse;
import com.qrouland.katatondeuse.controller.TontePelouseController;
import com.qrouland.katatondeuse.parser.FileInputParser;
import com.qrouland.katatondeuse.view.ConsolePelouseView;
import com.qrouland.katatondeuse.view.PelouseView;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
int exitCode = run(args);
System.exit(exitCode);
}
public static int run(String[] args) {
PelouseView view = new ConsolePelouseView();
try {
File inputFile = FileInputParser.parseInputFile(args);
TontePelouseController controller = new TontePelouseController(inputFile, view);
controller.run();
} catch (IllegalArgumentException e) {
view.displayError("Error: " + e.getMessage());
view.displayError("Usage: java Main <inputfile>");
return 1;
} catch (IOException e) {
view.displayError("Error reading file: " + e.getMessage());
return 1;
}
return 0;
}
}

View File

@ -0,0 +1,72 @@
package com.qrouland.katatondeuse.controller;
import com.qrouland.katatondeuse.model.Direction;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.parser.FileInputParser;
import com.qrouland.katatondeuse.view.PelouseView;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.*;
import java.util.List;
public class TontePelouseController {
private final File instructionsFile;
private final PelouseView pelouseView;
public TontePelouseController(File instructionsFile, PelouseView view) {
this.instructionsFile = instructionsFile;
this.pelouseView = view;
}
/**
* Starts the tonte (mowing) simulation by reading instructions from the input file.
*
* @throws IOException an I/O error occurs.
*/
public void run() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(instructionsFile))) {
Pelouse pelouse = initPelouse(reader);
executeInstructions(reader, pelouse);
}
}
/**
* Reads and executes the {@link Tondeuse} movement instructions from the reader.
*
* @param reader BufferedReader for reading lines from the instructions file.
* @param pelouse The {@link Pelouse} on which {@link Tondeuse} operate.
* @throws IOException an I/O error occurs.
*/
private void executeInstructions(BufferedReader reader, Pelouse pelouse) throws IOException {
String positionLine;
while ((positionLine = reader.readLine()) != null) {
String instructionsLine = reader.readLine();
ImmutablePair<Tondeuse, List<Direction>> instructions =
FileInputParser.parseTondeuseInstructions(positionLine, instructionsLine);
Tondeuse tondeuse = instructions.left;
List<Direction> directions = instructions.right;
pelouse.addTondeuse(tondeuse);
pelouseView.displayTondeuseAdd(tondeuse);
TonteTondeuseController controller = new TonteTondeuseController(tondeuse, pelouse, this.pelouseView);
controller.executeInstructions(directions);
pelouseView.displayPelouseState(pelouse);
}
}
/**
* Parses the first line of the input to initialize the .
*
* @param reader BufferedReader reading from the instructions file.
* @return The initialized {@link Pelouse}.
* @throws IOException an I/O error occurs.
*/
private Pelouse initPelouse(BufferedReader reader) throws IOException {
String firstLine = reader.readLine();
Pelouse pelouse = FileInputParser.parsePelouseDefinition(firstLine);
pelouseView.displayPelouseState(pelouse);
return pelouse;
}
}

View File

@ -0,0 +1,47 @@
package com.qrouland.katatondeuse.controller;
import com.qrouland.katatondeuse.controller.movement.MovementAhead;
import com.qrouland.katatondeuse.controller.movement.MovementLeft;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.controller.movement.MovementRight;
import com.qrouland.katatondeuse.model.*;
import com.qrouland.katatondeuse.view.PelouseView;
import java.util.List;
import java.util.Map;
/**
* Controls a {@link Tondeuse} on {@link Pelouse}.
*/
public class TonteTondeuseController {
private final Pelouse pelouse;
private final Tondeuse tondeuse;
private final PelouseView pelouseView;
private static final Map<Direction, MovementStrategy> MOVEMENTS = Map.of(
Direction.D, new MovementRight(),
Direction.G, new MovementLeft(),
Direction.A, new MovementAhead()
);
public TonteTondeuseController(Tondeuse tondeuse, Pelouse pelouse, PelouseView view) {
this.pelouse = pelouse;
this.tondeuse = tondeuse;
this.pelouseView = view;
}
/**
* Executes a list of instructions of movement directions.
*
* @param directions the list of movement directions (D, G, A).
*/
public void executeInstructions(List<Direction> directions) {
for (Direction direction : directions) {
Position initial_position = tondeuse.getPosition();
MovementStrategy movement = MOVEMENTS.get(direction);
movement.move(this.pelouse, this.tondeuse);
pelouseView.displayTondeuseMove(initial_position, tondeuse.getPosition(), movement);
}
}
}

View File

@ -0,0 +1,35 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementAhead implements MovementStrategy {
/**
* Moves the {@link Tondeuse} one step ahead in the direction of its current orientation.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position current = tondeuse.getPosition();
int x = current.getX();
int y = current.getY();
Orientation orientation = current.getOrientation();
switch (orientation) {
case N -> y += 1;
case S -> y -= 1;
case E -> x += 1;
case W -> x -= 1;
}
if (x >= 0 && x <= pelouse.getMaxX() && y >= 0 && y <= pelouse.getMaxY()) {
tondeuse.setPosition(new Position(x, y, orientation));
} else {
System.err.println("Move ignored: out of grid bounds.");
}
}
}

View File

@ -0,0 +1,28 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementLeft implements MovementStrategy {
/**
* Rotates the {@link Tondeuse} 90 degrees to the left.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position position = tondeuse.getPosition();
Orientation new_orientation = switch (position.getOrientation()) {
case N -> Orientation.W;
case W -> Orientation.S;
case S -> Orientation.E;
case E -> Orientation.N;
};
tondeuse.setPosition(new Position(position.getX(), position.getY(), new_orientation));
}
}

View File

@ -0,0 +1,26 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class MovementRight implements MovementStrategy {
/**
* Rotates the {@link Tondeuse} 90 degrees to the right.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
@Override
public void move(Pelouse pelouse, Tondeuse tondeuse) {
Position position = tondeuse.getPosition();
Orientation new_orientation = switch (position.getOrientation()) {
case N -> Orientation.E;
case E -> Orientation.S;
case S -> Orientation.W;
case W -> Orientation.N;
};
tondeuse.setPosition(new Position(position.getX(), position.getY(), new_orientation));
}
}

View File

@ -0,0 +1,14 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
public interface MovementStrategy {
/**
* Executes a movement of a {@link Tondeuse} on the {@link Pelouse}.
*
* @param pelouse The {@link Pelouse} on which the {@link Tondeuse} operates.
* @param tondeuse The {@link Tondeuse} to be moved.
*/
void move(Pelouse pelouse, Tondeuse tondeuse);
}

View File

@ -0,0 +1,20 @@
package com.qrouland.katatondeuse.model;
/**
* Three directions for moving a {@link Tondeuse} using a {@link com.qrouland.katatondeuse.controller.movement.MovementStrategy} .
*/
public enum Direction {
D, // Droite (droite)
G, // Gauche (left)
A; // Avance (ahead)
public static Direction fromChar(char c) {
return switch (c) {
case 'A' -> A;
case 'G' -> G;
case 'D' -> D;
default -> throw new IllegalArgumentException("Invalid direction character: " + c);
};
}
}

View File

@ -0,0 +1,22 @@
package com.qrouland.katatondeuse.model;
/**
* Four cardinal directions for orientations of the {@link Tondeuse}.
*/
public enum Orientation {
N, // North
E, // East
W, // West
S; // South
public static Orientation fromChar(char c) {
return switch (c) {
case 'N' -> N;
case 'E' -> E;
case 'S' -> S;
case 'W' -> W;
default -> throw new IllegalArgumentException("Invalid orientation character: " + c);
};
}
}

View File

@ -0,0 +1,47 @@
package com.qrouland.katatondeuse.model;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a pelouse (lawn) on which {@link Tondeuse} (mowers) operate.
*/
public class Pelouse {
private final int maxX;
private final int maxY;
private final List<Tondeuse> tondeuses;
/**
* Constructs a Pelouse.
*
* @param maxX the max x coordinate.
* @param maxY the max x coordinate.
*/
public Pelouse(int maxX, int maxY) {
this.maxX = maxX;
this.maxY = maxY;
this.tondeuses = new ArrayList<>();
}
public int getMaxX() {
return maxX;
}
public int getMaxY() {
return maxY;
}
public List<Tondeuse> getTondeuses() {
return tondeuses;
}
public void addTondeuse(Tondeuse tondeuse) {
this.tondeuses.add(tondeuse);
}
@Override
public String toString() {
return String.format("Pelouse{maxX=%d, maxY=%d, tondeuses=%s}", maxX, maxY, tondeuses);
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.model;
/**
* Represents the position of a {@link Tondeuse} on a 2D grid (x,y) and its
* orientation (N, E, S, W).
*/
public class Position {
private final int x;
private final int y;
private final Orientation orientation;
/**
* Constructs a Position.
*
* @param x the x coordinate.
* @param y the y coordinate.
* @param orientation the orientation (N, E, S, W).
*/
public Position(int x, int y, Orientation orientation) {
this.x = x;
this.y = y;
this.orientation = orientation;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Orientation getOrientation() {
return orientation;
}
@Override
public String toString() {
return String.format("%d %d %s", x, y, orientation.toString());
}
}

View File

@ -0,0 +1,30 @@
package com.qrouland.katatondeuse.model;
/**
* Represents a Tondeuse (mower) with a current {@link Position} on a 2D grid.
*/
public class Tondeuse {
private Position position;
/**
* Constructs a Tondeuse at the given position.
*
* @param position the initial position of the Tondeuse.
*/
public Tondeuse(Position position) {
this.position = position;
}
public Position getPosition() {
return position;
}
public void setPosition(Position position) {
this.position = position;
}
@Override
public String toString() {
return String.format("Tondeuse { position = %s }", position.toString());
}
}

View File

@ -0,0 +1,82 @@
package com.qrouland.katatondeuse.parser;
import com.qrouland.katatondeuse.model.*;
import org.apache.commons.lang3.tuple.ImmutablePair;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileInputParser {
/**
* Parse instructions for a {@link Tondeuse} initial position and its movement instructions.
*
* @param positionLine string representing the {@link Tondeuse} initial position.
* @param instructionsLine string representing movement instructions.
* @return An ImmutablePair of {@link Tondeuse} and {@link Direction}.
*/
public static ImmutablePair<Tondeuse, List<Direction>> parseTondeuseInstructions(
String positionLine, String instructionsLine) {
if (!positionLine.matches("^\\d+ \\d+ [NESW]$")) {
throw new IllegalArgumentException("Invalid tondeuse position format : " + positionLine);
}
if (!instructionsLine.matches("^[AGD]+$")) {
throw new IllegalArgumentException("Invalid tondeuse instruction format : " + instructionsLine);
}
String[] parts = positionLine.split(" ");
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
Orientation orientation = Orientation.fromChar(parts[2].charAt(0));
List<Direction> directions = new ArrayList<>();
for (char c : instructionsLine.toCharArray()) {
directions.add(Direction.fromChar(c));
}
Tondeuse tondeuse = new Tondeuse(new Position(x, y, orientation));
return new ImmutablePair<>(tondeuse, directions);
}
/**
* Parse a line to instantiate a {@link Pelouse}.
*
* @param line The line of the pelouse definition to parse.
* @return a Pelouse of the correct format.
*/
public static Pelouse parsePelouseDefinition(String line) {
String pattern = "^\\d+ \\d+$";
if (!line.matches(pattern)) {
throw new IllegalArgumentException("Invalid Pelouse size format: " + line);
}
String[] parts = line.split(" ");
int xMax = Integer.parseInt(parts[0]);
int yMax = Integer.parseInt(parts[1]);
return new Pelouse(xMax, yMax);
}
/**
* Parses the input arguments to retrieve the input file.
*
* @param args cli args.
* @return a File object representing the input file.
* @throws IllegalArgumentException if no arguments provided or the file does not exist.
*/
public static File parseInputFile(String[] args) {
if (args.length < 1) {
throw new IllegalArgumentException("Missing input file argument.");
}
File inputFile = new File(args[0]);
if (!inputFile.exists()) {
throw new IllegalArgumentException("File does not exist: " + args[0]);
}
return inputFile;
}
}

View File

@ -0,0 +1,58 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public class ConsolePelouseView implements PelouseView {
/**
* Displays the current state of the {@link Pelouse}, including the positions of all {@link Tondeuse}.
*
* @param pelouse The {@link Pelouse} to be displayed.
*/
@Override
public void displayPelouseState(Pelouse pelouse) {
System.out.printf("Pelouse %d x %d : ", pelouse.getMaxX(), pelouse.getMaxY());
for (int i = 0; i < pelouse.getTondeuses().size(); i++) {
Tondeuse tondeuse = pelouse.getTondeuses().get(i);
System.out.print(tondeuse.getPosition());
if (i < pelouse.getTondeuses().size() - 1) {
System.out.print(" | ");
}
}
System.out.println();
}
/**
* Displays a message indicating a new {@link Tondeuse} has been added.
*
* @param tondeuse The {@link Tondeuse} that was added.
*/
@Override
public void displayTondeuseAdd(Tondeuse tondeuse) {
System.out.printf("Added : %s%n", tondeuse.getPosition());
}
/**
* Displays a message indicating a {@link Tondeuse} has moved from its origin to its destination.
*
* @param origin The original position of the {@link Tondeuse}.
* @param destination The new position of the {@link Tondeuse}.
* @param movement The {@link MovementStrategy} used for the movement.
*/
@Override
public void displayTondeuseMove(Position origin, Position destination, MovementStrategy movement) {
System.out.printf("Moved : %s to %s using %s%n", origin, destination, movement.getClass().getSimpleName());
}
/**
* Displays an error message.
*
* @param message The error message.
*/
@Override
public void displayError(String message) {
System.err.println(message);
}
}

View File

@ -0,0 +1,39 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Position;
import com.qrouland.katatondeuse.model.Tondeuse;
public interface PelouseView {
/**
* Displays the current state of the {@link Pelouse}, including the positions of all {@link Tondeuse}.
*
* @param pelouse The {@link Pelouse} to be displayed.
*/
void displayPelouseState(Pelouse pelouse);
/**
* Displays a message indicating a new {@link Tondeuse} has been added.
*
* @param tondeuse The {@link Tondeuse} that was added.
*/
void displayTondeuseAdd(Tondeuse tondeuse);
/**
* Displays a message indicating a {@link Tondeuse} has moved from its origin {@link Position} to its destination {@link Position}.
*
* @param origin The original {@link Position} of the {@link Tondeuse}.
* @param destination The new {@link Position} of the {@link Tondeuse}.
* @param movement The {@link MovementStrategy} used for the movement.
*/
void displayTondeuseMove(Position origin, Position destination, MovementStrategy movement);
/**
* Displays an error message.
*
* @param message The error message.
*/
void displayError(String message);
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.view.PelouseView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.*;
import java.util.List;
public class TontePelouseControllerTest {
private PelouseView pelouseViewMock;
@BeforeEach
public void setup() {
pelouseViewMock = mock(PelouseView.class);
}
@Test
public void testRun() throws IOException {
String fileContent = """
5 5
1 2 N
GAGAGAGAA
3 3 E
AADAADADDA
""";
File tempFile = File.createTempFile("testInstructions", ".txt");
tempFile.deleteOnExit();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
writer.write(fileContent);
}
TontePelouseController controller = new TontePelouseController(tempFile, pelouseViewMock);
controller.run();
verify(pelouseViewMock, atLeastOnce()).displayPelouseState(any(Pelouse.class));
verify(pelouseViewMock, atLeastOnce()).displayTondeuseAdd(any(Tondeuse.class));
ArgumentCaptor<Tondeuse> tondeuseCaptor = ArgumentCaptor.forClass(Tondeuse.class);
verify(pelouseViewMock, atLeastOnce()).displayTondeuseAdd(tondeuseCaptor.capture());
List<Tondeuse> addedTondeuses = tondeuseCaptor.getAllValues();
assertEquals(2, addedTondeuses.size());
assertEquals(1, addedTondeuses.get(0).getPosition().getX());
assertEquals(3, addedTondeuses.get(0).getPosition().getY());
assertEquals(com.qrouland.katatondeuse.model.Orientation.N, addedTondeuses.get(0).getPosition().getOrientation());
assertEquals(5, addedTondeuses.get(1).getPosition().getX());
assertEquals(1, addedTondeuses.get(1).getPosition().getY());
assertEquals(com.qrouland.katatondeuse.model.Orientation.E, addedTondeuses.get(1).getPosition().getOrientation());
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.controller;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import com.qrouland.katatondeuse.controller.movement.MovementStrategy;
import com.qrouland.katatondeuse.model.*;
import com.qrouland.katatondeuse.view.PelouseView;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
public class TonteTondeuseControllerTest {
private Pelouse pelouse;
private Tondeuse tondeuse;
private PelouseView pelouseView;
private TonteTondeuseController controller;
@BeforeEach
public void setup() {
pelouse = new Pelouse(5, 5);
Position initialPosition = new Position(1, 2, Orientation.N);
tondeuse = new Tondeuse(initialPosition);
pelouseView = mock(PelouseView.class);
controller = new TonteTondeuseController(tondeuse, pelouse, pelouseView);
}
@Test
public void testExecuteInstructions() {
List<Direction> instructions = List.of(Direction.A, Direction.D, Direction.G);
controller.executeInstructions(instructions);
verify(pelouseView, times(3))
.displayTondeuseMove(any(Position.class), any(Position.class), any(MovementStrategy.class));
Position finalPosition = tondeuse.getPosition();
assertEquals("1 3 N", finalPosition.toString());
}
}

View File

@ -0,0 +1,103 @@
package com.qrouland.katatondeuse.controller.movement;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MovementAheadTest {
private MovementAhead movementAhead;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementAhead = new MovementAhead();
pelouse = new Pelouse(5, 5);
}
@Test
void moveNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.N));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
@Test
void moveSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.S));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void moveEast() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.E));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(2, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void moveWest() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 2, Orientation.W));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(2, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void move_OutOfBounds_MaxY() {
Tondeuse tondeuse = new Tondeuse(new Position(3, pelouse.getMaxY(), Orientation.N));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(pelouse.getMaxY(), pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
@Test
void move_OutOfBounds_MaxX() {
Tondeuse tondeuse = new Tondeuse(new Position(pelouse.getMaxX(), 3, Orientation.E));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(pelouse.getMaxX(), pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void move_OutOfBounds_MinY() {
Tondeuse tondeuse = new Tondeuse(new Position(3, 0, Orientation.S));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(3, pos.getX());
assertEquals(0, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void mov_OutOfBounds_MinX() {
Tondeuse tondeuse = new Tondeuse(new Position(0, 3, Orientation.W));
movementAhead.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(0, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller.movement;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MovementLeftTest {
private MovementLeft movementLeft;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementLeft = new MovementLeft();
pelouse = new Pelouse(5, 5);
}
@Test
void testTurnLeftFromNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.N));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void testTurnLeftFromWest() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.W));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void testTurnLeftFromSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.S));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void testTurnLeftFromEast() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.E));
movementLeft.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(2, pos.getX());
assertEquals(3, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
}

View File

@ -0,0 +1,62 @@
package com.qrouland.katatondeuse.controller.movement;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class MovementRightTest {
private MovementRight movementRight;
private Pelouse pelouse;
@BeforeEach
void setUp() {
movementRight = new MovementRight();
pelouse = new Pelouse(5, 5);
}
@Test
void testTurnRightFromNorth() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.N));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.E, pos.getOrientation());
}
@Test
void testTurnRightFromEast() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.E));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.S, pos.getOrientation());
}
@Test
void testTurnRightFromSouth() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.S));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.W, pos.getOrientation());
}
@Test
void testTurnRightFromWest() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 1, Orientation.W));
movementRight.move(pelouse, tondeuse);
Position pos = tondeuse.getPosition();
assertEquals(1, pos.getX());
assertEquals(1, pos.getY());
assertEquals(Orientation.N, pos.getOrientation());
}
}

View File

@ -0,0 +1,28 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class DirectionTest {
@Test
void testFromChar() {
assertEquals(Direction.A, Direction.fromChar('A'));
assertEquals(Direction.G, Direction.fromChar('G'));
assertEquals(Direction.D, Direction.fromChar('D'));
}
@Test
void testFromChar_invalidCharacter() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('X'));
assertEquals("Invalid direction character: X", exception.getMessage());
}
@Test
void testFromChar_lowercaseInput() {
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('a'));
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('g'));
assertThrows(IllegalArgumentException.class, () -> Direction.fromChar('d'));
}
}

View File

@ -0,0 +1,27 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class OrientationTest {
@Test
void testFromChar() {
assertEquals(Orientation.N, Orientation.fromChar('N'));
assertEquals(Orientation.E, Orientation.fromChar('E'));
assertEquals(Orientation.S, Orientation.fromChar('S'));
assertEquals(Orientation.W, Orientation.fromChar('W'));
}
@Test
void testFromChar_invalidCharacter() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> Orientation.fromChar('X'));
assertEquals("Invalid orientation character: X", exception.getMessage());
}
@Test
void testFromChar_lowercaseInput() {
assertThrows(IllegalArgumentException.class, () -> Orientation.fromChar('n'));
}
}

View File

@ -0,0 +1,60 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class PelouseTest {
private Pelouse pelouse;
@BeforeEach
void setUp() {
pelouse = new Pelouse(5, 5);
}
@Test
void testAddTondeuseAndGetTondeuses() {
Tondeuse tondeuse = new Tondeuse(new Position(1, 2, Orientation.N));
pelouse.addTondeuse(tondeuse);
List<Tondeuse> tondeuses = pelouse.getTondeuses();
assertEquals(1, tondeuses.size());
assertSame(tondeuse, tondeuses.get(0));
}
@Test
void testGetTondeuses() {
assertTrue(pelouse.getTondeuses().isEmpty());
}
@Test
void testGetMaxX_returnsCorrectValue() {
Pelouse pelouse = new Pelouse(10, 7);
assertEquals(10, pelouse.getMaxX());
}
@Test
void testGetMaxY_returnsCorrectValue() {
Pelouse pelouse = new Pelouse(10, 7);
assertEquals(7, pelouse.getMaxY());
}
@Test
void testToString() {
Tondeuse tondeuse = new Tondeuse(new Position(3, 4, Orientation.E));
pelouse.addTondeuse(tondeuse);
String result = pelouse.toString();
assertTrue(result.contains("maxX=5"));
assertTrue(result.contains("maxY=5"));
assertTrue(result.contains("Tondeuse { position ="));
}
}

View File

@ -0,0 +1,14 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PositionTest {
@Test
void testPositionToString() {
Position position = new Position(0, 0, Orientation.S);
String expected = "0 0 S";
assertEquals(expected, position.toString());
}
}

View File

@ -0,0 +1,41 @@
package com.qrouland.katatondeuse.model;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TondeuseTest {
@Test
void testPositionGetter() {
Position position = new Position(1, 2, Orientation.N);
Tondeuse tondeuse = new Tondeuse(position);
assertEquals(position, tondeuse.getPosition());
}
@Test
void testSetPosition() {
Position initialPosition = new Position(1, 2, Orientation.N);
Tondeuse tondeuse = new Tondeuse(initialPosition);
Position newPosition = new Position(3, 4, Orientation.E);
tondeuse.setPosition(newPosition);
assertEquals(newPosition, tondeuse.getPosition());
assertEquals(3, tondeuse.getPosition().getX());
assertEquals(4, tondeuse.getPosition().getY());
assertEquals(Orientation.E, tondeuse.getPosition().getOrientation());
}
@Test
void testTondeuseToString() {
Position position = new Position(3, 4, Orientation.E);
Tondeuse tondeuse = new Tondeuse(position);
String expected = "Tondeuse { position = 3 4 E }";
assertEquals(expected, tondeuse.toString());
}
}

View File

@ -0,0 +1,107 @@
package com.qrouland.katatondeuse;
import com.qrouland.katatondeuse.model.Direction;
import com.qrouland.katatondeuse.model.Orientation;
import com.qrouland.katatondeuse.model.Pelouse;
import com.qrouland.katatondeuse.model.Tondeuse;
import com.qrouland.katatondeuse.parser.FileInputParser;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class FileInputParserTest {
@Test
void testParseTondeuseInstructions_validInput() {
String position = "1 2 N";
String instructions = "AGAD";
ImmutablePair<Tondeuse, List<Direction>> result = FileInputParser.parseTondeuseInstructions(position, instructions);
assertNotNull(result);
assertEquals(1, result.left.getPosition().getX());
assertEquals(2, result.left.getPosition().getY());
assertEquals(Orientation.N, result.left.getPosition().getOrientation());
assertEquals(List.of(Direction.A, Direction.G, Direction.A, Direction.D), result.right);
}
@Test
void testParseTondeuseInstructions_invalidPositionFormat() {
String invalidPosition = "1 N 2";
String instructions = "AGAD";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parseTondeuseInstructions(invalidPosition, instructions));
assertEquals("Invalid tondeuse position format : " + invalidPosition, exception.getMessage());
}
@Test
void testParseTondeuseInstructions_invalidInstructionsFormat() {
String position = "1 2 N";
String invalidInstructions = "AGZRA";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parseTondeuseInstructions(position, invalidInstructions));
assertEquals("Invalid tondeuse instruction format : " + invalidInstructions, exception.getMessage());
}
@Test
void testParsePelouseDefinition_ValidInput() {
Pelouse pelouse = FileInputParser.parsePelouseDefinition("5 5");
assertNotNull(pelouse);
assertEquals(5, pelouse.getMaxX());
assertEquals(5, pelouse.getMaxY());
}
@Test
void testParsePelouseDefinition_InvalidInput() {
String invalidLine = "5x5";
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
FileInputParser.parsePelouseDefinition(invalidLine));
assertTrue(exception.getMessage().contains("Invalid Pelouse size format"));
}
@Test
void testInputParserFile_ValidInputFile() throws IOException {
File tempFile = File.createTempFile("test", ".txt");
tempFile.deleteOnExit();
String[] args = { tempFile.getAbsolutePath() };
File result = FileInputParser.parseInputFile(args);
assertNotNull(result);
assertTrue(result.exists());
assertEquals(tempFile.getAbsolutePath(), result.getAbsolutePath());
}
@Test
void testFileInputParser_MissingArgument() {
String[] args = {};
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
FileInputParser.parseInputFile(args);
});
assertEquals("Missing input file argument.", exception.getMessage());
}
@Test
void testFileInputParser_NonExistentFile() {
String[] args = { "nonexistentfile.txt" };
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
FileInputParser.parseInputFile(args);
});
assertEquals("File does not exist: nonexistentfile.txt", exception.getMessage());
}
}

View File

@ -0,0 +1,81 @@
package com.qrouland.katatondeuse.view;
import com.qrouland.katatondeuse.controller.movement.MovementAhead;
import com.qrouland.katatondeuse.model.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.*;
class ConsolePelouseViewTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
private final ConsolePelouseView view = new ConsolePelouseView();
@BeforeEach
void setUpStreams() {
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@AfterEach
void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
@Test
void testDisplayPelouseState() {
Pelouse pelouse = new Pelouse(3, 3);
pelouse.addTondeuse(new Tondeuse(new Position(0, 0, Orientation.S)));
pelouse.addTondeuse(new Tondeuse(new Position(2, 2, Orientation.E)));
view.displayPelouseState(pelouse);
String output = outContent.toString().trim();
assertTrue(output.contains("Pelouse 3 x 3"));
assertTrue(output.contains("0 0 S | 2 2 E"));
}
@Test
void testDisplayTondeuseAdd() {
Tondeuse tondeuse = new Tondeuse(new Position(2, 3, Orientation.E));
view.displayTondeuseAdd(tondeuse);
String output = outContent.toString().trim();
assertTrue(output.contains("Added"));
assertTrue(output.contains("2 3 E"));
}
@Test
void testDisplayTondeuseMove() {
Position origin = new Position(1, 1, Orientation.N);
Position destination = new Position(1, 2, Orientation.N);
MovementAhead movement = new MovementAhead();
view.displayTondeuseMove(origin, destination, movement);
String output = outContent.toString().trim();
assertTrue(output.contains("Moved"));
assertTrue(output.contains("1 1 N"));
assertTrue(output.contains("1 2 N"));
assertTrue(output.contains("MovementAhead"));
}
@Test
void testDisplayError() {
view.displayError("Test error message");
String output = errContent.toString().trim();
assertEquals("Test error message", output);
}
}