initial commit

This commit is contained in:
2025-03-04 08:22:58 +01:00
commit a5c1d9274e
12 changed files with 424 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

24
pom.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.spijkerman.ivo</groupId>
<artifactId>binaire</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>22</maven.compiler.source>
<maven.compiler.target>22</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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<KeyEvent>();
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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}