introduce bitloops
This commit is contained in:
@@ -15,9 +15,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.LongConsumer;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
public class BuildClueAndSimpelIndex {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
@@ -1,106 +1,111 @@
|
||||
package puzzle;
|
||||
|
||||
import module java.base;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import puzzle.Masker.Clues;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static precomp.Const9x8.OFF_0_0;
|
||||
import static precomp.Const9x8.OFF_1_1;
|
||||
import static precomp.Const9x8.OFF_1_2;
|
||||
import static precomp.Const9x8.OFF_2_0;
|
||||
import static precomp.Const9x8.OFF_2_1;
|
||||
import static precomp.Const9x8.OFF_2_2;
|
||||
import static precomp.Const9x8.OFF_3_1;
|
||||
import static precomp.Const9x8.OFF_7_7;
|
||||
import static puzzle.SwedishGenerator.STACK_SIZE;
|
||||
|
||||
public class ConnectivityTest {
|
||||
|
||||
@Test
|
||||
void testConnectivityPenalty() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
// 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen)
|
||||
Clues singleComp = Clues.createEmpty();
|
||||
// Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3)
|
||||
// Clue 2: (1,2) Up. Slot: (0,2)
|
||||
// Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2)
|
||||
singleComp.setClueLo(1L << Masker.offset(0, 0), (byte)1);
|
||||
singleComp.setClueLo(1L << Masker.offset(2, 2), (byte)2); // Up van (2,2) naar (1,2), (0,2)
|
||||
|
||||
long fitnessSingle = masker.maskFitness(singleComp, 2);
|
||||
|
||||
// 2. Maak een masker met twee eilandjes van clues
|
||||
Clues twoIslands = Clues.createEmpty();
|
||||
twoIslands.setClueLo(1L << Masker.offset(0, 0), (byte)1);
|
||||
twoIslands.setClueLo(Masker.offset(7, 7) < 64 ? 1L << Masker.offset(7, 7) : 0, (byte)1);
|
||||
// Voor de zekerheid checken we of offset(7,7) in lo of hi zit
|
||||
int off77 = Masker.offset(7, 7);
|
||||
if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte)1);
|
||||
else twoIslands.setClueHi(1L << (off77 - 64), (byte)1);
|
||||
|
||||
long fitnessIslands = masker.maskFitness(twoIslands, 2);
|
||||
|
||||
System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle);
|
||||
System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands);
|
||||
|
||||
assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPhysicalAdjacency() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Twee clues naast elkaar, maar slots kruisen niet.
|
||||
// Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4)
|
||||
// Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4)
|
||||
clues.setClueLo(1L << Masker.offset(1, 1), (byte)1);
|
||||
clues.setClueLo(1L << Masker.offset(2, 1), (byte)1);
|
||||
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
// Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn.
|
||||
System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness);
|
||||
assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent");
|
||||
Clues clues2 = Clues.createEmpty();
|
||||
// Twee clues naast elkaar, maar slots kruisen niet.
|
||||
// Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4)
|
||||
// Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4)
|
||||
clues2.setClueLo(1L << Masker.offset(1, 1), (byte)1);
|
||||
clues2.setClueLo(1L << Masker.offset(3, 1), (byte)1);
|
||||
long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2);
|
||||
// Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn.
|
||||
System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2);
|
||||
assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerClueConnectivity() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ...
|
||||
// Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)...
|
||||
// Ze kruisen op (2,3).
|
||||
clues.setClueLo(1L << Masker.offset(2, 0), (byte)1); // Right
|
||||
clues.setClueLo(1L << Masker.offset(1, 2), (byte)4); // Corner Down
|
||||
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness);
|
||||
|
||||
// Als ze verbonden zijn, is de penalty voor eilandjes 0.
|
||||
// We vergelijken met een island scenario (2 clues die elkaar NIET raken)
|
||||
Clues island = Clues.createEmpty();
|
||||
int offA = Masker.offset(2, 0);
|
||||
if (offA < 64) island.setClueLo(1L << offA, (byte)1);
|
||||
else island.setClueHi(1L << (offA - 64), (byte)1);
|
||||
|
||||
int offB = Masker.offset(7, 7);
|
||||
if (offB < 64) island.setClueLo(1L << offB, (byte)1);
|
||||
else island.setClueHi(1L << (offB - 64), (byte)1);
|
||||
|
||||
long fitnessIsland = masker.maskFitness(island, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland);
|
||||
|
||||
assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConnectivityPenalty() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
// 1. Maak een masker met één component van clues (bijv. 2 clues die elkaar kruisen)
|
||||
Clues singleComp = Clues.createEmpty();
|
||||
// Clue 1: (0,0) Right. Slot: (0,1), (0,2), (0,3)
|
||||
// Clue 2: (1,2) Up. Slot: (0,2)
|
||||
// Ze zijn NIET 8-naburig, maar wel verbonden via slot op (0,2)
|
||||
singleComp.setClueLo(1L << OFF_0_0, (byte) 1);
|
||||
singleComp.setClueLo(1L << OFF_2_2, (byte) 2); // Up van (2,2) naar (1,2), (0,2)
|
||||
|
||||
long fitnessSingle = masker.maskFitness(singleComp, 2);
|
||||
|
||||
// 2. Maak een masker met twee eilandjes van clues
|
||||
Clues twoIslands = Clues.createEmpty();
|
||||
twoIslands.setClueLo(1L << OFF_0_0, (byte) 1);
|
||||
twoIslands.setClueLo(OFF_7_7 < 64 ? 1L << OFF_7_7 : 0, (byte) 1);
|
||||
// Voor de zekerheid checken we of offset(7,7) in lo of hi zit
|
||||
int off77 = OFF_7_7;
|
||||
if (off77 < 64) twoIslands.setClueLo(1L << off77, (byte) 1);
|
||||
else twoIslands.setClueHi(1L << (off77 - 64), (byte) 1);
|
||||
|
||||
long fitnessIslands = masker.maskFitness(twoIslands, 2);
|
||||
|
||||
System.out.println("[DEBUG_LOG] Fitness single component: " + fitnessSingle);
|
||||
System.out.println("[DEBUG_LOG] Fitness two islands: " + fitnessIslands);
|
||||
|
||||
assertTrue(fitnessIslands > fitnessSingle + 10000, "Islands should have much higher penalty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPhysicalAdjacency() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Twee clues naast elkaar, maar slots kruisen niet.
|
||||
// Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4)
|
||||
// Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4)
|
||||
clues.setClueLo(1L << OFF_1_1, (byte) 1);
|
||||
clues.setClueLo(1L << OFF_2_1, (byte) 1);
|
||||
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
// Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn.
|
||||
System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness);
|
||||
assertTrue(fitness > 20000, "Should have island penalty even if physically adjacent");
|
||||
Clues clues2 = Clues.createEmpty();
|
||||
// Twee clues naast elkaar, maar slots kruisen niet.
|
||||
// Clue 1: (1,1) Right. Slot (1,2), (1,3), (1,4)
|
||||
// Clue 2: (2,1) Right. Slot (2,2), (2,3), (2,4)
|
||||
clues2.setClueLo(1L << OFF_1_1, (byte) 1);
|
||||
clues2.setClueLo(1L << OFF_3_1, (byte) 1);
|
||||
long fitness2 = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty()).maskFitness(clues2, 2);
|
||||
// Als 8-naburigheid NIET MEER meetelt, moet de penalty hoog zijn.
|
||||
System.out.println("[DEBUG_LOG] Fitness physically adjacent: " + fitness2);
|
||||
assertTrue(fitness2 > 20000, "Should have island penalty even if physically adjacent");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerClueConnectivity() {
|
||||
Rng rng = new Rng(42);
|
||||
Masker masker = new Masker(rng, new int[STACK_SIZE], Clues.createEmpty());
|
||||
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue A: (2,0) Right. Slot: (2,1), (2,2), (2,3), ...
|
||||
// Clue B: (1,2) Corner Down. Word starts at (1,3) en gaat omlaag: (1,3), (2,3), (3,3)...
|
||||
// Ze kruisen op (2,3).
|
||||
clues.setClueLo(1L << OFF_2_0, (byte) 1); // Right
|
||||
clues.setClueLo(1L << OFF_1_2, (byte) 4); // Corner Down
|
||||
|
||||
long fitness = masker.maskFitness(clues, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness corner clue connected: " + fitness);
|
||||
|
||||
// Als ze verbonden zijn, is de penalty voor eilandjes 0.
|
||||
// We vergelijken met een island scenario (2 clues die elkaar NIET raken)
|
||||
Clues island = Clues.createEmpty();
|
||||
int offA = OFF_2_0;
|
||||
if (offA < 64) island.setClueLo(1L << offA, (byte) 1);
|
||||
else island.setClueHi(1L << (offA - 64), (byte) 1);
|
||||
|
||||
int offB = OFF_7_7;
|
||||
if (offB < 64) island.setClueLo(1L << offB, (byte) 1);
|
||||
else island.setClueHi(1L << (offB - 64), (byte) 1);
|
||||
|
||||
long fitnessIsland = masker.maskFitness(island, 2);
|
||||
System.out.println("[DEBUG_LOG] Fitness island: " + fitnessIsland);
|
||||
|
||||
assertTrue(fitnessIsland > fitness + 20000, "Island should add significant penalty compared to connected corner clue");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,90 @@
|
||||
package puzzle;
|
||||
|
||||
import module java.base;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import puzzle.Masker.Clues;
|
||||
import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.Slotinfo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static precomp.Const9x8.OFF_0_0;
|
||||
import static precomp.Const9x8.OFF_0_1;
|
||||
import static precomp.Const9x8.OFF_1_0;
|
||||
import static precomp.Const9x8.OFF_1_1;
|
||||
|
||||
public class CornerClueTest {
|
||||
@Test
|
||||
void testCornerDownSlot() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue op (0,0), type 4 (Corner Down)
|
||||
int idx = Masker.offset(0, 0);
|
||||
clues.setClueLo(1L << idx, (byte)4);
|
||||
|
||||
assertEquals(4, clues.getDir(idx));
|
||||
|
||||
// Controleer of forEachSlot het slot vindt
|
||||
final boolean[] found = {false};
|
||||
clues.forEachSlot((key, lo, hi) -> {
|
||||
if (Masker.Slot.dir(key) == 4) {
|
||||
found[0] = true;
|
||||
// Woord zou moeten starten op (0,1)
|
||||
int startIdx = Masker.offset(0, 1);
|
||||
assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,1)");
|
||||
// En omlaag gaan
|
||||
int secondIdx = Masker.offset(1, 1);
|
||||
assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,1)");
|
||||
|
||||
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1)
|
||||
assertEquals(8, Masker.Slot.length(lo, hi));
|
||||
}
|
||||
});
|
||||
assertTrue(found[0], "Corner Down slot should be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownExtraction() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
int idx = Masker.offset(0, 0);
|
||||
clues.setClueLo(1L << idx, (byte)4);
|
||||
|
||||
DictEntry[] dict = DictData.DICT.index();
|
||||
Slotinfo[] slots = Masker.slots(clues, dict);
|
||||
|
||||
assertEquals(1, slots.length);
|
||||
assertEquals(4, Masker.Slot.dir(slots[0].key()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownLeftSlot() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
// Clue op (0,1), type 5 (Corner Down Left)
|
||||
// Should result in word starting at (0,0) going down.
|
||||
int idx = Masker.offset(0, 1);
|
||||
clues.setClueLo(1L << idx, (byte)5);
|
||||
|
||||
assertEquals(5, clues.getDir(idx));
|
||||
|
||||
// Controleer of forEachSlot het slot vindt
|
||||
final boolean[] found = {false};
|
||||
clues.forEachSlot((key, lo, hi) -> {
|
||||
if (Masker.Slot.dir(key) == 5) {
|
||||
found[0] = true;
|
||||
// Woord zou moeten starten op (0,0)
|
||||
int startIdx = Masker.offset(0, 0);
|
||||
assertTrue((lo & (1L << startIdx)) != 0, "Slot should start at (0,0)");
|
||||
// En omlaag gaan
|
||||
int secondIdx = Masker.offset(1, 0);
|
||||
assertTrue((lo & (1L << secondIdx)) != 0, "Slot should continue to (1,0)");
|
||||
|
||||
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0)
|
||||
assertEquals(8, Masker.Slot.length(lo, hi));
|
||||
}
|
||||
});
|
||||
assertTrue(found[0], "Corner Down Left slot should be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownLeftExtraction() {
|
||||
Clues clues = Clues.createEmpty();
|
||||
int idx = Masker.offset(0, 1);
|
||||
clues.setClueLo(1L << idx, (byte)5);
|
||||
|
||||
DictEntry[] dict = DictData.DICT.index();
|
||||
Slotinfo[] slots = Masker.slots(clues, dict);
|
||||
|
||||
assertEquals(1, slots.length);
|
||||
assertEquals(5, Masker.Slot.dir(slots[0].key()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownSlot() {
|
||||
var clues = Clues.createEmpty();
|
||||
// Clue op (0,0), type 4 (Corner Down)
|
||||
var idx = OFF_0_0;
|
||||
clues.setClueLo(1L << idx, (byte) 4);
|
||||
|
||||
assertEquals(4, clues.getDir(idx));
|
||||
|
||||
// Controleer of forEachSlot het slot vindt
|
||||
final var found = new boolean[]{ false };
|
||||
clues.forEachSlot((key, lo, hi) -> {
|
||||
if (Masker.Slot.dir(key) == 4) {
|
||||
found[0] = true;
|
||||
// Woord zou moeten starten op (0,1)
|
||||
assertTrue((lo & (1L << OFF_0_1)) != 0, "Slot should start at (0,1)");
|
||||
// En omlaag gaan
|
||||
assertTrue((lo & (1L << OFF_1_1)) != 0, "Slot should continue to (1,1)");
|
||||
|
||||
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 1)
|
||||
assertEquals(8, Masker.Slot.length(lo, hi));
|
||||
}
|
||||
});
|
||||
assertTrue(found[0], "Corner Down slot should be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownExtraction() {
|
||||
var clues = Clues.createEmpty();
|
||||
clues.setClueLo(1L << OFF_0_0, (byte) 4);
|
||||
|
||||
var dict = DictData.DICT.index();
|
||||
var slots = Masker.slots(clues, dict);
|
||||
|
||||
assertEquals(1, slots.length);
|
||||
assertEquals(4, Masker.Slot.dir(slots[0].key()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownLeftSlot() {
|
||||
var clues = Clues.createEmpty();
|
||||
// Clue op (0,1), type 5 (Corner Down Left)
|
||||
// Should result in word starting at (0,0) going down.
|
||||
var idx = OFF_0_1;
|
||||
clues.setClueLo(1L << idx, (byte) 5);
|
||||
|
||||
assertEquals(5, clues.getDir(idx));
|
||||
|
||||
// Controleer of forEachSlot het slot vindt
|
||||
final var found = new boolean[]{ false };
|
||||
clues.forEachSlot((key, lo, hi) -> {
|
||||
if (Masker.Slot.dir(key) == 5) {
|
||||
found[0] = true;
|
||||
// Woord zou moeten starten op (0,0)
|
||||
assertTrue((lo & (1L << OFF_0_0)) != 0, "Slot should start at (0,0)");
|
||||
// En omlaag gaan
|
||||
assertTrue((lo & (1L << OFF_1_0)) != 0, "Slot should continue to (1,0)");
|
||||
|
||||
// Lengte van het slot zou 8 moeten zijn (van rij 0 t/m 7 in kolom 0)
|
||||
assertEquals(8, Masker.Slot.length(lo, hi));
|
||||
}
|
||||
});
|
||||
assertTrue(found[0], "Corner Down Left slot should be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCornerDownLeftExtraction() {
|
||||
var clues = Clues.createEmpty();
|
||||
clues.setClueLo(1L << OFF_0_1, (byte) 5);
|
||||
|
||||
var dict = DictData.DICT.index();
|
||||
var slots = Masker.slots(clues, dict);
|
||||
|
||||
assertEquals(1, slots.length);
|
||||
assertEquals(5, Masker.Slot.dir(slots[0].key()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package puzzle;
|
||||
|
||||
import module java.base;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Gridded;
|
||||
@@ -16,6 +17,10 @@ import puzzle.SwedishGeneratorTest.Idx;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static precomp.Const9x8.OFF_0_1;
|
||||
import static precomp.Const9x8.OFF_0_2;
|
||||
import static precomp.Const9x8.OFF_0_3;
|
||||
import static precomp.Const9x8.OFF_0_4;
|
||||
import static puzzle.Export.Clue.LEFT;
|
||||
import static puzzle.Export.Clue.RIGHT;
|
||||
import static puzzle.SwedishGenerator.C;
|
||||
@@ -24,10 +29,6 @@ import static puzzle.SwedishGenerator.FillStats;
|
||||
import static puzzle.SwedishGenerator.R;
|
||||
import static puzzle.Masker.Slot;
|
||||
import static puzzle.GridBuilder.placeWord;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_3;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_4;
|
||||
import static puzzle.SwedishGeneratorTest.TEST;
|
||||
|
||||
public class ExportFormatTest {
|
||||
@@ -104,20 +105,17 @@ public class ExportFormatTest {
|
||||
assertTrue(row.matches("#+"));
|
||||
}
|
||||
}
|
||||
Path shardKey(long word) {
|
||||
return Path.of("src/main/generated-sources/puzzle").resolve(Lemma.unpackSize(word) + 1 + ".idx");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShardToClue() {
|
||||
for (int length = 2; length <= 8; length++) {
|
||||
for (var length = 2; length <= 8; length++) {
|
||||
val entry = DictData.DICT.index()[length];
|
||||
if (entry == null) continue;
|
||||
val words = entry.words();
|
||||
for (int i = 0; i < Math.min(words.length, 5); i++) {
|
||||
val wordVal = words[i];
|
||||
val word = Lemma.asWord(wordVal, Export.BYTES.get());
|
||||
val clueRec = Meta.lookup(wordVal);
|
||||
for (var i = 0; i < Math.min(words.length, 5); i++) {
|
||||
val wordVal = words[i];
|
||||
val word = Lemma.asWord(wordVal, Export.BYTES.get());
|
||||
val clueRec = Meta.lookup(wordVal);
|
||||
|
||||
assertNotNull(clueRec);
|
||||
assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
|
||||
@@ -130,43 +128,28 @@ public class ExportFormatTest {
|
||||
@Test
|
||||
void testSpecificWords() {
|
||||
// These words are known to be in the CSV and likely in the dictionary
|
||||
String[] testWords = { "EEN", "NAAR", "IEDEREEN" };
|
||||
for (String wStr : testWords) {
|
||||
long w = Lemma.from(wStr);
|
||||
int L = wStr.length();
|
||||
var entry = DictData.DICT.index()[L];
|
||||
if (entry == null) continue;
|
||||
var testWords = new String[]{ "EEN", "NAAR", "IEDEREEN" };
|
||||
for (var wStr : testWords) {
|
||||
var w = Lemma.from(wStr);
|
||||
|
||||
// Find index of word in entry
|
||||
int idx = -1;
|
||||
long[] words = entry.words();
|
||||
for (int i = 0; i < words.length; i++) {
|
||||
if (Lemma.asWord(words[i], Export.BYTES.get()).equals(wStr)) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
val clueRec = Meta.lookup(w);
|
||||
assertNotNull(clueRec);
|
||||
assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
|
||||
// Check some expected complexity values (from CSV head output, column 3)
|
||||
if (wStr.equals("EEN")) {
|
||||
assertEquals(451, clueRec.simpel());
|
||||
assertEquals("een geheel vormend", clueRec.clues()[0]);
|
||||
}
|
||||
if (wStr.equals("NAAR")) {
|
||||
assertEquals(497, clueRec.simpel());
|
||||
assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]);
|
||||
}
|
||||
if (wStr.equals("IEDEREEN")) {
|
||||
assertEquals(501, clueRec.simpel());
|
||||
assertEquals("elke persoon", clueRec.clues()[0]);
|
||||
}
|
||||
|
||||
if (idx != -1) {
|
||||
val clueRec = Meta.lookup(w);
|
||||
assertNotNull(clueRec);
|
||||
assertEquals(wStr, Lemma.asWord(clueRec.w(), Export.BYTES.get()));
|
||||
// Check some expected complexity values (from CSV head output, column 3)
|
||||
if (wStr.equals("EEN")) {
|
||||
assertEquals(451, clueRec.simpel());
|
||||
assertEquals("een geheel vormend", clueRec.clues()[0]);
|
||||
}
|
||||
if (wStr.equals("NAAR")) {
|
||||
assertEquals(497, clueRec.simpel());
|
||||
assertEquals("onaangenaam, vervelend, rot, niet leuk", clueRec.clues()[0]);
|
||||
}
|
||||
if (wStr.equals("IEDEREEN")) {
|
||||
assertEquals(501, clueRec.simpel());
|
||||
assertEquals("elke persoon", clueRec.clues()[0]);
|
||||
}
|
||||
|
||||
assertTrue(clueRec.clues().length > 0);
|
||||
}
|
||||
assertTrue(clueRec.clues().length > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,17 @@ import puzzle.SwedishGenerator.Rng;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static precomp.Const9x8.OFF_0_0;
|
||||
import static precomp.Const9x8.OFF_0_1;
|
||||
import static precomp.Const9x8.OFF_0_2;
|
||||
import static precomp.Const9x8.OFF_1_1;
|
||||
import static precomp.Const9x8.OFF_1_2;
|
||||
import static precomp.Const9x8.OFF_2_1;
|
||||
import static precomp.Const9x8.OFF_2_3;
|
||||
import static puzzle.Export.Clue.DOWN;
|
||||
import static puzzle.Export.Clue.LEFT;
|
||||
import static puzzle.Export.Clue.RIGHT;
|
||||
import static puzzle.Export.Clue.UP;
|
||||
import static puzzle.SwedishGenerator.Dict;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.SwedishGenerator.Slotinfo;
|
||||
import static puzzle.SwedishGenerator.fillMask;
|
||||
@@ -36,13 +42,6 @@ import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1;
|
||||
import static puzzle.SwedishGeneratorTest.LETTER_A;
|
||||
import static puzzle.SwedishGeneratorTest.LETTER_Z;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_0;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_1_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_1_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_2_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_2_3;
|
||||
|
||||
public class MainTest {
|
||||
|
||||
@@ -57,7 +56,6 @@ public class MainTest {
|
||||
this.tries = 1;
|
||||
this.verbose = false;
|
||||
}};
|
||||
static final Dict dict = DictData.DICT;//loadDict(opts.wordsPath);
|
||||
@Test
|
||||
void testExtractSlots() {
|
||||
|
||||
@@ -68,7 +66,7 @@ public class MainTest {
|
||||
val g = grid.grid().g;
|
||||
GridBuilder.placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
|
||||
|
||||
var slots = Masker.extractSlots(clues, dict.index());
|
||||
var slots = Masker.extractSlots(clues, DictData.DICT.index());
|
||||
assertEquals(1, slots.length);
|
||||
var s = slots[0];
|
||||
assertEquals(8, Masker.Slot.length(s.lo(), s.hi()));
|
||||
@@ -184,7 +182,7 @@ public class MainTest {
|
||||
Assertions.assertEquals(20, mask.clueCount());
|
||||
val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
|
||||
Assertions.assertEquals(20, map.size());
|
||||
var slots = Masker.slots(mask.c(), dict.index());
|
||||
var slots = Masker.slots(mask.c(), DictData.DICT.index());
|
||||
// var filled = fillMask(rng, slotInfo, grid, false);
|
||||
// val res = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0));
|
||||
}
|
||||
@@ -200,7 +198,7 @@ public class MainTest {
|
||||
" 1 \n" +
|
||||
" 1 2\n" +
|
||||
"21 22 3");
|
||||
var slotInfo = Masker.slots(mask.c(), dict.index());
|
||||
var slotInfo = Masker.slots(mask.c(), DictData.DICT.index());
|
||||
var grid = Slotinfo.grid(slotInfo);
|
||||
var filled = fillMask(rng, slotInfo, grid);
|
||||
Assertions.assertTrue(filled.ok(), "Puzzle generation failed (not ok)");
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
package puzzle;
|
||||
|
||||
import module java.base;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clued;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static precomp.Const9x8.OFF_0_0;
|
||||
import static precomp.Const9x8.OFF_0_2;
|
||||
import static precomp.Const9x8.OFF_0_3;
|
||||
import static precomp.Const9x8.OFF_0_7;
|
||||
import static precomp.Const9x8.OFF_0_8;
|
||||
import static precomp.Const9x8.OFF_1_1;
|
||||
import static precomp.Const9x8.OFF_2_2;
|
||||
import static puzzle.Masker.Clues;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.Masker.Slot;
|
||||
import static puzzle.SwedishGenerator.Rng;
|
||||
import static puzzle.SwedishGenerator.STACK_SIZE;
|
||||
|
||||
public class MaskerCluesTest {
|
||||
|
||||
@@ -18,7 +27,7 @@ public class MaskerCluesTest {
|
||||
for (int i = 0; i < 200; i++) {
|
||||
for (int j = 19; j < 24; j++) {
|
||||
var clues = masker.randomMask(j);
|
||||
assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
|
||||
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +45,7 @@ public class MaskerCluesTest {
|
||||
simCount++;
|
||||
masker.mutate(clues);
|
||||
sim += orig.similarity(clues);
|
||||
assertTrue(masker.isValid(clues, MIN_LEN), "Mask should be valid for length \n" + new Clued(clues).gridToString());
|
||||
assertTrue(masker.isValid(clues), "Mask should be valid for length \n" + new Clued(clues).gridToString());
|
||||
}
|
||||
}
|
||||
System.out.println("Average similarity: " + sim / simCount);
|
||||
@@ -55,7 +64,7 @@ public class MaskerCluesTest {
|
||||
simCount++;
|
||||
var cross = masker.crossover(clues, clues2);
|
||||
sim += Math.max(cross.similarity(clues), cross.similarity(clues2));
|
||||
assertTrue(masker.isValid(cross, MIN_LEN), "Mask should be valid for length \n" + new Clued(cross).gridToString());
|
||||
assertTrue(masker.isValid(cross), "Mask should be valid for length \n" + new Clued(cross).gridToString());
|
||||
}
|
||||
}
|
||||
System.out.println("Average similarity: " + sim / simCount);
|
||||
@@ -90,16 +99,16 @@ public class MaskerCluesTest {
|
||||
void testIsValid() {
|
||||
Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty());
|
||||
Clues g = Clues.createEmpty();
|
||||
assertTrue(masker.isValid(g, MIN_LEN));
|
||||
assertTrue(masker.isValid(g));
|
||||
|
||||
// Valid clue: Right from (0,0) in 9x8 grid. Length is 8.
|
||||
g.setClueLo(1L << Masker.offset(0, 0), (byte) 1);
|
||||
assertTrue(masker.isValid(g, MIN_LEN));
|
||||
g.setClueLo(1L << OFF_0_0, (byte) 1);
|
||||
assertTrue(masker.isValid(g));
|
||||
|
||||
// Invalid clue: Right from (0,7) in 9x8 grid. Length is 1 (too short if MIN_LEN >= 2).
|
||||
Clues g2 = Clues.createEmpty();
|
||||
g2.setClueLo(1L << Masker.offset(0, 7), (byte) 1);
|
||||
assertFalse(masker.isValid(g2, MIN_LEN));
|
||||
g2.setClueLo(1L << OFF_0_7, (byte) 1);
|
||||
assertFalse(masker.isValid(g2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,22 +116,22 @@ public class MaskerCluesTest {
|
||||
Clues g = Clues.createEmpty();
|
||||
|
||||
// Room for Right clue at (0,0) (length 8)
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1)));
|
||||
|
||||
// No room for Right clue at (0,8) (length 0 < MIN_LEN)
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 8), 1)));
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_8, 1)));
|
||||
|
||||
// Blocked room
|
||||
// Let's place a clue that leaves only 1 cell for another clue.
|
||||
g.setClueLo(1L << Masker.offset(0, 2), (byte) 1);
|
||||
g.setClueLo(1L << OFF_0_2, (byte) 1);
|
||||
// Now Right at (0,0) only has (0,1) available -> length 1 < MIN_LEN (which is 2)
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
assertFalse(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1)));
|
||||
|
||||
// But enough room
|
||||
g.clearClueLo(0L);
|
||||
g.setClueLo(1L << Masker.offset(0, 3), (byte) 1);
|
||||
g.setClueLo(1L << OFF_0_3, (byte) 1);
|
||||
// Now Right at (0,0) has (0,1), (0,2) -> length 2 == MIN_LEN
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(Masker.offset(0, 0), 1)));
|
||||
assertTrue(g.hasRoomForClue(Slot.packSlotKey(OFF_0_0, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,17 +139,17 @@ public class MaskerCluesTest {
|
||||
Clues g = Clues.createEmpty();
|
||||
Masker masker = new Masker(new Rng(42), new int[STACK_SIZE], Clues.createEmpty());
|
||||
// Clue 1: (0,0) Right. Slot cells: (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8)
|
||||
g.setClueLo(1L << Masker.offset(0, 0), (byte) 1);
|
||||
g.setClueLo(1L << OFF_0_0, (byte) 1);
|
||||
|
||||
// Clue 2: (1,2) Up. Slot cells: (0,2)
|
||||
// Intersection is exactly 1 cell (0,2). Valid.
|
||||
g.setClueLo(1L << Masker.offset(2, 2), (byte) 2);
|
||||
assertTrue(masker.isValid(g, MIN_LEN));
|
||||
g.setClueLo(1L << OFF_2_2, (byte) 2);
|
||||
assertTrue(masker.isValid(g));
|
||||
|
||||
// Clue 3: (1,1) Right. Slot cells: (1,2), (1,3), ...
|
||||
// No intersection with Clue 1 or 2. Valid.
|
||||
g.setClueLo(1L << Masker.offset(1, 1), (byte) 1);
|
||||
assertTrue(masker.isValid(g, MIN_LEN));
|
||||
g.setClueLo(1L << OFF_1_1, (byte) 1);
|
||||
assertTrue(masker.isValid(g));
|
||||
|
||||
// Now create a violation: two slots sharing 2 cells.
|
||||
// We can do this with Corner Down and another clue.
|
||||
@@ -149,9 +158,9 @@ public class MaskerCluesTest {
|
||||
// They share MANY cells starting from (0,1).
|
||||
|
||||
Clues g3 = Clues.createEmpty();
|
||||
g3.setClueLo(1L << Masker.offset(0, 0), (byte) 4); // Corner Down
|
||||
g3.setClueLo(1L << Masker.offset(0, 2), (byte) 5); // Corner Down Left
|
||||
assertFalse(masker.isValid(g3, MIN_LEN));
|
||||
g3.setClueLo(1L << OFF_0_0, (byte) 4); // Corner Down
|
||||
g3.setClueLo(1L << OFF_0_2, (byte) 5); // Corner Down Left
|
||||
assertFalse(masker.isValid(g3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -160,11 +169,11 @@ public class MaskerCluesTest {
|
||||
Clues g = Clues.createEmpty();
|
||||
// Dir 6 (x=1, r=1, v=0) is invalid
|
||||
g.setClueLo(1L << 0, (byte) 6);
|
||||
assertFalse(masker.isValid(g,MIN_LEN));
|
||||
assertFalse(masker.isValid(g));
|
||||
|
||||
// Dir 7 (x=1, r=1, v=1) is invalid
|
||||
Clues g2 = Clues.createEmpty();
|
||||
g2.setClueLo(1L << 0, (byte) 7);
|
||||
assertFalse(masker.isValid(g2,MIN_LEN));
|
||||
assertFalse(masker.isValid(g2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package puzzle;
|
||||
import module java.base;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clue;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Masker.Clues;
|
||||
@@ -12,12 +13,14 @@ import puzzle.SwedishGenerator.Slotinfo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static puzzle.SwedishGenerator.fillMask;
|
||||
import static puzzle.dict800.DictData.DICT800;
|
||||
import static puzzle.dict900.DictData.DICT900;
|
||||
|
||||
public class PerformanceTest {
|
||||
|
||||
final DictEntry[] EN = DictData.DICT.index();
|
||||
final DictEntry[] EN = DICT800.index();
|
||||
void main() {
|
||||
testPerformance();
|
||||
testIncrementalComplexity();
|
||||
}
|
||||
@Test
|
||||
void testPerformance() {
|
||||
@@ -80,7 +83,7 @@ public class PerformanceTest {
|
||||
" 1 \n" +
|
||||
"221 22\n";
|
||||
val mask = Clued.parse(maskStr);
|
||||
val allSlots = Masker.slots(mask.c(), EN);
|
||||
val allSlots = Masker.slots(mask.c(), DICT900.index());
|
||||
//mask.toGrid()
|
||||
System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---");
|
||||
System.out.println("[DEBUG_LOG] Full Slot Layout:");
|
||||
@@ -123,7 +126,9 @@ public class PerformanceTest {
|
||||
for (Slotinfo s : slots) s.assign().w = 0;
|
||||
|
||||
val result = fillMask(rng, slots, Slotinfo.grid(slots));
|
||||
if (result.ok()) successCount++;
|
||||
if (result.ok()) {
|
||||
successCount++;
|
||||
}
|
||||
totalNodes += result.nodes();
|
||||
totalBacktracks += result.backtracks();
|
||||
}
|
||||
@@ -132,6 +137,7 @@ public class PerformanceTest {
|
||||
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] %s: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n",
|
||||
label, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration);
|
||||
visualizeSlots(slots);
|
||||
}
|
||||
|
||||
private void visualizeSlots(Slotinfo[] slots) {
|
||||
@@ -141,39 +147,17 @@ public class PerformanceTest {
|
||||
for (int r = 0; r < R; r++) Arrays.fill(display[r], ' ');
|
||||
|
||||
for (Slotinfo slot : slots) {
|
||||
int key = slot.key();
|
||||
int dir = Masker.Slot.dir(key);
|
||||
int clueIdx = Masker.Slot.clueIndex(key);
|
||||
int key = slot.key();
|
||||
Clue dir = Clue.from(Masker.Slot.dir(key));
|
||||
int clueIdx = Masker.Slot.clueIndex(key);
|
||||
|
||||
int cr = Masker.IT[clueIdx].r();
|
||||
int cc = Masker.IT[clueIdx].c();
|
||||
|
||||
// User requested: aAAAA for a four letter to RIGHT clue slot.
|
||||
// SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT
|
||||
char clueChar;
|
||||
char slotChar;
|
||||
switch (dir) {
|
||||
case 1:
|
||||
clueChar = 'a';
|
||||
slotChar = 'A';
|
||||
break; // RIGHT
|
||||
case 0:
|
||||
clueChar = 'b';
|
||||
slotChar = 'B';
|
||||
break; // DOWN
|
||||
case 2:
|
||||
clueChar = 'c';
|
||||
slotChar = 'C';
|
||||
break; // UP
|
||||
case 3:
|
||||
clueChar = 'd';
|
||||
slotChar = 'D';
|
||||
break; // LEFT
|
||||
default:
|
||||
clueChar = '?';
|
||||
slotChar = '?';
|
||||
}
|
||||
|
||||
char clueChar = dir.clueChar;
|
||||
char slotChar = dir.slotChar;
|
||||
display[cr][cc] = clueChar;
|
||||
|
||||
Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).forEach(idx -> {
|
||||
|
||||
@@ -12,6 +12,7 @@ import puzzle.Masker.Clues;
|
||||
import puzzle.Masker.Slot;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static precomp.Const9x8.*;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0;
|
||||
|
||||
@@ -78,31 +79,6 @@ public class SwedishGeneratorTest {
|
||||
static final byte CLUE_UP = 2;
|
||||
static final byte CLUE_LEFT = 3;
|
||||
|
||||
static final int OFF_1_0 = Masker.offset(1, 0);
|
||||
static final int OFF_1_1 = Masker.offset(1, 1);
|
||||
static final int OFF_1_2 = Masker.offset(1, 2);
|
||||
static final int OFF_1_3 = Masker.offset(1, 3);
|
||||
static final int OFF_1_4 = Masker.offset(1, 4);
|
||||
static final int OFF_1_5 = Masker.offset(1, 5);
|
||||
static final int OFF_2_1 = Masker.offset(2, 1);
|
||||
static final int OFF_2_3 = Masker.offset(2, 3);
|
||||
static final int OFF_2_2 = Masker.offset(2, 2);
|
||||
static final int OFF_2_4 = Masker.offset(2, 4);
|
||||
static final int OFF_0_0 = Masker.offset(0, 0);
|
||||
static final int OFF_0_4 = Masker.offset(0, 4);
|
||||
static final int OFF_0_5 = Masker.offset(0, 5);
|
||||
static final int OFF_0_1 = Masker.offset(0, 1);
|
||||
static final int OFF_0_2 = Masker.offset(0, 2);
|
||||
static final int OFF_0_3 = Masker.offset(0, 3);
|
||||
static final int OFF_2_0 = Masker.offset(2, 0);
|
||||
static final int OFF_2_5 = Masker.offset(2, 5);
|
||||
static final int OFF_3_5 = Masker.offset(3, 5);
|
||||
static final int OFF_4_5 = Masker.offset(4, 5);
|
||||
static final int OFF_3_0 = Masker.offset(3, 0);
|
||||
static final int OFF_3_1 = Masker.offset(3, 1);
|
||||
static final int OFF_3_2 = Masker.offset(3, 2);
|
||||
static final int OFF_3_3 = Masker.offset(3, 3);
|
||||
static final int OFF_3_4 = Masker.offset(3, 4);
|
||||
static final byte D_BYTE_2 = CLUE_RIGHT;
|
||||
|
||||
enum Idx {
|
||||
|
||||
Reference in New Issue
Block a user