Skip to content

Commit dcfe0aa

Browse files
committed
add tests coverage for gameRules and fix gameRules logic
1 parent 9f7f7ab commit dcfe0aa

File tree

5 files changed

+129
-21
lines changed

5 files changed

+129
-21
lines changed

app/src/main/java/core/JFXApp.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
public class JFXApp extends Application {
1414

1515
public static void main(String[] args) {
16-
EntityGrid.getEntityGrid().addEntityToGrid(30, 30, SpaceShip.GLIDER);
16+
EntityGrid.getEntityGrid().addEntityToGrid(32, 32, SpaceShip.GLIDER);
1717

1818
Thread frontendApp = new Thread(()-> {
1919
JFXApp.launch(args);

app/src/main/java/core/entities/EntityGrid.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package core.entities;
22

3+
import java.util.concurrent.atomic.AtomicInteger;
4+
35
/**
46
* This class will represent the abstract 2d plane on which the game of life takes place. For the first implementation
57
* this will be limited to a static grid size outside of which the entities will no longer be simulated. Increasing the
@@ -68,9 +70,11 @@ public void startSimulation() {
6870
Thread gameSimulationRunner = new Thread(() -> {
6971
try {
7072
Thread.sleep(1000);
73+
AtomicInteger iteratation = new AtomicInteger();
74+
iteratation.set(0);
7175
while (gameRunning) {
7276
Thread.sleep(gameSpeed);
73-
EntityGrid.getEntityGrid().executeSimulationTick();
77+
EntityGrid.getEntityGrid().executeSimulationTick(iteratation);
7478
}
7579
} catch (InterruptedException e) {
7680
e.printStackTrace();
@@ -94,8 +98,8 @@ public void setSimulationSpeed(int speed) {
9498
EntityGrid.getEntityGrid().gameSpeed = speed;
9599
}
96100

97-
private void executeSimulationTick() {
98-
this.entityGrid = ruleSet.applyRulesToGrid(this.entityGrid);
101+
private void executeSimulationTick(AtomicInteger iteration) {
102+
this.entityGrid = ruleSet.applyRulesToGrid(this.entityGrid, iteration);
99103
}
100104

101105

app/src/main/java/core/entities/EntityUtility.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.FileWriter;
66
import java.io.IOException;
77
import java.util.ArrayList;
8+
import java.util.Arrays;
89
import java.util.Scanner;
910

1011
public class EntityUtility {
@@ -103,4 +104,14 @@ public static boolean[][] loadEntityFromString(String[] entityRows) {
103104
return outputEntity;
104105
}
105106

107+
/**
108+
* Deep clones an array.
109+
* From <a href=https://stackoverflow.com/questions/1564832/how-do-i-do-a-deep-copy-of-a-2d-array-in-java>Stackoverflow question ref</a>
110+
* @param matrix Array to clone.
111+
* @return Cloned array.
112+
*/
113+
public static boolean[][] deepCopy(boolean[][] matrix) {
114+
return Arrays.stream(matrix).map(boolean[]::clone).toArray($ -> matrix.clone());
115+
}
116+
106117
}
Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,99 @@
11
package core.entities;
22

3+
import java.util.Arrays;
4+
import java.util.concurrent.atomic.AtomicInteger;
5+
36
public class GameOfLifeRules {
47

58
/**
69
* See class GameOfLifeRules for rules. This method takes a simulated grid and updates the status 1 tick in accordance to the game rules.
710
* @param grid A game of life grid.
11+
* @param iteration a tracker for checking the age of the simulation.
812
* @return The simulated game of life grid one game tick forward.
913
*/
10-
public boolean[][] applyRulesToGrid(boolean[][] grid) {
14+
public boolean[][] applyRulesToGrid(boolean[][] grid, AtomicInteger iteration) {
1115
/*
1216
* To ensure all square updates are applied at the same time we clone the simulated grid.
1317
* This allows for applying the updates in bulk by replacing the simulated grid with the altered grid
1418
* on an event of our choosing.
19+
* Alternatively this could be created as a queue of changes (more work needed). This would allow for
20+
* changes to be streamed and would avoid the need for cloning the original array. This way even a large
21+
* grid could remain responsive. Streaming changes as nodes would also allow for backtracking.
1522
*/
16-
boolean[][] outputGrid = grid.clone();
23+
boolean[][] outputGrid = EntityUtility.deepCopy(grid);
1724
for (int rowIndex = 0; rowIndex < outputGrid.length; rowIndex++) {
1825
for (int colIndex = 0; colIndex < outputGrid[0].length; colIndex++) {
19-
boolean squareActiveStatus = outputGrid[rowIndex][colIndex];
26+
boolean squareActiveStatus = grid[rowIndex][colIndex];
2027
int neighborCount = countActiveNeighbors(grid,colIndex,rowIndex);
2128
if (squareActiveStatus) {
2229
// Square is active
2330
switch (neighborCount) {
2431
case 0:
2532
case 1:
2633
// Death by solitude
27-
outputGrid[rowIndex][colIndex] = applyDeathBySolitude();
28-
break;
34+
outputGrid[rowIndex][colIndex] = applyDeathBySolitude(iteration, colIndex,rowIndex);
35+
continue;
2936
case 2:
3037
case 3:
3138
// Cell survives
32-
break;
39+
continue;
3340
case 4:
3441
case 5:
3542
case 6:
3643
case 7:
3744
case 8:
3845
// Death by overpopulation
39-
outputGrid[rowIndex][colIndex] = applyDeathByOverPopulation();
40-
break;
46+
outputGrid[rowIndex][colIndex] = applyDeathByOverPopulation(iteration, colIndex,rowIndex);
4147
}
4248
} else {
4349
// Square is not active
4450
if (neighborCount == 3) {
4551
// Cell becomes active
46-
outputGrid[rowIndex][colIndex] = applyGrowPopulation();
47-
break;
52+
outputGrid[rowIndex][colIndex] = applyGrowPopulation(iteration, colIndex,rowIndex);
4853
}
4954
}
5055
}
5156
}
57+
iteration.incrementAndGet();
5258
return outputGrid;
5359
}
5460

55-
private boolean applyDeathBySolitude() {
61+
private boolean applyDeathBySolitude(AtomicInteger iteration, int x, int y) {
62+
reportToConsole("Cell killed due to solitude at (" + x + ", " + y + ") during iteration " + iteration.get());
5663
return false;
5764
// FIXME: Amend with logic to handle cases around the edges of the boundaries of SIMULATED_GRID.
5865
}
5966

60-
private boolean applyDeathByOverPopulation() {
67+
private boolean applyDeathByOverPopulation(AtomicInteger iteration, int x, int y) {
68+
reportToConsole("Cell killed due to overpopulation at (" + x + ", " + y + ") during iteration " + iteration.get());
6169
return false;
6270
// FIXME: Amend with logic to handle cases around the edges of the boundaries of SIMULATED_GRID.
6371
}
6472

65-
private boolean applyGrowPopulation() {
73+
private boolean applyGrowPopulation(AtomicInteger iteration, int x, int y) {
74+
reportToConsole("Cell grown at (" + x + ", " + y + ") during iteration " + iteration.get());
6675
return true;
6776
// FIXME: Amend with logic to handle cases around the edges of the boundaries of SIMULATED_GRID.
6877
}
6978

79+
/**
80+
* Counts the number of active cells within a 3x3 grid centered on x,y.
81+
* @param grid the simulation grid.
82+
* @param x the center x index
83+
* @param y the center y index
84+
* @return count of active cells within a 3x3 grid centered on x,y
85+
*/
7086
private int countActiveNeighbors(boolean[][] grid, int x, int y) {
7187
int neighborIterator = 0;
72-
for (int rowIndex = x-1; rowIndex <= x+3; rowIndex++) {
88+
for (int rowIndex = y-1; rowIndex <= y+1; rowIndex++) {
7389
// Indexes outside the scope of the simulation will be treated as dead.
74-
if (rowIndex < 0 || rowIndex >= EntityGrid.getEntityGrid().getGridSize()){
90+
if (rowIndex < 0 || rowIndex >= grid.length){
7591
continue;
7692
}
77-
for (int colIndex = y-1; colIndex <= y+3; colIndex++) {
93+
for (int colIndex = x-1; colIndex <= x+1; colIndex++) {
7894
// Indexes outside the scope of the simulation will be treated as dead.
79-
if (colIndex < 0 || colIndex >= EntityGrid.getEntityGrid().getGridSize()){
95+
// FIXME in certain cases this could result in spaceships that turn on the simulation edge. This shouldn't happen.
96+
if (colIndex < 0 || colIndex >= grid[rowIndex].length){
8097
continue;
8198
}
8299
// Only count neighbor cells so we skip the center of the 3x3
@@ -91,4 +108,8 @@ private int countActiveNeighbors(boolean[][] grid, int x, int y) {
91108
// FIXME: Amend with logic to handle cases around the edges of the boundaries of SIMULATED_GRID.
92109
return neighborIterator;
93110
}
111+
112+
private void reportToConsole(String s) {
113+
System.out.println(s);
114+
}
94115
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package core.entities;
2+
3+
import org.junit.Test;
4+
5+
import java.util.concurrent.atomic.AtomicInteger;
6+
7+
import static org.junit.Assert.*;
8+
9+
public class GameOfLifeRulesTest {
10+
private final static String[] STILL_LIFE_STRING_ARRAY_CONFIGURATION = {
11+
"-+-",
12+
"+-+",
13+
"+-+",
14+
"-+-",
15+
};
16+
private final static String[] OSCILLATOR_STRING_ARRAY_CONFIGURATION = {
17+
"-+-",
18+
"-+-",
19+
"-+-",
20+
};
21+
private final static String[] EMPTY_STRING_ARRAY_CONFIGURATION = {
22+
"---",
23+
"---",
24+
"---",
25+
};
26+
27+
@Test
28+
public void testEmptyGrid() {
29+
GameOfLifeRules gameOfLifeRules = new GameOfLifeRules();
30+
AtomicInteger simulationAge = new AtomicInteger(0);
31+
boolean[][] emptyGrid = gameOfLifeRules.applyRulesToGrid(EntityUtility.loadEntityFromString(EMPTY_STRING_ARRAY_CONFIGURATION), simulationAge);
32+
assertEquals(1, simulationAge.get());
33+
assertArrayEquals( new boolean[3][3], emptyGrid);
34+
}
35+
36+
@Test
37+
public void testStillLife() {
38+
GameOfLifeRules gameOfLifeRules = new GameOfLifeRules();
39+
AtomicInteger simulationAge = new AtomicInteger(0);
40+
boolean[][] stillLifeGrid = gameOfLifeRules.applyRulesToGrid(EntityUtility.loadEntityFromString(STILL_LIFE_STRING_ARRAY_CONFIGURATION), simulationAge);
41+
boolean[][] expectedGrid = {
42+
{false,true,false,},
43+
{true,false,true},
44+
{true,false,true,},
45+
{false,true,false,},
46+
};
47+
assertEquals(1, simulationAge.get());
48+
assertArrayEquals(expectedGrid, stillLifeGrid);
49+
}
50+
51+
@Test
52+
public void testOscillator() {
53+
GameOfLifeRules gameOfLifeRules = new GameOfLifeRules();
54+
AtomicInteger simulationAge = new AtomicInteger(0);
55+
boolean[][] oscillatorGrid = gameOfLifeRules.applyRulesToGrid(EntityUtility.loadEntityFromString(OSCILLATOR_STRING_ARRAY_CONFIGURATION), simulationAge);
56+
boolean[][] expectedOddIteration = {
57+
{false,false,false,},
58+
{true,true,true},
59+
{false,false,false,},
60+
};
61+
boolean[][] expectedEvenIteration = {
62+
{false,true,false,},
63+
{false,true,false},
64+
{false,true,false,},
65+
};
66+
assertEquals(1, simulationAge.get());
67+
assertArrayEquals(expectedOddIteration, oscillatorGrid);
68+
oscillatorGrid = gameOfLifeRules.applyRulesToGrid(oscillatorGrid, simulationAge);
69+
assertEquals(2, simulationAge.get());
70+
assertArrayEquals(expectedEvenIteration, oscillatorGrid);
71+
}
72+
}

0 commit comments

Comments
 (0)