Commit 0fbb81e4 authored by Colin DAMON's avatar Colin DAMON

Game of life part 2

parent e8e8cf09
......@@ -2,24 +2,16 @@ package fr.ippon.life;
import java.util.Set;
public class Cell {
public record Cell(int row, int column) {
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;
return neighbourgCount == LOW_POPULATION_THRESHOLD
|| neighbourgCount == HIGH_POPULATION_THRESHOLD;
}
boolean born(Set<Cell> aliveCells) {
......@@ -27,7 +19,9 @@ public class Cell {
}
private long countNeigbours(Set<Cell> aliveCells) {
return aliveCells.stream().filter(this::isNeighbour).count();
return aliveCells.stream()
.filter(this::isNeighbour)
.count();
}
private boolean isNeighbour(Cell other) {
......@@ -35,38 +29,11 @@ public class Cell {
return false;
}
return delta(row, other.row) <= 1 && delta(column, other.column) <= 1;
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;
}
}
......@@ -9,55 +9,77 @@ import java.util.stream.Stream;
public class Grid {
private final Set<Cell> deadCells;
private final Box box;
private final Box border;
Grid(Set<Cell> cells) {
box = new Box(cells);
this.deadCells = buildDeadCells(cells);
Grid(Set<Cell> aliveCells) {
this.deadCells = buildDeadCells(aliveCells);
border = new Box(aliveCells);
}
private Set<Cell> buildDeadCells(Set<Cell> cells) {
return IntStream
.range(box.getTopRow(), box.getBottomRow())
.mapToObj(Integer::valueOf)
.flatMap(row -> toDeadCell(cells, row))
private Set<Cell> buildDeadCells(Set<Cell> aliveCells) {
return aliveCells
.stream()
.map(Box::new)
.flatMap(Box::cells)
.filter(cell -> !aliveCells.contains(cell))
.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;
}
public int leftColumn() {
return border.leftColumn();
}
public int rightColumn() {
return border.rightColumn();
}
public int topRow() {
return border.topRow();
}
public int bottomRow() {
return border.bottomRow();
}
private static class Box {
private final Point topLeft;
private final Point bottomRight;
public Box(Cell cell) {
this(Set.of(cell));
}
public Box(Set<Cell> cells) {
topLeft = Point.topLeft(cells);
bottomRight = Point.bottomRight(cells);
}
public int getTopRow() {
private Stream<Cell> cells() {
return IntStream.range(leftColumn(), rightColumn()).mapToObj(Integer::valueOf).flatMap(column -> cellsColumn(column));
}
private Stream<Cell> cellsColumn(Integer column) {
return IntStream.range(topRow(), bottomRow()).mapToObj(Integer::valueOf).map(row -> new Cell(row, column));
}
public int topRow() {
return topLeft.getRow();
}
public int getBottomRow() {
public int bottomRow() {
return bottomRight.getRow() + 1;
}
public int getLeftColumn() {
public int leftColumn() {
return topLeft.getColumn();
}
public int getRightColumn() {
public int rightColumn() {
return bottomRight.getColumn() + 1;
}
}
......@@ -73,17 +95,17 @@ public class Grid {
}
private static Point topLeft(Set<Cell> cells) {
int row = cells.stream().mapToInt(Cell::getRow).min().orElse(0);
int row = cells.stream().mapToInt(Cell::row).min().orElse(0);
int column = cells.stream().mapToInt(Cell::getColumn).min().orElse(0);
int column = cells.stream().mapToInt(Cell::column).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 row = cells.stream().mapToInt(Cell::row).max().orElse(0);
int column = cells.stream().mapToInt(Cell::getColumn).max().orElse(0);
int column = cells.stream().mapToInt(Cell::column).max().orElse(0);
return new Point(row + 1, column + 1);
}
......
package fr.ippon.life;
public interface Renderer<T> {
T run(Grid grid);
}
package fr.ippon.life;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StringRenderer implements Renderer<String> {
@Override
public String run(Grid grid) {
return new GridRenderer(grid).run();
}
private static class GridRenderer {
private final Grid grid;
private GridRenderer(Grid grid) {
this.grid = grid;
}
private String run() {
return IntStream
.range(grid.topRow(), grid.bottomRow())
.mapToObj(Integer::valueOf)
.map(row -> rowRepresentation(grid, row))
.collect(Collectors.joining(System.lineSeparator(), "", System.lineSeparator()));
}
private String rowRepresentation(Grid grid, Integer row) {
return IntStream.range(grid.leftColumn(), grid.rightColumn()).mapToObj(cellRepresentation(row)).collect(Collectors.joining());
}
private IntFunction<String> cellRepresentation(Integer row) {
return column -> {
if (aliveCell(row, column)) {
return "o";
}
return "x";
};
}
private boolean aliveCell(Integer row, int column) {
return !grid.deadCells().contains(new Cell(row, column));
}
}
}
......@@ -39,20 +39,20 @@ class GameOfLifeTest {
@Test
void nextGenerationShouldHaveOneColumnFromOneLine() {
Generation nextGeneration = new Generation(cell(4, 5), cell(5, 5), cell(6, 5)).next();
Generation nextGeneration = threeCellsColumn().next();
assertThat(nextGeneration.getAliveCells()).containsExactlyInAnyOrder(cell(5, 4), cell(5, 5), cell(5, 6));
}
@Test
void shouldAdvanceInGenerations() {
Generation nextGeneration = threeCellsColumn();
Generation nextGeneration = threeCellsColumn().next().next();
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();
return new Generation(cell(4, 5), cell(5, 5), cell(6, 5));
}
private static Cell cell(int row, int column) {
......
package fr.ippon.life;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
class StringRendererUnitTest {
private static final StringRenderer renderer = new StringRenderer();
@Test
void shouldDisplayGridWithOneAliveCell() {
assertThat(renderer.run(grid().alive(1, 1).build())).isEqualTo(join("xxx", "xox", "xxx"));
}
@Test
void shouldDisplayGridWithTwoAliveCell() {
assertThat(renderer.run(grid().alive(1, 1).alive(1, 3).build())).isEqualTo(join("xxxxx", "xoxox", "xxxxx"));
}
private static String join(String... parts) {
return Arrays.stream(parts).collect(Collectors.joining(System.lineSeparator(), "", System.lineSeparator()));
}
private GridBuilder grid() {
return new GridBuilder();
}
private static class GridBuilder {
private final Set<Cell> cells = new HashSet<>();
public GridBuilder alive(int row, int column) {
cells.add(new Cell(row, column));
return this;
}
public Grid build() {
return new Grid(cells);
}
}
}
......@@ -1276,8 +1276,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isobject": {
"version": "3.0.1",
......@@ -2093,6 +2092,66 @@
"java-parser": "1.0.2",
"lodash": "4.17.21",
"prettier": "2.2.1"
},
"dependencies": {
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"requires": {
"path-key": "^3.0.0"
}
},
"onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"requires": {
"mimic-fn": "^2.1.0"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
},
"prettier": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
"dev": true
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"pretty-quick": {
......
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