package puzzle; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import puzzle.SwedishGenerator.*; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; public class SwedishGeneratorTest { @Test void testPatternForSlotAllLetters() { var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C var slot = Slot.from(0 << 8 | 1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; SwedishGenerator.patternForSlot(grid, slot, pattern); assertArrayEquals(new byte[]{ 'A', 'B', 'C' }, pattern); } @Test void testPatternForSlotMixed() { var grid = new Grid(new byte[]{ 65, SwedishGenerator.DASH, 67 }); // A - C var slot = Slot.from(0 << 8 | 1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; SwedishGenerator.patternForSlot(grid, slot, pattern); assertArrayEquals(new byte[]{ 'A', SwedishGenerator.DASH, 'C' }, pattern); } @Test void testPatternForSlotAllDashes() { var grid = new Grid(new byte[]{ SwedishGenerator.DASH, SwedishGenerator.DASH, SwedishGenerator.DASH }); // - - - var slot = Slot.from(0 << 8 | 1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; SwedishGenerator.patternForSlot(grid, slot, pattern); assertArrayEquals(new byte[]{ SwedishGenerator.DASH, SwedishGenerator.DASH, SwedishGenerator.DASH }, pattern); } @Test void testPatternForSlotSingleLetter() { var grid = new Grid(new byte[]{ 65, SwedishGenerator.DASH, SwedishGenerator.DASH }); // A - - var slot = Slot.from(0 << 8 | 1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var pattern = new byte[3]; SwedishGenerator.patternForSlot(grid, slot, pattern); assertArrayEquals(new byte[]{ 'A', SwedishGenerator.DASH, SwedishGenerator.DASH }, 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 = SwedishGenerator.makeEmptyGrid(); grid.setCharAt(0, 0, 'A'); grid.setCharAt(0, 1, '1'); assertEquals('A', grid.getCharAt(0, 0)); assertEquals(1, grid.digitAt(0, 1)); assertTrue(grid.isLetterAt(0, 0)); assertFalse(grid.isDigitAt(0, 0)); assertTrue(grid.isDigitAt(0, 1)); assertFalse(grid.isLetterAt(0, 1)); assertTrue(grid.isLettercell(0, 0)); assertFalse(grid.isLettercell(0, 1)); var copy = grid.deepCopyGrid(); assertEquals('A', copy.getCharAt(0, 0)); copy.setCharAt(0, 0, 'B'); assertEquals('B', copy.getCharAt(0, 0)); assertEquals('A', grid.getCharAt(0, 0)); } @Test void testIntList() { var list = new IntList(); 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 l1 = new Lemma("APPLE", 5, "A fruit"); Assertions.assertArrayEquals("APPLE".getBytes(java.nio.charset.StandardCharsets.US_ASCII), l1.word()); assertEquals(5, l1.word().length); assertEquals(5, l1.simpel()); assertEquals((byte) 'A', l1.byteAt(0)); var l2 = new Lemma("AXE", 2, "A tool"); var dict = new Dict(new Lemma[]{ l1, l2 }); assertEquals(1, dict.lenCounts()[3]); assertEquals(1, dict.lenCounts()[5]); var entry3 = dict.index()[3]; assertEquals(1, entry3.words().size()); Assertions.assertArrayEquals("AXE".getBytes(java.nio.charset.StandardCharsets.US_ASCII), entry3.words().getFirst().word()); // 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() { // key = (r << 8) | (c << 4) | d var key = (2 << 8) | (3 << 4) | 5; long packedPos = 0; // pos 0: (2, 5) packedPos |= (long) Grid.offset(2, 5) << 0; // 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); 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 } @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 res = SwedishGenerator.intersectSorted(buff, a, a.length, b, b.length); assertArrayEquals(new int[]{ 3, 7 }, res); var c = new int[]{ 1, 2, 3 }; var d = new int[]{ 4, 5, 6 }; res = SwedishGenerator.intersectSorted(buff, c, c.length, d, d.length); assertEquals(0, res.length); } @Test void testCandidateInfoForPattern() { var l1 = new Lemma("APPLE", 1, "fruit"); var l2 = new Lemma("APPLY", 1, "verb"); var l3 = new Lemma("BANAN", 1, "fruit"); var dict = new Dict(new Lemma[]{ l1, l2, l3 }); var gen = new SwedishGenerator(); // Pattern "APP--" for length 5 var context = new Context(); context.setPatter(new byte[]{ 'A', 'P', 'P', SwedishGenerator.DASH, SwedishGenerator.DASH }); var info = gen.candidateInfoForPattern(context, dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices()); } @Test void testForEachSlotAndExtractSlots() { var gen = new SwedishGenerator(); var grid = SwedishGenerator.makeEmptyGrid(); // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) // Set '2' (right) at 0,0 grid.setCharAt(0, 0, '2'); // This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2) var slots = gen.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(); var grid = SwedishGenerator.makeEmptyGrid(); 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, lenCounts); assertTrue(f1 >= 1_000_000_000L); // Add a slot grid.setCharAt(0, 0, '2'); var f2 = gen.maskFitness(grid, lenCounts); assertTrue(f2 < f1); } @Test void testGeneticAlgorithmComponents() { var gen = new SwedishGenerator(); var rng = new Rng(42); var g1 = gen.randomMask(rng); assertNotNull(g1); var g2 = gen.mutate(rng, g1); assertNotNull(g2); assertNotSame(g1, g2); var g3 = gen.crossover(rng, g1, g2); assertNotNull(g3); var lenCounts = new int[12]; Arrays.fill(lenCounts, 10); var g4 = gen.hillclimb(rng, g1, lenCounts, 10); assertNotNull(g4); } @Test void testPlaceWord() { var grid = SwedishGenerator.makeEmptyGrid(); // 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", 1, "test"); var undoBuffer = new int[10]; // 1. Successful placement in empty grid int placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0); assertEquals(1, placed); assertEquals('A', grid.getCharAt(0, 0)); assertEquals('B', grid.getCharAt(0, 1)); assertEquals('C', grid.getCharAt(0, 2)); assertEquals(0b111L, undoBuffer[0]); // 2. Successful placement with partial overlap (same characters) placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 1); assertEquals(1, placed); assertEquals(0L, undoBuffer[1]); // 0 new characters placed // 3. Conflict: place "ABD" where "ABC" is var w2 = new Lemma("ABD", 1, "conflict"); placed = SwedishGenerator.placeWord(grid, s, w2, undoBuffer, 2); assertEquals(-1, placed); // Verify grid is unchanged (still "ABC") assertEquals('A', grid.getCharAt(0, 0)); assertEquals('B', grid.getCharAt(0, 1)); assertEquals('C', grid.getCharAt(0, 2)); // 4. Partial placement then conflict (rollback) grid = SwedishGenerator.makeEmptyGrid(); grid.setCharAt(0, 2, 'X'); // Conflict at the end placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 3); assertEquals(-1, placed); // Verify grid is still empty (except for 'X') assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 0)); assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1)); assertEquals('X', grid.getCharAt(0, 2)); } @Test void testBacktrackingHelpers() { var grid = SwedishGenerator.makeEmptyGrid(); // 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", 1, "A to Z"); var undoBuffer = new int[10]; var placed = SwedishGenerator.placeWord(grid, s, w, undoBuffer, 0); assertEquals(1, placed); assertEquals('A', grid.getCharAt(0, 1)); assertEquals('Z', grid.getCharAt(0, 2)); assertEquals(0b11L, undoBuffer[0]); SwedishGenerator.undoPlace(grid, s, undoBuffer[0]); assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1)); assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 2)); } }