From b9754f9b53a2565775c39c7c7749db001c2152d6 Mon Sep 17 00:00:00 2001 From: Guyslain <guyslain.naves@lis-lab.fr> Date: Tue, 10 Oct 2023 16:00:39 +0200 Subject: [PATCH] Tests and Javadox, thanks chatGPT --- src/main/java/SimulatorApplication.java | 7 +- src/main/java/controller/Controller.java | 9 +- src/main/java/controller/Simulation.java | 58 ++++++- src/main/java/datastruct/Coordinate.java | 62 ++++++- .../java/datastruct/CoordinateIterator.java | 25 ++- src/main/java/datastruct/Lens.java | 15 ++ src/main/java/datastruct/Matrix.java | 118 +++++++++++-- .../java/datastruct/MatrixInitializer.java | 12 ++ src/main/java/model/Cell.java | 19 +- src/main/java/model/CellularAutomaton.java | 34 +++- .../model/CellularAutomatonSimulation.java | 73 +++----- .../java/model/ConstantCellInitializer.java | 12 ++ .../java/model/NextGenerationInitializer.java | 23 ++- src/main/java/model/State.java | 41 ++++- .../java/model/automata/BiColorAutomaton.java | 2 - .../java/model/automata/BiColorState.java | 18 +- .../java/model/automata/BriansBrainState.java | 13 +- .../model/automata/GameOfLifeAutomaton.java | 4 +- .../java/model/automata/GameOfLifeState.java | 8 +- .../java/model/automata/SeedsAutomaton.java | 2 +- src/main/java/model/automata/SeedsState.java | 5 +- src/main/java/view/FillingMouseListener.java | 3 +- src/main/java/view/MatrixPane.java | 1 + src/main/java/view/MouseListener.java | 1 + src/main/java/view/WaitingMouseListener.java | 8 +- .../ConstantMatrixInitializerTest.java | 31 ++++ .../datastruct/CoordinateIteratorTest.java | 53 ++++++ src/test/java/datastruct/CoordinateTest.java | 100 +++++++++++ src/test/java/datastruct/MatrixTest.java | 88 ++++++++++ src/test/java/model/CellTest.java | 40 +++++ .../CellularAutomatonSimulationTest.java | 162 ++++++++++++++++++ src/test/java/model/GridTest.java | 50 ------ src/test/java/model/StateTest.java | 39 +++++ .../model/automata/GameOfLifeStateTest.java | 70 ++++++++ 34 files changed, 1026 insertions(+), 180 deletions(-) create mode 100644 src/test/java/datastruct/ConstantMatrixInitializerTest.java create mode 100644 src/test/java/datastruct/CoordinateIteratorTest.java create mode 100644 src/test/java/datastruct/CoordinateTest.java create mode 100644 src/test/java/datastruct/MatrixTest.java create mode 100644 src/test/java/model/CellTest.java create mode 100644 src/test/java/model/CellularAutomatonSimulationTest.java delete mode 100644 src/test/java/model/GridTest.java create mode 100644 src/test/java/model/StateTest.java create mode 100644 src/test/java/model/automata/GameOfLifeStateTest.java diff --git a/src/main/java/SimulatorApplication.java b/src/main/java/SimulatorApplication.java index 0fed297..1e2f7b3 100644 --- a/src/main/java/SimulatorApplication.java +++ b/src/main/java/SimulatorApplication.java @@ -7,16 +7,13 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import model.CellularAutomatonSimulation; -import model.automata.BriansBrainAutomaton; -import model.automata.GameOfLifeAutomaton; +import model.automata.BiColorAutomaton; import model.automata.SeedsAutomaton; import java.io.IOException; import java.net.URL; import java.util.Random; -import static java.util.Objects.requireNonNull; - /** * Entry point for <i>The Game of Life</i> application. * @@ -41,7 +38,7 @@ public class SimulatorApplication extends Application { public SimulatorApplication() { this.simulation = new CellularAutomatonSimulation<>( - new SeedsAutomaton(NUMBER_OF_COLUMNS,NUMBER_OF_ROWS), + new BiColorAutomaton(NUMBER_OF_COLUMNS,NUMBER_OF_ROWS), GENERATOR ); } diff --git a/src/main/java/controller/Controller.java b/src/main/java/controller/Controller.java index 84e73a3..0e8cf64 100644 --- a/src/main/java/controller/Controller.java +++ b/src/main/java/controller/Controller.java @@ -65,7 +65,14 @@ public class Controller { } private void setGenerationNumberLabelTextProperty() { - generationNumberLabel.textProperty().bind(simulation.generationNumberProperty().asString()); + updateGenerationNumber(0); + this.simulation.setGenerationNumberChangeListener( + (oldValue, newValue) -> updateGenerationNumber(newValue) + ); + } + + private void updateGenerationNumber(int newValue) { + generationNumberLabel.textProperty().set(String.valueOf(newValue)); } private void initializeMatrixPane() { diff --git a/src/main/java/controller/Simulation.java b/src/main/java/controller/Simulation.java index 674a0fd..8c33146 100644 --- a/src/main/java/controller/Simulation.java +++ b/src/main/java/controller/Simulation.java @@ -1,28 +1,82 @@ package controller; import datastruct.Coordinate; -import javafx.beans.property.ReadOnlyLongProperty; import javafx.scene.paint.Color; import model.OnChangeListener; +/** + * Represents a simulation of a 2D cellular automaton, such as the Game of Life. + * Provides methods for updating the simulation, retrieving information, and managing listeners. + */ public interface Simulation extends Iterable<Coordinate> { + /** + * Returns the number of columns in the simulation grid. + * + * @return The number of columns in the grid. + */ int numberOfColumns(); + + /** + * Returns the number of rows in the simulation grid. + * + * @return The number of rows in the grid. + */ int numberOfRows(); + + /** + * Updates the simulation to the next generation. This is done by computing, for each + * coordinate, a new state that depends on the states of its neighbours. + */ void updateToNextGeneration(); + /** + * Changes the state at a given {@link Coordinate}. This is used to edit the grid with the mouse. It + * is not part of the simulation of the cellular automaton. + * + * @param coordinate The {@link Coordinate} to advance to the next state. + */ void next(Coordinate coordinate); + /** + * Copies the state from the source {@link Coordinate} to the destination {@link Coordinate}. + * + * @param source The source {@link Coordinate}. + * @param destination The destination {@link Coordinate}. + */ void copy(Coordinate source, Coordinate destination); + /** + * Gets the {@link Color} associated with the state at the specified {@link Coordinate}. + * + * @param coordinate The {@link Coordinate} to retrieve the color for. + * @return The {@link Color} associated with the state at the specified {@link Coordinate}. + */ Color getColor(Coordinate coordinate); + /** + * Sets a listener to be executed when the state at the specified {@link Coordinate} changes. + * + * @param coordinate The {@link Coordinate} to listen for changes. + * @param listener The listener to execute when the state changes. + */ void setChangeListener(Coordinate coordinate, Runnable listener); - ReadOnlyLongProperty generationNumberProperty(); + /** + * Sets a listener to be executed when the generation number changes. + * + * @param listener The listener to execute when the generation number changes. + */ + void setGenerationNumberChangeListener(OnChangeListener<Integer> listener); + /** + * Resets the simulation to random states. + */ void reset(); + /** + * Clears the simulation, setting all states to their default values. + */ void clear(); } diff --git a/src/main/java/datastruct/Coordinate.java b/src/main/java/datastruct/Coordinate.java index b807b96..18282bc 100644 --- a/src/main/java/datastruct/Coordinate.java +++ b/src/main/java/datastruct/Coordinate.java @@ -3,28 +3,63 @@ package datastruct; import java.util.ArrayList; import java.util.List; +/** + * Represents a 2D integer coordinate used to specify positions in a grid. + */ public record Coordinate(int x, int y) { + /** + * Creates a new {@link Coordinate} instance with the given {@code x} and {@code y} values. + * + * @param x The x-coordinate value. + * @param y The y-coordinate value. + * @return A new {@link Coordinate} instance. + */ public static Coordinate of(int x, int y) { - return new Coordinate(x,y); + return new Coordinate(x, y); } + /** + * Computes and returns the {@link Coordinate} to the left of this one. + * + * @return The left adjacent {@link Coordinate}. + */ public Coordinate left() { - return new Coordinate(x-1,y); + return new Coordinate(x - 1, y); } + /** + * Computes and returns the {@link Coordinate} to the right of this one. + * + * @return The right adjacent {@link Coordinate}. + */ public Coordinate right() { - return new Coordinate(x+1,y); + return new Coordinate(x + 1, y); } + /** + * Computes and returns the {@link Coordinate} above this one. + * + * @return The above adjacent {@link Coordinate}. + */ public Coordinate above() { - return new Coordinate(x,y+1); + return new Coordinate(x, y + 1); } + /** + * Computes and returns the {@link Coordinate} below this one. + * + * @return The below adjacent {@link Coordinate}. + */ public Coordinate below() { - return new Coordinate(x, y-1); + return new Coordinate(x, y - 1); } + /** + * Computes and returns a list of orthogonal (adjacent in horizontal or vertical direction) neighbors. + * + * @return A list of orthogonal neighboring {@link Coordinate}s. + */ public List<Coordinate> orthogonalNeighbours() { return List.of( this.right(), @@ -34,6 +69,11 @@ public record Coordinate(int x, int y) { ); } + /** + * Computes and returns a list of diagonal (adjacent in diagonal direction) neighbors. + * + * @return A list of diagonal neighboring {@link Coordinate}s. + */ public List<Coordinate> diagonalNeighbours() { return List.of( this.right().above(), @@ -43,9 +83,19 @@ public record Coordinate(int x, int y) { ); } + /** + * Computes and returns a list of all orthogonal and diagonal neighbors. + * + * @return A list of all neighboring {@link Coordinate}s. + */ public List<Coordinate> orthodiagonalNeighbours() { List<Coordinate> neighbours = new ArrayList<>(this.orthogonalNeighbours()); neighbours.addAll(this.diagonalNeighbours()); return neighbours; } -} + + @Override + public String toString() { + return "(" + this.x + "," + this.y + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/datastruct/CoordinateIterator.java b/src/main/java/datastruct/CoordinateIterator.java index 5ea7c49..312d2de 100644 --- a/src/main/java/datastruct/CoordinateIterator.java +++ b/src/main/java/datastruct/CoordinateIterator.java @@ -3,33 +3,54 @@ package datastruct; import java.util.Iterator; import java.util.NoSuchElementException; +/** + * An {@link Iterator} for generating 2D {@link Coordinate}s within a specified width and + * height range. + */ class CoordinateIterator implements Iterator<Coordinate> { private final int width; private final int height; private int x = 0; private int y = 0; + /** + * Creates a new {@link CoordinateIterator} with the specified width and height. + * + * @param width The width of the coordinate range. + * @param height The height of the coordinate range. + */ public CoordinateIterator(int width, int height) { this.width = width; this.height = height; } + /** + * Checks if there are more {@link Coordinate}s to iterate over. + * + * @return true if there are more {@link Coordinate}s; otherwise, false. + */ @Override public boolean hasNext() { return y < this.height; } + /** + * Returns the next {@link Coordinate} in the iteration. + * + * @return The next {@link Coordinate} in the iteration. + * @throws NoSuchElementException if there are no more {@link Coordinate}s to iterate over. + */ @Override public Coordinate next() { if (!this.hasNext()) { throw new NoSuchElementException(); } - Coordinate coord = new Coordinate(this.x, this.y); + Coordinate coordinate = new Coordinate(this.x, this.y); this.x = this.x + 1; if (this.x == this.width) { this.x = 0; this.y = this.y + 1; } - return coord; + return coordinate; } } diff --git a/src/main/java/datastruct/Lens.java b/src/main/java/datastruct/Lens.java index c9845dd..a98fd46 100644 --- a/src/main/java/datastruct/Lens.java +++ b/src/main/java/datastruct/Lens.java @@ -1,7 +1,22 @@ package datastruct; +/** + * A lens interface representing a view into a mutable state. + * + * @param <S> The type of the value stored in the lens. + */ public interface Lens<S> { + /** + * Gets the value from the {@link Lens}. + * + * @return The value stored in the place designated by {@link Lens}. + */ S get(); + /** + * Sets a new value into the {@link Lens}. + * + * @param value The new value to set in the place designated by the {@link Lens}. + */ void set(S value); } diff --git a/src/main/java/datastruct/Matrix.java b/src/main/java/datastruct/Matrix.java index 9290fa6..f98bd7a 100644 --- a/src/main/java/datastruct/Matrix.java +++ b/src/main/java/datastruct/Matrix.java @@ -4,14 +4,26 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; + +/** + * Represents a matrix, a rectangular array, with generic values in each cell. + * + * @param <T> The type of values stored in the matrix cells. + */ public class Matrix<T> implements Iterable<T> { private final List<List<T>> matrix; - - private final int width; private final int height; + /** + * Creates a new {@link Matrix} with the specified width, height, and an initializer to set + * values. + * + * @param width The width of the {@link Matrix}. + * @param height The height of the {@link Matrix}. + * @param initializer A matrix initializer to set values in the {@link Matrix}. + */ public Matrix(int width, int height, MatrixInitializer<T> initializer) { this.width = width; this.height = height; @@ -19,6 +31,14 @@ public class Matrix<T> implements Iterable<T> { this.initializeWith(initializer); } + /** + * Creates a new {@link Matrix} with the specified width, height, and initial value for all + * cells. + * + * @param width The width of the {@link Matrix}. + * @param height The height of the {@link Matrix}. + * @param initialValue The initial value to set in all cells of the {@link Matrix}. + */ public Matrix(int width, int height, T initialValue) { this(width, height, new ConstantMatrixInitializer<>(initialValue)); } @@ -28,65 +48,135 @@ public class Matrix<T> implements Iterable<T> { List<T> row = new ArrayList<>(); this.matrix.add(row); for (int y = 0; y < height; y++) { - row.add(initializer.initialValueAt(Coordinate.of(x,y))); + row.add(initializer.initialValueAt(Coordinate.of(x, y))); } } } + /** + * Returns the width of the {@link Matrix}. + * + * @return The width of the {@link Matrix}. + */ public int width() { return width; } + /** + * Returns the height of the {@link Matrix}. + * + * @return The height of the {@link Matrix}. + */ public int height() { return height; } + + /** + * Gets the value at the specified coordinates (x, y) in the {@link Matrix}. + * + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return The value at the specified coordinates. + */ public T get(int x, int y) { return this.matrix.get(x).get(y); } - public T get(Coordinate coord) { - return this.get(coord.x(), coord.y()); + /** + * Gets the value at the specified {@link Coordinate} in the {@link Matrix}. + * + * @param coordinate The {@link Coordinate}. + * @return The value at the specified {@link Coordinate}. + */ + public T get(Coordinate coordinate) { + return this.get(coordinate.x(), coordinate.y()); } + /** + * Sets the value at the specified coordinates (x, y) in the {@link Matrix}. + * + * @param x The x-coordinate. + * @param y The y-coordinate. + * @param value The value to set at the specified coordinates. + */ public void set(int x, int y, T value) { - this.matrix.get(x).set(y,value); + this.matrix.get(x).set(y, value); } - public void set(Coordinate coord, T value) { - this.set(coord.x(), coord.y(), value); - } + /** + * Sets the value at the specified {@link Coordinate} in the {@link Matrix}. + * + * @param coordinate The {@link Coordinate}. + * @param value The value to set at the specified {@link Coordinate}. + */ + public void set(Coordinate coordinate, T value) { + this.set(coordinate.x(), coordinate.y(), value); + } + /** + * Returns an {@link Iterator} that allows iterating over the elements in the {@link Matrix} in + * row-major order. + * + * @return An {@link Iterator} for the {@link Matrix}. + */ public Iterator<T> iterator() { Iterator<Coordinate> coordIterator = this.coordinatesIterator(); return new MatrixIterator<>(this, coordIterator); } + /** + * Returns an {@link Iterable} that provides access to the {@link Coordinate}s of the + * {@link Matrix} in row-major order. This means that a {@code for} loop on a {@link Matrix} + * will loop over the coordinates of the {@link Matrix}. + * + * @return An {@link Iterable} for the {@link Coordinate}s of the {@link Matrix}. + */ public Iterable<Coordinate> coordinates() { return this::coordinatesIterator; } + /** + * Returns an {@link Iterator} that allows iterating over the {@link Coordinate}s in the + * {@link Matrix} in row-major order. + * + * @return An {@link Iterator} for the {@link Matrix}. + */ private Iterator<Coordinate> coordinatesIterator() { return new CoordinateIterator(this.width, this.height); } - + /** + * Returns a lens for accessing and modifying the value at the specified coordinates (x, y) in + * the {@link Matrix}. + * + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return A lens for the specified coordinates. + */ public Lens<T> at(int x, int y) { return new Lens<T>() { @Override public T get() { - return Matrix.this.get(x,y); + return Matrix.this.get(x, y); } @Override public void set(T value) { - Matrix.this.set(x,y,value); + Matrix.this.set(x, y, value); } }; } - public Lens<T> at(Coordinate coord) { - return this.at(coord.x(), coord.y()); + /** + * Returns a lens for accessing and modifying the value at the specified coordinate in the + * {@link Matrix}. + * + * @param coordinate The {@link Coordinate}. + * @return A lens for the specified {@link Coordinate}. + */ + public Lens<T> at(Coordinate coordinate) { + return this.at(coordinate.x(), coordinate.y()); } } diff --git a/src/main/java/datastruct/MatrixInitializer.java b/src/main/java/datastruct/MatrixInitializer.java index c7fb5c5..dab2bf2 100644 --- a/src/main/java/datastruct/MatrixInitializer.java +++ b/src/main/java/datastruct/MatrixInitializer.java @@ -1,6 +1,18 @@ package datastruct; +/** + * An interface for initializing a {@link Matrix} by providing initial values for each cell. + * + * @param <T> The type of values to initialize the {@link Matrix} with. + */ public interface MatrixInitializer<T> { + /** + * Returns the initial value to be set in a {@link Matrix} cell at the specified + * {@link Coordinate}. + * + * @param coordinate The {@link Coordinate} at which to set the initial value. + * @return The initial value for the specified cell. + */ T initialValueAt(Coordinate coordinate); } diff --git a/src/main/java/model/Cell.java b/src/main/java/model/Cell.java index 7da6824..35022e7 100644 --- a/src/main/java/model/Cell.java +++ b/src/main/java/model/Cell.java @@ -5,10 +5,12 @@ import datastruct.Lens; import java.util.ArrayList; import java.util.List; + /** - * {@link Cell} instances represent the cells of the grid in a simulation of cellular automata. + * A class representing a cell that holds a value and allows adding listeners to track value changes. + * + * @param <T> The type of value stored in the cell. */ - public class Cell<T> implements Lens<T> { private T content; private final List<OnChangeListener<T>> listeners = new ArrayList<>(); @@ -21,19 +23,26 @@ public class Cell<T> implements Lens<T> { this.content = initialContent; } + /** Add a {@link OnChangeListener} to react to any change of value in the cell. + * + * @param listener the {@link OnChangeListener} to activate when the value in the cell is + * changed. + */ public void addOnChangeListener(OnChangeListener<T> listener) { this.listeners.add(listener); } /** - * Sets the content of this {@link Cell}. + * Sets the content of this {@link Cell}. This will also call all the listeners that were + * registered by the method {@code addOnChangeListener}. * * @param value the new content of this {@link Cell} */ public void set(T value) { + T oldValue = this.content; this.content = value; for (OnChangeListener<T> listener : this.listeners) { - listener.valueChanged(this.content, value); + listener.valueChanged(oldValue, value); } } @@ -45,6 +54,4 @@ public class Cell<T> implements Lens<T> { public T get(){ return this.content; } - - } diff --git a/src/main/java/model/CellularAutomaton.java b/src/main/java/model/CellularAutomaton.java index 92b83c4..a2762ce 100644 --- a/src/main/java/model/CellularAutomaton.java +++ b/src/main/java/model/CellularAutomaton.java @@ -2,9 +2,41 @@ package model; import java.util.Random; +/** + * Represents a cellular automaton, which defines the main parameters of a cellular automaton. + * The rules for updating states are defined in the class used as {@code S}. + * + * @param <S> The type of state used in the cellular automaton. + */ public interface CellularAutomaton<S extends State<S>> { + + /** + * Returns the number of columns in the grid of the cellular automaton. + * + * @return The number of columns in the grid. + */ int numberOfColumns(); + + /** + * Returns the number of rows in the grid of the cellular automaton. + * + * @return The number of rows in the grid. + */ int numberOfRows(); + + /** + * Returns the default state that is used to initialize cells in the automaton. + * + * @return The default state for cells in the automaton. + */ S defaultState(); + + + /** + * Generates a random state using the specified random number generator. + * + * @param generator The random number generator to use. + * @return A randomly generated state. + */ S randomState(Random generator); -} +} \ No newline at end of file diff --git a/src/main/java/model/CellularAutomatonSimulation.java b/src/main/java/model/CellularAutomatonSimulation.java index aa7404c..0604e8f 100644 --- a/src/main/java/model/CellularAutomatonSimulation.java +++ b/src/main/java/model/CellularAutomatonSimulation.java @@ -3,30 +3,31 @@ package model; import controller.Simulation; import datastruct.Coordinate; import datastruct.Matrix; -import javafx.beans.property.ReadOnlyLongProperty; -import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; + import java.util.Iterator; import java.util.Random; -import java.util.function.Supplier; /** * {@link CellularAutomatonSimulation} instances run <i>The Game of Life</i>. + * + * @param <S> The type of state used in the simulation. */ public class CellularAutomatonSimulation<S extends State<S>> implements Simulation { private final Matrix<Cell<S>> grid; - private final ReadOnlyLongWrapper generationNumber = new ReadOnlyLongWrapper(); + private final Cell<Integer> generationNumber = new Cell<>(0); private final CellularAutomaton<S> automaton; private final Random generator; /** * Creates a new {@link CellularAutomatonSimulation} instance for a given automaton. * - * @param automaton a description of the {@link CellularAutomaton} + * @param automaton A description of the {@link CellularAutomaton}. + * @param generator The {@link Random} instance used for random state generation. */ public CellularAutomatonSimulation(CellularAutomaton<S> automaton, Random generator) { this.automaton = automaton; @@ -38,18 +39,6 @@ public class CellularAutomatonSimulation<S extends State<S>> this.generator = generator; } - /** - * Goes through each {@link Cell} in this {@code CellGrid} and sets it states with a - * state obtained from the supplier. - * - * @param generator {@link Random} instance used to generate a random state for each cell - * {@link Cell}. - */ - public void fillRandomly(Random generator) { - for (Cell<S> cell : this.grid) { - cell.set(this.automaton.randomState(generator)); - } - } @Override public int numberOfColumns() { @@ -61,24 +50,32 @@ public class CellularAutomatonSimulation<S extends State<S>> return this.grid.height(); } + /** + * Returns the {@link Cell} at the specified coordinate. + * + * @param coordinate The coordinate of the cell to retrieve. + * @return The cell at the specified coordinate. + */ public Cell<S> at(Coordinate coordinate) { return this.grid.get(coordinate); } + @Override public void updateToNextGeneration() { - this.generationNumber.set(getGenerationNumber() + 1); + this.generationNumber.set(this.generationNumber.get()+1); Matrix<S> nextStates = this.nextGenerationMatrix(); for (Coordinate coordinate : this.grid.coordinates()) { this.at(coordinate).set(nextStates.get(coordinate)); } } - /** Computes the {link Matrix} of states obtained after a single step of updates + + /** Computes the {@link Matrix} of states obtained after a single step of updates * of the simulation. * * @return the states of each cell after one generation */ private Matrix<S> nextGenerationMatrix() { - return new Matrix<S>( + return new Matrix<>( this.grid.width(), this.grid.height(), new NextGenerationInitializer<>(this) @@ -92,7 +89,6 @@ public class CellularAutomatonSimulation<S extends State<S>> @Override public void copy(Coordinate source, Coordinate destination) { - System.out.println("bip (" + source + ") (" + destination + ")"); S state = this.at(source).get(); this.at(destination).set(state); } @@ -109,29 +105,13 @@ public class CellularAutomatonSimulation<S extends State<S>> ); } - - /** - * Returns the current generationNumber. - * - * @return the current generationNumber - */ - private long getGenerationNumber() { - return this.generationNumber.get(); - } - - /** - * Returns the generationNumber {@link ReadOnlyLongProperty}. - * - * @return the generationNumber {@link ReadOnlyLongProperty} - */ - public ReadOnlyLongProperty generationNumberProperty() { - return this.generationNumber.getReadOnlyProperty(); + @Override + public void setGenerationNumberChangeListener(OnChangeListener<Integer> listener){ + this.generationNumber.addOnChangeListener(listener); } - /** - * Clears the current game. - */ + @Override public void clear() { for (Cell<S> cell : this.grid) { cell.set(this.automaton.defaultState()); @@ -139,12 +119,13 @@ public class CellularAutomatonSimulation<S extends State<S>> this.generationNumber.set(0); } - /** - * Clears the current game and randomly generates a new one. - */ + + @Override public void reset() { - this.clear(); - this.fillRandomly(this.generator); + for (Cell<S> cell : this.grid) { + cell.set(this.automaton.randomState(generator)); + } + this.generationNumber.set(0); } @Override diff --git a/src/main/java/model/ConstantCellInitializer.java b/src/main/java/model/ConstantCellInitializer.java index 43edd96..1016b57 100644 --- a/src/main/java/model/ConstantCellInitializer.java +++ b/src/main/java/model/ConstantCellInitializer.java @@ -1,11 +1,23 @@ package model; import datastruct.Coordinate; +import datastruct.Matrix; import datastruct.MatrixInitializer; +/** + * An initializer for {@link Matrix} of {@link Cell}s, where each cell is initialized to the + * same value. + * + * @param <T> the type of content of each cell + */ public class ConstantCellInitializer<T> implements MatrixInitializer<Cell<T>> { private final T defaultValue; + /** Make a new {@link MatrixInitializer} with cells containing a {@link Cell} with the same + * value. + * + * @param defaultValue the value stored in each cell. + */ public ConstantCellInitializer(T defaultValue) { this.defaultValue = defaultValue; } diff --git a/src/main/java/model/NextGenerationInitializer.java b/src/main/java/model/NextGenerationInitializer.java index f344e93..1242f43 100644 --- a/src/main/java/model/NextGenerationInitializer.java +++ b/src/main/java/model/NextGenerationInitializer.java @@ -1,16 +1,27 @@ package model; import datastruct.Coordinate; -import datastruct.Matrix; import datastruct.MatrixInitializer; - +import datastruct.Matrix; +import controller.Simulation; import java.util.ArrayList; import java.util.List; +/** + * An initializer for a {@link Matrix} of states, where each state is computed based on the value + * of its neighbours in a {@link Simulation} of a cellular automaton. + * + * @param <S> the type of states in the simulation. + */ public class NextGenerationInitializer<S extends State<S>> implements MatrixInitializer<S> { private final CellularAutomatonSimulation<S> simulation; + /** Create a {@link MatrixInitializer} to compute the next generation in + * a 2D cellular automaton. + * + * @param simulation the {@link Simulation} representing the cellular automaton. + */ public NextGenerationInitializer(CellularAutomatonSimulation<S> simulation) { this.simulation = simulation; } @@ -26,6 +37,14 @@ public class NextGenerationInitializer<S extends State<S>> implements MatrixInit return state.update(neighbours); } + /** Computes the grid {@link Coordinate} for an arbitrary {@link Coordinate}, even outside + * the grid. This is done by considering that the grid wraps over its edges, connecting the left side to the right + * side, and the top side to the bottom side. This way, every cell has 4 orthogonal + * neighbours and 4 diagonal neighbours. + * + * @param coordinate a {@link Coordinate} that may be outside the grid. + * @return a corresponding {@link Coordinate}, that is inside the grid. + */ private Coordinate wrap(Coordinate coordinate) { return new Coordinate( modulo(coordinate.x(),this.simulation.numberOfColumns()), diff --git a/src/main/java/model/State.java b/src/main/java/model/State.java index b21230c..213723b 100644 --- a/src/main/java/model/State.java +++ b/src/main/java/model/State.java @@ -4,12 +4,51 @@ import javafx.scene.paint.Color; import java.util.List; +/** + * Represents a state of a cell in a cellular automaton, and the update rules for the cellular + * automaton. + * + * @param <S> The type of state used in the cellular automaton. + */ public interface State<S> { + /** + * Returns the color associated with this state. + * + * @return The color representing this state. + */ Color getColor(); + /** + * Computes and returns the next state based on the rules of the cellular automaton. + * + * @return The next state. + */ S next(); + /** + * Updates the state based on the states of its neighboring cells. + * + * @param neighbours A list of neighboring cell states. + * @return The updated state based on the neighbors. + */ S update(List<State<S>> neighbours); -} + /** + * Counts the occurrences of a specific state within a list of neighboring states. + * + * @param <T> The type of state to count. + * @param state The state to count occurrences of. + * @param neighbours A list of neighboring states to search within. + * @return The number of times the specified state appears in the list of neighbors. + */ + static <T> int count(T state, List<T> neighbours) { + int count = 0; + for (T neighbour : neighbours) { + if (neighbour.equals(state)) { + count++; + } + } + return count; + } +} \ No newline at end of file diff --git a/src/main/java/model/automata/BiColorAutomaton.java b/src/main/java/model/automata/BiColorAutomaton.java index 7a1cb11..da2c939 100644 --- a/src/main/java/model/automata/BiColorAutomaton.java +++ b/src/main/java/model/automata/BiColorAutomaton.java @@ -1,7 +1,5 @@ package model.automata; -import model.CellularAutomaton; - import java.util.Random; public class BiColorAutomaton extends AbstractAutomaton<BiColorState> { diff --git a/src/main/java/model/automata/BiColorState.java b/src/main/java/model/automata/BiColorState.java index bdeb690..8344296 100644 --- a/src/main/java/model/automata/BiColorState.java +++ b/src/main/java/model/automata/BiColorState.java @@ -4,7 +4,6 @@ import javafx.scene.paint.Color; import model.State; import java.util.List; -import java.util.Random; public enum BiColorState implements State<BiColorState> { BLUE, RED, DEAD; @@ -31,22 +30,15 @@ public enum BiColorState implements State<BiColorState> { @Override public BiColorState update(List<State<BiColorState>> neighbours) { - int countBlue = 0; - int countRed = 0; - for (State<BiColorState> neighbour : neighbours) { - if (neighbour == RED) { - countRed++; - } - if (neighbour == BLUE) { - countBlue++; - } - } + int countBlue = State.count(BLUE, neighbours); + int countRed = State.count(RED, neighbours); + int countAlive = countBlue + countRed; if (this == DEAD) { - return (countBlue + countRed != 3)? DEAD: + return (countAlive != 3)? DEAD: countBlue > countRed? BLUE: RED; } - return 2 <= countBlue + countRed && countBlue + countRed <= 3? this: + return 2 <= countAlive && countAlive <= 3? this: DEAD; } diff --git a/src/main/java/model/automata/BriansBrainState.java b/src/main/java/model/automata/BriansBrainState.java index 1f831b8..5658683 100644 --- a/src/main/java/model/automata/BriansBrainState.java +++ b/src/main/java/model/automata/BriansBrainState.java @@ -4,7 +4,6 @@ import javafx.scene.paint.Color; import model.State; import java.util.List; -import java.util.Random; public enum BriansBrainState implements State<BriansBrainState> { ON, OFF, DYING; @@ -33,19 +32,11 @@ public enum BriansBrainState implements State<BriansBrainState> { case ON -> DYING; case DYING -> OFF; case OFF -> { - int count = countList(ON, neighbours); + int count = State.count(ON, neighbours); yield count==2 ? ON : OFF; } }; } - static <T> int countList(T value, List<T> elements) { - int count = 0; - for (T v : elements) { - if (v.equals(value)) { - count++; - } - } - return count; - } + } diff --git a/src/main/java/model/automata/GameOfLifeAutomaton.java b/src/main/java/model/automata/GameOfLifeAutomaton.java index cfe2b73..a25c577 100644 --- a/src/main/java/model/automata/GameOfLifeAutomaton.java +++ b/src/main/java/model/automata/GameOfLifeAutomaton.java @@ -1,12 +1,10 @@ package model.automata; -import model.CellularAutomaton; - import java.util.Random; public class GameOfLifeAutomaton extends AbstractAutomaton<GameOfLifeState> { - protected GameOfLifeAutomaton(int numberOfColumns, int numberOfRows) { + public GameOfLifeAutomaton(int numberOfColumns, int numberOfRows) { super(numberOfColumns, numberOfRows); } diff --git a/src/main/java/model/automata/GameOfLifeState.java b/src/main/java/model/automata/GameOfLifeState.java index 8e4e546..1d8c75c 100644 --- a/src/main/java/model/automata/GameOfLifeState.java +++ b/src/main/java/model/automata/GameOfLifeState.java @@ -4,7 +4,6 @@ import javafx.scene.paint.Color; import model.State; import java.util.List; -import java.util.Random; /** * {@link GameOfLifeState} instances represent the possible states of a {@link GameOfLifeState}. @@ -31,12 +30,7 @@ public enum GameOfLifeState implements State<GameOfLifeState> { @Override public GameOfLifeState update(List<State<GameOfLifeState>> neighbours) { - int countAlive = 0; - for (State<GameOfLifeState> state : neighbours) { - if (state.equals(ALIVE)) { - countAlive++; - } - } + int countAlive = State.count(ALIVE, neighbours); boolean isAlive = (this == DEAD && 3 == countAlive) || (this == ALIVE && 2 <= countAlive && countAlive <= 3); diff --git a/src/main/java/model/automata/SeedsAutomaton.java b/src/main/java/model/automata/SeedsAutomaton.java index 9a9b4a3..c7204df 100644 --- a/src/main/java/model/automata/SeedsAutomaton.java +++ b/src/main/java/model/automata/SeedsAutomaton.java @@ -14,7 +14,7 @@ public class SeedsAutomaton extends AbstractAutomaton<SeedsState> { @Override public SeedsState randomState(Random generator) { - return generator.nextInt() == 0? + return generator.nextInt(10) == 0? SeedsState.ON: SeedsState.OFF; } diff --git a/src/main/java/model/automata/SeedsState.java b/src/main/java/model/automata/SeedsState.java index ebfe4c3..c167fad 100644 --- a/src/main/java/model/automata/SeedsState.java +++ b/src/main/java/model/automata/SeedsState.java @@ -5,14 +5,13 @@ import model.State; import java.util.List; -import static model.automata.BriansBrainState.countList; public enum SeedsState implements State<SeedsState> { ON, OFF; @Override public Color getColor() { return switch (this) { - case ON -> Color.WHITE; + case ON -> Color.GOLD; case OFF -> Color.BLACK; }; } @@ -29,7 +28,7 @@ public enum SeedsState implements State<SeedsState> { public SeedsState update(List<State<SeedsState>> neighbours) { return switch (this) { case ON -> OFF; - case OFF -> countList(ON,neighbours) == 2 ? ON: OFF; + case OFF -> State.count(ON,neighbours) == 2 ? ON: OFF; }; } diff --git a/src/main/java/view/FillingMouseListener.java b/src/main/java/view/FillingMouseListener.java index 5e7b207..a4946eb 100644 --- a/src/main/java/view/FillingMouseListener.java +++ b/src/main/java/view/FillingMouseListener.java @@ -14,7 +14,7 @@ public class FillingMouseListener implements MouseListener { } @Override - public void onMouseReleased(MouseEvent event, Coordinate coord) { + public void onMouseReleased(MouseEvent event, Coordinate coordinate) { this.matrix.resetWaitingListener(); } @@ -34,4 +34,5 @@ public class FillingMouseListener implements MouseListener { new FillingMouseListener(this.matrix, coordinate) ); } + } diff --git a/src/main/java/view/MatrixPane.java b/src/main/java/view/MatrixPane.java index 41ed00d..050f739 100644 --- a/src/main/java/view/MatrixPane.java +++ b/src/main/java/view/MatrixPane.java @@ -52,6 +52,7 @@ public class MatrixPane extends GridPane { } private void addEventHandler(Rectangle cellRectangle, Coordinate coord) { + cellRectangle.addEventHandler( MouseEvent.MOUSE_PRESSED, event -> mouseListener.onMousePressed(event, coord) diff --git a/src/main/java/view/MouseListener.java b/src/main/java/view/MouseListener.java index ef83233..775cc91 100644 --- a/src/main/java/view/MouseListener.java +++ b/src/main/java/view/MouseListener.java @@ -9,4 +9,5 @@ interface MouseListener { default void onMouseReleased(MouseEvent event, Coordinate coordinate) {} default void onMouseEntered(MouseEvent event, Coordinate coordinate) {}; + } diff --git a/src/main/java/view/WaitingMouseListener.java b/src/main/java/view/WaitingMouseListener.java index b56d65a..68601de 100644 --- a/src/main/java/view/WaitingMouseListener.java +++ b/src/main/java/view/WaitingMouseListener.java @@ -13,8 +13,10 @@ class WaitingMouseListener implements MouseListener { } @Override - public void onMousePressed(MouseEvent event, Coordinate coord) { - this.matrix.getController().getSimulation().next(coord); - matrix.setMouseListener(new FillingMouseListener(this.matrix, coord)); + public void onMousePressed(MouseEvent event, Coordinate coordinate) { + this.matrix.getController().getSimulation().next(coordinate); + this.matrix.setMouseListener(new FillingMouseListener(this.matrix, coordinate)); } + + } diff --git a/src/test/java/datastruct/ConstantMatrixInitializerTest.java b/src/test/java/datastruct/ConstantMatrixInitializerTest.java new file mode 100644 index 0000000..4db9c2a --- /dev/null +++ b/src/test/java/datastruct/ConstantMatrixInitializerTest.java @@ -0,0 +1,31 @@ +package datastruct; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ConstantMatrixInitializerTest { + @Test + public void testMatrixInitializationWithConstantValue() { + Matrix<String> matrix = new Matrix<>(3, 3, new ConstantMatrixInitializer<>("X")); + + // Test that all cells have the constant value. + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 3; y++) { + assertEquals("X", matrix.get(x, y)); + } + } + } + + @Test + public void testMatrixInitializationWithConstantValue2() { + Matrix<Integer> matrix = new Matrix<>(3, 5, new ConstantMatrixInitializer<>(12)); + + // Test that all cells have the constant value. + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 5; y++) { + assertEquals(12, matrix.get(x, y)); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/datastruct/CoordinateIteratorTest.java b/src/test/java/datastruct/CoordinateIteratorTest.java new file mode 100644 index 0000000..90be82e --- /dev/null +++ b/src/test/java/datastruct/CoordinateIteratorTest.java @@ -0,0 +1,53 @@ +package datastruct; + +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.*; + +class CoordinateIteratorTest { + + @Test + public void testIteratorWithZeroDimensions() { + CoordinateIterator iterator = new CoordinateIterator(0, 0); + assertFalse(iterator.hasNext()); + } + + @Test + public void testIteratorWithOneDimension() { + CoordinateIterator iterator = new CoordinateIterator(5, 1); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(0, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(1, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(2, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(3, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(4, 0), iterator.next()); + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, iterator::next); + } + + @Test + public void testIteratorWithTwoDimensions() { + CoordinateIterator iterator = new CoordinateIterator(3, 2); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(0, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(1, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(2, 0), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(0, 1), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(1, 1), iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(Coordinate.of(2, 1), iterator.next()); + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, iterator::next); + } + +} \ No newline at end of file diff --git a/src/test/java/datastruct/CoordinateTest.java b/src/test/java/datastruct/CoordinateTest.java new file mode 100644 index 0000000..bddaae9 --- /dev/null +++ b/src/test/java/datastruct/CoordinateTest.java @@ -0,0 +1,100 @@ +package datastruct; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class CoordinateTest { + @Test + public void testCoordinateCreation() { + Coordinate coordinate = Coordinate.of(3, 4); + assertEquals(3, coordinate.x()); + assertEquals(4, coordinate.y()); + } + + @Test + public void testLeft() { + Coordinate coordinate = Coordinate.of(3, 4); + Coordinate left = coordinate.left(); + assertEquals(2, left.x()); + assertEquals(4, left.y()); + } + + @Test + public void testRight() { + Coordinate coordinate = Coordinate.of(3, 4); + Coordinate right = coordinate.right(); + assertEquals(4, right.x()); + assertEquals(4, right.y()); + } + + @Test + public void testAbove() { + Coordinate coordinate = Coordinate.of(3, 4); + Coordinate above = coordinate.above(); + assertEquals(3, above.x()); + assertEquals(5, above.y()); + } + + @Test + public void testBelow() { + Coordinate coordinate = Coordinate.of(3, 4); + Coordinate below = coordinate.below(); + assertEquals(3, below.x()); + assertEquals(3, below.y()); + } + + @Test + public void testOrthogonalNeighbours() { + Coordinate coordinate = Coordinate.of(3, 4); + List<Coordinate> neighbours = coordinate.orthogonalNeighbours(); + assertEquals(4, neighbours.size()); + assertTrue(neighbours.contains(Coordinate.of(2, 4))); + assertTrue(neighbours.contains(Coordinate.of(4, 4))); + assertTrue(neighbours.contains(Coordinate.of(3, 3))); + assertTrue(neighbours.contains(Coordinate.of(3, 5))); + assertFalse(neighbours.contains(coordinate)); + assertFalse(neighbours.contains(Coordinate.of(1, 4))); + assertFalse(neighbours.contains(Coordinate.of(5, 4))); + assertFalse(neighbours.contains(Coordinate.of(3, 6))); + assertFalse(neighbours.contains(Coordinate.of(3, 1))); + } + + @Test + public void testDiagonalNeighbours() { + Coordinate coordinate = Coordinate.of(3, 4); + List<Coordinate> neighbours = coordinate.diagonalNeighbours(); + assertEquals(4, neighbours.size()); + assertTrue(neighbours.contains(Coordinate.of(2, 3))); + assertTrue(neighbours.contains(Coordinate.of(4, 3))); + assertTrue(neighbours.contains(Coordinate.of(2, 5))); + assertTrue(neighbours.contains(Coordinate.of(4, 5))); + assertFalse(neighbours.contains(coordinate)); + assertFalse(neighbours.contains(Coordinate.of(1, 4))); + assertFalse(neighbours.contains(Coordinate.of(5, 4))); + assertFalse(neighbours.contains(Coordinate.of(3, 6))); + assertFalse(neighbours.contains(Coordinate.of(3, 1))); + } + + @Test + public void testOrthodiagonalNeighbours() { + Coordinate coordinate = Coordinate.of(3, 4); + List<Coordinate> neighbours = coordinate.orthodiagonalNeighbours(); + assertEquals(8, neighbours.size()); + assertTrue(neighbours.contains(Coordinate.of(2, 4))); + assertTrue(neighbours.contains(Coordinate.of(4, 4))); + assertTrue(neighbours.contains(Coordinate.of(3, 3))); + assertTrue(neighbours.contains(Coordinate.of(3, 5))); + assertTrue(neighbours.contains(Coordinate.of(2, 3))); + assertTrue(neighbours.contains(Coordinate.of(4, 3))); + assertTrue(neighbours.contains(Coordinate.of(2, 5))); + assertTrue(neighbours.contains(Coordinate.of(4, 5))); + assertFalse(neighbours.contains(coordinate)); + assertFalse(neighbours.contains(Coordinate.of(1, 4))); + assertFalse(neighbours.contains(Coordinate.of(5, 4))); + assertFalse(neighbours.contains(Coordinate.of(3, 6))); + assertFalse(neighbours.contains(Coordinate.of(3, 1))); + } +} \ No newline at end of file diff --git a/src/test/java/datastruct/MatrixTest.java b/src/test/java/datastruct/MatrixTest.java new file mode 100644 index 0000000..346c29b --- /dev/null +++ b/src/test/java/datastruct/MatrixTest.java @@ -0,0 +1,88 @@ +package datastruct; + +import org.junit.jupiter.api.Test; + +import java.util.Iterator; + +import static org.junit.jupiter.api.Assertions.*; + +class MatrixTest { + + private final MatrixInitializer<Integer> sumInitializer = + coord -> coord.x() + coord.y(); + + @Test + public void testMatrixCreationWithInitializer() { + Matrix<Integer> matrix = new Matrix<>(3, 4, sumInitializer); + assertEquals(3, matrix.width()); + assertEquals(4, matrix.height()); + assertEquals(4, matrix.get(2, 2)); + assertEquals(3, matrix.get(1, 2)); + assertEquals(3, matrix.get(2, 1)); + assertEquals(2, matrix.get(1, 1)); + } + + @Test + public void testMatrixCreationWithInitialValue() { + Matrix<String> matrix = new Matrix<>(2, 2, "Foo"); + assertEquals(2, matrix.width()); + assertEquals(2, matrix.height()); + assertEquals("Foo", matrix.get(1, 1)); // Test a specific cell value. + } + + @Test + public void testMatrixSetAndGet() { + Matrix<Integer> matrix = new Matrix<>(3, 3, 0); + matrix.set(1, 1, 42); + assertEquals(42, matrix.get(1, 1)); + matrix.set(0, 2, 10); + assertEquals(10, matrix.get(0, 2)); + matrix.set(Coordinate.of(2, 2), 99); + assertEquals(99, matrix.get(Coordinate.of(2, 2))); + } + + @Test + public void testMatrixWidthAndHeight() { + Matrix<String> matrix = new Matrix<>(4, 2, "A"); + assertEquals(4, matrix.width()); + assertEquals(2, matrix.height()); + matrix.set(3, 1, "B"); + assertEquals(4, matrix.width()); + assertEquals(2, matrix.height()); + } + + @Test + public void testMatrixIterator() { + Matrix<Integer> matrix = new Matrix<>(2, 2, sumInitializer); + Iterator<Integer> iterator = matrix.iterator(); + assertTrue(iterator.hasNext()); + assertEquals(0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(1, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(1, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals(2, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test + public void testMatrixCoordinates() { + Matrix<Integer> matrix = new Matrix<>(2, 2, 0); + Iterable<Coordinate> coordinates = matrix.coordinates(); + int count = 0; + for (Coordinate coord : coordinates) { + count++; + } + assertEquals(4, count); + } + + @Test + public void testMatrixLens() { + Matrix<Integer> matrix = new Matrix<>(2, 2, 0); + Lens<Integer> lens = matrix.at(1, 1); + assertEquals(0, lens.get()); + lens.set(42); + assertEquals(42, matrix.get(1, 1)); + } +} diff --git a/src/test/java/model/CellTest.java b/src/test/java/model/CellTest.java new file mode 100644 index 0000000..f879455 --- /dev/null +++ b/src/test/java/model/CellTest.java @@ -0,0 +1,40 @@ +package model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CellTest { + + private record Change<T>(T oldValue, T newValue) {} + private static class MockListener<T> implements OnChangeListener<T> { + private Change<T> lastChange = null; + + public boolean checksLastChange(T oldValue, T newValue) { + return this.lastChange != null + && this.lastChange.oldValue.equals(oldValue) + && this.lastChange.newValue.equals(newValue); + } + + @Override + public void valueChanged(T oldValue, T newValue) { + lastChange = new Change<>(oldValue, newValue); + } + } + @Test + public void testCellValueChangeWithListener() { + Cell<Integer> cell = new Cell<>(42); + MockListener<Integer> mockListener = new MockListener<>(); + cell.addOnChangeListener(mockListener); + cell.set(99); + assertTrue(mockListener.checksLastChange(42, 99)); + assertEquals(99, cell.get()); + } + + @Test + public void testCellWithoutListener() { + Cell<String> cell = new Cell<>("Foo"); + cell.set("Bar"); + assertEquals("Bar", cell.get()); + } +} \ No newline at end of file diff --git a/src/test/java/model/CellularAutomatonSimulationTest.java b/src/test/java/model/CellularAutomatonSimulationTest.java new file mode 100644 index 0000000..81c4eac --- /dev/null +++ b/src/test/java/model/CellularAutomatonSimulationTest.java @@ -0,0 +1,162 @@ +package model; + +import datastruct.Coordinate; +import javafx.scene.paint.Color; +import model.automata.GameOfLifeAutomaton; +import static model.automata.GameOfLifeState.*; +import model.automata.GameOfLifeState; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import static org.junit.jupiter.api.Assertions.*; + + +class CellularAutomatonSimulationTest { + + private final CellularAutomatonSimulation<GameOfLifeState> simulation; + private final CellularAutomaton<GameOfLifeState> automaton; + private final Random randomGenerator; + + CellularAutomatonSimulationTest() { + this.randomGenerator = new Random(); + this.automaton = new GameOfLifeAutomaton(4,3); + this.simulation = new CellularAutomatonSimulation<>(automaton, randomGenerator); + } + @BeforeEach + public void setUp() { + this.simulation.clear(); + } + + @Test + public void testNumberOfColumns() { + assertEquals(4, simulation.numberOfColumns()); + } + + @Test + public void testNumberOfRows() { + assertEquals(3, simulation.numberOfRows()); + } + + @Test + public void testAt() { + Coordinate coordinate = Coordinate.of(1, 2); + Cell<GameOfLifeState> cell = simulation.at(coordinate); + assertEquals(DEAD,cell.get()); + } + + @Test + public void testUpdateToNextGeneration() { + GameOfLifeState[][] input = + { {ALIVE, ALIVE, ALIVE, DEAD}, + {ALIVE, DEAD, DEAD, DEAD}, + {DEAD, ALIVE, DEAD, DEAD} + }; + GameOfLifeState[][] output = + { {ALIVE, DEAD, ALIVE, ALIVE}, + {ALIVE, DEAD, ALIVE, ALIVE}, + {DEAD, DEAD, ALIVE, ALIVE} + }; + for (Coordinate coordinate : this.simulation) { + this.simulation.at(coordinate).set(input[coordinate.y()][coordinate.x()]); + } + simulation.updateToNextGeneration(); + for (Coordinate coordinate : this.simulation) { + assertEquals( + output[coordinate.y()][coordinate.x()], + this.simulation.at(coordinate).get(), + "Generation at " + coordinate + ); + } + } + + @Test + public void testNext() { + Coordinate coordinate = Coordinate.of(1, 1); + Cell<GameOfLifeState> cell = simulation.at(coordinate); + GameOfLifeState oldState = cell.get(); + simulation.next(coordinate); + assertNotEquals(oldState, cell.get()); + } + + @Test + public void testCopy() { + Coordinate source = Coordinate.of(1, 1); + Coordinate destination = Coordinate.of(2, 2); + this.simulation.at(source).set(ALIVE); + simulation.copy(source, destination); + assertEquals( + ALIVE, + this.simulation.at(destination).get() + ); + } + + @Test + public void testColor() { + Coordinate coordinate = Coordinate.of(1, 1); + assertEquals(Color.WHITE, this.simulation.getColor(coordinate)); + this.simulation.at(coordinate).set(ALIVE); + assertEquals(Color.RED, this.simulation.getColor(coordinate)); + } + + @Test + public void testSetChangeListener() { + Coordinate coordinate = Coordinate.of(1, 1); + Coordinate otherCoordinate = Coordinate.of(0,2); + Cell<GameOfLifeState> cell = simulation.at(coordinate); + List<GameOfLifeState> states = new ArrayList<>(); + Runnable listener = () -> states.add(this.simulation.at(coordinate).get()); + simulation.setChangeListener(coordinate, listener); + this.simulation.at(otherCoordinate).set(ALIVE); + assertEquals(Collections.emptyList(), states); + this.simulation.at(coordinate).set(ALIVE); + this.simulation.at(otherCoordinate).set(DEAD); + assertEquals(List.of(ALIVE), states); + this.simulation.at(coordinate).set(ALIVE); + this.simulation.at(otherCoordinate).set(ALIVE); + this.simulation.at(coordinate).set(DEAD); + assertEquals(List.of(ALIVE, ALIVE, DEAD), states); + } + + @Test + public void testSetGenerationNumberChangeListener() { + List<Integer> values = new ArrayList<>(); + OnChangeListener<Integer> listener = (oldValue, newValue) -> values.add(newValue); + simulation.setGenerationNumberChangeListener(listener); + assertEquals(Collections.emptyList(), values); + simulation.clear(); + assertEquals(List.of(0), values); + simulation.updateToNextGeneration(); + assertEquals(List.of(0,1), values); + simulation.updateToNextGeneration(); + simulation.updateToNextGeneration(); + assertEquals(List.of(0,1,2,3), values); + } + + @Test + public void testClear() { + for (Coordinate coordinate : this.simulation) { + this.simulation.at(coordinate).set(ALIVE); + } + this.simulation.clear(); + for (Coordinate coordinate : this.simulation) { + assertEquals(DEAD, this.simulation.at(coordinate).get()); + } + } + + @Test + public void testReset() { + randomGenerator.setSeed(321); + this.simulation.reset(); + int count = 0; + for (Coordinate coordinate : this.simulation) { + if (this.simulation.at(coordinate).get().equals(ALIVE)) { + count = count + 1; + } + } + assertEquals(7, count); + } +} \ No newline at end of file diff --git a/src/test/java/model/GridTest.java b/src/test/java/model/GridTest.java deleted file mode 100644 index 5487ff2..0000000 --- a/src/test/java/model/GridTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package model; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class GridTest { - private CellGrid grid; - -// @BeforeEach -// public void initializeGrid() { -// grid = new CellGrid(6, 6); -// } -// -// @Test -// public void testGetNeighbours() { -// assertThat(grid.getNeighbors(1, 1)).isNotNull(); -// assertThat(grid.getNeighbors(1, 1)).hasSize(8); -// assertThat(grid.getNeighbors(1, 1)) -// .containsExactlyInAnyOrder(grid.getCell(0, 0), -// grid.getCell(0, 1), -// grid.getCell(0, 2), -// grid.getCell(1, 0), -// grid.getCell(1, 2), -// grid.getCell(2, 0), -// grid.getCell(2, 1), -// grid.getCell(2, 2)); -// } -// -// @Test -// public void testCountAliveNeighbours() { -// assertThat(grid.countAliveNeighbors(1, 1)).isEqualTo(0); -// grid.getCell(2, 2).setState(CellState.ALIVE); -// grid.getCell(0, 0).setState(CellState.ALIVE); -// assertThat(grid.countAliveNeighbors(1, 1)).isEqualTo(2); -// } -// -// @Test -// public void testCalculateNextState() { -// grid.getCell(1, 0).setState(CellState.ALIVE); -// grid.getCell(1, 1).setState(CellState.ALIVE); -// grid.getCell(1, 2).setState(CellState.ALIVE); -// assertThat(grid.calculateNextState(0, 0).isAlive).isFalse(); -// assertThat(grid.calculateNextState(1, 0).isAlive).isFalse(); -// assertThat(grid.calculateNextState(1, 1).isAlive).isTrue(); -// assertThat(grid.calculateNextState(2, 1).isAlive).isTrue(); -// } - -} \ No newline at end of file diff --git a/src/test/java/model/StateTest.java b/src/test/java/model/StateTest.java new file mode 100644 index 0000000..54a77f4 --- /dev/null +++ b/src/test/java/model/StateTest.java @@ -0,0 +1,39 @@ +package model; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static model.State.count; +import static org.junit.jupiter.api.Assertions.*; + +class StateTest { + @Test + public void testCountMethodWithInteger() { + List<Integer> neighbours = List.of(1, 2, 1, 3, 1); + int result = count(1, neighbours); + assertEquals(3, result); + } + + @Test + public void testCountMethodWithString() { + List<String> neighbours = List.of("apple", "banana", "apple", "cherry", "apple"); + int result = count("apple", neighbours); + assertEquals(3, result); + } + + @Test + public void testCountMethodWithEmptyList() { + List<Double> neighbours = List.of(); + int result = count(5.0, neighbours); + assertEquals(0, result); + } + + @Test + public void testCountMethodWithNoMatchingElements() { + List<Character> neighbours = List.of('a', 'b', 'c'); + int result = count('x', neighbours); + assertEquals(0, result); + } + +} \ No newline at end of file diff --git a/src/test/java/model/automata/GameOfLifeStateTest.java b/src/test/java/model/automata/GameOfLifeStateTest.java new file mode 100644 index 0000000..8c9f0d1 --- /dev/null +++ b/src/test/java/model/automata/GameOfLifeStateTest.java @@ -0,0 +1,70 @@ +package model.automata; + +import javafx.scene.paint.Color; +import model.State; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static model.automata.GameOfLifeState.*; +import static org.junit.jupiter.api.Assertions.*; + +class GameOfLifeStateTest { + @Test + public void testGetColor() { + assertEquals(Color.WHITE, DEAD.getColor()); + assertEquals(Color.RED, ALIVE.getColor()); + } + + @Test + public void testNext() { + assertEquals(ALIVE.next(), DEAD); + assertEquals(DEAD.next(), ALIVE); + } + + @Test + public void testAliveUpdate() { + // Test with three alive neighbors, should be ALIVE + List<State<GameOfLifeState>> aliveNeighbors = + List.of(ALIVE, DEAD, ALIVE, DEAD, ALIVE); + assertEquals(ALIVE, ALIVE.update(aliveNeighbors)); + + // Test with two alive neighbors, should be ALIVE + List<State<GameOfLifeState>> twoAliveNeighbors = + List.of(ALIVE, DEAD, ALIVE, DEAD, DEAD); + assertEquals(ALIVE, ALIVE.update(twoAliveNeighbors)); + + // Test with four alive neighbors, should be DEAD + List<State<GameOfLifeState>> fourAliveNeighbors = + List.of(ALIVE, ALIVE, DEAD, ALIVE, ALIVE); + assertEquals(DEAD, ALIVE.update(fourAliveNeighbors)); + + // Test with zero alive neighbors, should be DEAD + List<State<GameOfLifeState>> zeroAliveNeighbors = + List.of(DEAD, DEAD, DEAD, DEAD); + assertEquals(DEAD, ALIVE.update(zeroAliveNeighbors)); + } + + @Test + public void testDeadUpdate() { + // Test with three alive neighbors, should be ALIVE + List<State<GameOfLifeState>> aliveNeighbors = + List.of(ALIVE, DEAD, ALIVE, DEAD, ALIVE); + assertEquals(ALIVE, DEAD.update(aliveNeighbors)); + + // Test with two alive neighbors, should be DEAD + List<State<GameOfLifeState>> twoAliveNeighbors = + List.of(ALIVE, DEAD, ALIVE, DEAD, DEAD); + assertEquals(DEAD, DEAD.update(twoAliveNeighbors)); + + // Test with four alive neighbors, should be DEAD + List<State<GameOfLifeState>> fourAliveNeighbors = + List.of(ALIVE, ALIVE, DEAD, ALIVE, ALIVE); + assertEquals(DEAD, DEAD.update(fourAliveNeighbors)); + + // Test with zero alive neighbors, should be DEAD + List<State<GameOfLifeState>> zeroAliveNeighbors = + List.of(DEAD, DEAD, DEAD, DEAD); + assertEquals(DEAD, DEAD.update(zeroAliveNeighbors)); + } +} \ No newline at end of file -- GitLab