package puzzle; import module java.base; import lombok.val; import org.junit.jupiter.api.Test; import puzzle.Export.Clued; import puzzle.Export.Gridded; import puzzle.Export.Placed; import puzzle.Export.PuzzleResult; import puzzle.Export.Rewards; import puzzle.SwedishGenerator.Assign; import puzzle.SwedishGenerator.FillResult; import puzzle.SwedishGenerator.Lemma; import puzzle.SwedishGenerator.Slotinfo; 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 puzzle.Export.Clue.LEFT; import static puzzle.Export.Clue.RIGHT; import static puzzle.SwedishGenerator.C; import static puzzle.Masker.Clues; import static puzzle.SwedishGenerator.FillStats; import static puzzle.SwedishGenerator.R; import static puzzle.Masker.Slot; import static puzzle.GridBuilder.placeWord; import static puzzle.SwedishGenerator.THRESS; 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 { @Test void testExportFormatFromFilled() { val clues = Clues.createEmpty(); // Place a RIGHT clue at (0,0) clues.setClueLo(Idx.IDX_0_0.lo, RIGHT.dir); // This creates a slot starting at (0,1) // Terminate the slot at (0,5) with another digit to avoid it extending to MAX_WORD_LENGTH clues.setClueLo(Idx.IDX_0_5.lo, LEFT.dir); var grid = new Gridded(clues); // key = (cellIndex << 2) | (direction) var key = Slot.packSlotKey(0, RIGHT.dir); var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST)); var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{ new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null, 0) }, fillResult, 0); var rewards = new Rewards(10, 5, 1); var exported = puzzleResult.exportFormatFromFilled(2, rewards); assertNotNull(exported); assertEquals(2, exported.difficulty()); assertEquals(rewards, exported.rewards()); // Check words assertEquals(1, exported.words().length); var w = exported.words()[0]; assertEquals("TEST", w.word()); assertEquals(Placed.HORIZONTAL, w.direction()); // The bounding box should include (0,0) for the arrow and (0,1)-(0,4) for the word. // minR=0, maxR=0, minC=0, maxC=4 // startRow = 0 - minR = 0 // startCol = 1 - minC = 1 assertEquals(0, w.startRow()); assertEquals(1, w.startCol()); assertEquals(0, w.arrowRow()); assertEquals(0, w.arrowCol()); // Check gridv2 // It should be 1 row, containing "2TEST" -> but letters are mapped, digits are not explicitly in letterAt. // Wait, look at exportFormatFromFilled logic: // row.append(letterAt.getOrDefault(pack(r, c), '#')); // letterAt only contains letters from placed words. // arrow cells are NOT in letterAt unless they are also part of a word (unlikely). // So (0,0) should be '#' assertEquals(1, exported.grid().length); assertEquals("#TEST", exported.grid()[0]); } @Test void testExportFormatEmpty() { var grid = SwedishGeneratorTest.createEmpty(); val clues = Clues.createEmpty(); var fillResult = new FillResult(true, 0, 0, 0, 0, new FillStats()); var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid, clues), new Slotinfo[0], fillResult, 0); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); assertNotNull(exported); assertEquals(0, exported.words().length); // Should return full grid with '#' assertEquals(R, exported.grid().length); for (var row : exported.grid()) { assertEquals(C, row.length()); 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++) { 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 assigned = new Assign(wordVal); val shard = shardKey(assigned.w); val clueRec = Meta.readRecord(shard, i); assertNotNull(clueRec); assertEquals(word, Lemma.asWord(clueRec.w(), Export.BYTES.get())); assertTrue(clueRec.simpel() >= 0); assertTrue(clueRec.clues().length > 0); } } } @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; // 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; } } if (idx != -1) { val shard = shardKey(w); val clueRec = Meta.readRecord(shard, idx); 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("het getal 1", clueRec.clues()[0]); } if (wStr.equals("NAAR")) { assertEquals(497, clueRec.simpel()); assertEquals("in de richting van", clueRec.clues()[0]); } if (wStr.equals("IEDEREEN")) { assertEquals(501, clueRec.simpel()); assertEquals("elke persoon", clueRec.clues()[0]); } assertTrue(clueRec.clues().length > 0); } } } }