Skip to content
Snippets Groups Projects
Commit 669a2a62 authored by Guyslain's avatar Guyslain
Browse files

Refactoring

parent 0fa0c6d4
No related branches found
No related tags found
No related merge requests found
Showing
with 750 additions and 108 deletions
...@@ -5,8 +5,9 @@ import javafx.fxml.FXMLLoader; ...@@ -5,8 +5,9 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import model.GameOfLife; import model.CellGrid;
import model.Grid; import model.CellularAutomataSimulation;
import model.GameOfLifeState;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
...@@ -25,7 +26,7 @@ public class GameOfLifeApplication extends Application { ...@@ -25,7 +26,7 @@ public class GameOfLifeApplication extends Application {
private static final String APP_NAME = "Game of Life"; private static final String APP_NAME = "Game of Life";
private static final String VIEW_RESOURCE_PATH = "/view/view.fxml"; private static final String VIEW_RESOURCE_PATH = "/view/view.fxml";
private final GameOfLife gameOfLife; private final CellularAutomataSimulation<GameOfLifeState> gameOfLife;
private Stage primaryStage; private Stage primaryStage;
private Parent view; private Parent view;
...@@ -33,17 +34,21 @@ public class GameOfLifeApplication extends Application { ...@@ -33,17 +34,21 @@ public class GameOfLifeApplication extends Application {
* Creates a new {@code GameOfLifeApplication} instance. * Creates a new {@code GameOfLifeApplication} instance.
*/ */
public GameOfLifeApplication() { public GameOfLifeApplication() {
this(new GameOfLife(new Grid(NUMBER_OF_ROWS, NUMBER_OF_COLUMNS))); this(new CellularAutomataSimulation<GameOfLifeState>(
new CellGrid<>(NUMBER_OF_COLUMNS, NUMBER_OF_ROWS, GameOfLifeState.ALIVE),
GameOfLifeState.DEAD,
GameOfLifeState::random
));
} }
/** /**
* Creates a new {@code GameOfLifeApplication} instance given a {@link GameOfLife} instance. * Creates a new {@code GameOfLifeApplication} instance given a {@link CellularAutomataSimulation} instance.
* *
* @param gameOfLife the {@link GameOfLife} instance * @param cellularAutomataSimulation the {@link CellularAutomataSimulation} instance
* @throws NullPointerException if {@code gameOfLife} is {@code null} * @throws NullPointerException if {@code gameOfLife} is {@code null}
*/ */
private GameOfLifeApplication(GameOfLife gameOfLife) { private GameOfLifeApplication(CellularAutomataSimulation<GameOfLifeState> cellularAutomataSimulation) {
this.gameOfLife = requireNonNull(gameOfLife, "game of life is null"); this.gameOfLife = requireNonNull(cellularAutomataSimulation, "game of life is null");
} }
@Override @Override
...@@ -67,7 +72,7 @@ public class GameOfLifeApplication extends Application { ...@@ -67,7 +72,7 @@ public class GameOfLifeApplication extends Application {
loader.setLocation(location); loader.setLocation(location);
view = loader.load(); view = loader.load();
Controller controller = loader.getController(); Controller controller = loader.getController();
controller.setGameOfLife(gameOfLife); controller.setSimulation(gameOfLife);
} }
......
package controller; package controller;
import datastruct.Coordinate;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToggleGroup;
import model.GameOfLife; import javafx.util.Duration;
import model.Grid; import model.CellularAutomataSimulation;
import view.MatrixPane; import view.MatrixPane;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
...@@ -15,6 +21,7 @@ import static java.util.Objects.requireNonNull; ...@@ -15,6 +21,7 @@ import static java.util.Objects.requireNonNull;
*/ */
public class Controller { public class Controller {
public static final int PERIOD_IN_MILLISECONDS = 100;
@FXML @FXML
private ToggleButton playToggleButton; private ToggleButton playToggleButton;
@FXML @FXML
...@@ -23,12 +30,18 @@ public class Controller { ...@@ -23,12 +30,18 @@ public class Controller {
private Label generationNumberLabel; private Label generationNumberLabel;
@FXML @FXML
private MatrixPane matrixPane; private MatrixPane matrixPane;
private Timeline timeline;
private GameOfLife gameOfLife; public Simulation getSimulation() {
return simulation;
}
private Simulation simulation;
@FXML @FXML
private void initialize() { private void initialize() {
initializePlayAndPauseToggleButtons(); initializePlayAndPauseToggleButtons();
updateTimeline();
} }
private void initializePlayAndPauseToggleButtons() { private void initializePlayAndPauseToggleButtons() {
...@@ -39,46 +52,76 @@ public class Controller { ...@@ -39,46 +52,76 @@ public class Controller {
/** /**
* Sets {@link GameOfLife} instance. * Sets {@link CellularAutomataSimulation} instance.
* *
* @param gameOfLife {@link GameOfLife} instance * @param simulation {@link CellularAutomataSimulation} instance
* @throws NullPointerException if {@code gameOfLife} is {@code null} * @throws NullPointerException if {@code gameOfLife} is {@code null}
*/ */
public void setGameOfLife(GameOfLife gameOfLife) { public void setSimulation(Simulation simulation) {
this.gameOfLife = requireNonNull(gameOfLife, "game of life is null"); this.simulation = requireNonNull(simulation, "game of life is null");
setGenerationNumberLabelTextProperty(); setGenerationNumberLabelTextProperty();
initializeMatrixPane(); initializeMatrixPane();
} }
private void setGenerationNumberLabelTextProperty() { private void setGenerationNumberLabelTextProperty() {
generationNumberLabel.textProperty().bind(gameOfLife.generationNumberProperty().asString()); generationNumberLabel.textProperty().bind(simulation.generationNumberProperty().asString());
} }
private void initializeMatrixPane() { private void initializeMatrixPane() {
Grid grid = gameOfLife.getGrid(); matrixPane.initialize(this);
matrixPane.initialize(grid);
} }
@FXML @FXML
private void playToggleButtonAction() { private void playToggleButtonAction() {
gameOfLife.play(); this.play();
} }
@FXML @FXML
private void pauseToggleButtonAction() { private void pauseToggleButtonAction() {
gameOfLife.pause(); this.pause();
} }
@FXML @FXML
private void resetButtonAction() { private void resetButtonAction() {
gameOfLife.reset(); this.pause();
simulation.reset();
pauseToggleButton.setSelected(true); pauseToggleButton.setSelected(true);
} }
@FXML @FXML
private void clearButtonAction() { private void clearButtonAction() {
gameOfLife.clear(); this.pause();
simulation.clear();
pauseToggleButton.setSelected(true); pauseToggleButton.setSelected(true);
} }
public Iterable<Coordinate> coordinates() {
return simulation;
}
private void updateTimeline() {
Duration duration = new Duration(Controller.PERIOD_IN_MILLISECONDS);
EventHandler<ActionEvent> eventHandler =
event -> simulation.updateToNextGeneration();
KeyFrame keyFrame = new KeyFrame(duration, eventHandler);
timeline = new Timeline(keyFrame);
timeline.setCycleCount(Animation.INDEFINITE);
}
/**
* Plays the game.
*/
public void play() {
timeline.play();
}
/**
* Pauses the game.
*/
public void pause() {
timeline.pause();
}
} }
package controller;
import datastruct.Coordinate;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.scene.paint.Color;
public interface Simulation extends Iterable<Coordinate> {
int numberOfColumns();
int numberOfRows();
void updateToNextGeneration();
void next(Coordinate coordinate);
void copy(Coordinate source, Coordinate destination);
Color getColor(Coordinate coordinate);
void setChangeListener(Coordinate coordinate, Runnable run);
ReadOnlyLongProperty generationNumberProperty();
void reset();
void clear();
}
package datastruct;
public class ConstantMatrixInitializer<T> implements MatrixInitializer<T> {
private final T constant;
public ConstantMatrixInitializer(T constant) {
this.constant = constant;
}
@Override
public T initialValueAt(Coordinate coordinate) {
return constant;
}
}
package datastruct;
import java.util.ArrayList;
import java.util.List;
public record Coordinate(int x, int y) {
public static Coordinate of(int x, int y) {
return new Coordinate(x,y);
}
public Coordinate left() {
return new Coordinate(x-1,y);
}
public Coordinate right() {
return new Coordinate(x+1,y);
}
public Coordinate above() {
return new Coordinate(x,y+1);
}
public Coordinate below() {
return new Coordinate(x, y-1);
}
public List<Coordinate> orthogonalNeighbours() {
return List.of(
this.right(),
this.left(),
this.above(),
this.below()
);
}
public List<Coordinate> diagonalNeighbours() {
return List.of(
this.right().above(),
this.left().above(),
this.left().below(),
this.right().below()
);
}
public List<Coordinate> orthodiagonalNeighbours() {
List<Coordinate> neighbours = new ArrayList<>(this.orthogonalNeighbours());
neighbours.addAll(this.diagonalNeighbours());
return neighbours;
}
}
package datastruct;
import java.util.Iterator;
import java.util.NoSuchElementException;
class CoordinateIterator implements Iterator<Coordinate> {
private final int width;
private final int height;
private int x = 0;
private int y = 0;
public CoordinateIterator(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public boolean hasNext() {
return y < this.height;
}
@Override
public Coordinate next() {
if (!this.hasNext()) {
throw new NoSuchElementException();
}
Coordinate coord = 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;
}
}
package datastruct;
public interface Lens<S> {
S get();
void set(S value);
}
package datastruct;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Matrix<T> {
private final List<List<T>> matrix;
private final int width;
private final int height;
public Matrix(int width, int height, MatrixInitializer<T> initializer) {
this.width = width;
this.height = height;
this.matrix = new ArrayList<>();
this.initializeWith(initializer);
}
public Matrix(int width, int height, T initialValue) {
this(width, height, new ConstantMatrixInitializer<>(initialValue));
}
private void initializeWith(MatrixInitializer<T> initializer) {
for (int x = 0; x < width; x++) {
List<T> row = new ArrayList<>();
this.matrix.add(row);
for (int y = 0; y < height; y++) {
row.add(initializer.initialValueAt(Coordinate.of(x,y)));
}
}
}
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());
}
public void set(int x, int y, T value) {
this.matrix.get(x).set(y,value);
}
public void set(Coordinate coord, T value) {
this.set(coord.x(), coord.y(), value);
}
public Iterator<T> iterator() {
Iterator<Coordinate> coordIterator = this.coordinatesIterator();
return new MatrixIterator(this, coordIterator);
}
public Iterable<Coordinate> coordinates() {
return this::coordinatesIterator;
}
private Iterator<Coordinate> coordinatesIterator() {
return new CoordinateIterator(this.width, this.height);
}
public Lens<T> at(int x, int y) {
return new Lens<T>() {
@Override
public T get() {
return Matrix.this.get(x,y);
}
@Override
public void set(T value) {
Matrix.this.set(x,y,value);
}
};
}
public Lens<T> at(Coordinate coord) {
return this.at(coord.x(), coord.y());
}
}
package datastruct;
public interface MatrixInitializer<T> {
T initialValueAt(Coordinate coordinate);
}
package datastruct;
import java.util.Iterator;
import java.util.NoSuchElementException;
class MatrixIterator<T> implements Iterator<T> {
private final Iterator<Coordinate> coordIterator;
private final Matrix<T> matrix;
public MatrixIterator(Matrix<T> matrix, Iterator<Coordinate> coordIterator) {
this.coordIterator = coordIterator;
this.matrix = matrix;
}
@Override
public boolean hasNext() {
return coordIterator.hasNext();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return matrix.get(coordIterator.next());
}
}
...@@ -2,32 +2,39 @@ package model; ...@@ -2,32 +2,39 @@ package model;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
/** /**
* {@link Cell} instances represent the cells of <i>The Game of Life</i>. * {@link Cell} instances represent the cells of <i>The Game of Life</i>.
*/ */
public class Cell { public class Cell<S extends State<S>> {
private final Property<CellState> stateProperty = new SimpleObjectProperty<>(CellState.DEAD); private final Property<S> stateProperty;
public Cell(S initialState) {
this.stateProperty = new SimpleObjectProperty<>(initialState);
}
/** /**
* Determines whether this {@link Cell} is alive or not. * Determines the color associated with the state in which
* this {@link Cell} is.
* *
* @return {@code true} if this {@link Cell} is alive and {@code false} otherwise * @return the {@link Color} associated with the state in
* which this {@link Cell} is
*/ */
public boolean isAlive() { public Color getColor() {
return getState().isAlive; return this.getState().getColor();
} }
/** /**
* Sets the state of this {@link Cell}. * Sets the state of this {@link Cell}.
* *
* @param cellState the new state of this {@link Cell} * @param state the new state of this {@link Cell}
*/ */
public void setState(CellState cellState) { public void setState(S state) {
getStateProperty().setValue(cellState); getStateProperty().setValue(state);
} }
/** /**
...@@ -36,19 +43,16 @@ public class Cell { ...@@ -36,19 +43,16 @@ public class Cell {
* @return the current state of this {@link Cell} * @return the current state of this {@link Cell}
*/ */
public CellState getState(){ public S getState(){
return getStateProperty().getValue(); return getStateProperty().getValue();
} }
/** /**
* Change the state of this {@link Cell} from ALIVE to DEAD or from DEAD to ALIVE. * Change the state of this {@link Cell} to the next possible state.
*/ */
public void toggleState() { public void toggleState() {
CellState[] possibleStates = CellState.values(); setState(getState().next());
int stateOrdinal = getState().ordinal();
int numberOfPossibleStates = possibleStates.length;
setState(possibleStates[(stateOrdinal+1)%numberOfPossibleStates]);
} }
/** /**
...@@ -56,7 +60,7 @@ public class Cell { ...@@ -56,7 +60,7 @@ public class Cell {
* *
* @return this {@link Cell}'s state property. * @return this {@link Cell}'s state property.
*/ */
public Property<CellState> getStateProperty() { public Property<S> getStateProperty() {
return stateProperty; return stateProperty;
} }
......
package model; package model;
import datastruct.Coordinate;
import datastruct.Lens;
import datastruct.Matrix;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.function.Supplier;
import java.util.Random;
/** /**
* {@link Grid} instances represent the grid in <i>The Game of Life</i>. * {@link CellGrid} instances represent the grid in <i>The Game of Life</i>.
*/ */
public class Grid implements Iterable<Cell> { public class CellGrid<S extends State<S>> implements Iterable<Cell<S>> {
private final int numberOfRows; private final int numberOfRows;
private final int numberOfColumns; private final int numberOfColumns;
private final Cell[][] cells; private final Matrix<Cell<S>> cells;
/** /**
* Creates a new {@code Grid} instance given the number of rows and columns. * Creates a new {@code Grid} instance given the number of rows and columns.
* *
* @param numberOfRows the number of rows
* @param numberOfColumns the number of columns * @param numberOfColumns the number of columns
* @param numberOfRows the number of rows
* @throws IllegalArgumentException if {@code numberOfRows} or {@code numberOfColumns} are * @throws IllegalArgumentException if {@code numberOfRows} or {@code numberOfColumns} are
* less than or equal to 0 * less than or equal to 0
*/ */
public Grid(int numberOfRows, int numberOfColumns) { public CellGrid(int numberOfColumns, int numberOfRows, S defaultState) {
this.numberOfRows = numberOfRows; this.numberOfRows = numberOfRows;
this.numberOfColumns = numberOfColumns; this.numberOfColumns = numberOfColumns;
this.cells = createCells(); this.cells = new Matrix<>(numberOfColumns, numberOfRows, coord -> new Cell<>(defaultState));
}
public boolean contains(Coordinate coordinate) {
return (coordinate.x() >= 0)
&& (coordinate.x() < this.numberOfColumns)
&& (coordinate.y() > 0)
&& (coordinate.y() <= this.numberOfRows);
} }
/** /**
* Returns an iterator over the cells in this {@code Grid}. * Returns an iterator over the cells in this {@code Grid}.
* *
* @return an iterator over the cells in this {@code Grid} * @return an iterator over the cells in this {@code Grid}
*/ */
@Override @Override
public Iterator<Cell> iterator() { public Iterator<Cell<S>> iterator() {
return new GridIterator(this); return this.cells.iterator();
} }
private Cell[][] createCells() { public Iterable<Coordinate> coordinates() {
Cell[][] cells = new Cell[getNumberOfRows()][getNumberOfColumns()]; return this.cells.coordinates();
for (int rowIndex = 0; rowIndex < getNumberOfRows(); rowIndex++) {
for (int columnIndex = 0; columnIndex < getNumberOfColumns(); columnIndex++) {
cells[rowIndex][columnIndex] = new Cell();
} }
public Lens<S> at(Coordinate coord) {
return new CellLens<>(this.cellAt(coord));
} }
return cells;
public Cell<S> cellAt(Coordinate coord) {
return this.cells.at(coord).get();
} }
/** private Coordinate wrap(Coordinate coordinate) {
* Returns the {@link Cell} at the given index. return new Coordinate(
* modulo(coordinate.x(), getNumberOfColumns()),
* <p>Note that the index is wrapped around so that a {@link Cell} is always returned. modulo(coordinate.y(), getNumberOfRows())
* );
* @param rowIndex the row index of the {@link Cell}
* @param columnIndex the column index of the {@link Cell}
* @return the {@link Cell} at the given row and column index
*/
public Cell getCell(int rowIndex, int columnIndex) {
return cells[getWrappedRowIndex(rowIndex)][getWrappedColumnIndex(columnIndex)];
} }
private int getWrappedRowIndex(int rowIndex) { private static int modulo(int n, int d) {
return (rowIndex + getNumberOfRows()) % getNumberOfRows(); int result = n % d;
return n < 0 ? result + d : result;
}
public Lens<S> atWrapped(Coordinate coord) {
return this.at(wrap(coord));
} }
private int getWrappedColumnIndex(int columnIndex) { public Cell<S> cellAtWrapped(Coordinate coord) {
return (columnIndex + getNumberOfColumns()) % getNumberOfColumns(); return this.cellAt(wrap(coord));
} }
/** /**
...@@ -89,32 +101,14 @@ public class Grid implements Iterable<Cell> { ...@@ -89,32 +101,14 @@ public class Grid implements Iterable<Cell> {
} }
// TODO: Écrire une version correcte de cette méthode.
public List<Cell> getNeighbors(int rowIndex, int columnIndex) {
return null;
}
// TODO: Écrire une version correcte de cette méthode.
public int countAliveNeighbors(int rowIndex, int columnIndex) {
return 0;
}
// TODO: Écrire une version correcte de cette méthode.
public CellState calculateNextState(int rowIndex, int columnIndex) {
return null;
}
// TODO: Écrire une version correcte de cette méthode. private Matrix<S> nextGenerationMatrix() {
public CellState[][] calculateNextStates() { return new Matrix<>(
CellState[][] nextCellState = new CellState[getNumberOfRows()][getNumberOfColumns()]; this.numberOfColumns,
return nextCellState; this.numberOfRows,
} new OneStepMatrixInitializer<>(this)
);
// TODO: Écrire une version correcte de cette méthode.
public void updateStates(CellState[][] nextState) {
} }
/** /**
...@@ -132,25 +126,35 @@ public class Grid implements Iterable<Cell> { ...@@ -132,25 +126,35 @@ public class Grid implements Iterable<Cell> {
*/ */
// TODO: Écrire une version correcte de cette méthode. // TODO: Écrire une version correcte de cette méthode.
public void updateToNextGeneration() { public void updateToNextGeneration() {
Matrix<S> nextStates = this.nextGenerationMatrix();
for (Coordinate coordinate : this.coordinates()) {
this.cellAt(coordinate).setState(nextStates.get(coordinate));
}
} }
/** /**
* Sets all {@link Cell}s in this {@code Grid} as dead. * Sets all {@link Cell}s in this {@code Grid} as dead.
*/ */
// TODO: Écrire une version correcte de cette méthode. // TODO: Écrire une version correcte de cette méthode.
public void clear() { public void clear(S clearState) {
for (Cell<S> cell : this) {
cell.setState(clearState);
} }
}
/** /**
* Goes through each {@link Cell} in this {@code Grid} and randomly sets its state as ALIVE or DEAD. * Goes through each {@link Cell} in this {@code Grid} and sets it states with a
* state obtained from the supplier.
* *
* @param random {@link Random} instance used to decide if each {@link Cell} is ALIVE or DEAD. * @param supplier {@link Supplier} instance used to decide a state for each cell {@link Cell}.
* @throws NullPointerException if {@code random} is {@code null}. * @throws NullPointerException if {@code supplier} is {@code null}.
*/ */
// TODO: Écrire une version correcte de cette méthode. // TODO: Écrire une version correcte de cette méthode.
public void randomGeneration(Random random) { public void fillRandomly(Supplier<S> supplier) {
for (Cell<S> cell : this) {
cell.setState(supplier.get());
}
} }
} }
package model;
import datastruct.Lens;
public class CellLens<S extends State<S>> implements Lens<S> {
private final Cell<S> cell;
public CellLens(Cell<S> cell) {
this.cell = cell;
}
@Override
public S get() {
return cell.getState();
}
@Override
public void set(S value) {
cell.setState(value);
}
}
package model;
import javafx.scene.paint.Color;
/**
* {@link CellState} instances represent the possible states of a {@link CellState}.
*/
public enum CellState {
ALIVE(true, Color.RED),
DEAD(false, Color.WHITE);
public final boolean isAlive;
public final Color color;
CellState(boolean isAlive, Color color) {
this.isAlive = isAlive;
this.color = color;
}
}
package model; package model;
import javafx.animation.Animation; import controller.Simulation;
import javafx.animation.KeyFrame; import datastruct.Coordinate;
import javafx.animation.Timeline;
import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper; import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.event.ActionEvent; import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.util.Duration;
import java.util.Random; import java.util.Iterator;
import java.util.function.Supplier;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
/** /**
* {@link GameOfLife} instances run <i>The Game of Life</i>. * {@link CellularAutomataSimulation} instances run <i>The Game of Life</i>.
*/ */
public class GameOfLife { public class CellularAutomataSimulation<S extends State<S>>
implements Simulation {
private final Random random = new Random(); private final CellGrid<S> grid;
private static final int PERIOD_IN_MILLISECONDS = 100; private final Supplier<S> supplier;
private final S defaultState;
private final Grid grid;
private final ReadOnlyLongWrapper generationNumber = new ReadOnlyLongWrapper(); private final ReadOnlyLongWrapper generationNumber = new ReadOnlyLongWrapper();
private Timeline timeline;
/** /**
* Creates a new {@code GameOfLife} instance given the underlying {@link Grid}. * Creates a new {@code GameOfLife} instance given the underlying {@link CellGrid}.
* *
* @param grid the underlying {@link Grid} * @param grid the underlying {@link CellGrid}
* @param defaultState the state value to use when clearing the grid
* @param supplier a {@Link Supplier} to produce values to initialize or reset the grid
* @throws NullPointerException if {@code grid} is {@code null} * @throws NullPointerException if {@code grid} is {@code null}
*/ */
public GameOfLife(Grid grid) { public CellularAutomataSimulation(CellGrid<S> grid, S defaultState, Supplier<S> supplier) {
this.grid = requireNonNull(grid, "grid is null"); this.grid = requireNonNull(grid, "grid is null");
updateTimeline(); this.supplier = requireNonNull(supplier, "supplier is null");
grid.randomGeneration(random); this.defaultState = requireNonNull(defaultState, "defaultState is null");
grid.fillRandomly(this.supplier);
} }
private void updateTimeline() {
Duration duration = new Duration(PERIOD_IN_MILLISECONDS);
EventHandler<ActionEvent> eventHandler = event -> next(); @Override
KeyFrame keyFrame = new KeyFrame(duration, eventHandler); public int numberOfColumns() {
timeline = new Timeline(keyFrame); return this.grid.getNumberOfColumns();
timeline.setCycleCount(Animation.INDEFINITE); }
@Override
public int numberOfRows() {
return this.grid.getNumberOfRows();
} }
/** /**
* Transitions into the next generationNumber. * Transitions into the next generationNumber.
*/ */
private void next() { @Override
public void updateToNextGeneration() {
grid.updateToNextGeneration(); grid.updateToNextGeneration();
generationNumber.set(getGenerationNumber() + 1); generationNumber.set(getGenerationNumber() + 1);
} }
@Override
public void next(Coordinate coordinate) {
this.grid.cellAt(coordinate).toggleState();
}
@Override
public void copy(Coordinate source, Coordinate destination) {
S state = this.grid.at(source).get();
this.grid.at(destination).set(state);
}
@Override
public Color getColor(Coordinate coordinate) {
return this.grid.at(coordinate).get().getColor();
}
@Override
public void setChangeListener(Coordinate coordinate, Runnable runnable) {
this.grid.cellAt(coordinate).getStateProperty().addListener(
(obs,oldV,newV) -> runnable.run()
);
}
/** /**
* Returns the current generationNumber. * Returns the current generationNumber.
...@@ -72,35 +100,12 @@ public class GameOfLife { ...@@ -72,35 +100,12 @@ public class GameOfLife {
return generationNumber.getReadOnlyProperty(); return generationNumber.getReadOnlyProperty();
} }
/**
* Returns the {@link Grid}.
*
* @return the {@link Grid}
*/
public Grid getGrid() {
return grid;
}
/**
* Plays the game.
*/
public void play() {
timeline.play();
}
/**
* Pauses the game.
*/
public void pause() {
timeline.pause();
}
/** /**
* Clears the current game. * Clears the current game.
*/ */
public void clear() { public void clear() {
pause(); grid.clear(defaultState);
grid.clear();
generationNumber.set(0); generationNumber.set(0);
} }
...@@ -109,7 +114,11 @@ public class GameOfLife { ...@@ -109,7 +114,11 @@ public class GameOfLife {
*/ */
public void reset() { public void reset() {
clear(); clear();
grid.randomGeneration(random); grid.fillRandomly(supplier);
} }
@Override
public Iterator<Coordinate> iterator() {
return this.grid.coordinates().iterator();
}
} }
package model;
import javafx.scene.paint.Color;
import java.util.List;
import java.util.Random;
/**
* {@link GameOfLifeState} instances represent the possible states of a {@link GameOfLifeState}.
*/
public enum GameOfLifeState implements State<GameOfLifeState> {
ALIVE(Color.RED),
DEAD(Color.WHITE);
public final Color color;
GameOfLifeState(Color color) {
this.color = color;
}
@Override
public Color getColor() {
return this.color;
}
@Override
public GameOfLifeState next() {
return GameOfLifeState.values()[1 - this.ordinal()];
}
@Override
public GameOfLifeState update(List<State<GameOfLifeState>> neighbours) {
int countAlive = 0;
for (State<GameOfLifeState> state : neighbours) {
if (state.equals(ALIVE)) {
countAlive++;
}
}
boolean isAlive =
(this == DEAD && 3 == countAlive)
|| (this == ALIVE && 2 <= countAlive && countAlive <= 3);
return isAlive ? ALIVE : DEAD;
}
private static final Random randomGen = new Random();
public static GameOfLifeState random() {
return randomGen.nextBoolean() ? ALIVE : DEAD;
}
}
package model;
import java.util.Iterator;
/**
* {@link GridIterator} instances are used to iterate over the cells of a grid.
*/
public class GridIterator implements Iterator<Cell> {
private int rowIndex;
private int columnIndex;
private final Grid grid;
GridIterator(Grid grid) {
this.rowIndex = 0;
this.columnIndex = 0;
this.grid = grid;
}
@Override
public boolean hasNext() {
return columnIndex < grid.getNumberOfColumns() && rowIndex < grid.getNumberOfRows();
}
@Override
public Cell next() {
final Cell result = grid.getCell(rowIndex, columnIndex);
columnIndex = (columnIndex +1) % grid.getNumberOfColumns();
if(columnIndex == 0){
rowIndex++;
}
return result;
}
}
package model;
import datastruct.Coordinate;
import datastruct.MatrixInitializer;
import java.util.ArrayList;
import java.util.List;
public class OneStepMatrixInitializer<S extends State<S>> implements MatrixInitializer<S> {
private final CellGrid<S> grid;
public OneStepMatrixInitializer(CellGrid<S> grid) {
this.grid = grid;
}
@Override
public S initialValueAt(Coordinate coordinate) {
List<State<S>> neighbours = new ArrayList<>();
for (Coordinate neighbourCoord : coordinate.orthodiagonalNeighbours()) {
neighbours.add(this.grid.cellAtWrapped(neighbourCoord).getState());
}
S state = this.grid.cellAt(coordinate).getState();
return state.update(neighbours);
}
}
package model;
import javafx.scene.paint.Color;
import java.util.List;
public interface State<S> {
Color getColor();
S next();
S update(List<State<S>> neighbours);
}
package view; package view;
import datastruct.Coordinate;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import model.Cell;
import model.CellState;
public class FillingMouseListener implements MouseListener { public class FillingMouseListener implements MouseListener {
private final MatrixPane matrix; private final MatrixPane matrix;
private final CellState cellState; private final Coordinate source;
public FillingMouseListener(MatrixPane matrix, CellState cellState) {
public FillingMouseListener(MatrixPane matrix, Coordinate source) {
this.matrix = matrix; this.matrix = matrix;
this.cellState = cellState; this.source = source;
} }
@Override @Override
public void onMouseReleased(MouseEvent event, Cell cell) { public void onMouseReleased(MouseEvent event, Coordinate coord) {
System.out.println("Filling Release");
this.matrix.resetWaitingListener(); this.matrix.resetWaitingListener();
} }
@Override @Override
public void onMouseEntered(MouseEvent event, Cell cell) { public void onMouseEntered(MouseEvent event, Coordinate destination) {
System.out.println("Filling Enter");
if (!event.isPrimaryButtonDown()) { if (!event.isPrimaryButtonDown()) {
this.matrix.resetWaitingListener(); this.matrix.resetWaitingListener();
return; return;
} }
while (!cellState.equals(cell.getState())) { this.matrix.getController().getSimulation().copy(source, destination);
cell.toggleState();
}
} }
@Override @Override
public void onMousePressed(MouseEvent event, Cell cell) { public void onMousePressed(MouseEvent event, Coordinate coordinate) {
System.out.println("Filling Pressed"); this.matrix.getController().getSimulation().next(coordinate);
cell.toggleState(); this.matrix.setMouseListener(
CellState state = cell.getState(); new FillingMouseListener(this.matrix, coordinate)
this.matrix.setMouseListener(new FillingMouseListener(this.matrix, state)); );
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment