From 5d9df6c53a7e0c5a52e85f1a1df5a4c9d2e11345 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sun, 16 Jan 2022 18:10:28 +0100 Subject: [PATCH 01/16] 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 09d72c12658d345880efba24e0074b0064467029 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Mon, 17 Jan 2022 18:48:29 +0100 Subject: [PATCH 02/16] 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 812f80b833d7eeccad95ef0539d0fb0484fda996 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Mon, 17 Jan 2022 19:08:19 +0100 Subject: [PATCH 03/16] 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 c685aa9bdad34c3fc85fe7d5afbcd289ac3844f8 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Wed, 19 Jan 2022 07:20:50 +0100 Subject: [PATCH 04/16] 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 ad21c7e1406169dd5a164d7543b2c77c4cca0e9f Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Fri, 28 Jan 2022 07:57:44 +0100 Subject: [PATCH 05/16] 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 ab4f3811407fd7fc8fe91aa38a73f61292fd32f1 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Fri, 28 Jan 2022 08:01:21 +0100 Subject: [PATCH 06/16] 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 8b21b4164e0b845751fed4bd708b9a2d3957711d Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 17:21:28 +1100 Subject: [PATCH 07/16] Apply java toolchain to subprojects, not main project -- this fixes compilation errors in Boxing and AceyDucey17. Add AceyDucey17 as its own subproject. Add test implementation libraries Truth, JUnit, and Stefan Birkner's system rules for testing command line applications. --- buildJvm/build.gradle.kts | 22 +++++++++++++------ .../gradle.properties | 2 ++ buildJvm/build_96_Word_java/build.gradle | 8 ------- buildJvm/settings.gradle | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 buildJvm/build_01_Acey_Ducey_java17/gradle.properties diff --git a/buildJvm/build.gradle.kts b/buildJvm/build.gradle.kts index a43017d0..6cfba2e7 100644 --- a/buildJvm/build.gradle.kts +++ b/buildJvm/build.gradle.kts @@ -7,17 +7,13 @@ version = "unspecified" repositories { mavenCentral() + google() } dependencies { implementation(kotlin("stdlib")) } -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} task("distributeBin", Copy::class) { from(filesType("bin")) @@ -43,9 +39,21 @@ task("copyAll") { subprojects { apply(plugin = "application") apply(plugin = "kotlin") - repositories { - mavenCentral() + apply(plugin = "java") + repositories { + mavenCentral() + } + dependencies { + testImplementation("junit:junit:4.13.2") + testImplementation("com.github.stefanbirkner:system-rules:1.19.0") + testImplementation("com.google.truth:truth:1.1.3") + } + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) } + } + } fun filesType(type: String) = diff --git a/buildJvm/build_01_Acey_Ducey_java17/gradle.properties b/buildJvm/build_01_Acey_Ducey_java17/gradle.properties new file mode 100644 index 00000000..d708d964 --- /dev/null +++ b/buildJvm/build_01_Acey_Ducey_java17/gradle.properties @@ -0,0 +1,2 @@ +gameSource=01_Acey_Ducey/java/src +gameMain=AceyDucey17 diff --git a/buildJvm/build_96_Word_java/build.gradle b/buildJvm/build_96_Word_java/build.gradle index 3432056c..132b05dd 100644 --- a/buildJvm/build_96_Word_java/build.gradle +++ b/buildJvm/build_96_Word_java/build.gradle @@ -1,7 +1,3 @@ -plugins { - id 'application' -} - sourceSets { main { java { @@ -10,10 +6,6 @@ sourceSets { } } -repositories { - mavenCentral() -} - application { mainClass = gameMain } diff --git a/buildJvm/settings.gradle b/buildJvm/settings.gradle index b7ce0404..9164e5fa 100644 --- a/buildJvm/settings.gradle +++ b/buildJvm/settings.gradle @@ -10,6 +10,7 @@ include ":build_89_Tic-Tac-Toe_kotlin" include ":build_94_War_kotlin" include ":build_01_Acey_Ducey_java" +include ":build_01_Acey_Ducey_java17" include ":build_02_Amazing_java" include ":build_03_Animal_java" include ":build_04_Awari_java" From f6aceb0d48ccbfd3c6b4db0f0d3e483d69d40fd5 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 17:23:43 +1100 Subject: [PATCH 08/16] Oops AceyDucey17 build.gradle --- buildJvm/build_01_Acey_Ducey_java17/build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 buildJvm/build_01_Acey_Ducey_java17/build.gradle diff --git a/buildJvm/build_01_Acey_Ducey_java17/build.gradle b/buildJvm/build_01_Acey_Ducey_java17/build.gradle new file mode 100644 index 00000000..132b05dd --- /dev/null +++ b/buildJvm/build_01_Acey_Ducey_java17/build.gradle @@ -0,0 +1,11 @@ +sourceSets { + main { + java { + srcDirs "../../$gameSource" + } + } +} + +application { + mainClass = gameMain +} From 0ba18afee1c100c342693334356e365acf1ef13d Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 22:36:29 +1100 Subject: [PATCH 09/16] 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 ++++++++++++ buildJvm/build_03_Animal_java/build.gradle | 5 ++ .../build_03_Animal_java/gradle.properties | 3 +- buildJvm/build_03_Animal_kotlin/build.gradle | 5 ++ .../build_03_Animal_kotlin/gradle.properties | 3 +- 10 files changed, 195 insertions(+), 5 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 diff --git a/buildJvm/build_03_Animal_java/build.gradle b/buildJvm/build_03_Animal_java/build.gradle index 132b05dd..bd59f127 100644 --- a/buildJvm/build_03_Animal_java/build.gradle +++ b/buildJvm/build_03_Animal_java/build.gradle @@ -4,6 +4,11 @@ sourceSets { srcDirs "../../$gameSource" } } + test { + java { + srcDirs "../../$gameTest" + } + } } application { diff --git a/buildJvm/build_03_Animal_java/gradle.properties b/buildJvm/build_03_Animal_java/gradle.properties index 679638f2..496fc2d7 100644 --- a/buildJvm/build_03_Animal_java/gradle.properties +++ b/buildJvm/build_03_Animal_java/gradle.properties @@ -1,2 +1,3 @@ -gameSource=03_Animal/java +gameSource=03_Animal/java/src +gameTest=03_Animal/java/test gameMain=Animal diff --git a/buildJvm/build_03_Animal_kotlin/build.gradle b/buildJvm/build_03_Animal_kotlin/build.gradle index 132b05dd..bd59f127 100644 --- a/buildJvm/build_03_Animal_kotlin/build.gradle +++ b/buildJvm/build_03_Animal_kotlin/build.gradle @@ -4,6 +4,11 @@ sourceSets { srcDirs "../../$gameSource" } } + test { + java { + srcDirs "../../$gameTest" + } + } } application { diff --git a/buildJvm/build_03_Animal_kotlin/gradle.properties b/buildJvm/build_03_Animal_kotlin/gradle.properties index ea63333f..a3fdf99f 100644 --- a/buildJvm/build_03_Animal_kotlin/gradle.properties +++ b/buildJvm/build_03_Animal_kotlin/gradle.properties @@ -1,2 +1,3 @@ -gameSource=03_Animal/kotlin +gameSource=03_Animal/kotlin/src +gameTest=03_Animal/kotlin/test gameMain=AnimalKt From 81c0681580c56ade5808f73d84ee837badd9e008 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sat, 29 Jan 2022 23:48:53 +1100 Subject: [PATCH 10/16] 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 ------------------- buildJvm/build_00_utilities/build.gradle | 7 ++++ buildJvm/build_00_utilities/gradle.properties | 1 + buildJvm/build_03_Animal_java/build.gradle | 4 +++ buildJvm/build_03_Animal_kotlin/build.gradle | 4 +++ buildJvm/settings.gradle | 2 ++ 9 files changed, 22 insertions(+), 35 deletions(-) rename {03_Animal/java => 00_Utilities/jvmTestUtils/kotlin}/test/ConsoleTest.kt (96%) delete mode 100644 03_Animal/kotlin/test/ConsoleTest.kt create mode 100644 buildJvm/build_00_utilities/build.gradle create mode 100644 buildJvm/build_00_utilities/gradle.properties 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 diff --git a/buildJvm/build_00_utilities/build.gradle b/buildJvm/build_00_utilities/build.gradle new file mode 100644 index 00000000..72b8b8c4 --- /dev/null +++ b/buildJvm/build_00_utilities/build.gradle @@ -0,0 +1,7 @@ +sourceSets { + test { + java { + srcDirs "../../$testSource" + } + } +} diff --git a/buildJvm/build_00_utilities/gradle.properties b/buildJvm/build_00_utilities/gradle.properties new file mode 100644 index 00000000..2ad9bb1a --- /dev/null +++ b/buildJvm/build_00_utilities/gradle.properties @@ -0,0 +1 @@ +testSource=00_Utilities/jvmTestUtils/kotlin/test diff --git a/buildJvm/build_03_Animal_java/build.gradle b/buildJvm/build_03_Animal_java/build.gradle index bd59f127..cc447526 100644 --- a/buildJvm/build_03_Animal_java/build.gradle +++ b/buildJvm/build_03_Animal_java/build.gradle @@ -14,3 +14,7 @@ sourceSets { application { mainClass = gameMain } + +dependencies { + testImplementation(project(":build_00_utilities").sourceSets.test.output) +} diff --git a/buildJvm/build_03_Animal_kotlin/build.gradle b/buildJvm/build_03_Animal_kotlin/build.gradle index bd59f127..cc447526 100644 --- a/buildJvm/build_03_Animal_kotlin/build.gradle +++ b/buildJvm/build_03_Animal_kotlin/build.gradle @@ -14,3 +14,7 @@ sourceSets { application { mainClass = gameMain } + +dependencies { + testImplementation(project(":build_00_utilities").sourceSets.test.output) +} diff --git a/buildJvm/settings.gradle b/buildJvm/settings.gradle index 9164e5fa..c5c4cbd8 100644 --- a/buildJvm/settings.gradle +++ b/buildJvm/settings.gradle @@ -1,5 +1,7 @@ rootProject.name = 'BasicComputerGames' +include ":build_00_utilities" + include ":build_01_Acey_Ducey_kotlin" include ":build_03_Animal_kotlin" include ":build_53_King_kotlin" From 934b2055b132565b1cfaa9cb715867708fe9e672 Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 30 Jan 2022 00:11:59 +1100 Subject: [PATCH 11/16] 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 49677e5c56e0a9a0d71454e4cd868c308555042a Mon Sep 17 00:00:00 2001 From: Paul Holt Date: Sun, 30 Jan 2022 00:21:25 +1100 Subject: [PATCH 12/16] 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 9476fcd5f45ed599fd793c4f94e4bce78b11ce8d Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 16:30:42 +0100 Subject: [PATCH 13/16] 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 d90db646b193d62903c69e1249b540c3f095860a Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 16:56:53 +0100 Subject: [PATCH 14/16] 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 def0398956dae18fde7e13b1c1c3cb75b352ee29 Mon Sep 17 00:00:00 2001 From: Stefan Waldmann Date: Sat, 29 Jan 2022 17:05:10 +0100 Subject: [PATCH 15/16] 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 c6c56a7f20d7195b47d44fff218d83165cb0ad30 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 16/16] 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