diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 64b372da1c3f8fc7e9e12173dd274a3d421db08c..987ad16f6ee2b0c829189e3d09d98611fc7583fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,49 +1,26 @@ -# This file is a template, and might need editing before it works on your project. -# This is a sample GitLab CI/CD configuration file that should run without any modifications. -# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, -# it uses echo commands to simulate the pipeline execution. -# -# A pipeline is composed of independent jobs that run scripts, grouped into stages. -# Stages run in sequential order, but jobs within stages run in parallel. -# -# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages -# -# You can copy and paste this template into a new `.gitlab-ci.yml` file. -# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword. -# -# To contribute improvements to CI/CD templates, please follow the Development guide at: -# https://docs.gitlab.com/ee/development/cicd/templates.html -# This specific template is located at: -# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml +image: maven:eclipse-temurin -stages: # List of stages for jobs, and their order of execution +stages: - build - test - - deploy + - package -build-job: # This job runs in the build stage, which runs first. +before_script: + - cd ChaosGame # Navigate to the ChaosGame directory + +build: stage: build script: - - echo "Compiling the code..." - - echo "Compile complete." + - mvn compile -unit-test-job: # This job runs in the test stage. - stage: test # It only starts when the job in the build stage completes successfully. +test: + stage: test script: - - echo "Running unit tests... This will take about 60 seconds." - - sleep 60 - - echo "Code coverage is 90%" + - mvn clean test -lint-test-job: # This job also runs in the test stage. - stage: test # It can run at the same time as unit-test-job (in parallel). +package: + stage: package script: - - echo "Linting code... This will take about 10 seconds." - - sleep 10 - - echo "No lint issues found." + - mvn clean package + -deploy-job: # This job runs in the deploy stage. - stage: deploy # It only runs when *both* jobs in the test stage complete successfully. - environment: production - script: - - echo "Deploying application..." - - echo "Application successfully deployed." diff --git a/ChaosGame/pom.xml b/ChaosGame/pom.xml index 99427ddd439c1256ad238550b04ce2bf9c48dcea..819aaee5f89f5c558a36aa210f0c9f3e629e42ff 100644 --- a/ChaosGame/pom.xml +++ b/ChaosGame/pom.xml @@ -26,6 +26,11 @@ <artifactId>javafx-controls</artifactId> <version>21.0.1</version> </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>21.0.1</version> + </dependency> </dependencies> <build> diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/App.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/App.java index 14f0a1f105ec695ab263ea6ecddde627d17f1e48..d09eea3af84cffba7537a333f4ff4be809d1b2e6 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/App.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/App.java @@ -1,55 +1,65 @@ package edu.ntnu.idatt2003.group6; -import edu.ntnu.idatt2003.group6.view.HomePage; +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; +import edu.ntnu.idatt2003.group6.controller.navigation.NavigationController; +import edu.ntnu.idatt2003.group6.models.navigation.NavigationModel; +import edu.ntnu.idatt2003.group6.view.frames.HomePage; +import java.util.Objects; import javafx.application.Application; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import javafx.stage.StageStyle; -import java.util.Objects; /** * Main class for the application. * - * @version 0.1.1 + * @version 0.3.2 * @since 0.1.1 */ public class App extends Application { - private HomePage homePage; - + /** + * The main method of the application. + * + * @param args The arguments to the main method. + */ public static void main(String[] args) { + launch(args); } + /** + * The start method of the application. + * + * @param primaryStage The primary stage of the application. + */ @Override public void start(Stage primaryStage) { primaryStage.setTitle("Chaos Game"); - primaryStage.setMaximized(true); - primaryStage.setMinWidth(800); + primaryStage.setMinWidth(950); primaryStage.setMinHeight(600); - //primaryStage.getIcons().add(new Image(Objects.requireNonNull( - // getClass().getResourceAsStream("/icon.png")))); - ; + primaryStage.setWidth(1200); + primaryStage.setHeight(800); + primaryStage.centerOnScreen(); + primaryStage.getIcons().add(new Image(Objects.requireNonNull( + ChaosGameController.class.getResourceAsStream("/Logo.png")))); - homePage = new HomePage(primaryStage); - homePage.getStylesheets().add(Objects.requireNonNull( - getClass().getResource("/globals.css")).toExternalForm()); + HomePage homePage = new HomePage(primaryStage); - homePage.setActionOnButton("playGame", event -> playGameButtonAction()); - homePage.setActionOnButton("files", event -> filesButtonAction()); + //Creates a new NavigationModel and NavigationController, + // these acts to change the view of the application. + NavigationModel navigationModel = new NavigationModel(); + NavigationController.getInstance(homePage, navigationModel); Scene scene = new Scene(homePage); primaryStage.setScene(scene); primaryStage.show(); - } - private void playGameButtonAction() { - } - private void filesButtonAction() { + //Adds the global stylesheet to the scene. + scene.getStylesheets().add( + Objects.requireNonNull(getClass().getResource("/stylesheets/globals.css")) + .toExternalForm()); } } + diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/ChaosGame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/ChaosGame.java deleted file mode 100644 index 67493408da72fd68abc3a63daa1b8b6fd8d0e14f..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/ChaosGame.java +++ /dev/null @@ -1,78 +0,0 @@ -package edu.ntnu.idatt2003.group6.controller; - -import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosCanvas; -import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; -import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameObserver; -import edu.ntnu.idatt2003.group6.models.vector.Vector2D; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * This class represents a chaos game. It contains a canvas for rendering the game, a description of - * the game, and methods for running the game. - * - * @version 0.2.3 - * @since 0.2.3 - */ -public class ChaosGame { - - private final List<ChaosGameObserver> observers; - private final ChaosCanvas canvas; - private final ChaosGameDescription description; - public Random random; - private Vector2D currentPoint; - - /** - * Constructor for the ChaosGame class. Creates a new chaos game with the given description and - * - * @param description The description of the chaos game. - * @param width The width of the canvas. - * @param height The height of the canvas. - */ - public ChaosGame(ChaosGameDescription description, int width, int height) { - this.canvas = new ChaosCanvas( - width, height, description.getMinCoords(), description.getMaxCoords()); - this.description = description; - this.currentPoint = new Vector2D(0, 0); - this.random = new Random(); - this.observers = new ArrayList<>(); - } - - /** - * Returns the canvas of the chaos game. - * - * @return The canvas of the chaos game. - */ - public ChaosCanvas getCanvas() { - return canvas; - } - - /** - * Returns the description of the chaos game. - * - * @param steps The number of steps to run the chaos game for. - */ - public void runSteps(int steps) { - for (int i = 0; i < steps; i++) { - int randomIndex = random.nextInt(description.getTransformations().size()); - currentPoint = description.getTransformations().get(randomIndex).transform(currentPoint); - canvas.putPixel(currentPoint); - } - } - - public void addObserver(ChaosGameObserver observer) { - observers.add(observer); - } - - public void removeObserver(ChaosGameObserver observer) { - observers.remove(observer); - } - - public void notifyObservers() { - for (ChaosGameObserver observer : observers) { - observer.update(this); - } - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGame.java new file mode 100644 index 0000000000000000000000000000000000000000..7c6a971f4745a5f1d868e20057829f1a12a7ba8e --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGame.java @@ -0,0 +1,165 @@ +package edu.ntnu.idatt2003.group6.controller.chaosgame; + +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosCanvas; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.utils.exceptions.IllegalChaosGameException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * This class represents a chaos game. It contains a canvas for rendering the game, a description of + * the game, and methods for running the game. + * + * @version 0.2.3 + * @since 0.2.3 + */ +public class ChaosGame { + public final Random random; + private final ChaosCanvas canvas; + private final ChaosGameDescription description; + private final List<ChaosGameObserver> chaosGameObservers; + private Vector2D currentPoint; + private GameState state; + + /** + * Constructor for the ChaosGame class. Creates a new chaos game with the given description and + * + * @param description The description of the chaos game. + * @param width The width of the canvas. + * @param height The height of the canvas. + * @throws IllegalChaosGameException If any of the parameters is invalid. + */ + public ChaosGame(ChaosGameDescription description, int width, int height) + throws IllegalChaosGameException { + try { + if (description == null) { + throw new IllegalArgumentException("Description cannot be null"); + } + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0"); + } + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0"); + } + this.canvas = new ChaosCanvas( + width, height, description.getMinCoords(), description.getMaxCoords()); + this.description = description; + this.currentPoint = new Vector2D(0, 0); + this.random = new Random(); + this.chaosGameObservers = new ArrayList<>(); + } catch (IllegalArgumentException e) { + throw new IllegalChaosGameException("Illegal chaos game description", e); + } + } + + /** + * Returns the state of the chaos game. + * + * @return The state of the chaos game. + */ + public GameState getState() { + return state; + } + + /** + * Sets the state of the chaos game. + * + * @param state The state of the chaos game. + */ + public void setState(GameState state) { + this.state = state; + notifyObservers(); + } + + /** + * Adds an observer to the chaos game. + * + * @param chaosGameObserver The observer to add. + * @throws IllegalArgumentException If the observer is null. + */ + public void addObserver(ChaosGameObserver chaosGameObserver) throws IllegalArgumentException { + validateObserver(chaosGameObserver); + chaosGameObservers.add(chaosGameObserver); + } + + /** + * Removes an observer from the chaos game. + * + * @param chaosGameObserver The observer to remove. + * @throws IllegalArgumentException If the observer is null. + */ + public void removeObserver(ChaosGameObserver chaosGameObserver) throws IllegalArgumentException { + validateObserver(chaosGameObserver); + chaosGameObservers.remove(chaosGameObserver); + } + + /** + * Validates the observer. + * + * @param chaosGameObserver The observer to validate. + */ + private void validateObserver(ChaosGameObserver chaosGameObserver) { + if (chaosGameObserver == null) { + throw new IllegalArgumentException("Observer cannot be null"); + } + } + + /** + * Notifies the observers of the chaos game changing state. + */ + private void notifyObservers() { + chaosGameObservers.forEach(chaosGameObserver -> chaosGameObserver.update(state)); + } + + /** + * Returns the canvas of the chaos game. + * + * @return The canvas of the chaos game. + */ + public ChaosCanvas getCanvas() { + return canvas; + } + + /** + * Returns the description of the chaos game. + * + * @param steps The number of steps to run the chaos game for. + * @throws IllegalArgumentException If the number of steps is less than 0. + */ + public void runSteps(int steps) throws IllegalArgumentException { + if (steps < 1) { + throw new IllegalArgumentException("Number of steps must be equal or greater than 1"); + } + for (int i = 0; i < steps; i++) { + setState(GameState.RUNNING); + notifyObservers(); + int randomIndex = random.nextInt(description.getTransformations().size()); + currentPoint = description.getTransformations().get(randomIndex).transform(currentPoint); + canvas.putPixel(currentPoint); + } + setState(GameState.DONE); + notifyObservers(); + } + + /** + * Returns the description of the chaos game. + * + * @return The description of the chaos game. + */ + public ChaosGameDescription getChaosGameDescription() { + return description; + } + + /** + * Returns the game type of the chaos game. + * + * @return The game type of the chaos game. + */ + public GameType getGameType() { + return description.getGameType(); + } + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameController.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameController.java new file mode 100644 index 0000000000000000000000000000000000000000..b9238ab236f5643ab05c32cd03d1efdf5bc9d711 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameController.java @@ -0,0 +1,322 @@ +package edu.ntnu.idatt2003.group6.controller.chaosgame; + + +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getAttributesAffineControlsList; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getAttributesJuliaControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getMaxCoordsControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getMinCoordsControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.setAttributesAffineControlsList; +import static edu.ntnu.idatt2003.group6.utils.Utils.inputStringToInt; + +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import edu.ntnu.idatt2003.group6.models.files.FileModel; +import edu.ntnu.idatt2003.group6.models.transformation.JuliaTransform; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.utils.ControllerUtils; +import edu.ntnu.idatt2003.group6.view.alert.AlertError; +import edu.ntnu.idatt2003.group6.view.alert.AlertInputFileName; +import edu.ntnu.idatt2003.group6.view.frames.HomePage; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineControlsView; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineTransformationControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.JuliaTransformationControls; +import java.util.List; +import java.util.Objects; +import java.util.logging.Logger; + + +/** + * The controller for the ChaosGame model. This class is responsible for running the chaos game and + * updating the view the result. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class ChaosGameController implements ChaosGameObserver { + private static final Logger LOGGER = Logger.getLogger(ChaosGameController.class.getName()); + private ChaosGame model; + private final HomePage view; + private GameType gameType; + private final AffineControlsView affineControlsView; + private final FileModel fileModel = FileModel.getInstance(); + + /** + * Creates a new ChaosGameController. + * + * @param model The model for the chaos game. + * @param view The view for the chaos game. + */ + public ChaosGameController(ChaosGame model, HomePage view) { + this.model = model; + this.view = view; + this.gameType = model.getGameType(); + this.affineControlsView = view.getAffineControlsView(); + model.addObserver(this); + + setAttributesView(); + setButtonActions(); + } + + /** + * Sets the actions for the buttons in the view. + */ + private void setButtonActions() { + view.getButtonBoxControls().getRunGameButton().setOnAction(e -> { + try { + this.runGame(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + view.getButtonBoxControls().getLoadParametersButton().setOnAction(e -> { + try { + this.loadNewParameter(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + view.getButtonBoxControls().getSaveButton().setOnAction(e -> { + try { + this.saveFile(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + affineControlsView.getAddTransformButton().setOnAction(e -> { + try { + this.addAffineTransformation(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + affineControlsView.getRemoveTransformButton().setOnAction(e -> { + try { + this.removeAffineTransformation(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + } + + /** + * Sets the attributes for the view. + */ + private void setAttributesView() { + try { + setAttributesCommon(); + if (gameType == GameType.JULIA) { + setJuliaAttributes(); + } else { + setAffineAttributes(); + } + } catch (Exception e) { + String message = e.getMessage().split("@")[0]; + String description = e.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + } + } + + /** + * Sets the common attributes in a chaos game for the view. + */ + private void setAttributesCommon() { + try { + ControllerUtils.setAttributesCommon(view.getAttributeControls(), + model.getChaosGameDescription()); + } catch (Exception e) { + LOGGER.warning("Error setting common attributes"); + throw new IllegalArgumentException("Error setting common attributes @" + e.getMessage()); + } + } + + /** + * Sets the attributes for the Julia transformation in the view. + */ + private void setJuliaAttributes() { + JuliaTransformationControls juliaView = view.getJuliaTransformationControls(); + + JuliaTransform julia = + (JuliaTransform) model.getChaosGameDescription().getTransformations().getFirst(); + juliaView.setImaginaryC(String.valueOf(julia.getPoint().getX1())); + juliaView.setRealC(String.valueOf(julia.getPoint().getX0())); + + view.setJuliaTransformationControls(juliaView); + } + + /** + * Sets the attributes for the affine transformations in the view. + * + * @throws IllegalArgumentException If an error occurs while setting the affine attributes. + */ + private void setAffineAttributes() throws IllegalArgumentException { + try { + List<AffineTransformationControls> affineControls = setAttributesAffineControlsList( + model.getChaosGameDescription()); + affineControlsView.setAffineControlsList(affineControls); + } catch (Exception e) { + LOGGER.warning("Error setting affine attributes"); + throw new IllegalArgumentException("Error setting affine attributes @" + e.getMessage()); + } + } + + /** + * Adds an affine transformation to the view. + */ + private void addAffineTransformation() { + try { + List<AffineTransformationControls> affineTransformList = + affineControlsView.getAffineControlsList(); + affineTransformList.add(new AffineTransformationControls()); + affineControlsView.setAffineControlsList(affineTransformList); + } catch (Exception e) { + LOGGER.warning("Error adding affine transformation"); + throw new IllegalArgumentException("Error adding affine transformation @" + e.getMessage()); + } + } + + /** + * Removes an affine transformation from the view. + */ + private void removeAffineTransformation() { + try { + List<AffineTransformationControls> affineTransformList = + affineControlsView.getAffineControlsList(); + if (affineTransformList.size() > 1) { + affineTransformList.removeLast(); + } + affineControlsView.setAffineControlsList(affineTransformList); + } catch (Exception e) { + LOGGER.warning("Error removing affine transformation"); + throw new IllegalArgumentException("Error removing affine transformation @" + e.getMessage()); + } + } + + /** + * runs the chaos game. + */ + private void runGame() { + try { + model.getCanvas().clear(); + int steps = inputStringToInt(view.getAttributeControls().getStepsField()); + model.runSteps(steps); + } catch (Exception e) { + LOGGER.warning("Error running game"); + throw new IllegalArgumentException("Error running game @" + e.getMessage()); + } + } + + /** + * Saves the game parameters to a file. + */ + private void loadNewParameter() throws IllegalArgumentException { + try { + Vector2D minCoords = getMinCoordsControls(view.getAttributeControls()); + Vector2D maxCoords = getMaxCoordsControls(view.getAttributeControls()); + if (gameType == GameType.JULIA) { + loadJulia(minCoords, maxCoords); + } else { + loadAffine(minCoords, maxCoords); + } + } catch (Exception e) { + LOGGER.warning("Error loading new parameters"); + throw new IllegalArgumentException("Error loading new parameters @" + e.getMessage()); + } + } + + /** + * Loads a Julia transformation. + * + * @param minCoords the minimum coordinates for the transformation. + * @param maxCoords the maximum coordinates for the transformation. + * @throws IllegalArgumentException If an error occurs while loading the Julia transformation. + */ + private void loadJulia(Vector2D minCoords, Vector2D maxCoords) throws IllegalArgumentException { + model = new ChaosGame(getAttributesJuliaControls( + view.getJuliaTransformationControls(), minCoords, maxCoords), + 1000, 1000); + model.addObserver(this); + this.gameType = model.getGameType(); + setAttributesView(); + } + + /** + * Loads an affine transformation. + * + * @param minCoords the minimum coordinates for the transformation. + * @param maxCoords the maximum coordinates for the transformation. + * @throws IllegalArgumentException If an error occurs while loading the affine transformation. + */ + private void loadAffine(Vector2D minCoords, Vector2D maxCoords) throws IllegalArgumentException { + List<AffineTransformationControls> affineControls = affineControlsView.getAffineControlsList(); + model = new ChaosGame(getAttributesAffineControlsList( + affineControls, minCoords, maxCoords), + 1000, 1000); + model.addObserver(this); + + setAttributesView(); + } + + /** + * Saves the game parameters to a file. + * + * @throws IllegalArgumentException If an error occurs while saving the file. + */ + private void saveFile() throws IllegalArgumentException { + AlertInputFileName alert = new AlertInputFileName(); + alert.showAlert(); + String filename = alert.getFileName(); + + //Todo: Add try catch + if (filename == null || filename.isEmpty()) { + throw new IllegalArgumentException("Save to file did not execute @" + + " Filename cannot be empty"); + } else { + try { + fileModel.addFile(filename, model.getChaosGameDescription()); + } catch (Exception e) { + LOGGER.warning("Error saving file"); + throw new IllegalArgumentException("Error saving file @" + e.getMessage()); + } + } + } + + + /** + * Updates the view with the transformation picture. + */ + private void updateView() { + view.setTransformationPicture(model); + } + + /** + * Updates the controller based on the game state. + * + * @param state The state of the game. + */ + @Override + public void update(GameState state) { + if (Objects.requireNonNull(state) == GameState.DONE) { + updateView(); + } + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameObserver.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..f25f2656930422531945e22e041881ec3631c151 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/ChaosGameObserver.java @@ -0,0 +1,12 @@ +package edu.ntnu.idatt2003.group6.controller.chaosgame; + +/** + * An interface for observing the state of the chaos game. + * The observer will be notified when the state of the game changes. + * + * @version 0.3.1 + * @since 0.3.1 + */ +public interface ChaosGameObserver { + void update(GameState state); +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/GameState.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/GameState.java new file mode 100644 index 0000000000000000000000000000000000000000..b9e1f9102d06657ce9e56b09ac4968f08e863ebc --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/chaosgame/GameState.java @@ -0,0 +1,13 @@ +package edu.ntnu.idatt2003.group6.controller.chaosgame; + +/** + * An enum representing the state of the game. + * Gives a reference to the current state of the game to decide further actions. + * + * @version 3.2 + * @since 3.2 + */ +public enum GameState { + RUNNING, + DONE +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileController.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileController.java new file mode 100644 index 0000000000000000000000000000000000000000..87bdb079e337bb699d84154477edf24acace5661 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileController.java @@ -0,0 +1,400 @@ +package edu.ntnu.idatt2003.group6.controller.file; + +import static edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory.createChaosGameDescription; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getAttributesAffineControlsList; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getAttributesJuliaControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getMaxCoordsControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.getMinCoordsControls; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.setAttributesAffineControlsList; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.setAttributesCommon; +import static edu.ntnu.idatt2003.group6.utils.ControllerUtils.setAttributesJulia; + +import edu.ntnu.idatt2003.group6.controller.navigation.NavigationController; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import edu.ntnu.idatt2003.group6.models.files.FileModel; +import edu.ntnu.idatt2003.group6.models.files.FileObserver; +import edu.ntnu.idatt2003.group6.models.files.FileState; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.view.alert.AlertError; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxFiles; +import edu.ntnu.idatt2003.group6.view.buttonbox.CustomToggleButton; +import edu.ntnu.idatt2003.group6.view.frames.EditFileFrame; +import edu.ntnu.idatt2003.group6.view.frames.HomePage; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineControlsView; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineTransformationControls; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.logging.Logger; + +/** + * FileController is a class that controls the files in the application. + * It is used to create, edit and delete files. + * It is also used to save and load files. + * It is a singleton class. + * It implements the FileObserver interface. + * It has a list of FileControllerObservers. + * + * @version 0.3.2 + * @see FileObserver + * @see FileControllerObserver + * @since 0.3.2 + */ +public class FileController implements FileObserver { + + private static final Logger LOGGER = Logger.getLogger(FileController.class.getName()); + private static FileController instance = null; + private final ButtonBoxFiles buttonBoxFiles; + private final FileModel files; + private final NavigationController controller; + private final EditFileFrame editFileFrame; + private final AffineControlsView affineControlsView; + private final List<FileControllerObserver> observers = new ArrayList<>(); + private ChaosGameDescription chaosGameDescription; + private Vector2D minCoords; + private Vector2D maxCoords; + private List<CustomToggleButton> fileButtons; + + + /** + * Constructor for FileController. + * + * @param view HomePage of the application + * @param controller NavigationController of the application + */ + private FileController(HomePage view, NavigationController controller) { + this.files = FileModel.getInstance(); + this.buttonBoxFiles = view.getButtonBoxFiles(); + this.controller = controller; + editFileFrame = view.getEditFileFrame(); + affineControlsView = editFileFrame.getAffineControlsView(); + + files.addObserver(this); + + buttonsController(); + showEditFileView(); + deleteFile(); + updateButtons(); + } + + /** + * Returns an instance of the FileController class. + * If the instance is null, it creates a new instance. + * Following the singleton design pattern. + * + * @param view HomePage of the application + * @param controller NavigationController of the application + * @return an instance of the FileController class + */ + public static FileController getInstance(HomePage view, NavigationController controller) { + if (instance == null) { + instance = new FileController(view, controller); + } + return instance; + } + + /** + * Adds an observer to the list of observers. + * + * @param observer the observer to add + */ + public void addObserver(FileControllerObserver observer) { + observers.add(observer); + } + + /** + * Notifies all observers in the list of observers. + */ + public void notifyObservers() { + for (FileControllerObserver observer : observers) { + observer.update(); + } + } + + /** + * Sets the actions of the buttons in the FileController. + */ + private void buttonsController() { + affineControlsView.getAddTransformButton().setOnAction(e -> { + try { + addTransformAffine(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + + affineControlsView.getRemoveTransformButton().setOnAction(e -> { + try { + removeTransformAffine(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + + editFileFrame.getSaveButton().setOnAction(e -> { + try { + updateFileAttributes(); + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + + editFileFrame.getJuliaTransformButton().setOnAction(e -> { + try { + chaosGameDescription = createChaosGameDescription(GameType.JULIA_NEW); + showFileAttributes(); + } catch (IllegalArgumentException ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + + editFileFrame.getAffineTransformButton().setOnAction(e -> { + try { + chaosGameDescription = createChaosGameDescription(GameType.AFFINE_NEW); + showFileAttributes(); + } catch (IllegalArgumentException ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + + buttonBoxFiles.getNewFileButton().setOnAction(e -> { + editFileFrame.clearFields(); + editFileFrame.setFileNameField("New file"); + buttonBoxFiles.getToggleButtons().selectToggle(null); + controller.goToEditFile(); + }); + } + + /** + * Adds a new affine transformation to the list of affine transformations. + */ + private void addTransformAffine() { + List<AffineTransformationControls> affineTransformationControlsList = + affineControlsView.getAffineControlsList(); + AffineTransformationControls affineView = new AffineTransformationControls(); + affineView.setTransformationNumber(String.valueOf(affineTransformationControlsList.size() + 1)); + try { + affineTransformationControlsList.add(affineView); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Failed to add transformation @" + e.getMessage()); + } + affineControlsView.setAffineControlsList(affineTransformationControlsList); + } + + /** + * Removes the last affine transformation from the list of affine transformations. + */ + private void removeTransformAffine() { + List<AffineTransformationControls> affineTransformationControlsList = + affineControlsView.getAffineControlsList(); + try { + affineTransformationControlsList.removeLast(); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Failed to remove transformation @" + e.getMessage()); + } + affineControlsView.setAffineControlsList(affineTransformationControlsList); + } + + /** + * Shows the edit file view. + */ + private void showEditFileView() { + buttonBoxFiles.getEditFileButton().setOnAction(e -> { + try { + for (CustomToggleButton fileButton : fileButtons) { + if (fileButton.isSelected()) { + controller.goToEditFile(); + chaosGameDescription = files.getFile(fileButton.getText()); + editFileFrame.setFileNameField(fileButton.getText()); + showFileAttributes(); + break; + } + } + } catch (IllegalArgumentException ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + } + + /** + * Deletes a file from the list of files depending on the selected file. + */ + private void deleteFile() { + buttonBoxFiles.getDeleteFileButton().setOnAction(e -> { + try { + for (CustomToggleButton fileButton : fileButtons) { + if (fileButton.isSelected()) { + files.removeFile(fileButton.getText()); + + break; + } + } + } catch (IllegalArgumentException ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + }); + } + + /** + * Sets the attributes of the file. + * + * @throws IllegalArgumentException if the file does not contain the correct attributes. + */ + private void showFileAttributes() throws IllegalArgumentException { + //Sets min and max coords + try { + setAttributesCommon(editFileFrame.getAttributeControls(), chaosGameDescription); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Failed to set file attributes @" + e.getMessage()); + } + editFileFrame.showFileAttributes(); + //Checks game type + try { + if (chaosGameDescription.getGameType().equals(GameType.JULIA)) { + showJuliaAttributes(); + } else if (chaosGameDescription.getGameType().equals(GameType.AFFINE)) { + showAffineAttributes(); + } + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Failed to show file attributes @" + e.getMessage()); + } + } + + /** + * Sets the attributes of the Julia transformation. + * + * @throws IllegalArgumentException if the file does not contain the correct attributes. + */ + private void showJuliaAttributes() throws IllegalArgumentException { + editFileFrame.setJuliaTransformControls( + setAttributesJulia(chaosGameDescription)); + editFileFrame.showJuliaAttributes(); + } + + /** + * Sets the attributes of the affine transformations. + * + * @throws IllegalArgumentException if the file does not contain the correct attributes. + */ + private void showAffineAttributes() throws IllegalArgumentException { + List<AffineTransformationControls> affineControls = setAttributesAffineControlsList( + chaosGameDescription); + + affineControlsView.setAffineControlsList(affineControls); + editFileFrame.showAffineAttributes(); + } + + + /** + * Updates the file attributes. + * + * @throws IllegalArgumentException if the file does not contain the correct attributes. + */ + private void updateFileAttributes() throws IllegalArgumentException { + try { + minCoords = getMinCoordsControls(editFileFrame.getAttributeControls()); + maxCoords = getMaxCoordsControls(editFileFrame.getAttributeControls()); + + if (chaosGameDescription.getGameType().equals(GameType.JULIA)) { + updateJuliaAttributes(); + } else { + updateAffineAttributes(); + } + + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Failed to save file attributes, " + + "File did not contain the correct attributes. @" + e.getMessage()); + } + } + + /** + * Updates the Julia attributes. + * + * @throws IllegalArgumentException if the view field does not contain the correct attributes. + */ + private void updateJuliaAttributes() throws IllegalArgumentException { + chaosGameDescription = getAttributesJuliaControls( + editFileFrame.getJuliaTransformationControls(), minCoords, maxCoords); + saveFile(); + } + + /** + * Updates the affine attributes. + * + * @throws IllegalArgumentException if the view field does not contain the correct attributes. + */ + private void updateAffineAttributes() throws IllegalArgumentException { + chaosGameDescription = getAttributesAffineControlsList( + editFileFrame.getAffineTransformControls(), minCoords, maxCoords); + saveFile(); + } + + /** + * Saves the file. + * + * @throws IllegalArgumentException if the file does not contain the correct attributes. + */ + private void saveFile() throws IllegalArgumentException { + String filename = editFileFrame.getFileNameField(); + files.addFile(filename, chaosGameDescription); + } + + + /** + * Updates the buttons in the file controller. + */ + private void updateButtons() { + buttonBoxFiles.clearFileButtons(); + List<String> file = files.getFiles(); + file.forEach(buttonBoxFiles::addFileButton); + fileButtons = buttonBoxFiles.getFileButtonsToggle(); + } + + + /** + * Updates the file buttons and notifies the observers. + * + * @param state the state of the file. + */ + @Override + public void update(FileState state) { + if (Objects.requireNonNull(state) == FileState.FILES_CHANGED) { + updateButtons(); + notifyObservers(); + } + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileControllerObserver.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileControllerObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..53f0b34cc44b9c979cd8fe74d6828235e500d026 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/file/FileControllerObserver.java @@ -0,0 +1,8 @@ +package edu.ntnu.idatt2003.group6.controller.file; + +/** + * Interface for the FileControllerObserver class. + */ +public interface FileControllerObserver { + void update(); +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/NavigationController.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/NavigationController.java new file mode 100644 index 0000000000000000000000000000000000000000..6ab0693dd781bf27212e71d1096c0c0e7bf27c2d --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/NavigationController.java @@ -0,0 +1,214 @@ +package edu.ntnu.idatt2003.group6.controller.navigation; + +import static edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory.createChaosGameDescription; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; +import edu.ntnu.idatt2003.group6.controller.file.FileController; +import edu.ntnu.idatt2003.group6.controller.file.FileControllerObserver; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import edu.ntnu.idatt2003.group6.models.files.FileModel; +import edu.ntnu.idatt2003.group6.models.navigation.NavigationModel; +import edu.ntnu.idatt2003.group6.models.navigation.NavigationState; +import edu.ntnu.idatt2003.group6.view.alert.AlertError; +import edu.ntnu.idatt2003.group6.view.frames.HomePage; + +/** + * The NavigationController class controls the navigation of the application. + * It changes the view of the application based on the NavigationState. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class NavigationController implements FileControllerObserver { + + private static NavigationController instance = null; + private final HomePage view; + private final NavigationModel model; + @SuppressWarnings("unused") + private ChaosGameController gameController; + private final FileModel fileModel = FileModel.getInstance(); + + /** + * Constructor for NavigationController. A singleton class. + * Create a new NavigationController with a reference to the HomePage and NavigationModel. + * + * @param view The HomePage to bind the navigation to. + * @param model The NavigationModel to bind the navigation to. + */ + private NavigationController(HomePage view, NavigationModel model) { + this.view = view; + this.model = model; + model.addObserver(view); + + //Buttons for each of the files are created by the FileController by observing the FileModel + FileController fileController = FileController.getInstance(view, this); + fileController.addObserver(this); + + SettingsController.getInstance(view); + + //Buttons to go to different views from the menu + view.getButtonBoxMenu().getPlayGameButton().setOnAction(e -> this.goToSelectGame()); + view.getButtonBoxMenu().getFilesButton().setOnAction(e -> this.goToFiles()); + view.getButtonBoxMenu().getSettingsButton().setOnAction(e -> this.goToSettings()); + view.getButtonBoxMenu().getQuitButton().setOnAction(e -> System.exit(0)); + + + //Buttons to go to the menu + view.getButtonBoxControls().getBackToMenuButton().setOnAction(e -> this.goToMenu()); + view.getButtonBoxFiles().getBackToMenuButton().setOnAction(e -> this.goToMenu()); + view.getButtonBoxSettings().getBackToMenuButton().setOnAction(e -> this.goToMenu()); + view.getButtonBoxGames().getBackToMenuButton().setOnAction(e -> this.goToMenu()); + + //Select which game to play + selectGame(); + } + + /** + * Returns the instance of the NavigationController. + * If the instance is null, a new instance is created. + * + * @param view The HomePage to bind the navigation to. + * @param model The NavigationModel to bind the navigation to. + * @return The instance of the NavigationController. + */ + public static NavigationController getInstance(HomePage view, NavigationModel model) { + if (instance == null) { + instance = new NavigationController(view, model); + } + return instance; + } + + /** + * Starts the Affine game with the given ChaosGame. + * + * @param game The ChaosGame to start. + */ + private void startGameAffine(ChaosGame game) { + gameController = new ChaosGameController(game, view); + goToPlayAffine(); + } + + /** + * Starts the Julia game with the given ChaosGame. + * + * @param game The ChaosGame to start. + */ + private void startGameJulia(ChaosGame game) { + gameController = new ChaosGameController(game, view); + goToPlayJulia(); + } + + /** + * Selects which game to play. + */ + private void selectGame() { + //Empty Affine game + view.getButtonBoxGames().getNewAffineButtonButton().setOnAction(e -> this.startGameAffine( + new ChaosGame(createChaosGameDescription(GameType.AFFINE_NEW), 700, 700))); + //Empty Julia game + view.getButtonBoxGames().getNewJuliaButtonButton().setOnAction(e -> this.startGameJulia( + new ChaosGame(createChaosGameDescription(GameType.JULIA_NEW), 700, 700))); + //Sierpinski game + view.getButtonBoxGames().getSierpinskiButton().setOnAction(e -> this.startGameAffine( + new ChaosGame(createChaosGameDescription(GameType.SIERPINSKI), 700, 700))); + //Julia game + view.getButtonBoxGames().getJuliaButton().setOnAction(e -> this.startGameJulia( + new ChaosGame(createChaosGameDescription(GameType.JULIA), 700, 700))); + //Barnsley game + view.getButtonBoxGames().getBarnsleyButton().setOnAction(e -> this.startGameAffine( + new ChaosGame(createChaosGameDescription(GameType.BARNSLEY), 700, 700))); + //Show games from file + view.getButtonBoxGames().getFileButton().setOnAction(e -> this.goToSelectGameFiles()); + + selectGameFiles(); + } + + /** + * Selects the game files to play. + */ + public void selectGameFiles() { + //Gets the buttons for each file made by the FileController + view.getButtonBoxFiles().getFileButtons().forEach(button -> button.setOnAction(e -> { + try { + ChaosGameDescription game = fileModel.getFile(button.getText()); + if (game.getGameType().equals(GameType.JULIA)) { + startGameJulia(new ChaosGame(game, 700, 700)); + } else { + startGameAffine(new ChaosGame(game, 700, 700)); + } + } catch (Exception ex) { + String message = ex.getMessage().split("@")[0]; + String description = ex.getMessage().split("@")[1]; + AlertError alertError = new AlertError(message, description); + alertError.showAlertError(); + e.consume(); + } + })); + } + + /** + * Selects the game files to show. + */ + private void goToFiles() { + model.setState(NavigationState.SELECT_FILE); + } + + /** + * Allows for editing a file. + */ + public void goToEditFile() { + model.setState(NavigationState.EDIT_FILE); + } + + /** + * Allows for selecting a game to play. + */ + private void goToSelectGame() { + model.setState(NavigationState.SELECT_GAME); + } + + /** + * Allows for selecting a game file to play. + */ + private void goToSelectGameFiles() { + model.setState(NavigationState.SELECT_GAME_FILES); + } + + /** + * Allows for playing the Julia game. + */ + private void goToPlayJulia() { + model.setState(NavigationState.PLAY_JULIA); + } + + /** + * Allows for playing the Affine game. + */ + private void goToPlayAffine() { + model.setState(NavigationState.PLAY_AFFINE); + } + + /** + * Allows for going to the settings. + */ + private void goToSettings() { + model.setState(NavigationState.SETTINGS); + } + + /** + * Allows for going to the menu. + */ + private void goToMenu() { + model.setState(NavigationState.MENU); + } + + /** + * Updates the view to show the files in the file directory. With the buttons for each file. + */ + @Override + public void update() { + selectGameFiles(); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/SettingsController.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/SettingsController.java new file mode 100644 index 0000000000000000000000000000000000000000..6d2d37aac2a8346268332575b0292f551ec863e2 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/controller/navigation/SettingsController.java @@ -0,0 +1,77 @@ +package edu.ntnu.idatt2003.group6.controller.navigation; + +import edu.ntnu.idatt2003.group6.models.files.FilePath; +import edu.ntnu.idatt2003.group6.view.frames.HomePage; +import edu.ntnu.idatt2003.group6.view.frames.SettingsFrame; +import java.io.File; +import javafx.scene.control.ToggleGroup; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; + +/** + * The SettingsController class controls the settings of the application. + * Mainly the music settings. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class SettingsController { + private static SettingsController instance = null; + private final ToggleGroup musicOption; + private final SettingsFrame settingsFrame; + private final MediaPlayer mediaPlayer; + + /** + * Constructor for SettingsController. + * Creates a new SettingsController with a reference to the HomePage. + * + * @param homePage The HomePage to bind the settings to. + */ + private SettingsController(HomePage homePage) { + this.settingsFrame = homePage.getSettingsFrame(); + this.musicOption = settingsFrame.getMusicOption(); + + Media media = + new Media(new File( + FilePath.MUSIC.getPath() + "Legio_Symphonica.mp3").toURI().toString()); + mediaPlayer = new MediaPlayer(media); + mediaPlayer.setCycleCount(MediaPlayer.INDEFINITE); + + musicOption.selectToggle(settingsFrame.getMusicOff()); + buttonsController(); + } + + /** + * Returns the instance of the SettingsController. + * If the instance is null, a new instance is created. + * + * @param homepage The HomePage to bind the settings to. + * @return The instance of the SettingsController. + */ + public static SettingsController getInstance(HomePage homepage) { + if (instance == null) { + instance = new SettingsController(homepage); + } + return instance; + } + + /** + * Controls the music settings. + * If the music is turned off, the mediaPlayer is stopped. + * If the music is turned on, the mediaPlayer is played. + * The volume of the mediaPlayer is set to the value of the volumeSlider. + */ + private void buttonsController() { + musicOption.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { + if (musicOption.getSelectedToggle() == settingsFrame.getMusicOn()) { + mediaPlayer.play(); + mediaPlayer.setVolume(settingsFrame.getVolumeSlider().getValue()); + } else if (musicOption.getSelectedToggle() == settingsFrame.getMusicOff()) { + mediaPlayer.stop(); + } + }); + + settingsFrame.getVolumeSlider().valueProperty().addListener((observable, oldValue, newValue) -> + mediaPlayer.setVolume(settingsFrame.getVolumeSlider().getValue())); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvas.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvas.java index 7cfe1d9f3933903298208fd4a20767053e55413f..7d8e53418040f3d9dce041cfbc4d821a0074528c 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvas.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvas.java @@ -27,8 +27,13 @@ public class ChaosCanvas { * @param height The height of the canvas. * @param minCoords The minimum coordinates of the canvas. * @param maxCoords The maximum coordinates of the canvas. + * @throws IllegalArgumentException If the width or height is less than or equal to 0. */ - public ChaosCanvas(int width, int height, Vector2D minCoords, Vector2D maxCoords) { + public ChaosCanvas(int width, int height, Vector2D minCoords, Vector2D maxCoords) + throws IllegalArgumentException { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Width and height must be positive."); + } this.width = width; this.height = height; this.minCoords = minCoords; @@ -45,10 +50,8 @@ public class ChaosCanvas { * @return The pixel value at the given point. */ public int getPixel(Vector2D point) { - Vector2D transformedPoint = this.transformCoordsToIndices.transform(point); - int x = (int) Math.round(transformedPoint.getX0()); - int y = (int) Math.round(transformedPoint.getX1()); - return this.canvas[x][y]; + Vector2D transformedPoint = transformAndCheckPoint(point); + return this.canvas[(int) transformedPoint.getX0()][(int) transformedPoint.getX1()]; } /** @@ -57,10 +60,26 @@ public class ChaosCanvas { * @param point The point to set the pixel value at. */ public void putPixel(Vector2D point) { + Vector2D transformedPoint = transformAndCheckPoint(point); + this.canvas[(int) transformedPoint.getX0()][(int) transformedPoint.getX1()]++; + } + + /** + * Sets the pixel value at the given point. + * + * @param point The point to get or set the pixel value at. + * @return The pixel value at the given point. + * @throws IllegalArgumentException If the point is out of bounds. + */ + private Vector2D transformAndCheckPoint(Vector2D point) throws IllegalArgumentException { Vector2D transformedPoint = this.transformCoordsToIndices.transform(point); int x = (int) Math.round(transformedPoint.getX0()); int y = (int) Math.round(transformedPoint.getX1()); - this.canvas[x][y] = 1; // or some other value depending on your fractal rendering logic + + if (x < 0 || x >= width || y < 0 || y >= height) { + throw new IllegalArgumentException("Point is out of bounds."); + } + return transformedPoint; } /** @@ -76,10 +95,9 @@ public class ChaosCanvas { * Clears the canvas by setting all pixels to 0. */ public void clear() { - //Using for loop to set all pixels to 0. Since the canvas is a 2D array. for (int i = 0; i < this.width; i++) { for (int j = 0; j < this.height; j++) { - this.canvas[i][j] = 0; + this.canvas[j][i] = 0; } } } @@ -102,7 +120,7 @@ public class ChaosCanvas { */ private Vector2D getVectorForIndices() { return new Vector2D( - ((height - 1) * maxCoords.getX1()) / (maxCoords.getX1() - minCoords.getX1()), - ((width - 1) * minCoords.getX0()) / (minCoords.getX0() - maxCoords.getX0())); + ((height - 1) * maxCoords.getX1()) / (maxCoords.getX1() - minCoords.getX1()), + ((width - 1) * minCoords.getX0()) / (minCoords.getX0() - maxCoords.getX0())); } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescription.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescription.java index 2efe27ece08282ba823b91e231deedcb89360af5..5f1673411e8f3014fc3262c87e4952c72b61a539 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescription.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescription.java @@ -2,6 +2,7 @@ package edu.ntnu.idatt2003.group6.models.chaosgame; import edu.ntnu.idatt2003.group6.models.transformation.Transform2D; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.utils.exceptions.IllegalChaosGameException; import java.util.List; /** @@ -12,6 +13,7 @@ import java.util.List; * @since 0.2.1 */ public class ChaosGameDescription { + private final GameType gameType; private Vector2D minCoords; private Vector2D maxCoords; private List<Transform2D> transformations; @@ -22,12 +24,19 @@ public class ChaosGameDescription { * @param transforms A list of transformations * @param minCoords start coordinates * @param maxCoords stop coordinates + * @throws IllegalChaosGameException if the transformations, minCoords or maxCoords are null */ public ChaosGameDescription(List<Transform2D> transforms, - Vector2D minCoords, Vector2D maxCoords) { + Vector2D minCoords, Vector2D maxCoords) + throws IllegalChaosGameException { + if (transforms == null || minCoords == null || maxCoords == null) { + throw new IllegalChaosGameException( + "Transformations, minCoords or maxCoords cannot be null."); + } this.minCoords = minCoords; this.maxCoords = maxCoords; this.transformations = transforms; + this.gameType = transformations.getFirst().gameType(); } /** @@ -83,4 +92,8 @@ public class ChaosGameDescription { public void setMaxCoords(Vector2D maxCoords) { this.maxCoords = maxCoords; } + + public GameType getGameType() { + return gameType; + } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactory.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactory.java index a1d4d0b0d335e3afdefc7143618a3322ef0ab16f..3b14cf7cf46ae47657b97ed9b8102395e9ad2741 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactory.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactory.java @@ -5,7 +5,6 @@ import edu.ntnu.idatt2003.group6.models.transformation.AffineTransform2D; import edu.ntnu.idatt2003.group6.models.transformation.JuliaTransform; import edu.ntnu.idatt2003.group6.models.vector.Complex; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; - import java.util.List; /** @@ -17,34 +16,46 @@ import java.util.List; public class ChaosGameDescriptionFactory { /** - * Creates a standard ChaosGameDescription based on the game type you want. + * Creates a standard ChaosGameDescription based on the game type provided. * * @param gameType The type of chaos game to create * @return A ChaosGameDescription of the type you want + * @throws IllegalArgumentException If the game type is null or invalid */ - public static ChaosGameDescription createChaosGameDescription(String gameType) { - - return switch (gameType) { - case "Sierpinski" -> new ChaosGameDescription(List.of( - new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0)), - new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5)), - new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0)) + public static ChaosGameDescription createChaosGameDescription(GameType gameType) + throws IllegalArgumentException { + try { + return switch (gameType) { + case SIERPINSKI -> new ChaosGameDescription(List.of( + new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0)), + new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5)), + new AffineTransform2D(new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0)) ), new Vector2D(0, 0), new Vector2D(1, 1)); - case "Barnsley" -> new ChaosGameDescription(List.of( - new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), - new AffineTransform2D(new Matrix2x2(0.85, 0.04, -0.04, 0.85), new Vector2D(0, 1.6)), - new AffineTransform2D(new Matrix2x2(0.2, -0.26, 0.23, 0.22), new Vector2D(0, 1.6)), - new AffineTransform2D(new Matrix2x2(-0.15, 0.28, 0.26, 0.24), new Vector2D(0, 0.44)) + case BARNSLEY -> new ChaosGameDescription(List.of( + new AffineTransform2D(new Matrix2x2(0, 0, 0, 0.16), new Vector2D(0, 0)), + new AffineTransform2D(new Matrix2x2(0.85, 0.04, -0.04, 0.85), new Vector2D(0, 1.6)), + new AffineTransform2D(new Matrix2x2(0.2, -0.26, 0.23, 0.22), new Vector2D(0, 1.6)), + new AffineTransform2D(new Matrix2x2(-0.15, 0.28, 0.26, 0.24), new Vector2D(0, 0.44)) ), new Vector2D(-2.65, 0), new Vector2D(2.65, 10)); - case "Julia" -> new ChaosGameDescription(List.of( - new JuliaTransform(new Complex(-0.74543, 0.11301), -1), - new JuliaTransform(new Complex(-0.74543, 0.11301), 1) + case JULIA -> new ChaosGameDescription(List.of( + new JuliaTransform(new Complex(-0.74543, 0.11301), 1), + new JuliaTransform(new Complex(-0.74543, 0.11301), -1) ), new Vector2D(-1.6, -1), new Vector2D(1.6, 1)); - // TODO Add custom exception - default -> null; - }; + case JULIA_NEW -> new ChaosGameDescription(List.of( + new JuliaTransform(new Complex(0, 0), -1), + new JuliaTransform(new Complex(0, 0), 1) + ), new Vector2D(0, 0), new Vector2D(0, 0)); + + case AFFINE_NEW -> new ChaosGameDescription(List.of( + new AffineTransform2D(new Matrix2x2(0, 0, 0, 0), new Vector2D(0, 0)) + ), new Vector2D(0, 0), new Vector2D(0, 0)); + default -> null; + }; + } catch (NullPointerException e) { + throw new IllegalArgumentException("Game type cannot be null. @" + e.getMessage(), e); + } } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameObserver.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameObserver.java deleted file mode 100644 index 599da52c6cb11536f324e72ef3d65861f525f15e..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameObserver.java +++ /dev/null @@ -1,8 +0,0 @@ -package edu.ntnu.idatt2003.group6.models.chaosgame; - -import edu.ntnu.idatt2003.group6.controller.ChaosGame; - -public interface ChaosGameObserver { - - void update(ChaosGame chaosGame); -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameType.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameType.java new file mode 100644 index 0000000000000000000000000000000000000000..ca3dd3c5cd6b31f33efe08038a28a50783c9cb38 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameType.java @@ -0,0 +1,29 @@ +package edu.ntnu.idatt2003.group6.models.chaosgame; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; + +/** + * Enum for the different types of games that can be played. + * Used to determine certain actions that are different for each game type. + * Mainly used in the ChaosGameDescriptionFactory class. + * Or when the game type is needed in the ChaosGame class. + * The main GameType is AFFINE and JULIA. + * + * @version 0.3.2 + * @since 0.3.2 + * @see ChaosGameDescriptionFactory + * @see ChaosGame + * @see ChaosGameController + * @see edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription + * @see edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory + * + */ +public enum GameType { + SIERPINSKI, + BARNSLEY, + JULIA, + AFFINE, + JULIA_NEW, + AFFINE_NEW +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileModel.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileModel.java new file mode 100644 index 0000000000000000000000000000000000000000..bcaabe8d461c266856c1ca217f25eb15b032b73e --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileModel.java @@ -0,0 +1,174 @@ +package edu.ntnu.idatt2003.group6.models.files; + +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.utils.ChaosGameFileHandler; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * The FileModel class is a singleton + * that manages the files containing the chaos game descriptions. + * It is responsible for reading, writing, and deleting files. + * It also notifies observers when the amount of files changes. + * The class is part of the Model-View-Controller pattern. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class FileModel { + private static final Logger LOGGER = Logger.getLogger(FileModel.class.getName()); + private static FileModel instance = null; + private final List<FileObserver> observers; + private FileState state; + + /** + * Constructor for the FileModel class. + */ + private FileModel() { + observers = new ArrayList<>(); + } + + /** + * Returns the instance of the FileModel class. + * Following the singleton design principle. + * + * @return the instance of the FileModel class. + */ + public static FileModel getInstance() { + if (instance == null) { + instance = new FileModel(); + } + return instance; + } + + /** + * Returns a list of the files in the descriptions folder. + * + * @return List of file names in the descriptions folder. + * @throws IllegalArgumentException if the files could not be found. + */ + public List<String> getFiles() throws IllegalArgumentException { + try { + return ChaosGameFileHandler.showFiles(FilePath.DESCRIPTIONS.getPath()); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Could not find files @" + e.getMessage()); + } + } + + /** + * Adds a file to the descriptions' folder. + * + * @param fileName the name of the file to be added. + * @param chaosGame the chaos game description to be written to the file. + * @throws IllegalArgumentException if the file could not be written. + */ + public void addFile(String fileName, ChaosGameDescription chaosGame) + throws IllegalArgumentException { + if (fileName == null || chaosGame == null) { + throw new IllegalArgumentException("File name and chaos game description cannot be null"); + } + setState(FileState.FILES_UPDATING); + try { + ChaosGameFileHandler.writeChaosGameDescriptionToFile( + FilePath.DESCRIPTIONS.getPath() + fileName, chaosGame); + setState(FileState.FILES_CHANGED); + } catch (Exception e) { + setState(FileState.FILES_UNCHANGED); + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Could not write file @" + e.getMessage()); + } + } + + /** + * Returns a ChaosGameDescription object from a file. + * + * @param fileName the name of the file to be read. + * @return the ChaosGameDescription object from the file. + * @throws IllegalArgumentException if the file could not be read or if formatting is wrong. + */ + public ChaosGameDescription getFile(String fileName) { + if (fileName == null) { + throw new IllegalArgumentException("File name cannot be null"); + } + try { + return ChaosGameFileHandler.readChaosGameDescriptionFromFile( + FilePath.DESCRIPTIONS.getPath() + fileName); + } catch (Exception e) { + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Could not read file @" + e.getMessage()); + } + } + + /** + * Removes a file from the descriptions' folder. + * + * @param fileName the name of the file to be removed. + * @throws IllegalArgumentException if the file could not be deleted. + */ + public void removeFile(String fileName) throws IllegalArgumentException { + if (fileName == null) { + throw new IllegalArgumentException("File name cannot be null"); + } + setState(FileState.FILES_UPDATING); + try { + ChaosGameFileHandler.deleteFile(FilePath.DESCRIPTIONS.getPath() + fileName); + setState(FileState.FILES_CHANGED); + + } catch (Exception e) { + setState(FileState.FILES_UNCHANGED); + LOGGER.severe(e.getMessage()); + throw new IllegalArgumentException("Could not delete file @" + e.getMessage()); + } + } + + /** + * Sets the state of the FileModel. + * + * @param state the state to be set. + * @throws IllegalArgumentException if the state is null. + */ + public void setState(FileState state) throws IllegalArgumentException { + if (state == null) { + throw new IllegalArgumentException("State cannot be null"); + } + if (!state.equals(this.state)) { + this.state = state; + notifyObservers(); + } + } + + /** + * Adds an observer to be notified when the state of the FileModel changes. + * + * @param observer the observer to be added. + */ + public void addObserver(FileObserver observer) { + if (observer == null) { + throw new IllegalArgumentException("Observer cannot be null"); + } + observers.add(observer); + } + + /** + * Removes an observer from the list of observers. + * + * @param observer the observer to be removed. + */ + public void removeObserver(FileObserver observer) { + if (observer == null) { + throw new IllegalArgumentException("Observer cannot be null"); + } + observers.remove(observer); + } + + /** + * Notifies all observers that the state of the FileModel has changed. + */ + private void notifyObservers() { + observers.forEach(observer -> observer.update(state)); + } + + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileObserver.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..b8c35fd027ce5c2180025df2bd4a3fe0edef0206 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileObserver.java @@ -0,0 +1,15 @@ +package edu.ntnu.idatt2003.group6.models.files; + +/** + * Interface for the FileObserver class. + * The FileObserver class is used to observe changes in the FileState class. + * The FileObserver class is part of the Observer pattern. + * + * @version 0.3.2 + * @see FileState + * @see FileObserver + * @since 0.3.2 + */ +public interface FileObserver { + void update(FileState state); +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FilePath.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FilePath.java new file mode 100644 index 0000000000000000000000000000000000000000..98cff2236029ef39b285b5bcb6296f90d02f21e3 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FilePath.java @@ -0,0 +1,37 @@ +package edu.ntnu.idatt2003.group6.models.files; + +/** + * Enum class for the file paths. + * Contains the paths for the descriptions and images. + * The paths are used to locate the files. + * + * @version 0.3.2 + * @see FileModel + * @since 0.3.2 + */ +public enum FilePath { + DESCRIPTIONS("src/main/resources/descriptions/"), + IMAGES("src/main/resources/images/"), + + MUSIC("src/main/resources/mp3/"); + + private final String path; + + /** + * Constructor for the FilePath class. + * + * @param path the path of the file. + */ + FilePath(String path) { + this.path = path; + } + + /** + * Returns the path of the file. + * + * @return the path of the file. + */ + public String getPath() { + return path; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileState.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileState.java new file mode 100644 index 0000000000000000000000000000000000000000..6be8efbb86bb907e07dcfb69fe46a22ad25c18b6 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/files/FileState.java @@ -0,0 +1,19 @@ +package edu.ntnu.idatt2003.group6.models.files; + +/** + * Enum class for the file states used in the FileModel class. + * The states are FILES_UPDATING, FILES_UNCHANGED and FILES_CHANGED. + * FILES_UPDATING is used when the files are being updated. + * FILES_UNCHANGED is used when the files are unchanged. + * FILES_CHANGED is used when the files are changed. + * + * @version 0.3.2 + * @see edu.ntnu.idatt2003.group6.models.files.FileModel + * @since 0.3.2 + */ +public enum FileState { + FILES_UPDATING, + FILES_UNCHANGED, + FILES_CHANGED + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2.java index d3f90b095aa13477ea006cd56f82e9280474c95d..c755bdc4a873c7f2533ed1513ef79b5a5c9a4d8f 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2.java @@ -1,21 +1,16 @@ package edu.ntnu.idatt2003.group6.models.matrix; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; -import edu.ntnu.idatt2003.group6.utils.Utils; + /** - * This class represents a 2x2 matrix.It contains the components of the matrix and methods to - * multiply a vector by the matrix. + * This record represents a 2x2 matrix. + * It contains the components of the matrix and methods to multiply a vector by the matrix. * * @version 0.1.2 * @since 0.1.2 */ -public class Matrix2x2 { - private final double a00; - private final double a01; - private final double a10; - private final double a11; - +public record Matrix2x2(double a00, double a01, double a10, double a11) { /** * Constructor for the Matrix2x2 class. Creates a new matrix with the given components. * @@ -23,17 +18,9 @@ public class Matrix2x2 { * @param a01 The a01 component of the matrix. * @param a10 The a10 component of the matrix. * @param a11 The a11 component of the matrix. - * @throws IllegalArgumentException If the given components are invalid. */ - public Matrix2x2(double a00, double a01, double a10, double a11) { - Utils.verifyDoubleParameter(a00, "a00"); - Utils.verifyDoubleParameter(a01, "a01"); - Utils.verifyDoubleParameter(a10, "a10"); - Utils.verifyDoubleParameter(a11, "a11"); - this.a00 = a00; - this.a01 = a01; - this.a10 = a10; - this.a11 = a11; + public Matrix2x2 { + // The record class automatically generates a constructor with the given components. } /** @@ -43,25 +30,12 @@ public class Matrix2x2 { * @return A new vector that is the result of the multiplication. * @throws IllegalArgumentException If the given vector is null or if its components are invalid. */ - public Vector2D multiply(Vector2D vector) { + public Vector2D multiply(Vector2D vector) throws IllegalArgumentException { + if (vector == null) { + throw new IllegalArgumentException("The given vector cannot be null."); + } return new Vector2D(this.a00 * vector.getX0() + this.a01 * vector.getX1(), - this.a10 * vector.getX0() + this.a11 * vector.getX1()); + this.a10 * vector.getX0() + this.a11 * vector.getX1()); } - public double getA00() { - return a00; - } - - public double getA01() { - return a01; - } - - public double getA10() { - return a10; - } - - public double getA11() { - return a11; - } - } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModel.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModel.java new file mode 100644 index 0000000000000000000000000000000000000000..bfdfd834ffc73dbcda673407daf976550f9b8308 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModel.java @@ -0,0 +1,79 @@ +package edu.ntnu.idatt2003.group6.models.navigation; + +import edu.ntnu.idatt2003.group6.controller.navigation.NavigationController; +import java.util.ArrayList; +import java.util.List; + +/** + * The NavigationModel class manages the + * state of the navigation in the application. + * It provides methods for setting and getting the state + * and for adding and removing observers. + * The observers are notified when the state changes. + * The class is part of the Model-View-Controller pattern. + * + * @version 0.3.2 + * @see NavigationState + * @see NavigationObserver + * @see NavigationController + * @since 0.3.2 + */ +public class NavigationModel { + private final List<NavigationObserver> observers = new ArrayList<>(); + private NavigationState state; + + /** + * Returns the current state of the navigation. + */ + public NavigationState getState() { + return state; + } + + /** + * Sets the state of the navigation. + * + * @param state the new state of the navigation. + * @throws IllegalArgumentException if the state is null. + */ + public void setState(NavigationState state) throws IllegalArgumentException { + if (state == null) { + throw new IllegalArgumentException("State cannot be null"); + } + this.state = state; + notifyObservers(); + } + + /** + * Adds an observer to the navigation model. + * + * @param observer the observer to be added. + * @throws IllegalArgumentException if the observer is null. + */ + public void addObserver(NavigationObserver observer) throws IllegalArgumentException { + if (observer == null) { + throw new IllegalArgumentException("Observer cannot be null"); + } + observers.add(observer); + } + + /** + * Removes an observer from the navigation model. + * + * @param observer the observer to be removed. + * @throws IllegalArgumentException if the observer is null. + */ + public void removeObserver(NavigationObserver observer) throws IllegalArgumentException { + if (observer == null) { + throw new IllegalArgumentException("Observer cannot be null"); + } + observers.remove(observer); + } + + /** + * Notifies all observers that the state has changed. + */ + private void notifyObservers() { + observers.forEach(observer -> observer.update(state)); + } + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationObserver.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..360b7367729377880a512492314b836fe243f26f --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationObserver.java @@ -0,0 +1,13 @@ +package edu.ntnu.idatt2003.group6.models.navigation; + +/** + * Interface for an observer of the NavigationState. + * + * @version 3.2 + * @see NavigationState + * @see NavigationModel + * @since 3.2 + */ +public interface NavigationObserver { + void update(NavigationState state); +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationState.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationState.java new file mode 100644 index 0000000000000000000000000000000000000000..4378cd0b6b02a33a7781c2b014637e53ac13e69d --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationState.java @@ -0,0 +1,24 @@ +package edu.ntnu.idatt2003.group6.models.navigation; + +import edu.ntnu.idatt2003.group6.controller.navigation.NavigationController; + +/** + * Enum class for the different states of the view. Used to navigate between different views. + * + * @version 3.2 + * @see NavigationModel + * @see NavigationObserver + * @see NavigationController + * @since 3.2 + */ +public enum NavigationState { + SELECT_FILE, + EDIT_FILE, + SELECT_GAME, + SELECT_GAME_FILES, + PLAY_GAME, + PLAY_JULIA, + PLAY_AFFINE, + SETTINGS, + MENU +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2D.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2D.java index 8cdc05a97e5b7d52351930733c2c8ce2d3bdbbde..20e8de7f3b178607fd84d6592bb869c4b50e1304 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2D.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2D.java @@ -1,5 +1,6 @@ package edu.ntnu.idatt2003.group6.models.transformation; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; @@ -7,12 +8,13 @@ import edu.ntnu.idatt2003.group6.models.vector.Vector2D; * This class represents an affine transformation in 2D space. It contains a 2x2 matrix and a 2D * vector, and methods to transform a 2D vector by the affine transformation. * - * @version 0.1.3 + * @version 0.3.2 + * @see Transform2D + * @see Matrix2x2 * @since 0.1.3 */ -public class AffineTransform2D implements Transform2D { - private final Matrix2x2 matrix; - private final Vector2D vector; +public record AffineTransform2D(Matrix2x2 matrix, Vector2D vector) implements Transform2D { + private static final GameType gameType = GameType.AFFINE; /** * Constructor for the AffineTransform2D class. Creates a new affine transformation with the given @@ -22,9 +24,10 @@ public class AffineTransform2D implements Transform2D { * @param vector The 2D vector of the affine transformation. * @throws IllegalArgumentException If the given matrix or vector are null. */ - public AffineTransform2D(Matrix2x2 matrix, Vector2D vector) { - this.matrix = matrix; - this.vector = vector; + public AffineTransform2D { + if (matrix == null || vector == null) { + throw new IllegalArgumentException("The matrix or vector cannot be null."); + } } /** @@ -38,12 +41,32 @@ public class AffineTransform2D implements Transform2D { return this.matrix.multiply(vector).add(this.vector); } - - public Matrix2x2 getMatrix() { + /** + * Returns the matrix of this affine transformation. + * + * @return The 2x2 matrix of this affine transformation. + */ + @Override + public Matrix2x2 matrix() { return this.matrix; } - public Vector2D getVector() { + /** + * Returns the vector of this affine transformation. + * + * @return The 2D vector of this affine transformation. + */ + @Override + public Vector2D vector() { return this.vector; } + + /** + * Returns the game type of this affine transformation. + * + * @return The game type of this affine transformation. + */ + public GameType gameType() { + return gameType; + } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransform.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransform.java index a11cd672e72453ed0966bd147002f56209432bd1..809bedbe0f0d1b151b5204e414d372351bb5f32e 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransform.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransform.java @@ -1,5 +1,6 @@ package edu.ntnu.idatt2003.group6.models.transformation; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; import edu.ntnu.idatt2003.group6.models.vector.Complex; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; import edu.ntnu.idatt2003.group6.utils.Utils; @@ -9,23 +10,28 @@ import edu.ntnu.idatt2003.group6.utils.Utils; * and a signature to determine the direction of the transformation, positive or negative * and methods to transform a 2D vector by the Julia transformation. * - * @version 0.1.3 + * @version 0.3.2 * @since 0.1.3 */ public class JuliaTransform implements Transform2D { private final Complex point; private final int sign; + private static final GameType gameType = GameType.JULIA; /** * Constructor for the JuliaTransform class. Creates a new Julia transformation with the given * complex number and signature. * * @param point The complex number of the Julia transformation. - * @param sign The signature of the Julia transformation. + * @param sign The signature of the Julia transformation. * @throws IllegalArgumentException If the given complex number is null - * or if its components are invalid. Also, if the sign is invalid. + * or if its components are invalid. + * Also, if the sign is not +-1. */ - public JuliaTransform(Complex point, int sign) { + public JuliaTransform(Complex point, int sign) throws IllegalArgumentException { + if (sign != 1 && sign != -1) { + throw new IllegalArgumentException("The sign must be either 1 or -1."); + } Utils.verifyInt(sign, "sign"); this.point = point; this.sign = sign; @@ -35,34 +41,22 @@ public class JuliaTransform implements Transform2D { return point; } + public GameType gameType() { + return gameType; + } + /** * Transforms the given vector by this Julia transformation. * * @param vector The vector to be transformed by this Julia transformation. * @return A new vector that is the result of the transformation. */ - @Override public Vector2D transform(Vector2D vector) { - // Convert the input vector to a Complex object - Complex z = new Complex(vector.getX0(), vector.getX1()); - - // Convert the point to a Complex object - Complex c = new Complex(point.getX0(), point.getX1()); - - // Subtract the complex point from the input complex number - Complex subtracted = new Complex(z.getX0() - c.getX0(), z.getX1() - c.getX1()); - - // Take the square root of the subtracted complex number - Complex result = subtracted.sqrt(); - - // Negate the imaginary part if sign is -1 - if (sign == -1) { - result = new Complex(result.getX0(), -result.getX1()); - } - - // Convert the result back to a Vector2D object - return new Vector2D(result.getX0(), result.getX1()); + Vector2D z = vector.subtract(point); // z = z - c + Complex complexZ = new Complex(z.getX0(), z.getX1()); // Casting to complex + Complex sqrt = complexZ.sqrt(); // sqrt(z) + return new Vector2D(sign * sqrt.getX0(), + sign * sqrt.getX1()); // Adjusting imaginary part by sign } - } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/Transform2D.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/Transform2D.java index d57a6db6bcde571265eb2b9de1fa1cb905fcccb5..bb9d8cb7e68fe3975f90e8c4acf8698d95f007b8 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/Transform2D.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/transformation/Transform2D.java @@ -1,5 +1,6 @@ package edu.ntnu.idatt2003.group6.models.transformation; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; /** @@ -7,6 +8,8 @@ import edu.ntnu.idatt2003.group6.models.vector.Vector2D; * It contains an abstract method to transform a 2D vector. * * @version 0.1.3 + * @see AffineTransform2D + * @see JuliaTransform * @since 0.1.3 */ public interface Transform2D { @@ -18,4 +21,11 @@ public interface Transform2D { * @return A new vector that is the result of the transformation. */ Vector2D transform(Vector2D vector); + + /** + * Returns the type of game that this transformation is used for. + * + * @return The type of game that this transformation is used for. + */ + GameType gameType(); } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Complex.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Complex.java index ece36d3d32c16d6b5600292c2444127fc532b417..0d4eb816d2c2279ef3fd09315cbf907f8f1b565e 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Complex.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Complex.java @@ -4,7 +4,8 @@ package edu.ntnu.idatt2003.group6.models.vector; * This class represents a complex number in 2D space. It contains the real and imaginary parts of * the complex number and methods to perform operations with complex numbers. * - * @version 0.1.2 + * @version 0.3.2 + * @see Vector2D * @since 0.1.2 */ public class Complex extends Vector2D { @@ -27,10 +28,15 @@ public class Complex extends Vector2D { * @return A new complex vector that is the result of the square root. */ public Complex sqrt() { - double realPart = Math.sqrt((double) 1 / 2 * (Math.sqrt(Math.pow(this.getX0(), 2) - + Math.pow(this.getX1(), 2)) + this.getX0())); - double imaginaryPart = -Math.sqrt((double) 1 / 2 * (Math.sqrt(Math.pow(this.getX0(), 2) - + Math.pow(this.getX1(), 2)) - this.getX0())); + double x = this.getX0(); + double y = this.getX1(); + // the core is square root of x^2 + y^2 + double core = Math.sqrt(x * x + y * y); + // square root of 1/2 * (x^2 + y^2 + x) + double realPart = Math.sqrt((core + x) / 2); + // square root of i * 1/2 * (x^2 + y^2 - x) + double imaginaryPart = Math.signum(y) * Math.sqrt((core - x) / 2); + // return the new complex number return new Complex(realPart, imaginaryPart); } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Vector2D.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Vector2D.java index 75b7c41964109a9cf96b32164ae44359b7e94370..e8f4abdd25fd32c084f4bbbd207c9d041c739e8d 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Vector2D.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/models/vector/Vector2D.java @@ -1,12 +1,11 @@ package edu.ntnu.idatt2003.group6.models.vector; -import edu.ntnu.idatt2003.group6.utils.Utils; - /** * This class represents a 2D vector. It contains the components of the vector and methods to add, * subtract, and multiply the vector by a scalar. * * @version 0.1.2 + * @see Complex * @since 0.1.2 */ public class Vector2D { @@ -21,8 +20,6 @@ public class Vector2D { * @throws IllegalArgumentException If the given components are invalid. */ public Vector2D(double x0, double x1) { - Utils.verifyDoubleParameter(x0, "x0"); - Utils.verifyDoubleParameter(x1, "x1"); this.x0 = x0; this.x1 = x1; } @@ -83,11 +80,12 @@ public class Vector2D { * Verifies that the given vector is not null and that its components are valid. * * @param vector The vector to be verified. - * @throws IllegalArgumentException If the vector is null or if its components are invalid. + * @throws IllegalArgumentException If the vector is null. */ private void verifyVector2D(Vector2D vector) { - Utils.verifyDoubleParameter(vector.getX0(), "x0"); - Utils.verifyDoubleParameter(vector.getX1(), "x1"); + if (vector == null) { + throw new IllegalArgumentException("The vector can not be null"); + } } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandler.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandler.java index 76ce9b9a1bf455e4ae2184f2a17c8b9ff732197b..616afa9956cc30f326b77f8809ff8ccb03b0dfcb 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandler.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandler.java @@ -7,25 +7,26 @@ import edu.ntnu.idatt2003.group6.models.transformation.JuliaTransform; import edu.ntnu.idatt2003.group6.models.transformation.Transform2D; import edu.ntnu.idatt2003.group6.models.vector.Complex; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; -import java.io.BufferedReader; import java.io.FileWriter; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Collectors; /** - * This class contains methods to handle reading and writing ChaosGameDescriptions to and from files. + * This class contains methods to handle reading and + * writing ChaosGameDescriptions to and from files. * - * @version 0.2.4 + * @version 0.3.2 * @since 0.2.4 */ public class ChaosGameFileHandler { - public ChaosGameFileHandler() { - } + + private static final Logger LOGGER = Logger.getLogger(ChaosGameFileHandler.class.getName()); /** * Method to write to file. @@ -33,12 +34,12 @@ public class ChaosGameFileHandler { * @param fileName name of file * @param content content to write to file */ - private static void writeToFile(String fileName, String content) { - + static void writeToFile(String fileName, String content) { try (FileWriter fileWriter = new FileWriter(fileName)) { fileWriter.write(content); } catch (IOException e) { - //handle exception + LOGGER.severe("An error occurred while writing to file: " + e.getMessage()); + throw new RuntimeException("An error occurred while writing to file: " + e.getMessage()); } } @@ -46,34 +47,33 @@ public class ChaosGameFileHandler { * Method to write extra lines to file. * * @param fileName name of file - * @param content content to be added to file + * @param content content to be added to the file + * without overwriting the existing content in the file. + * @throws RuntimeException if an error occurs while writing to file. */ - private static void writeToFileExtra(String fileName, String content) { + static void writeToFileExtra(String fileName, String content) throws RuntimeException { try (FileWriter fileWriter = new FileWriter(fileName, true)) { fileWriter.write(content); } catch (IOException e) { - //handle exception + LOGGER.severe("An error occurred while writing to file: " + e.getMessage()); + throw new RuntimeException("An error occurred while writing to file: " + e.getMessage()); } } /** - * Method to read from file. + * Method to read from a file. * * @param fileName name of file * @return content of file + * @throws RuntimeException if an error occurs while reading from file */ - private static String readFromFile(String fileName) { - StringWriter stringWriter = new StringWriter(); - try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(fileName))) { - PrintWriter printWriter = new PrintWriter(stringWriter); - String line; - while ((line = bufferedReader.readLine()) != null) { - printWriter.println(line); - } + static String readFromFile(String fileName) throws RuntimeException { + try { + return Files.readString(Paths.get(fileName)); } catch (IOException e) { - //handle exception + LOGGER.severe("An error occurred while reading from file: " + e.getMessage()); + throw new RuntimeException("An error occurred while reading from file: " + e.getMessage(), e); } - return stringWriter.toString(); } /** @@ -81,120 +81,196 @@ public class ChaosGameFileHandler { * * @param fileName name of file to write to * @param chaosGameDescription the ChaosGameDescription to write to file + * @throws RuntimeException if an error occurs while writing to file */ public static void writeChaosGameDescriptionToFile( - String fileName, ChaosGameDescription chaosGameDescription) { + String fileName, ChaosGameDescription chaosGameDescription) throws RuntimeException { + try { - // Write first line. Type of transformation - if (chaosGameDescription.getTransformations().getFirst().getClass() == AffineTransform2D.class) { - writeToFile(fileName, "Affine2D # Type of transform \n"); - } else if (chaosGameDescription.getTransformations().getFirst().getClass() == JuliaTransform.class) { - writeToFile(fileName, "Julia # Type of transform \n"); - } + /* Write the first line. Type of transformation */ + if (chaosGameDescription.getTransformations().getFirst().getClass() + == AffineTransform2D.class) { + writeToFile(fileName, "Affine2D # Type of transform \n"); + } else if (chaosGameDescription.getTransformations().getFirst().getClass() + == JuliaTransform.class) { + writeToFile(fileName, "Julia # Type of transform \n"); + } + + /* Write the second line. Coordinates of the lower left corner of the canvas */ + writeToFileExtra(fileName, + chaosGameDescription.getMinCoords().getX0() + ", " + + chaosGameDescription.getMinCoords().getX1() + " # Lower left" + "\n"); - // Write second line. coordinates of the lower left corner of the canvas - writeToFileExtra(fileName, - chaosGameDescription.getMinCoords().getX0() + ", " - + chaosGameDescription.getMinCoords().getX1() + " # Lower left" + "\n"); - - // Write third line. Coordinates of the upper right corner of the canvas - writeToFileExtra(fileName, - chaosGameDescription.getMaxCoords().getX0() + ", " - + chaosGameDescription.getMaxCoords().getX1() + " # Upper right" + "\n"); - - // Write fourth line+. A line for each transformation - // With all parameters separated by a comma. - List<Transform2D> transformations = chaosGameDescription.getTransformations(); - for (int i = 0; i < transformations.size(); i++) { - if (transformations.get(i) instanceof AffineTransform2D affineTransform2D) { - writeToFileExtra(fileName, - affineTransform2D.getMatrix().getA00() - + ", " + affineTransform2D.getMatrix().getA01() - + ", " + affineTransform2D.getMatrix().getA10() - + ", " + affineTransform2D.getMatrix().getA11() - + ", " + affineTransform2D.getVector().getX0() - + ", " + affineTransform2D.getVector().getX1() - + " # " + (i + 1) + "nd transform" + "\n"); - } else if (transformations.get(i) instanceof JuliaTransform juliaTransform) { - writeToFileExtra(fileName, - juliaTransform.getPoint().getX0() + ", " - + juliaTransform.getPoint().getX1() + ", " - + " # Real and imaginary part of the constant c"); + /* Write the third line. The Coordinates in the upper right corner of the canvas */ + writeToFileExtra(fileName, + chaosGameDescription.getMaxCoords().getX0() + ", " + + chaosGameDescription.getMaxCoords().getX1() + " # Upper right" + "\n"); + + /* Write fourth line++. + A line for each transformation With all parameters separated by a comma. */ + List<Transform2D> transformations = chaosGameDescription.getTransformations(); + for (int i = 0; i < transformations.size(); i++) { + if (transformations.get(i) instanceof AffineTransform2D affineTransform2D) { + writeToFileExtra(fileName, + affineTransform2D.matrix().a00() + + ", " + affineTransform2D.matrix().a01() + + ", " + affineTransform2D.matrix().a10() + + ", " + affineTransform2D.matrix().a11() + + ", " + affineTransform2D.vector().getX0() + + ", " + affineTransform2D.vector().getX1() + + " # " + (i + 1) + "nd transform" + "\n"); + } else if (transformations.get(i) instanceof JuliaTransform juliaTransform) { + writeToFileExtra(fileName, + juliaTransform.getPoint().getX0() + ", " + + juliaTransform.getPoint().getX1() + ", " + + " # Real and imaginary part of the constant c"); + return; + } } + } catch (Exception e) { + LOGGER.severe("An error occurred while writing ChaosGameDescription to file: " + + e.getMessage()); + throw new RuntimeException("An error occurred while writing ChaosGameDescription to file: " + + e.getMessage(), e); } } - public static ChaosGameDescription readChaosGameDescriptionFromFile(String fileName) { - String content = readFromFile(fileName); - String[] lines = content.split("\n"); - String type = lines[0].split("#")[0].trim(); - String[] lowerLeft = lines[1].split("#")[0].split(","); - String[] upperRight = lines[2].split("#")[0].split(","); - String[] transformations = new String[lines.length - 3]; - - List<Transform2D> transformList = new ArrayList<>(); - - for (int i = 3; i < lines.length; i++) { - transformations[i - 3] = lines[i].split("#")[0].trim(); - - if (type.equals("Affine2D")) { - String[] matrix = transformations[i - 3].split(","); - double a00 = Double.parseDouble(matrix[0].trim()); - double a01 = Double.parseDouble(matrix[1].trim()); - double a10 = Double.parseDouble(matrix[2].trim()); - double a11 = Double.parseDouble(matrix[3].trim()); - double x0 = Double.parseDouble(matrix[4].trim()); - double x1 = Double.parseDouble(matrix[5].trim()); - Matrix2x2 matrix2x2 = new Matrix2x2(a00, a01, a10, a11); - Vector2D vector2D = new Vector2D(x0, x1); - - AffineTransform2D affineTransform2D = new AffineTransform2D(matrix2x2, vector2D); - transformList.add(affineTransform2D); - } else if (type.equals("Julia")) { - String[] point = transformations[i - 3].split(","); - double x0 = Double.parseDouble(point[0].trim()); - double x1 = Double.parseDouble(point[1].trim()); - Complex complex = new Complex(x0, x1); - - JuliaTransform juliaTransformPos = new JuliaTransform(complex, 1); - transformList.add(juliaTransformPos); - JuliaTransform juliaTransformNeg = new JuliaTransform(complex, -1); - transformList.add(juliaTransformNeg); + /** + * Method to read a ChaosGameDescription from a file. + * + * @param fileName name of file to read from + * @return ChaosGameDescription read from file + * @throws RuntimeException if an error occurs while reading from file + */ + public static ChaosGameDescription readChaosGameDescriptionFromFile(String fileName) + throws RuntimeException { + try { + String content = readFromFile(fileName); + String[] lines = content.split("\n"); + + String type = lines[0].split("#")[0].trim(); + Vector2D minCoords = parseVector2D(lines[1]); + Vector2D maxCoords = parseVector2D(lines[2]); + + List<Transform2D> transformList = new ArrayList<>(); + for (int i = 3; i < lines.length; i++) { + String transformation = lines[i].split("#")[0].trim(); + if (type.equals("Affine2D")) { + transformList.add(parseAffineTransform2D(transformation)); + } else if (type.equals("Julia")) { + transformList.add(parseJuliaTransform(transformation, 1)); + transformList.add(parseJuliaTransform(transformation, -1)); + } } + return new ChaosGameDescription(transformList, minCoords, maxCoords); + } catch (Exception e) { + LOGGER.severe("An error occurred while reading ChaosGameDescription from file: " + + e.getMessage()); + throw new RuntimeException("An error occurred while reading ChaosGameDescription from file: " + + e.getMessage(), e); } + } - Vector2D minCoords = new Vector2D( - Double.parseDouble(lowerLeft[0].trim()), Double.parseDouble(lowerLeft[1].trim())); + /** + * Method to parse a string to a Vector2D object. + * + * @param line The string to parse. + * @return A Vector2D object. + */ + private static Vector2D parseVector2D(String line) { + String[] coords = line.split("#")[0].split(","); + double x0 = Double.parseDouble(coords[0].trim()); + double x1 = Double.parseDouble(coords[1].trim()); + return new Vector2D(x0, x1); + } - Vector2D upperCoords = new Vector2D( - Double.parseDouble(upperRight[0].trim()), Double.parseDouble(upperRight[1].trim())); + /** + * Method to parse a string to an AffineTransform2D object. + * + * @param transformation The string to parse. + * @return An AffineTransform2D object. + */ + private static AffineTransform2D parseAffineTransform2D(String transformation) { + String[] params = transformation.split(","); + double a00 = Double.parseDouble(params[0].trim()); + double a01 = Double.parseDouble(params[1].trim()); + double a10 = Double.parseDouble(params[2].trim()); + double a11 = Double.parseDouble(params[3].trim()); + double x0 = Double.parseDouble(params[4].trim()); + double x1 = Double.parseDouble(params[5].trim()); + Matrix2x2 matrix = new Matrix2x2(a00, a01, a10, a11); + Vector2D vector = new Vector2D(x0, x1); + return new AffineTransform2D(matrix, vector); + } - return new ChaosGameDescription(transformList, minCoords, upperCoords); + /** + * Method to parse a string to a JuliaTransform object. + * + * @param transformation The string to parse. + * @param sign The sign of the imaginary part of the complex number. + * @return A JuliaTransform object. + */ + private static JuliaTransform parseJuliaTransform(String transformation, int sign) { + String[] params = transformation.split(","); + double x0 = Double.parseDouble(params[0].trim()); + double x1 = Double.parseDouble(params[1].trim()); + Complex complex = new Complex(x0, x1); + return new JuliaTransform(complex, sign); } - public static List<String> showFiles(String path) { - List<String> files = new ArrayList<>(); - try { - Files.walk(Paths.get(path)) + /** + * Method to show files in a directory. + * + * @param directoryPath The path to the directory. + * @return A list of strings with the name of files in the directory. + * @throws RuntimeException if an error occurs while showing files. + */ + public static List<String> showFiles(String directoryPath) throws RuntimeException { + Path path = Paths.get(directoryPath); + + try (var stream = Files.walk(path)) { + return stream .filter(Files::isRegularFile) - .map(path1 -> path1.toFile().getName()) - .forEach(files::add); + .map(filePath -> filePath.getFileName().toString()) + .collect(Collectors.toList()); } catch (IOException e) { - e.printStackTrace(); + LOGGER.severe("An error occurred while showing files: " + e.getMessage()); + throw new RuntimeException("An error occurred while showing files: " + e.getMessage()); } - return files; } - public static int numberOfFiles(String path) { - int count = 0; - try { - count = (int) Files.walk(Paths.get(path)) + /** + * Method to count the number of files in a directory. + * + * @param directoryPath The path to the directory. + * @return The number of files in the directory. + * @throws RuntimeException if an error occurs while counting files. + */ + public static int numberOfFiles(String directoryPath) throws RuntimeException { + Path path = Paths.get(directoryPath); + try (var stream = Files.walk(path)) { + return (int) stream .filter(Files::isRegularFile) - .map(path1 -> path1.toFile().getName()) .count(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.severe("An error occurred while counting files: " + e.getMessage()); + throw new RuntimeException("An error occurred while counting files: " + e.getMessage()); + } + } + + /** + * Method to delete a file from the resource folder. + * + * @param path The name of the file to be deleted. + * The file must be in the resource folder. + * @throws RuntimeException if an error occurs while deleting the file. + */ + public static void deleteFile(String path) throws RuntimeException { + try { + Files.delete(Paths.get(path)); + } catch (IOException e) { + LOGGER.severe("An error occurred while deleting file: " + e.getMessage()); + throw new RuntimeException("An error occurred while deleting file: " + e.getMessage()); } - return count; } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ControllerUtils.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ControllerUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..52fdce8b00a1ee5e4b4c6cfbe1231f721a992120 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/ControllerUtils.java @@ -0,0 +1,207 @@ +package edu.ntnu.idatt2003.group6.utils; + +import static edu.ntnu.idatt2003.group6.utils.Utils.inputStringToDouble; + +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; +import edu.ntnu.idatt2003.group6.models.transformation.AffineTransform2D; +import edu.ntnu.idatt2003.group6.models.transformation.JuliaTransform; +import edu.ntnu.idatt2003.group6.models.transformation.Transform2D; +import edu.ntnu.idatt2003.group6.models.vector.Complex; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineTransformationControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AttributeControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.JuliaTransformationControls; +import java.util.ArrayList; +import java.util.List; + +/** + * This class contains methods to handle reading and writing ChaosGameDescriptions to and from + * files. + * + * @version 0.3.2 + * @since 0.3.2 + * @see ChaosGameDescription + * @see AffineTransformationControls + * @see JuliaTransformationControls + * @see AttributeControls + * @see Vector2D + * @see Transform2D + * @see AffineTransform2D + * @see JuliaTransform + * @see Complex + * @see Matrix2x2 + * @see Utils + */ +public class ControllerUtils { + + /** + * Get the common attributes for a chaos game description + * and put them in the EditFileFrame. + * + * @param attributeView The attribute view to get the attributes from. + * @param model The ChaosGameDescription model to get the attributes from. + * @throws IllegalArgumentException If the model is null. + */ + public static void setAttributesCommon(AttributeControls attributeView, + ChaosGameDescription model) + throws IllegalArgumentException { + try { + attributeView.setMinCoordsX0Field(String.valueOf(model.getMinCoords().getX0())); + attributeView.setMinCoordsX1Field(String.valueOf(model.getMinCoords().getX1())); + attributeView.setMaxCoordsX0Field(String.valueOf(model.getMaxCoords().getX0())); + attributeView.setMaxCoordsX1Field(String.valueOf(model.getMaxCoords().getX1())); + } catch (Exception e) { + throw new IllegalArgumentException("Model is null: "); + } + } + + /** + * Set the attributes for the Julia transformation in the JuliaFrame. + * + * @param chaosGameDescription The ChaosGameDescription to get the attributes from. + * @return The JuliaTransformationControls frame with the attributes' set. + * @throws IllegalArgumentException If the ChaosGameDescription is null. + */ + public static JuliaTransformationControls setAttributesJulia( + ChaosGameDescription chaosGameDescription) throws IllegalArgumentException { + try { + JuliaTransformationControls juliaView = new JuliaTransformationControls(); + JuliaTransform julia = (JuliaTransform) chaosGameDescription.getTransformations().getFirst(); + juliaView.setImaginaryC(String.valueOf(julia.getPoint().getX1())); + juliaView.setRealC(String.valueOf(julia.getPoint().getX0())); + return juliaView; + } catch (Exception e) { + throw new IllegalArgumentException("Attributes could not be set: @" + e.getMessage()); + } + } + + /** + * Set the attributes for the Affine transformations in the AffineFrame. + * + * @param model The ChaosGameDescription to get the attributes from. + * @return The AffineTransformationControls frame with the attribute set. + * @throws IllegalArgumentException If the ChaosGameDescription is null. + */ + public static List<AffineTransformationControls> setAttributesAffineControlsList( + ChaosGameDescription model) throws IllegalArgumentException { + try { + List<Transform2D> affineTransforms = model.getTransformations(); + List<AffineTransformationControls> affineViews = new ArrayList<>(); + affineTransforms.forEach(transform -> { + AffineTransform2D affine = (AffineTransform2D) transform; + AffineTransformationControls affineView = new AffineTransformationControls(); + affineView.setTransformationNumber( + String.valueOf(affineTransforms.indexOf(transform) + 1)); + affineView.setA00(String.valueOf(affine.matrix().a00())); + affineView.setA01(String.valueOf(affine.matrix().a01())); + affineView.setA10(String.valueOf(affine.matrix().a10())); + affineView.setA11(String.valueOf(affine.matrix().a11())); + affineView.setX0(String.valueOf(affine.vector().getX0())); + affineView.setX1(String.valueOf(affine.vector().getX1())); + affineViews.add(affineView); + }); + return affineViews; + } catch (Exception e) { + throw new IllegalArgumentException("View could not be set: @" + e.getMessage()); + } + } + + /** + * Get the attributes for a ChaosGameDescription from the EditFileFrame. + * + * @param affineTransformationControlsList The list of affine transformations to get the + * attributes from. + * @param minCoords The minimum coordinates to get the attributes for. + * @param maxCoords The maximum coordinates to get the attributes for. + * @return The ChaosGameDescription with the found attributes. + * @throws IllegalArgumentException If the ChaosGameDescription could not be created. + */ + public static ChaosGameDescription getAttributesAffineControlsList( + List<AffineTransformationControls> affineTransformationControlsList, + Vector2D minCoords, Vector2D maxCoords) throws IllegalArgumentException { + try { + List<Transform2D> transforms = new ArrayList<>(); + affineTransformationControlsList.forEach(affine -> { + double a00 = inputStringToDouble(affine.getA00()); + double a01 = inputStringToDouble(affine.getA01()); + double a10 = inputStringToDouble(affine.getA10()); + double a11 = inputStringToDouble(affine.getA11()); + double x0 = inputStringToDouble(affine.getX0()); + double x1 = inputStringToDouble(affine.getX1()); + Matrix2x2 matrix = new Matrix2x2(a00, a01, a10, a11); + Vector2D vector = new Vector2D(x0, x1); + AffineTransform2D affineTransform = new AffineTransform2D(matrix, vector); + transforms.add(affineTransform); + }); + return new ChaosGameDescription(transforms, minCoords, maxCoords); + } catch (Exception e) { + throw new IllegalArgumentException( + "ChaosGameDescription could not be created: @" + e.getMessage()); + } + } + + /** + * Get the attributes for a ChaosGameDescription from the JuliaFrame. + * + * @param juliaTransform The JuliaTransformationControls to get the attributes from. + * @param minCoords The minimum coordinates to get the attributes for. + * @param maxCoords The maximum coordinates to get the attributes for. + * @return The ChaosGameDescription with the found attributes. + * @throws IllegalArgumentException If the ChaosGameDescription could not be created. + */ + public static ChaosGameDescription getAttributesJuliaControls( + JuliaTransformationControls juliaTransform, Vector2D minCoords, Vector2D maxCoords) + throws IllegalArgumentException { + try { + List<Transform2D> transforms = new ArrayList<>(); + double realC = inputStringToDouble(juliaTransform.getRealC()); + double imaginaryC = inputStringToDouble(juliaTransform.getImaginaryC()); + Complex point = new Complex(realC, imaginaryC); + JuliaTransform juliaPos = new JuliaTransform(point, 1); + JuliaTransform juliaNeg = new JuliaTransform(point, -1); + transforms.add(juliaPos); + transforms.add(juliaNeg); + return new ChaosGameDescription(transforms, minCoords, maxCoords); + } catch (Exception e) { + throw new IllegalArgumentException( + "ChaosGameDescription could not be created: @" + e.getMessage()); + } + } + + /** + * Get the minimum coordinates from the AttributeControls. + * + * @param attributeControls The AttributeControls to get the minimum coordinates from. + * @return The minimum coordinates. + * @throws IllegalArgumentException If the minimum coordinates could not be found. + */ + public static Vector2D getMinCoordsControls(AttributeControls attributeControls) + throws IllegalArgumentException { + try { + double minCoordsX0 = inputStringToDouble(attributeControls.getMinCoordsX0Field()); + double minCoordsX1 = inputStringToDouble(attributeControls.getMinCoordsX1Field()); + return new Vector2D(minCoordsX0, minCoordsX1); + } catch (Exception e) { + throw new IllegalArgumentException("Could not get min coords: @" + e.getMessage()); + } + } + + /** + * Get the maximum coordinates from the AttributeControls. + * + * @param attributeControls The AttributeControls to get the maximum coordinates from. + * @return The maximum coordinates. + * @throws IllegalArgumentException If the maximum coordinates could not be found. + */ + public static Vector2D getMaxCoordsControls(AttributeControls attributeControls) + throws IllegalArgumentException { + try { + double maxCoordsX0 = inputStringToDouble(attributeControls.getMaxCoordsX0Field()); + double maxCoordsX1 = inputStringToDouble(attributeControls.getMaxCoordsX1Field()); + return new Vector2D(maxCoordsX0, maxCoordsX1); + } catch (Exception e) { + throw new IllegalArgumentException("Could not get max coords: @" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/Utils.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/Utils.java index edd3404f069f7d1d215a71ca0cc5a803ac8ca855..7bd920fe3a7328a119310ebb81e10645106dc07f 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/Utils.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/Utils.java @@ -1,7 +1,6 @@ package edu.ntnu.idatt2003.group6.utils; - import java.util.List; import java.util.Scanner; @@ -15,22 +14,7 @@ import java.util.Scanner; public class Utils { /** - * Verifies if the given parameter is a valid double. - * - * @param parameter The parameter to be verified. - * @param parameterName The name of the parameter to be used in the exception message. - * @throws IllegalArgumentException If the given parameter is not a valid double. - */ - public static void verifyDoubleParameter(Double parameter, String parameterName) - throws IllegalArgumentException { - if (parameter.isInfinite() || parameter.isNaN()) { - throw new IllegalArgumentException("The string for the parameter " - + parameterName + " was not valid, try to register again"); - } - } - - /** - * Verifies if the given parameter is a valid sign, 1 or -1. + * Verifies if the given parameter is a valid sign, +-1. * * @param parameter The parameter to be verified. * @param parameterName The name of the parameter to be used in the exception message. @@ -59,6 +43,11 @@ public class Utils { return in.nextDouble(); } + /** + * A method to input an integer from the user. Verifies if the input is a valid integer. + * + * @return The integer input by the user. + */ public static int inputInt() { Scanner in = new Scanner(System.in); while (!in.hasNextInt()) { @@ -68,12 +57,20 @@ public class Utils { return in.nextInt(); } + /** + * A method to print a list of strings with a number in front. + */ public static void printListWithNumbers(List<String> list) { for (int i = 0; i < list.size(); i++) { System.out.println(i + 1 + ". " + list.get(i)); } } + /** + * A method to verify if the user input is y or n. + * + * @return true if the user input is y, false if the user input is n. + */ public static boolean editYesNo() { Scanner in = new Scanner(System.in); String choice = in.nextLine(); @@ -82,4 +79,34 @@ public class Utils { } return choice.equals("y"); } + + /** + * a method to parse a string to an integer. + * + * @param input the string to be parsed. + * @return the integer value of the string, or 0 if the string is not a valid integer. + * @throws IllegalArgumentException if the string is not a valid integer. + */ + public static int inputStringToInt(String input) throws IllegalArgumentException { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("The string was not a valid integer"); + } + } + + /** + * a method to parse a string to a double. + * + * @param input the string to be parsed. + * @return the double value of the string, or 0 if the string is not a valid double. + * @throws IllegalArgumentException if the string is not a valid double. + */ + public static double inputStringToDouble(String input) throws IllegalArgumentException { + try { + return Double.parseDouble(input); + } catch (NullPointerException e) { + throw new IllegalArgumentException("The string was not a valid double"); + } + } } diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/exceptions/IllegalChaosGameException.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/exceptions/IllegalChaosGameException.java new file mode 100644 index 0000000000000000000000000000000000000000..69b6864d28d9921d879488bfe23cc0b294729c47 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/utils/exceptions/IllegalChaosGameException.java @@ -0,0 +1,33 @@ +package edu.ntnu.idatt2003.group6.utils.exceptions; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; + +/** + * Exception thrown when an illegal operation is performed on a ChaosGame object. + * + * @version 0.3.2 + * @see ChaosGame + * @see edu.ntnu.idatt2003.group6.utils.ChaosGameFileHandler + * @since 0.3.2 + */ +public class IllegalChaosGameException extends IllegalArgumentException { + + /** + * Constructs an IllegalChaosGameException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause + */ + public IllegalChaosGameException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an IllegalChaosGameException with the specified detail message. + * + * @param message the detail message + */ + public IllegalChaosGameException(String message) { + super(message); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/ButtonBox.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/ButtonBox.java deleted file mode 100644 index 588fac14e25d4311989a8e675d175b54f06c5cf0..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/ButtonBox.java +++ /dev/null @@ -1,102 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.scene.control.ToggleGroup; -import javafx.scene.layout.VBox; - -public class ButtonBox extends VBox { - - private static final String MENU_BUTTON_STYLE = "menuButton"; - private static final String SUB_MENU_BUTTON_STYLE = "subMenuButton"; - - private final CustomToggleButton playGameButton; - private final CustomToggleButton filesButton; - private final CustomToggleButton quitButton; - private final CustomToggleButton playSierpinskiButton; - private final CustomToggleButton playJuliaButton; - private final CustomToggleButton playBarnsleyButton; - private final CustomToggleButton playMandelbrotButton; - - private final ToggleGroup menuButtonGroup = new ToggleGroup(); - private final ToggleGroup subMenuButtonGroup = new ToggleGroup(); - - public ButtonBox() { - getStyleClass().add("buttonBox"); - - playGameButton = new CustomToggleButton("Play Game", MENU_BUTTON_STYLE); - filesButton = new CustomToggleButton("Files", MENU_BUTTON_STYLE); - quitButton = new CustomToggleButton("Quit", MENU_BUTTON_STYLE); - - playSierpinskiButton = new CustomToggleButton("Sierpinski", SUB_MENU_BUTTON_STYLE); - playJuliaButton = new CustomToggleButton("Julia", SUB_MENU_BUTTON_STYLE); - playBarnsleyButton = new CustomToggleButton("Barnsley", SUB_MENU_BUTTON_STYLE); - playMandelbrotButton = new CustomToggleButton("Mandelbrot", SUB_MENU_BUTTON_STYLE); - - playGameButton.setToggleGroup(menuButtonGroup); - filesButton.setToggleGroup(menuButtonGroup); - quitButton.setToggleGroup(menuButtonGroup); - - playSierpinskiButton.setToggleGroup(subMenuButtonGroup); - playJuliaButton.setToggleGroup(subMenuButtonGroup); - playBarnsleyButton.setToggleGroup(subMenuButtonGroup); - playMandelbrotButton.setToggleGroup(subMenuButtonGroup); - - menuButtonGroup.selectToggle(null); - getChildren().addAll(playGameButton, filesButton, quitButton); - - menuButtonGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == playGameButton) { - addSubMenuButtonBox(); - } else { - clearSubMenuButtonBox(); - } - }); - } - - public void setActionOnButton(String buttonType, EventHandler<ActionEvent> eventHandler) { - switch (buttonType) { - case "playGame": - playGameButton.setOnAction(eventHandler); - break; - case "files": - filesButton.setOnAction(eventHandler); - break; - case "quit": - quitButton.setOnAction(eventHandler); - break; - case "playSierpinski": - playSierpinskiButton.setOnAction(eventHandler); - break; - case "playJulia": - playJuliaButton.setOnAction(eventHandler); - break; - case "playBarnsley": - playBarnsleyButton.setOnAction(eventHandler); - break; - case "playMandelbrot": - - playMandelbrotButton.setOnAction(eventHandler); - break; - default: - break; - } - } - - private void addSubMenuButtonBox(){ - VBox subMenuButtonBox = new VBox(); - - subMenuButtonBox.getChildren().addAll( - playSierpinskiButton, playJuliaButton, playBarnsleyButton, playMandelbrotButton); - subMenuButtonBox.setPadding(new Insets(0, 0, 0, 20)); - - getChildren().add(1, subMenuButtonBox); - } - private void clearSubMenuButtonBox(){ - if (getChildren().get(1) instanceof VBox) { - subMenuButtonGroup.selectToggle(null); - getChildren().remove(1); - } - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CommandLineInterface.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CommandLineInterface.java index 1434c428f3e9f234a229fa989fdc950f2634fd2d..c35db60fdd1cacd0f389eeb0e32c86c2693e563a 100644 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CommandLineInterface.java +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CommandLineInterface.java @@ -9,7 +9,7 @@ import static edu.ntnu.idatt2003.group6.utils.Utils.inputDouble; import static edu.ntnu.idatt2003.group6.utils.Utils.inputInt; import static edu.ntnu.idatt2003.group6.utils.Utils.printListWithNumbers; -import edu.ntnu.idatt2003.group6.controller.ChaosGame; +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; import edu.ntnu.idatt2003.group6.models.transformation.AffineTransform2D; @@ -37,6 +37,11 @@ public class CommandLineInterface { private static ChaosGame chaosGame; private static ChaosGameDescription description; + /** + * Main method to run the Chaos Game as a command line application. + * + * @param args The command line arguments. + */ public static void main(String[] args) { CommandLineInterface commandLineInterface = new CommandLineInterface(); @@ -102,7 +107,11 @@ public class CommandLineInterface { private void runGame() { System.out.println("Enter the number of steps to run the chaos game for: "); int steps = inputInt(); - chaosGame.runSteps(steps); + try { + chaosGame.runSteps(steps); + } catch (Exception e) { + System.out.println(" Could not run the game: \n" + e.getMessage()); + } } /** @@ -134,16 +143,17 @@ public class CommandLineInterface { System.out.println("No file chosen. Returning to main menu."); } else { description = readChaosGameDescriptionFromFile(file); - chaosGame = new ChaosGame(description, 100, 30); + chaosGame = new ChaosGame(description, 50, 50); System.out.println("Chaos Game Description loaded successfully!"); } } /** * Method to choose a file from the list of files in the descriptions folder. + * * @return The file path of the chosen file. */ - public String chooseFile(){ + public String chooseFile() { System.out.println( "Which file would you like to choose?" + "\n0. Back to main menu"); @@ -166,27 +176,31 @@ public class CommandLineInterface { * Method to display the canvas of the chaos game. */ private void displayCanvas() { - int[][] canvasArray = chaosGame.getCanvas().getCanvasArray(); - for (int[] row : canvasArray) { - for (int pixel : row) { - if (pixel == 0) { - System.out.print(" "); - } else if (pixel == 1) { - System.out.print("X"); + try { + int[][] canvasArray = chaosGame.getCanvas().getCanvasArray(); + for (int[] row : canvasArray) { + for (int pixel : row) { + if (pixel == 0) { + System.out.print(" "); + } else if (pixel >= 1) { + System.out.print("X"); + } } + System.out.println(); } - System.out.println(); + } catch (Exception e) { + System.out.println("Could not print the canvas: \n" + e.getMessage()); } } /** * Method to edit the coordinates of the chaos game. */ - private void editCoordinate(){ + private void editCoordinate() { System.out.println("Current min coordinates: " - + description.getMinCoords().getX0() + ", " + description.getMinCoords().getX1() +"\n" + + description.getMinCoords().getX0() + ", " + description.getMinCoords().getX1() + "\n" + "Current max coordinates: " - + description.getMaxCoords().getX0() + ", " + description.getMaxCoords().getX1() +"\n" + + description.getMaxCoords().getX0() + ", " + description.getMaxCoords().getX1() + "\n" + "Would you like to edit the min and max coordinates? (y/n)"); if (editYesNo()) { System.out.println("Enter the new min coordinates: \n"); @@ -205,18 +219,18 @@ public class CommandLineInterface { .filter(transform -> transform instanceof AffineTransform2D) .forEach(transform -> { AffineTransform2D affineTransforms = (AffineTransform2D) transform; - Matrix2x2 matrix = affineTransforms.getMatrix(); - Vector2D vector = affineTransforms.getVector(); + Matrix2x2 matrix = affineTransforms.matrix(); + Vector2D vector = affineTransforms.vector(); - System.out.println("Current matrix: " + matrix.getA00() + ", " + matrix.getA01() - + ", " + matrix.getA10() + ", " + matrix.getA11() + System.out.println("Current matrix: " + matrix.a00() + ", " + matrix.a01() + + ", " + matrix.a10() + ", " + matrix.a11() + "\nWould you like to edit the matrix (y/n)"); - if (editYesNo()){ + if (editYesNo()) { matrix = changeMatrix(); } System.out.println("Current vector: " + vector.getX0() + ", " + vector.getX1() + "\nWould you like to edit the vector (y/n)"); - if (editYesNo()){ + if (editYesNo()) { vector = changeVector2D(); } newAffineTransforms.add(new AffineTransform2D(matrix, vector)); @@ -232,7 +246,7 @@ public class CommandLineInterface { */ private void writeJuliaToFile(String file) { System.out.println("Would you like to edit the constant c? (y/n)"); - if (editYesNo()){ + if (editYesNo()) { List<Transform2D> transforms = new ArrayList<>(); Complex constant = changeComplex(); transforms.add(new JuliaTransform(constant, 1)); @@ -245,6 +259,7 @@ public class CommandLineInterface { /** * Method to change the vector2D of the chaos game. + * * @return The new vector2D. */ private Vector2D changeVector2D() { @@ -254,23 +269,26 @@ public class CommandLineInterface { double x1 = inputDouble(); return new Vector2D(x0, x1); } + /** * Method to change the complex number of the chaos game. + * * @return The new complex number. */ private Complex changeComplex() { JuliaTransform descriptionJulia = (JuliaTransform) description.getTransformations().getFirst(); System.out.println("Enter the real part of the constant c:\n" - + "Current real part: "+descriptionJulia.getPoint().getX0()); + + "Current real part: " + descriptionJulia.getPoint().getX0()); double x0 = inputDouble(); System.out.println("Enter the imaginary part of the constant c:\n" - + "Current imaginary part: "+descriptionJulia.getPoint().getX0()); + + "Current imaginary part: " + descriptionJulia.getPoint().getX0()); double x1 = inputDouble(); return new Complex(x0, x1); } /** * Method to change the matrix of the chaos game. + * * @return The new matrix. */ private Matrix2x2 changeMatrix() { diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomButton.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomButton.java deleted file mode 100644 index de793f1b93f39975ba120133864ab68e369930fb..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomButton.java +++ /dev/null @@ -1,12 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - - -import javafx.scene.control.Button; - -public class CustomButton extends Button { - - public CustomButton(String text, String styleClass) { - super(text); - getStyleClass().add(styleClass); - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomToggleButton.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomToggleButton.java deleted file mode 100644 index fdc87954dd76079e46b990f938ad646dbaa1ab5b..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/CustomToggleButton.java +++ /dev/null @@ -1,11 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import javafx.scene.control.ToggleButton; - -public class CustomToggleButton extends ToggleButton { - - public CustomToggleButton(String text, String styleClass) { - super(text); - getStyleClass().add(styleClass); - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/HomePage.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/HomePage.java deleted file mode 100644 index 55b2ee1289eacbd667385d73ac9aa54abaa718be..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/HomePage.java +++ /dev/null @@ -1,43 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.layout.*; -import javafx.stage.Stage; - -public class HomePage extends StackPane { - - private ButtonBox buttonBox; - private TransformationFrame transformationFrame; - - public HomePage(Stage stage) { - this.prefWidthProperty().bind(stage.widthProperty()); - this.prefHeightProperty().bind(stage.heightProperty()); - getStyleClass().add("homePage"); - - buttonBox = new ButtonBox(); - transformationFrame = new TransformationFrame(); - - - BorderPane buttonBoxFrame = new BorderPane(); - Pane contentFrame = new Pane(); - buttonBoxFrame.setBottom(buttonBox); - contentFrame.getChildren().add(transformationFrame); - - HBox homePageFrame = new HBox(); - homePageFrame.getChildren().addAll(buttonBoxFrame, contentFrame); - - HBox.setHgrow(buttonBoxFrame, Priority.ALWAYS); - HBox.setHgrow(contentFrame, Priority.ALWAYS); - buttonBoxFrame.prefWidthProperty().bind(homePageFrame.widthProperty().multiply(0.3)); - contentFrame.prefWidthProperty().bind(homePageFrame.widthProperty().multiply(0.7)); - - this.getChildren().add(homePageFrame); - } - - - - public void setActionOnButton(String buttonType, EventHandler<ActionEvent> eventHandler) { - buttonBox.setActionOnButton(buttonType, eventHandler); - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationAttributeFrame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationAttributeFrame.java deleted file mode 100644 index f49bf3e3e80e95758d7c6d68e01ce5b246f22f71..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationAttributeFrame.java +++ /dev/null @@ -1,9 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import javafx.scene.layout.GridPane; - -public class TransformationAttributeFrame extends GridPane { - public TransformationAttributeFrame() { - super(); - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationDisplay.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationDisplay.java deleted file mode 100644 index dd5e038a035c7015da8fead19d82a2da90b1ea4c..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationDisplay.java +++ /dev/null @@ -1,14 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import edu.ntnu.idatt2003.group6.controller.ChaosGame; -import javafx.scene.layout.StackPane; - -public class TransformationDisplay extends StackPane { - - public TransformationDisplay() {; - } - - public void setTransformationDisplay(ChaosGame chaosGame) { - //unused - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationFrame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationFrame.java deleted file mode 100644 index ef324cf08a78086523b28fd7268da80f3728d282..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/TransformationFrame.java +++ /dev/null @@ -1,20 +0,0 @@ -package edu.ntnu.idatt2003.group6.view; - -import edu.ntnu.idatt2003.group6.controller.ChaosGame; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; - -public class TransformationFrame extends VBox { - private TransformationDisplay transformationDisplay; - private TransformationAttributeFrame transformationAttributeFrame; - - public TransformationFrame() { - transformationDisplay = new TransformationDisplay(); - transformationAttributeFrame = new TransformationAttributeFrame(); - } - - public void setTransformationDisplay(ChaosGame chaosGame) { - transformationDisplay.setTransformationDisplay(chaosGame); - } -} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertError.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertError.java new file mode 100644 index 0000000000000000000000000000000000000000..77238e9c0b6416e5f93aa1db97f1c1f16a43d856 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertError.java @@ -0,0 +1,66 @@ +package edu.ntnu.idatt2003.group6.view.alert; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; +import java.util.Objects; +import javafx.geometry.Insets; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.image.Image; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +/** + * A class for creating an error alert. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class AlertError extends Alert { + + /** + * Creates an error alert with the specified message. + * + * @param message the message to be displayed in the alert + */ + public AlertError(String message, String description) { + super(AlertType.ERROR); + this.getDialogPane().getStylesheets().add( + Objects.requireNonNull(getClass().getResource("/stylesheets/alert.css")).toExternalForm()); + this.setTitle("Error"); + setIcon(); + this.setGraphic(null); + this.setHeaderText(null); + + Text text = new Text(message); + text.getStyleClass().add("errorText"); + Text descriptionText = new Text(description); + descriptionText.getStyleClass().add("errorDescription"); + + VBox content = new VBox(); + content.getStyleClass().add("vBox"); + content.setPadding(new Insets(10)); + content.setSpacing(10); + content.getChildren().addAll(text, descriptionText); + + this.getDialogPane().setContent(content); + + this.getDialogPane().lookupButton(ButtonType.OK).getStyleClass().add("positiveButton"); + } + + /** + * Shows the alert. + */ + public void showAlertError() { + this.showAndWait(); + } + + /** + * Sets the icon of the alert. + */ + private void setIcon() { + ((Stage) this.getDialogPane().getScene().getWindow()).getIcons().add( + new Image(Objects.requireNonNull( + ChaosGameController.class.getResourceAsStream("/Logo.png")))); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertInputFileName.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertInputFileName.java new file mode 100644 index 0000000000000000000000000000000000000000..f667fecad7a2aadcfb3789941f33a2c15eab7944 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/alert/AlertInputFileName.java @@ -0,0 +1,76 @@ +package edu.ntnu.idatt2003.group6.view.alert; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; +import java.util.Objects; +import javafx.geometry.Insets; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +/** + * Alert for inputting a file name. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class AlertInputFileName extends Alert { + private final TextField fileName; + + /** + * Constructor for the AlertInputFileName class. + */ + public AlertInputFileName() { + super(AlertType.CONFIRMATION); + this.getDialogPane().getStylesheets().add( + Objects.requireNonNull(getClass().getResource( + "/stylesheets/alert.css")).toExternalForm()); + + setTitle("Enter file name"); + setHeaderText(null); + setGraphic(null); + setIcon(); + + VBox content = new VBox(); + content.setPadding(new Insets(10)); + content.setSpacing(10); + Text text = new Text("Please enter the name of the file you want to save: "); + fileName = new TextField(); + text.getStyleClass().add("informationText"); + fileName.getStyleClass().add("textField"); + content.getChildren().addAll(text, fileName); + getDialogPane().setContent(content); + + getDialogPane().lookupButton(ButtonType.OK).getStyleClass().add("positiveButton"); + getDialogPane().lookupButton(ButtonType.CANCEL).getStyleClass().add("negativeButton"); + } + + + /** + * Returns the file name. + * + * @return the file name. + */ + public String getFileName() { + return fileName.getText(); + } + + /** + * Shows the alert. + */ + public void showAlert() { + this.showAndWait(); + } + + /** + * Sets the icon of the alert. + */ + private void setIcon() { + ((Stage) this.getDialogPane().getScene().getWindow()).getIcons() + .add(new Image(Objects.requireNonNull( + ChaosGameController.class.getResourceAsStream("/Logo.png")))); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxControls.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxControls.java new file mode 100644 index 0000000000000000000000000000000000000000..4b700549e1b9bc13adc38bd1a5883421577eb7ab --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxControls.java @@ -0,0 +1,71 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import javafx.scene.layout.VBox; + +/** + * A class for creating a VBox with buttons for saving, + * loading and running the game and returning to + * the menu. + * + * @version 0.3.2 + * @see CustomButton + * @since 0.3.2 + */ +public class ButtonBoxControls extends VBox { + + private static final String MENU_BUTTON_STYLE = "menuButton"; + private final CustomButton saveButton; + private final CustomButton runGameButton; + private final CustomButton backToMenuButton; + private final CustomButton loadParametersButton; + + /** + * Constructor for PlayGameButtonBox. + * Creates a VBox with text fields for steps, max cords and min cords, and buttons for saving, + * loading and running the game and returning to the menu. + */ + public ButtonBoxControls() { + + getStyleClass().add("buttonBox"); + loadParametersButton = new CustomButton("Load Parameters", MENU_BUTTON_STYLE); + saveButton = new CustomButton("Save Game", MENU_BUTTON_STYLE); + runGameButton = new CustomButton("Run Game", MENU_BUTTON_STYLE); + backToMenuButton = new CustomButton("Back to menu", MENU_BUTTON_STYLE); + + getChildren().addAll( + runGameButton, loadParametersButton, saveButton, backToMenuButton); + + } + + + /** + * Returns the save button. + * + * @return the save button. + */ + public CustomButton getSaveButton() { + return saveButton; + } + + /** + * Returns the run game button. + * + * @return the run game button. + */ + public CustomButton getRunGameButton() { + return runGameButton; + } + + /** + * Returns the back to menu button. + * + * @return the back to menu button. + */ + public CustomButton getBackToMenuButton() { + return backToMenuButton; + } + + public CustomButton getLoadParametersButton() { + return loadParametersButton; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxFiles.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxFiles.java new file mode 100644 index 0000000000000000000000000000000000000000..a88e79577a34b9b07a5bc2a84a810357f213d911 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxFiles.java @@ -0,0 +1,115 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import java.util.List; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.VBox; + +/** + * A class for creating a VBox with buttons for editing, + * creating and deleting files and returning to the menu. + * + * @version 0.3.2 + * @see CustomButton + * @see CustomToggleButton + * @since 0.3.2 + */ +public class ButtonBoxFiles extends VBox { + + private static final String MENU_BUTTON_STYLE = "menuButton"; + + private final VBox filesButtonsBoxToggle; + private final VBox filesButtonsBox; + + private final CustomButton newFileButton; + private final CustomButton editFileButton; + private final CustomButton deleteFileButton; + private final CustomButton backToMenuButton; + private final ToggleGroup toggleButtons; + + /** + * Constructor for PlayGameButtonBox. + * Creates a VBox with text fields for steps, max cords and min cords, and buttons for saving, + * loading and running the game and returning to the menu. + */ + public ButtonBoxFiles() { + + toggleButtons = new ToggleGroup(); + filesButtonsBoxToggle = new VBox(); + filesButtonsBox = new VBox(); + + getStyleClass().add("buttonBox"); + + editFileButton = new CustomButton("Edit file", MENU_BUTTON_STYLE); + newFileButton = new CustomButton("New file", MENU_BUTTON_STYLE); + deleteFileButton = new CustomButton("Delete file", MENU_BUTTON_STYLE); + backToMenuButton = new CustomButton("Back to menu", MENU_BUTTON_STYLE); + + VBox menuButtonsBox = new VBox(); + menuButtonsBox.getChildren().addAll(editFileButton, newFileButton, deleteFileButton, + backToMenuButton); + setSpacing(20); + getChildren().addAll(filesButtonsBoxToggle, menuButtonsBox); + } + + public VBox getFilesButtonsBox() { + return filesButtonsBox; + } + + public List<CustomToggleButton> getFileButtonsToggle() { + return filesButtonsBoxToggle.getChildren().stream() + .map(e -> (CustomToggleButton) e).toList(); + } + + public List<CustomButton> getFileButtons() { + return filesButtonsBox.getChildren().stream() + .map(e -> (CustomButton) e).toList(); + } + + public void addFileButton(String filename) { + addFileButtonToggleGroup(filename); + addFileButtonCustom(filename); + } + + private void addFileButtonToggleGroup(String filename) { + CustomToggleButton button = new CustomToggleButton(filename, MENU_BUTTON_STYLE); + filesButtonsBoxToggle.getChildren().add(button); + button.setToggleGroup(toggleButtons); + } + + private void addFileButtonCustom(String filename) { + CustomButton button = new CustomButton(filename, MENU_BUTTON_STYLE); + filesButtonsBox.getChildren().add(button); + } + + + public void clearFileButtons() { + filesButtonsBoxToggle.getChildren().remove(0, filesButtonsBoxToggle.getChildren().size()); + filesButtonsBox.getChildren().remove(0, filesButtonsBox.getChildren().size()); + } + + public ToggleGroup getToggleButtons() { + return toggleButtons; + } + + /** + * Returns the back to menu button. + * + * @return the back to menu button. + */ + public CustomButton getBackToMenuButton() { + return backToMenuButton; + } + + public CustomButton getEditFileButton() { + return editFileButton; + } + + public CustomButton getDeleteFileButton() { + return deleteFileButton; + } + + public CustomButton getNewFileButton() { + return newFileButton; + } + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxGames.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxGames.java new file mode 100644 index 0000000000000000000000000000000000000000..4d28ccb64f76d2cb9240bf0e1fbf9d6421526002 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxGames.java @@ -0,0 +1,72 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import javafx.scene.layout.VBox; + +/** + * A class for creating a VBox with buttons for selecting and starting a game, and returning to + * the menu. + * + * @version 0.3.2 + * @see CustomButton + * @since 0.1 + */ +public class ButtonBoxGames extends VBox { + private static final String MENU_BUTTON_STYLE = "menuButton"; + + private final CustomButton newAffineButton; + private final CustomButton newJuliaButton; + private final CustomButton juliaButton; + private final CustomButton sierpinskiButton; + private final CustomButton barnsleyButton; + private final CustomButton fileButton; + private final CustomButton backtoMenuButton; + + /** + * Constructor for PlayGameButtonBox. + * Creates a VBox with text fields for steps, max cords and min cords, and buttons for saving, + * loading and running the game and returning to the menu. + */ + public ButtonBoxGames() { + getStyleClass().add("buttonBox"); + newAffineButton = new CustomButton("New Affine", MENU_BUTTON_STYLE); + newJuliaButton = new CustomButton("New Julia", MENU_BUTTON_STYLE); + juliaButton = new CustomButton("Preset Julia Game", MENU_BUTTON_STYLE); + sierpinskiButton = new CustomButton("Preset Sierpinski Game", MENU_BUTTON_STYLE); + barnsleyButton = new CustomButton("Preset Barnsley Game", MENU_BUTTON_STYLE); + fileButton = new CustomButton("Game from file", MENU_BUTTON_STYLE); + backtoMenuButton = new CustomButton("Back to menu", MENU_BUTTON_STYLE); + + getChildren().addAll(newAffineButton, newJuliaButton, juliaButton, sierpinskiButton, + barnsleyButton, + fileButton, backtoMenuButton); + } + + public CustomButton getNewAffineButtonButton() { + return newAffineButton; + } + + public CustomButton getNewJuliaButtonButton() { + return newJuliaButton; + } + + public CustomButton getJuliaButton() { + return juliaButton; + } + + public CustomButton getSierpinskiButton() { + return sierpinskiButton; + } + + public CustomButton getBarnsleyButton() { + return barnsleyButton; + } + + public CustomButton getFileButton() { + return fileButton; + } + + public CustomButton getBackToMenuButton() { + return backtoMenuButton; + } +} + diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxMenu.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxMenu.java new file mode 100644 index 0000000000000000000000000000000000000000..348763a02a447ea9c5dde903f17aecfed2232e6f --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxMenu.java @@ -0,0 +1,77 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import javafx.scene.layout.VBox; + +/** + * A class for creating a VBox with buttons for the menu. + * The buttons are Play Game, Files, Settings and Quit. + * The buttons are styled with the class menuButton. + * The VBox is styled with the class buttonBox. + * The class extends VBox. + * + * @version 0.3.2 + * @see VBox + * @see CustomButton + * @see CustomButton + * @since 0.3.2 + */ +public class ButtonBoxMenu extends VBox { + + private static final String MENU_BUTTON_STYLE = "menuButton"; + private final CustomButton playGameButton; + private final CustomButton filesButton; + private final CustomButton settingsButton; + private final CustomButton quitButton; + + + /** + * Constructor for the ButtonBoxMenu class. + */ + public ButtonBoxMenu() { + getStyleClass().add("buttonBox"); + + playGameButton = new CustomButton("Play Game", MENU_BUTTON_STYLE); + filesButton = new CustomButton("Files", MENU_BUTTON_STYLE); + settingsButton = new CustomButton("Settings", MENU_BUTTON_STYLE); + quitButton = new CustomButton("Quit", MENU_BUTTON_STYLE); + + getChildren().addAll(playGameButton, filesButton, settingsButton, quitButton); + } + + + /** + * Method for getting the play Game Button. + * + * @return the playGameButton + */ + public CustomButton getPlayGameButton() { + return playGameButton; + } + + /** + * Method for getting the file Button. + * + * @return the filesButton + */ + public CustomButton getFilesButton() { + return filesButton; + } + + /** + * Method for getting the settings Button. + * + * @return the settingsButton + */ + public CustomButton getSettingsButton() { + return settingsButton; + } + + /** + * Method for getting the quit Button. + * + * @return the quit Button + */ + public CustomButton getQuitButton() { + return quitButton; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxSettings.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxSettings.java new file mode 100644 index 0000000000000000000000000000000000000000..f779c0e9c97821d3f824b3cfdb65aec3cced164c --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/ButtonBoxSettings.java @@ -0,0 +1,39 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import javafx.scene.layout.VBox; + +/** + * The ButtonBoxSettings class creates a VBox with buttons for the settings menu. + * The buttons are for general settings, background settings, + * music settings and returning to the menu. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class ButtonBoxSettings extends VBox { + + private static final String MENU_BUTTON_STYLE = "menuButton"; + private final CustomButton backToMenuButton; + + /** + * Constructor for PlayGameButtonBox. + * Creates a VBox with text fields for steps, max cords and min cords, and buttons for saving, + * loading and running the game and returning to the menu. + */ + public ButtonBoxSettings() { + getStyleClass().add("buttonBox"); + + backToMenuButton = new CustomButton("Back to menu", MENU_BUTTON_STYLE); + + getChildren().addAll(backToMenuButton); + } + + /** + * Returns the back to menu button. + * + * @return the back to menu button. + */ + public CustomButton getBackToMenuButton() { + return backToMenuButton; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomButton.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomButton.java new file mode 100644 index 0000000000000000000000000000000000000000..dcb4e53799b499c789465c1b4982398149092b61 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomButton.java @@ -0,0 +1,19 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + + +import javafx.scene.control.Button; + +/** + * CustomButton is a class that extends the Button class in JavaFX. + * It is used to create buttons with a specific style class. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class CustomButton extends Button { + + public CustomButton(String text, String styleClass) { + super(text); + getStyleClass().add(styleClass); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomToggleButton.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomToggleButton.java new file mode 100644 index 0000000000000000000000000000000000000000..cccfda524a07d963c45db5247f64f7004d7c66b4 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/buttonbox/CustomToggleButton.java @@ -0,0 +1,18 @@ +package edu.ntnu.idatt2003.group6.view.buttonbox; + +import javafx.scene.control.ToggleButton; + +/** + * CustomToggleButton is a class that extends the ToggleButton class in JavaFX. + * It is used to create toggle buttons with a specific style class. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class CustomToggleButton extends ToggleButton { + + public CustomToggleButton(String text, String styleClass) { + super(text); + getStyleClass().add(styleClass); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/EditFileFrame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/EditFileFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..5565cf1ff5286a669b79511a0ab8fa24edb38f8a --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/EditFileFrame.java @@ -0,0 +1,192 @@ +package edu.ntnu.idatt2003.group6.view.frames; + +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineControlsView; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineTransformationControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AttributeControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.JuliaTransformationControls; +import java.util.List; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +/** + * EditFileFrame is a class that extends the VBox class in JavaFX. + * It is used to create a frame for editing files with different attributes. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class EditFileFrame extends VBox { + Label title; + TextField fileNameField; + AttributeControls transformationControls; + JuliaTransformationControls juliaTransformationControls; + AffineControlsView affineControlsView; + HBox typeButtons; + Button juliaTransformButton; + Button affineTransformButton; + Button saveButton; + + /** + * Constructor for EditFileFrame. + */ + public EditFileFrame() { + getStylesheets().add("/stylesheets/transformationAttributes.css"); + setAlignment(Pos.CENTER); + title = new Label("Test"); + + Text fileName = new Text("File Name:"); + fileName.getStyleClass().add("attributeNames"); + fileNameField = new TextField(); + fileNameField.setPromptText("Enter new file name"); + fileNameField.getStyleClass().add("textField"); + HBox fileNameBox = new HBox(); + fileNameBox.getChildren().addAll(fileName, fileNameField); + + transformationControls = new AttributeControls(); + transformationControls.removeSteps(); + + affineControlsView = new AffineControlsView(); + juliaTransformButton = new Button("Julia Transformation"); + affineTransformButton = new Button("Affine Transformation"); + saveButton = new Button("Save To File"); + + typeButtons = new HBox(); + typeButtons.getChildren().addAll(juliaTransformButton, affineTransformButton); + getChildren().addAll(fileNameBox, transformationControls, typeButtons, saveButton); + } + + + /** + * Method for clearing the fields in the EditFileFrame. + */ + public void clearFields() { + getChildren().remove(2); + getChildren().add(2, new Pane()); + getChildren().remove(3); + getChildren().add(3, typeButtons); + } + + /** + * Method for setting the file name field in the EditFileFrame. + * + * @param fileNameField The file name to be set. + */ + public void setFileNameField(String fileNameField) { + this.fileNameField.setText(fileNameField); + } + + /** + * Method for setting the transformation controls in the EditFileFrame. + * + * @param juliaTransformationControls the julia transformation controls to be set. + */ + public void setJuliaTransformControls(JuliaTransformationControls juliaTransformationControls) { + this.juliaTransformationControls = juliaTransformationControls; + showJuliaAttributes(); + } + + /** + * method to get the name field of the file. + * + * @return the name of the file. + */ + public String getFileNameField() { + return fileNameField.getText(); + } + + /** + * Method to get the common attributes of a transformation. + * + * @return the common transformation attributes. + */ + public AttributeControls getAttributeControls() { + return transformationControls; + } + + /** + * Method to get the julia transformation controls. + * + * @return the julia transformation controls. + */ + public JuliaTransformationControls getJuliaTransformationControls() { + return juliaTransformationControls; + } + + /** + * Method to get the affine transformation controls. + * + * @return the affine transformation controls. + */ + public List<AffineTransformationControls> getAffineTransformControls() { + return affineControlsView.getAffineControlsList(); + } + + /** + * Method to get the julia transformation button. + * + * @return the julia transformation button. + */ + public Button getJuliaTransformButton() { + return juliaTransformButton; + } + + /** + * Method to get the affine transformation button. + * + * @return the affine transformation button. + */ + public Button getAffineTransformButton() { + return affineTransformButton; + } + + /** + * Method to get the affine controls view. + * + * @return the affine controls view. + */ + public AffineControlsView getAffineControlsView() { + return affineControlsView; + } + + /** + * Method to get the save button. + * + * @return the save button. + */ + public Button getSaveButton() { + return saveButton; + } + + /** + * Method to show the common transformation attributes from a file. + */ + public void showFileAttributes() { + getChildren().clear(); + getChildren().addAll(title, fileNameField, transformationControls, typeButtons, saveButton); + } + + /** + * Method to show the affine transformation attributes from a file. + */ + public void showAffineAttributes() { + getChildren().clear(); + getChildren().addAll(title, fileNameField, transformationControls, + affineControlsView.getScrollPaneAffineControls(), saveButton); + } + + /** + * Method to show the julia transformation attributes from a file. + */ + public void showJuliaAttributes() { + getChildren().clear(); + getChildren().addAll(title, fileNameField, transformationControls, + juliaTransformationControls, saveButton); + } + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/HomePage.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/HomePage.java new file mode 100644 index 0000000000000000000000000000000000000000..0fb1a483bc9cb17d9512a425c345b79c379f6478 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/HomePage.java @@ -0,0 +1,277 @@ +package edu.ntnu.idatt2003.group6.view.frames; + + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; +import edu.ntnu.idatt2003.group6.models.navigation.NavigationObserver; +import edu.ntnu.idatt2003.group6.models.navigation.NavigationState; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxControls; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxFiles; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxGames; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxMenu; +import edu.ntnu.idatt2003.group6.view.buttonbox.ButtonBoxSettings; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AffineControlsView; +import edu.ntnu.idatt2003.group6.view.gamecontrols.AttributeControls; +import edu.ntnu.idatt2003.group6.view.gamecontrols.JuliaTransformationControls; +import java.util.Objects; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; + + +/** + * The HomePage class is the main view of the application. + * It contains the menu buttons and the content frame. + * The content frame is where the different views are displayed. + * The views are controlled by the NavigationState enum. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class HomePage extends StackPane implements NavigationObserver { + private final Pane contentFrame; + private final ButtonBoxMenu buttonBoxMenu; + private final BorderPane buttonBoxFrame; + private final ButtonBoxControls buttonBoxControls; + private final ButtonBoxFiles buttonBoxFiles; + private final ButtonBoxSettings buttonBoxSettings; + private final TransformationFrame transformationFrame; + private final ButtonBoxGames buttonBoxSelectGame; + private final AttributeControls attributeControls; + private JuliaTransformationControls juliaTransformationControls; + private final AffineControlsView affineControlsView; + private final EditFileFrame editFileFrame; + private final SettingsFrame settingsFrame; + private final ImageView logo; + + /** + * Creates an instance of the HomePage class. + * + * @param stage The stage to bind the width and height properties to. + */ + public HomePage(Stage stage) { + + buttonBoxMenu = new ButtonBoxMenu(); + buttonBoxFrame = new BorderPane(); + contentFrame = new Pane(); + buttonBoxControls = new ButtonBoxControls(); + buttonBoxFiles = new ButtonBoxFiles(); + buttonBoxSettings = new ButtonBoxSettings(); + transformationFrame = new TransformationFrame(); + buttonBoxSelectGame = new ButtonBoxGames(); + editFileFrame = new EditFileFrame(); + settingsFrame = new SettingsFrame(); + + attributeControls = new AttributeControls(); + affineControlsView = new AffineControlsView(); + juliaTransformationControls = new JuliaTransformationControls(); + + this.prefWidthProperty().bind(stage.widthProperty()); + this.prefHeightProperty().bind(stage.heightProperty()); + getStyleClass().add("homePage"); + Image image = new Image( + Objects.requireNonNull(getClass().getResourceAsStream("/Logo.png"))); + + logo = new ImageView(image); + logo.opacityProperty().setValue(0.5); + logo.fitWidthProperty().bind(contentFrame.widthProperty()); + logo.fitHeightProperty().bind(contentFrame.heightProperty()); + + HBox homePageFrame = new HBox(); + homePageFrame.getChildren().addAll(buttonBoxFrame, contentFrame); + + HBox.setHgrow(buttonBoxFrame, Priority.ALWAYS); + HBox.setHgrow(contentFrame, Priority.ALWAYS); + + buttonBoxFrame.prefWidthProperty().bind(homePageFrame.widthProperty().multiply(0.3)); + buttonBoxFrame.prefHeightProperty().bind(homePageFrame.heightProperty()); + + contentFrame.prefWidthProperty().bind(homePageFrame.widthProperty().multiply(0.7)); + contentFrame.prefHeightProperty().bind(homePageFrame.heightProperty()); + + + transformationFrame.prefWidthProperty().bind(contentFrame.widthProperty()); + transformationFrame.prefHeightProperty().bind(contentFrame.heightProperty()); + + HBox.setHgrow(contentFrame, Priority.ALWAYS); + editFileFrame.prefHeightProperty().bind(contentFrame.heightProperty()); + + settingsFrame.prefWidthProperty().bind(contentFrame.widthProperty()); + settingsFrame.prefHeightProperty().bind(contentFrame.heightProperty()); + + this.getChildren().add(homePageFrame); + getStylesheets().add("/stylesheets/transformationAttributes.css"); + getStylesheets().add("/stylesheets/buttonBox.css"); + update(NavigationState.MENU); + } + + /** + * Gets the buttonBoxMenu. + * + * @return The buttonBoxMenu. + */ + public ButtonBoxMenu getButtonBoxMenu() { + return buttonBoxMenu; + } + + /** + * Gets the buttonBoxControls. + * + * @return The buttonBoxControls. + */ + public ButtonBoxControls getButtonBoxControls() { + return buttonBoxControls; + } + + /** + * Gets the buttonBoxFiles. + * + * @return The buttonBoxFiles. + */ + public ButtonBoxFiles getButtonBoxFiles() { + return buttonBoxFiles; + } + + /** + * Gets the buttonBoxSettings. + * + * @return The buttonBoxSettings. + */ + public ButtonBoxSettings getButtonBoxSettings() { + return buttonBoxSettings; + } + + /** + * Gets the buttonBoxSelectGame. + * + * @return The buttonBoxSelectGame. + */ + public ButtonBoxGames getButtonBoxGames() { + return buttonBoxSelectGame; + } + + /** + * Gets the attributeControls. + * + * @return The attributeControls. + */ + public AttributeControls getAttributeControls() { + return attributeControls; + } + + /** + * Gets the editFileFrame. + * + * @return The editFileFrame. + */ + public EditFileFrame getEditFileFrame() { + return editFileFrame; + } + + /** + * Gets the settingsFrame. + * + * @return The settingsFrame. + */ + public SettingsFrame getSettingsFrame() { + return settingsFrame; + } + + /** + * Gets the affineControlsView. + * + * @return The affineControlsView. + */ + public AffineControlsView getAffineControlsView() { + return affineControlsView; + } + + /** + * Sets the transformation picture in the transformationFrame. + * + * @param chaosGame The chaosGame to set the transformation picture from. + */ + public void setTransformationPicture(ChaosGame chaosGame) { + contentFrame.getChildren().clear(); + transformationFrame.showTransformationPicture(chaosGame); + contentFrame.getChildren().add(transformationFrame); + } + + /** + * Gets the juliaTransformationControls. + * + * @return The juliaTransformationControls. + */ + public JuliaTransformationControls getJuliaTransformationControls() { + return juliaTransformationControls; + } + + /** + * Sets the juliaTransformationControls. + * + * @param juliaTransformationControls The juliaTransformationControls to set. + */ + public void setJuliaTransformationControls( + JuliaTransformationControls juliaTransformationControls) { + this.juliaTransformationControls = juliaTransformationControls; + } + + /** + * Updates the view based on the NavigationState. + * + * @param state The NavigationState to update the view based on. + */ + @Override + public void update(NavigationState state) { + switch (state) { + case MENU: + //Clears the contentFrame and buttonBoxFrame and adds the menu buttons to the buttonBoxFrame + buttonBoxFrame.getChildren().clear(); + contentFrame.getChildren().clear(); + buttonBoxFrame.setBottom(buttonBoxMenu); + contentFrame.getChildren().add(logo); + break; + case SELECT_GAME: + buttonBoxFrame.getChildren().clear(); + buttonBoxFrame.setBottom(buttonBoxSelectGame); + break; + case SELECT_GAME_FILES: + buttonBoxFrame.setCenter(buttonBoxFiles.getFilesButtonsBox()); + break; + case PLAY_AFFINE: + contentFrame.getChildren().clear(); + //Sets the common attributes for the affine transformations + buttonBoxFrame.setTop(attributeControls); + //Sets the affine transformation controls as a scrollPane + buttonBoxFrame.setCenter(affineControlsView.getScrollPaneAffineControls()); + //Sets the buttons to run and load new transformations + buttonBoxFrame.setBottom(buttonBoxControls); + break; + case PLAY_JULIA: + contentFrame.getChildren().clear(); + buttonBoxFrame.setTop(attributeControls); + buttonBoxFrame.setCenter(juliaTransformationControls); + buttonBoxFrame.setBottom(buttonBoxControls); + break; + case SELECT_FILE: + buttonBoxFrame.setBottom(buttonBoxFiles); + break; + case SETTINGS: + buttonBoxFrame.setBottom(buttonBoxSettings); + contentFrame.getChildren().clear(); + contentFrame.getChildren().add(settingsFrame); + break; + case EDIT_FILE: + contentFrame.getChildren().clear(); + contentFrame.getChildren().add(editFileFrame); + break; + + default: + break; + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/SettingsFrame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/SettingsFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..071ff7fc3002e40ac81a4510ef913f1971383f83 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/SettingsFrame.java @@ -0,0 +1,99 @@ +package edu.ntnu.idatt2003.group6.view.frames; + +import edu.ntnu.idatt2003.group6.view.buttonbox.CustomToggleButton; +import java.util.Objects; +import javafx.geometry.Pos; +import javafx.scene.control.Slider; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; + +/** + * The SettingsFrame class creates a StackPane with a toggle group for music settings. + * + * @version 0.3.2 + * @since 0.3.2 + */ +public class SettingsFrame extends StackPane { + private final ToggleGroup musicOption; + private final CustomToggleButton musicOn; + private final CustomToggleButton musicOff; + private final Slider volumeSlider; + + /** + * Constructor for SettingsFrame. + * Creates a StackPane with a toggle group for music settings. + */ + public SettingsFrame() { + getStylesheets().add(Objects.requireNonNull(getClass() + .getResource("/stylesheets/settings.css")).toExternalForm()); + + + + musicOption = new ToggleGroup(); + musicOn = new CustomToggleButton("On", "optionButton"); + musicOff = new CustomToggleButton("Off", "optionButton"); + musicOn.setToggleGroup(musicOption); + musicOff.setToggleGroup(musicOption); + + volumeSlider = new Slider(0, 0.2, 0.02); + + Text musicText = new Text("Music: "); + musicText.getStyleClass().add("text"); + + Text volumeText = new Text("Volume: "); + volumeText.getStyleClass().add("text"); + + HBox buttons = new HBox(musicOn, musicOff); + GridPane musicPane = new GridPane(); + musicPane.add(musicText, 0, 0); + musicPane.add(buttons, 1, 0); + musicPane.add(volumeText, 0, 1); + musicPane.add(volumeSlider, 1, 1); + + setAlignment(Pos.CENTER); + getChildren().add(musicPane); + + musicPane.maxHeightProperty().bind(heightProperty().multiply(0.2)); + musicPane.maxWidthProperty().bind(widthProperty().multiply(0.5)); + musicPane.minWidthProperty().bind(widthProperty().multiply(0.5)); + } + + /** + * Gets the toggle group for the option buttons. + * + * @return the toggle group + */ + public ToggleGroup getMusicOption() { + return musicOption; + } + + /** + * Gets the button for turning music on. + * + * @return the button for turning music on + */ + public CustomToggleButton getMusicOn() { + return musicOn; + } + + /** + * Gets the button for turning music off. + * + * @return the button for turning music off + */ + public CustomToggleButton getMusicOff() { + return musicOff; + } + + /** + * Gets the volume slider. + * + * @return the volume slider + */ + public Slider getVolumeSlider() { + return volumeSlider; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/TransformationFrame.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/TransformationFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..59b2032968dd340504d1460298d038dac448d7a3 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/frames/TransformationFrame.java @@ -0,0 +1,100 @@ +package edu.ntnu.idatt2003.group6.view.frames; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosCanvas; +import javafx.geometry.Insets; +import javafx.scene.image.ImageView; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; + +/** + * This class is a view for displaying the transformation picture of a chaos game. + * The transformation picture is a heatmap of the pixels that have been visited by the chaos game. + * The more times a pixel has been visited, the more red it will be. + * The transformation picture is displayed in a frame with a border. + * + * @version 0.3.2 + * @see ChaosGame + * @see ChaosCanvas + * @since 0.3.2 + */ +public class TransformationFrame extends StackPane { + + private final BorderPane frame; + + /** + * Creates an instance of the TransformationFrame class. + */ + public TransformationFrame() { + frame = new BorderPane(); + setPadding(new Insets(10)); + frame.setBorder(new Border(new BorderStroke( + Color.BLACK, BorderStrokeStyle.SOLID, new CornerRadii(10), new BorderWidths(1)))); + getChildren().add(frame); + } + + /** + * Show the transformation picture of a chaos game in the frame. + * + * @param chaosGame The chaos game to show the transformation picture of. + */ + public void showTransformationPicture(ChaosGame chaosGame) { + getChildren().clear(); + getChildren().add(frame); + + ChaosCanvas canvas = chaosGame.getCanvas(); + int[][] canvasArray = canvas.getCanvasArray(); + int width = canvasArray[0].length; + int height = canvasArray.length; + + // Find the maximum pixel count + int maxPixelCount = 0; + for (int[] row : canvasArray) { + for (int pixelCount : row) { + if (pixelCount > maxPixelCount) { + maxPixelCount = pixelCount; + } + } + } + + WritableImage image = new WritableImage(width, height); + PixelWriter pixelWriter = image.getPixelWriter(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixelCount = canvasArray[y][x]; + // Map the pixel count to a hue value for the heatmap + // If pixelCount is 0, set color to black + if (pixelCount == 0) { + pixelWriter.setColor(x, y, Color.grayRgb(20)); + } else { + double hue = + 240 + (120 * (pixelCount / (double) maxPixelCount)); // 240 is blue, 360 is red + Color color = Color.hsb(hue, 1.0, 1.0); // saturation and brightness are set to maximum + pixelWriter.setColor(x, y, color); + } + } + } + + /* puts the finished image inside a frame */ + ImageView imageView = new ImageView(image); + frame.setCenter(imageView); + Insets padding = this.getPadding(); + imageView.setPreserveRatio(true); + imageView.fitWidthProperty().bind( + frame.widthProperty().subtract(padding.getLeft() + padding.getRight())); + imageView.fitHeightProperty().bind( + frame.heightProperty().subtract(padding.getTop() + padding.getBottom())); + imageView.setPreserveRatio(true); + getChildren().add(imageView); + } + +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineControlsView.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineControlsView.java new file mode 100644 index 0000000000000000000000000000000000000000..068e4697440bb4eb0a3425a7da03173c658ff3ba --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineControlsView.java @@ -0,0 +1,123 @@ +package edu.ntnu.idatt2003.group6.view.gamecontrols; + +import java.util.ArrayList; +import java.util.List; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; + +/** + * The AffineControlsView class is a view class that creates a scrollable pane containing affine + * transformation controls. + * The class contains a list of affine transformation controls, and a function to add a new + * transformation, a remove transformation button, + * a scroll pane, and a VBox to contain the + * affine transformation controls. + * + * @version 0.3.2 + * @see AffineTransformationControls + * @since 0.3.2 + */ +public class AffineControlsView { + private final Button addTransformButton; + private final Button removeTransformButton; + private final ScrollPane scrollPaneAffineControls; + private final VBox affineControlsBox; + private ArrayList<AffineTransformationControls> affineControlsList; + + + /** + * Constructor for the AffineControlsView class. + */ + public AffineControlsView() { + this.affineControlsList = new ArrayList<>(); + this.addTransformButton = new Button("Add Transformation"); + this.removeTransformButton = new Button("Remove Transformation"); + this.affineControlsBox = new VBox(); + affineControlsBox.alignmentProperty().setValue(javafx.geometry.Pos.CENTER); + affineControlsBox.getChildren().addAll(addTransformButton, removeTransformButton); + this.scrollPaneAffineControls = makeScrollPaneAffineControls(); + } + + /** + * Method to get a list of affine transformation controls. + * + * @return a list of affine transformation controls. + */ + public List<AffineTransformationControls> getAffineControlsList() { + return affineControlsList; + } + + /** + * Method to set a list of affine transformation controls in the view. + * + * @param affineControlsList a list of affine transformation controls. + */ + public void setAffineControlsList(List<AffineTransformationControls> affineControlsList) { + this.affineControlsList = (ArrayList<AffineTransformationControls>) affineControlsList; + updateScrollPane(); + } + + /** + * Method to add an affine transformation control to the view. + * + * @return the button to add an affine transformation control. + */ + public Button getAddTransformButton() { + return addTransformButton; + } + + /** + * Method to get the button to remove an affine transformation control. + * + * @return the button to remove an affine transformation control. + */ + public Button getRemoveTransformButton() { + return removeTransformButton; + } + + /** + * Method to update the scroll pane containing the affine transformation controls. + */ + private void updateScrollPane() { + scrollPaneAffineControls.setContent(makeScrollPaneAffineControls()); + } + + /** + * Method to get the scroll pane containing the affine transformation controls. + * + * @return the scroll pane containing the affine transformation controls. + */ + public ScrollPane getScrollPaneAffineControls() { + return scrollPaneAffineControls; + } + + /** + * Method to make a scroll pane containing the affine transformation controls. + * + * @return a scroll pane containing the affine transformation controls. + */ + private ScrollPane makeScrollPaneAffineControls() { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.getStyleClass().add("scrollPane"); + scrollPane.setFitToWidth(true); + scrollPane.hbarPolicyProperty().setValue(ScrollPane.ScrollBarPolicy.NEVER); + VBox collector = new VBox(); + collector.getStyleClass().add("vbox"); + for (AffineTransformationControls affineTransformationControl : affineControlsList) { + collector.getChildren().add(affineTransformationControl); + } + + int size = affineControlsList.size(); + if (size < 2) { + removeTransformButton.setDisable(true); + removeTransformButton.setVisible(false); + } else { + removeTransformButton.setDisable(false); + removeTransformButton.setVisible(true); + } + collector.getChildren().add(affineControlsBox); + scrollPane.setContent(collector); + return scrollPane; + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineTransformationControls.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineTransformationControls.java new file mode 100644 index 0000000000000000000000000000000000000000..b6401440fed0cbcb1990b2e834b21cec01e7518f --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AffineTransformationControls.java @@ -0,0 +1,199 @@ +package edu.ntnu.idatt2003.group6.view.gamecontrols; + +import java.util.List; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.text.Text; + +/** + * A class that represents the controls for the affine transformations in the Chaos Game. + * The class extends GridPane and contains TextFields for the affine transformation matrix A and + * the vector B. The class also contains a Text object for the transformation number and a Label + * for error messages. + */ +public class AffineTransformationControls extends GridPane { + + private final Text transformationNumber; + private final TextField a00; + private final TextField a01; + private final TextField a10; + private final TextField a11; + private final TextField x0; + private final TextField x1; + + /** + * Constructor for the AffineTransformationControls class. + * Initializes the TextFields and Labels + * for the affine transformation matrix A and the vector B. + * Also sets the style classes for the + * different nodes. + */ + public AffineTransformationControls() { + getStylesheets().add("/stylesheets/transformationAttributes.css"); + getStyleClass().add("frame"); + + transformationNumber = new Text(); + a00 = new TextField(); + a01 = new TextField(); + a10 = new TextField(); + a11 = new TextField(); + x0 = new TextField(); + x1 = new TextField(); + + a00.setPromptText("a00"); + a01.setPromptText("a01"); + a10.setPromptText("a10"); + a11.setPromptText("a11"); + x0.setPromptText("x0"); + x1.setPromptText("x1"); + + add(transformationNumber, 0, 0); + Text matrixA = new Text("A:"); + add(matrixA, 1, 0); + add(a00, 2, 0); + add(a01, 3, 0); + add(a10, 2, 1); + add(a11, 3, 1); + + Text vectorB = new Text("B:"); + add(vectorB, 4, 0); + add(x0, 5, 0); + add(x1, 5, 1); + Label errorLabel = new Label(); + add(errorLabel, 1, 2); + + setColumnSpan(errorLabel, 5); + + List<Node> children = getChildren(); + for (Node node : children) { + if (node instanceof Text) { + if (node == transformationNumber) { + node.getStyleClass().add("attributeNumber"); + } else { + node.getStyleClass().add("attributeNames"); + } + } else if (node instanceof TextField) { + node.getStyleClass().add("textField"); + } else if (node instanceof Label) { + node.getStyleClass().add("errorLabel"); + } + } + } + + /** + * Method that sets the transformation number for the transformation. + */ + public void setTransformationNumber(String transformationNumber) { + this.transformationNumber.setText(transformationNumber); + } + + /** + * method to get the string for A00 in the matrix. + * + * @return the string for A00. + */ + public String getA00() { + return a00.getText(); + } + + /** + * method to get the string for A01 in the matrix. + * + * @return the string for A01 + */ + public String getA01() { + return a01.getText(); + } + + /** + * method to get the string for A10 in the matrix. + * + * @return the string for A10. + */ + public String getA10() { + return a10.getText(); + } + + /** + * method to get the string for A11 in the matrix. + * + * @return the string for A11. + */ + public String getA11() { + return a11.getText(); + } + + /** + * method to get the string for X0 in the vector. + * + * @return the string for X0. + */ + public String getX0() { + return x0.getText(); + } + + /** + * method to get the string for X1 in the vector. + * + * @return the string for X1. + */ + public String getX1() { + return x1.getText(); + } + + /** + * Method to set the A00 value in the matrix. + * + * @param a00 the value to set. + */ + public void setA00(String a00) { + this.a00.setText(a00); + } + + /** + * Method to set the A01 value in the matrix. + * + * @param a01 the value to set. + */ + public void setA01(String a01) { + this.a01.setText(a01); + } + + /** + * Method to set the A10 value in the matrix. + * + * @param a10 the value to set. + */ + public void setA10(String a10) { + this.a10.setText(a10); + } + + /** + * Method to set the A11 value in the matrix. + * + * @param a11 the value to set. + */ + public void setA11(String a11) { + this.a11.setText(a11); + } + + /** + * Method to set the X0 value in the vector. + * + * @param x0 the value to set. + */ + public void setX0(String x0) { + this.x0.setText(x0); + } + + /** + * Method to set the X1 value in the vector. + * + * @param x1 the value to set. + */ + public void setX1(String x1) { + this.x1.setText(x1); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AttributeControls.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AttributeControls.java new file mode 100644 index 0000000000000000000000000000000000000000..b4bad8a7ada4222edc6b667e469fd2c1335b4869 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/AttributeControls.java @@ -0,0 +1,186 @@ +package edu.ntnu.idatt2003.group6.view.gamecontrols; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGameController; +import java.util.List; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.text.Text; + +/** + * A class that represents the common attributes for the transformations in the Chaos Game. + * The class extends GridPane and contains TextFields for the steps and the min and max coordinates. + * The class also contains a Text object for the attribute names and a Label for error messages. + * The class is used for both the affine and julia transformations. + * + * @version 0.3.2 + * @see AffineTransformationControls + * @see JuliaTransformationControls + * @see ChaosGameController + * @see edu.ntnu.idatt2003.group6.view.gamecontrols.JuliaTransformationControls + * @see edu.ntnu.idatt2003.group6.view.gamecontrols.AffineTransformationControls + * @since 0.3.2 + */ +public class AttributeControls extends GridPane { + + private final Text steps; + private final TextField stepsField; + private final TextField minCoordsX0Field; + private final TextField minCoordsX1Field; + private final TextField maxCoordsX0Field; + private final TextField maxCoordsX1Field; + private final Label stepsErrorLabel; + + /** + * Constructor for the AttributeControls class. + * Initializes the TextFields and Labels for the steps and the min and max coordinates. + * Also sets the style classes for the different nodes. + */ + public AttributeControls() { + getStylesheets().add("/stylesheets/transformationAttributes.css"); + getStyleClass().add("frame"); + + steps = new Text("Steps:"); + stepsField = new TextField(); + minCoordsX0Field = new TextField(); + minCoordsX1Field = new TextField(); + maxCoordsX0Field = new TextField(); + maxCoordsX1Field = new TextField(); + + stepsField.setPromptText("Enter steps"); + minCoordsX0Field.setPromptText("0.0"); + minCoordsX1Field.setPromptText("0.0"); + maxCoordsX0Field.setPromptText("0.0"); + maxCoordsX1Field.setPromptText("0.0"); + + stepsErrorLabel = new Label(); + + + + add(steps, 0, 0); + add(stepsField, 1, 0); + add(stepsErrorLabel, 0, 1); + + Text minCords = new Text("Min Cords:"); + add(minCords, 0, 2); + add(minCoordsX0Field, 1, 2); + add(minCoordsX1Field, 2, 2); + + Label minCordsErrorLabel = new Label(); + add(minCordsErrorLabel, 0, 3); + + Text maxCords = new Text("Max Cords:"); + add(maxCords, 0, 4); + add(maxCoordsX0Field, 1, 4); + add(maxCoordsX1Field, 2, 4); + + Label maxCordsErrorLabel = new Label(); + add(maxCordsErrorLabel, 0, 5); + + setColumnSpan(stepsErrorLabel, 3); + setColumnSpan(minCordsErrorLabel, 3); + setColumnSpan(maxCordsErrorLabel, 3); + + List<Node> children = getChildren(); + for (Node node : children) { + if (node instanceof Text) { + node.getStyleClass().add("attributeNames"); + } else if (node instanceof TextField) { + node.getStyleClass().add("textField"); + } else if (node instanceof Label) { + node.getStyleClass().add("errorLabel"); + } + } + } + + /** + * Returns the steps TextField. + * + * @return The steps TextField. + */ + public String getStepsField() { + return stepsField.getText(); + } + + /** + * gets the minimum coords for x0. + * + * @return the minimum coords for x0. + */ + public String getMinCoordsX0Field() { + return minCoordsX0Field.getText(); + } + + /** + * gets the minimum coords for x1. + * + * @return the minimum coords for x1. + */ + public String getMinCoordsX1Field() { + return minCoordsX1Field.getText(); + } + + /** + * gets the maximum coords for x0. + * + * @return the maximum coords for x0. + */ + public String getMaxCoordsX0Field() { + return maxCoordsX0Field.getText(); + } + + /** + * gets the maximum coords for x1. + * + * @return the maximum coords for x1. + */ + public String getMaxCoordsX1Field() { + return maxCoordsX1Field.getText(); + } + + /** + * sets the minimum coords for x0. + * + * @param minCoordsX0 the minimum coords for x0. + */ + public void setMinCoordsX0Field(String minCoordsX0) { + minCoordsX0Field.setText(minCoordsX0); + } + + /** + * sets the minimum coords for x1. + * + * @param minCoordsX1 the minimum coords for x1. + */ + public void setMinCoordsX1Field(String minCoordsX1) { + minCoordsX1Field.setText(minCoordsX1); + } + + /** + * sets the maximum coords for x0. + * + * @param maxCoordsX0 the maximum coords for x0. + */ + public void setMaxCoordsX0Field(String maxCoordsX0) { + maxCoordsX0Field.setText(maxCoordsX0); + } + + /** + * sets the maximum coords for x1. + * + * @param maxCoordsX1 the maximum coords for x1. + */ + public void setMaxCoordsX1Field(String maxCoordsX1) { + maxCoordsX1Field.setText(maxCoordsX1); + } + + /** + * removes the steps from the attribute controls. + */ + public void removeSteps() { + getChildren().remove(steps); + getChildren().remove(stepsField); + getChildren().remove(stepsErrorLabel); + } +} diff --git a/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/JuliaTransformationControls.java b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/JuliaTransformationControls.java new file mode 100644 index 0000000000000000000000000000000000000000..792ea69806d64e79510b09d167bb0fdf67c98b76 --- /dev/null +++ b/ChaosGame/src/main/java/edu/ntnu/idatt2003/group6/view/gamecontrols/JuliaTransformationControls.java @@ -0,0 +1,93 @@ +package edu.ntnu.idatt2003.group6.view.gamecontrols; + +import java.util.List; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.text.Text; + +/** + * A class that represents the controls for the affine transformations in the Chaos Game. + * + * @version 0.3.2 + * @see JuliaTransformationControls + */ +public class JuliaTransformationControls extends GridPane { + + private final TextField realC; + private final TextField imaginaryC; + + /** + * Constructor for the JuliaTransformationControls class. + */ + public JuliaTransformationControls() { + getStylesheets().add("/stylesheets/transformationAttributes.css"); + getStyleClass().add("frame"); + + realC = new TextField(); + imaginaryC = new TextField(); + + realC.setPromptText("Real part of c"); + imaginaryC.setPromptText("Imaginary part of c"); + + Text transformationNumber = new Text(); + add(transformationNumber, 0, 0); + + Text c = new Text("C:"); + add(c, 1, 0); + add(realC, 2, 0); + add(imaginaryC, 3, 0); + + List<Node> children = getChildren(); + for (Node node : children) { + if (node instanceof Text) { + if (node == transformationNumber) { + node.getStyleClass().add("attributeNumber"); + } else { + node.getStyleClass().add("attributeNames"); + } + } else if (node instanceof TextField) { + node.getStyleClass().add("textField"); + } else if (node instanceof Label) { + node.getStyleClass().add("errorLabel"); + } + } + } + + /** + * Returns the real part of the complex number c. + * + * @return the real part of the complex number c. + */ + public String getRealC() { + return realC.getText(); + } + + /** + * Returns the imaginary part of the complex number c. + * + * @return the imaginary part of the complex number c. + */ + public String getImaginaryC() { + return imaginaryC.getText(); + } + + /** + * Sets the real part of the complex number c. + * + * @param realC the real part of the complex number c. + */ + public void setRealC(String realC) { + this.realC.setText(realC); + } + + /** + * Sets the imaginary part of the complex number c. + * + * @param imaginaryC the imaginary part of the complex number c. + */ + public void setImaginaryC(String imaginaryC) { + this.imaginaryC.setText(imaginaryC); + } +} diff --git a/ChaosGame/src/main/resources/Logo.png b/ChaosGame/src/main/resources/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..21f270726ed3a4ced702aca04dd2ea430bcea161 Binary files /dev/null and b/ChaosGame/src/main/resources/Logo.png differ diff --git a/ChaosGame/src/main/resources/descriptions/chaosGameDescription.txt b/ChaosGame/src/main/resources/descriptions/chaosGameDescription.txt deleted file mode 100644 index fb6d7b0f676fa3884a21e166a4b84be05864bd4a..0000000000000000000000000000000000000000 --- a/ChaosGame/src/main/resources/descriptions/chaosGameDescription.txt +++ /dev/null @@ -1,6 +0,0 @@ -Affine2D # Type of transformation -0.0, 0.0 # Lower left -1.0, 1.0 # Upper right -0.5, 0.0, 0.0, 0.5, 0.0, 0.0 # 1nd transform -0.5, 0.0, 0.0, 0.5, 0.25, 0.5 # 2nd transform -0.5, 0.0, 0.0, 0.5, 0.5, 0.0 # 3nd transform diff --git a/ChaosGame/src/main/resources/mp3/Legio_Symphonica.mp3 b/ChaosGame/src/main/resources/mp3/Legio_Symphonica.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3e90123cbc402f2ecda3df299afbe936f878b7c7 Binary files /dev/null and b/ChaosGame/src/main/resources/mp3/Legio_Symphonica.mp3 differ diff --git a/ChaosGame/src/main/resources/stylesheets/alert.css b/ChaosGame/src/main/resources/stylesheets/alert.css new file mode 100644 index 0000000000000000000000000000000000000000..346d509a0358f23390a8175d501c68c39d1aa134 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/alert.css @@ -0,0 +1,66 @@ +.dialog-pane { + -fx-background-color: #181C82; + -fx-border-color: #c26d37; + -fx-border-radius: 10; + -fx-border-width: 2; + -fx-background-radius: 10; + -fx-padding: 10; + + -fx-pref-height: 200; + -fx-pref-width: 400; +} + +.vBox { + -fx-background-color: rgba(196, 0, 0, 0.9); + -fx-background-radius: 6; +} + +.errorText { + -fx-font-size: 16; + -fx-font-weight: bold; + -fx-fill: #ffffff; +} + +.errorDescription { + -fx-font-size: 14; + -fx-font-weight: bold; + -fx-font-style: italic; + -fx-fill: #ffffff; +} + +.informationText { + -fx-font-size: 16; + -fx-fill: #ffffff; +} + +.dialog-pane .button { + -fx-background-radius: 12; + -fx-border-radius: 10; + -fx-border-width: 2; + -fx-text-fill: #ffffff; + -fx-font-weight: bold; + -fx-pref-height: 40; + -fx-pref-width: 100; + -fx-font-size: 14; + -fx-alignment: center; +} + +.positiveButton { + -fx-background-color: #c26d37; + -fx-border-color: #0f2465; +} + +.negativeButton { + -fx-background-color: #0f2465; + -fx-border-color: #c26d37; +} + +.textField { + -fx-background-color: #0F2465FF; + -fx-text-fill: #ffffff; + -fx-prompt-text-fill: #565656; + -fx-font-size: 14; + -fx-border-color: #c26d37; + -fx-border-width: 2; + -fx-border-radius: 5; +} \ No newline at end of file diff --git a/ChaosGame/src/main/resources/stylesheets/buttonBox.css b/ChaosGame/src/main/resources/stylesheets/buttonBox.css new file mode 100644 index 0000000000000000000000000000000000000000..399162aa425ee41ddb79fee8a8220470a9239224 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/buttonBox.css @@ -0,0 +1,33 @@ +.buttonBox { + -fx-background-color: transparent; + -fx-border-width: 0; + -fx-padding: 0 40 0 40; +} +.menuButton { + -fx-font-size: 14; + -fx-alignment: center-left; +} +.subMenuButton { + -fx-font-size: 12; + -fx-alignment: center-left; +} +.menuButton, +.subMenuButton { + -fx-background-color: radial-gradient(center 50% 50%, radius 100%, #0f2465, transparent); + -fx-background-radius: 0; + -fx-border-width: 0; + -fx-text-fill: #ffffff; + -fx-font-weight: bold; + -fx-padding: 10 20 10 20; + -fx-pref-width: infinity; + -fx-pref-height: 40; +} +.menuButton:hover, +.menuButton:pressed, +.menuButton:selected, +.subMenuButton:hover, +.subMenuButton:pressed, +.subMenuButton:selected { + -fx-background-color: linear-gradient(to right, #c26d37 50%, transparent); + -fx-text-fill: #ffffff; +} diff --git a/ChaosGame/src/main/resources/stylesheets/exitBox.css b/ChaosGame/src/main/resources/stylesheets/exitBox.css new file mode 100644 index 0000000000000000000000000000000000000000..17a5b36ad9e5e2539ff1858c3578be23a86559b6 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/exitBox.css @@ -0,0 +1,17 @@ +.label { + -fx-font-size: 20px; + -fx-font-weight: bold; + -fx-text-fill: #eeeeee; +} + +.text { + -fx-font-size: 18px; + -fx-font-weight: bold; + -fx-text-fill: #eeeeee; +} + +.box { + -fx-padding: 0 0 0 10; + -fx-background-color: radial-gradient(center 20% 50%, radius 100%, #000000, rgba(2, 2, 2, 0.85)); + -fx-border-width: 0px; +} \ No newline at end of file diff --git a/ChaosGame/src/main/resources/globals.css b/ChaosGame/src/main/resources/stylesheets/globals.css similarity index 70% rename from ChaosGame/src/main/resources/globals.css rename to ChaosGame/src/main/resources/stylesheets/globals.css index 9a87450f25ccbcbe79698e0e951588fabf194357..9c917f65c4a4872ebc670d84f820adc864de3ff6 100644 --- a/ChaosGame/src/main/resources/globals.css +++ b/ChaosGame/src/main/resources/stylesheets/globals.css @@ -2,26 +2,25 @@ -fx-background-color: #181C82; -fx-border-width: 0; } - .buttonBox { -fx-background-color: transparent; -fx-border-width: 0; -fx-padding: 0 40 0 40; } - - .menuButton { -fx-font-size: 14; + -fx-alignment: center-left; } .subMenuButton { -fx-font-size: 12; + -fx-alignment: center-left; } .menuButton, .subMenuButton { - -fx-background-color: #a3a9c4; + -fx-background-color: radial-gradient(center 50% 50%, radius 100%, #0f2465, transparent); -fx-background-radius: 0; -fx-border-width: 0; - -fx-text-fill: #000000; + -fx-text-fill: #ffffff; -fx-font-weight: bold; -fx-padding: 10 20 10 20; -fx-pref-width: infinity; @@ -33,6 +32,6 @@ .subMenuButton:hover, .subMenuButton:pressed, .subMenuButton:selected { - -fx-background-color: #3436b0; + -fx-background-color: linear-gradient(to right, #c26d37 50%, transparent); -fx-text-fill: #ffffff; } diff --git a/ChaosGame/src/main/resources/stylesheets/homePage.css b/ChaosGame/src/main/resources/stylesheets/homePage.css new file mode 100644 index 0000000000000000000000000000000000000000..8280f274516c64187784daaba04de52a3c135612 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/homePage.css @@ -0,0 +1,4 @@ +.homePage { + -fx-background-color: #181C82; + -fx-border-width: 0; +} \ No newline at end of file diff --git a/ChaosGame/src/main/resources/stylesheets/settings.css b/ChaosGame/src/main/resources/stylesheets/settings.css new file mode 100644 index 0000000000000000000000000000000000000000..5a1bddfaa0bae4f99d19d0462b0af89fb994456e --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/settings.css @@ -0,0 +1,34 @@ +.text { + -fx-font-size: 20px; + -fx-font-weight: bold; + -fx-font-family: italic; + -fx-fill: #c4c4c4; + -fx-pref-height: 40; +} + +.optionButton { + -fx-font-size: 14; + -fx-alignment: center-left; + -fx-background-color: radial-gradient(center 50% 50%, radius 100%, #0f2465, transparent); + -fx-background-radius: 0; + -fx-border-width: 0; + -fx-text-fill: #ffffff; + -fx-font-weight: bold; + -fx-padding: 10 20 10 20; + -fx-pref-width: 80; + -fx-pref-height: 40; +} + +.optionButton:hover, +.optionButton:selected { + -fx-background-color: linear-gradient(to right, #c26d37 50%, transparent); + -fx-text-fill: #ffffff; +} + +.slider .track { + -fx-background-color: #12165b; +} + +.slider .thumb { + -fx-background-color: #c26d37; +} \ No newline at end of file diff --git a/ChaosGame/src/main/resources/stylesheets/transformationAttributes.css b/ChaosGame/src/main/resources/stylesheets/transformationAttributes.css new file mode 100644 index 0000000000000000000000000000000000000000..efb1757b69f141aab44d2b47a75ea2c88fa9b128 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/transformationAttributes.css @@ -0,0 +1,70 @@ +.textField { + -fx-background-color: #0F2465FF; + -fx-text-fill: #ffffff; + -fx-prompt-text-fill: #565656; + -fx-font-size: 14; + -fx-border-color: #c26d37; + -fx-border-width: 2; + -fx-border-radius: 5; +} + +.attributeNames { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-fill: #cccccc; +} + +.errorLabel { + -fx-font-size: 10px; + -fx-text-fill: #ff0000; +} + +.attributeNumber { + -fx-font-size: 20px; + -fx-font-weight: bold; + -fx-font-family: italic; + -fx-text-fill: #c4c4c4; +} + +.frame { + -fx-border-width: 0px; + -fx-padding: 20 40 0 40; + -fx-hgap: 10; + -fx-vgap: 10; +} + +.vbox { + -fx-background-color: #181C82; +} + +.scroll-bar, +.scroll-pane, +.scroll-pane .viewport { + -fx-background-color: #181C82; +} + +.scroll-bar .thumb { + -fx-background-color: #c26d37; +} + +.button { + -fx-background-color: radial-gradient(center 50% 50%, radius 100%, #0f2465, transparent); + -fx-background-radius: 0; + -fx-border-width: 0; + -fx-text-fill: #ffffff; + -fx-font-weight: bold; + -fx-padding: 10 20 10 20; + -fx-pref-height: 40; + -fx-pref-width: 250; + -fx-font-size: 12; + -fx-alignment: center; +} + +.button:hover, +.button:pressed, +.button:selected { + -fx-background-color: #c26d37; + -fx-text-fill: #ffffff; + + +} \ No newline at end of file diff --git a/ChaosGame/src/main/resources/stylesheets/transformationPicture.css b/ChaosGame/src/main/resources/stylesheets/transformationPicture.css new file mode 100644 index 0000000000000000000000000000000000000000..552c8f351a73d2ac3764fc8780f3c253af4946d2 --- /dev/null +++ b/ChaosGame/src/main/resources/stylesheets/transformationPicture.css @@ -0,0 +1,7 @@ +.frame { + -fx-border-width: 4; + -fx-border-color: #0f2465; + -fx-border-radius: 10; + -fx-background-radius: 12; + -fx-effect: dropshadow(gaussian, rgba(19, 19, 19, 0.8), 10, 2, 10, 6); +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/controller/ChaosGameTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/controller/ChaosGameTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bff4cd721c335c6a3240efaed9a68816436f38eb --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/controller/ChaosGameTest.java @@ -0,0 +1,155 @@ +package edu.ntnu.idatt2003.group6.controller; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import edu.ntnu.idatt2003.group6.controller.chaosgame.ChaosGame; +import edu.ntnu.idatt2003.group6.controller.chaosgame.GameState; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosCanvas; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ChaosGameTest { + + private ChaosGame chaosGame; + private ChaosGameDescription chaosGameDescription; + + @BeforeEach + void setUp() { + chaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.SIERPINSKI); + chaosGame = new ChaosGame(chaosGameDescription, 700, 700); + } + + @Nested + @DisplayName("Positive tests for ChaosGame") + class methodsDoesNotThrowException { + + @Test + @DisplayName("ChaosGame constructor creates an instance of ChaosGame without throwing an exception") + void chaosGameDoesntThrowOnNewChaosGame() { + assertNotNull(chaosGame); + } + + @Test + @DisplayName("getState method returns the expected value without throwing an exception") + void evaluateChaosGameGetState() { + chaosGame.runSteps(1); + GameState result = chaosGame.getState(); + assertEquals(GameState.DONE, result); + } + + @Test + @DisplayName("runSteps method executes without throwing an exception") + void runStepsDoesntThrow() { + assertDoesNotThrow(() -> chaosGame.runSteps(1)); + } + + @Test + @DisplayName("addObserver method executes without throwing an exception") + void addObserverDoesntThrow() { + assertDoesNotThrow(() -> chaosGame.addObserver((o) -> { + })); + } + + @Test + @DisplayName("removeObserver method executes without throwing an exception") + void removeObserverDoesntThrow() { + assertDoesNotThrow(() -> chaosGame.removeObserver((o) -> { + })); + } + + @Test + @DisplayName("ChaosGame constructor throws on null description") + void chaosGameThrowsOnNullDescription() { + assertThrows(IllegalArgumentException.class, () -> new ChaosGame(null, 700, 700)); + } + + @Test + @DisplayName("ChaosGame constructor throws on invalid width") + void chaosGameThrowsOnInvalidWidth() { + assertThrows(IllegalArgumentException.class, + () -> new ChaosGame(chaosGameDescription, 0, 700)); + } + + @Test + @DisplayName("ChaosGame constructor throws on invalid height") + void chaosGameThrowsOnInvalidHeight() { + assertThrows(IllegalArgumentException.class, + () -> new ChaosGame(chaosGameDescription, 700, 0)); + } + + @Test + @DisplayName("getCanvas method returns the canvas") + void evaluateChaosGameGetCanvas() { + ChaosCanvas result = chaosGame.getCanvas(); + assertNotNull(result); + } + + @Test + @DisplayName("getChaosGameDescription method returns the chaos game description") + void evaluateChaosGameGetChaosGameDescription() { + ChaosGameDescription result = chaosGame.getChaosGameDescription(); + assertNotNull(result); + } + + @Test + @DisplayName("getGameType method returns the game type") + void evaluateChaosGameGetGameType() { + GameType result = chaosGame.getGameType(); + assertEquals(GameType.AFFINE, result); + } + + + } + + @Nested + @DisplayName("Negative tests for ChaosGame") + class methodsThrowsExceptions { + + @Test + @DisplayName("ChaosGame constructor throws an exception when description is null") + void chaosGameThrowsOnNullDescription() { + assertThrows(IllegalArgumentException.class, () -> new ChaosGame(null, 700, 700)); + } + + @Test + @DisplayName("ChaosGame constructor throws an exception when width is less than or equal to 0") + void chaosGameThrowsOnInvalidWidth() { + assertThrows(IllegalArgumentException.class, + () -> new ChaosGame(chaosGameDescription, 0, 700)); + } + + @Test + @DisplayName("ChaosGame constructor throws an exception when height is less than or equal to 0") + void chaosGameThrowsOnInvalidHeight() { + assertThrows(IllegalArgumentException.class, + () -> new ChaosGame(chaosGameDescription, 700, 0)); + } + + @Test + @DisplayName("runSteps method throws an exception when steps is less than 1") + void runStepsThrowsOnInvalidSteps() { + assertThrows(IllegalArgumentException.class, () -> chaosGame.runSteps(-1)); + } + + @Test + @DisplayName("addObserver method throws an exception when observer is null") + void addObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> chaosGame.addObserver(null)); + } + + @Test + @DisplayName("removeObserver method throws an exception when observer is null") + void removeObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> chaosGame.removeObserver(null)); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvasTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvasTest.java new file mode 100644 index 0000000000000000000000000000000000000000..56ab7d5ad7dd732c6eedf76ddfd0696bce4dbf19 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosCanvasTest.java @@ -0,0 +1,125 @@ +package edu.ntnu.idatt2003.group6.models.chaosgame; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ChaosCanvasTest { + + ChaosCanvas chaosCanvas; + + @BeforeEach + public void setUp() { + chaosCanvas = new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1)); + } + + @Nested + @DisplayName("Positive tests for ChaosCanvas") + class methodsDoesNotThrowException { + @Test + @DisplayName("ChaosCanvas constructor creates an instance of ChaosCanvas " + + "without throwing an exception") + void chaosCanvasDoesntThrowOnNewChaosCanvas() { + try { + ChaosCanvas testChaosCanvas = + new ChaosCanvas(100, 100, new Vector2D(0, 0), new Vector2D(1, 1)); + assertNotNull(testChaosCanvas); + } catch (IllegalArgumentException e) { + fail("The test chaosCanvasDoesntThrowOnNewChaosCanvas failed with the exception " + + "message " + e.getMessage()); + } + } + + @Test + @DisplayName("getPixel method returns the expected value without throwing an exception") + void evaluateChaosCanvasGetPixel() { + Vector2D point = new Vector2D(0.5, 0.5); + int result = chaosCanvas.getPixel(point); + assertEquals(0, result); + } + + @Test + @DisplayName("putPixel method returns the expected value without throwing an exception") + void evaluateChaosCanvasPutPixel() { + Vector2D point = new Vector2D(0.5, 0.5); + chaosCanvas.putPixel(point); + int result = chaosCanvas.getPixel(point); + assertEquals(1, result); + } + + @Test + @DisplayName("getCanvasArray method returns the expected value without throwing an exception") + void evaluateChaosCanvasGetCanvasArray() { + int[][] result = chaosCanvas.getCanvasArray(); + assertEquals(100, result.length); + assertEquals(100, result[0].length); + } + + @Test + @DisplayName("clear method returns the expected value without throwing an exception") + void evaluateChaosCanvasClear() { + chaosCanvas.putPixel(new Vector2D(0.5, 0.5)); + chaosCanvas.clear(); + int[][] result = chaosCanvas.getCanvasArray(); + assertEquals(0, result[50][50]); + } + + @Test + @DisplayName("getMatrixForIndices method returns the expected value without throwing an exception") + void evaluateChaosCanvasGetMatrixForIndices() { + try { + var method = ChaosCanvas.class.getDeclaredMethod("getMatrixForIndices"); + method.setAccessible(true); + var result = (Matrix2x2) method.invoke(chaosCanvas); + assertNotNull(result); + } catch (Exception e) { + fail("The test evaluateChaosCanvasGetMatrixForIndices failed with the exception " + + "message " + e.getMessage()); + } + } + } + + @Nested + @DisplayName("Negative tests for ChaosCanvas") + class methodsThrowsExceptions { + @Test + @DisplayName("ChaosCanvas constructor throws an exception when width is less than or equal to 0") + void chaosCanvasThrowsOnInvalidWidth() { + assertThrows(IllegalArgumentException.class, () -> + new ChaosCanvas(0, 100, new Vector2D(0, 0), new Vector2D(1, 1))); + } + + @Test + @DisplayName("ChaosCanvas constructor throws an exception when height is less than or equal to 0") + void chaosCanvasThrowsOnInvalidHeight() { + assertThrows(IllegalArgumentException.class, () -> + new ChaosCanvas(100, 0, new Vector2D(0, 0), new Vector2D(1, 1))); + } + + @Test + @DisplayName("getPixel method throws an exception when point is out of bounds") + void evaluateChaosCanvasGetPixelThrows() { + Vector2D point = new Vector2D(2, 2); + assertThrows(IllegalArgumentException.class, () -> + chaosCanvas.getPixel(point)); + } + + @Test + @DisplayName("putPixel method throws an exception when point is out of bounds") + void evaluateChaosCanvasPutPixelThrows() { + Vector2D point = new Vector2D(2, 2); + assertThrows(IllegalArgumentException.class, () -> + chaosCanvas.putPixel(point)); + } + } + + +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactoryTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..146da27f7db1fac8323cb6369fda79edd0627adb --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionFactoryTest.java @@ -0,0 +1,107 @@ +package edu.ntnu.idatt2003.group6.models.chaosgame; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ChaosGameDescriptionFactoryTest { + + + @Nested + @DisplayName("Positive tests for ChaosGameDescriptionFactory") + class methodsDoesNotThrowException { + @Test + @DisplayName("createChaosGameDescription method creates an instance of Sierpinski " + + "without throwing an exception") + void createChaosGameDescriptionSierpinskiDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.SIERPINSKI); + assertNotNull(testChaosGameDescription); + } catch (Exception e) { + fail( + "The test createChaosGameDescriptionDoesntThrowOnNewChaosGameDescription failed" + + "with the exception message " + e.getMessage()); + } + } + + @Test + @DisplayName("createChaosGameDescription method creates an instance of Barnsley " + + "without throwing an exception") + void createChaosGameDescriptionBarnsleyDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.BARNSLEY); + assertNotNull(testChaosGameDescription); + } catch (Exception e) { + fail( + "The test createChaosGameDescriptionBarnsleyDoesntThrowOnNewChaosGameDescription " + + "failed with the exception message " + e.getMessage()); + } + } + + @Test + @DisplayName("createChaosGameDescription method creates an instance of Julia " + + "without throwing an exception") + void createChaosGameDescriptionJuliaDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.JULIA); + assertNotNull(testChaosGameDescription); + } catch (Exception e) { + fail( + "The test createChaosGameDescriptionJuliaDoesntThrowOnNewChaosGameDescription " + + "failed with the exception message " + e.getMessage()); + } + } + + @Test + @DisplayName("createChaosGameDescription method creates an instance of Julia_New " + + "without throwing an exception") + void createChaosGameDescriptionJuliaNewDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.JULIA_NEW); + assertNotNull(testChaosGameDescription); + } catch (Exception e) { + fail( + "The test createChaosGameDescriptionJuliaNewDoesntThrowOnNewChaosGameDescription " + + "failed with the exception message " + e.getMessage()); + } + } + + @Test + @DisplayName("createChaosGameDescription method creates an instance of Affine_New " + + "without throwing an exception") + void createChaosGameDescriptionAffineNewDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.AFFINE_NEW); + assertNotNull(testChaosGameDescription); + } catch (Exception e) { + fail( + "The test createChaosGameDescriptionAffineNewDoesntThrowOnNewChaosGameDescription " + + "failed with the exception message " + e.getMessage()); + } + } + + @Test + @DisplayName("createChaosGameDescription method throws an exception when gameType is null") + void createChaosGameDescriptionThrowsOnNullGameType() { + assertThrows(IllegalArgumentException.class, () -> + ChaosGameDescriptionFactory.createChaosGameDescription(null)); + } + + @Test + @DisplayName("createChaosGameDescription method throws an exception when gameType is invalid") + void createChaosGameDescriptionThrowsOnInvalidGameType() { + assertThrows(IllegalArgumentException.class, () -> + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.valueOf("INVALID"))); + } + + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b3ac6277bf7347c82198415247a6ddc83d016353 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/ChaosGameDescriptionTest.java @@ -0,0 +1,142 @@ +package edu.ntnu.idatt2003.group6.models.chaosgame; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; +import edu.ntnu.idatt2003.group6.models.transformation.AffineTransform2D; +import edu.ntnu.idatt2003.group6.models.transformation.Transform2D; +import edu.ntnu.idatt2003.group6.models.vector.Vector2D; +import edu.ntnu.idatt2003.group6.utils.exceptions.IllegalChaosGameException; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ChaosGameDescriptionTest { + + ChaosGameDescription chaosGameDescription; + List<Transform2D> transformations; + + @BeforeEach + public void setUp() { + transformations = Arrays.asList(new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0) + ), new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0) + ), new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5) + )); + chaosGameDescription = + new ChaosGameDescription(transformations, new Vector2D(0, 0), new Vector2D(1, 1)); + } + + @Nested + @DisplayName("Positive tests for ChaosGameDescription") + class methodsDoesNotThrowException { + @Test + @DisplayName("ChaosGameDescription constructor creates an instance of ChaosGameDescription " + + "without throwing an exception") + void chaosGameDescriptionDoesntThrowOnNewChaosGameDescription() { + try { + ChaosGameDescription testChaosGameDescription = + new ChaosGameDescription(transformations, new Vector2D(0, 0), new Vector2D(1, 1)); + assertNotNull(testChaosGameDescription); + } catch (IllegalChaosGameException e) { + fail( + "The test chaosGameDescriptionDoesntThrowOnNewChaosGameDescription failed with the exception " + + "message " + e.getMessage()); + } + } + + @Test + @DisplayName("getTransformations method returns the expected value without throwing an exception") + void evaluateChaosGameDescriptionGetTransformations() { + List<Transform2D> result = chaosGameDescription.getTransformations(); + assertEquals(transformations, result); + } + + @Test + @DisplayName("getMinCoords method returns the expected value without throwing an exception") + void evaluateChaosGameDescriptionGetMinCoords() { + Vector2D result = chaosGameDescription.getMinCoords(); + assertEquals(0.0, result.getX0()); + assertEquals(0.0, result.getX1()); + } + + @Test + @DisplayName("getMaxCoords method returns the expected value without throwing an exception") + void evaluateChaosGameDescriptionGetMaxCoords() { + Vector2D result = chaosGameDescription.getMaxCoords(); + assertEquals(1.0, result.getX0()); + assertEquals(1.0, result.getX1()); + } + + @Test + @DisplayName("setTransformations method sets the transformations correctly") + void evaluateChaosGameDescriptionSetTransformations() { + List<Transform2D> newTransformations = Arrays.asList(new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0, 0) + ), new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.5, 0) + ), new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2D(0.25, 0.5) + )); + chaosGameDescription.setTransformations(newTransformations); + assertEquals(newTransformations, chaosGameDescription.getTransformations()); + } + + @Test + @DisplayName("setMinCoords method sets the minimum coordinates correctly") + void evaluateChaosGameDescriptionSetMinCoords() { + Vector2D newMinCoords = new Vector2D(0.5, 0.5); + chaosGameDescription.setMinCoords(newMinCoords); + assertEquals(newMinCoords, chaosGameDescription.getMinCoords()); + } + + @Test + @DisplayName("setMaxCoords method sets the maximum coordinates correctly") + void evaluateChaosGameDescriptionSetMaxCoords() { + Vector2D newMaxCoords = new Vector2D(1.5, 1.5); + chaosGameDescription.setMaxCoords(newMaxCoords); + assertEquals(newMaxCoords, chaosGameDescription.getMaxCoords()); + } + + @Test + @DisplayName("getGameType method returns the expected value") + void evaluateChaosGameDescriptionGetGameType() { + GameType result = chaosGameDescription.getGameType(); + + assertEquals(GameType.AFFINE, result); + } + } + + @Nested + @DisplayName("Negative tests for ChaosGameDescription") + class MethodsThrowsExceptions { + @Test + @DisplayName("ChaosGameDescription constructor throws an exception when transformations is null") + void chaosGameDescriptionThrowsOnNullTransformations() { + assertThrows(IllegalChaosGameException.class, () -> + new ChaosGameDescription(null, new Vector2D(0, 0), new Vector2D(1, 1))); + } + + @Test + @DisplayName("ChaosGameDescription constructor throws an exception when minCoords is null") + void chaosGameDescriptionThrowsOnNullMinCoords() { + assertThrows(IllegalChaosGameException.class, () -> + new ChaosGameDescription(transformations, null, new Vector2D(1, 1))); + } + + @Test + @DisplayName("ChaosGameDescription constructor throws an exception when maxCoords is null") + void chaosGameDescriptionThrowsOnNullMaxCoords() { + assertThrows(IllegalChaosGameException.class, () -> + new ChaosGameDescription(transformations, new Vector2D(0, 0), null)); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameTypeTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameTypeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7bb8f6de251cbea57ad988a42cd4e9c5fc61ddd6 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/chaosgame/GameTypeTest.java @@ -0,0 +1,23 @@ +package edu.ntnu.idatt2003.group6.models.chaosgame; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class GameTypeTest { + + @Test + void testEnumValues() { + // Test that the enum contains the expected number of values + assertEquals(6, GameType.values().length); + + // Test that the enum contains the expected values + assertEquals(GameType.SIERPINSKI, GameType.valueOf("SIERPINSKI")); + assertEquals(GameType.BARNSLEY, GameType.valueOf("BARNSLEY")); + assertEquals(GameType.JULIA, GameType.valueOf("JULIA")); + assertEquals(GameType.AFFINE, GameType.valueOf("AFFINE")); + assertEquals(GameType.JULIA_NEW, GameType.valueOf("JULIA_NEW")); + assertEquals(GameType.AFFINE_NEW, GameType.valueOf("AFFINE_NEW")); + } + +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileModelTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..db053d1544c0bf07e07715ee62ac83c0e7409f8e --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileModelTest.java @@ -0,0 +1,155 @@ +package edu.ntnu.idatt2003.group6.models.files; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +//Disabled since maven struggles to run the files, even though they can run locally +@Disabled +class FileModelTest { + + private FileModel fileModel; + private ChaosGameDescription chaosGameDescription; + + @BeforeEach + void setUp() { + fileModel = FileModel.getInstance(); + chaosGameDescription = ChaosGameDescriptionFactory. + createChaosGameDescription(GameType.SIERPINSKI); + } + + + @Nested + @DisplayName("Positive tests for FileModel") + class methodsDoesNotThrowException { + + @Test + @DisplayName("getInstance method creates an instance of FileModel without throwing an exception") + void getInstanceDoesntThrow() { + assertNotNull(fileModel); + } + + @Test + @DisplayName("getFiles method executes without throwing an exception") + void getFilesDoesntThrow() { + assertDoesNotThrow(() -> fileModel.getFiles()); + } + + @Test + @DisplayName("addFile method executes without throwing an exception") + void addFileDoesntThrow() { + assertDoesNotThrow(() -> fileModel.addFile("testFile", chaosGameDescription)); + } + + @Test + @DisplayName("getFile method executes without throwing an exception") + void getFileDoesntThrow() { + assertDoesNotThrow(() -> fileModel.getFile("testFile")); + } + + @Test + @DisplayName("removeFile method executes without throwing an exception") + void removeFileDoesntThrow() { + assertDoesNotThrow(() -> fileModel.removeFile("testFile")); + } + + @Test + @DisplayName("setState method executes without throwing an exception") + void setStateDoesntThrow() { + assertDoesNotThrow(() -> fileModel.setState(FileState.FILES_CHANGED)); + } + + @Test + @DisplayName("addObserver method executes without throwing an exception") + void addObserverDoesntThrow() { + FileObserver observer = state -> { + // Do nothing + }; + assertDoesNotThrow(() -> fileModel.addObserver(observer)); + } + + @Test + @DisplayName("removeObserver method executes without throwing an exception") + void removeObserverDoesntThrow() { + FileObserver observer = state -> { + // Do nothing + }; + fileModel.addObserver(observer); + assertDoesNotThrow(() -> fileModel.removeObserver(observer)); + } + } + + @Nested + @DisplayName("Negative tests for FileModel") + class methodsThrowsExceptions { + + @Test + @DisplayName("getFiles method throws an exception when file does not exist") + void getFilesThrowsOnNonExistingFile() { + assertThrows(IllegalArgumentException.class, () -> fileModel.getFile(null)); + } + + @Test + @DisplayName("addFile method throws an exception when fileName is null") + void addFileThrowsOnNullFileName() { + assertThrows(IllegalArgumentException.class, () -> fileModel.addFile(null, chaosGameDescription)); + } + + @Test + @DisplayName("addFile method throws an exception when chaosGameDescription is null") + void addFileThrowsOnNullChaosGameDescription() { + assertThrows(IllegalArgumentException.class, () -> fileModel.addFile("testFile", null)); + } + + @Test + @DisplayName("getFile method throws an exception when fileName is null") + void getFileThrowsOnNullFileName() { + assertThrows(IllegalArgumentException.class, () -> fileModel.getFile(null)); + } + + @Test + @DisplayName("getFile method throws an exception when file does not exist") + void getFileThrowsOnNonExistingFile() { + assertThrows(IllegalArgumentException.class, () -> fileModel.getFile("nonExistingFile")); + } + + @Test + @DisplayName("removeFile method throws an exception when fileName is null") + void removeFileThrowsOnNullFileName() { + assertThrows(IllegalArgumentException.class, () -> fileModel.removeFile(null)); + } + + @Test + @DisplayName("removeFile method throws an exception when file does not exist") + void removeFileThrowsOnNonExistingFile() { + assertThrows(IllegalArgumentException.class, () -> fileModel.removeFile("nonExistingFile")); + } + + @Test + @DisplayName("setState method throws an exception when state is null") + void setStateThrowsOnNullState() { + assertThrows(IllegalArgumentException.class, () -> fileModel.setState(null)); + } + + @Test + @DisplayName("addObserver method throws an exception when observer is null") + void addObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> fileModel.addObserver(null)); + } + + @Test + @DisplayName("removeObserver method throws an exception when observer is null") + void removeObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> fileModel.removeObserver(null)); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FilePathTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FilePathTest.java new file mode 100644 index 0000000000000000000000000000000000000000..33fb174ce3517c0f05b35528480e02ea25e72b7d --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FilePathTest.java @@ -0,0 +1,19 @@ +package edu.ntnu.idatt2003.group6.models.files; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FilePathTest { + + @Test + void testEnumValues() { + // Test that the enum contains the expected number of values + assertEquals(3, FilePath.values().length); + // Test that the enum contains the expected values + assertEquals(FilePath.DESCRIPTIONS, FilePath.valueOf("DESCRIPTIONS")); + assertEquals(FilePath.IMAGES, FilePath.valueOf("IMAGES")); + assertEquals(FilePath.MUSIC, FilePath.valueOf("MUSIC")); + assertEquals("src/main/resources/descriptions/", FilePath.DESCRIPTIONS.getPath()); + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileStateTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileStateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..88d14ce4bb33dc68939c499592042f6e6435d666 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/files/FileStateTest.java @@ -0,0 +1,19 @@ +package edu.ntnu.idatt2003.group6.models.files; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FileStateTest { + + @Test + void testEnumValues() { + // Test that the enum contains the expected number of values + assertEquals(3, FileState.values().length); + + // Test that the enum contains the expected values + assertEquals(FileState.FILES_UPDATING, FileState.valueOf("FILES_UPDATING")); + assertEquals(FileState.FILES_UNCHANGED, FileState.valueOf("FILES_UNCHANGED")); + assertEquals(FileState.FILES_CHANGED, FileState.valueOf("FILES_CHANGED")); + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2Test.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2Test.java index 7e952d40a4d8f7d457350c308cd5c56a038ba933..d3fb777f82ba86b282135fedb7ba6d7b20d57c1e 100644 --- a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2Test.java +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/matrix/Matrix2x2Test.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; -import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,6 +22,16 @@ public class Matrix2x2Test { @Nested @DisplayName("Positive tests for Matrix2x2") class methodsDoesNotThrowException { + + @Test + void testRecordFields() { + Matrix2x2 matrix = new Matrix2x2(1, 2, 3, 4); + assertEquals(1, matrix.a00()); + assertEquals(2, matrix.a01()); + assertEquals(3, matrix.a10()); + assertEquals(4, matrix.a11()); + } + @Test @DisplayName("Matrix2x2 constructor creates an instance of Matrix2x2 " + "without throwing an exception") @@ -49,17 +58,16 @@ public class Matrix2x2Test { @Nested @DisplayName("Negative tests for Matrix2x2") class methodsThrowsExceptions { - @Test - @DisplayName("Matrix2x2 constructor throws an exception when a00 is invalid") - void matrix2x2ThrowsOnInvalidA00() { + @DisplayName("Matrix2x2 multiply throws an exception when the vector is invalid") + void matrix2x2ThrowsOnInvalidVector() { try { - new Matrix2x2(Double.NaN, 2, 3, 4); - fail("The test matrix2x2ThrowsOnInvalidA00 failed"); + matrix.multiply(null); + fail("The test matrix2x2ThrowsOnInvalidVector failed"); } catch (IllegalArgumentException e) { - assertEquals("The string for the parameter a00 was not valid, try to register " + - "again", e.getMessage()); + assertEquals("The given vector cannot be null.", e.getMessage()); } } + } } \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModelTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1cd3a86ec67d6481e3954adf05b9dd19b3842930 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationModelTest.java @@ -0,0 +1,77 @@ +package edu.ntnu.idatt2003.group6.models.navigation; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class NavigationModelTest { + + private NavigationModel navigationModel; + + @BeforeEach + void setUp() { + navigationModel = new NavigationModel(); + } + + @Nested + @DisplayName("Positive tests for NavigationModel") + class methodsDoesNotThrowException { + + @Test + @DisplayName("getState method returns the expected value without throwing an exception") + void getStateDoesntThrow() { + navigationModel.setState(NavigationState.MENU); + assertEquals(NavigationState.MENU, navigationModel.getState()); + } + + @Test + @DisplayName("setState method sets the state correctly without throwing an exception") + void setStateDoesntThrow() { + navigationModel.setState(NavigationState.SETTINGS); + assertEquals(NavigationState.SETTINGS, navigationModel.getState()); + } + + @Test + @DisplayName("addObserver method adds an observer correctly without throwing an exception") + void addObserverDoesntThrow() { + NavigationObserver observer = state -> {}; + assertDoesNotThrow(() -> navigationModel.addObserver(observer)); + } + + @Test + @DisplayName("removeObserver method removes an observer correctly without throwing an exception") + void removeObserverDoesntThrow() { + NavigationObserver observer = state -> {}; + navigationModel.addObserver(observer); + assertDoesNotThrow(() -> navigationModel.removeObserver(observer)); + } + } + + @Nested + @DisplayName("Negative tests for NavigationModel") + class methodsThrowsExceptions { + + @Test + @DisplayName("setState method throws an exception when state is null") + void setStateThrowsOnNullState() { + assertThrows(IllegalArgumentException.class, () -> navigationModel.setState(null)); + } + + @Test + @DisplayName("addObserver method throws an exception when observer is null") + void addObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> navigationModel.addObserver(null)); + } + + @Test + @DisplayName("removeObserver method throws an exception when observer is null") + void removeObserverThrowsOnNullObserver() { + assertThrows(IllegalArgumentException.class, () -> navigationModel.removeObserver(null)); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationStateTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationStateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9ddfe05ad439c00bfe4b286b5f64792596f4a511 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/navigation/NavigationStateTest.java @@ -0,0 +1,25 @@ +package edu.ntnu.idatt2003.group6.models.navigation; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NavigationStateTest { + + @Test + void testEnumValues() { + // Test that the enum contains the expected number of values + assertEquals(9, NavigationState.values().length); + + // Test that the enum contains the expected values + assertEquals(NavigationState.SELECT_FILE, NavigationState.valueOf("SELECT_FILE")); + assertEquals(NavigationState.EDIT_FILE, NavigationState.valueOf("EDIT_FILE")); + assertEquals(NavigationState.SELECT_GAME, NavigationState.valueOf("SELECT_GAME")); + assertEquals(NavigationState.SELECT_GAME_FILES, NavigationState.valueOf("SELECT_GAME_FILES")); + assertEquals(NavigationState.PLAY_GAME, NavigationState.valueOf("PLAY_GAME")); + assertEquals(NavigationState.PLAY_JULIA, NavigationState.valueOf("PLAY_JULIA")); + assertEquals(NavigationState.PLAY_AFFINE, NavigationState.valueOf("PLAY_AFFINE")); + assertEquals(NavigationState.SETTINGS, NavigationState.valueOf("SETTINGS")); + assertEquals(NavigationState.MENU, NavigationState.valueOf("MENU")); + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2DTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2DTest.java index 85f0230ab2244f2fb3c06a0e51e1341e8fe25ac1..146f32d1ae6eab21c384d7412e0f2ec3cb52c628 100644 --- a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2DTest.java +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/AffineTransform2DTest.java @@ -2,10 +2,11 @@ package edu.ntnu.idatt2003.group6.models.transformation; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; import edu.ntnu.idatt2003.group6.models.matrix.Matrix2x2; -import edu.ntnu.idatt2003.group6.models.transformation.AffineTransform2D; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,16 +17,30 @@ public class AffineTransform2DTest { Matrix2x2 matrix; Vector2D vector; + AffineTransform2D affineTransform2D; @BeforeEach public void setUp() { matrix = new Matrix2x2(1, 2, 3, 4); vector = new Vector2D(1, 2); + affineTransform2D = new AffineTransform2D(matrix, vector); } @Nested @DisplayName("Positive tests for AffineTransform2D") class methodsDoesNotThrowException { + + @Test + @DisplayName("Record fields are set correctly") + void testRecordFields() { + Matrix2x2 matrix = new Matrix2x2(1, 2, 3, 4); + Vector2D vector = new Vector2D(1, 2); + AffineTransform2D transform = new AffineTransform2D(matrix, vector); + + assertEquals(matrix, transform.matrix()); + assertEquals(vector, transform.vector()); + } + @Test @DisplayName("AffineTransform2D constructor creates an instance of AffineTransform2D " + "without throwing an exception") @@ -40,6 +55,18 @@ public class AffineTransform2DTest { } } + @Test + @DisplayName("matrix method returns the expected value without throwing an exception") + void matrixDoesntThrow() { + assertEquals(matrix, affineTransform2D.matrix()); + } + + @Test + @DisplayName("vector method returns the expected value without throwing an exception") + void vectorDoesntThrow() { + assertEquals(vector, affineTransform2D.vector()); + } + @Test @DisplayName("Transform method returns the expected value without throwing an exception") void evaluateAffineTransform2DTransform() { @@ -47,6 +74,46 @@ public class AffineTransform2DTest { assertEquals(6, result.getX0()); assertEquals(13, result.getX1()); } + + @Test + @DisplayName("gameType method returns the expected value without throwing an exception") + void gameTypeDoesntThrow() { + assertEquals(GameType.AFFINE, affineTransform2D.gameType()); + } + + @Test + void testToString() { + Matrix2x2 matrix = new Matrix2x2(1, 2, 3, 4); + Vector2D vector = new Vector2D(1, 2); + AffineTransform2D transform = new AffineTransform2D(matrix, vector); + + String expected = "AffineTransform2D[matrix=" + matrix + ", vector=" + vector + "]"; + assertEquals(expected, transform.toString()); + } + + } + + @Nested + @DisplayName("Negative tests for AffineTransform2D") + class methodsThrowsExceptions { + + @Test + @DisplayName("AffineTransform2D constructor throws an exception when matrix is null") + void affineTransform2DThrowsOnNullMatrix() { + assertThrows(IllegalArgumentException.class, () -> new AffineTransform2D(null, vector)); + } + + @Test + @DisplayName("AffineTransform2D constructor throws an exception when vector is null") + void affineTransform2DThrowsOnNullVector() { + assertThrows(IllegalArgumentException.class, () -> new AffineTransform2D(matrix, null)); + } + + @Test + @DisplayName("transform method throws an exception when vector is null") + void transformThrowsOnNullVector() { + assertThrows(IllegalArgumentException.class, () -> affineTransform2D.transform(null)); + } } } \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransformTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransformTest.java index 91fdc6d4c0bea3c110a790ef17007175aa3a1ac4..8f37c6d6e6854c67c2b8e1916de4bbf8afcb5257 100644 --- a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransformTest.java +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/transformation/JuliaTransformTest.java @@ -1,8 +1,10 @@ package edu.ntnu.idatt2003.group6.models.transformation; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; -import edu.ntnu.idatt2003.group6.models.transformation.JuliaTransform; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; import edu.ntnu.idatt2003.group6.models.vector.Complex; import edu.ntnu.idatt2003.group6.models.vector.Vector2D; import org.junit.jupiter.api.BeforeEach; @@ -16,13 +18,13 @@ public class JuliaTransformTest { int complexInt; @BeforeEach - public void setUp() { - complex = new Complex(1, 2); - complexInt = 1; - } + public void setUp() { + complex = new Complex(1, 2); + complexInt = 1; + } - @Nested - @DisplayName("Positive tests for JuliaTransform") + @Nested + @DisplayName("Positive tests for JuliaTransform") public class methodsDoesNotThrowException { @Test @DisplayName("JuliaTransform constructor creates an instance of JuliaTransform " + @@ -36,15 +38,16 @@ public class JuliaTransformTest { "message " + e.getMessage()); } } + @Test - @DisplayName("Transform method returns the expected value without throwing an exception") - void evaluateJuliaTransformTransform() { - JuliaTransform juliaTransform = new JuliaTransform(complex, complexInt); - Vector2D result = new Vector2D(1, 1); - juliaTransform.transform(result); - assertEquals(1, result.getX0()); - assertEquals(1, result.getX1()); - } + @DisplayName("Transform method returns the expected value without throwing an exception") + void evaluateJuliaTransformTransform() { + JuliaTransform juliaTransform = new JuliaTransform(complex, complexInt); + Vector2D result = new Vector2D(1, 1); + juliaTransform.transform(result); + assertEquals(1, result.getX0()); + assertEquals(1, result.getX1()); + } @Test @DisplayName("Transform method returns the expected value without throwing an exception") @@ -56,5 +59,34 @@ public class JuliaTransformTest { assertEquals(1, result.getX1()); } + @Test + @DisplayName("GetPoint method returns the expected value without throwing an exception") + void evaluateJuliaTransformGetPoint() { + JuliaTransform juliaTransform = new JuliaTransform(complex, complexInt); + assertEquals(complex, juliaTransform.getPoint()); + } + + @Test + @DisplayName("GameType method returns the expected value without throwing an exception") + void evaluateJuliaTransformGameType() { + JuliaTransform juliaTransform = new JuliaTransform(complex, complexInt); + assertEquals(GameType.JULIA, juliaTransform.gameType()); + } + + } + + @Nested + @DisplayName("Negative tests for JuliaTransform") + public class methodsThrowException { + @Test + @DisplayName("JuliaTransform constructor throws an exception when the sign is not +-1") + void juliaTransformThrowsOnInvalidSign() { + try { + new JuliaTransform(complex, 0); + fail("The test juliaTransformThrowsOnInvalidSign failed"); + } catch (IllegalArgumentException e) { + assertEquals("The sign must be either 1 or -1.", e.getMessage()); + } + } } } \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/ComplexTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/ComplexTest.java index d7babbf21113d891c573b43ed4ec5a136b01d590..512c3775c1e69c09a42ad9252c72e2ba053fa45e 100644 --- a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/ComplexTest.java +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/ComplexTest.java @@ -36,7 +36,7 @@ public class ComplexTest { public void testComplexSqrt() { Complex result = complex.sqrt(); assertEquals(1.272019649514069, result.getX0()); - assertEquals(-0.7861513777574233, result.getX1()); + assertEquals(0.7861513777574233, result.getX1()); } } } \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/Vector2DTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/Vector2DTest.java index 5eb9dcb15324430c5c0e708b4b22340ce870f330..7ea89a5410d1504663203047d344dab8ad281986 100644 --- a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/Vector2DTest.java +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/models/vector/Vector2DTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; -import edu.ntnu.idatt2003.group6.models.vector.Vector2D; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -76,27 +75,14 @@ public class Vector2DTest { @Nested @DisplayName("Negative tests for Vector2D") class methodsThrowsException { - @Test - @DisplayName("Constructor throws an exception when given invalid input") - void Vector2DThrowsOnInvalidInput() { - try { - new Vector2D(Double.NaN, 2); - fail("The test Vector2DThrowsOnInvalidInput failed"); - } catch (IllegalArgumentException e) { - assertEquals("The string for the parameter x0 was not valid, " + - "try to register again", e.getMessage()); - } - } - @Test @DisplayName("Add method throws an exception when given invalid input") void Vector2DAddThrowsOnInvalidInput() { try { - vector.add(new Vector2D(Double.NaN, 2)); + vector.add(null); fail("The test Vector2DAddThrowsOnInvalidInput failed"); } catch (IllegalArgumentException e) { - assertEquals("The string for the parameter x0 was not valid, " + - "try to register again", + assertEquals("The vector can not be null", e.getMessage()); } } @@ -105,11 +91,10 @@ public class Vector2DTest { @DisplayName("Subtract method throws an exception when given invalid input") void Vector2DSubtractThrowsOnInvalidInput() { try { - vector.subtract(new Vector2D(Double.NaN, 2)); + vector.subtract(null); fail("The test Vector2DSubtractThrowsOnInvalidInput failed"); } catch (IllegalArgumentException e) { - assertEquals("The string for the parameter x0 was not valid, " + - "try to register again", + assertEquals("The vector can not be null", e.getMessage()); } } diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandlerTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..857062a3b3c883e98d682f2f93a5bc9c21b4a830 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/ChaosGameFileHandlerTest.java @@ -0,0 +1,194 @@ +package edu.ntnu.idatt2003.group6.utils; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescription; +import edu.ntnu.idatt2003.group6.models.chaosgame.ChaosGameDescriptionFactory; +import edu.ntnu.idatt2003.group6.models.chaosgame.GameType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ChaosGameFileHandlerTest { + private ChaosGameDescription chaosGameDescriptionAffine; + private ChaosGameDescription chaosGameDescriptionJulia; + private String fileName; + + @BeforeEach + void setUp() { + chaosGameDescriptionAffine = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.SIERPINSKI); + chaosGameDescriptionJulia = + ChaosGameDescriptionFactory.createChaosGameDescription(GameType.JULIA); + fileName = "testFile"; + } + + @Nested + @DisplayName("Positive tests for ChaosGameFileHandler") + class methodsDoesNotThrowException { + + @Test + @DisplayName("writeChaosGameDescriptionToFile method writes to file without throwing an exception") + void writeChaosGameDescriptionAffineToFileDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.writeChaosGameDescriptionToFile( + fileName, chaosGameDescriptionAffine)); + } + + @Test + @DisplayName("readChaosGameDescriptionFromFile method reads from file without throwing an exception") + void readChaosGameDescriptionAffineFromFileDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.readChaosGameDescriptionFromFile(fileName)); + } + + @Test + @DisplayName("writeChaosGameDescriptionToFile method writes a ChaosGameDescriptionJulia to file without throwing an exception") + void writeChaosGameDescriptionJuliaToFileDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.writeChaosGameDescriptionToFile(fileName, + chaosGameDescriptionJulia)); + } + + @Test + @DisplayName("readChaosGameDescriptionFromFile method reads a ChaosGameDescriptionJulia from file without throwing an exception") + void readChaosGameDescriptionJuliaFromFileDoesntThrow() { + ChaosGameFileHandler.writeChaosGameDescriptionToFile(fileName, chaosGameDescriptionJulia); + assertDoesNotThrow(() -> ChaosGameFileHandler.readChaosGameDescriptionFromFile(fileName)); + } + + @Test + @DisplayName("readChaosGameDescriptionFromFile method reads a ChaosGameDescriptionJulia from file correctly") + void readChaosGameDescriptionFromFileCorrectly() { + ChaosGameFileHandler.writeChaosGameDescriptionToFile(fileName, chaosGameDescriptionJulia); + ChaosGameDescription readChaosGameDescriptionJulia = + ChaosGameFileHandler.readChaosGameDescriptionFromFile(fileName); + assertEquals(chaosGameDescriptionJulia.getTransformations().getFirst().gameType(), + readChaosGameDescriptionJulia.getTransformations().getFirst().gameType()); + assertEquals(chaosGameDescriptionJulia.getMinCoords().getX0(), + readChaosGameDescriptionJulia.getMinCoords().getX0()); + assertEquals(chaosGameDescriptionJulia.getMinCoords().getX1(), + readChaosGameDescriptionJulia.getMinCoords().getX1()); + assertEquals(chaosGameDescriptionJulia.getMaxCoords().getX0(), + readChaosGameDescriptionJulia.getMaxCoords().getX0()); + assertEquals(chaosGameDescriptionJulia.getMaxCoords().getX1(), + readChaosGameDescriptionJulia.getMaxCoords().getX1()); + assertEquals(GameType.JULIA, readChaosGameDescriptionJulia.getGameType()); + } + + @Test + @DisplayName("showFiles method shows files without throwing an exception") + void showFilesDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.showFiles(".")); + } + + @Test + @DisplayName("numberOfFiles method counts files without throwing an exception") + void numberOfFilesDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.numberOfFiles(".")); + } + + @Test + @DisplayName("deleteFile method deletes file without throwing an exception") + void deleteFileDoesntThrow() { + assertDoesNotThrow(() -> ChaosGameFileHandler.deleteFile(fileName)); + } + } + + @Nested + @DisplayName("Negative tests for ChaosGameFileHandler") + class methodsThrowsExceptions { + + @Test + @DisplayName("writeChaosGameDescriptionToFile method throws an exception when fileName is null") + void writeChaosGameDescriptionToFileThrowsOnNullFileName() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.writeChaosGameDescriptionToFile( + null, chaosGameDescriptionAffine)); + } + + @Test + @DisplayName("writeChaosGameDescriptionToFile method throws an exception when chaosGameDescription is null") + void writeChaosGameDescriptionToFileThrowsOnNullChaosGameDescription() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.writeChaosGameDescriptionToFile( + fileName, null)); + } + + @Test + @DisplayName("readChaosGameDescriptionFromFile method throws an exception when fileName is null") + void readChaosGameDescriptionFromFileThrowsOnNullFileName() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.readChaosGameDescriptionFromFile(null)); + } + + @Test + @DisplayName("showFiles method throws an exception when directoryPath is null") + void showFilesThrowsOnNullDirectoryPath() { + assertThrows(RuntimeException.class, () -> ChaosGameFileHandler.showFiles(null)); + } + + @Test + @DisplayName("numberOfFiles method throws an exception when directoryPath is null") + void numberOfFilesThrowsOnNullDirectoryPath() { + assertThrows(RuntimeException.class, () -> ChaosGameFileHandler.numberOfFiles(null)); + } + + @Test + @DisplayName("deleteFile method throws an exception when path is null") + void deleteFileThrowsOnNullPath() { + assertThrows(RuntimeException.class, () -> ChaosGameFileHandler.deleteFile(null)); + } + + @TempDir + Path tempDir; + + @Test + void testReadFromFileIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.readFromFile(null)); + } + + @Test + void testWriteToFileExtraIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.writeToFileExtra(null, "content")); + } + + @Test + void testWriteToFileIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.writeToFile(null, "content")); + } + + @Test + void testShowFilesIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.showFiles("nonexistentdirectory")); + } + + @Test + void testNumberOfFilesIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.numberOfFiles("nonexistentdirectory")); + } + + @Test + void testDeleteFileIOException() { + assertThrows(RuntimeException.class, + () -> ChaosGameFileHandler.deleteFile(null)); + } + + @Test + void testDeleteFileSuccess() throws IOException { + Path file = Files.createFile(tempDir.resolve("testfile.txt")); + assertDoesNotThrow(() -> ChaosGameFileHandler.deleteFile(file.toString())); + assertFalse(Files.exists(file)); + } + } +} \ No newline at end of file diff --git a/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/UtilsTest.java b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/UtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7b329be9279e5be15f2a1115dd5d68937418bfc7 --- /dev/null +++ b/ChaosGame/src/test/java/edu/ntnu/idatt2003/group6/utils/UtilsTest.java @@ -0,0 +1,82 @@ +package edu.ntnu.idatt2003.group6.utils; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class UtilsTest { + + @Nested + @DisplayName("Positive tests for Utils") + class utilsDoesNotThrowException { + + @Test + @DisplayName("verifyInt method does not throw an exception when given a positive integer") + void verifyIntDoesNotThrowOnPositiveInt() { + assertDoesNotThrow(() -> Utils.verifyInt(1, "test")); + } + + @Test + @DisplayName("verifyInt method does not throw an exception when given a negative integer") + void verifyIntDoesNotThrowOnNegativeInt() { + assertDoesNotThrow(() -> Utils.verifyInt(-1, "test")); + } + + @Test + @DisplayName("verifyInt method throws an exception when given 0") + void verifyIntThrowsOnZero() { + assertThrows(IllegalArgumentException.class, () -> Utils.verifyInt(0, "test")); + } + + @Test + @DisplayName("inputStringToInt method returns the expected value without throwing an exception") + void inputStringToIntReturnsExpectedValue() { + assertEquals(10, Utils.inputStringToInt("10")); + } + + @Test + @DisplayName("inputStringToInt method throws an exception when given a non-integer string") + void inputStringToIntThrowsOnNonIntegerString() { + assertThrows(IllegalArgumentException.class, () -> Utils.inputStringToInt("test")); + } + + @Test + @DisplayName("inputStringToDouble method returns the expected value without throwing an exception") + void inputStringToDoubleReturnsExpectedValue() { + assertEquals(10.5, Utils.inputStringToDouble("10.5")); + } + + @Test + @DisplayName("inputStringToDouble method throws an exception when given a non-double string") + void inputStringToDoubleThrowsOnNonDoubleString() { + assertThrows(IllegalArgumentException.class, () -> Utils.inputStringToDouble("test")); + } + } + + @Nested + @DisplayName("Negative tests for Utils") + class utilsThrowsException { + + @Test + @DisplayName("verifyInt method throws an exception when given a null string") + void verifyIntThrowsOnNullString() { + assertThrows(IllegalArgumentException.class, () -> Utils.verifyInt(0, "test")); + } + + @Test + @DisplayName("inputStringToInt method throws an exception when given a null string") + void inputStringToIntThrowsOnNullString() { + assertThrows(IllegalArgumentException.class, () -> Utils.inputStringToInt(null)); + } + + @Test + @DisplayName("inputStringToDouble method throws an exception when given a null string") + void inputStringToDoubleThrowsOnNullString() { + assertThrows(IllegalArgumentException.class, () -> Utils.inputStringToDouble(null)); + } + } +} \ No newline at end of file