Skip to content

Commit

Permalink
Add parser for Guardian puzzles.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpd236 committed Oct 29, 2024
1 parent 198bd80 commit da2bb70
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.jeffpdavidson.kotwords.formats

import com.jeffpdavidson.kotwords.formats.json.GuardianJson
import com.jeffpdavidson.kotwords.formats.json.JsonSerializer
import com.jeffpdavidson.kotwords.model.Crossword
import com.jeffpdavidson.kotwords.model.Puzzle

class Guardian(
private val json: String,
private val copyright: String = "",
) : DelegatingPuzzleable() {
override suspend fun getPuzzleable(): Puzzleable {
val data = JsonSerializer.fromJson<GuardianJson.Data>(json)
val cells = mutableMapOf<Pair<Int, Int>, Puzzle.Cell>()
val acrossClues = mutableMapOf<Int, String>()
val downClues = mutableMapOf<Int, String>()
data.entries.forEach { entry ->
val isAcross = entry.direction == "across"
val clues = if (isAcross) acrossClues else downClues
clues[entry.number] = entry.clue
repeat(entry.length) { i ->
val x = entry.position.x + if (isAcross) i else 0
val y = entry.position.y + if (!isAcross) i else 0
val solution = if (entry.solution.isNotEmpty()) "${entry.solution[i]}" else ""
val existingCell = cells[x to y]
require(existingCell == null || existingCell.solution == solution) {
"Solution mismatch in cell ($x, $y)"
}
cells[x to y] = Puzzle.Cell(
number = if (i == 0) "${entry.number}" else "",
solution = solution,
)
}
}
val grid = (0 until data.dimensions.rows).map { y ->
(0 until data.dimensions.cols).map { x ->
val cell = cells[x to y]
if (cell != null) {
cell
} else {
Puzzle.Cell(cellType = Puzzle.CellType.BLOCK)
}
}
}
return Crossword(
title = data.name,
creator = data.creator.name,
copyright = copyright,
description = data.instructions,
grid = grid,
acrossClues = acrossClues,
downClues = downClues,
hasHtmlClues = true,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.jeffpdavidson.kotwords.formats.json

import kotlinx.serialization.Serializable

internal object GuardianJson {

@Serializable
internal data class Entry(
val number: Int,
val clue: String,
val direction: String,
val length: Int,
val position: Position,
val solution: String = "",
) {
@Serializable
internal data class Position(
val x: Int,
val y: Int,
)
}

@Serializable
internal data class Data(
val name: String,
val creator: Creator = Creator(),
val dimensions: Dimensions,
val instructions: String = "",
val entries: List<Entry>,
) {
@Serializable
internal data class Dimensions(
val cols: Int,
val rows: Int,
)

@Serializable
internal data class Creator(
val name: String = "",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.jeffpdavidson.kotwords.formats

import com.jeffpdavidson.kotwords.readBinaryResource
import com.jeffpdavidson.kotwords.readStringResource
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertTrue

class GuardianTest {
@Test
fun crossword() = runTest {
assertTrue(
readBinaryResource(GuardianTest::class, "puz/test-simple.puz").contentEquals(
Guardian(
readStringResource(GuardianTest::class, "guardian/test-simple.json"),
copyright = "\u00a9 2018 Jeff Davidson",
).asPuzzle().asAcrossLiteBinary()
)
)
}
}
122 changes: 122 additions & 0 deletions src/commonTest/resources/guardian/test-simple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"name": "Example Puzzle for Kotwords",
"creator": {
"name": "Jeff Davidson"
},
"entries": [
{
"number": 1,
"clue": "First across clue",
"direction": "across",
"length": 4,
"position": {
"x": 0,
"y": 0
},
"solution": "ABCD"
},
{
"number": 5,
"clue": "Second across clue",
"direction": "across",
"length": 4,
"position": {
"x": 0,
"y": 1
},
"solution": "EFGH"
},
{
"number": 6,
"clue": "Third across clue",
"direction": "across",
"length": 5,
"position": {
"x": 0,
"y": 2
},
"solution": "IJKLM"
},
{
"number": 8,
"clue": "Fourth across clue",
"direction": "across",
"length": 4,
"position": {
"x": 1,
"y": 3
},
"solution": "NOPQ"
},
{
"number": 9,
"clue": "Fifth across clue",
"direction": "across",
"length": 4,
"position": {
"x": 1,
"y": 4
},
"solution": "RSTU"
},
{
"number": 1,
"clue": "First down clue",
"direction": "down",
"length": 3,
"position": {
"x": 0,
"y": 0
},
"solution": "AEI"
},
{
"number": 2,
"clue": "Second down clue",
"direction": "down",
"length": 5,
"position": {
"x": 1,
"y": 0
},
"solution": "BFJNR"
},
{
"number": 3,
"clue": "Third down clue",
"direction": "down",
"length": 5,
"position": {
"x": 2,
"y": 0
},
"solution": "CGKOS"
},
{
"number": 4,
"clue": "Fourth down clue",
"direction": "down",
"length": 5,
"position": {
"x": 3,
"y": 0
},
"solution": "DHLPT"
},
{
"number": 7,
"clue": "Fifth down clue",
"direction": "down",
"length": 3,
"position": {
"x": 4,
"y": 2
},
"solution": "MQU"
}
],
"dimensions": {
"cols": 5,
"rows": 5
}
}
2 changes: 2 additions & 0 deletions src/nativeMain/kotlin/com/jeffpdavidson/kotwords/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.jeffpdavidson.kotwords.formats.AcrossLite
import com.jeffpdavidson.kotwords.formats.Apz
import com.jeffpdavidson.kotwords.formats.BostonGlobe
import com.jeffpdavidson.kotwords.formats.Crosshare
import com.jeffpdavidson.kotwords.formats.Guardian
import com.jeffpdavidson.kotwords.formats.Ipuz
import com.jeffpdavidson.kotwords.formats.JpzFile
import com.jeffpdavidson.kotwords.formats.NewYorkTimes
Expand Down Expand Up @@ -47,6 +48,7 @@ enum class Format(
RGZ(listOf("rg", "rgz"), { data, _, _, _ -> Rgz.fromRgzFile(data) }),
BOSTON_GLOBE_HTML(listOf(), { data, _, _, _ -> BostonGlobe(data.decodeToString()) }),
CROSSHARE_JSON(listOf(), { data, _, _, _ -> Crosshare(data.decodeToString()) }),
GUARDIAN_JSON(listOf(), { data, _, _, copyright -> Guardian(data.decodeToString(), copyright) }),
NEW_YORK_TIMES_HTML(listOf(), { data, _, _, _ -> NewYorkTimes.fromHtml(data.decodeToString()) }),
PUZZLE_ME_JSON(listOf(), { data, _, _, _ -> PuzzleMe(data.decodeToString()) }),
PZZL_TEXT(listOf(), { data, _, _, _ -> Pzzl(data.decodeToString()) }),
Expand Down

0 comments on commit da2bb70

Please sign in to comment.