Skip to content
Snippets Groups Projects
Commit c6910659 authored by Sverre Grønhaug Halvorsen's avatar Sverre Grønhaug Halvorsen
Browse files

Merge branch 'dev' into 'main'

final delivery

See merge request !41
parents a6029831 b3020b4f
Branches main
No related tags found
1 merge request!41final delivery
Pipeline #289007 passed
Showing
with 1524 additions and 13 deletions
......@@ -8,6 +8,7 @@ target/
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/*
*.iws
*.iml
*.ipr
......
......@@ -6,3 +6,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ADDITIONAL_TAGS" value="Vektor2d" />
</inspection_tool>
</profile>
</component>
\ No newline at end of file
......@@ -45,7 +45,7 @@
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>edu.ntnu.idatt2003.Main</mainClass>
<mainClass>edu.ntnu.idatt2003.launcher.Main</mainClass>
</configuration>
</plugin>
<plugin>
......
package edu.ntnu.idatt2003;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
\ No newline at end of file
package edu.ntnu.idatt2003.controller;
import edu.ntnu.idatt2003.model.ChaosGame;
import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Manages the game state by providing methods to save and load the game state from a file.
*/
public class GameStateManager {
private static final String SERIALIZED_GAME_PATH = "src/main/resources/savedGameState.ser";
private static final Logger LOGGER = Logger.getLogger(GameStateManager.class.getName());
/**
* Saves the current game state to a serialized file.
*
* @param game The current game state to be saved.
*/
public static void saveGameState(ChaosGame game) {
LOGGER.log(Level.INFO, "Saving game state.");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(SERIALIZED_GAME_PATH))) {
oos.writeObject(game);
LOGGER.log(Level.INFO, "Game state saved successfully in {0}", SERIALIZED_GAME_PATH);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to save game state. Next time the application is"
+ " started, the game will be launched in same game state as this time.");
}
}
/**
* Loads the game state from a serialized file. If the file does not exist or loading fails,
* a new game state is created.
*
* @return The loaded game state, or a new game state if the file does not exist or loading fails.
*/
public static ChaosGame loadGameState() {
LOGGER.log(Level.INFO, "Loading game state.");
File file = new File(SERIALIZED_GAME_PATH);
if (file.exists()) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
ChaosGame loadedGame = (ChaosGame) ois.readObject();
LOGGER.log(Level.INFO, "Game state loaded successfully.");
return loadedGame;
} catch (IOException | ClassNotFoundException e) {
LOGGER.log(Level.WARNING, "Failed to load game state. Creating new game.");
}
} else {
LOGGER.log(Level.WARNING, "No saved game state found. Creating new game.");
}
return createNewGame();
}
/**
* Creates a new game state with default settings.
*
* @return The newly created game state.
*/
private static ChaosGame createNewGame() {
ChaosGame newGame = new ChaosGame(
ChaosGameDescriptionFactory.get(
ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE),
650, 650);
newGame.setDescriptionName(
ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE.toString());
return newGame;
}
}
package edu.ntnu.idatt2003.controller;
import edu.ntnu.idatt2003.model.ChaosGame;
import edu.ntnu.idatt2003.model.ChaosGameDescription;
import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory;
import edu.ntnu.idatt2003.model.ChaosGameFileHandler;
import edu.ntnu.idatt2003.model.Complex;
import edu.ntnu.idatt2003.model.JuliaTransform;
import edu.ntnu.idatt2003.model.Transform2D;
import edu.ntnu.idatt2003.model.Vector2d;
import edu.ntnu.idatt2003.utils.LoggerUtil;
import edu.ntnu.idatt2003.utils.TransformationParser;
import edu.ntnu.idatt2003.view.MainPageView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
/**
* The controller class for the main page of the ChaosGame application.
* This class is responsible for initializing the game and the view,
* and handling events from the view.
*/
public class MainPageController {
private final ChaosGame game;
private final MainPageView view;
private final List<String> customFractalNames;
private boolean addingCustomFractal;
private static final String FRACTAL_PATH = "src/main/resources/fractals/";
private static final Logger LOGGER = LoggerUtil.setupLogger(MainPageController.class.getName());
/**
* The constructor for the MainPageController class.
* The constructor initializes the game and the view,
* and renders the view.
*/
public MainPageController() {
this.game = GameStateManager.loadGameState();
this.view = new MainPageView(this);
this.game.registerObserver(view);
this.customFractalNames = new ArrayList<>(getAllCustomFractalsNames());
this.addingCustomFractal = false;
this.view.render();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
game.removeObserver(view);
GameStateManager.saveGameState(game);
}));
LOGGER.log(Level.INFO, "MainPageController initialized successfully.");
}
/**
* Get the view of the main page.
*
* @return the view of the main page.
*/
public MainPageView getView() {
return view;
}
/**
* Get the ChaosGame of the main page.
*
* @return the ChaosGame of the main page.
*/
public ChaosGame getGame() {
return game;
}
/**
* Get the minimum coordinates of the game.
*
* @return the minimum coordinates of the game.
*/
public double[] getMinCoordsX() {
return game.getMinCoordsList();
}
/**
* Get the maximum coordinates of the game.
*
* @return the maximum coordinates of the game.
*/
public double[] getMaxCoordsX() {
return game.getMaxCoordsList();
}
/**
* Get the name of the current fractal.
*
* @return the name of the current fractal
*/
public String getCurrentFractalName() {
return game.getDescriptionName();
}
/**
* Check if the user is adding a custom fractal.
*
* @return true if the user is adding a custom fractal, false otherwise.
*/
public boolean isAddingCustomFractal() {
return addingCustomFractal;
}
/**
* Run the chaos game simulation for the specified number of steps. If
* the number of steps is negative, the canvas will be cleared.
*
* @param steps The number of steps to run the simulation.
*/
public void runSteps(int steps) {
game.runStepsAndUpdateTotal(steps);
LOGGER.log(Level.INFO, "Chaos game simulation ran {0} steps successfully.", steps);
}
/**
* Run the chaos game simulation for the specified number of steps. If
* the number of steps is negative, the canvas will be cleared. If the
* input cant be converted to an integer, an alert will be shown. And
* no steps will be run.
*
* @param steps The number of steps to run the simulation.
*/
public void runCustomSteps(String steps) {
try {
int stepsInt = Integer.parseInt(steps);
runSteps(stepsInt);
} catch (NumberFormatException e) {
view.showAlert("Invalid input. Please enter a valid integer.");
LOGGER.log(Level.WARNING, "Invalid input. Chaos game simulation was not run.");
}
}
/**
* Validates the file that is uploaded., and calls storeFile if the
* file exists and is formatted correctly.
*
* @param file The file to upload.
* @throws InputMismatchException If the file is not found or the input is invalid.
*/
public void uploadFile(File file) {
LOGGER.log(Level.INFO, "Uploading file: {0}", file.getName());
if (ChaosGameFileHandler.validateFile(file)
&& (!Files.exists(Path.of(ChaosGameFileHandler.FRACTAL_PATH + file.getName()))
|| view.askConfirmation("File already exists. Do you want to overwrite it?"))) {
try {
ChaosGameFileHandler.storeFile(file);
LOGGER.log(Level.INFO, "File {0} uploaded successfully.", file.getName());
view.showAlert("File " + file.getName() + " uploaded successfully.");
} catch (IOException e) {
view.showAlert("Error storing file. Please try again.");
LOGGER.log(Level.WARNING, "Error storing file. File was not stored.");
}
}
}
/**
* Change the fractal-type of the chaos game.
*
* @param descriptionType The type of fractal description to retrieve.
*/
public void changeFractal(ChaosGameDescriptionFactory.DescriptionTypeEnum descriptionType) {
addingCustomFractal = false;
game.changeFractal(ChaosGameDescriptionFactory.get(descriptionType),
descriptionType.toString());
LOGGER.log(Level.INFO, "Fractal was changed successfully to {0}",
descriptionType);
}
/**
* Changes the current fractal based on the given custom name.
*
* @param customName the name of the custom fractal to be applied
*/
public void changeFractal(String customName) {
if (customName.equalsIgnoreCase("add new")) {
addingCustomFractal = true;
this.view.render();
} else {
addingCustomFractal = false;
try {
game.changeFractal(ChaosGameDescriptionFactory.getCustom(customName), customName);
LOGGER.log(Level.INFO, "Fractal was changed successfully to {0}",
customName);
} catch (FileNotFoundException e) {
view.showAlert("File not found. Please try again.");
LOGGER.log(Level.WARNING, "Error changing fractal. File not found.");
}
}
}
/**
* Adds a new custom fractal by creating a ChaosGameDescription based on the
* parameters which is written to a file in the fractals' directory.
*
* @param minCoords the minimum coordinates for the transformation
* @param maxCoords the maximum coordinates for the transformation
* @param transformations the list of 2D transformations to be applied
* @param fractalName the name of the custom fractal
*/
public void addCustomFractal(String[] minCoords, String[] maxCoords,
List<String[]> transformations, String fractalName) {
try {
ChaosGameDescription newChaosGameDescription =
new ChaosGameDescription(
TransformationParser.getVector2dFromStringList(minCoords),
TransformationParser.getVector2dFromStringList(maxCoords),
TransformationParser.getTransformListFromStringList(transformations)
);
if (!Files.exists(Path.of(FRACTAL_PATH + fractalName + ".txt"))
|| view.askConfirmation("A custom fractal with the same name already exists. "
+ "Do you want to overwrite it?")) {
ChaosGameFileHandler.writeToFile(newChaosGameDescription,
FRACTAL_PATH + fractalName + ".txt");
customFractalNames.add(fractalName);
changeFractal(fractalName);
view.render();
view.showAlert("Custom fractal " + fractalName + " added successfully.");
}
} catch (IllegalArgumentException e) {
view.showAlert(e.getMessage());
}
}
/**
* Saves the current transformation to a file in local directory.
*
* @param file the location to save the file.
*/
public void saveToLocalDirectory(File file) {
ChaosGameFileHandler.writeToFile(game.getDescription(), file.getAbsolutePath());
view.showAlert("File saved successfully in " + file.getAbsolutePath());
LOGGER.log(Level.INFO, "File saved successfully in {0}", file.getAbsolutePath());
}
/**
* Retrieves a list of all custom fractal files in the fractals directory.
*
* @return the list of custom fractal file names.
*/
public List<String> getAllCustomFractalsNames() {
List<String> transformations = new ArrayList<>();
Path transformationsPath = Paths.get(FRACTAL_PATH);
if (Files.exists(transformationsPath) && Files.isDirectory(transformationsPath)) {
try (Stream<Path> paths = Files.list(transformationsPath)) {
transformations = paths
.filter(Files::isRegularFile)
.map(Path::getFileName)
.map(Path::toString)
.map(name -> name.replace(".txt", ""))
.toList();
LOGGER.log(Level.INFO, "All custom transformations retrieved successfully.");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error retrieving custom transformation files.", e);
}
} else {
LOGGER.log(Level.WARNING, "Fractal directory is not a directory.");
}
return transformations;
}
/**
* Retrieves the list of custom fractal names.
*
* @return the list of custom transformation names
*/
public List<String> getCustomFractalNames() {
return customFractalNames;
}
/**
* Dynamically changes the Julia set transformation based on the provided
* normalized coordinates.
*
* @param x the normalized x-coordinate for the Julia transformation
* @param y the normalized y-coordinate for the Julia transformation
*/
public void changeJuliaTransformationDynamic(double x, double y) {
ChaosGameDescription description = game.getDescription();
Vector2d max = description.getMaxCoords();
Vector2d min = description.getMinCoords();
List<Transform2D> list = new ArrayList<>();
Complex complex = new Complex(x, y);
list.add(new JuliaTransform(complex, 1));
list.add(new JuliaTransform(complex, -1));
ChaosGameDescription chaosGameDescription = new ChaosGameDescription(min, max, list);
game.setDescription(chaosGameDescription);
game.runStepsWithoutUpdatingTotal(game.getTotalSteps());
}
}
package edu.ntnu.idatt2003.launcher;
import edu.ntnu.idatt2003.view.CommandLineInterface;
/**
* Launcher for the command line interface
* To read or write from file write src/main/resources/fractals/filename(with .txt)
*/
public class CLILauncher {
public static void main(String[] args) {
CommandLineInterface menuDrivenCommandLine = new CommandLineInterface();
menuDrivenCommandLine.start();
}
}
package edu.ntnu.idatt2003.launcher;
import edu.ntnu.idatt2003.controller.MainPageController;
import javafx.application.Application;
import javafx.stage.Stage;
/**
* The main class for the ChaosGame application.
* This class is responsible for starting the application
* and initializing the main view.
*/
public class Main extends Application {
/**
* The main method is the entry point for the ChaosGame application.
* The method launches the application.
*
* @param args Command-line arguments.
*/
public static void main(String[] args) {
launch(args);
}
/**
* The starts the application, by creating a MainPageController, and sets the Scene
* to its view.
*
* @param primaryStage The primary stage for this application,
* onto which the application scene can be set.
*/
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chaos Game");
primaryStage.setScene(new MainPageController().getView());
primaryStage.show();
}
}
\ No newline at end of file
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
/**
* Represents a 2-dimensional affine transformation.
* Contains a constructor and a method for transforming a 2-dimensional vector.
* Goal: act as a model for a 2-dimensional affine transformation.
* autor: nicklapt
*/
public class AffineTransform2D implements Transform2D, Serializable {
/**
* The 2x2 matrix of the affine transformation.
*/
private final Matrix2x2 matrix;
/**
* The 2-dimensional vector of the affine transformation.
*/
private final Vector2d vector;
/**
* Constructs a new AffineTransform2D with specified values for its matrix and vector.
*
* @param matrix The 2x2 matrix of the affine transformation.
* @param vector The 2-dimensional vector of the affine transformation.
*/
public AffineTransform2D(Matrix2x2 matrix, Vector2d vector) {
this.matrix = matrix;
this.vector = vector;
}
/**
* Transforms a 2-dimensional vector using the affine transformation.
*
* @param point The 2-dimensional vector to be transformed.
* @return A new Vector2D representing the result of the transformation.
*/
@Override
public Vector2d transform(Vector2d point) {
return matrix.multiply(point).add(vector);
}
public double[] getMatrixCoordsList() {
return matrix.getCoordsList();
}
public double[] getVectorCoordsList() {
return vector.getCoordsList();
}
/**
* Returns a string representation of the AffineTransform2D, by combining the strings
* of the matrix and vector separated by a ','.
*
* @return A string representation of the AffineTransform2D.
*/
@Override
public String toString() {
return matrix.toString() + ", " + vector.toString();
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
/**
* Class for creating a canvas for the chaos game.
* Contains methods for converting coordinates to indices on the canvas, and for displaying the canvas.
* Includes a method for clearing the canvas.
* Goal: act as a model for a canvas for the chaos game.
*/
public class ChaosCanvas implements Serializable {
private final int[][] canvas;
private final int width;
private final int height;
private final Vector2d minCoords;
private final Vector2d maxCoords;
private final AffineTransform2D transformCoordsToIndices;
/**
* Constructs a new ChaosCanvas with specified width, height, minimum coordinates and maximum coordinates.
* It also gives Matrix A and Vector b to the AffineTransform2D given in task description, used to transform coordinates to indices.
*
*
* @param width The width of the canvas.
* @param height The height of the canvas.
* @param minCoords The minimum coordinates of the canvas.
* @param maxCoords The maximum coordinates of the canvas.
*/
public ChaosCanvas(int width, int height, Vector2d minCoords, Vector2d maxCoords) {
this.width = width;
this.height = height;
this.minCoords = minCoords;
this.maxCoords = maxCoords;
canvas = new int[height][width];
transformCoordsToIndices = setTransformCoordsToIndices();
}
/**
* Translates a vector with coordinates in the range of minCoords and maxCoords to the corresponding indices in the canvas.
* Returns the pixel at the defined vector.
*
* @param point The placement in the picture defined as vector.
* @return the pixel as 0 or 1.
*/
public int getPixel(Vector2d point) {
Vector2d transformedPoint = transformCoordsToIndices.transform(point);
int x = (int) transformedPoint.getX0();
int y = (int) transformedPoint.getX1();
if (x >= 0 && x < width && y >= 0 && y < height) {
return canvas[x][y];
} else {
return 0; // Return 0 for out-of-bounds points
}
}
/**
* Translates a vector with coordinates in the range of minCoords and maxCoords to the corresponding indices in the canvas.
* Sets pixel to 1 at the placement of the defined vector.
*
* @param point The placement of pixel defined as a vector.
*/
public void putPixel(Vector2d point) {
Vector2d transformedPoint = transformCoordsToIndices.transform(point);
int x = (int) transformedPoint.getX0();
int y = (int) transformedPoint.getX1();
if (x >= 0 && x < width && y >= 0 && y < height) {
canvas[x][y] = 1;
}
}
/**
* Returns the canvas array.
*
* @return the canvas array.
*/
public int[][] getCanvasArray() {
return canvas;
}
/**
* Clears the canvas by setting all pixels to 0.
*/
public void clear() {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
canvas[i][j] = 0;
}
}
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
/**
* Sets the transformation matrix and vector to convert coordinates to indices on the canvas.
* Calculates the scaling factors and translation values based on canvas dimensions and coordinate bounds.
*
* @return AffineTransform2D object representing the transformation matrix and vector.
*/
private AffineTransform2D setTransformCoordsToIndices() {
double a01 = (height - 1) / (minCoords.getX1() - maxCoords.getX1());
double a10 = (width - 1) / (maxCoords.getX0() - minCoords.getX0());
Matrix2x2 transformMatrix = new Matrix2x2(0, a01, a10, 0);
double x0 = ((height - 1) * maxCoords.getX1()) / (maxCoords.getX1() - minCoords.getX1());
double x1 = ((width - 1) * minCoords.getX0()) / (minCoords.getX0() - maxCoords.getX0());
Vector2d transformVector = new Vector2d(x0, x1);
return new AffineTransform2D(transformMatrix, transformVector);
}
/**
* Displays the canvas in the console.
*/
public void showCanvas() {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (canvas[i][j] == 1) {
System.out.print("X");
} else {
System.out.print(" ");
}
}
System.out.println();
}
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Represents a chaos game.
* Contains a canvas, current point and a description of the game conditions.
* Includes the method runSteps to repeatedly apply random transformations to the current point.
* Goal: act as a model for a chaos game.
*/
public class ChaosGame implements Serializable {
private ChaosCanvas canvas;
private ChaosGameDescription description;
private String descriptionName;
private Vector2d currentPoint;
private final int width;
private final int height;
private final List<ChaosGameObserver> observers;
private int totalSteps;
Random randomGenerator;
/**
* Constructs a new ChaosGame with specified description and dimensions of the canvas.
*
* @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.observers = new ArrayList<>();
this.width = width;
this.height = height;
this.descriptionName = "";
setDescription(description);
randomGenerator = new Random();
this.totalSteps = 0;
}
/**
* Returns the canvas of the chaos game.
*
* @return The canvas of the chaos game.
*/
public ChaosCanvas getCanvas() {
return canvas;
}
/**
* Returns the minimum coordinates of the chaos game.
*
* @return The minimum coordinates of the chaos game.
*/
public double[] getMinCoordsList() {
return description.getMinCoordsList();
}
/**
* Returns the maximum coordinates of the chaos game.
*
* @return The maximum coordinates of the chaos game.
*/
public double[] getMaxCoordsList() {
return description.getMaxCoordsList();
}
/**
* Returns the list of transformations of the chaos game.
*
* @return The list of transformations of the chaos game.
*/
public List<Transform2D> getTransformList() {
return description.getTransform();
}
/**
* Returns the total number of steps run in the chaos game.
*
* @return The total number of steps run in the chaos game.
*/
public int getTotalSteps() {
return totalSteps;
}
public String getDescriptionName() {
return descriptionName;
}
public void setDescriptionName(String descriptionName) {
this.descriptionName = descriptionName;
}
/**
* Runs the chaos game simulation for the specified number of steps, by
* calling the runSteps-method and passing true as the second parameter
* to also update totalSteps.
*
* @param steps The number of steps to run the simulation.
*/
public void runStepsAndUpdateTotal(int steps) {
runSteps(steps, true);
}
/**
* Runs the chaos game simulation for the specified number of steps, by
* calling the runSteps-method and passing false as the second parameter
* to not update totalSteps.
*
* @param steps The number of steps to run the simulation.
*/
public void runStepsWithoutUpdatingTotal(int steps) {
runSteps(steps, false);
}
/**
* Runs the chaos game simulation for the specified number of steps or cleared
* if steps is negative. Generates points on the canvas based on random chosen
* transformation. If addSteps is true, the totalSteps is updated, if false,
* the totalSteps is not updated. In the end, the observers are notified.
*
* @param steps The number of steps to run the simulation.
* @param addSteps Whether to update the totalSteps or not.
*/
private void runSteps(int steps, boolean addSteps) {
if (steps < 0) {
this.canvas.clear();
totalSteps = 0;
} else {
for (int i = 0; i < steps; i++) {
applyRandomTransformation();
}
if (addSteps) {
totalSteps += steps;
}
}
notifyObservers();
}
/**
* Generates a point on the canvas based on a random transformation, and puts
* the point on the canvas. It also updates the current point to the new point.
*/
private void applyRandomTransformation() {
int randomIndex = randomGenerator.nextInt(description.getTransform().size());
currentPoint = description.getTransform().get(randomIndex).transform(currentPoint);
canvas.putPixel(currentPoint);
}
/**
* Changes the fractal of the chaos game. Calls the setDescription-method
* and notifies the observers that it has changed.
*
* @param chaosGameDescription The type of fractal description to retrieve.
*/
public void changeFractal(ChaosGameDescription chaosGameDescription,
String descriptionName) {
setDescription(chaosGameDescription);
setDescriptionName(descriptionName);
totalSteps = 0;
notifyObservers();
}
/**
* Sets the description of the chaos game, and creates a new canvas
* based on the new description.
*
* @param description The description of the chaos game.
*/
public void setDescription(ChaosGameDescription description) {
this.description = description;
this.canvas = new ChaosCanvas(width, height,
description.getMinCoords(), description.getMaxCoords());
this.currentPoint = new Vector2d(0, 0);
notifyObservers();
}
public ChaosGameDescription getDescription() {
return description;
}
/**
* Registers an observer to the list of observers.
*
* @param observer The observer to be registered.
*/
public void registerObserver(ChaosGameObserver observer) {
observers.add(observer);
}
/**
* Removes an observer from the list of observers.
*
* @param observer The observer to be removed.
*/
public void removeObserver(ChaosGameObserver observer) {
observers.remove(observer);
}
/**
* Notifies all observers, and calls their update-method.
*/
public void notifyObservers() {
for (ChaosGameObserver observer : observers) {
observer.update();
}
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
import java.util.List;
/**
* This class represents a chaos game description.
* It contains the minimum (bottom left) and maximum (top Right) coordinates of
* the game, and the transformations to be used.
* Goal: act as a model for a chaos game description.
*/
public class ChaosGameDescription implements Serializable {
/**
* The minimum (bottom left) coordinates of the game.
*/
private final Vector2d minCoords;
/**
* The maximum (top right) coordinates of the game.
*/
private final Vector2d maxCoords;
/**
* A list of the transformations to be used.
*/
private final List<Transform2D> transform;
/**
* Constructs a ChaosGameDescription object.
*
* @param minCoords The minimum (bottom left) coordinates of the game.
* @param maxCoords The maximum (top right) coordinates of the game.
* @param transform A list of the transformations to be used.
*/
public ChaosGameDescription(Vector2d minCoords, Vector2d maxCoords, List<Transform2D> transform) {
this.minCoords = minCoords;
this.maxCoords = maxCoords;
this.transform = transform;
}
/**
* Get the minimum (bottom left) coordinates of the game.
*
* @return the minimum (bottom left) coordinates of the game.
*/
public Vector2d getMinCoords() {
return minCoords;
}
/**
* Get the maximum (top right) coordinates of the game.
*
* @return the maximum (top right) coordinates of the game.
*/
public Vector2d getMaxCoords() {
return maxCoords;
}
public double[] getMinCoordsList() {
return minCoords.getCoordsList();
}
public double[] getMaxCoordsList() {
return maxCoords.getCoordsList();
}
/**
* Get the list of transformations to be used.
*
* @return the list of transformations to be used.
*/
public List<Transform2D> getTransform() {
return transform;
}
@Override
public String toString() {
StringBuilder output = new StringBuilder();
if (transform.get(0) instanceof AffineTransform2D) {
output.append("Affine2D\n");
} else {
output.append("Julia\n");
}
output.append(minCoords.toString()).append("\n");
output.append(maxCoords.toString()).append("\n");
if (transform.get(0) instanceof AffineTransform2D) {
for (Transform2D t : transform) {
output.append(t.toString()).append("\n");
}
} else {
output.append(transform.get(0).toString()).append("\n");
}
return output.toString();
}
}
package edu.ntnu.idatt2003.model;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a factory for handling descriptions.
* Contains a variety of descriptions, and the method to switch between descriptions.
* Includes an enum.
* Goal: Create pre-defined ChaosGameDescriptions.
*/
public class ChaosGameDescriptionFactory {
/**
* Retrieves a ChaosGameDescription based on the specified type of fractal description.
* This method acts as a factory, selecting the appropriate method to generate the fractal
* description based on the enum value provided.
*
* @param descriptionType the type of fractal description to retrieve. This enum specifies which
* fractal pattern description is to be generated.
* @return a ChaosGameDescription object corresponding to the specified fractal type.
*/
public static ChaosGameDescription get(DescriptionTypeEnum descriptionType) {
return switch (descriptionType) {
case SIERPINSKI_TRIANGLE -> sierpinskiTriangle();
case BARNSLEY_FERN -> barnsleyFern();
case JULIA -> juliaTransformation();
case LEVY_C_CURVE -> levyCurve();
case DRAGON_CURVE -> dragonCurve();
};
}
/**
* Enumerates the types of fractal descriptions that can be generated. Each enum constant
* corresponds to a specific fractal pattern, which the factory can produce.
*/
public enum DescriptionTypeEnum {
SIERPINSKI_TRIANGLE,
BARNSLEY_FERN,
JULIA,
LEVY_C_CURVE,
DRAGON_CURVE
}
/**
* A static method that generates a ChaosGameDescription for a pre-defined
* Affine transformation, which is called a Sierpinski Triangle.
*
* @return a description for sierpinski triangle
*/
private static ChaosGameDescription sierpinskiTriangle() {
List<Transform2D> transformations = new ArrayList<>();
transformations.add(new AffineTransform2D(
new Matrix2x2(0.5, 0.0, 0.0, 0.5),
new Vector2d(0.0, 0.0))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.5, 0.0, 0.0, 0.5),
new Vector2d(.25, .5))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.5, 0.0, 0.0, 0.5),
new Vector2d(.5, 0))
);
return new ChaosGameDescription(
new Vector2d(0, 0),
new Vector2d(1, 1),
transformations
);
}
/**
* A static method that generates a ChaosGameDescription for a pre-defined
* Affine transformation, which is called a Barnsley Fern.
*
* @return a description of the barnsley fern transformation.
*/
private static ChaosGameDescription barnsleyFern() {
List<Transform2D> transformations = new ArrayList<>();
transformations.add(new AffineTransform2D(
new Matrix2x2(0, 0, 0, 0.16),
new Vector2d(0, 0))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.85, 0.04, -0.04, 0.85),
new Vector2d(0, 1.6))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.2, -0.26, 0.23, 0.22),
new Vector2d(0, 0.16))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(-.15, .28, .26, .24),
new Vector2d(0, 0.44))
);
return new ChaosGameDescription(
new Vector2d(-2.5, 0),
new Vector2d(2.5, 10),
transformations
);
}
/**
* A static method that generates a ChaosGameDescription for a pre-defined
* Julia transformation.
*
* @return a description based on a julia transformation.
*/
private static ChaosGameDescription juliaTransformation() {
return new ChaosGameDescription(
new Vector2d(-1.6, -1),
new Vector2d(1.6, 1),
List.of(
new JuliaTransform(new Complex(-0.74543, 0.11301), 1),
new JuliaTransform(new Complex(-0.74543, 0.11301), -1)
));
}
private static ChaosGameDescription levyCurve() {
List<Transform2D> transformations = new ArrayList<>();
transformations.add(new AffineTransform2D(
new Matrix2x2(0.5, -0.5, 0.5, 0.5),
new Vector2d(0, 0))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.5, 0.5, -0.5, 0.5),
new Vector2d(0.5, 0.5))
);
return new ChaosGameDescription(
new Vector2d(-1, -0.5),
new Vector2d(2, 1.5),
transformations
);
}
private static ChaosGameDescription dragonCurve() {
List<Transform2D> transformations = new ArrayList<>();
transformations.add(new AffineTransform2D(
new Matrix2x2(0.824074, 0.281482, -0.212346, 0.864198),
new Vector2d(-1.882290, -0.110607))
);
transformations.add(new AffineTransform2D(
new Matrix2x2(0.088272, 0.520988, -0.463889, -0.377778),
new Vector2d(0.785360, 8.095795))
);
return new ChaosGameDescription(
new Vector2d(-7, 0),
new Vector2d(6, 11),
transformations
);
}
/**
* A static method that reads a fractal from a file and returns a ChaosGameDescription.
*
* @param pathToFile the path to the file containing the fractal description
* @return a ChaosGameDescription based on the fractal description in the file
*/
private static ChaosGameDescription readFractal(String pathToFile)
throws FileNotFoundException{
try {
return ChaosGameFileHandler.readFromFile(pathToFile);
} catch (FileNotFoundException e) {
throw new FileNotFoundException("File " + pathToFile + " not found." + e.getMessage());
}
}
/**
* A static method for returning a ChaosGameDescription based on the given transformation name.
*
* @param transformationName the name of the custom transformation
* @return the ChaosGameDescription corresponding to the given transformation name
*/
public static ChaosGameDescription getCustom(String transformationName)
throws FileNotFoundException{
try {
String filePath = "src/main/resources/fractals/" + transformationName + ".txt";
return readFractal(filePath);
} catch (FileNotFoundException e) {
throw new FileNotFoundException("File " + e.getMessage());
}
}
}
package edu.ntnu.idatt2003.model;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* A class that handles reading and writing ChaosGameDescriptions to and from files.
* The class can read from a file and create a ChaosGameDescription object from the file.
* The class can also write a ChaosGameDescription object to a file.
* The class uses a delimiter filter to read the file.
*/
public class ChaosGameFileHandler {
public static final String FRACTAL_PATH = "src/main/resources/fractals/";
/**
* Private constructor to prevent instantiation.
*/
private ChaosGameFileHandler() {
}
/**
* Checks if the file exists and reads a ChaosGameDescription using the other
* readFromFile, if the path is valid.
*
* @param path the path to the file.
* @return The ChaosGameDescription read from the file.
* @throws FileNotFoundException If the file is not found.
*/
public static ChaosGameDescription readFromFile(String path)
throws FileNotFoundException {
try {
Files.exists(Paths.get(path));
return readFromFile(new File(path));
} catch (FileNotFoundException e) {
throw new FileNotFoundException("File '" + path + "' not found.");
}
}
/**
* Reads a ChaosGameDescription from a file.
* Delimiter filter is co-created using ChatGPT.
*
* @param file the file to read.
* @return The ChaosGameDescription read from the file.
* @throws FileNotFoundException If the file is not found.
* @throws InputMismatchException If the file has invalid input.
*/
public static ChaosGameDescription readFromFile(File file)
throws InputMismatchException, FileNotFoundException {
try (Scanner scanner = new Scanner(file)) {
scanner.useDelimiter("\\s|,\\s*");
scanner.useLocale(Locale.ENGLISH);
String transformType = scanner.nextLine().trim().toLowerCase().split(" ")[0];
Vector2d minCoords = new Vector2d(readDouble(scanner), readDouble(scanner));
Vector2d maxCoords = new Vector2d(readDouble(scanner), readDouble(scanner));
List<Transform2D> transform = parseTransformations(scanner, transformType);
return new ChaosGameDescription(minCoords, maxCoords, transform);
} catch (NoSuchElementException e) {
throw new InputMismatchException("Invalid format in file: " + file.getPath()
+ ". Please try again.");
} catch (FileNotFoundException e) {
throw new FileNotFoundException("File '" + file.getName() + "' not found.");
}
}
/**
* Reads a double from the scanner and skips to the next line
* if the next token is not a double. This is used to skip
* comments in the file.
*
* @param scanner The scanner to read from.
* @return The double read from the scanner.
*/
private static double readDouble(Scanner scanner) {
double value = scanner.nextDouble();
while (scanner.hasNextLine() && !scanner.hasNextDouble()) {
scanner.nextLine();
}
return value;
}
/**
* Parses the transformations from the scanner based on the transformType.
* It calls the appropriate method to parse the transformations based on the type
* and returns the list of transformations.
*
* @param scanner The scanner to read from.
* @param transformType The type of transformation to parse.
* @return The list of transformations.
* @throws IllegalArgumentException If the transformation type is unknown.
*/
private static List<Transform2D> parseTransformations(Scanner scanner, String transformType) {
return switch (transformType) {
case "affine2d" -> parseAffine2dTransformation(scanner);
case "julia" -> parseJuliaTransformations(scanner);
default -> throw new IllegalArgumentException("Unknown transformation type: "
+ transformType);
};
}
/**
* Parses the affine2d transformations from the scanner.
* It reads the matrix and vector from the scanner and creates an AffineTransform2D object.
* It adds the AffineTransform2D object to the list of transformations.
*
* @param scanner The scanner to read from.
* @return The list of transformations.
*/
private static List<Transform2D> parseAffine2dTransformation(Scanner scanner) {
List<Transform2D> transform = new ArrayList<>();
Matrix2x2 matrix;
Vector2d vector;
while (scanner.hasNextLine()) {
matrix = new Matrix2x2(readDouble(scanner), readDouble(scanner),
readDouble(scanner), readDouble(scanner));
vector = new Vector2d(readDouble(scanner), readDouble(scanner));
transform.add(new AffineTransform2D(matrix, vector));
}
return transform;
}
/**
* Parses the julia transformations from the scanner.
* It reads the complex number from the scanner and creates two JuliaTransform objects.
* It adds the JuliaTransform objects to the list of transformations and returns the list.
*
* @param scanner The scanner to read from.
* @return The list of julia transformations.
*/
private static List<Transform2D> parseJuliaTransformations(Scanner scanner) {
List<Transform2D> transform = new ArrayList<>();
Complex complex = new Complex(readDouble(scanner), readDouble(scanner));
transform.add(new JuliaTransform(complex, -1));
transform.add(new JuliaTransform(complex, 1));
return transform;
}
/**
* Writes a ChaosGameDescription to a file using its toString method.
*
* @param chaosGameDescription The ChaosGameDescription to write.
* @param path The path to write the file.
* @throws IllegalArgumentException If the file could not be written to.
*/
public static void writeToFile(ChaosGameDescription chaosGameDescription, String path) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(path))) {
writer.write(chaosGameDescription.toString());
} catch (IOException e) {
throw new IllegalArgumentException("Could not write to file '" + path + "'.");
}
}
/**
* Validates if the file exists.
*
* @param file the file to validate
*
* @return a boolean value
*/
public static boolean validateFile(File file) {
try {
ChaosGameFileHandler.readFromFile(file);
return true;
} catch (InputMismatchException | FileNotFoundException e) {
return false;
}
}
/**
* Stores the file in the resources/transformations folder of the project.
*
* @param file The file to store.
*/
public static void storeFile(File file) throws IOException {
String projectPath = System.getProperty("user.dir");
String destinationPath = projectPath + File.separator + FRACTAL_PATH + file.getName();
Files.copy(file.toPath(), Path.of(destinationPath), StandardCopyOption.REPLACE_EXISTING);
}
}
package edu.ntnu.idatt2003.model;
/**
* Represents a ChaosGameObserver.
* Contains a method for updating the observer.
* Goal: act as an observer for a chaos game.
*
*/
public interface ChaosGameObserver {
/**
* Updates the observer.
*/
void update();
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
/**
* Represents a subclass that extends the {@link Vector2d}.
* It adds the additional functionality of finding the square root of a complex number.
* Goal: act as a model for a complex number.
*
* @author svhaa
*/
public class Complex extends Vector2d implements Serializable {
/**
* Constructs a Complex object.
*
* @param realPart The real part of the complex number.
* @param imaginaryPart The imaginary part of the complex number.
*/
public Complex(double realPart, double imaginaryPart) {
super(realPart, imaginaryPart);
}
/**
* New method introduced in subclass.
* Computes the square root of a complex number.
*
* @return the square root of the complex number.
*/
public Complex sqrt() {
double length = Math.sqrt(getX0() * getX0() + getX1() * getX1());
double realPart = Math.sqrt((getX0() + length) / 2);
double imaginaryPart = Math.signum(getX1()) * Math.sqrt((length - getX0()) / 2);
return new Complex(realPart, imaginaryPart);
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
/**
* Represents a Julia transformation applied to 2D vectors.
* Contains a constructor and a method for transforming a 2D vector.
* Goal: act as a model for a Julia transformation.
*
* @author sverrgha
*/
public class JuliaTransform implements Transform2D, Serializable {
/**
* The point to transform the 2D vector by.
*/
private final Complex point;
/**
* The sign of the transformation (+/- 1).
*/
private final int sign;
/**
* Constructs a JuliaTransform object.
*
* @param point The point to transform the 2D vector by.
* @param sign The sign of the transformation (+/- 1).
*/
public JuliaTransform(Complex point, int sign) {
this.point = point;
this.sign = sign;
}
public double[] getPointAsList() {
return new double[]{point.getX0(), point.getX1()};
}
/**
* Transforms a 2D vector by taking adding/subtracting based on the sign
* and taking the square root.
*
* @param point The 2D vector to transform.
* @return The transformed 2D vector.
*/
public Vector2d transform(Vector2d point) {
Vector2d subtracted = point.subtract(this.point);
Complex z = new Complex(subtracted.getX0(), subtracted.getX1());
Complex complex = z.sqrt();
return new Complex(complex.getX0() * this.sign,
complex.getX1() * this.sign);
}
/**
* Returns a string representation of the JuliaTransform object, that is equal
* to the string representation for the transformation's point.
*
* @return A string representation of the JuliaTransform object.
*/
@Override
public String toString() {
return point.toString();
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
import java.util.Locale;
/**
* A 2x2 matrix representation with basic operations.
*
* @author nicklapt
*
*/
public class Matrix2x2 {
public class Matrix2x2 implements Serializable {
private final double a00;
private final double a01;
......@@ -40,5 +42,18 @@ public class Matrix2x2 {
double x1 = a10 * vector.getX0() + a11 * vector.getX1();
return new Vector2d(x0, x1);
}
public double[] getCoordsList() {
return new double[]{a00, a01, a10, a11};
}
/**
* Returns a string representation of the Matrix2x2, that separates the
* coordinates by ', '.
*
* @return A string representation of the Matrix2x2.
*/
@Override
public String toString() {
return String.format(Locale.ENGLISH, "%.2f, %.2f, %.2f, %.2f", a00, a01, a10, a11);
}
}
package edu.ntnu.idatt2003.model;
import java.io.Serializable;
/**
* Represents a 2-dimensional vector.
* Contains a constructor and getters for the x0 and x1 attributes.
......@@ -8,17 +10,17 @@ package edu.ntnu.idatt2003.model;
*
* @author sverrgha
*/
public class Vector2d {
public class Vector2d implements Serializable {
/**
* The first coordinate of the vector.
*/
double x0;
private final double x0;
/**
* The second coordinate of the vector.
*/
double x1;
private final double x1;
/**
* Constructs a Vector2d object.
......@@ -49,6 +51,9 @@ public class Vector2d {
return x1;
}
public double[] getCoordsList() {
return new double[]{x0, x1};
}
/**
* Adds another vector to this vector.
*
......@@ -68,4 +73,15 @@ public class Vector2d {
public Vector2d subtract(Vector2d other) {
return new Vector2d(x0 - other.x0, x1 - other.x1);
}
/**
* Returns a string representation of the vector, that separates the coordinates
* with ', '.
*
* @return A string representation of the vector.
*/
@Override
public String toString() {
return x0 + ", " + x1;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment