From b2735b1d2e43895aab13a4ce6fcc7b4887ef8d43 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sun, 16 Jan 2022 18:10:28 +0100 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 302e1a0e0b688e6b029cef2e6a7f81681cd6ecfe Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 22:36:29 +1100 Subject: [PATCH 07/14] Testing added! Now some full behaviour tests on both Animal JVM implementations. Still todo: Move the ConsoleTest library into its own module, add a gradle dependency on it. --- 03_Animal/java/{ => src}/Animal.java | 6 +-- 03_Animal/java/test/AnimalJavaTest.kt | 54 +++++++++++++++++++++++++++ 03_Animal/java/test/ConsoleTest.kt | 35 +++++++++++++++++ 03_Animal/kotlin/{ => src}/Animal.kt | 0 03_Animal/kotlin/test/AnimalKtTest.kt | 54 +++++++++++++++++++++++++++ 03_Animal/kotlin/test/ConsoleTest.kt | 35 +++++++++++++++++ 6 files changed, 181 insertions(+), 3 deletions(-) rename 03_Animal/java/{ => src}/Animal.java (98%) create mode 100644 03_Animal/java/test/AnimalJavaTest.kt create mode 100644 03_Animal/java/test/ConsoleTest.kt rename 03_Animal/kotlin/{ => src}/Animal.kt (100%) create mode 100644 03_Animal/kotlin/test/AnimalKtTest.kt create mode 100644 03_Animal/kotlin/test/ConsoleTest.kt diff --git a/03_Animal/java/Animal.java b/03_Animal/java/src/Animal.java similarity index 98% rename from 03_Animal/java/Animal.java rename to 03_Animal/java/src/Animal.java index 681425c1..c4222c5f 100644 --- a/03_Animal/java/Animal.java +++ b/03_Animal/java/src/Animal.java @@ -74,11 +74,11 @@ public class Animal { private static void askForInformationAndSave(Scanner scan, AnimalNode current, QuestionNode previous, boolean previousToCurrentDecisionChoice) { //Failed to get it right and ran out of questions //Let's ask the user for the new information - System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A "); + System.out.print("THE ANIMAL YOU WERE THINKING OF WAS A ? "); String animal = scan.nextLine(); - System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ", animal, current.getAnimal()); + System.out.printf("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A %s FROM A %s ? ", animal, current.getAnimal()); String newQuestion = scan.nextLine(); - System.out.printf("FOR A %s THE ANSWER WOULD BE ", animal); + System.out.printf("FOR A %s THE ANSWER WOULD BE ? ", animal); boolean newAnswer = readYesOrNo(scan); //Add it to our question store addNewAnimal(current, previous, animal, newQuestion, newAnswer, previousToCurrentDecisionChoice); diff --git a/03_Animal/java/test/AnimalJavaTest.kt b/03_Animal/java/test/AnimalJavaTest.kt new file mode 100644 index 00000000..06513481 --- /dev/null +++ b/03_Animal/java/test/AnimalJavaTest.kt @@ -0,0 +1,54 @@ +import org.junit.Test + +class AnimalJavaTest : ConsoleTest() { + + @Test + fun `given a standard setup, find the fish`() { + assertConversation( + """ + $title + ARE YOU THINKING OF AN ANIMAL ? {YES} + DOES IT SWIM ? {YES} + IS IT A FISH ? {YES} + WHY NOT TRY ANOTHER ANIMAL? + ARE YOU THINKING OF AN ANIMAL ? {QUIT} + """ + ) { + Animal.main(emptyArray()) + } + } + + @Test + fun `given a standard setup, create a cow, and verify`() { + assertConversation( + """ + $title + ARE YOU THINKING OF AN ANIMAL ? {YES} + DOES IT SWIM ? {NO} + IS IT A BIRD ? {NO} + THE ANIMAL YOU WERE THINKING OF WAS A ? {COW} + PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A + COW FROM A BIRD + ? {DOES IT EAT GRASS} + FOR A COW THE ANSWER WOULD BE ? {YES} + ARE YOU THINKING OF AN ANIMAL ? {YES} + DOES IT SWIM ? {NO} + DOES IT EAT GRASS ? {YES} + IS IT A COW ? {YES} + WHY NOT TRY ANOTHER ANIMAL? + ARE YOU THINKING OF AN ANIMAL ? {QUIT} + """ + ) { + Animal.main(emptyArray()) + } + } + + private val title = """ + ANIMAL + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + PLAY 'GUESS THE ANIMAL' + THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT. + """ +} + diff --git a/03_Animal/java/test/ConsoleTest.kt b/03_Animal/java/test/ConsoleTest.kt new file mode 100644 index 00000000..3eebdcbb --- /dev/null +++ b/03_Animal/java/test/ConsoleTest.kt @@ -0,0 +1,35 @@ +import com.google.common.truth.Truth +import org.junit.Rule +import org.junit.contrib.java.lang.system.SystemOutRule +import org.junit.contrib.java.lang.system.TextFromStandardInputStream + +abstract class ConsoleTest { + @get:Rule + val inputRule = TextFromStandardInputStream.emptyStandardInputStream() + + @get:Rule + val systemOutRule = SystemOutRule().enableLog() + + val regexInputCommand = "\\{(.*)}".toRegex() + + fun assertConversation(conversation: String, runMain: () -> Unit) { + + inputRule.provideLines(*regexInputCommand + .findAll(conversation) + .map { it.groupValues[1] } + .toList().toTypedArray()) + + runMain() + + Truth.assertThat( + systemOutRule.log.trimWhiteSpace() + ) + .isEqualTo( + regexInputCommand + .replace(conversation, "").trimWhiteSpace() + ) + } + + private fun String.trimWhiteSpace() = + replace("[\\s]+".toRegex(), " ") +} \ No newline at end of file diff --git a/03_Animal/kotlin/Animal.kt b/03_Animal/kotlin/src/Animal.kt similarity index 100% rename from 03_Animal/kotlin/Animal.kt rename to 03_Animal/kotlin/src/Animal.kt diff --git a/03_Animal/kotlin/test/AnimalKtTest.kt b/03_Animal/kotlin/test/AnimalKtTest.kt new file mode 100644 index 00000000..38fae815 --- /dev/null +++ b/03_Animal/kotlin/test/AnimalKtTest.kt @@ -0,0 +1,54 @@ +import org.junit.Test + +class AnimalKtTest : ConsoleTest() { + + @Test + fun `given a standard setup, find the fish`() { + assertConversation( + """ + $title + ARE YOU THINKING OF AN ANIMAL? {YES} + DOES IT SWIM? {YES} + IS IT A FISH? {YES} + WHY NOT TRY ANOTHER ANIMAL? + ARE YOU THINKING OF AN ANIMAL? {QUIT} + """ + ) { + main() + } + } + + @Test + fun `given a standard setup, create a cow, and verify`() { + assertConversation( + """ + $title + ARE YOU THINKING OF AN ANIMAL? {YES} + DOES IT SWIM? {NO} + IS IT A BIRD? {NO} + THE ANIMAL YOU WERE THINKING OF WAS A? {COW} + PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A + COW FROM A BIRD + ? {DOES IT EAT GRASS} + FOR A COW THE ANSWER WOULD BE? {YES} + ARE YOU THINKING OF AN ANIMAL? {YES} + DOES IT SWIM? {NO} + DOES IT EAT GRASS? {YES} + IS IT A COW? {YES} + WHY NOT TRY ANOTHER ANIMAL? + ARE YOU THINKING OF AN ANIMAL? {QUIT} + """ + ) { + main() + } + } + + private val title = """ + ANIMAL + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + PLAY 'GUESS THE ANIMAL' + THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT. + """ +} + diff --git a/03_Animal/kotlin/test/ConsoleTest.kt b/03_Animal/kotlin/test/ConsoleTest.kt new file mode 100644 index 00000000..3eebdcbb --- /dev/null +++ b/03_Animal/kotlin/test/ConsoleTest.kt @@ -0,0 +1,35 @@ +import com.google.common.truth.Truth +import org.junit.Rule +import org.junit.contrib.java.lang.system.SystemOutRule +import org.junit.contrib.java.lang.system.TextFromStandardInputStream + +abstract class ConsoleTest { + @get:Rule + val inputRule = TextFromStandardInputStream.emptyStandardInputStream() + + @get:Rule + val systemOutRule = SystemOutRule().enableLog() + + val regexInputCommand = "\\{(.*)}".toRegex() + + fun assertConversation(conversation: String, runMain: () -> Unit) { + + inputRule.provideLines(*regexInputCommand + .findAll(conversation) + .map { it.groupValues[1] } + .toList().toTypedArray()) + + runMain() + + Truth.assertThat( + systemOutRule.log.trimWhiteSpace() + ) + .isEqualTo( + regexInputCommand + .replace(conversation, "").trimWhiteSpace() + ) + } + + private fun String.trimWhiteSpace() = + replace("[\\s]+".toRegex(), " ") +} \ No newline at end of file From f756389037a8abd02c37bde4d2479325741c48e6 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 23:48:53 +1100 Subject: [PATCH 08/14] Createa a build_00_utilities project containing testing helpers, including the ConsoleTest abstract class. --- .../jvmTestUtils/kotlin}/test/ConsoleTest.kt | 2 ++ 03_Animal/java/test/AnimalJavaTest.kt | 1 + 03_Animal/kotlin/test/AnimalKtTest.kt | 1 + 03_Animal/kotlin/test/ConsoleTest.kt | 35 ------------------- 4 files changed, 4 insertions(+), 35 deletions(-) rename {03_Animal/java => 00_Utilities/jvmTestUtils/kotlin}/test/ConsoleTest.kt (96%) delete mode 100644 03_Animal/kotlin/test/ConsoleTest.kt diff --git a/03_Animal/java/test/ConsoleTest.kt b/00_Utilities/jvmTestUtils/kotlin/test/ConsoleTest.kt similarity index 96% rename from 03_Animal/java/test/ConsoleTest.kt rename to 00_Utilities/jvmTestUtils/kotlin/test/ConsoleTest.kt index 3eebdcbb..d58af170 100644 --- a/03_Animal/java/test/ConsoleTest.kt +++ b/00_Utilities/jvmTestUtils/kotlin/test/ConsoleTest.kt @@ -1,3 +1,5 @@ +package com.pcholt.console.testutils + import com.google.common.truth.Truth import org.junit.Rule import org.junit.contrib.java.lang.system.SystemOutRule diff --git a/03_Animal/java/test/AnimalJavaTest.kt b/03_Animal/java/test/AnimalJavaTest.kt index 06513481..013655a3 100644 --- a/03_Animal/java/test/AnimalJavaTest.kt +++ b/03_Animal/java/test/AnimalJavaTest.kt @@ -1,3 +1,4 @@ +import com.pcholt.console.testutils.ConsoleTest import org.junit.Test class AnimalJavaTest : ConsoleTest() { diff --git a/03_Animal/kotlin/test/AnimalKtTest.kt b/03_Animal/kotlin/test/AnimalKtTest.kt index 38fae815..e93857ea 100644 --- a/03_Animal/kotlin/test/AnimalKtTest.kt +++ b/03_Animal/kotlin/test/AnimalKtTest.kt @@ -1,3 +1,4 @@ +import com.pcholt.console.testutils.ConsoleTest import org.junit.Test class AnimalKtTest : ConsoleTest() { diff --git a/03_Animal/kotlin/test/ConsoleTest.kt b/03_Animal/kotlin/test/ConsoleTest.kt deleted file mode 100644 index 3eebdcbb..00000000 --- a/03_Animal/kotlin/test/ConsoleTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -import com.google.common.truth.Truth -import org.junit.Rule -import org.junit.contrib.java.lang.system.SystemOutRule -import org.junit.contrib.java.lang.system.TextFromStandardInputStream - -abstract class ConsoleTest { - @get:Rule - val inputRule = TextFromStandardInputStream.emptyStandardInputStream() - - @get:Rule - val systemOutRule = SystemOutRule().enableLog() - - val regexInputCommand = "\\{(.*)}".toRegex() - - fun assertConversation(conversation: String, runMain: () -> Unit) { - - inputRule.provideLines(*regexInputCommand - .findAll(conversation) - .map { it.groupValues[1] } - .toList().toTypedArray()) - - runMain() - - Truth.assertThat( - systemOutRule.log.trimWhiteSpace() - ) - .isEqualTo( - regexInputCommand - .replace(conversation, "").trimWhiteSpace() - ) - } - - private fun String.trimWhiteSpace() = - replace("[\\s]+".toRegex(), " ") -} \ No newline at end of file From 7674913388397e09c1cf15ef39726beb550e2733 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 30 Jan 2022 00:11:59 +1100 Subject: [PATCH 09/14] Description added to README --- buildJvm/README.md | 95 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/buildJvm/README.md b/buildJvm/README.md index 4ecba004..3db4014a 100644 --- a/buildJvm/README.md +++ b/buildJvm/README.md @@ -67,16 +67,16 @@ directory for the java or kotlin file, and the class that contains the `main` me The `build.gradle` file **should** be identical to all the other `build.gradle` files in all the other subprojects: ```groovy - sourceSets { - main { - java { - srcDirs "../../$gameSource" - } - } - } - application { - mainClass = gameMain - } + sourceSets { + main { + java { + srcDirs "../../$gameSource" + } + } + } + application { + mainClass = gameMain + } ``` The `gradle.properties` file should look like this: @@ -92,3 +92,78 @@ project to the list. ```groovy include ":build_91_Train_java" ``` + +### Adding a game with tests + +You can add tests for JVM games with a `build.gradle` looking a little different. +Use the build files from `03_Animal` as a template to add tests: + +```groovy +sourceSets { + main { + java { + srcDirs "../../$gameSource" + } + } + test { + java { + srcDirs "../../$gameTest" + } + } +} + +application { + mainClass = gameMain +} + +dependencies { + testImplementation(project(":build_00_utilities").sourceSets.test.output) +} +``` + +The gradle.properties needs an additional directory name for the tests, as `gameTest` : +``` +gameSource=03_Animal/java/src +gameTest=03_Animal/java/test +gameMain=Animal +``` + +Each project should have its own test, and shouldn't share test source directories +with other projects, even if they are for the same game. + +Tests are constructed by subclassing `ConsoleTest`. This allows you to use the +`assertConversation` function to check for correct interactive conversations. +```kotlin +import com.pcholt.console.testutils.ConsoleTest +import org.junit.Test + +class AnimalJavaTest : ConsoleTest() { + @Test + fun `should have a simple conversation`() { + assertConversation( + """ + WHAT'S YOUR NAME? {PAUL} + YOUR NAME IS PAUL? {YES} + THANKS FOR PLAYING + """ + ) { + // The game's Main method + main() + } + } +} +``` + +Curly brackets are the expected user input. +Note - this is actually just a way of defining the expected input as "PAUL" and "YES" +and not that the input happens at the exact prompt position. Thus this is equivalent: +```kotlin +""" +{PAUL} {YES} WHAT'S YOUR NAME? +YOUR NAME IS PAUL? +THANKS FOR PLAYING +""" +``` + +Amounts of whitespace are not counted, but whitespace is significant: You will get a failure if +your game emits `"NAME?"` when it expects `"NAME ?"`. From cefe471fdf79fd95589285bb92e4e8fcd0e286db Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 30 Jan 2022 00:21:25 +1100 Subject: [PATCH 10/14] Description added to README --- buildJvm/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/buildJvm/README.md b/buildJvm/README.md index 3db4014a..c08811b9 100644 --- a/buildJvm/README.md +++ b/buildJvm/README.md @@ -167,3 +167,9 @@ THANKS FOR PLAYING Amounts of whitespace are not counted, but whitespace is significant: You will get a failure if your game emits `"NAME?"` when it expects `"NAME ?"`. + +Run all the tests from within the buildJvm project directory: +```bash +cd buildJvm +./gradlew test +``` From 6f7e311b5197ae61a4ff883802a812bad5a1c599 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 16:30:42 +0100 Subject: [PATCH 11/14] 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 12/14] 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 13/14] 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 From c552019cd8332daa8c4b86b0071977382a185ce1 Mon Sep 17 00:00:00 2001 From: roygilliam <58305899+roygilliam@users.noreply.github.com> Date: Sat, 29 Jan 2022 11:15:11 -0500 Subject: [PATCH 14/14] Initial conversion to C# --- 85_Synonym/csharp/Synonym.cs | 149 +++++++++++++++++++++++++++++++ 85_Synonym/csharp/Synonym.csproj | 3 +- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 85_Synonym/csharp/Synonym.cs diff --git a/85_Synonym/csharp/Synonym.cs b/85_Synonym/csharp/Synonym.cs new file mode 100644 index 00000000..9d58da71 --- /dev/null +++ b/85_Synonym/csharp/Synonym.cs @@ -0,0 +1,149 @@ +using System.Text; + +namespace Synonym +{ + class Synonym + { + Random rand = new Random(); + + // Initialize list of corrent responses + private string[] Affirmations = { "Right", "Correct", "Fine", "Good!", "Check" }; + + // Initialize list of words and their synonyms + private string[][] Words = + { + new string[] {"first", "start", "beginning", "onset", "initial"}, + new string[] {"similar", "alike", "same", "like", "resembling"}, + new string[] {"model", "pattern", "prototype", "standard", "criterion"}, + new string[] {"small", "insignificant", "little", "tiny", "minute"}, + new string[] {"stop", "halt", "stay", "arrest", "check", "standstill"}, + new string[] {"house", "dwelling", "residence", "domicile", "lodging", "habitation"}, + new string[] {"pit", "hole", "hollow", "well", "gulf", "chasm", "abyss"}, + new string[] {"push", "shove", "thrust", "prod", "poke", "butt", "press"}, + new string[] {"red", "rouge", "scarlet", "crimson", "flame", "ruby"}, + new string[] {"pain", "suffering", "hurt", "misery", "distress", "ache", "discomfort"} + }; + + private void DisplayIntro() + { + Console.WriteLine(""); + Console.WriteLine("SYNONYM".PadLeft(23)); + Console.WriteLine("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + Console.WriteLine(""); + Console.WriteLine("A synonym of a word means another word in the English"); + Console.WriteLine("language which has the same or very nearly the same meaning."); + Console.WriteLine("I choose a word -- you type a synonym."); + Console.WriteLine("If you can't think of a synonym, type the word 'help'"); + Console.WriteLine("and I will tell you a synonym."); + Console.WriteLine(""); + } + + private void DisplayOutro() + { + Console.WriteLine("Synonym drill completed."); + } + + private void RandomizeTheList() + { + // Randomize the list of Words to pick from + int[] Order = new int[Words.Length]; + foreach (int i in Order) + { + Order[i] = rand.Next(); + } + Array.Sort(Order, Words); + } + + private string GetAnAffirmation() + { + return Affirmations[rand.Next(Affirmations.Length)]; + } + + private bool CheckTheResponse(string WordName, int WordIndex, string LineInput, string[] WordList) + { + if (LineInput.Equals("help")) + { + // Choose a random correct synonym response that doesn't equal the current word given + int HelpIndex = rand.Next(WordList.Length); + while (HelpIndex == WordIndex) + { + HelpIndex = rand.Next(0, WordList.Length); + } + Console.WriteLine("**** A synonym of {0} is {1}.", WordName, WordList[HelpIndex]); + + return false; + } + else + { + // Check to see if the response is one of the listed synonyms and not the current word prompt + if (WordList.Contains(LineInput) && LineInput != WordName) + { + // Randomly display one of the five correct answer exclamations + Console.WriteLine(GetAnAffirmation()); + + return true; + } + else + { + // Incorrect response. Try again. + Console.WriteLine(" Try again.".PadLeft(5)); + + return false; + } + } + } + + private string PromptForSynonym(string WordName) + { + Console.Write(" What is a synonym of {0}? ", WordName); + string LineInput = Console.ReadLine().Trim().ToLower(); + + return LineInput; + } + + private void AskForSynonyms() + { + Random rand = new Random(); + + // Loop through the now randomized list of Words and display a random word from each to prompt for a synonym + foreach (string[] WordList in Words) + { + int WordIndex = rand.Next(WordList.Length); // random word position in the current list of words + string WordName = WordList[WordIndex]; // what is that actual word + bool Success = false; + + while (!Success) + { + // Ask for the synonym of the current word + string LineInput = PromptForSynonym(WordName); + + // Check the response + Success = CheckTheResponse(WordName, WordIndex, LineInput, WordList); + + // Add extra line space for formatting + Console.WriteLine(""); + } + } + } + + public void PlayTheGame() + { + RandomizeTheList(); + + DisplayIntro(); + + AskForSynonyms(); + + DisplayOutro(); + } + } + class Program + { + static void Main(string[] args) + { + + new Synonym().PlayTheGame(); + + } + } +} \ No newline at end of file diff --git a/85_Synonym/csharp/Synonym.csproj b/85_Synonym/csharp/Synonym.csproj index d3fe4757..1fd332a6 100644 --- a/85_Synonym/csharp/Synonym.csproj +++ b/85_Synonym/csharp/Synonym.csproj @@ -1,9 +1,10 @@ - + Exe net6.0 10 enable enable + Synonym.Program