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

More and more refactoring

parent a152b729
No related branches found
No related tags found
No related merge requests found
Showing
with 289 additions and 304 deletions
import controller.Controller;
import controller.Simulation;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
......@@ -6,8 +7,9 @@ import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.CellularAutomatonSimulation;
import model.states.BriansBrainState;
import model.states.SeedsState;
import model.automata.BriansBrainAutomaton;
import model.automata.GameOfLifeAutomaton;
import model.automata.SeedsAutomaton;
import java.io.IOException;
import java.net.URL;
......@@ -26,10 +28,10 @@ public class SimulatorApplication extends Application {
public static final Random GENERATOR = new Random();
private static final String APP_NAME = "Game of Life";
private static final String APP_NAME = "2D Cellular automata";
private static final String VIEW_RESOURCE_PATH = "/view/view.fxml";
private final CellularAutomatonSimulation<SeedsState> gameOfLife;
private final Simulation simulation;
private Stage primaryStage;
private Parent view;
......@@ -37,12 +39,10 @@ public class SimulatorApplication extends Application {
* Creates a new {@code GameOfLifeApplication} instance.
*/
public SimulatorApplication() {
this.gameOfLife =
this.simulation =
new CellularAutomatonSimulation<>(
NUMBER_OF_COLUMNS,
NUMBER_OF_ROWS,
SeedsState.OFF,
SeedsState::random
new SeedsAutomaton(NUMBER_OF_COLUMNS,NUMBER_OF_ROWS),
GENERATOR
);
}
......@@ -68,7 +68,7 @@ public class SimulatorApplication extends Application {
loader.setLocation(location);
view = loader.load();
Controller controller = loader.getController();
controller.setSimulation(gameOfLife);
controller.setSimulation(simulation);
}
......
......@@ -3,6 +3,7 @@ package controller;
import datastruct.Coordinate;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.scene.paint.Color;
import model.OnChangeListener;
public interface Simulation extends Iterable<Coordinate> {
......@@ -17,7 +18,7 @@ public interface Simulation extends Iterable<Coordinate> {
Color getColor(Coordinate coordinate);
void setChangeListener(Coordinate coordinate, Runnable run);
void setChangeListener(Coordinate coordinate, Runnable listener);
ReadOnlyLongProperty generationNumberProperty();
......
......@@ -4,9 +4,11 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Matrix<T> {
public class Matrix<T> implements Iterable<T> {
private final List<List<T>> matrix;
private final int width;
private final int height;
......@@ -31,6 +33,13 @@ public class Matrix<T> {
}
}
public int width() {
return width;
}
public int height() {
return height;
}
public T get(int x, int y) {
return this.matrix.get(x).get(y);
}
......
package model;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.paint.Color;
import datastruct.Lens;
/**
* {@link Cell} instances represent the cells of <i>The Game of Life</i>.
*/
public class Cell<S extends State<S>> {
private final Property<S> stateProperty;
public Cell(S initialState) {
this.stateProperty = new SimpleObjectProperty<>(initialState);
}
import java.util.ArrayList;
import java.util.List;
/**
* Determines the color associated with the state in which
* this {@link Cell} is.
*
* @return the {@link Color} associated with the state in
* which this {@link Cell} is
* {@link Cell} instances represent the cells of the grid in a simulation of cellular automata.
*/
public Color getColor() {
return this.getState().getColor();
}
public class Cell<T> implements Lens<T> {
private T content;
private final List<OnChangeListener<T>> listeners = new ArrayList<>();
/**
* Sets the state of this {@link Cell}.
/** Initialize a new cell with a given value.
*
* @param state the new state of this {@link Cell}
* @param initialContent the value initially stored by the cell.
*/
public Cell(T initialContent) {
this.content = initialContent;
}
public void setState(S state) {
getStateProperty().setValue(state);
public void addOnChangeListener(OnChangeListener<T> listener) {
this.listeners.add(listener);
}
/**
* Returns the current state of this {@link Cell}.
* Sets the content of this {@link Cell}.
*
* @return the current state of this {@link Cell}
* @param value the new content of this {@link Cell}
*/
public S getState(){
return getStateProperty().getValue();
public void set(T value) {
this.content = value;
for (OnChangeListener<T> listener : this.listeners) {
listener.valueChanged(this.content, value);
}
/**
* Change the state of this {@link Cell} to the next possible state.
*/
public void toggleState() {
setState(getState().next());
}
/**
* Returns this {@link Cell}'s state property.
* Returns the current content of this {@link Cell}.
*
* @return this {@link Cell}'s state property.
* @return the current content of this {@link Cell}
*/
public Property<S> getStateProperty() {
return stateProperty;
public T get(){
return this.content;
}
}
package model;
import datastruct.Coordinate;
import datastruct.Lens;
import datastruct.Matrix;
import java.util.Iterator;
import java.util.function.Supplier;
/**
* {@link CellGrid} instances represent the grid in <i>The Game of Life</i>.
*/
public class CellGrid<S extends State<S>> implements Iterable<Cell<S>> {
private final int numberOfRows;
private final int numberOfColumns;
private final Matrix<Cell<S>> cells;
/**
* Creates a new {@code Grid} instance given the number of rows and columns.
*
* @param numberOfColumns the number of columns
* @param numberOfRows the number of rows
* @throws IllegalArgumentException if {@code numberOfRows} or {@code numberOfColumns} are
* less than or equal to 0
*/
public CellGrid(int numberOfColumns, int numberOfRows, S defaultState) {
this.numberOfRows = numberOfRows;
this.numberOfColumns = numberOfColumns;
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}.
*
* @return an iterator over the cells in this {@code Grid}
*/
@Override
public Iterator<Cell<S>> iterator() {
return this.cells.iterator();
}
public Iterable<Coordinate> coordinates() {
return this.cells.coordinates();
}
public Lens<S> at(Coordinate coord) {
return new CellLens<>(this.cellAt(coord));
}
public Cell<S> cellAt(Coordinate coord) {
return this.cells.at(coord).get();
}
private Coordinate wrap(Coordinate coordinate) {
return new Coordinate(
modulo(coordinate.x(), getNumberOfColumns()),
modulo(coordinate.y(), getNumberOfRows())
);
}
private static int modulo(int n, int d) {
int result = n % d;
return n < 0 ? result + d : result;
}
public Lens<S> atWrapped(Coordinate coord) {
return this.at(wrap(coord));
}
public Cell<S> cellAtWrapped(Coordinate coord) {
return this.cellAt(wrap(coord));
}
/**
* Returns the number of rows in this {@code Grid}.
*
* @return the number of rows in this {@code Grid}
*/
public int getNumberOfRows() {
return numberOfRows;
}
/**
* Returns the number of columns in this {@code Grid}.
*
* @return the number of columns in this {@code Grid}
*/
public int getNumberOfColumns() {
return numberOfColumns;
}
private Matrix<S> nextGenerationMatrix() {
return new Matrix<>(
this.numberOfColumns,
this.numberOfRows,
new NextGenerationInitializer<>(this)
);
}
/**
* Transitions all {@link Cell}s in this {@code Grid} to the next generation.
*
* <p>The following rules are applied:
* <ul>
* <li>Any live {@link Cell} with fewer than two live neighbours dies, i.e. underpopulation.</li>
* <li>Any live {@link Cell} with two or three live neighbours lives on to the next
* generation.</li>
* <li>Any live {@link Cell} with more than three live neighbours dies, i.e. overpopulation.</li>
* <li>Any dead {@link Cell} with exactly three live neighbours becomes a live cell, i.e.
* reproduction.</li>
* </ul>
*/
// TODO: Écrire une version correcte de cette méthode.
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.
*/
// TODO: Écrire une version correcte de cette méthode.
public void clear(S clearState) {
for (Cell<S> cell : this) {
cell.setState(clearState);
}
}
/**
* Goes through each {@link Cell} in this {@code Grid} and sets it states with a
* state obtained from the supplier.
*
* @param supplier {@link Supplier} instance used to decide a state for each cell {@link Cell}.
* @throws NullPointerException if {@code supplier} is {@code null}.
*/
// TODO: Écrire une version correcte de cette méthode.
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 java.util.Random;
public interface CellularAutomaton<S extends State<S>> {
int numberOfColumns();
int numberOfRows();
S defaultState();
S randomState(Random generator);
}
......@@ -2,14 +2,15 @@ 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;
import static java.util.Objects.requireNonNull;
/**
* {@link CellularAutomatonSimulation} instances run <i>The Game of Life</i>.
......@@ -17,69 +18,94 @@ import static java.util.Objects.requireNonNull;
public class CellularAutomatonSimulation<S extends State<S>>
implements Simulation {
private final CellGrid<S> grid;
private final Supplier<S> randomState;
private final S defaultState;
private final Matrix<Cell<S>> grid;
private final ReadOnlyLongWrapper generationNumber = new ReadOnlyLongWrapper();
private final CellularAutomaton<S> automaton;
private final Random generator;
/**
* Creates a new {@code CellularAutomataSimulation} instance for a given automaton.
* Creates a new {@link CellularAutomatonSimulation} instance for a given automaton.
*
* @param width an {@code int} representing the number of columns
* @param height an {@code int} representing the number of rows
* @param defaultState a state {@code S} used to fill the grid when using
* the clear action
* @param randomState a generator of states {@code} used to fill the grid
* when using the reset action
* @param automaton a description of the {@link CellularAutomaton}
*/
public CellularAutomatonSimulation(int width, int height, S defaultState, Supplier<S> randomState) {
this.grid = new CellGrid<>(width, height, defaultState);
this.defaultState = defaultState;
this.randomState = randomState;
grid.fillRandomly(randomState);
public CellularAutomatonSimulation(CellularAutomaton<S> automaton, Random generator) {
this.automaton = automaton;
this.grid = new Matrix<>(
automaton.numberOfColumns(),
automaton.numberOfRows(),
new ConstantCellInitializer<>(automaton.defaultState())
);
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() {
return this.grid.getNumberOfColumns();
return this.grid.width();
}
@Override
public int numberOfRows() {
return this.grid.getNumberOfRows();
return this.grid.height();
}
public Cell<S> at(Coordinate coordinate) {
return this.grid.get(coordinate);
}
/**
* Transitions into the next generationNumber.
*/
@Override
public void updateToNextGeneration() {
this.grid.updateToNextGeneration();
this.generationNumber.set(getGenerationNumber() + 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
* of the simulation.
*
* @return the states of each cell after one generation
*/
private Matrix<S> nextGenerationMatrix() {
return new Matrix<S>(
this.grid.width(),
this.grid.height(),
new NextGenerationInitializer<>(this)
);
}
@Override
public void next(Coordinate coordinate) {
this.grid.cellAt(coordinate).toggleState();
S oldState = this.grid.get(coordinate).get();
this.at(coordinate).set(oldState.next());
}
@Override
public void copy(Coordinate source, Coordinate destination) {
S state = this.grid.at(source).get();
this.grid.at(destination).set(state);
System.out.println("bip (" + source + ") (" + destination + ")");
S state = this.at(source).get();
this.at(destination).set(state);
}
@Override
public Color getColor(Coordinate coordinate) {
return this.grid.at(coordinate).get().getColor();
return this.at(coordinate).get().getColor();
}
@Override
public void setChangeListener(Coordinate coordinate, Runnable runnable) {
this.grid.cellAt(coordinate).getStateProperty().addListener(
(obs,oldV,newV) -> runnable.run()
public void setChangeListener(Coordinate coordinate, Runnable listener) {
this.at(coordinate).addOnChangeListener(
(oldValue, newValue) -> listener.run()
);
}
......@@ -107,7 +133,9 @@ public class CellularAutomatonSimulation<S extends State<S>>
* Clears the current game.
*/
public void clear() {
this.grid.clear(this.defaultState);
for (Cell<S> cell : this.grid) {
cell.set(this.automaton.defaultState());
}
this.generationNumber.set(0);
}
......@@ -116,7 +144,7 @@ public class CellularAutomatonSimulation<S extends State<S>>
*/
public void reset() {
this.clear();
this.grid.fillRandomly(this.randomState);
this.fillRandomly(this.generator);
}
@Override
......
package model;
import datastruct.Coordinate;
import datastruct.MatrixInitializer;
public class ConstantCellInitializer<T> implements MatrixInitializer<Cell<T>> {
private final T defaultValue;
public ConstantCellInitializer(T defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public Cell<T> initialValueAt(Coordinate coordinate) {
return new Cell<>(defaultValue);
}
}
package model;
import datastruct.Coordinate;
import datastruct.Matrix;
import datastruct.MatrixInitializer;
import java.util.ArrayList;
......@@ -8,19 +9,38 @@ import java.util.List;
public class NextGenerationInitializer<S extends State<S>> implements MatrixInitializer<S> {
private final CellGrid<S> grid;
private final CellularAutomatonSimulation<S> simulation;
public NextGenerationInitializer(CellGrid<S> grid) {
this.grid = grid;
public NextGenerationInitializer(CellularAutomatonSimulation<S> simulation) {
this.simulation = simulation;
}
@Override
public S initialValueAt(Coordinate coordinate) {
List<State<S>> neighbours = new ArrayList<>();
for (Coordinate neighbourCoord : coordinate.orthodiagonalNeighbours()) {
neighbours.add(this.grid.cellAtWrapped(neighbourCoord).getState());
Coordinate wrapped = wrap(neighbourCoord);
neighbours.add(this.simulation.at(wrapped).get());
}
S state = this.grid.cellAt(coordinate).getState();
S state = this.simulation.at(coordinate).get();
return state.update(neighbours);
}
private Coordinate wrap(Coordinate coordinate) {
return new Coordinate(
modulo(coordinate.x(),this.simulation.numberOfColumns()),
modulo(coordinate.y(),this.simulation.numberOfRows())
);
}
/** The non-negative remainder of n divided by d.
*
* @param n an arbitrary integer.
* @param d a non-zero integer.
* @return the remainder of {@code n/d}, between {@code 0} and {@code n-1}.
*/
private static int modulo(int n, int d) {
int result = n % d;
return n < 0 ? result + d : result;
}
}
package model;
public interface OnChangeListener<T> {
void valueChanged(T oldValue, T newValue);
}
package model.automata;
import model.CellularAutomaton;
import model.State;
public abstract class AbstractAutomaton<S extends State<S>> implements CellularAutomaton<S> {
private final int numberOfColumns;
private final int numberOfRows;
protected AbstractAutomaton(int numberOfColumns, int numberOfRows) {
this.numberOfColumns = numberOfColumns;
this.numberOfRows = numberOfRows;
}
public int numberOfColumns() {
return numberOfColumns;
}
public int numberOfRows() {
return numberOfRows;
}
}
package model.automata;
import model.CellularAutomaton;
import java.util.Random;
public class BiColorAutomaton extends AbstractAutomaton<BiColorState> {
public BiColorAutomaton(int numberOfColumns, int numberOfRows) {
super(numberOfColumns, numberOfRows);
}
@Override
public BiColorState defaultState() {
return BiColorState.DEAD;
}
@Override
public BiColorState randomState(Random generator) {
return generator.nextBoolean()? BiColorState.DEAD:
generator.nextBoolean()? BiColorState.RED:
BiColorState.BLUE;
}
}
package model.states;
package model.automata;
import javafx.scene.paint.Color;
import model.State;
......@@ -50,12 +50,4 @@ public enum BiColorState implements State<BiColorState> {
DEAD;
}
private final static Random generator = new Random();
public static BiColorState random() {
return generator.nextBoolean()? DEAD:
generator.nextBoolean()? RED:
BLUE;
}
}
package model.automata;
import java.util.Random;
public class BriansBrainAutomaton extends AbstractAutomaton<BriansBrainState> {
public BriansBrainAutomaton(int numberOfColumns, int numberOfRows) {
super(numberOfColumns, numberOfRows);
}
@Override
public BriansBrainState defaultState() {
return BriansBrainState.OFF;
}
@Override
public BriansBrainState randomState(Random generator) {
return generator.nextInt(10) == 0 ?
BriansBrainState.ON:
BriansBrainState.OFF;
}
}
package model.states;
package model.automata;
import javafx.scene.paint.Color;
import model.State;
......@@ -48,10 +48,4 @@ public enum BriansBrainState implements State<BriansBrainState> {
}
return count;
}
final static Random GENERATOR = new Random();
public static BriansBrainState random() {
return GENERATOR.nextInt(10) == 0 ? ON: OFF;
}
}
package model.automata;
import model.CellularAutomaton;
import java.util.Random;
public class GameOfLifeAutomaton extends AbstractAutomaton<GameOfLifeState> {
protected GameOfLifeAutomaton(int numberOfColumns, int numberOfRows) {
super(numberOfColumns, numberOfRows);
}
@Override
public GameOfLifeState defaultState() {
return GameOfLifeState.DEAD;
}
@Override
public GameOfLifeState randomState(Random generator) {
return generator.nextBoolean()?
GameOfLifeState.ALIVE:
GameOfLifeState.DEAD;
}
}
package model.states;
package model.automata;
import javafx.scene.paint.Color;
import model.State;
......@@ -43,8 +43,4 @@ public enum GameOfLifeState implements State<GameOfLifeState> {
return isAlive ? ALIVE : DEAD;
}
private static final Random randomGen = new Random();
public static GameOfLifeState random() {
return randomGen.nextBoolean() ? ALIVE : DEAD;
}
}
package model.automata;
import java.util.Random;
public class SeedsAutomaton extends AbstractAutomaton<SeedsState> {
public SeedsAutomaton(int numberOfColumns, int numberOfRows) {
super(numberOfColumns, numberOfRows);
}
@Override
public SeedsState defaultState() {
return SeedsState.OFF;
}
@Override
public SeedsState randomState(Random generator) {
return generator.nextInt() == 0?
SeedsState.ON:
SeedsState.OFF;
}
}
package model.states;
package model.automata;
import javafx.scene.paint.Color;
import model.State;
import java.util.List;
import static model.states.BriansBrainState.GENERATOR;
import static model.states.BriansBrainState.countList;
import static model.automata.BriansBrainState.countList;
public enum SeedsState implements State<SeedsState> {
ON, OFF;
......@@ -34,7 +33,4 @@ public enum SeedsState implements State<SeedsState> {
};
}
public static SeedsState random() {
return GENERATOR.nextInt(10)==0? ON: OFF;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment