diff --git a/oolab/build.gradle b/oolab/build.gradle index 34ad45c..e89a777 100644 --- a/oolab/build.gradle +++ b/oolab/build.gradle @@ -1,13 +1,20 @@ plugins { id 'application' id 'java' + id 'org.openjfx.javafxplugin' version '0.0.13' +// id 'org.openjfx.javafxplugin' version '0.1.0' } + group = 'org.example' version = '1.0-SNAPSHOT' repositories { mavenCentral() + javafx { + version = "17" + modules = ['javafx.base', 'javafx.controls', 'javafx.fxml', 'javafx.graphics', 'javafx.media', 'javafx.swing', 'javafx.web'] + } } dependencies { @@ -20,7 +27,8 @@ test { } application { - getMainClass().set('agh.ics.oop.World') + getMainClass().set('agh.ics.oop.Main') + } java { diff --git a/oolab/src/main/java/agh/ics/oop/Main.java b/oolab/src/main/java/agh/ics/oop/Main.java new file mode 100644 index 0000000..6a19f0c --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/Main.java @@ -0,0 +1,10 @@ +//package agh.ics.oop; +// +//import javafx.application.Application; +// +//public class Main { +// +// public static void main(String[] args) { +// Application.launch(SimulationApp.class, args); +// } +//} \ No newline at end of file diff --git a/oolab/src/main/java/agh/ics/oop/OptionsParser.java b/oolab/src/main/java/agh/ics/oop/OptionsParser.java index 4c158f8..db88d52 100644 --- a/oolab/src/main/java/agh/ics/oop/OptionsParser.java +++ b/oolab/src/main/java/agh/ics/oop/OptionsParser.java @@ -1,6 +1,8 @@ package agh.ics.oop; import agh.ics.oop.model.MoveDirection; +import agh.ics.oop.model.exceptions.IllegalMoveSpecificationException; + import java.util.List; import java.util.ArrayList; @@ -14,7 +16,7 @@ public static List parse(String[] input) { case "b" -> moves.add(MoveDirection.BACKWARD); case "r" -> moves.add(MoveDirection.RIGHT); case "l" -> moves.add(MoveDirection.LEFT); - default -> throw new IllegalArgumentException(letter + " is not legal move specification"); + default -> throw new IllegalMoveSpecificationException(letter); } } return moves; diff --git a/oolab/src/main/java/agh/ics/oop/Simulation.java b/oolab/src/main/java/agh/ics/oop/Simulation.java index 9c85a12..0d90e7c 100644 --- a/oolab/src/main/java/agh/ics/oop/Simulation.java +++ b/oolab/src/main/java/agh/ics/oop/Simulation.java @@ -8,7 +8,7 @@ import java.util.Collections; import java.util.List; -public class Simulation { +public class Simulation implements Runnable { public static final String ANIMAL_STRING = "Animal"; private final List positions; private final List moves; @@ -17,7 +17,7 @@ public Simulation(List positions, List moves, WorldMap this.positions = new ArrayList<>(positions); //zeby dało sie usuwać this.moves = moves; this.worldMap = worldMap; - fillWorldMap(); +// fillWorldMap(); } private void fillWorldMap() { @@ -27,8 +27,11 @@ private void fillWorldMap() { Animal animal = new Animal(position); try { worldMap.place(animal); + Thread.sleep(750); }catch (IncorrectPositionException e){ indicesToRemove.add(i); + } catch (InterruptedException e) { + e.printStackTrace(); } } indicesToRemove.sort(Collections.reverseOrder()); @@ -49,13 +52,19 @@ public List getPositions() { public void run(){ int animalsIndex; Vector2d currPos; - + fillWorldMap(); for (int i = 0; i < moves.size(); i++) { animalsIndex = i % positions.size(); currPos = positions.get(animalsIndex); //pozycja zwierzaka, którego bedziemy chcieli przenieść WorldElement animalAtCurrPos = worldMap.objectAt(currPos); worldMap.move((Animal) animalAtCurrPos, moves.get(i)); //próba przeniesienia positions.set(animalsIndex, animalAtCurrPos.getPos()); //zaktualizuj pozycje + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } } + } } diff --git a/oolab/src/main/java/agh/ics/oop/SimulationApp.java b/oolab/src/main/java/agh/ics/oop/SimulationApp.java new file mode 100644 index 0000000..54e85c1 --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/SimulationApp.java @@ -0,0 +1,61 @@ +package agh.ics.oop; + +import agh.ics.oop.model.*; +import agh.ics.oop.model.exceptions.IllegalMoveSpecificationException; +import agh.ics.oop.presenter.SimulationPresenter; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class SimulationApp extends Application { + @Override + public void start(Stage primaryStage) throws Exception { + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(getClass().getClassLoader().getResource("simulation.fxml")); + BorderPane viewRoot = loader.load(); + SimulationPresenter presenter = loader.getController(); + + configureStage(primaryStage, viewRoot); + + primaryStage.show(); + + + + + +// java.util.List directions = null; +// try { +// directions = List.of(MoveDirection.FORWARD, MoveDirection.BACKWARD, MoveDirection.FORWARD, MoveDirection.LEFT, MoveDirection.FORWARD, MoveDirection.RIGHT); +// } +// catch (IllegalMoveSpecificationException e) { +// e.printStackTrace(); +// System.err.println("error: " + e.getMessage()); +// return; +// } +// java.util.List positions = java.util.List.of(new Vector2d(2,2), new Vector2d(3,4)); +// List simulations = new ArrayList<>(); +// AbstractWorldMap grassF = new GrassField(10); +// grassF.addObserver(new ConsoleMapDisplay()); +// SimulationPresenter simPresenter = loader.getController(); +// simPresenter.setWorldMap(grassF); +// grassF.addObserver(simPresenter); +// simulations.add(new Simulation(positions,directions,grassF)); +// +// SimulationEngine simEngine = new SimulationEngine(simulations); +// simEngine.runSync(); +// simEngine.runAsyncInThreadPool(); + } + private void configureStage(Stage primaryStage, BorderPane viewRoot) { + var scene = new Scene(viewRoot); + primaryStage.setScene(scene); + primaryStage.setTitle("Simulation app"); + primaryStage.minWidthProperty().bind(viewRoot.minWidthProperty()); + primaryStage.minHeightProperty().bind(viewRoot.minHeightProperty()); + } +} diff --git a/oolab/src/main/java/agh/ics/oop/World.java b/oolab/src/main/java/agh/ics/oop/World.java index f0a369c..4b3437e 100644 --- a/oolab/src/main/java/agh/ics/oop/World.java +++ b/oolab/src/main/java/agh/ics/oop/World.java @@ -1,25 +1,40 @@ package agh.ics.oop; import agh.ics.oop.model.*; +import agh.ics.oop.model.exceptions.IllegalMoveSpecificationException; +import javafx.application.Application; +import javafx.stage.Stage; +import java.util.ArrayList; import java.util.List; public class World { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { List directions = null; try { directions = OptionsParser.parse(args); } - catch (IllegalArgumentException e) { + catch (IllegalMoveSpecificationException e) { e.printStackTrace(); System.err.println("error: " + e.getMessage()); return; } List positions = List.of(new Vector2d(2,2), new Vector2d(3,4)); - AbstractWorldMap grassField = new GrassField(10); - ConsoleMapDisplay observer1 = new ConsoleMapDisplay(); - grassField.addObserver(observer1); - Simulation simulation = new Simulation(positions, directions, grassField); - simulation.run(); + List simulations = new ArrayList<>(); + for (int i = 0; i < 1000;i++){ + AbstractWorldMap grassF = new GrassField(10); + grassF.addObserver(new ConsoleMapDisplay()); + AbstractWorldMap rectangularF = new RectangularMap(5,5); + rectangularF.addObserver(new ConsoleMapDisplay()); + simulations.add(new Simulation(positions,directions,rectangularF)); + simulations.add(new Simulation(positions,directions,grassF)); + } + SimulationEngine simEngine = new SimulationEngine(simulations); +// simEngine.runSync(); + simEngine.runAsyncInThreadPool(); + System.out.println("system zakonczyl dzialanie"); +// SimulationApp simulationApp = new SimulationApp(); +// Stage stage = new Stage(); +// simulationApp.start(stage); } public static void run(MoveDirection[] input){ for (MoveDirection arg : input) { diff --git a/oolab/src/main/java/agh/ics/oop/WorldGUI.java b/oolab/src/main/java/agh/ics/oop/WorldGUI.java new file mode 100644 index 0000000..4aae34e --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/WorldGUI.java @@ -0,0 +1,10 @@ +package agh.ics.oop; + +import javafx.application.Application; + +public class WorldGUI { + public static void main(String[] args) { + //to co normalnie bym w World robil teraz powinno byc w WorldGUI + Application.launch(SimulationApp.class, args); + } +} diff --git a/oolab/src/main/java/agh/ics/oop/model/AbstractWorldMap.java b/oolab/src/main/java/agh/ics/oop/model/AbstractWorldMap.java index 8fc05d5..a2d7805 100644 --- a/oolab/src/main/java/agh/ics/oop/model/AbstractWorldMap.java +++ b/oolab/src/main/java/agh/ics/oop/model/AbstractWorldMap.java @@ -9,6 +9,7 @@ import static agh.ics.oop.Simulation.ANIMAL_STRING; public abstract class AbstractWorldMap implements WorldMap{ + private final UUID id = UUID.randomUUID(); protected final Map animals = new HashMap<>(); //animals jest w obu mapach public abstract Boundary getCurrentBounds(); public abstract boolean canMoveTo(Vector2d position); @@ -90,5 +91,8 @@ public List getElements(){ return new ArrayList<>(animals.values()); } + public UUID getId(){ + return id; + } } diff --git a/oolab/src/main/java/agh/ics/oop/model/ConsoleMapDisplay.java b/oolab/src/main/java/agh/ics/oop/model/ConsoleMapDisplay.java index 152398b..25a6769 100644 --- a/oolab/src/main/java/agh/ics/oop/model/ConsoleMapDisplay.java +++ b/oolab/src/main/java/agh/ics/oop/model/ConsoleMapDisplay.java @@ -6,9 +6,11 @@ public class ConsoleMapDisplay implements MapChangeListener{ private int updateCnt = 0; @Override public void mapChanged(WorldMap worldMap, String message) { - updateCnt++; - System.out.println(message); - System.out.println(worldMap); - System.out.println("%s %s: %d\n\n".formatted(UPDATE_STRING, COUNT_STRING, updateCnt)); + synchronized (System.out) { + updateCnt++; + System.out.println(message); + System.out.println(worldMap); + System.out.println("%s %s: %d ID:%s\n\n".formatted(UPDATE_STRING, COUNT_STRING, updateCnt, worldMap.getId())); + } } } diff --git a/oolab/src/main/java/agh/ics/oop/model/RectangularMap.java b/oolab/src/main/java/agh/ics/oop/model/RectangularMap.java index 78c7055..165fd06 100644 --- a/oolab/src/main/java/agh/ics/oop/model/RectangularMap.java +++ b/oolab/src/main/java/agh/ics/oop/model/RectangularMap.java @@ -32,7 +32,7 @@ public boolean canMoveTo(Vector2d position) { @Override public Boundary getCurrentBounds(){ - return new Boundary(upperRightBoundary, lowerLeftBoundary); + return new Boundary(lowerLeftBoundary, upperRightBoundary); } diff --git a/oolab/src/main/java/agh/ics/oop/model/SimulationEngine.java b/oolab/src/main/java/agh/ics/oop/model/SimulationEngine.java new file mode 100644 index 0000000..1f33eb4 --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/model/SimulationEngine.java @@ -0,0 +1,60 @@ +package agh.ics.oop.model; + +import agh.ics.oop.Simulation; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class SimulationEngine { + public static final String EXECUTOR_TIMEOUT = "Executor did not terminate in time"; + private final List simulations; + private final List threads = new ArrayList<>(); + private final ExecutorService executor = Executors.newFixedThreadPool(4); + public SimulationEngine(List simulations){ + this.simulations = simulations; + } + + public void runSync(){ + for (Simulation sim : simulations){ + sim.run(); + } + } + + public void runAsync(){ + for (Simulation sim : simulations){ + Thread thread = new Thread(sim); + threads.add(thread); + thread.start(); + } + try { + awaitSimulationEnd(); + }catch(InterruptedException e) { + e.printStackTrace(); + } + } + + public void awaitSimulationEnd() throws InterruptedException { + for (Thread thread : threads){ + thread.join(); + } + executor.shutdown(); + if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { + System.out.println("%s (%d s)".formatted(EXECUTOR_TIMEOUT, 10)); + executor.shutdownNow(); + } + } + + public void runAsyncInThreadPool(){ + for (Runnable simulation : simulations) { + executor.submit(simulation); + } + try { + awaitSimulationEnd(); + }catch(InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/oolab/src/main/java/agh/ics/oop/model/WorldMap.java b/oolab/src/main/java/agh/ics/oop/model/WorldMap.java index 3553af3..742f673 100644 --- a/oolab/src/main/java/agh/ics/oop/model/WorldMap.java +++ b/oolab/src/main/java/agh/ics/oop/model/WorldMap.java @@ -5,6 +5,7 @@ import agh.ics.oop.model.exceptions.IncorrectPositionException; import java.util.List; +import java.util.UUID; /** * The interface responsible for interacting with the map of the world. @@ -51,4 +52,9 @@ public interface WorldMap extends MoveValidator { * @return ArrayList of all objects on map */ List getElements(); + + /** + * @return MapID + */ + UUID getId(); } \ No newline at end of file diff --git a/oolab/src/main/java/agh/ics/oop/model/exceptions/IllegalMoveSpecificationException.java b/oolab/src/main/java/agh/ics/oop/model/exceptions/IllegalMoveSpecificationException.java new file mode 100644 index 0000000..370e8fc --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/model/exceptions/IllegalMoveSpecificationException.java @@ -0,0 +1,8 @@ +package agh.ics.oop.model.exceptions; + +public class IllegalMoveSpecificationException extends RuntimeException { + public static final String ILLEGAL_MOVE_SPEC_MESSAGE = "move specification is illegal"; + public IllegalMoveSpecificationException(String letter) { + super("%s: %s".formatted(letter, ILLEGAL_MOVE_SPEC_MESSAGE)); + } +} diff --git a/oolab/src/main/java/agh/ics/oop/presenter/SimulationPresenter.java b/oolab/src/main/java/agh/ics/oop/presenter/SimulationPresenter.java new file mode 100644 index 0000000..76098f1 --- /dev/null +++ b/oolab/src/main/java/agh/ics/oop/presenter/SimulationPresenter.java @@ -0,0 +1,145 @@ +package agh.ics.oop.presenter; + +import agh.ics.oop.OptionsParser; +import agh.ics.oop.Simulation; +import agh.ics.oop.model.*; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +import java.util.List; + +public class SimulationPresenter implements MapChangeListener { + private WorldMap worldMap; + + @FXML + private Label moveInfo; + + @FXML + private TextField textField; + + @FXML + private GridPane mapGrid; + + + private Vector2d lowerLeft; + private Vector2d upperRight; + + public void setWorldMap(WorldMap worldMap) { + this.worldMap = worldMap; + } + + + private int calculateColsCnt(){ + return 2 + upperRight.getX() - lowerLeft.getX(); + } + private int calculateRowsCnt(){ + return 2 + upperRight.getY() - lowerLeft.getY(); + } + + private void constructAxes(){ + Label cell = new Label("y/x"); + GridPane.setHalignment(cell, HPos.CENTER); // Wyrównanie poziome + GridPane.setValignment(cell, VPos.CENTER); // Wyrównanie pionowe + cell.setFont(Font.font("System", FontWeight.BOLD, 14)); + mapGrid.add(cell, 0, 0); + lowerLeft = ((AbstractWorldMap) worldMap).getLowerLeftBoundary(); + upperRight = ((AbstractWorldMap) worldMap).getUpperRightBoundary(); + int cols = calculateColsCnt(); + int rows = calculateRowsCnt(); + for (int i = 0; i < cols; i++) { + ColumnConstraints colConstraints = new ColumnConstraints(); + colConstraints.setPercentWidth(80.0 / cols); + mapGrid.getColumnConstraints().add(colConstraints); + } + + for (int i = 0; i < rows; i++) { + RowConstraints rowConstraints = new RowConstraints(); + rowConstraints.setPercentHeight(80.0 / rows); + mapGrid.getRowConstraints().add(rowConstraints); + } + + for (int i = rows - 1; i >= 1; i--) { + Label cellR = new Label("%d".formatted(lowerLeft.getY() + (rows - 1 - i))); + GridPane.setHalignment(cellR, HPos.CENTER); // Wyrównanie poziome + GridPane.setValignment(cellR, VPos.CENTER); // Wyrównanie pionowe + cellR.setFont(Font.font("System", FontWeight.BOLD, 14)); + mapGrid.add(cellR, 0, i); + } + for (int i = 1; i < cols; i++) { + Label cellC = new Label("%d".formatted(i + lowerLeft.getX())); + GridPane.setHalignment(cellC, HPos.CENTER); // Wyrównanie poziome + GridPane.setValignment(cellC, VPos.CENTER); // Wyrównanie pionowe + cellC.setFont(Font.font("System", FontWeight.BOLD, 14)); + mapGrid.add(cellC, i, 0); + } + } + + private void fillMapGrid(){ + int cols = calculateColsCnt(); + int rows = calculateRowsCnt(); + int minX = lowerLeft.getX(); + int minY = lowerLeft.getY(); + for (int i = minX; i < minX + cols - 1; i++){ + for (int j = minY; j < minY + rows - 1; j++){ + Vector2d pos = new Vector2d(i, j); + if (worldMap.isOccupied(pos)){ + Label cell = new Label(worldMap.objectAt(pos).toString()); + GridPane.setHalignment(cell, HPos.CENTER); // Wyrównanie poziome + GridPane.setValignment(cell, VPos.CENTER); // Wyrównanie pionowe + cell.setFont(Font.font("System", FontWeight.BOLD, 30)); + mapGrid.add(cell, i - minX + 1,(rows - 1) - (j - minY)); + } + } + } + } + + + @FXML + public void drawMap() { + clearGrid(); + constructAxes(); + fillMapGrid(); + mapGrid.setGridLinesVisible(true); + System.out.println(moveInfo.getText()); + moveInfo.setText(worldMap.toString()); + } + + @FXML + public void initializeSim() { + String directionsArgs = textField.getText(); + List directions = OptionsParser.parse(directionsArgs.split("\\s+")); + java.util.List positions = java.util.List.of(new Vector2d(2,2), new Vector2d(3,4)); + AbstractWorldMap grassF = new GrassField(10); + grassF.addObserver(this); +// this.setWorldMap(worldMap); + Simulation sim = new Simulation(positions, directions, grassF); + SimulationEngine simEngine = new SimulationEngine(List.of(sim)); + new Thread(simEngine::runSync).start(); + } + + private void clearGrid() { + mapGrid.getChildren().retainAll(mapGrid.getChildren().get(0)); // hack to retain visible grid lines + mapGrid.getColumnConstraints().clear(); + mapGrid.getRowConstraints().clear(); + } + + @Override + public void mapChanged(WorldMap map, String message) { + this.setWorldMap(map); + Platform.runLater(() -> { + drawMap(); + moveInfo.setText(message); + // ewentualny inny kod zmieniający kontrolki + }); + } +} + diff --git a/oolab/src/main/resources/simulation.fxml b/oolab/src/main/resources/simulation.fxml new file mode 100644 index 0000000..b38a813 --- /dev/null +++ b/oolab/src/main/resources/simulation.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + +
+ +
+ + + +