From b2735b1d2e43895aab13a4ce6fcc7b4887ef8d43 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sun, 16 Jan 2022 18:10:28 +0100 Subject: [PATCH 1/9] Add Java implementation --- 55_Life/java/src/java/Life.java | 127 ++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 55_Life/java/src/java/Life.java diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java new file mode 100644 index 00000000..3b592c14 --- /dev/null +++ b/55_Life/java/src/java/Life.java @@ -0,0 +1,127 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +record Transition(int y, int x, byte newState) { } + +public class Life { + + private static final byte DEAD = 0; + private static final byte ALIVE = 1; + + private final byte[][] matrix = new byte[21][67]; + private int generation = 0; + private int population = 0; + boolean invalid = false; + + private void start() throws Exception { + Scanner s = new Scanner(System.in); + printGameHeader(); + readPattern(); + while (true) { + printPattern(); + advanceToNextGeneration(); + s.nextLine(); +// Thread.sleep(1000); + } + } + + private void advanceToNextGeneration() { + List transitions = new ArrayList<>(); + for (int y = 0; y < matrix.length; y++) { + for (int x = 0; x < matrix[y].length; x++) { + int neighbours = countNeighbours(y, x); + if (matrix[y][x] == DEAD) { + if (neighbours == 3) { + transitions.add(new Transition(y, x, ALIVE)); + population++; + } + } else { + // cell is alive + if (neighbours < 2 || neighbours > 3) { + transitions.add(new Transition(y, x, DEAD)); + population--; + } + } + } + } + transitions.forEach(t -> matrix[t.y()][t.x()] = t.newState()); + generation++; + } + + private int countNeighbours(int y, int x) { + int neighbours = 0; + for (int row = Math.max(y - 1, 0); row <= Math.min(y + 1, matrix.length - 1); row++) { + for (int col = Math.max(x - 1, 0); col <= Math.min(x + 1, matrix[row].length - 1); col++) { + if (row == y && col == x) { + continue; + } + if (matrix[row][col] == ALIVE) { + neighbours++; + } + } + } + return neighbours; + } + + private void readPattern() { + System.out.println("ENTER YOUR PATTERN:"); + Scanner s = new Scanner(System.in); + List lines = new ArrayList<>(); + String line; + int maxLineLength = 0; + boolean reading = true; + while (reading) { + System.out.print("? "); + line = s.nextLine(); + if (line.equalsIgnoreCase("done")) { + reading = false; + } else { + // optional support for the '.' that is needed in the BASIC version + lines.add(line.replace('.', ' ')); + maxLineLength = Math.max(maxLineLength, line.length()); + } + } + fillMatrix(lines, maxLineLength); + } + + private void fillMatrix(List lines, int maxLineLength) { + float xMin = 33 - maxLineLength / 2f; + float yMin = 11 - lines.size() / 2f; + System.out.println("lines=" + lines.size() + " columns=" + maxLineLength + " yMin=" + yMin + " xMin=" + xMin); + for (int y = 0; y < lines.size(); y++) { + String line = lines.get(y); + for (int x = 1; x <= line.length(); x++) { + if (line.charAt(x-1) == '*') { + matrix[round(yMin + y)][round(xMin + x)] = ALIVE; + population++; + } + } + } + } + + private int round(float f) { + return (int) Math.floor(f); + } + + private void printGameHeader() { + System.out.println(" ".repeat(34) + "LIFE"); + System.out.println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + System.out.println("\n\n\n"); + } + + private void printPattern() { + System.out.println("GENERATION: " + generation + " POPULATION: " + population); + for (int y = 0; y < matrix.length; y++) { + for (int x = 0; x < matrix[y].length; x++) { + System.out.print(matrix[y][x] == 1 ? "*" : " "); + } + System.out.println(); + } + } + + public static void main(String[] args) throws Exception { + new Life().start(); + } + +} From 8df8eb6165dbb9c8bfa02de9463e212704506689 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Mon, 17 Jan 2022 18:48:29 +0100 Subject: [PATCH 2/9] Refactor, add Javadoc --- 55_Life/java/src/java/Life.java | 63 ++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index 3b592c14..41b30c8d 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -2,49 +2,69 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; +/** + * Represents a state change for a single cell within the matrix. + * + * @param y the y coordinate (row) of the cell + * @param x the x coordinate (column) of the cell + * @param newState the new state of the cell (either DEAD or ALIVE) + */ record Transition(int y, int x, byte newState) { } +/** + * The Game of Life class.
+ *
+ * Mimics the behaviour of the BASIC version, however the Java code does not have much in common with the original. + *
+ * Differences in behaviour: + *
    + *
  • Input supports the "." character, but it's optional.
  • + *
  • Input regarding the "DONE" string is case insensitive.
  • + *
+ */ public class Life { private static final byte DEAD = 0; private static final byte ALIVE = 1; + private final Scanner consoleReader = new Scanner(System.in); + private final byte[][] matrix = new byte[21][67]; private int generation = 0; private int population = 0; boolean invalid = false; private void start() throws Exception { - Scanner s = new Scanner(System.in); printGameHeader(); readPattern(); while (true) { - printPattern(); + printGeneration(); advanceToNextGeneration(); - s.nextLine(); + consoleReader.nextLine(); // Thread.sleep(1000); } } private void advanceToNextGeneration() { + // store all transitions of cells in a list, i.e. if a dead cell becomes alive, or a living cell dies List transitions = new ArrayList<>(); for (int y = 0; y < matrix.length; y++) { for (int x = 0; x < matrix[y].length; x++) { int neighbours = countNeighbours(y, x); - if (matrix[y][x] == DEAD) { - if (neighbours == 3) { - transitions.add(new Transition(y, x, ALIVE)); - population++; - } - } else { - // cell is alive + if (matrix[y][x] == ALIVE) { if (neighbours < 2 || neighbours > 3) { transitions.add(new Transition(y, x, DEAD)); population--; } + } else { // cell is dead + if (neighbours == 3) { + transitions.add(new Transition(y, x, ALIVE)); + population++; + } } } } + // apply all transitions to the matrix transitions.forEach(t -> matrix[t.y()][t.x()] = t.newState()); generation++; } @@ -66,14 +86,13 @@ public class Life { private void readPattern() { System.out.println("ENTER YOUR PATTERN:"); - Scanner s = new Scanner(System.in); List lines = new ArrayList<>(); String line; int maxLineLength = 0; boolean reading = true; while (reading) { System.out.print("? "); - line = s.nextLine(); + line = consoleReader.nextLine(); if (line.equalsIgnoreCase("done")) { reading = false; } else { @@ -93,24 +112,28 @@ public class Life { String line = lines.get(y); for (int x = 1; x <= line.length(); x++) { if (line.charAt(x-1) == '*') { - matrix[round(yMin + y)][round(xMin + x)] = ALIVE; + matrix[floor(yMin + y)][floor(xMin + x)] = ALIVE; population++; } } } } - private int round(float f) { + private int floor(float f) { return (int) Math.floor(f); } private void printGameHeader() { - System.out.println(" ".repeat(34) + "LIFE"); - System.out.println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + printIndented(34, "LIFE"); + printIndented(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); System.out.println("\n\n\n"); } - private void printPattern() { + private void printIndented(int spaces, String str) { + System.out.println(" ".repeat(spaces) + str); + } + + private void printGeneration() { System.out.println("GENERATION: " + generation + " POPULATION: " + population); for (int y = 0; y < matrix.length; y++) { for (int x = 0; x < matrix[y].length; x++) { @@ -120,6 +143,12 @@ public class Life { } } + /** + * Main method that starts the program. + * + * @param args the command line arguments. + * @throws Exception if something goes wrong. + */ public static void main(String[] args) throws Exception { new Life().start(); } From 064907c83ef9cd9e784a37a0d1e33e8df9586db4 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Mon, 17 Jan 2022 19:08:19 +0100 Subject: [PATCH 3/9] Print generation header formatted correctly --- 55_Life/java/src/java/Life.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index 41b30c8d..d3477c66 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -134,7 +134,7 @@ public class Life { } private void printGeneration() { - System.out.println("GENERATION: " + generation + " POPULATION: " + population); + printGenerationHeader(); for (int y = 0; y < matrix.length; y++) { for (int x = 0; x < matrix[y].length; x++) { System.out.print(matrix[y][x] == 1 ? "*" : " "); @@ -143,6 +143,11 @@ public class Life { } } + private void printGenerationHeader() { + String invalidText = invalid ? "INVALID!" : ""; + System.out.printf("GENERATION: %-13d POPULATION: %d %s\n", generation, population, invalidText); + } + /** * Main method that starts the program. * From 7dadd251abb0bba5e4d2ea7e5eded669f8e7e873 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Wed, 19 Jan 2022 07:20:50 +0100 Subject: [PATCH 4/9] Evaluate invalid state --- 55_Life/java/src/java/Life.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index d3477c66..e3c8ade3 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -58,6 +58,9 @@ public class Life { } } else { // cell is dead if (neighbours == 3) { + if (x < 2 || x > 67 || y < 2 || y > 21) { + invalid = true; + } transitions.add(new Transition(y, x, ALIVE)); population++; } From be5552f677db3eab52249759c085e31bd3cd5911 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Fri, 28 Jan 2022 07:57:44 +0100 Subject: [PATCH 5/9] Add command line arg to stop after each generation --- 55_Life/java/src/java/Life.java | 50 ++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index e3c8ade3..55f55ee8 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -2,15 +2,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; -/** - * Represents a state change for a single cell within the matrix. - * - * @param y the y coordinate (row) of the cell - * @param x the x coordinate (column) of the cell - * @param newState the new state of the cell (either DEAD or ALIVE) - */ -record Transition(int y, int x, byte newState) { } - /** * The Game of Life class.
*
@@ -26,28 +17,47 @@ public class Life { private static final byte DEAD = 0; private static final byte ALIVE = 1; + private static final String NEWLINE = "\n"; private final Scanner consoleReader = new Scanner(System.in); private final byte[][] matrix = new byte[21][67]; private int generation = 0; private int population = 0; + boolean stopAfterGen = false; boolean invalid = false; - private void start() throws Exception { + + public Life(String[] args) { + parse(args); + } + + private void parse(String[] args) { + for (String arg : args) { + if ("-s".equals(arg)) { + stopAfterGen = true; + break; + } + } + } + + private void start() { printGameHeader(); readPattern(); while (true) { printGeneration(); advanceToNextGeneration(); - consoleReader.nextLine(); -// Thread.sleep(1000); + if (stopAfterGen) { + consoleReader.nextLine(); + } } } private void advanceToNextGeneration() { - // store all transitions of cells in a list, i.e. if a dead cell becomes alive, or a living cell dies + // store all cell transitions in a list, i.e. if a dead cell becomes alive, or a living cell dies List transitions = new ArrayList<>(); + // there's still room for optimization: instead of iterating over all cells in the matrix, + // we could consider only the section containing the pattern(s), as in the BASIC version for (int y = 0; y < matrix.length; y++) { for (int x = 0; x < matrix[y].length; x++) { int neighbours = countNeighbours(y, x); @@ -110,7 +120,6 @@ public class Life { private void fillMatrix(List lines, int maxLineLength) { float xMin = 33 - maxLineLength / 2f; float yMin = 11 - lines.size() / 2f; - System.out.println("lines=" + lines.size() + " columns=" + maxLineLength + " yMin=" + yMin + " xMin=" + xMin); for (int y = 0; y < lines.size(); y++) { String line = lines.get(y); for (int x = 1; x <= line.length(); x++) { @@ -129,7 +138,7 @@ public class Life { private void printGameHeader() { printIndented(34, "LIFE"); printIndented(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); - System.out.println("\n\n\n"); + System.out.println(NEWLINE.repeat(3)); } private void printIndented(int spaces, String str) { @@ -158,7 +167,16 @@ public class Life { * @throws Exception if something goes wrong. */ public static void main(String[] args) throws Exception { - new Life().start(); + new Life(args).start(); } } + +/** + * Represents a state change for a single cell within the matrix. + * + * @param y the y coordinate (row) of the cell + * @param x the x coordinate (column) of the cell + * @param newState the new state of the cell (either DEAD or ALIVE) + */ +record Transition(int y, int x, byte newState) { } From 69a62c5c4b6c42fe54285909364570219a477891 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Fri, 28 Jan 2022 08:01:21 +0100 Subject: [PATCH 6/9] Prompt for ENTER to continue --- 55_Life/java/src/java/Life.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index 55f55ee8..0e202240 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -48,6 +48,7 @@ public class Life { printGeneration(); advanceToNextGeneration(); if (stopAfterGen) { + System.out.print("PRESS ENTER TO CONTINUE"); consoleReader.nextLine(); } } @@ -163,7 +164,8 @@ public class Life { /** * Main method that starts the program. * - * @param args the command line arguments. + * @param args the command line arguments: + *
-s: Stop after each generation (press enter to continue)
* @throws Exception if something goes wrong. */ public static void main(String[] args) throws Exception { From 6f7e311b5197ae61a4ff883802a812bad5a1c599 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 16:30:42 +0100 Subject: [PATCH 7/9] Add Javadoc --- 55_Life/java/src/java/Life.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/55_Life/java/src/java/Life.java b/55_Life/java/src/java/Life.java index 0e202240..2f5c184a 100644 --- a/55_Life/java/src/java/Life.java +++ b/55_Life/java/src/java/Life.java @@ -27,7 +27,11 @@ public class Life { boolean stopAfterGen = false; boolean invalid = false; - + /** + * Constructor. + * + * @param args the command line arguments + */ public Life(String[] args) { parse(args); } @@ -41,7 +45,10 @@ public class Life { } } - private void start() { + /** + * Starts the game. + */ + public void start() { printGameHeader(); readPattern(); while (true) { From 1f3855e98c7a463613ae016b23cd4da085c55ecc Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 16:56:53 +0100 Subject: [PATCH 8/9] Edit README.md --- 55_Life/java/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/55_Life/java/README.md b/55_Life/java/README.md index 51edd8d4..27b6941b 100644 --- a/55_Life/java/README.md +++ b/55_Life/java/README.md @@ -1,3 +1,18 @@ +# Game of Life - Java version + Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Oracle Java](https://openjdk.java.net/) + +## Requirements + +* Requires Java 17 (or later) + +## Notes + +The Java version of Game of Life tries to mimics the behaviour of the BASIC version. +However, the Java code does not have much in common with the original. + +**Differences in behaviour:** +* Input supports the ```.``` character, but it's optional. +* Evaluation of ```DONE``` input string is case insensitive. From a7b9ab89c67f97c9414c948f98cd8fe5d536af18 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 17:05:10 +0100 Subject: [PATCH 9/9] Edit README.md --- 55_Life/java/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/55_Life/java/README.md b/55_Life/java/README.md index 27b6941b..19a5ff38 100644 --- a/55_Life/java/README.md +++ b/55_Life/java/README.md @@ -16,3 +16,4 @@ However, the Java code does not have much in common with the original. **Differences in behaviour:** * Input supports the ```.``` character, but it's optional. * Evaluation of ```DONE``` input string is case insensitive. +* Run with the ```-s``` command line argument to halt the program after each generation, and continue when ```ENTER``` is pressed. \ No newline at end of file