diff --git a/scenariovirus.txt b/scenariovirus.txt new file mode 100644 index 0000000000000000000000000000000000000000..d43e533bb6bbb2ca17d9d80a418b2e6f13b7b03b --- /dev/null +++ b/scenariovirus.txt @@ -0,0 +1,15 @@ +Patients : +- Interactions et mouvement : Se déplacent et interagissent socialement, augmentant ainsi les chances de transmission du virus. Certains peuvent être asymptomatiques. +- Réaction en cas de maladie : S'ils se rendent compte qu'ils sont malades, ils cherchent un docteur pour se faire soigner. Sinon, ils continuent leur vie normalement, pouvant transmettre le virus sans le savoir. + + +Virus : +- Mouvement et infection : Entité unique sur le plateau qui se déplace aléatoirement, infectant les patients lorsqu'il passe à proximité. +- Le virus infecte les patients lorsqu'il passe à proximité. +- Évolue tous les 30 tours pour donner un nouveau variant, lorsqu'un nouveau variant est détecté, les médecins mettent plus de temps à le soigner au début + + +Docteurs : +- Traitement des patients : Traitent les patients malades qui se trouvent dans des cases adjacentes à leur position. +- Statique : Ne se déplacent pas entre les tours, ce qui nécessite que les patients viennent à eux pour être soignés. + diff --git a/src/main/java/app/SimulatorApplication.java b/src/main/java/app/SimulatorApplication.java index 86abb8525b4a41c5a54cea60981c01025d5ae3f8..10fa4af1b221ac2dd223eb45bd7fbe5a8b97fa48 100644 --- a/src/main/java/app/SimulatorApplication.java +++ b/src/main/java/app/SimulatorApplication.java @@ -13,15 +13,20 @@ import javafx.stage.Stage; public class SimulatorApplication extends javafx.application.Application { private static final String VIEW_RESOURCE_PATH = "/view/view.fxml"; private static final String APP_NAME = "Firefighter simulator"; - private static final int ROW_COUNT = 20; - private static final int COLUMN_COUNT = 20; - private static final int BOX_WIDTH = 25; - private static final int BOX_HEIGHT = 25; - public static final int INITIAL_FIRE_COUNT = 3; - public static final int INITIAL_FIREFIGHTER_COUNT = 6; + private static final int ROW_COUNT = 40; + private static final int COLUMN_COUNT = 40; + private static final int BOX_WIDTH = 15; + private static final int BOX_HEIGHT = 15; + public static final int INITIAL_FIRE_COUNT = 8; + public static final int INITIAL_FIREFIGHTER_COUNT = 12; + public static final int INITIAL_MOTORIZED_FIREFIGHTER_COUNT = 8; + public static final int INITIAL_CLOUD_COUNT = 8; + public static final int INITIAL_MOUNTAIN_COUNT= 6; + public static final int TURNS_FOR_SPAWNING_AIRTANKER = 10; private Stage primaryStage; private Parent view; + private void initializePrimaryStage(Stage primaryStage) { this.primaryStage = primaryStage; this.primaryStage.setTitle(APP_NAME); @@ -44,7 +49,7 @@ public class SimulatorApplication extends javafx.application.Application { view = loader.load(); Controller controller = loader.getController(); controller.initialize(BOX_WIDTH, BOX_HEIGHT, COLUMN_COUNT, ROW_COUNT, - INITIAL_FIRE_COUNT, INITIAL_FIREFIGHTER_COUNT); + INITIAL_FIRE_COUNT, INITIAL_FIREFIGHTER_COUNT, INITIAL_MOTORIZED_FIREFIGHTER_COUNT, INITIAL_CLOUD_COUNT, INITIAL_MOUNTAIN_COUNT, TURNS_FOR_SPAWNING_AIRTANKER); } private void showScene() { diff --git a/src/main/java/controller/Controller.java b/src/main/java/controller/Controller.java index 154a1b81d01ae5805d59c55456aa57465a1e6b38..97a8c819e04f4036c399b435a844740cfc98727f 100644 --- a/src/main/java/controller/Controller.java +++ b/src/main/java/controller/Controller.java @@ -3,7 +3,9 @@ package controller; import static java.util.Objects.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javafx.animation.Animation; import javafx.animation.KeyFrame; @@ -18,8 +20,18 @@ import javafx.scene.control.ToggleGroup; import javafx.util.Duration; import javafx.util.Pair; import model.Board; +import model.EmptySquare; import model.Entity; -import model.FireFighterScenario; +import model.EntityFactory; +import model.Model; +import model.Square; +import model.firefighterscenario.Cloud; +import model.firefighterscenario.Fire; +import model.firefighterscenario.FireFighter; +import model.firefighterscenario.FireFighterScenario; +import model.firefighterscenario.MotorizedFireFighter; +import model.firefighterscenario.Mountain; +import model.firefighterscenario.Rockery; import util.Position; import view.Grid; import view.ViewElement; @@ -40,7 +52,8 @@ public class Controller { @FXML private Grid<ViewElement> grid; private Timeline timeline; - private Board<Entity> board; + private Board<Square> board; + private Model model; @FXML private void initialize() { @@ -54,15 +67,16 @@ public class Controller { pauseToggleButton.setSelected(true); } - private void setModel(Board<Entity> board) { - this.board = requireNonNull(board, "board is null"); + private void setModel(Model model) { + this.board = requireNonNull(model.getBoard(), "board is null"); + this.model = model; } - private void updateBoard(){ - List<Position> updatedPositions = board.updateToNextGeneration(); + private void updateBoard() { + List<Position> updatedPositions = model.updateToNextGeneration(); List<Pair<Position, ViewElement>> updatedSquares = new ArrayList<>(); - for(Position updatedPosition : updatedPositions){ - Entity squareState = board.getState(updatedPosition); + for (Position updatedPosition : updatedPositions) { + Square squareState = board.getStates(updatedPosition); ViewElement viewElement = getViewElement(squareState); updatedSquares.add(new Pair<>(updatedPosition, viewElement)); } @@ -70,25 +84,30 @@ public class Controller { updateGenerationLabel(board.stepNumber()); } - private void repaintGrid(){ + private void repaintGrid() { int columnCount = board.columnCount(); int rowCount = board.rowCount(); ViewElement[][] viewElements = new ViewElement[rowCount][columnCount]; - for(int column = 0; column < columnCount; column++) - for(int row = 0; row < rowCount; row++) - viewElements[row][column] = getViewElement(board.getState(new Position(row, column))); + for (int column = 0; column < columnCount; column++) + for (int row = 0; row < rowCount; row++) + viewElements[row][column] = getViewElement(board.getStates(new Position(row, column))); grid.repaint(viewElements); updateGenerationLabel(board.stepNumber()); } - private ViewElement getViewElement(Entity entity) { - return new ViewElement(entity.getViewColor()); + private ViewElement getViewElement(Square square) { + if (!square.getEntities().isEmpty()) { + for (Entity entity : square.getEntities()) { + if(entity instanceof EmptySquare)continue; + return entity.getViewElement(); + } + } + return new ViewElement(square.getViewColor()); } private void initializeTimeline() { Duration duration = new Duration(Controller.PERIOD_IN_MILLISECONDS); - EventHandler<ActionEvent> eventHandler = - event -> updateBoard(); + EventHandler<ActionEvent> eventHandler = event -> updateBoard(); KeyFrame keyFrame = new KeyFrame(duration, eventHandler); timeline = new Timeline(keyFrame); timeline.setCycleCount(Animation.INDEFINITE); @@ -113,23 +132,47 @@ public class Controller { public void restartButtonAction() { this.pause(); board.reset(); + System.gc(); pauseToggleButton.setSelected(true); repaintGrid(); } public void initialize(int squareWidth, int squareHeight, int columnCount, - int rowCount, int initialFireCount, int initialFirefighterCount) { + int rowCount, int initialFireCount, int initialFirefighterCount, int initialMotorizedFirefightersCount, int initialcloudCount, int initialmountaincount, int turnsForSpawningAirTanker) { grid.setDimensions(columnCount, rowCount, squareWidth, squareHeight); - this.setModel(new FireFighterScenario(columnCount, rowCount, initialFireCount, initialFirefighterCount)); + + Map<EntityFactory, Integer> entityCounts = new HashMap<EntityFactory, Integer>(); + + entityCounts.put((pos, b) -> new Fire(pos), initialFireCount); + entityCounts.put((pos, b) -> new FireFighter(pos,b), initialFirefighterCount); + entityCounts.put((pos, b) -> new MotorizedFireFighter(pos, b), initialMotorizedFirefightersCount); + entityCounts.put((pos, b) -> new Cloud(pos, b), initialcloudCount); + entityCounts.put((pos, b) -> new Mountain(pos), initialmountaincount); + entityCounts.put((pos, b) -> new Rockery(pos), 3); + + + /* + entityCounts.put((pos, b) -> new Rock(pos), 10); + entityCounts.put((pos, b) -> new Cisor(pos), 10); + entityCounts.put((pos, b) -> new Paper(pos), 10); + */ + + /* + entityCounts.put((pos, b) -> new Patient(pos), 70); + entityCounts.put((pos, b) -> new Virus(pos), 6); + entityCounts.put((pos, b) -> new Doctor(pos), 3); + */ + Model model = new FireFighterScenario(columnCount, rowCount, entityCounts); + this.setModel(model); repaintGrid(); } - + public void oneStepButtonAction() { this.pause(); updateBoard(); } - private void updateGenerationLabel(int value){ + private void updateGenerationLabel(int value) { generationNumberLabel.setText(Integer.toString(value)); } } \ No newline at end of file diff --git a/src/main/java/model/Board.java b/src/main/java/model/Board.java index 1326ce88c7878fd042329c66fe7cdaa66941452f..6fc78ddca79a959bf915ed614020208bd9016cc4 100644 --- a/src/main/java/model/Board.java +++ b/src/main/java/model/Board.java @@ -1,11 +1,13 @@ package model; import java.util.List; +import java.util.Map; import util.Position; /** - * This interface represents a generic board for modeling various state-based systems. + * This interface represents a generic board for modeling various state-based + * systems. * * @param <S> The type of state represented on the board. */ @@ -17,15 +19,15 @@ public interface Board<S> { * @param position The position on the board for which to retrieve the state. * @return The state at the specified position. */ - S getState(Position position); + S getStates(Position position); /** * Set the state of a specific position on the board to the specified state. * - * @param state The state to set for the given position. + * @param state The state to set for the given position. * @param position The position on the board for which to set the state. */ - void setState(S state, Position position); + void setSquare(S square); /** * Get the number of rows in the board. @@ -41,15 +43,6 @@ public interface Board<S> { */ int columnCount(); - /** - * Update the board to its next generation or state. This method may modify the - * internal state of the board and return a list of positions that have changed - * during the update. - * - * @return A list of positions that have changed during the update. - */ - List<Position> updateToNextGeneration(); - /** * Reset the board to its initial state. */ @@ -63,13 +56,25 @@ public interface Board<S> { int stepNumber(); public int getStepNumber(); + // Le booléen replaceState permet de forcer le remplacement des cases vides - public void setState(Entity state, Position position, boolean replaceStates); + public void setSquare(S square, boolean replaceStates); public boolean doesPositionExist(Position position); - public void clearCase(Position position); + public void clearCaseFrom(Entity entity, Position position); - public Position getNearestEntity(Position fromPos, Class<?> entityType); -} + public Position getNearestEntity(Position fromPos, Class<?> entityType, List<Entity> exclusionList); + + public boolean doesSquareContainEntity(Position squarePos, Class<?> entityType); + + public void addEntityAtSquare(Entity entity, Position position); + public void placeInitialEntities(Map<EntityFactory, Integer> entityCounts); + //Return if the position is completely free + public boolean isPositionEmpty(Position position); + //Return if the position is available for the specified priority + public boolean isPositionFree(Position position, int priority); + + +} diff --git a/src/main/java/model/EmptySquare.java b/src/main/java/model/EmptySquare.java index 113ea0a394870345e6520690e083b03c9bb70a7c..e14e38d7c9164b0f00a3146efd551e1bf7ded8c7 100644 --- a/src/main/java/model/EmptySquare.java +++ b/src/main/java/model/EmptySquare.java @@ -4,23 +4,38 @@ import java.util.ArrayList; import java.util.List; import javafx.scene.paint.Color; +import model.firefighterscenario.Cloud; import util.Position; +import view.ViewElement; -public class EmptySquare implements Entity{ +public class EmptySquare implements Entity { private Position position; private final Color viewColor = Color.WHITE; private int age; - public EmptySquare(Position p){ + private final int priotity = -1; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/img.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public EmptySquare(Position p) { this.position = p; this.age = -999; } - public EmptySquare(Position p, int age){ + + public EmptySquare(Position p, int age) { this.position = p; this.age = age; } + @Override - public List<Position> nextTurn(Board<Entity> board) { + public List<Position> nextTurn(Board<Square> board) { return new ArrayList<Position>(); } @@ -33,10 +48,11 @@ public class EmptySquare implements Entity{ public void setPosition(Position p) { this.position = p; } - - public Color getViewColor(){ + + public Color getViewColor() { return this.viewColor; } + @Override public int getAge() { return this.age; @@ -46,4 +62,19 @@ public class EmptySquare implements Entity{ public void incrementAge() { age = age + 1; } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public int getPriority(){ + return this.priotity; + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } } diff --git a/src/main/java/model/Entity.java b/src/main/java/model/Entity.java index 5569aa5ced37576d3776bd79bb3bb58ba675237b..2b53e3ae3319c167dfee1d7843b73d3fa312c4bf 100644 --- a/src/main/java/model/Entity.java +++ b/src/main/java/model/Entity.java @@ -1,9 +1,10 @@ package model; - import java.util.List; import javafx.scene.paint.Color; import util.Position; +import view.ViewElement; + public interface Entity { /** @@ -12,10 +13,13 @@ public interface Entity { * @param b Le plateau de jeu contenant des entités. * @return Une liste de positions affectées durant le tour (que la vue doit mettre à jour). */ - public List<Position> nextTurn(Board<Entity> board); + public List<Position> nextTurn(Board<Square> board); public Position getPosition(); public void setPosition(Position p); public int getAge(); + public void setAge(int age); public void incrementAge(); public Color getViewColor(); + public int getPriority(); + public ViewElement getViewElement(); } diff --git a/src/main/java/model/EntityFactory.java b/src/main/java/model/EntityFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8d7249c90e63612199f1d40835910e92e2fc62e7 --- /dev/null +++ b/src/main/java/model/EntityFactory.java @@ -0,0 +1,7 @@ +package model; + +import util.Position; + +public interface EntityFactory { + Entity create(Position position, Board<Square> board); +} diff --git a/src/main/java/model/EntityNotFoundException.java b/src/main/java/model/EntityNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..9902ad982952d8d34e0ceb55266b8782b9901a98 --- /dev/null +++ b/src/main/java/model/EntityNotFoundException.java @@ -0,0 +1,7 @@ +package model; + +class EntityNotFoundException extends Exception { + public EntityNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/model/EntityScenario.java b/src/main/java/model/EntityScenario.java deleted file mode 100644 index 094862a46b940eeefeacb5d0582d5c5a6d250fc8..0000000000000000000000000000000000000000 --- a/src/main/java/model/EntityScenario.java +++ /dev/null @@ -1,14 +0,0 @@ -package model; - -import util.Matrix; -import util.Position; - -public abstract class EntityScenario implements Scenario{ - public void initScenario(Matrix<Entity> matrix){ - for(int x = 0; x < matrix.getRows(); x++){ - for(int y = 0; y < matrix.getColumns(); y++){ - matrix.set(x,y, new EmptySquare(new Position(x, y))); - } - } - } -} diff --git a/src/main/java/model/EntitySpawner.java b/src/main/java/model/EntitySpawner.java new file mode 100644 index 0000000000000000000000000000000000000000..bd6fa6e39196e6ffd798760bc8546e96887694f2 --- /dev/null +++ b/src/main/java/model/EntitySpawner.java @@ -0,0 +1,262 @@ +package model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import util.Direction; +import util.Position; +import util.PositionUtil; + +public class EntitySpawner { + private final Board<Square> board; + private final Random random = new Random(); + + public EntitySpawner(Board<Square> board) { + this.board = board; + } + + public void spawnEntities(Map<EntityFactory, Integer> entityCounts) { + Map<EntityFactory, Integer> counts = new HashMap<>(); + for (EntityFactory factory : entityCounts.keySet()) { + counts.put(factory, 0); + } + + int totalEntitiesToPlace = entityCounts.values().stream().mapToInt(Integer::intValue).sum(); + int totalEntitiesPlaced = 0; + + int chance = 5; + List<Position> positions = generateAllPositions(); + + while (totalEntitiesPlaced < totalEntitiesToPlace) { + Collections.shuffle(positions); + + for (Position pos : positions) { + if (board.getStates(pos).isEmpty()) { + for (EntityFactory factory : entityCounts.keySet()) { + int desiredCount = entityCounts.get(factory); + int currentCount = counts.get(factory); + + if (currentCount < desiredCount && random.nextInt(100) < chance) { + Entity entity = factory.create(pos, board); + board.setSquare(new Square(pos, entity)); + counts.put(factory, currentCount + 1); + totalEntitiesPlaced++; + + if (totalEntitiesPlaced == totalEntitiesToPlace) { + return; + } + + break; // Move to the next position + } + } + } + } + + // Increase chance after each full traversal + chance = Math.min(chance + 5, 100); + } + } + + private List<Position> generateAllPositions() { + List<Position> positions = new ArrayList<>(); + for (int x = 0; x < board.rowCount(); x++) { + for (int y = 0; y < board.columnCount(); y++) { + positions.add(new Position(x, y)); + } + } + return positions; + } + + public static void generateEntitiesInLine(Board<Square> board, Position anchor, EntityFactory entityFactory) { + int xIncrement = 0; + int yIncrement = 0; + + // Determine increments based on which coordinate is zero + if (anchor.x() == 0 && anchor.y() >= 0) { + // Starting from the left edge (x == 0), increment x to move right + xIncrement = 1; + } else if (anchor.y() == 0 && anchor.x() >= 0) { + // Starting from the top edge (y == 0), increment y to move down + yIncrement = 1; + } else { + // If neither x nor y is 0, cannot determine direction + throw new IllegalArgumentException("Anchor position must have x or y equal to 0"); + } + + int x = anchor.x(); + int y = anchor.y(); + + // Continue until we reach the edge of the board + while (board.doesPositionExist(new Position(x, y))) { + Position pos = new Position(x, y); + // Create a new entity for each position + Entity entity = entityFactory.create(pos, board); + entity.setPosition(pos); // Set the position if not already set in the factory + board.addEntityAtSquare(entity, pos); + + x += xIncrement; + y += yIncrement; + } + } + + public static List<Position> generateEntitiesInRandomLine(Board<Square> board, Position anchor, int maxStepsInDirection, int minimumRoadLength) { + Random random = new Random(); + List<Position> path = new ArrayList<>(); + int x = anchor.x(); + int y = anchor.y(); + + // Toutes les directions possibles + List<Direction> allDirections = Arrays.asList(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST); + + // Choisir une direction initiale aléatoire + Direction initialDirection = allDirections.get(random.nextInt(allDirections.size())); + path.add(new Position(x, y)); // Ajouter la position de l'ancre au chemin + int roadLength = 1; + + // Déterminer la direction interdite (opposée à la direction initiale) + Direction forbiddenDirection = PositionUtil.getOppositeDirection(initialDirection); + + // Initialiser la direction courante + Direction currentDirection = initialDirection; + int stepsInCurrentDirection = 0; + + // Ensemble des directions définitivement exclues (direction initiale) + Set<Direction> permanentlyExcludedDirections = new HashSet<>(); + permanentlyExcludedDirections.add(initialDirection); + + // Ensemble des directions temporairement exclues (initialement vide) + Set<Direction> temporarilyExcludedDirections = new HashSet<>(); + + while (true) { + // Calculer la prochaine position dans la direction courante + int nextX = x + getXIncrement(currentDirection); + int nextY = y + getYIncrement(currentDirection); + Position nextPos = new Position(nextX, nextY); + + if (board.doesPositionExist(nextPos)) { + // Ajouter la position au chemin + path.add(nextPos); + x = nextX; + y = nextY; + roadLength++; + stepsInCurrentDirection++; + } else { + // La position dans la direction courante est invalide + if (roadLength < minimumRoadLength) { + // Exclure temporairement la direction courante + temporarilyExcludedDirections.add(currentDirection); + + // Choisir une nouvelle direction valide + Direction newDirection = chooseNewDirection(allDirections, currentDirection, forbiddenDirection, + permanentlyExcludedDirections, temporarilyExcludedDirections, board, x, y, random); + + if (newDirection == null) { + // Aucune direction valide disponible pour atteindre la longueur minimale + break; + } + + // Mettre à jour la direction courante + currentDirection = newDirection; + forbiddenDirection = PositionUtil.getOppositeDirection(currentDirection); + stepsInCurrentDirection = 0; + continue; // Recommencer avec la nouvelle direction + } else { + // La longueur minimale est atteinte, arrêter la génération + break; + } + } + + // Vérifier s'il est temps de changer de direction + if (stepsInCurrentDirection >= maxStepsInDirection) { + // Choisir une nouvelle direction + Direction newDirection = chooseNewDirection(allDirections, currentDirection, forbiddenDirection, + permanentlyExcludedDirections, temporarilyExcludedDirections, board, x, y, random); + + if (newDirection == null) { + // Aucune direction valide disponible + break; + } + + // Mettre à jour la direction courante + currentDirection = newDirection; + forbiddenDirection = PositionUtil.getOppositeDirection(currentDirection); + stepsInCurrentDirection = 0; + } + } + + return path; // Retourner la liste des positions formant le serpent + } + + /** + * Choisit une nouvelle direction valide en tenant compte des exclusions permanentes et temporaires. + * + * @param allDirections Toutes les directions possibles. + * @param currentDirection La direction actuelle. + * @param forbiddenDirection La direction opposée à la direction actuelle (interdite). + * @param permanentlyExcludedDirections Les directions définitivement exclues. + * @param temporarilyExcludedDirections Les directions temporairement exclues. + * @param board Le plateau de jeu. + * @param x La coordonnée X actuelle. + * @param y La coordonnée Y actuelle. + * @param random Une instance de Random pour le choix aléatoire. + * @return La nouvelle direction choisie ou null si aucune direction valide n'est disponible. + */ + private static Direction chooseNewDirection(List<Direction> allDirections, Direction currentDirection, Direction forbiddenDirection, + Set<Direction> permanentlyExcludedDirections, Set<Direction> temporarilyExcludedDirections, + Board<Square> board, int x, int y, Random random) { + // Créer une liste de directions valides en excluant : + // - La direction actuelle + // - La direction interdite (opposée à la direction actuelle) + // - Les directions définitivement exclues + // - Les directions temporairement exclues + List<Direction> validDirections = new ArrayList<>(allDirections); + validDirections.remove(currentDirection); + validDirections.remove(forbiddenDirection); + validDirections.removeAll(permanentlyExcludedDirections); + validDirections.removeAll(temporarilyExcludedDirections); + + // Filtrer les directions qui permettent de continuer le chemin + validDirections.removeIf(dir -> !board.doesPositionExist(new Position(x + getXIncrement(dir), y + getYIncrement(dir)))); + + if (validDirections.isEmpty()) { + // Aucune direction valide disponible + return null; + } + + // Choisir une nouvelle direction aléatoirement + return validDirections.get(random.nextInt(validDirections.size())); + } + + private static int getXIncrement(Direction direction) { + switch (direction) { + case NORTH: return -1; + case SOUTH: return 1; + default: return 0; + } + } + + private static int getYIncrement(Direction direction) { + switch (direction) { + case EAST: return 1; + case WEST: return -1; + default: return 0; + } + } + + + + + + + + + + +} diff --git a/src/main/java/model/Fire.java b/src/main/java/model/Fire.java deleted file mode 100644 index 019137ed7268f5d7ff88db50ca42d89afdda1fb2..0000000000000000000000000000000000000000 --- a/src/main/java/model/Fire.java +++ /dev/null @@ -1,67 +0,0 @@ -package model; -import java.util.ArrayList; -import java.util.List; - -import javafx.scene.paint.Color; -import util.Position; -import util.PositionUtil; - -public class Fire implements Entity{ - Board<Entity> b; - private Position position; - private final Color viewColor = Color.RED; - private int age; - public Fire(Position position, Board<Entity> b){ - this.b = b; - this.position = position; - this.age = 0; - } - public Fire(Position position, Board<Entity> b, int age){ - this.b = b; - this.position = position; - this.age = age; - } - - @Override - public List<Position> nextTurn(Board<Entity> board) { - if(board.getStepNumber() % 2 == 0)return new ArrayList<Position>(); - List<Position> positions = PositionUtil.generateAdjacentPositions(position, board); - for(Position p : positions){ - if(b.getState(p) instanceof EmptySquare){ - if(b.getState(p).getAge() < b.getStepNumber()){ - board.setState(new Fire(p, board), p); - } - } - if(!b.doesPositionExist(p)){ - positions.remove(p); - } - } - return positions; - } - - - - - @Override - public void setPosition(Position p) { - this.position = p; - } - @Override - public Position getPosition() { - return this.position; - } - - public Color getViewColor(){ - return this.viewColor; - } - - @Override - public int getAge() { - return this.age; - } - - @Override - public void incrementAge() { - this.age = age + 1 ; - } -} diff --git a/src/main/java/model/FireFighter.java b/src/main/java/model/FireFighter.java deleted file mode 100644 index e9f2afb2c450b85727fea9a8fba26778a4439fea..0000000000000000000000000000000000000000 --- a/src/main/java/model/FireFighter.java +++ /dev/null @@ -1,132 +0,0 @@ -package model; -import java.util.ArrayList; -import java.util.List; - -import javafx.scene.paint.Color; -import util.Position; -import util.PositionUtil; - -public class FireFighter implements Entity{ - private int age; - private Position position; - private final Color viewColor = Color.BLUE; - - - public FireFighter(Position position, Board<Entity> b){ - this.position = position; - this.age = 0; - } - public FireFighter(Position position, Board<Entity> b, int age){ - this.position = position; - this.age = age; - } - - public List<Position> nextTurn(Board<Entity> b) { - List<Position> positions = new ArrayList<>(); - - // Générer les positions adjacentes - List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(position, b); - - // Vérifier s'il y a du feu dans une des positions adjacentes - boolean hasFire = adjacentPositions.stream() - .anyMatch(p -> b.getState(p) instanceof Fire); - - if (hasFire) { - // Si du feu est trouvé, on éteint les feux adjacents - positions.addAll(extinguish(adjacentPositions, b)); - } else { - // Chercher la position du feu le plus proche - Position nearestFirePos = b.getNearestEntity(position, Fire.class); - - if (nearestFirePos != null && !nearestFirePos.equals(position)) { - // Trouver la meilleure position pour se rapprocher du feu - Position nextPos = getNextPositionTowards(position, nearestFirePos, b); - - if (nextPos != null) { - // Mettre à jour la position du pompier - b.clearCase(position); // Vider l'ancienne case - positions.add(new Position(position.x(), position.y())); - this.position = nextPos; - b.setState(this, nextPos); // Mettre à jour la nouvelle case - positions.add(nextPos); - adjacentPositions = PositionUtil.generateAdjacentPositions(nextPos, b); - positions.addAll(extinguish(adjacentPositions, b)); - } - // Aucun déplacement possible = le pompier reste sur place - } - } - - return positions; - } - - private List<Position> extinguish(List<Position> adjacentPositions, Board<Entity> b) { - List<Position> extinguishedPositions = new ArrayList<>(); - for (Position p : adjacentPositions) { - if (b.getState(p) instanceof Fire) { - b.clearCase(p); - extinguishedPositions.add(p); // Ajouter la position où le feu a été éteint - } - } - return extinguishedPositions; - } - - private Position getNextPositionTowards(Position currentPos, Position targetPos, Board<Entity> b) { - // Générer les 8 positions adjacentes possibles - List<Position> possibleMoves = PositionUtil.generateAllAdjacentPositions(currentPos, b); - - // Filtrer les positions qui sont libres - possibleMoves.removeIf(p -> !(b.getState(p) instanceof EmptySquare)); - - // Si aucune position libre n'est disponible, retourner null - if (possibleMoves.isEmpty()) { - return null; - } - - // Calculer la distance actuelle vers la cible - int currentDistance = PositionUtil.getManhattanDistance(currentPos, targetPos); - - // Choisir la position libre qui réduit le plus la distance vers le feu - Position bestMove = null; - int minDistance = currentDistance; - for (Position move : possibleMoves) { - int distance = PositionUtil.getManhattanDistance(move, targetPos); - if (distance < minDistance) { - minDistance = distance; - bestMove = move; - } - } - - // Si aucun déplacement ne réduit la distance, annuler le déplacement - if (bestMove == null) { - return null; - } - return bestMove; - } - - - - - @Override - public void setPosition(Position p) { - this.position = p; - } - - @Override - public Position getPosition() { - return this.position; - } - public Color getViewColor(){ - return this.viewColor; - } - - @Override - public int getAge() { - return this.age; - } - @Override - public void incrementAge() { - this.age = age + 1 ; - } - - -} diff --git a/src/main/java/model/FireFighterScenario.java b/src/main/java/model/FireFighterScenario.java deleted file mode 100644 index ff4d8d870836c8a5a5b7dfd772adf1319f22662e..0000000000000000000000000000000000000000 --- a/src/main/java/model/FireFighterScenario.java +++ /dev/null @@ -1,175 +0,0 @@ -package model; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Random; - -import util.Matrix; -import util.Position; -import util.PositionUtil; - -public class FireFighterScenario extends EntityScenario implements Board<Entity> { - - private Matrix<Entity> matrix; - private int step; - - private int initialFireCount; - private int initialFireFightersCount; - public FireFighterScenario(int columns, int rows, int initialFireCount, int initialFireFightersCount) { - this.matrix = new Matrix<Entity>(columns, rows); - this.initialFireCount = initialFireCount; - this.initialFireFightersCount = initialFireFightersCount; - initScenario(matrix); - placeInitialActors(initialFireCount, initialFireFightersCount); - this.step = 0; - } - - private void placeInitialActors(int initialFireCount, int initialFireFightersCount) { - int fireCount = 0; - int fireFighterCount = 0; - int chance = 5; // Chance initiale en pourcentage - Random random = new Random(); - - List<Position> positions = new ArrayList<>(); - for (int x = 0; x < matrix.getRows(); x++) { - for (int y = 0; y < matrix.getColumns(); y++) { - positions.add(new Position(x, y)); - } - } - - while (fireCount < initialFireCount || fireFighterCount < initialFireFightersCount) { - Collections.shuffle(positions); // Mélange les positions pour un parcours aléatoire - - for (Position pos : positions) { - if (getState(pos) instanceof EmptySquare) { - if (fireCount < initialFireCount && random.nextInt(100) < chance) { - setState(new Fire(pos, this, 1), pos); - fireCount++; - if (fireCount == initialFireCount && fireFighterCount == initialFireFightersCount) { - return; - } - continue; - } - - if (fireFighterCount < initialFireFightersCount && random.nextInt(100) < chance) { - setState(new FireFighter(pos, this, 1), pos); - fireFighterCount++; - if (fireCount == initialFireCount && fireFighterCount == initialFireFightersCount) { - return; - } - } - } - } - - // Augmente la chance de placement après chaque parcours complet - chance = Math.min(chance + 5, 100); // Ne dépasse pas 100% - } - } - - public Entity getState(Position position) { - if (position.x() > matrix.size() || position.y() > matrix.size()) { - throw new IllegalArgumentException( - "The position x:" + position.x() + " y:" + position.y() + " is out of the board."); - } - return matrix.get(position.x(), position.y()); - } - - public void setState(Entity state, Position position) { - if (!(getState(position) instanceof EmptySquare)) { - return; - } - if (doesPositionExist(position)) { - matrix.set(position.x(), position.y(), state); - } - } - - public void setState(Entity state, Position position, boolean replaceStates) { - if (!(getState(position) instanceof EmptySquare) && !replaceStates) { - return; - } - matrix.set(position.x(), position.y(), state); - } - - public int rowCount() { - return matrix.getRows(); - } - - public int columnCount() { - return matrix.getColumns(); - } - @Override - public void clearCase(Position position){ - setState(new EmptySquare(position, step+1), position, true); - } - - public List<Position> updateToNextGeneration() { - ArrayList<Position> changedPositions = new ArrayList<>(); - Iterator<Entity> iterator = matrix.iterator(); - while (iterator.hasNext()) { - Entity e = iterator.next(); - if (e instanceof EmptySquare) - continue; - if (e.getAge() == 0) { - e.incrementAge(); - continue; - } - if(e.getAge() == step+1){ - continue; - } - List<Position> entityUpdatedPositions = e.nextTurn(this); - e.incrementAge(); - changedPositions.addAll(entityUpdatedPositions); - } - return changedPositions; - } - - public Position getNearestEntity(Position fromPos, Class<?> entityType) { - int rows = matrix.getRows(); - int cols = matrix.getColumns(); - Position nearestPosition = fromPos; - - // Définir la distance maximale possible - int maxDistance = rows + cols; - - // Parcourir les distances croissantes à partir de 1 - for (int distance = 1; distance < maxDistance; distance++) { - List<Position> positionsAtDistance = PositionUtil.getPositionsAtManhattanDistance(fromPos, distance, rows, cols); - - for (Position currentPos : positionsAtDistance) { - Entity currentEntity = matrix.get(currentPos.x(), currentPos.y()); - if (entityType.isInstance(currentEntity)) { - // Dès qu'une entité est trouvée à cette distance, elle est la plus proche - // possible - return currentPos; - } - } - } - - return nearestPosition; // Retourne null si aucune entité n'est trouvée - } - - - public void reset() { - step = 0; - matrix.clear(); - initScenario(matrix); - placeInitialActors(initialFireCount, initialFireFightersCount); - } - - public int stepNumber() { - this.step = step + 1; - return this.step; - } - - @Override - public boolean doesPositionExist(Position position) { - return matrix.validateIndex(position); - } - - @Override - public int getStepNumber() { - return step; - } -} diff --git a/src/main/java/model/Model.java b/src/main/java/model/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..64bd75a7a9d95ae0a82f2fdfc0cc90e7034dec88 --- /dev/null +++ b/src/main/java/model/Model.java @@ -0,0 +1,10 @@ +package model; + +import java.util.List; + +import util.Position; + +public interface Model { + public List<Position> updateToNextGeneration(); + public Board<Square> getBoard(); +} diff --git a/src/main/java/model/Road.java b/src/main/java/model/Road.java new file mode 100644 index 0000000000000000000000000000000000000000..e208d99a8f20bfbec11d75ae327f055f06472099 --- /dev/null +++ b/src/main/java/model/Road.java @@ -0,0 +1,73 @@ +package model; + +import java.util.List; + +import javafx.scene.paint.Color; +import model.firefighterscenario.Cloud; +import util.Position; +import view.ViewElement; + +public class Road implements Entity{ + private int age; + private final int PRIORITY = 0; + private final Color VIEW_COLOR = Color.BLACK; + private Position position; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/route.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Road(Position position){ + this.position = position; + } + @Override + public List<Position> nextTurn(Board<Square> board) { + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public int getAge() { + return age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age = age + 1; + } + + @Override + public Color getViewColor() { + return this.VIEW_COLOR; + } + + @Override + public int getPriority() { + return this.PRIORITY; + } + + @Override + public ViewElement getViewElement(){ + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/Scenario.java b/src/main/java/model/Scenario.java index f4dfc48efe42a5c6945132194a404f757996beba..912f7fbb89abb070eab0b2c42cd202b5727c60f2 100644 --- a/src/main/java/model/Scenario.java +++ b/src/main/java/model/Scenario.java @@ -1,7 +1,165 @@ package model; + +import java.util.List; +import java.util.Map; + +import app.SimulatorApplication; import util.Matrix; +import util.Position; +import util.PositionUtil; + +public class Scenario implements Board<Square>{ + + private Matrix<Square> matrix; + protected int step; + protected int turnsToSpawnAirTanker; + + protected Map<EntityFactory, Integer> initialMap; + + public Scenario(int columns, int rows, Map<EntityFactory, Integer> initialMap) { + this.matrix = new Matrix<Square>(columns, rows); + initScenario(matrix); + this.turnsToSpawnAirTanker = SimulatorApplication.TURNS_FOR_SPAWNING_AIRTANKER; + this.step = 0; + this.initialMap = initialMap; + placeInitialEntities(initialMap); + } + public void initScenario(Matrix<Square> matrix){ + for(int x = 0; x < matrix.getRows(); x++){ + for(int y = 0; y < matrix.getColumns(); y++){ + Square s = new Square(new Position(x, y), new EmptySquare(new Position(x,y))); + matrix.set(x,y, s); + } + } +} + protected Matrix<Square> getMatrix(){ + return this.matrix; + } + + public void placeInitialEntities(Map<EntityFactory, Integer> initialMap) { + EntitySpawner spawner = new EntitySpawner(this); + spawner.spawnEntities(initialMap); + + } + + public Square getStates(Position position) { + if (position.x() > matrix.size() || position.y() > matrix.size()) { + throw new IllegalArgumentException( + "The position x:" + position.x() + " y:" + position.y() + " is out of the board."); + } + return matrix.get(position.x(), position.y()); + } + + public void setSquare(Square square) { + Position position = square.getPosition(); + if (!(getStates(position).isEmpty())) { + return; + } + if (doesPositionExist(position)) { + matrix.set(position.x(), position.y(), square); + } + } + + public void setSquare(Square square, boolean replaceStates) { + Position position = square.getPosition(); + if (!(getStates(position).isEmpty()) && !replaceStates) { + return; + } + matrix.set(position.x(), position.y(), square); + } + + public void addEntityAtSquare(Entity entity, Position position) { + if (doesPositionExist(position)) { + matrix.get(position.x(), position.y()).addEntity(entity); + } + } + + public int rowCount() { + return matrix.getRows(); + } + + public int columnCount() { + return matrix.getColumns(); + } + + @Override + public void clearCaseFrom(Entity entity, Position position) { + if(!matrix.get(position.x(), position.y()).getEntities().removeIf(element -> element.equals(entity))){ + for(Entity e : getStates(position).getEntities()){ + System.out.println(e); + } + } + } + + public Position getNearestEntity(Position fromPos, Class<?> entityType, List<Entity> exclusionList) { + int rows = matrix.getRows(); + int cols = matrix.getColumns(); + if(exclusionList == null){ + exclusionList = List.of(); + } + // Définir la distance maximale possible + int maxDistance = rows + cols; + // Parcourir les distances croissantes à partir de 1 + for (int distance = 1; distance < maxDistance; distance++) { + List<Position> positionsAtDistance = PositionUtil.getPositionsAtManhattanDistance(fromPos, distance, rows, cols); + + for (Position currentPos : positionsAtDistance) { + if(isPositionEmpty(currentPos))continue; + Square currentSquare = matrix.get(currentPos.x(), currentPos.y()); + for (Entity currentEntity : currentSquare.getEntities()) { + if (entityType.isInstance(currentEntity) && exclusionList != null && !exclusionList.contains(currentEntity)) { + // Vérifie si l'entité actuelle n'est pas dans la liste des exclusions + // Dès qu'une entité éligible est trouvée à cette distance, elle est la plus proche possible + return currentPos; + } + } + } + } + return null; // Retourne null si aucune entité éligible n'est trouvée +} + + + public void reset() { + step = 0; + matrix.clear(); + initScenario(matrix); + placeInitialEntities(initialMap); + } + + public int stepNumber() { + return this.step; + } + + @Override + public boolean doesPositionExist(Position position) { + return matrix.validateIndex(position); + } + + @Override + public int getStepNumber() { + return step; + } + + @Override + public boolean doesSquareContainEntity(Position squarePos, Class<?> entityType) { + return getStates(squarePos).getEntities().stream().anyMatch(entityType::isInstance); + } + + @Override + public boolean isPositionEmpty(Position position) { + return getStates(position).isEmpty(); + } + + @Override + public boolean isPositionFree(Position position, int priority) { + List<Entity> entities = matrix.get(position.x(), position.y()).getEntities(); + for (Entity e : entities) { + if (e.getPriority() == priority) { + return false; + } + } + return true; + } -public interface Scenario { - public void initScenario(Matrix<Entity> matrix); } diff --git a/src/main/java/model/Square.java b/src/main/java/model/Square.java new file mode 100644 index 0000000000000000000000000000000000000000..50c9a839cefa34a4bea0da061346f9e44e8bfeeb --- /dev/null +++ b/src/main/java/model/Square.java @@ -0,0 +1,93 @@ +package model; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.paint.Color; +import util.Position; + +public class Square { + private List<Entity> entities; + private Position position; + public Square(Position position){ + this.entities = new ArrayList<Entity>(); + this.position = position; + } + public Square(Position position, Entity entity){ + this.entities = new ArrayList<Entity>(); + this.entities.add(entity); + this.position = position; + } + + public List<Entity> getEntities(){ + return this.entities; + } + + public Position getPosition(){ + return this.position; + } + + public void addEntity(Entity entity){ + entities.add(entity); + } + + public void removeEntity(Entity entity){ + entities.remove(entity); + } + + public void setEntities(List<Entity> entities){ + this.entities = entities; + } + + public boolean isEmpty(){ + return entities.isEmpty() ||(entities.size() == 1 && entities.get(0) instanceof EmptySquare); + } + + public int getMinimalAge(){ + int minimalAge = 0; + for(Entity e : entities){ + if(e.getAge() < minimalAge){ + minimalAge = e.getAge(); + } + } + return minimalAge; + } + + public int getMaxAge(){ + int maxAge = 0; + for(Entity e : entities){ + if(e.getAge() > maxAge){ + maxAge = e.getAge(); + } + } + return maxAge; + } + + public void incrementAllAges(){ + for(Entity e : entities){ + e.incrementAge(); + } + } + + public Color getViewColor() { + if (entities.isEmpty()) { + return Color.WHITE; + } else { + int maxPriority = -1; // Assuming priorities are non-negative + Color c = Color.WHITE; + + // Iterate over entities to find the one with the highest priority + for (Entity e : entities) { + // Check for null entities if necessary + if (e != null && e.getPriority() > maxPriority) { + maxPriority = e.getPriority(); + c = e.getViewColor(); + } + } + return c; + } + } + + + +} diff --git a/src/main/java/model/doctorviruspatient/Doctor.java b/src/main/java/model/doctorviruspatient/Doctor.java new file mode 100644 index 0000000000000000000000000000000000000000..cb2d809abc762d4db9990b7d15f2c90d15e1b390 --- /dev/null +++ b/src/main/java/model/doctorviruspatient/Doctor.java @@ -0,0 +1,172 @@ +package model.doctorviruspatient; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Doctor implements Entity { + private final int priority = 1; + private Position position; + private int age; + private final Color viewColor = Color.RED; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/virus/docteur.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Statique car les médecins partagent les connaissances et les recherches + private static List<Integer> knownVirusVariant = new ArrayList<Integer>(); + private static Map<Integer, Double> currentResearch = new HashMap<>(); // Map<variantId, % de recherche> + + public Doctor(Position p) { + this.position = p; + } + + public Doctor(Position p, int age) { + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + // Mettre à jour les recherches en cours + updateCurrentResearch(); + + // Interagir avec les positions adjacentes + interactWithAdjacentPositions(PositionUtil.generateAllAdjacentPositions(position, board), board); + + return List.of(); + } + + /** + * Met à jour toutes les recherches en cours en augmentant leur avancement de x%. + * Si une recherche atteint ou dépasse 100%, elle est terminée. + */ + private void updateCurrentResearch() { + List<Integer> completedResearch = new ArrayList<>(); + + for (Map.Entry<Integer, Double> entry : currentResearch.entrySet()) { + int variantId = entry.getKey(); + double progress = entry.getValue() + 2.0; + if (progress >= 100.0) { + progress = 100.0; + completedResearch.add(variantId); + } + currentResearch.put(variantId, progress); + } + + // Traiter les recherches complétées + for (int variantId : completedResearch) { + currentResearch.remove(variantId); + knownVirusVariant.add(variantId); + System.out.println("Recherche terminée pour le variant ID: " + variantId); + } + } + + private List<Position> interactWithAdjacentPositions(List<Position> adjacentPositions, Board<Square> board) { + List<Position> result = new ArrayList<>(); + for (Position p : adjacentPositions) { + if (board.doesSquareContainEntity(p, Patient.class)) { + handleEncounterWithPatient(p, board); + } + } + return result; + } + + private void handleEncounterWithPatient(Position p, Board<Square> board) { + if (board.doesSquareContainEntity(p, Patient.class)) { + Patient patient = (Patient) board.getStates(p).getEntities().stream() + .filter(e -> e instanceof Patient) + .findFirst() + .orElse(null); + if (patient != null && patient.isInfected()) { + Virus virus = patient.getVirus(); + if (virus != null) { + int variantId = virus.getVariantId(); + if (knownVirusVariant.contains(variantId)) { + patient.cure(); + } else { + // Si la variante n'est pas connue et pas en cours de recherche, l'ajouter à la recherche + if (!currentResearch.containsKey(variantId)) { + currentResearch.put(variantId, 0.0); + System.out.println("Nouvelle variante détectée! Variant ID: " + variantId + ". Recherche commencée."); + } + } + } + } + } + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Doctor) { + Doctor other = (Doctor) obj; + return this.position.equals(other.getPosition()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(position); + } + + @Override + public ViewElement getViewElement() { + return new ViewElement (cloudImage); + } + + +} diff --git a/src/main/java/model/doctorviruspatient/DoctorVirusPatientScenario.java b/src/main/java/model/doctorviruspatient/DoctorVirusPatientScenario.java new file mode 100644 index 0000000000000000000000000000000000000000..16e82608ee3584dc79aee63cc21369615de748dd --- /dev/null +++ b/src/main/java/model/doctorviruspatient/DoctorVirusPatientScenario.java @@ -0,0 +1,60 @@ +package model.doctorviruspatient; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import model.Board; +import model.Entity; +import model.EntityFactory; +import model.Model; +import model.Scenario; +import model.Square; +import util.Position; + + + +public class DoctorVirusPatientScenario extends Scenario implements Model{ + public DoctorVirusPatientScenario(int columns, int rows, Map<EntityFactory, Integer> initialMap) { + super(columns, rows, initialMap); + } + + public List<Position> updateToNextGeneration() { + ArrayList<Position> changedPositions = new ArrayList<>(); + Iterator<Square> iterator = getMatrix().iterator(); + while (iterator.hasNext()) { + Square s = iterator.next(); + if (s.isEmpty()) + continue; + if (s.getMaxAge() == 0) { + s.incrementAllAges(); + continue; + } + if(s.getMaxAge()>stepNumber()+1){ + continue; + } + List<Entity> entities = new ArrayList<>(s.getEntities()); + for (Entity e : entities) { + e.incrementAge(); + changedPositions.addAll(e.nextTurn(this)); + } + } + + // Increment the step counter + this.step = this.step + 1; + return changedPositions; + } + + + + @Override + public Board<Square> getBoard() { + return this; + } + + + + + +} diff --git a/src/main/java/model/doctorviruspatient/Patient.java b/src/main/java/model/doctorviruspatient/Patient.java new file mode 100644 index 0000000000000000000000000000000000000000..8e1387910f29240fffba163eec35593673304747 --- /dev/null +++ b/src/main/java/model/doctorviruspatient/Patient.java @@ -0,0 +1,358 @@ +package model.doctorviruspatient; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Random; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.PathGenerator; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Patient implements Entity { + private final int priority = 1; + private Position position; + private int age; + private final Color viewColor = Color.BLUE; + private Virus carriedVirus; + private List<Entity> visitedPatients; + private static int patientsCount = 0; + private int patientID; + private Position ennemy; + private boolean isAsymptomatic; + private boolean wandering; + + + + private static javafx.scene.image.Image healthyImage; + private static javafx.scene.image.Image infectedImage; + + static { + try { + healthyImage = new javafx.scene.image.Image(Patient.class.getResource("/view/icons/virus/humain.png").toExternalForm()); + infectedImage = new javafx.scene.image.Image(Patient.class.getResource("/view/icons/virus/patient.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + + // Attributs pour le chemin + private List<Position> path; + private int pathIndex; + private static final int MAX_STEPS_IN_DIRECTION = 5; + private static final int MINIMUM_ROAD_LENGTH = 10; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/virus/humain.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Patient(Position p) { + this.wandering = true; + patientID = patientsCount; + patientsCount += 1; + this.visitedPatients = new ArrayList<>(); + this.position = p; + Random r = new Random(); + int randomNumber = r.nextInt(10); + this.isAsymptomatic = randomNumber == 1; + // Initialiser le chemin + this.path = new ArrayList<>(); + this.pathIndex = 0; + } + + public Patient(Position p, int age) { + this.wandering = true; + patientID = patientsCount; + patientsCount += 1; + this.visitedPatients = new ArrayList<>(); + this.position = p; + this.age = age; + Random r = new Random(); + int randomNumber = r.nextInt(10); + this.isAsymptomatic = randomNumber == 1; + // Initialiser le chemin + this.path = new ArrayList<>(); + this.pathIndex = 0; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + if (isInfected() && !isAsymptomatic) { + return moveToDoctor(board); + } + updateWanderingStatus(board); + + if (wandering) { + return performWandering(board); + } + + resetVisitedPatientsIfNecessary(); + + Position target = determineTarget(board); + + if (target == null) { + visitedPatients.clear(); + return List.of(); + } + + Position nextPos = determineNextPosition(target, board); + + if (nextPos != null && board.doesPositionExist(nextPos)) { + + List<Position> adjacentPositions = PositionUtil.generateAllAdjacentPositions(nextPos, board); + List<Position> result = interactWithAdjacentPositions(adjacentPositions, board); + result.addAll(moveToPosition(nextPos, board)); + result.addAll(adjacentPositions); + result.add(position); + return result; + } + + return List.of(); + } + + private void updateWanderingStatus(Board<Square> board) { + if (board.getStepNumber() % 8 == 0) { + wandering = false; + // Réinitialiser le chemin lorsque le patient arrête d'errer + this.path.clear(); + this.pathIndex = 0; + } + } + + private List<Position> moveToDoctor(Board<Square> board) { + for (Position p : PositionUtil.generateAllAdjacentPositions(position, board)) { + if (!board.doesPositionExist(p)) continue; + if (board.doesSquareContainEntity(p, Doctor.class)) return List.of(); + } + Position nearestDoctor = board.getNearestEntity(position, Doctor.class, null); + Position nextPos = PositionUtil.getNextPositionTowards(position, nearestDoctor, board); + if (nextPos == null) return List.of(); + + List<Position> changedPositions = new ArrayList<>(); + changedPositions.addAll(moveToPosition(nextPos, board)); + return changedPositions; + } + + private List<Position> performWandering(Board<Square> board) { + if (path == null || path.isEmpty() || pathIndex >= path.size()) { + generateNewPath(board); + if (path.isEmpty()) { + return List.of(); + } + } + + Position nextPosition = path.get(pathIndex); + + if (board.doesPositionExist(nextPosition) && board.isPositionFree(nextPosition, priority)) { + List<Position> changedPositions = moveSelfOnBoard(nextPosition, board); + pathIndex++; + return changedPositions; + } else { + // Si la position n'est pas libre ou n'existe pas, générer un nouveau chemin + generateNewPath(board); + return List.of(); + } + } + + private void generateNewPath(Board<Square> board) { + PathGenerator pathGenerator = new PathGenerator(board, position, MAX_STEPS_IN_DIRECTION, MINIMUM_ROAD_LENGTH); + this.path = pathGenerator.generate(); + // Supprimer la position actuelle du chemin si elle est présente + if (!path.isEmpty() && path.get(0).equals(position)) { + path.remove(0); + } + this.pathIndex = 0; + } + + private void resetVisitedPatientsIfNecessary() { + if (visitedPatients.size() - 1 >= patientsCount) { + visitedPatients.clear(); + } + } + + private Position determineTarget(Board<Square> board) { + return board.getNearestEntity(position, Patient.class, getVisitedPatients()); + } + + private Position determineNextPosition(Position target, Board<Square> board) { + int enemyDistance = PositionUtil.getManhattanDistance(target, position); + + if (ennemy != null && enemyDistance < 2) { + return PositionUtil.getNextPositionAwayFrom(position, target, board); + } else if (target != null) { + return PositionUtil.getNextPositionTowards(position, target, board); + } + return null; + } + + private List<Position> moveToPosition(Position nextPos, Board<Square> board) { + List<Position> changedPosition = new ArrayList<>(); + if (!board.isPositionFree(nextPos, priority)) { + nextPos = findAlternativePosition(nextPos, board); + } + + if (nextPos != null) { + changedPosition.addAll(moveSelfOnBoard(nextPos, board)); + } + + return changedPosition; + } + + private Position findAlternativePosition(Position currentPos, Board<Square> board) { + List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(position, board); + for (Position p : adjacentPositions) { + if (ennemy != null) { + Position awayPos = PositionUtil.getNextPositionAwayFrom(p, ennemy, board); + if (p.equals(awayPos)) { + continue; + } + } + if (board.isPositionFree(p, priority)) { + return p; + } + } + return null; + } + + private List<Position> moveSelfOnBoard(Position newPosition, Board<Square> board) { + if(!board.isPositionFree(newPosition, priority))return List.of(); + Position oldPosition = new Position(this.position.x(), this.position.y()); + board.clearCaseFrom(this, oldPosition); + board.addEntityAtSquare(this, newPosition); + this.position = newPosition; + return List.of(oldPosition, this.position); + } + + private List<Position> interactWithAdjacentPositions(List<Position> adjacentPositions, Board<Square> board) { + List<Position> result = new ArrayList<>(); + for (Position p : adjacentPositions) { + if (board.doesSquareContainEntity(p, Patient.class)) { + handleEncounterWithPatient(p, board); + if (isInfected()) { + result.addAll(adjacentPositions); + } + } + } + return result; + } + + private void handleEncounterWithPatient(Position p, Board<Square> board) { + this.wandering = true; + // Réinitialiser le chemin lorsqu'un patient est rencontré + this.path.clear(); + this.pathIndex = 0; + Patient patient = (Patient) board.getStates(p).getEntities().stream() + .filter(entity -> entity instanceof Patient) + .findFirst() + .orElse(null); + if (isInfected()) { + patient.infect(carriedVirus); + } + if (patient != null) { + this.ennemy = patient.getPosition(); + visitedPatients.add(patient); + } + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return isInfected() ? Color.CYAN : this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + public boolean isInfected() { + return this.carriedVirus != null; + } + + public void infect(Virus virus) { + if (this.carriedVirus != null) return; + this.carriedVirus = virus; + } + + public Virus getVirus() { + return this.carriedVirus; + } + + public List<Entity> getVisitedPatients() { + return this.visitedPatients; + } + + public boolean hasVisited(Patient patient) { + return this.getVisitedPatients().contains(patient); + } + + public int getPatientId() { + return this.patientID; + } + + public void cure() { + if (this.carriedVirus.tryToKill()) { + this.carriedVirus = null; + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Patient) { + Patient other = (Patient) obj; + return this.patientID == other.getPatientId(); + } else { + return false; + } + + } + + @Override + public int hashCode() { + return Objects.hash(patientID); + } + + @Override + public ViewElement getViewElement() { + return isInfected() ? new ViewElement(infectedImage) : new ViewElement(healthyImage); + } + + +} diff --git a/src/main/java/model/doctorviruspatient/Virus.java b/src/main/java/model/doctorviruspatient/Virus.java new file mode 100644 index 0000000000000000000000000000000000000000..632b4258afba963d57cd1fc1d06262ae36f96e0e --- /dev/null +++ b/src/main/java/model/doctorviruspatient/Virus.java @@ -0,0 +1,167 @@ +package model.doctorviruspatient; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.PathGenerator; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Virus implements Entity { + private final int priority = 2; + private Position position; + private int age; + private final Color viewColor = Color.LIMEGREEN; + private List<Position> path; + private int pathIndex; + private static final int MAX_STEPS_IN_DIRECTION = 5; + private static final int MINIMUM_ROAD_LENGTH = 10; + private int health; + private int variantId; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/virus/virus.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Virus(Position p) { + this.variantId = 0; + this.health = 50; + this.position = p; + this.path = new ArrayList<>(); + this.age = 0; + this.pathIndex = 0; + } + + public Virus(Position p, int age) { + this.health = 8; + this.position = p; + this.age = age; + this.path = new ArrayList<>(); + this.pathIndex = 0; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + if(board.getStepNumber() % 100 == 0){ + evolve(); + } + // Génère un nouveau chemin si nécessaire + if (path == null || path.isEmpty() || pathIndex >= path.size()) { + generateNewPath(board); + if (path.isEmpty()) { + return List.of(); + } + } + List<Position> adjacentPosition = PositionUtil.generateAllAdjacentPositions(position, board); + for(Position p : adjacentPosition){ + if(board.doesSquareContainEntity(p, Patient.class)){ + Patient patient = (Patient) board.getStates(p).getEntities().stream().filter(e -> e instanceof Patient).findFirst().get(); + patient.infect(this); + } + } + // Se déplace vers la prochaine position du chemin + Position nextPosition = path.get(pathIndex); + if (board.doesPositionExist(nextPosition)) { + // Vérifier si la position est libre + if (board.isPositionFree(nextPosition, priority)) { + Position oldPosition = position; + position = nextPosition; + board.clearCaseFrom(this, oldPosition); + board.addEntityAtSquare(this, position); + pathIndex = pathIndex + 1; + return List.of(oldPosition, position); + } else { + // Si la position n'est pas libre, génère un nouveau chemin + generateNewPath(board); + return List.of(); + } + } else { + // Si la prochaine position n'existe pas, génère un nouveau chemin + generateNewPath(board); + return List.of(); + } + } + + private void generateNewPath(Board<Square> board) { + PathGenerator pathGenerator = new PathGenerator(board, position, MAX_STEPS_IN_DIRECTION, MINIMUM_ROAD_LENGTH); + this.path = pathGenerator.generate(); + // Supprime la position actuelle du chemin si elle est présente au début + if (!path.isEmpty() && path.get(0).equals(position)) { + path.remove(0); + } + this.pathIndex = 0; // Réinitialise l'index du chemin + } + + // Les autres méthodes restent inchangées + private void evolve(){ + this.variantId++; + Random r = new Random(); + this.health = r.nextInt(100); + } + + public int getVariantId(){ + return this.variantId; + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + public boolean tryToKill(){ + if(health <= 0){ + return true; + } + health--; + return false; + } + + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/firefighterscenario/AirTanker.java b/src/main/java/model/firefighterscenario/AirTanker.java new file mode 100644 index 0000000000000000000000000000000000000000..130850ac5509eca671da995d13c77c4d07d78cfc --- /dev/null +++ b/src/main/java/model/firefighterscenario/AirTanker.java @@ -0,0 +1,180 @@ +package model.firefighterscenario; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import util.Direction; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class AirTanker implements Entity{ + private final Color viewColor = Color.GOLD; + private Direction direction; // Direction in which the AirTanker moves + private int age; + private Position position; + private int priority = 3; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/avion.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public AirTanker(Position position, Board<Square> b, int age) { + this.age = age; + this.position = position; + determineDirection(b); + extinguish(position,b); + } + + @Override + public int getPriority() { + return this.priority; + } + + private void determineDirection(Board<Square> b) { + int rowCount = b.rowCount(); + int columnCount = b.columnCount(); + + if (position.x() == 0) { + direction = Direction.EAST; // Move east if on left edge + } else if (position.x() == columnCount - 1) { + direction = Direction.WEST; // Move west if on right edge + } else if (position.y() == 0) { + direction = Direction.SOUTH; // Move south if on top edge + } else if (position.y() == rowCount - 1) { + direction = Direction.NORTH; // Move north if on bottom edge + } else { + // Not on an edge; default to moving east or choose a random direction + direction = Direction.EAST; + } + } + + @Override + public List<Position> nextTurn(Board<Square> b) { + List<Position> positions = new ArrayList<>(); + // Move in the determined direction + Position nextPos = getNextPosition(position, direction, b); + + if (nextPos == null || !b.doesPositionExist(nextPos)) { + // Reached the edge; remove AirTanker from the board + b.clearCaseFrom(this, position); + positions.add(position); + return positions; + } else { + // Extinguish fires within a 3-square radius + + positions.addAll(extinguish(position, b)); + // Move to the next position + b.clearCaseFrom(this, position); + positions.add(new Position(position.x(), position.y())); // Old position + this.position = nextPos; + b.addEntityAtSquare(this, nextPos); + positions.add(nextPos); // New position + + return positions; + } + } + protected List<Position> extinguish(Position position, Board<Square> b){ + List<Position> positions = new ArrayList<Position>(); + List<Position> positionsInRange = PositionUtil.getPositionsInRadius(position, 3, b); + for (Position p : positionsInRange) { + if (b.doesSquareContainEntity(p, Fire.class)) { + b.getStates(p).getEntities().removeIf(e -> e instanceof Fire); + positions.add(p); // Add position where fire was extinguished + } + } + return positions; + } + private Position getNextPosition(Position currentPosition, Direction direction, Board<Square> b) { + int x = currentPosition.x(); + int y = currentPosition.y(); + + switch (direction) { + case NORTH: + y -= 1; + break; + case SOUTH: + y += 1; + break; + case EAST: + x += 1; + break; + case WEST: + x -= 1; + break; + default: + break; // Only handling cardinal directions + } + Position newPos = new Position(x, y); + if (b.doesPositionExist(newPos)) { + return newPos; + } else { + return null; // Reached the edge + } + } + + public Direction getDirection(){ + return this.direction; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void incrementAge() { + this.age++; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; // Check if same object + if (obj == null || getClass() != obj.getClass()) return false; // Check for null and class match + AirTanker airTanker = (AirTanker) obj; // Cast to Fire + return age == airTanker.age && position.equals(airTanker.position); // Compare age and position + } + + + @Override + public int hashCode() { + return Objects.hash(position, age); + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } +} diff --git a/src/main/java/model/firefighterscenario/Cloud.java b/src/main/java/model/firefighterscenario/Cloud.java new file mode 100644 index 0000000000000000000000000000000000000000..99caef1fe965b3093cf7f4b04bb520d18193ab85 --- /dev/null +++ b/src/main/java/model/firefighterscenario/Cloud.java @@ -0,0 +1,126 @@ +package model.firefighterscenario; + +import java.util.List; +import java.util.Random; + +import javafx.scene.paint.Color; +import model.Board; +import model.EmptySquare; +import model.Entity; +import model.Square; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Cloud implements Entity{ + private int age; + private Position position; + private final Color viewColor = Color.GRAY; + private final int priority = 2; + + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/nuage.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + public Cloud(Position position, Board<Square> b){ + this.age = 0; + this.position = position; + + } + + public Cloud(Position position, Board<Square> b, int age){ + this.age = age; + this.position = position; + + } + + @Override + public List<Position> nextTurn(Board<Square> b) { + List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(position, b); + adjacentPositions.removeIf( p -> b.doesSquareContainEntity(p, Cloud.class)); + adjacentPositions.removeIf( p -> b.doesSquareContainEntity(p, Mountain.class)); + + // Filtrer pour obtenir uniquement les positions qui ne contiennent pas de pompier + adjacentPositions.removeIf(p -> b.doesSquareContainEntity(p, FireFighter.class)); + + // Choisir une position aléatoire parmi les mouvements possibles + if(adjacentPositions.size() < 0){ + return List.of(); + } + Position next_position = adjacentPositions.get(new Random().nextInt(adjacentPositions.size())); + + // Si la nouvelle position contient un feu, éteindre le feu + if (b.getStates(next_position).getEntities().stream().anyMatch(element -> element instanceof Fire) ){ + extinguish(next_position, b); + } + + Position old_position = this.position; + this.position = next_position; + b.addEntityAtSquare(this, next_position); + b.clearCaseFrom(this, old_position); + + return List.of(old_position, this.position); + } + + + private Position extinguish(Position p, Board<Square> b) { + b.getStates(p).getEntities().removeIf(element -> element instanceof Fire); + List<Entity> entities = b.getStates(p).getEntities(); + for (Entity e : entities) { + if (e instanceof EmptySquare) { + e.setAge(b.stepNumber() + 1); + } + } + + return p; + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public int getAge() { + return age; + } + + @Override + public void setAge(int age) { + this.age =age; + } + + @Override + public void incrementAge() { + this.age += 1; + + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority(){ + return this.priority; + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } + + + +} diff --git a/src/main/java/model/firefighterscenario/Fire.java b/src/main/java/model/firefighterscenario/Fire.java new file mode 100644 index 0000000000000000000000000000000000000000..05f4b9874df8e830646f680d08b8c4696ff96b61 --- /dev/null +++ b/src/main/java/model/firefighterscenario/Fire.java @@ -0,0 +1,151 @@ +package model.firefighterscenario; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Road; +import model.Square; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + + +public class Fire implements Entity { + private Position position; + private final Color viewColor = Color.RED; + private int age; + private final int priority = 1; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/flamme.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Fire(Position position) { + this.position = position; + this.age = 0; + } + + public Fire(Position position, int age) { + this.position = position; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + List<Position> newFirePositions = new ArrayList<>(); + if(board.getStepNumber() <= 1){ + return newFirePositions; + } + // Fire propagates every 2 turns + if (age % 2 != 0) { + return newFirePositions; // No propagation this turn + } + + // Generate adjacent positions + List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(position, board); + + for (Position p : adjacentPositions) { + // Skip if position does not exist + if (!board.doesPositionExist(p)) { + continue; + } + + // Skip if the position contains a Mountain + if (board.doesSquareContainEntity(p, Mountain.class)) { + continue; + } + + // Skip if the position already contains a Fire + if (board.doesSquareContainEntity(p, Fire.class)) { + continue; + } + + // Skip if the position contains a Cloud (if clouds prevent fire spread) + if (board.doesSquareContainEntity(p, Road.class)) { + continue; + } + if (board.doesSquareContainEntity(p, Rockery.class)) { + Optional<Entity> e = board.getStates(p).getEntities().stream().findFirst(); + if(e != null && e.isPresent() && e.get() instanceof Rockery){ + Rockery rockery = (Rockery) e.get(); + if(rockery.getBurn() < 4){ + rockery.incrementBurn(); + continue; + } + + } + } + + // Add new Fire to the board + board.addEntityAtSquare(new Fire(p), p); + newFirePositions.add(p); // Keep track of new fires for updates + } + + return newFirePositions; + } + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void incrementAge() { + this.age++; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; // Check if same object + if (obj == null || getClass() != obj.getClass()) return false; // Check for null and class match + Fire fire = (Fire) obj; // Cast to Fire + return age == fire.age && position.equals(fire.position); // Compare age and position + } + + + @Override + public int hashCode() { + return Objects.hash(position, age); + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/firefighterscenario/FireFighter.java b/src/main/java/model/firefighterscenario/FireFighter.java new file mode 100644 index 0000000000000000000000000000000000000000..7403803f0ea2731ded1113656b0c62b6ffd5cd00 --- /dev/null +++ b/src/main/java/model/firefighterscenario/FireFighter.java @@ -0,0 +1,242 @@ +package model.firefighterscenario; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import javafx.scene.paint.Color; +import model.Board; +import model.EmptySquare; +import model.Entity; +import model.Square; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + + + +public class FireFighter implements Entity { + private int age; + private Position position; + private final Color viewColor = Color.BLUE; + private final int priority = 1; + protected List<Position> lastThreePosition; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/sapeur-pompier.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public FireFighter(Position position, Board<Square> b) { + this.position = position; + this.age = 0; + this.lastThreePosition = new ArrayList<Position>(); + } + + public FireFighter(Position position, Board<Square> b, int age) { + this.position = position; + this.age = age; + this.lastThreePosition = new ArrayList<Position>(); + } + + public List<Position> nextTurn(Board<Square> b) { + List<Position> positions = new ArrayList<>(); + + // Generate adjacent positions, excluding mountains + List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(position, b); + adjacentPositions.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + + // Check if there is fire in any adjacent positions + boolean hasFire = adjacentPositions.stream() + .anyMatch(p -> b.doesSquareContainEntity(p, Fire.class)); + + boolean isStuck = isStuck(); + + if (hasFire) { + // Extinguish fires in adjacent positions + positions.addAll(extinguish(adjacentPositions, b)); + if (isStuck) { + // If stuck, attempt to move and then extinguish fires + positions.addAll(moveAndExtinguish(b)); + } + } else { + // No fire adjacent; move and attempt to extinguish fires + positions.addAll(moveAndExtinguish(b)); + } + + // Update last three positions + updateLastPositions(); + + return positions; + } + + protected boolean isStuck() { + if (lastThreePosition.size() < 3) { + return false; + } + Position first = lastThreePosition.get(lastThreePosition.size() - 1); + return lastThreePosition.stream().allMatch(pos -> pos.equals(first)); + } + + private void updateLastPositions() { + if (lastThreePosition.size() >= 3) { + lastThreePosition.remove(0); + } + lastThreePosition.add(this.position); + } + + + protected List<Position> extinguish(List<Position> adjacentPositions, Board<Square> b) { + List<Position> extinguishedPositions = new ArrayList<>(); + for (Position p : adjacentPositions) { + if (b.doesSquareContainEntity(p, Fire.class)) { + if(b.doesSquareContainEntity(p, Rockery.class)){ + Rockery rockery = (Rockery) b.getStates(p).getEntities().stream() + .filter(Rockery.class::isInstance) + .findFirst().get(); + rockery.resetBurn(); + } + b.getStates(p).getEntities().removeIf(element -> element instanceof Fire); + // Update age for EmptyEntity if needed + b.getStates(p).getEntities().forEach(e -> { + if (e instanceof EmptySquare) { + e.setAge(b.stepNumber() + 1); + } + }); + extinguishedPositions.add(p); // Add position where fire was extinguished + } + } + return extinguishedPositions; + } + + + protected List<Position> moveAndExtinguish(Board<Square> b) { + List<Position> positions = new ArrayList<>(); + + // Find the nearest fire + Position nearestFirePos; + try { + nearestFirePos = b.getNearestEntity(position, Fire.class, null); + } catch (Exception e) { + return List.of(); + } + if (nearestFirePos != null) { + // Get the next position towards the fire + Position nextPos = getNextPositionTowards(position, nearestFirePos, b); + if (nextPos != null && !nextPos.equals(position)) { + // Move the firefighter + b.clearCaseFrom(this, position); // Clear old position + positions.add(new Position(position.x(), position.y())); // Add old position + this.position = nextPos; + b.addEntityAtSquare(this, nextPos); // Add to new position + positions.add(nextPos); // Add new position + + // After moving, attempt to extinguish fires adjacent to new position + List<Position> newAdjacentPositions = PositionUtil.generateAdjacentPositions(nextPos, b); + newAdjacentPositions.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + positions.addAll(extinguish(newAdjacentPositions, b)); + } + } + return positions; + } + + protected Position getNextPositionTowards(Position currentPos, Position targetPos, Board<Square> b) { + // Generate adjacent positions + List<Position> possibleMoves = PositionUtil.generateAllAdjacentPositions(currentPos, b); + + // Filter out positions that are not empty or contain obstacles + possibleMoves.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + + // If no possible moves, return null + if (possibleMoves.isEmpty()) { + return null; + } + + // Calculate the current distance to the target + int currentDistance = PositionUtil.getManhattanDistance(currentPos, targetPos); + + // Initialize variables to find the best moves + int minDistance = Integer.MAX_VALUE; + List<Position> bestMoves = new ArrayList<>(); + + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + + // Skip positions occupied by other firefighters + if (b.doesSquareContainEntity(move, FireFighter.class)) { + continue; + } + + // Find positions that minimize the distance + if (distance < minDistance) { + minDistance = distance; + bestMoves.clear(); + bestMoves.add(move); + } else if (distance == minDistance) { + bestMoves.add(move); + } + } + + // If no better move is found, consider moves that maintain the same distance + if (bestMoves.isEmpty()) { + minDistance = currentDistance; + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + if (distance == minDistance) { + bestMoves.add(move); + } + } + } + + // If still no move is found, stay in the current position + if (bestMoves.isEmpty()) { + return currentPos; + } + + // Select a move from the best moves (e.g., randomly or based on additional criteria) + Random r = new Random(); + + Position nextMove = bestMoves.get(r.nextInt(bestMoves.size())); + + return nextMove; + } + + + @Override + public void setPosition(Position p) { + this.position = p; + } + + @Override + public Position getPosition() { + return this.position; + } + + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void incrementAge() { + this.age = age + 1; + } + + @Override + public void setAge(int age) { + this.age = age; + } + public int getPriority(){ return this.priority;} + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } +} diff --git a/src/main/java/model/firefighterscenario/FireFighterScenario.java b/src/main/java/model/firefighterscenario/FireFighterScenario.java new file mode 100644 index 0000000000000000000000000000000000000000..05167eaa47d815d2d26e8384bd36cdb59495160d --- /dev/null +++ b/src/main/java/model/firefighterscenario/FireFighterScenario.java @@ -0,0 +1,151 @@ +package model.firefighterscenario; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import model.Board; +import model.Entity; +import model.EntityFactory; +import model.Model; +import model.Road; +import model.Scenario; +import model.Square; +import util.PathGenerator; +import util.Position; + +public class FireFighterScenario extends Scenario implements Model{ + public FireFighterScenario(int columns, int rows, Map<EntityFactory, Integer> initialMap) { + super(columns, rows, initialMap); + generateRoads(); + } + + public List<Position> updateToNextGeneration() { + ArrayList<Position> changedPositions = new ArrayList<>(); + Iterator<Square> iterator = getMatrix().iterator(); + List<Entity> updatedEntities = new ArrayList<Entity>(); + while (iterator.hasNext()) { + Square s = iterator.next(); + if (s.isEmpty()) + continue; + if (s.getMaxAge() == 0) { + s.incrementAllAges(); + continue; + } + if (s.getMaxAge() == step + 1) { + continue; + } + List<Entity> entities = new ArrayList<>(s.getEntities()); + for (Entity e : entities) { + if(updatedEntities.contains(e))continue; + if (e.getAge() >= stepNumber() - 1) { + continue; + } + e.incrementAge(); + updatedEntities.add(e); + changedPositions.addAll(e.nextTurn(this)); + } + } + + // Increment the step counter + this.step = this.step + 1; + + // Check if it's time to spawn an AirTanker + if (this.step % this.turnsToSpawnAirTanker == 0) { + // Spawn an AirTanker at a random edge position + spawnAirTanker(changedPositions); + } + + return changedPositions; + } + + // Helper method to spawn an AirTanker + private void spawnAirTanker(List<Position> changedPositions) { + Random rand = new Random(); + int edge = rand.nextInt(4); // 0: top, 1: bottom, 2: left, 3: right + Position position = null; + + if (edge == 0) { // Top edge (x == 0) + int y = rand.nextInt(columnCount() - 1); + position = new Position(0, y); + } else if (edge == 1) { // Bottom edge (x == rowCount() - 1) + int y = rand.nextInt(columnCount()); + position = new Position(rowCount() - 1, y); + } else if (edge == 2) { // Left edge (y == 0) + int x = rand.nextInt(rowCount() - 1); + position = new Position(x, 0); + } else if (edge == 3) { // Right edge (y == columnCount() - 1) + int x = rand.nextInt(rowCount() - 1); + position = new Position(x, columnCount() - 1); + } else { + // This else block is technically not necessary because edge will always be + // between 0 and 3 + throw new IllegalStateException("Unexpected edge value: " + edge); + } + + // Create a new AirTanker + AirTanker airTanker = new AirTanker(position, this, getStepNumber()); + // Add the AirTanker to the board + addEntityAtSquare(airTanker, position); + + // Record the changed position + changedPositions.add(position); + } + + private void generateRoads() { + if(columnCount() < 10 || rowCount() < 10){ + return; + } + Random random = new Random(); + + // Get board dimensions + int rowCount = rowCount(); // Number of rows (vertical axis) + int columnCount = columnCount(); // Number of columns (horizontal axis) + + // Decide randomly whether to set x or y to 0 + boolean setXToZero = random.nextBoolean(); + + int x = 0; + int y = 0; + + if (setXToZero) { + // x is set to 0, y is random within column bounds + x = 0; + y = random.nextInt(columnCount); + } else { + // y is set to 0, x is random within row bounds + x = random.nextInt(rowCount); + y = 0; + } + + Position startPosition = new Position(x, y); + PathGenerator pathGenerator = new PathGenerator(this, startPosition, 4, columnCount()); + // Call generateEntitiesInLine to place the roads + List<Position> snake = pathGenerator.generate(); + for(Position p : snake){ + List<Entity> entitiesAtSquare = List.copyOf(getStates(p).getEntities()); + for(Entity e: entitiesAtSquare){ + if(e instanceof Mountain || e instanceof Rockery || e instanceof Fire){ + clearCaseFrom(e, e.getPosition()); + } + } + addEntityAtSquare(new Road(p), p); + } +} + + @Override + public Board<Square> getBoard() { + return this; + } + + public void reset() { + step = 0; + super.getMatrix().clear(); + initScenario(super.getMatrix()); + placeInitialEntities(initialMap); + generateRoads(); + } + +} diff --git a/src/main/java/model/firefighterscenario/MotorizedFireFighter.java b/src/main/java/model/firefighterscenario/MotorizedFireFighter.java new file mode 100644 index 0000000000000000000000000000000000000000..904a3387a1b86b46d03ddea6b7d48eb6c5fc5da5 --- /dev/null +++ b/src/main/java/model/firefighterscenario/MotorizedFireFighter.java @@ -0,0 +1,132 @@ +package model.firefighterscenario; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.paint.Color; +import model.Board; +import model.Square; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class MotorizedFireFighter extends FireFighter { + private final Color viewColor = Color.CYAN; + private static javafx.scene.image.Image cloudImage; + + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/camion.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public MotorizedFireFighter(Position position, Board<Square> b) { + super(position, b); + } + + @Override + public List<Position> nextTurn(Board<Square> b) { + List<Position> positions = new ArrayList<>(); + + // Generate adjacent positions excluding mountains + List<Position> adjacentPositions = PositionUtil.generateAdjacentPositions(getPosition(), b); + adjacentPositions.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + + // Check if there is fire in any adjacent positions + boolean hasFire = adjacentPositions.stream() + .anyMatch(p -> b.doesSquareContainEntity(p, Fire.class)); + + boolean isStuck = isStuck(); + + if (hasFire) { + // Extinguish fires in adjacent positions + positions.addAll(extinguish(adjacentPositions, b)); + + if (isStuck) { + // If stuck, attempt to move and extinguish after moving + positions.addAll(moveAndExtinguish(b)); + } + } else { + // No fire adjacent; move and attempt to extinguish fires + positions.addAll(moveAndExtinguish(b)); + } + + // Update last positions for stuck detection + updateLastPositions(); + + return positions; + } + + @Override + protected List<Position> moveAndExtinguish(Board<Square> b) { + List<Position> positions = new ArrayList<>(); + + // Find the nearest fire + Position nearestFirePos; + try { + nearestFirePos = b.getNearestEntity(getPosition(), Fire.class, null); + } catch (Exception e) { + return List.of(); + } + if (nearestFirePos != null) { + // Get the next position after moving up to two steps towards the fire + Position nextPos = getNextPositionTowards(getPosition(), nearestFirePos, b, 2); + + if (nextPos != null && !nextPos.equals(getPosition())) { + // Move the firefighter + b.clearCaseFrom(this, getPosition()); // Clear old position + positions.add(new Position(getPosition().x(), getPosition().y())); // Add old position + setPosition(nextPos); + b.addEntityAtSquare(this, nextPos); // Add to new position + positions.add(nextPos); // Add new position + + // After moving, attempt to extinguish fires adjacent to new position + List<Position> newAdjacentPositions = PositionUtil.generateAdjacentPositions(nextPos, b); + newAdjacentPositions.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + positions.addAll(extinguish(newAdjacentPositions, b)); + } + } + + return positions; + } + + // Overloaded method to handle multiple steps + protected Position getNextPositionTowards(Position currentPos, Position targetPos, Board<Square> b, int steps) { + Position nextPos = currentPos; + + for (int i = 0; i < steps; i++) { + Position stepPos = super.getNextPositionTowards(nextPos, targetPos, b); + + if (stepPos == null || stepPos.equals(nextPos)) { + // Can't move further + break; + } + + nextPos = stepPos; + } + + return nextPos; + } + + // Override to ensure correct color + @Override + public Color getViewColor() { + return this.viewColor; + } + + // Ensure the last positions are updated correctly + protected void updateLastPositions() { + if (lastThreePosition.size() >= 3) { + lastThreePosition.remove(0); + } + lastThreePosition.add(this.getPosition()); + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } +} \ No newline at end of file diff --git a/src/main/java/model/firefighterscenario/Mountain.java b/src/main/java/model/firefighterscenario/Mountain.java new file mode 100644 index 0000000000000000000000000000000000000000..f8bd49c0f4a94c4c3f2deb1db85fc82670ec3f15 --- /dev/null +++ b/src/main/java/model/firefighterscenario/Mountain.java @@ -0,0 +1,82 @@ +package model.firefighterscenario; + +import java.util.List; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import util.Position; +import view.ViewElement; + +public class Mountain implements Entity{ + private final int priority = 0; + Position position; + private int age; + private final Color viewColor = Color.CHOCOLATE; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/montagne.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Mountain(Position p ){ + this.position = p; + } + + + public Mountain(Position p , int age){ + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } +} diff --git a/src/main/java/model/firefighterscenario/Rockery.java b/src/main/java/model/firefighterscenario/Rockery.java new file mode 100644 index 0000000000000000000000000000000000000000..26b0856a4089a35b61e36e4032292ea5a1c5b08f --- /dev/null +++ b/src/main/java/model/firefighterscenario/Rockery.java @@ -0,0 +1,97 @@ +package model.firefighterscenario; + +import java.util.List; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import util.Position; +import view.ViewElement; + +public class Rockery implements Entity{ + private final int priority = 0; + Position position; + private int age; + private int burn; + private final Color viewColor = Color.LIMEGREEN; + private static javafx.scene.image.Image cloudImage; + + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/fire/rochers.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Rockery(Position p ){ + this.position = p; + this.burn = 0; + } + + + public Rockery(Position p , int age){ + System.out.println("age : " + age); + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + public int getBurn(){ + return this.burn; + } + public void incrementBurn(){ + this.burn = burn + 1; + } + + @Override + public void setPosition(Position p) { + this.position = p; + + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + + public void resetBurn() { + this.burn = 0; + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } +} diff --git a/src/main/java/model/rockpapercisor/Cisor.java b/src/main/java/model/rockpapercisor/Cisor.java new file mode 100644 index 0000000000000000000000000000000000000000..0ab152ea50dc664b7054bb91c221051570bd675d --- /dev/null +++ b/src/main/java/model/rockpapercisor/Cisor.java @@ -0,0 +1,141 @@ +package model.rockpapercisor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Cisor implements Entity { + private final int priority = 0; + Position position; + private int age; + private final Color viewColor = Color.RED; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/pfc/cis.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Cisor(Position p) { + this.position = p; + } + + public Cisor(Position p, int age) { + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + Position target = null; + target = board.getNearestEntity(position, Paper.class, null); + + Position ennemy = null; + + ennemy = board.getNearestEntity(position, Rock.class, null); + if(ennemy == null && target == null){ + return List.of(); + } + Position nextPos = null; + // Vérifier la proximité d'un ennemi avant de choisir la direction. + if (ennemy != null && PositionUtil.getManhattanDistance(position, ennemy) < 5) { + nextPos = PositionUtil.getNextPositionAwayFrom(position, ennemy, board); + } else if(target != null){ + nextPos = PositionUtil.getNextPositionTowards(position, target, board); + } + + if (nextPos != null && board.doesPositionExist(nextPos)) { + board.addEntityAtSquare(this, nextPos); + board.clearCaseFrom(this, position); + Position oldPosition = new Position(position.x(), position.y()); + this.position = nextPos; + if (board.doesSquareContainEntity(nextPos, Paper.class)) { + List<Entity> entities = board.getStates(nextPos).getEntities(); + entities.removeIf(p -> p instanceof Paper); + } + + List<Position> result = new ArrayList<>(); + if (target != null) + result.add(target); + if (oldPosition != null) + result.add(oldPosition); + if (position != null) + result.add(position); + if (ennemy != null) + result.add(ennemy); + return result; + + } + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + Cisor cisor = (Cisor) obj; + return age == cisor.age && + Objects.equals(position, cisor.position); + } + + @Override + public int hashCode() { + return Objects.hash(position, age); + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/rockpapercisor/Paper.java b/src/main/java/model/rockpapercisor/Paper.java new file mode 100644 index 0000000000000000000000000000000000000000..9f972a30e2e18fcf11ab89700ae7798cc19aa01e --- /dev/null +++ b/src/main/java/model/rockpapercisor/Paper.java @@ -0,0 +1,123 @@ +package model.rockpapercisor; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Paper implements Entity { + private final int priority = 0; + Position position; + private int age; + private final Color viewColor = Color.GRAY; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/pfc/paper.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Paper(Position p) { + this.position = p; + } + + public Paper(Position p, int age) { + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + Position target = null; + target = board.getNearestEntity(position, Rock.class, null); + + Position ennemy = null; + ennemy = board.getNearestEntity(position, Cisor.class, null); + if(ennemy == null && target == null){ + return List.of(); + } + Position nextPos = null; + // Vérifier la proximité d'un ennemi avant de choisir la direction. + if (ennemy != null && PositionUtil.getManhattanDistance(position, ennemy) < 5) { + nextPos = PositionUtil.getNextPositionAwayFrom(position, ennemy, board); + } else if(target != null){ + nextPos = PositionUtil.getNextPositionTowards(position, target, board); + } + + if (nextPos != null && board.doesPositionExist(nextPos)) { + board.addEntityAtSquare(this, nextPos); + board.clearCaseFrom(this, position); + Position oldPosition = new Position(position.x(), position.y()); + this.position = nextPos; + if (board.doesSquareContainEntity(nextPos, Rock.class)) { + List<Entity> entities = board.getStates(nextPos).getEntities(); + entities.removeIf(p -> p instanceof Rock); + } + + List<Position> result = new ArrayList<>(); + if (target != null) + result.add(target); + if (oldPosition != null) + result.add(oldPosition); + if (position != null) + result.add(position); + if (ennemy != null) + result.add(ennemy); + return result; + + } + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public ViewElement getViewElement() { + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/rockpapercisor/Rock.java b/src/main/java/model/rockpapercisor/Rock.java new file mode 100644 index 0000000000000000000000000000000000000000..459c0e6895594f1f2b296f31483c487c5939d18f --- /dev/null +++ b/src/main/java/model/rockpapercisor/Rock.java @@ -0,0 +1,124 @@ +package model.rockpapercisor; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.paint.Color; +import model.Board; +import model.Entity; +import model.Square; +import model.firefighterscenario.Cloud; +import util.Position; +import util.PositionUtil; +import view.ViewElement; + +public class Rock implements Entity { + private final int priority = 0; + Position position; + private int age; + private final Color viewColor = Color.CHOCOLATE; + private static javafx.scene.image.Image cloudImage; + + static { + try { + cloudImage = new javafx.scene.image.Image(Cloud.class.getResource("/view/icons/pfc/rock.png").toExternalForm()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Rock(Position p) { + this.position = p; + } + + public Rock(Position p, int age) { + this.position = p; + this.age = age; + } + + @Override + public List<Position> nextTurn(Board<Square> board) { + Position target = null; + target = board.getNearestEntity(position, Cisor.class, null); + + Position ennemy = null; + ennemy = board.getNearestEntity(position, Paper.class, null); + + if(ennemy == null && target == null){ + return List.of(); + } + Position nextPos = null; + // Vérifier la proximité d'un ennemi avant de choisir la direction. + if (ennemy != null && PositionUtil.getManhattanDistance(position, ennemy) < 5) { + nextPos = PositionUtil.getNextPositionAwayFrom(position, ennemy, board); + } else if(target != null){ + nextPos = PositionUtil.getNextPositionTowards(position, target, board); + } + + if (nextPos != null && board.doesPositionExist(nextPos)) { + board.addEntityAtSquare(this, nextPos); + board.clearCaseFrom(this, position); + Position oldPosition = new Position(position.x(), position.y()); + this.position = nextPos; + if (board.doesSquareContainEntity(nextPos, Cisor.class)) { + List<Entity> entities = board.getStates(nextPos).getEntities(); + entities.removeIf(p -> p instanceof Cisor); + } + + List<Position> result = new ArrayList<>(); + if (target != null) + result.add(target); + if (oldPosition != null) + result.add(oldPosition); + if (position != null) + result.add(position); + if (ennemy != null) + result.add(ennemy); + return result; + + } + return List.of(); + } + + @Override + public Position getPosition() { + return this.position; + } + + @Override + public void setPosition(Position p) { + this.position = p; + + } + + @Override + public int getAge() { + return this.age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public void incrementAge() { + this.age += 1; + } + + @Override + public Color getViewColor() { + return this.viewColor; + } + + @Override + public int getPriority() { + return this.priority; + } + + @Override + public ViewElement getViewElement(){ + return new ViewElement(cloudImage); + } + +} diff --git a/src/main/java/model/rockpapercisor/RockPaperCisorScenario.java b/src/main/java/model/rockpapercisor/RockPaperCisorScenario.java new file mode 100644 index 0000000000000000000000000000000000000000..40207fddbd23605e56252ac9113341c9b7df4217 --- /dev/null +++ b/src/main/java/model/rockpapercisor/RockPaperCisorScenario.java @@ -0,0 +1,55 @@ +package model.rockpapercisor; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import model.Board; +import model.Entity; +import model.EntityFactory; +import model.Model; +import model.Scenario; +import model.Square; +import util.Position; + +public class RockPaperCisorScenario extends Scenario implements Model{ + public RockPaperCisorScenario(int columns, int rows, Map<EntityFactory, Integer> initialMap) { + super(columns, rows, initialMap); + } + + public List<Position> updateToNextGeneration() { + ArrayList<Position> changedPositions = new ArrayList<>(); + Iterator<Square> iterator = getMatrix().iterator(); + while (iterator.hasNext()) { + Square s = iterator.next(); + if (s.isEmpty()) + continue; + if (s.getMaxAge() == 0) { + s.incrementAllAges(); + continue; + } + if(s.getMaxAge()>stepNumber()+1){ + continue; + } + List<Entity> entities = new ArrayList<>(s.getEntities()); + for (Entity e : entities) { + e.incrementAge(); + changedPositions.addAll(e.nextTurn(this)); + } + } + + // Increment the step counter + this.step = this.step + 1; + return changedPositions; + } + + @Override + public Board<Square> getBoard() { + return this; + } + + + + +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4c36d97709b342e457203c75d081fc5cc1955c0f..1bd3e9a86156ea98b4650fee627b4411b90ff3fd 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,7 +2,8 @@ module firefighter { requires javafx.controls; requires javafx.fxml; requires javafx.graphics; - opens controller to javafx.fxml; + requires java.sql; + opens controller to javafx.fxml; exports app; opens app to javafx.fxml; } diff --git a/src/main/java/util/Direction.java b/src/main/java/util/Direction.java new file mode 100644 index 0000000000000000000000000000000000000000..471e8e7e31dbf1cbcc9eafdad645455bf4e07252 --- /dev/null +++ b/src/main/java/util/Direction.java @@ -0,0 +1,5 @@ +package util; + +public enum Direction { + NORTH, EAST, WEST, SOUTH +} diff --git a/src/main/java/util/DirectionUtils.java b/src/main/java/util/DirectionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f1daaeac1941f5fc6437868f42fa709ba2bc2bf0 --- /dev/null +++ b/src/main/java/util/DirectionUtils.java @@ -0,0 +1,109 @@ +package util; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import model.Board; +import model.Square; + +public class DirectionUtils { + + /** + * Obtient l'incrément sur l'axe X pour une direction donnée. + * + * @param direction La direction. + * @return L'incrément sur l'axe X. + */ + public static int getXIncrement(Direction direction) { + switch (direction) { + case NORTH: + return -1; + case SOUTH: + return 1; + default: + return 0; + } + } + + /** + * Obtient l'incrément sur l'axe Y pour une direction donnée. + * + * @param direction La direction. + * @return L'incrément sur l'axe Y. + */ + public static int getYIncrement(Direction direction) { + switch (direction) { + case EAST: + return 1; + case WEST: + return -1; + default: + return 0; + } + } + + /** + * Obtient la direction opposée. + * + * @param direction La direction actuelle. + * @return La direction opposée. + */ + public static Direction getOpposite(Direction direction) { + switch (direction) { + case NORTH: + return Direction.SOUTH; + case SOUTH: + return Direction.NORTH; + case EAST: + return Direction.WEST; + case WEST: + return Direction.EAST; + default: + throw new IllegalArgumentException("Direction non supportée : " + direction); + } + } + + /** + * Choisit une direction aléatoire parmi toutes les directions disponibles. + * + * @param random Instance de Random. + * @return Une direction aléatoire. + */ + public static Direction getRandomDirection() { + Random r = new Random(); + Direction[] directions = Direction.values(); + return directions[r.nextInt(directions.length)]; + } + + /** + * Obtient les directions valides en fonction des exclusions et de la position actuelle. + * + * @param currentDirection La direction actuelle. + * @param permanentlyExcludedDirections Les directions définitivement exclues. + * @param temporarilyExcludedDirections Les directions temporairement exclues. + * @param board Le plateau de jeu. + * @param currentPosition La position actuelle. + * @return Une liste de directions valides. + */ + public static List<Direction> getValidDirections( + Direction currentDirection, + Set<Direction> permanentlyExcludedDirections, + Set<Direction> temporarilyExcludedDirections, + Board<Square> board, + Position currentPosition + ) { + return Arrays.stream(Direction.values()) + .filter(dir -> dir != currentDirection) + .filter(dir -> dir != getOpposite(currentDirection)) + .filter(dir -> !permanentlyExcludedDirections.contains(dir)) + .filter(dir -> !temporarilyExcludedDirections.contains(dir)) + .filter(dir -> { + Position nextPos = currentPosition.move(dir); + return board.doesPositionExist(nextPos); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/util/Matrix.java b/src/main/java/util/Matrix.java index 6b5000be034cb782b77e941f7114ad37ab49b38d..5ed290f0c31f3134d241fb49e3d716fb3bd0f874 100644 --- a/src/main/java/util/Matrix.java +++ b/src/main/java/util/Matrix.java @@ -1,9 +1,15 @@ package util; + import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; +import model.Square; +import model.doctorviruspatient.Patient; +import model.firefighterscenario.Fire; +import model.firefighterscenario.FireFighter; + public class Matrix<E> implements Iterable<E> { private ArrayList<ArrayList<E>> matrix; private final int rows; @@ -59,7 +65,7 @@ public class Matrix<E> implements Iterable<E> { return this.rows; } - /* + public void displayMatrix() { System.out.print(" "); for (int j = 0; j < columns; j++) { @@ -71,11 +77,18 @@ public class Matrix<E> implements Iterable<E> { System.out.print("| "); for (int j = 0; j < columns; j++) { if (matrix.get(i).get(j) != null) { - if(matrix.get(i).get(j) instanceof Fire){ + Square s = (Square) matrix.get(i).get(j); + if(s.getEntities().stream().anyMatch(p -> p instanceof Fire)){ System.out.print(" F | "); } - else if(matrix.get(i).get(j) instanceof FireFighter){ + else if(s.getEntities().stream().anyMatch(p -> p instanceof FireFighter)){ System.out.print(" ff | "); + } + else if(s.getEntities().stream().anyMatch(p -> p instanceof Patient)){ + System.out.print(" P | "); + } + else if(s.getEntities().stream().anyMatch(p -> p instanceof model.doctorviruspatient.Virus)){ + System.out.print(" V | "); }else{ System.out.print(" | "); } @@ -92,7 +105,7 @@ public class Matrix<E> implements Iterable<E> { System.out.println(); } } - */ + private void validateIndex(int x, int y) { if (x < 0 || x >= rows || y < 0 || y >= columns) { diff --git a/src/main/java/util/PathGenerator.java b/src/main/java/util/PathGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..148116237eb0cc1ac4c04c91d23be3b5cb836543 --- /dev/null +++ b/src/main/java/util/PathGenerator.java @@ -0,0 +1,191 @@ +package util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import model.Board; + +public class PathGenerator { + private Board<?> board; + private final Random random = new Random(); + private final int maxStepsInDirection; + private final int minimumRoadLength; + private final Set<Direction> initialExcludedDirections = new HashSet<>(); + private final Position anchor; + + private Position currentPosition; + private Direction currentDirection; + private Direction excludedDirection = null; + private Direction lastDirection = null; + private int stepsInCurrentDirection = 0; + private int roadLength = 1; + private final List<Position> path = new ArrayList<>(); + + /** + * Constructeur de PathGenerator. + * + * @param board Le plateau de jeu. + * @param anchor La position de départ. + * @param maxStepsInDirection Nombre maximal de pas dans une direction avant de changer. + * @param minimumRoadLength Longueur minimale du chemin. + */ + public PathGenerator(Board<?> board, Position anchor, int maxStepsInDirection, int minimumRoadLength) { + this.board = board; + this.anchor = anchor; + this.maxStepsInDirection = maxStepsInDirection; + this.minimumRoadLength = minimumRoadLength; + initializePath(); + } + + /** + * Génère le chemin en respectant les contraintes. + * + * @return Liste des positions formant le chemin. + */ + public List<Position> generate() { + while (true) { + if (canMoveInCurrentDirection()) { + moveInCurrentDirection(); + if (shouldChangeDirection()) { + if (!changeDirection(false)) break; + } + } else { + if (!handleObstacle()) break; + } + } + return roadLength >= minimumRoadLength ? path : regeneratePath(); + } + public void setBoard(Board<?> board){ + this.board = board; + } + /** Initialise le chemin avec l'ancre et choisit la première direction. */ + private void initializePath() { + path.clear(); + roadLength = 1; + stepsInCurrentDirection = 0; + currentPosition = anchor; + path.add(anchor); + currentDirection = DirectionUtils.getRandomDirection(); + initialExcludedDirections.clear(); + initialExcludedDirections.add(currentDirection); + lastDirection = currentDirection; + excludedDirection = null; + } + + /** Vérifie si le mouvement dans la direction actuelle est possible. */ + private boolean canMoveInCurrentDirection() { + Position nextPosition = currentPosition.move(currentDirection); + return board.doesPositionExist(nextPosition); + } + + /** Effectue le mouvement dans la direction actuelle. */ + private void moveInCurrentDirection() { + currentPosition = currentPosition.move(currentDirection); + path.add(currentPosition); + roadLength++; + stepsInCurrentDirection++; + excludedDirection = DirectionUtils.getOpposite(currentDirection); + } + + /** Détermine si un changement de direction est nécessaire. */ + private boolean shouldChangeDirection() { + return stepsInCurrentDirection >= maxStepsInDirection; + } + + /** + * Change la direction actuelle. + * + * @param mustContinue Si true, la nouvelle direction doit permettre de continuer le chemin. + * @return true si la direction a été changée avec succès, sinon false. + */ + private boolean changeDirection(boolean mustContinue) { + Direction newDirection = chooseNewDirection(mustContinue); + if (newDirection == null) return false; + updateDirection(newDirection); + return true; + } + + /** Gère le cas où le mouvement n'est pas possible (obstacle ou bord du plateau). */ + private boolean handleObstacle() { + if (roadLength < minimumRoadLength) { + return changeDirection(true); + } + return false; + } + + /** Réinitialise et génère à nouveau le chemin. */ + private List<Position> regeneratePath() { + initializePath(); + return generate(); + } + + /** Met à jour la direction actuelle et les compteurs associés. */ + private void updateDirection(Direction newDirection) { + currentDirection = newDirection; + stepsInCurrentDirection = 0; + lastDirection = newDirection; + excludedDirection = DirectionUtils.getOpposite(newDirection); + } + + /** + * Choisit une nouvelle direction valide en tenant compte des exclusions. + * + * @param mustContinue Si true, la direction doit permettre de continuer le chemin. + * @return La nouvelle direction choisie ou null si aucune n'est valide. + */ + private Direction chooseNewDirection(boolean mustContinue) { + Set<Direction> exclusions = new HashSet<>(initialExcludedDirections); + Collections.addAll(exclusions, excludedDirection, lastDirection); + List<Direction> validDirections = getValidDirections(exclusions, currentPosition); + + if (mustContinue) { + validDirections = filterDirectionsToContinue(validDirections, currentPosition); + } + + return validDirections.isEmpty() ? null : validDirections.get(random.nextInt(validDirections.size())); + } + + /** + * Obtient les directions valides à partir de la position actuelle. + * + * @param exclusions Les directions à exclure. + * @param currentPosition La position actuelle. + * @return Liste des directions valides. + */ + private List<Direction> getValidDirections(Set<Direction> exclusions, Position currentPosition) { + List<Direction> validDirections = new ArrayList<>(); + for (Direction dir : Direction.values()) { + if (!exclusions.contains(dir)) { + Position nextPos = currentPosition.move(dir); + if (board.doesPositionExist(nextPos)) { + validDirections.add(dir); + } + } + } + return validDirections; + } + + /** + * Filtre les directions pour s'assurer qu'elles permettent de continuer le chemin. + * + * @param directions Liste des directions valides. + * @param currentPosition La position actuelle. + * @return Liste des directions permettant de continuer. + */ + private List<Direction> filterDirectionsToContinue(List<Direction> directions, Position currentPosition) { + List<Direction> filtered = new ArrayList<>(); + for (Direction dir : directions) { + Position nextPos = currentPosition.move(dir); + Set<Direction> futureExclusions = new HashSet<>(initialExcludedDirections); + futureExclusions.add(DirectionUtils.getOpposite(dir)); + if (!getValidDirections(futureExclusions, nextPos).isEmpty()) { + filtered.add(dir); + } + } + return filtered; + } +} diff --git a/src/main/java/util/Position.java b/src/main/java/util/Position.java index 389b8214dbc56af0914084e8fa7e68cd3c1b2e72..d6407fadb846754bdfb63ef8b4a5966dd78f25e1 100644 --- a/src/main/java/util/Position.java +++ b/src/main/java/util/Position.java @@ -1,5 +1,10 @@ package util; public record Position(int x, int y) { - + // Méthode pour déplacer la position dans une direction donnée + public Position move(Direction direction) { + int newX = this.x + DirectionUtils.getXIncrement(direction); + int newY = this.y + DirectionUtils.getYIncrement(direction); + return new Position(newX, newY); + } } diff --git a/src/main/java/util/PositionUtil.java b/src/main/java/util/PositionUtil.java index 48f5436d9856cd14eb84baf631f8391e8b26bdfe..096c290d4560c1edbf0d73da67370422515e64da 100644 --- a/src/main/java/util/PositionUtil.java +++ b/src/main/java/util/PositionUtil.java @@ -2,11 +2,14 @@ package util; import java.util.ArrayList; import java.util.List; +import java.util.Random; import java.util.stream.Collectors; import java.util.stream.Stream; import model.Board; -import model.Entity; +import model.Square; +import model.firefighterscenario.FireFighter; +import model.firefighterscenario.Mountain; public class PositionUtil { /** @@ -61,7 +64,7 @@ public class PositionUtil { * si une position existe. * @return une liste des positions adjacentes valides. */ - public static List<Position> generateAdjacentPositions(Position position, Board<Entity> board) { + public static List<Position> generateAdjacentPositions(Position position, Board<Square> board) { int x = position.x(); int y = position.y(); @@ -74,9 +77,19 @@ public class PositionUtil { .collect(Collectors.toList()); } + public static List<Position> generateAdjacentPositions(Position position, Board<Square> board, int distance) { + + List<Position> positions = new ArrayList<Position>(); + for(int i = 0; i < 4; i++){ + + } + return positions; + } + + // Méthode pour générer toutes les positions adjacentes (y compris les // diagonales) - public static List<Position> generateAllAdjacentPositions(Position position, Board<Entity> board) { + public static List<Position> generateAllAdjacentPositions(Position position, Board<Square> board) { int x = position.x(); int y = position.y(); @@ -110,4 +123,202 @@ public class PositionUtil { return Math.abs(p1.x() - p2.x()) + Math.abs(p1.y() - p2.y()); } + /** + * Generates all positions within a specified radius from a center position. + * It uses Manhattan distance by default. + * + * @param center The center position. + * @param radius The radius (distance) from the center position. + * @param board The board to consider boundaries. + * @return A list of positions within the radius. + */ + public static List<Position> getPositionsInRadius(Position center, int radius, Board<Square> board) { + List<Position> positions = new ArrayList<>(); + + int startX = center.x() - radius; + int endX = center.x() + radius; + int startY = center.y() - radius; + int endY = center.y() + radius; + + for (int x = startX; x <= endX; x++) { + for (int y = startY; y <= endY; y++) { + Position pos = new Position(x, y); + if (board.doesPositionExist(pos)) { + // Calculate Manhattan distance from the center + int distance = Math.abs(center.x() - x) + Math.abs(center.y() - y); + if (distance <= radius) { + positions.add(pos); + } + } + } + } + + return positions; + } + + + public static Position getNextPositionTowards(Position currentPos, Position targetPos, Board<Square> b) { + // Generate adjacent positions + List<Position> possibleMoves = PositionUtil.generateAllAdjacentPositions(currentPos, b); + + // Filter out positions that are not empty or contain obstacles + possibleMoves.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + + // If no possible moves, return null + if (possibleMoves.isEmpty()) { + return null; + } + + // Calculate the current distance to the target + int currentDistance = PositionUtil.getManhattanDistance(currentPos, targetPos); + + // Initialize variables to find the best moves + int minDistance = Integer.MAX_VALUE; + List<Position> bestMoves = new ArrayList<>(); + + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + + // Skip positions occupied by other firefighters + if (b.doesSquareContainEntity(move, FireFighter.class)) { + continue; + } + + // Find positions that minimize the distance + if (distance < minDistance) { + minDistance = distance; + bestMoves.clear(); + bestMoves.add(move); + } else if (distance == minDistance) { + bestMoves.add(move); + } + } + + // If no better move is found, consider moves that maintain the same distance + if (bestMoves.isEmpty()) { + minDistance = currentDistance; + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + if (distance == minDistance) { + bestMoves.add(move); + } + } + } + + // If still no move is found, stay in the current position + if (bestMoves.isEmpty()) { + return currentPos; + } + + // Select a move from the best moves (e.g., randomly or based on additional criteria) + Random r = new Random(); + + Position nextMove = bestMoves.get(r.nextInt(bestMoves.size())); + + return nextMove; + } + + public static Position getNextPositionAwayFrom(Position currentPos, Position targetPos, Board<Square> b) { + // Générer les positions adjacentes + List<Position> possibleMoves = PositionUtil.generateAllAdjacentPositions(currentPos, b); + + // Filtrer les positions qui ne sont pas vides ou contiennent des obstacles + possibleMoves.removeIf(p -> b.doesSquareContainEntity(p, Mountain.class)); + + // Si aucune possibilité de déplacement, retourner null + if (possibleMoves.isEmpty()) { + return null; + } + + // Calculer la distance actuelle par rapport à la cible + int currentDistance = PositionUtil.getManhattanDistance(currentPos, targetPos); + + // Initialiser les variables pour trouver les meilleurs déplacements + int maxDistance = Integer.MIN_VALUE; + List<Position> bestMoves = new ArrayList<>(); + + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + + // Ignorer les positions occupées par d'autres entités, comme les pompiers + if (b.doesSquareContainEntity(move, FireFighter.class)) { + continue; + } + + // Trouver les positions qui maximisent la distance + if (distance > maxDistance) { + maxDistance = distance; + bestMoves.clear(); + bestMoves.add(move); + } else if (distance == maxDistance) { + bestMoves.add(move); + } + } + + // Si aucun meilleur déplacement n'est trouvé, considérer les mouvements qui maintiennent la même distance + if (bestMoves.isEmpty()) { + maxDistance = currentDistance; + for (Position move : possibleMoves) { + int distance = PositionUtil.getManhattanDistance(move, targetPos); + if (distance == maxDistance) { + bestMoves.add(move); + } + } + } + + // Si toujours aucun mouvement n'est trouvé, rester à la position actuelle + if (bestMoves.isEmpty()) { + return currentPos; + } + + // Sélectionner un mouvement parmi les meilleurs mouvements (par exemple aléatoirement ou selon des critères supplémentaires) + Random r = new Random(); + + Position nextMove = bestMoves.get(r.nextInt(bestMoves.size())); + + return nextMove; + } + public static Direction getOppositeDirection(Direction direction) { + switch (direction) { + case NORTH: return Direction.SOUTH; + case SOUTH: return Direction.NORTH; + case EAST: return Direction.WEST; + case WEST: return Direction.EAST; + default: throw new IllegalArgumentException("Direction non supportée : " + direction); + } + } + + /** + * Détermine la direction principale (NORTH, SOUTH, EAST, WEST) de toPos par rapport à fromPos. + * + * @param fromPos la position de départ. + * @param toPos la position de destination. + * @return la direction principale de toPos par rapport à fromPos. + */ +public static Direction getDirectionFromTwoPoints(Position fromPos, Position toPos) { + int deltaX = toPos.x() - fromPos.x(); + int deltaY = toPos.y() - fromPos.y(); + + if (deltaX == 0 && deltaY == 0) { + return null; // Les positions sont identiques + } + + if (Math.abs(deltaX) > Math.abs(deltaY)) { + // Mouvement principalement vers l'Est ou l'Ouest + if (deltaX > 0) { + return Direction.EAST; + } else { + return Direction.WEST; + } + } else { + // Mouvement principalement vers le Nord ou le Sud + if (deltaY > 0) { + return Direction.SOUTH; + } else { + return Direction.NORTH; + } + } +} + + } \ No newline at end of file diff --git a/src/main/java/view/FirefighterGrid.java b/src/main/java/view/FirefighterGrid.java index e5061a3b16c98a44bfa670cb6423dc2b61dbf22f..210b737b11125956c79027096210a7e90d873953 100644 --- a/src/main/java/view/FirefighterGrid.java +++ b/src/main/java/view/FirefighterGrid.java @@ -1,16 +1,30 @@ package view; - import java.util.List; import javafx.scene.canvas.Canvas; +import javafx.scene.image.Image; import javafx.scene.paint.Color; import javafx.util.Pair; import util.Position; public class FirefighterGrid extends Canvas implements Grid<ViewElement> { + //private void paintElementAtPosition(ViewElement element, Position position) { + // paintBox(position.x(), position.y(), element.getColor()); + //} + + private void paintElementAtPosition(ViewElement element, Position position) { - paintBox(position.x(), position.y(), element.getColor()); + // Efface la case pour éviter les superpositions + clearBox(position.x(), position.y()); + + // Vérifie si une image est définie dans l'élément + if (element.getImage() != null) { + Image image = element.getImage(); + getGraphicsContext2D().drawImage(image, position.y() * boxWidth, position.x() * boxHeight, boxWidth, boxHeight); + } else { + paintBox(position.x(), position.y(), element.getColor()); + } } private int boxWidth; diff --git a/src/main/java/view/Grid.java b/src/main/java/view/Grid.java index b95d59f622a86b41f2a41261b8b27aaf2e911dfb..c0754aedc8933a5f2a4b2983d680aa56150a87ef 100644 --- a/src/main/java/view/Grid.java +++ b/src/main/java/view/Grid.java @@ -12,44 +12,44 @@ import java.util.List; */ public interface Grid<E> { - /** - * Repaint the grid with a list of elements, each associated with their respective positions. - * - * @param elements A list of pairs, each containing a position and the element to be displayed at that position. - */ - void repaint(List<Pair<Position, E>> elements); - - /** - * Repaint the grid with a two-dimensional array of elements. The array's dimensions should match - * the row and column count of the grid. - * - * @param elements A two-dimensional array of elements to be displayed on the grid. - */ - void repaint(E[][] elements); - - /** - * Set the dimensions of the grid to the specified number of columns, number of rows, square width, - * and square height. - * - * @param columnCount The new number of columns in the grid. - * @param rowCount The new number of rows in the grid. - * @param squareWidth The width of each square within the grid. - * @param squareHeight The height of each square within the grid. - */ - void setDimensions(int columnCount, int rowCount, int squareWidth, int squareHeight); - - /** - * Get the number of columns in the grid. - * - * @return The number of columns in the grid. - */ - int columnCount(); - - /** - * Get the number of rows in the grid. - * - * @return The number of rows in the grid. - */ - int rowCount(); + /** + * Repaint the grid with a list of elements, each associated with their respective positions. + * + * @param elements A list of pairs, each containing a position and the element to be displayed at that position. + */ + void repaint(List<Pair<Position, E>> elements); + + /** + * Repaint the grid with a two-dimensional array of elements. The array's dimensions should match + * the row and column count of the grid. + * + * @param elements A two-dimensional array of elements to be displayed on the grid. + */ + void repaint(E[][] elements); + + /** + * Set the dimensions of the grid to the specified number of columns, number of rows, square width, + * and square height. + * + * @param columnCount The new number of columns in the grid. + * @param rowCount The new number of rows in the grid. + * @param squareWidth The width of each square within the grid. + * @param squareHeight The height of each square within the grid. + */ + void setDimensions(int columnCount, int rowCount, int squareWidth, int squareHeight); + + /** + * Get the number of columns in the grid. + * + * @return The number of columns in the grid. + */ + int columnCount(); + + /** + * Get the number of rows in the grid. + * + * @return The number of rows in the grid. + */ + int rowCount(); } diff --git a/src/main/java/view/ViewElement.java b/src/main/java/view/ViewElement.java index 5043644547213f648760eba9e4bb0693411e527b..3059a474eba08dc25e8e0e9cb8a186344a5c85c1 100644 --- a/src/main/java/view/ViewElement.java +++ b/src/main/java/view/ViewElement.java @@ -1,15 +1,29 @@ package view; +import javafx.scene.image.Image; import javafx.scene.paint.Color; public class ViewElement { private final Color color; + private final Image image; + // Constructeur avec couleur uniquement public ViewElement(Color color) { this.color = color; + this.image = null; + } + + // Constructeur avec image + public ViewElement(Image image) { + this.color = null; + this.image = image; } public Color getColor() { return color; } + + public Image getImage() { + return image; + } } diff --git a/src/main/resources/view/icons/fire/avion.png b/src/main/resources/view/icons/fire/avion.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5d9a690230ddb0a55e5f4050faf69af578341b Binary files /dev/null and b/src/main/resources/view/icons/fire/avion.png differ diff --git a/src/main/resources/view/icons/fire/camion.png b/src/main/resources/view/icons/fire/camion.png new file mode 100644 index 0000000000000000000000000000000000000000..b3a361c84dc686238486079ae477cf346214528f Binary files /dev/null and b/src/main/resources/view/icons/fire/camion.png differ diff --git a/src/main/resources/view/icons/fire/flamme.png b/src/main/resources/view/icons/fire/flamme.png new file mode 100644 index 0000000000000000000000000000000000000000..0784e111e8761e9c2bacc9102eecc62b97bbf58b Binary files /dev/null and b/src/main/resources/view/icons/fire/flamme.png differ diff --git a/src/main/resources/view/icons/fire/img.png b/src/main/resources/view/icons/fire/img.png new file mode 100644 index 0000000000000000000000000000000000000000..f765539c06aafd59b0c2fc1a09fc389f9996410c Binary files /dev/null and b/src/main/resources/view/icons/fire/img.png differ diff --git a/src/main/resources/view/icons/fire/montagne.png b/src/main/resources/view/icons/fire/montagne.png new file mode 100644 index 0000000000000000000000000000000000000000..cf970e54796b833d6e28678344d48b48bf02f2ad Binary files /dev/null and b/src/main/resources/view/icons/fire/montagne.png differ diff --git a/src/main/resources/view/icons/fire/nuage.png b/src/main/resources/view/icons/fire/nuage.png new file mode 100644 index 0000000000000000000000000000000000000000..308714e9a1c1acafcf230fef7f7e4a79ccc0e49d Binary files /dev/null and b/src/main/resources/view/icons/fire/nuage.png differ diff --git a/src/main/resources/view/icons/fire/rochers.png b/src/main/resources/view/icons/fire/rochers.png new file mode 100644 index 0000000000000000000000000000000000000000..3c43a47397f45827363ed17e2bdbf1e651a3c864 Binary files /dev/null and b/src/main/resources/view/icons/fire/rochers.png differ diff --git a/src/main/resources/view/icons/fire/route.png b/src/main/resources/view/icons/fire/route.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3ba6dc051837260312bc0d168c2a9952788a32 Binary files /dev/null and b/src/main/resources/view/icons/fire/route.png differ diff --git a/src/main/resources/view/icons/fire/sapeur-pompier.png b/src/main/resources/view/icons/fire/sapeur-pompier.png new file mode 100644 index 0000000000000000000000000000000000000000..b2e592c7475ce7f1b4747300ee9e8f4f1fd1d345 Binary files /dev/null and b/src/main/resources/view/icons/fire/sapeur-pompier.png differ diff --git a/src/main/resources/view/icons/pfc/cis.png b/src/main/resources/view/icons/pfc/cis.png new file mode 100644 index 0000000000000000000000000000000000000000..646b9f7b984a658b64051266f3aaf4b26fa13b21 Binary files /dev/null and b/src/main/resources/view/icons/pfc/cis.png differ diff --git a/src/main/resources/view/icons/pfc/paper.png b/src/main/resources/view/icons/pfc/paper.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3f80c1ccdd8709ac1240f2bc9d36705500efc3 Binary files /dev/null and b/src/main/resources/view/icons/pfc/paper.png differ diff --git a/src/main/resources/view/icons/pfc/rock.png b/src/main/resources/view/icons/pfc/rock.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0bc082f7231fad12300e0cabf87a988e4aa33e Binary files /dev/null and b/src/main/resources/view/icons/pfc/rock.png differ diff --git a/src/main/resources/view/icons/virus/docteur.png b/src/main/resources/view/icons/virus/docteur.png new file mode 100644 index 0000000000000000000000000000000000000000..902a90c30e5eea76c075246b2573f4480704ed64 Binary files /dev/null and b/src/main/resources/view/icons/virus/docteur.png differ diff --git a/src/main/resources/view/icons/virus/humain.png b/src/main/resources/view/icons/virus/humain.png new file mode 100644 index 0000000000000000000000000000000000000000..d88de16c2b701a823f7bceccc5629562cab05a56 Binary files /dev/null and b/src/main/resources/view/icons/virus/humain.png differ diff --git a/src/main/resources/view/icons/virus/patient.png b/src/main/resources/view/icons/virus/patient.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa4113c8fd5ad39ee4592877058c27e2d2e52e0 Binary files /dev/null and b/src/main/resources/view/icons/virus/patient.png differ diff --git a/src/main/resources/view/icons/virus/virus.png b/src/main/resources/view/icons/virus/virus.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d6b7c5e10766a9fe5ce085a1da41486ce22716 Binary files /dev/null and b/src/main/resources/view/icons/virus/virus.png differ