From a5c1d9274ebc98950a28b5769f414e9d2bce9404 Mon Sep 17 00:00:00 2001 From: Ivo Spijkerman Date: Tue, 4 Mar 2025 08:22:58 +0100 Subject: [PATCH] initial commit --- .gitignore | 1 + pom.xml | 24 +++ .../java/com/spijkerman/ivo/binaire/Game.java | 138 ++++++++++++++++++ .../java/com/spijkerman/ivo/binaire/Rule.java | 15 ++ .../com/spijkerman/ivo/binaire/Value.java | 29 ++++ .../ivo/binaire/rules/DoubleRule.java | 30 ++++ .../ivo/binaire/rules/EnsureEqualValues.java | 27 ++++ .../ivo/binaire/rules/EnsureNoTriplets.java | 26 ++++ .../ivo/binaire/rules/HalfRule.java | 43 ++++++ .../spijkerman/ivo/binaire/rules/Rules.java | 24 +++ .../ivo/binaire/rules/StraightRule.java | 42 ++++++ .../ivo/binaire/rules/TripleRule.java | 25 ++++ 12 files changed, 424 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/spijkerman/ivo/binaire/Game.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/Rule.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/Value.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/DoubleRule.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/EnsureEqualValues.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/EnsureNoTriplets.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/HalfRule.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/Rules.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/StraightRule.java create mode 100644 src/main/java/com/spijkerman/ivo/binaire/rules/TripleRule.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..944aa0e --- /dev/null +++ b/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + nl.spijkerman.ivo + binaire + 1.0-SNAPSHOT + + + 22 + 22 + UTF-8 + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + diff --git a/src/main/java/com/spijkerman/ivo/binaire/Game.java b/src/main/java/com/spijkerman/ivo/binaire/Game.java new file mode 100644 index 0000000..22d1fab --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/Game.java @@ -0,0 +1,138 @@ +package com.spijkerman.ivo.binaire; + + +import com.spijkerman.ivo.binaire.rules.Rules; + +import javax.swing.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.Scanner; +import java.util.concurrent.CompletableFuture; + +public class Game { + + private final JFrame keyFrame; + private final Value[][] board; + private int cursorX, cursorY; + + + public static void main(String[] args) { + var scanner = new Scanner(System.in); + System.out.println("Enter width: "); + var width = scanner.nextInt(); + System.out.println("Enter height: "); + var height = scanner.nextInt(); + new Game(width, height); + } + + public Game(int width, int height) { + try { + this.board = new Value[width][height]; + this.keyFrame = createJFrame(); + var rules = new Rules(); + while (!isDone()) { + draw(); + prompt(); + if (rules.applyUntilDone(board)) { + moveNextEmpty(); + } + } + cursorY = -1; // Move out of sight to show entire puzzle + System.out.println("Congratulations!"); + draw(); + } catch (Exception e) { + cursorY = -1; + draw(); + e.printStackTrace(); + System.exit(1); + throw new RuntimeException("Unreachable code"); + } + } + + private void draw() { + var borderLine = "+" + "-".repeat(board.length) + "+\n"; + var field = new StringBuilder(borderLine); + for (var y = 0; y < board[0].length; y++) { + field.append('|'); + for (var x = 0; x < board.length; x++) { + if (x == cursorX && y == cursorY) { + field.append('_'); + } else if (board[x][y] == null) { + field.append(' '); + } else { + field.append(switch (board[x][y]) { + case ONE_USER -> '1'; + case ONE_SYSTEM -> 'I'; + case ZERO_USER -> '0'; + case ZERO_SYSTEM -> 'O'; + }); + } + } + field.append("|\n"); + } + field.append(borderLine); + System.out.println(field); + } + + private void prompt() { + System.out.printf("Press arrow key, 0, 1, q or c (%d,%d):%n", cursorX + 1, cursorY + 1); + var eventHolder = new CompletableFuture(); + var keyAdapter = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + super.keyPressed(e); + eventHolder.complete(e); + } + }; + keyFrame.addKeyListener(keyAdapter); + switch (eventHolder.join().getKeyCode()) { + case 37 -> cursorX = Math.max(0, cursorX - 1); + case 38 -> cursorY = Math.max(0, cursorY - 1); + case 39 -> cursorX = Math.min(board.length - 1, cursorX + 1); + case 40 -> cursorY = Math.min(board[0].length - 1, cursorY + 1); + case 48 -> moveNextEmpty(board[cursorX][cursorY] = Value.ZERO_USER); + case 49 -> moveNextEmpty(board[cursorX][cursorY] = Value.ONE_USER); + case 67 -> moveNextEmpty(board[cursorX][cursorY] = null); + case 81 -> System.exit(1); + } + keyFrame.removeKeyListener(keyAdapter); + } + + private static JFrame createJFrame() { + var frame = new JFrame(); + frame.setUndecorated(true); + frame.setSize(1, 1); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setOpacity(0); + frame.setLocation(-100, -100); + frame.setFocusable(true); + frame.setVisible(true); + return frame; + } + + private void moveNextEmpty(Value... ignore) { + if (isDone()) { + return; + } + while (board[cursorX][cursorY] != null) { + if (++cursorX >= board.length) { + cursorX = 0; + if (++cursorY >= board[0].length) { + cursorY = 0; + } + } + } + } + + private boolean isDone() { + for (var row : board) { + for (var cell : row) { + if (cell == null) { + return false; + } + } + } + return true; + } + +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/Rule.java b/src/main/java/com/spijkerman/ivo/binaire/Rule.java new file mode 100644 index 0000000..a7fa662 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/Rule.java @@ -0,0 +1,15 @@ +package com.spijkerman.ivo.binaire; + +public interface Rule { + + default boolean applyUntilDone(Value[][] board) { + var changed = false; + while (apply(board)) { + changed = true; + } + return changed; + } + + boolean apply(Value[][] board); + +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/Value.java b/src/main/java/com/spijkerman/ivo/binaire/Value.java new file mode 100644 index 0000000..e672b7a --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/Value.java @@ -0,0 +1,29 @@ +package com.spijkerman.ivo.binaire; + +public enum Value { + ONE_USER(1), + ONE_SYSTEM(1), + ZERO_USER(0), + ZERO_SYSTEM(0); + + public final int val; + + Value(int val) { + this.val = val; + } + + public boolean isSame(Value that) { + if (that == null) { + return false; + } + return this.val == that.val; + } + + public Value system() { + return val == 0 ? ZERO_SYSTEM : ONE_SYSTEM; + } + + public Value reverseSystem() { + return val == 0 ? ONE_SYSTEM : ZERO_SYSTEM; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/DoubleRule.java b/src/main/java/com/spijkerman/ivo/binaire/rules/DoubleRule.java new file mode 100644 index 0000000..d9140e9 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/DoubleRule.java @@ -0,0 +1,30 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Value; + +public class DoubleRule extends StraightRule { + @Override + Value[] apply(Value[] line) { + for (int i = 0; i < line.length - 1; i++) { + var curr = line[i]; + if (curr == null) { + continue; + } + var same = curr.isSame(line[i + 1]); + if (!same) { + continue; + } + var before = i - 1; + if (before >= 0 && line[before] == null) { + line[before] = curr.reverseSystem(); + return line; + } + var after = i + 2; + if (after < line.length && line[after] == null) { + line[after] = curr.reverseSystem(); + return line; + } + } + return null; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureEqualValues.java b/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureEqualValues.java new file mode 100644 index 0000000..21c7350 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureEqualValues.java @@ -0,0 +1,27 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Value; + +import java.util.Arrays; + +public class EnsureEqualValues extends StraightRule { + @Override + Value[] apply(Value[] line) { + if (line.length % 2 != 0) { + throw new IllegalArgumentException(); + } + var half = line.length / 2; + var zeroes = 0; + var ones = 0; + for (var cell : line) { + if (cell == null) { + continue; + } else if (cell.val == 0 && (++zeroes > half)) { + throw new IllegalStateException(Arrays.toString(line) + " has too many zeroes"); + } else if (cell.val == 1 && (++ones > half)){ + throw new IllegalStateException(Arrays.toString(line) + " has too many ones"); + } + } + return null; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureNoTriplets.java b/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureNoTriplets.java new file mode 100644 index 0000000..3788e3a --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/EnsureNoTriplets.java @@ -0,0 +1,26 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Value; + +import java.util.Arrays; + +public class EnsureNoTriplets extends StraightRule { + @Override + Value[] apply(Value[] line) { + for (int i = 2; i < line.length; i++) { + var left = line[i-2]; + if (left == null) { + continue; + } + var middle = line[i-1]; + if (!left.isSame(middle)) { + continue; + } + var right = line[i]; + if (left.isSame(right)) { + throw new IllegalStateException(Arrays.toString(line) + " contains a triplet of " + left.val); + } + } + return null; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/HalfRule.java b/src/main/java/com/spijkerman/ivo/binaire/rules/HalfRule.java new file mode 100644 index 0000000..92d0ba4 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/HalfRule.java @@ -0,0 +1,43 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Value; + +public class HalfRule extends StraightRule { + + @Override + Value[] apply(Value[] line) { + if (line.length % 2 != 0) { + throw new IllegalArgumentException(); + } + var half = line.length / 2; + var ones = 0; + var zeroes = 0; + for (var cell : line) { + if (cell == null) { + continue; + } else if (cell.val == 0) { + zeroes++; + } else { + ones++; + } + } + if (zeroes == ones) { + return null; + } + final Value toFill; + if (ones == half) { + toFill = Value.ZERO_SYSTEM; + } else if (zeroes == half) { + toFill = Value.ONE_SYSTEM; + } else { + return null; + } + for (var i = 0; i < line.length; i++) { + if (line[i] == null) { + line[i] = toFill; + } + } + System.err.println("Applied half rule!"); + return line; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/Rules.java b/src/main/java/com/spijkerman/ivo/binaire/rules/Rules.java new file mode 100644 index 0000000..fdac405 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/Rules.java @@ -0,0 +1,24 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Rule; +import com.spijkerman.ivo.binaire.Value; + +public class Rules implements Rule { + + private static final Rule[] rules = { + new DoubleRule(), + new TripleRule(), + new HalfRule(), + new EnsureEqualValues(), + new EnsureNoTriplets() + }; + + @Override + public boolean apply(Value[][] board) { + var changed = false; + for (var rule : rules) { + changed |= rule.apply(board); + } + return changed; + } +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/StraightRule.java b/src/main/java/com/spijkerman/ivo/binaire/rules/StraightRule.java new file mode 100644 index 0000000..acc576a --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/StraightRule.java @@ -0,0 +1,42 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Rule; +import com.spijkerman.ivo.binaire.Value; + +public abstract class StraightRule implements Rule { + + @Override + public boolean apply(Value[][] board) { + var width = board.length; + var height = board[0].length; + var max = Math.max(width, height); + + for (var i = 0; i < max; i++) { + if (i < width) { + var column = new Value[height]; + System.arraycopy(board[i], 0, column, 0, height); + column = apply(column); + if (column != null) { + System.arraycopy(column, 0, board[i], 0, height); + return true; + } + } + if (i < height) { + var row = new Value[width]; + for (int j = 0; j < width; j++) { + row[j] = board[j][i]; + } + row = apply(row); + if (row != null) { + for (int j = 0; j < width; j++) { + board[j][i] = row[j]; + } + return true; + } + } + } + return false; + } + + abstract Value[] apply(Value[] line); +} diff --git a/src/main/java/com/spijkerman/ivo/binaire/rules/TripleRule.java b/src/main/java/com/spijkerman/ivo/binaire/rules/TripleRule.java new file mode 100644 index 0000000..9033f38 --- /dev/null +++ b/src/main/java/com/spijkerman/ivo/binaire/rules/TripleRule.java @@ -0,0 +1,25 @@ +package com.spijkerman.ivo.binaire.rules; + +import com.spijkerman.ivo.binaire.Value; + +public class TripleRule extends StraightRule { + @Override + Value[] apply(Value[] line) { + for (int i = 0; i < line.length - 2; i ++) { + var left = line[i]; + if (left == null) { + continue; + } + var same = left.isSame(line[i + 2]); + if (!same) { + continue; + } + var middle = i + 1; + if (line[middle] == null) { + line[middle] = left.reverseSystem(); + return line; + } + } + return null; + } +}