Commit 04b65a9f authored by Colin DAMON's avatar Colin DAMON

Game of life implementation

parent 8d58b798
package fr.ippon.life;
import java.util.Set;
public class Cell {
private static final int LOW_POPULATION_THRESHOLD = 2;
private static final int HIGH_POPULATION_THRESHOLD = 3;
private static final int BORN_THRESHOLD = HIGH_POPULATION_THRESHOLD;
private final int row;
private final int column;
public Cell(int row, int column) {
this.row = row;
this.column = column;
}
boolean stayAlive(Set<Cell> aliveCells) {
long neighbourgCount = countNeigbours(aliveCells);
return neighbourgCount == LOW_POPULATION_THRESHOLD || neighbourgCount == HIGH_POPULATION_THRESHOLD;
}
boolean born(Set<Cell> aliveCells) {
return countNeigbours(aliveCells) == BORN_THRESHOLD;
}
private long countNeigbours(Set<Cell> aliveCells) {
return aliveCells.stream().filter(this::isNeighbour).count();
}
private boolean isNeighbour(Cell other) {
if (other.equals(this)) {
return false;
}
return delta(row, other.row) <= 1 && delta(column, other.column) <= 1;
}
private static int delta(int first, int second) {
return Math.abs(first - second);
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + column;
result = prime * result + row;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Cell other = (Cell) obj;
if (column != other.column) return false;
if (row != other.row) return false;
return true;
}
}
package fr.ippon.life;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Generation {
private Set<Cell> aliveCells;
public Generation(Cell... aliveCells) {
this(Set.of(aliveCells));
}
private Generation(Set<Cell> cells) {
aliveCells = cells;
}
public Generation next() {
Stream<Cell> stayAlive = aliveCells.stream().filter(cell -> cell.stayAlive(aliveCells));
Stream<Cell> born = buildGrid().deadCells().stream().filter(cell -> cell.born(aliveCells));
Set<Cell> cells = Stream.concat(stayAlive, born).collect(Collectors.toUnmodifiableSet());
return new Generation(cells);
}
public Collection<Cell> getAliveCells() {
return aliveCells;
}
private Grid buildGrid() {
return new Grid(aliveCells);
}
}
package fr.ippon.life;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Grid {
private final Set<Cell> deadCells;
private final Box box;
Grid(Set<Cell> cells) {
box = new Box(cells);
this.deadCells = buildDeadCells(cells);
}
private Set<Cell> buildDeadCells(Set<Cell> cells) {
return IntStream
.range(box.getTopRow(), box.getBottomRow())
.mapToObj(Integer::valueOf)
.flatMap(row -> toDeadCell(cells, row))
.collect(Collectors.toUnmodifiableSet());
}
private Stream<Cell> toDeadCell(Set<Cell> cells, int row) {
return IntStream
.range(box.getLeftColumn(), box.getRightColumn())
.mapToObj(column -> new Cell(row, column))
.filter(cell -> !cells.contains(cell));
}
public Collection<Cell> deadCells() {
return deadCells;
}
private static class Box {
private final Point topLeft;
private final Point bottomRight;
public Box(Set<Cell> cells) {
topLeft = Point.topLeft(cells);
bottomRight = Point.bottomRight(cells);
}
public int getTopRow() {
return topLeft.getRow();
}
public int getBottomRow() {
return bottomRight.getRow() + 1;
}
public int getLeftColumn() {
return topLeft.getColumn();
}
public int getRightColumn() {
return bottomRight.getColumn() + 1;
}
}
private static class Point {
private final int row;
private final int column;
public Point(int row, int column) {
this.row = row;
this.column = column;
}
private static Point topLeft(Set<Cell> cells) {
int row = cells.stream().mapToInt(Cell::getRow).min().orElse(0);
int column = cells.stream().mapToInt(Cell::getColumn).min().orElse(0);
return new Point(row - 1, column - 1);
}
private static Point bottomRight(Set<Cell> cells) {
int row = cells.stream().mapToInt(Cell::getRow).max().orElse(0);
int column = cells.stream().mapToInt(Cell::getColumn).max().orElse(0);
return new Point(row + 1, column + 1);
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
}
}
package fr.ippon.life;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
class GameOfLifeTest {
@Test
void nextGenerationShouldHaveAllDeadCellsForAllDeadGrid() {
Generation nextGeneration = new Generation().next();
assertThat(nextGeneration.getAliveCells()).isEmpty();
}
@Test
void nextGenerationShouldHaveAllDeadCellsFromOneAliveCell() {
Generation nextGeneration = new Generation(cell(1, 1)).next();
assertThat(nextGeneration.getAliveCells()).isEmpty();
}
@Test
void nextGenerationShouldHaveAllDeadCellsFromTwoContiguousAliveCells() {
Generation nextGeneration = new Generation(cell(1, 1), cell(2, 1)).next();
assertThat(nextGeneration.getAliveCells()).isEmpty();
}
@Test
void nextGenerationShouldHaveFourAliveCellsFromTriangularConfiguration() {
Cell first = cell(1, 2);
Cell second = cell(2, 1);
Cell third = cell(2, 2);
Generation nextGeneration = new Generation(first, second, third).next();
assertThat(nextGeneration.getAliveCells()).containsExactlyInAnyOrder(cell(1, 1), first, second, third);
}
@Test
void nextGenerationShouldHaveOneColumnFromOneLine() {
Generation nextGeneration = new Generation(cell(4, 5), cell(5, 5), cell(6, 5)).next();
assertThat(nextGeneration.getAliveCells()).containsExactlyInAnyOrder(cell(5, 4), cell(5, 5), cell(5, 6));
}
@Test
void shouldAdvanceInGenerations() {
Generation nextGeneration = threeCellsColumn();
assertThat(nextGeneration.getAliveCells()).containsExactlyInAnyOrder(cell(4, 5), cell(5, 5), cell(6, 5));
}
private Generation threeCellsColumn() {
return new Generation(cell(4, 5), cell(5, 5), cell(6, 5)).next().next();
}
private static Cell cell(int row, int column) {
return new Cell(row, column);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment