package puzzle; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.Export.IntListDTO; import java.nio.charset.StandardCharsets; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; import static puzzle.SwedishGenerator.*; public class SwedishGeneratorTest { static final byte D_BYTE_2 = 50; @Test void testPatternForSlotAllLetters() { var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C var slot = Slot.from(18, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); long pattern = patternForSlot(grid, slot); assertEquals(1 | (2 << 5) | (3 << 10), pattern); } @Test void testPatternForSlotMixed() { var grid = new Grid(new byte[]{ 65, DASH, 67 }); // A - C var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); long pattern = patternForSlot(grid, slot); assertEquals(1 | (0 << 5) | (3 << 10), pattern); } @Test void testPatternForSlotAllDashes() { var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); long pattern = patternForSlot(grid, slot); assertEquals(0, pattern); } @Test void testPatternForSlotSingleLetter() { var grid = new Grid(new byte[]{ 65, DASH, DASH }); // A - - var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); long pattern = patternForSlot(grid, slot); assertEquals(1, pattern); } @Test void testRng() { var rng = new Rng(123); var val1 = rng.nextU32(); var val2 = rng.nextU32(); assertNotEquals(val1, val2); var rng2 = new Rng(123); assertEquals(val1, rng2.nextU32()); for (var i = 0; i < 100; i++) { var r = rng.randint(5, 10); assertTrue(r >= 5 && r <= 10); var f = rng.nextFloat(); assertTrue(f >= 0.0 && f <= 1.0); } } @Test void testGrid() { var grid = Grid.createEmpty(); grid.setLetter(0, (byte) 'A'); grid.setClue(Grid.offset(0, 1), (byte) '1'); assertEquals('A', grid.byteAt(0)); assertEquals(1, grid.digitAt(Grid.offset(0, 1))); assertTrue(grid.isLetterAt(0)); assertFalse(grid.isClue(0)); assertTrue(grid.isClue(Grid.offset(0, 1))); assertFalse(grid.isLetterAt(Grid.offset(0, 1))); assertTrue(grid.isLetterAt(0)); assertFalse(grid.isLetterAt(Grid.offset(0, 1))); var copy = grid.deepCopyGrid(); assertEquals('A', copy.byteAt(0)); copy.setLetter(0, (byte) 'B'); assertEquals('B', copy.byteAt(0)); assertEquals('A', grid.byteAt(0)); } @Test void testIntList() { var list = new IntListDTO(); assertEquals(0, list.size()); for (var i = 0; i < 10; i++) { list.add(i); } assertEquals(10, list.size()); assertEquals(0, list.data()[0]); assertEquals(9, list.data()[9]); } @Test void testLemmaAndDict() { var l2a = new Lemma("IN"); var l4a = new Lemma("INER"); var l6a = new Lemma("INEREN"); var l7a = new Lemma("INERENA"); var l8a = new Lemma("INERENAE"); var l1 = new Lemma("APPLE"); Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(l1.word())); assertEquals(5, Lemma.length(l1.word())); assertEquals((byte) 'A', l1.byteAt(0)); assertEquals(1, l1.intAt(0)); var l2 = new Lemma("AXE"); var dict = new Dict(new Lemma[]{ l1, l2, l2a, l4a, l6a, l7a, l8a }); assertEquals(1, dict.index()[3].words().length); assertEquals(1, dict.index()[5].words().length); var entry3 = dict.index()[3]; assertEquals(1, entry3.words().length); assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(entry3.words()[0])); // Check pos indexing // AXE: A at 0, X at 1, E at 2 /* assertTrue(entry3.pos()[0][0].size() > 0); assertTrue(entry3.pos()[1]['X' - 'A'].size() > 0); assertTrue(entry3.pos()[2]['E' - 'A'].size() > 0);*/ } @Test void testSlot() { System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR); // key = (r << 8) | (c << 4) | d var offset = Grid.offset(2, 3); System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset); var key = (offset << Slot.BIT_FOR_DIR) | 5; System.out.println("[DEBUG_LOG] key = " + key); long packedPos = 0; // pos 0: (2, 5) packedPos |= Grid.offset(2, 5); // pos 1: (3, 5) packedPos |= (long) Grid.offset(3, 5) << 7; // pos 2: (4, 5) packedPos |= (long) Grid.offset(4, 5) << 14; var s = Slot.from(key, packedPos, 3); System.out.println("[DEBUG_LOG] s.dir() = " + s.dir()); assertEquals(2, s.clueR()); assertEquals(3, s.clueC()); assertEquals(5, s.dir()); assertFalse(s.horiz()); assertEquals(2, Grid.r(s.pos(0))); assertEquals(3, Grid.r(s.pos(1))); assertEquals(4, Grid.r(s.pos(2))); assertEquals(5, Grid.c(s.pos(0))); assertEquals(5, Grid.c(s.pos(1))); assertEquals(5, Grid.c(s.pos(2))); assertTrue(Slot.horiz(2)); // right assertFalse(Slot.horiz(3)); // down } static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) { if (aLen == 0 || bLen == 0) return 0; if (aLen < bLen >>> 4) { int k = 0; for (int i = 0; i < aLen; i++) { int x = a[i]; if (Arrays.binarySearch(b, 0, bLen, x) >= 0) out[k++] = x; } return k; } if (bLen < aLen >>> 4) { int k = 0; for (int i = 0; i < bLen; i++) { int y = b[i]; if (Arrays.binarySearch(a, 0, aLen, y) >= 0) out[k++] = y; } return k; } int i = 0, j = 0, k = 0, x, y; while (i < aLen && j < bLen) { x = a[i]; y = b[j]; if (x == y) { out[k++] = x; i++; j++; } else if (x < y) i++; else j++; } return k; } @Test void testIntersectSorted() { var buff = new int[10]; var a = new int[]{ 1, 3, 5, 7, 9 }; var b = new int[]{ 2, 3, 6, 7, 10 }; var count = intersectSorted(a, a.length, b, b.length, buff); assertEquals(2, count); assertEquals(3, buff[0]); assertEquals(7, buff[1]); var c = new int[]{ 1, 2, 3 }; var d = new int[]{ 4, 5, 6 }; count = intersectSorted(c, c.length, d, d.length, buff); assertEquals(0, count); } @Test void testCandidateInfoForPattern() { var l0 = new Lemma("IN"); var l3a = new Lemma("INE"); var l4a = new Lemma("INER"); var l6a = new Lemma("INEREN"); var l7a = new Lemma("INERENA"); var l8a = new Lemma("INERENAE"); var l1 = new Lemma("APPLE"); var l2 = new Lemma("APPLY"); var l3 = new Lemma("BANAN"); var dict = new Dict(new Lemma[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); // Pattern "APP--" for length 5 var context = new Context(); context.setPattern(Lemma.pack(new byte[]{ 'A', 'P', 'P', DASH, DASH })); var info = candidateInfoForPattern(context, dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices()); } @Test void testForEachSlotAndExtractSlots() { var gen = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) // Set '2' (right) at 0,0 grid.setClue(0, (byte) '2'); // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) var slots = extractSlots(grid); // Depending on MAX_WORD_LENGTH and grid size. // In 3x3, if we have '2' at 0,0, rr=0, cc=1. // while loop: // 1. rr=0, cc=1, n=0 -> packedRs |= 0, packedCs |= 1, n=1, rr=0, cc=2 // 2. rr=0, cc=2, n=1 -> packedRs |= 0, packedCs |= 2<<4, n=2, rr=0, cc=3 (out) // result: Slot with len 2. assertEquals(1, slots.size()); var s = slots.getFirst(); // MAX_WORD_LENGTH = Math.min(W, H). In tests with -DPUZZLE_ROWS=3 -DPUZZLE_COLS=3, it should be 3. // However, the test run might be using default Config values if not properly overridden in the test environment. // If Actual was 8, it means MAX_WORD_LENGTH was at least 8. assertTrue(s.len() >= 2); assertEquals(0, s.clueR()); assertEquals(0, s.clueC()); assertEquals(2, s.dir()); } @Test void testMaskFitnessBasic() { var gen = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); var lenCounts = new int[12]; lenCounts[2] = 10; lenCounts[8] = 10; // In case MAX_WORD_LENGTH is 8 // Empty grid should have high penalty (no slots) var f1 = gen.maskFitness(grid); assertTrue(f1 >= 1_000_000_000L); // Add a slot //var dbyte = OFFSETS[2].dbyte(); grid.setClue(0, D_BYTE_2); var f2 = gen.maskFitness(grid); assertTrue(f2 < f1); } @Test void testGeneticAlgorithmComponents() { var rng = new Rng(42); var gen = new SwedishGenerator(rng); var g1 = gen.randomMask(); assertNotNull(g1); var g2 = gen.mutate(g1); assertNotNull(g2); assertNotSame(g1, g2); var g3 = gen.crossover(g1, g2); assertNotNull(g3); var lenCounts = new int[12]; Arrays.fill(lenCounts, 10); var g4 = gen.hillclimb(g1, 10); assertNotNull(g4); } @Test void testPlaceWord() { var grid = Grid.createEmpty(); // Slot at (0,0) length 3, horizontal (right) // key = (r << 8) | (c << 4) | d. Here we just need a valid slot for placeWord. // r(i) and c(i) are used by placeWord. var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14); var s = Slot.from(0, packedPos, 3); var w1 = new Lemma("ABC").word(); var undoBuffer = new int[10]; // 1. Successful placement in empty grid assertTrue(placeWord(grid, s, w1, undoBuffer, 0)); assertEquals('A', grid.byteAt(0)); assertEquals('B', grid.byteAt(Grid.offset(0, 1))); assertEquals('C', grid.byteAt(Grid.offset(0, 2))); assertEquals(0b111L, undoBuffer[0]); // 2. Successful placement with partial overlap (same characters) assertTrue(placeWord(grid, s, w1, undoBuffer, 1)); assertEquals(0L, undoBuffer[1]); // 0 new characters placed // 3. Conflict: place "ABD" where "ABC" is var w2 = new Lemma("ABD").word(); assertFalse(placeWord(grid, s, w2, undoBuffer, 2)); // Verify grid is unchanged (still "ABC") assertEquals('A', grid.byteAt(Grid.offset(0, 0))); assertEquals('B', grid.byteAt(Grid.offset(0, 1))); assertEquals('C', grid.byteAt(Grid.offset(0, 2))); // 4. Partial placement then conflict (rollback) grid = Grid.createEmpty(); grid.setLetter(Grid.offset(0, 2), (byte) 'X'); // Conflict at the end assertFalse(placeWord(grid, s, w1, undoBuffer, 3)); // Verify grid is still empty (except for 'X') assertEquals(DASH, grid.byteAt(Grid.offset(0, 0))); assertEquals(DASH, grid.byteAt(Grid.offset(0, 1))); assertEquals('X', grid.byteAt(Grid.offset(0, 2))); } @Test void testBacktrackingHelpers() { var grid = Grid.createEmpty(); // Slot at 0,1 length 2 var packedPos = ((long) Grid.offset(0, 1)) | (((long) Grid.offset(0, 2)) << 7); var s = Slot.from((0 << 8) | (1 << 4) | 2, packedPos, 2); var w = new Lemma("AZ").word(); var undoBuffer = new int[10]; var placed = placeWord(grid, s, w, undoBuffer, 0); assertTrue(placed); assertEquals('A', grid.byteAt(Grid.offset(0, 1))); assertEquals('Z', grid.byteAt(Grid.offset(0, 2))); assertEquals(0b11L, undoBuffer[0]); s.undoPlace(grid, undoBuffer[0]); assertEquals(DASH, grid.byteAt(Grid.offset(0, 1))); assertEquals(DASH, grid.byteAt(Grid.offset(0, 2))); } }