From 04b65a9f1ab7d08a5ff8ad1c345b0cfc8d6bc487 Mon Sep 17 00:00:00 2001 From: Colin DAMON <cdamon@ippon.fr> Date: Thu, 21 Jan 2021 08:44:14 +0100 Subject: [PATCH] Game of life implementation --- .../src/main/java/fr/ippon/life/Cell.java | 71 ++++++++++++++ .../main/java/fr/ippon/life/Generation.java | 36 +++++++ .../src/main/java/fr/ippon/life/Grid.java | 96 +++++++++++++++++++ .../java/fr/ippon/life/GameOfLifeTest.java | 61 ++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 game-of-life/src/main/java/fr/ippon/life/Cell.java create mode 100644 game-of-life/src/main/java/fr/ippon/life/Generation.java create mode 100644 game-of-life/src/main/java/fr/ippon/life/Grid.java create mode 100644 game-of-life/src/test/java/fr/ippon/life/GameOfLifeTest.java diff --git a/game-of-life/src/main/java/fr/ippon/life/Cell.java b/game-of-life/src/main/java/fr/ippon/life/Cell.java new file mode 100644 index 00000000..9fdfad94 --- /dev/null +++ b/game-of-life/src/main/java/fr/ippon/life/Cell.java @@ -0,0 +1,71 @@ +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; + } +} diff --git a/game-of-life/src/main/java/fr/ippon/life/Generation.java b/game-of-life/src/main/java/fr/ippon/life/Generation.java new file mode 100644 index 00000000..d314bfe5 --- /dev/null +++ b/game-of-life/src/main/java/fr/ippon/life/Generation.java @@ -0,0 +1,36 @@ +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); + } +} diff --git a/game-of-life/src/main/java/fr/ippon/life/Grid.java b/game-of-life/src/main/java/fr/ippon/life/Grid.java new file mode 100644 index 00000000..fc866905 --- /dev/null +++ b/game-of-life/src/main/java/fr/ippon/life/Grid.java @@ -0,0 +1,96 @@ +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; + } + } +} diff --git a/game-of-life/src/test/java/fr/ippon/life/GameOfLifeTest.java b/game-of-life/src/test/java/fr/ippon/life/GameOfLifeTest.java new file mode 100644 index 00000000..799cb41e --- /dev/null +++ b/game-of-life/src/test/java/fr/ippon/life/GameOfLifeTest.java @@ -0,0 +1,61 @@ +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); + } +} -- GitLab