From 6236529180c31bbbd0bee177c957352938fb30ca Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Wed, 14 Feb 2024 21:08:44 -0800 Subject: [PATCH] Initial implementation of Hearts and Arrows puzzles. Built atop Rows Garden logic as these formats are quite similar in nature. Also fixes a bug with Rows Garden where row clues are labeled in the grid with letters but in the clue list with numbers. --- .../kotwords/model/HeartsAndArrows.kt | 82 +++++ .../kotwords/model/RowsGarden.kt | 299 ++++++++++-------- .../kotwords/model/HeartsAndArrowsTest.kt | 49 +++ .../hearts-and-arrows/hearts-and-arrows.jpz | 1 + .../rows-garden/rows-garden-mini.jpz | 2 +- .../resources/rows-garden/rows-garden.ipuz | 2 +- .../resources/rows-garden/rows-garden.jpz | 2 +- .../kotwords/web/HeartsAndArrowsForm.kt | 145 +++++++++ .../com/jeffpdavidson/kotwords/web/Index.kt | 1 + .../jsMain/resources/hearts-and-arrows.html | 6 +- .../resources/icons/hearts-and-arrows.png | Bin 0 -> 1201 bytes 11 files changed, 451 insertions(+), 138 deletions(-) create mode 100644 src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrows.kt create mode 100644 src/commonTest/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrowsTest.kt create mode 100644 src/commonTest/resources/hearts-and-arrows/hearts-and-arrows.jpz create mode 100644 src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/HeartsAndArrowsForm.kt rename patchwork.html => src/jsMain/resources/hearts-and-arrows.html (89%) create mode 100644 src/jsMain/resources/icons/hearts-and-arrows.png diff --git a/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrows.kt b/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrows.kt new file mode 100644 index 00000000..6fc24681 --- /dev/null +++ b/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrows.kt @@ -0,0 +1,82 @@ +package com.jeffpdavidson.kotwords.model + +import com.jeffpdavidson.kotwords.formats.Puzzleable +import kotlinx.serialization.Serializable +import kotlin.math.floor +import kotlin.math.roundToInt + +@Serializable +data class HeartsAndArrows( + val title: String, + val creator: String, + val copyright: String, + val description: String, + val solutionGrid: List>, + val arrows: List>, + val light: List, + val medium: List, + val dark: List, + val lightHeartColor: String = "#FFFFFF", + val mediumHeartColor: String = "#F4BABA", + val darkHeartColor: String = "#E06666", + val addWordCount: Boolean = true, + val addHyphenated: Boolean = true, + val hasHtmlClues: Boolean = false, +) : Puzzleable() { + override suspend fun createPuzzle(): Puzzle { + val originX = solutionGrid[0].indexOfFirst { it != '.' } + + return RowsGarden.createPuzzle( + title = title, + creator = creator, + copyright = copyright, + description = description, + solutionGrid = solutionGrid, + rows = arrows, + light = light, + medium = medium, + dark = dark, + lightColor = lightHeartColor, + mediumColor = mediumHeartColor, + darkColor = darkHeartColor, + addWordCount = addWordCount, + addHyphenated = addHyphenated, + hasHtmlClues = hasHtmlClues, + rowsListTitle = "Arrows", + bloomsListTitle = "Hearts", + emptyCellType = Puzzle.CellType.VOID, + bloomTypeForCoordinate = { x, y -> + val heartTopLeft = when ((3 * y - x + originX).mod(8)) { + 0 -> x to y + 1 -> x - 2 to y - 1 + 2 -> x - 1 to y - 1 + 3 -> x to y - 1 + 4 -> x - 2 to y - 2 + 5 -> x - 1 to y - 2 + 6 -> x to y - 2 + else -> x - 1 to y + } + val heartX = (heartTopLeft.first + heartTopLeft.second - originX) / 4 + val heartY = (3 * heartTopLeft.second - heartTopLeft.first + originX) / 8 + when ((heartX + heartY).mod(3)) { + 0 -> RowsGarden.BloomType.DARK + 1 -> RowsGarden.BloomType.LIGHT + else -> RowsGarden.BloomType.MEDIUM + } + }, + isStartOfBloom = { x, y -> (3 * y - x + originX).mod(8) == 7 }, + getBloomCoordinates = { x, y -> + listOf( + Puzzle.Coordinate(x = x - 1, y = y), + Puzzle.Coordinate(x = x, y = y), + Puzzle.Coordinate(x = x, y = y + 1), + Puzzle.Coordinate(x = x + 1, y = y + 1), + Puzzle.Coordinate(x = x + 1, y = y + 2), + Puzzle.Coordinate(x = x, y = y + 2), + Puzzle.Coordinate(x = x - 1, y = y + 2), + Puzzle.Coordinate(x = x - 1, y = y + 1), + ) + }, + ) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/RowsGarden.kt b/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/RowsGarden.kt index d7ac9224..3ac86785 100644 --- a/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/RowsGarden.kt +++ b/src/commonMain/kotlin/com/jeffpdavidson/kotwords/model/RowsGarden.kt @@ -41,7 +41,7 @@ data class RowsGarden( // solutionGrid[y][x] = the solution letter at (x, y) var hasShortFirstRow = false val solutionGrid = rowLetters.mapIndexed { y, letters -> - if (y == 0 || y == rowLetters.size - 1) { + val row = if (y == 0 || y == rowLetters.size - 1) { val validWidthShort = rowLetters[1].length / 3 / 2 * 3 val validWidthLong = (rowLetters[1].length / 3 / 2 + 1) * 3 when (letters.length) { @@ -61,156 +61,191 @@ data class RowsGarden( } else { letters } + row.toList() } - // Loop over the grid, adding the Puzzle clues for each bloom as we encounter them. - data class BloomData( - val color: String, - val clues: List, - val puzzleClues: MutableList = mutableListOf() + return Companion.createPuzzle( + title = title, + creator = creator, + copyright = copyright, + description = description, + solutionGrid = solutionGrid, + rows = rows, + light = light, + medium = medium, + dark = dark, + lightColor = lightBloomColor, + mediumColor = mediumBloomColor, + darkColor = darkBloomColor, + addWordCount = addWordCount, + addHyphenated = addHyphenated, + hasHtmlClues = hasHtmlClues, + rowsListTitle = "Rows", + bloomsListTitle = "Blooms", + emptyCellType = Puzzle.CellType.BLOCK, + bloomTypeForCoordinate = { x, y -> + val baseOffset = if (x % 6 < 3) 3 else 0 + val yOffset = if (hasShortFirstRow) baseOffset else baseOffset + 1 + when (floor((y + yOffset) / 2.0).roundToInt() % 3) { + 0 -> BloomType.MEDIUM + 1 -> BloomType.DARK + else -> BloomType.LIGHT + } + }, + isStartOfBloom = { x, y -> + val yOffset = if (hasShortFirstRow) 0 else 1 + ((y + yOffset) % 2 == 0 && x % 6 == 5) || ((y + yOffset) % 2 == 1 && x % 6 == 2) + }, + getBloomCoordinates = { x, y -> + listOf( + Puzzle.Coordinate(x = x - 2, y = y), + Puzzle.Coordinate(x = x - 1, y), + Puzzle.Coordinate(x = x, y = y), + Puzzle.Coordinate(x = x, y = y + 1), + Puzzle.Coordinate(x = x - 1, y = y + 1), + Puzzle.Coordinate(x = x - 2, y = y + 1), + ) + }, ) + } - val blooms = mapOf( - BloomType.LIGHT to BloomData(lightBloomColor, light), - BloomType.MEDIUM to BloomData(mediumBloomColor, medium), - BloomType.DARK to BloomData(darkBloomColor, dark) - ) - val cellMap = mutableMapOf, Puzzle.Cell>() - val bloomsWords = mutableListOf() - fun getOrCreateCell(x: Int, y: Int): Puzzle.Cell { - if (cellMap.containsKey(x to y)) { - return cellMap.getValue(x to y) - } - val bloomType = BloomType.forCoordinate(x, y, hasShortFirstRow) + internal enum class BloomType { + LIGHT, + MEDIUM, + DARK, + } - cellMap[x to y] = - if (solutionGrid[y][x] == '.') { - Puzzle.Cell(cellType = Puzzle.CellType.BLOCK) - } else { - var bloomIndex = 0 - val yOffset = if (hasShortFirstRow) 0 else 1 - val number = when { - (x == solutionGrid[y].indexOfFirst { it != '.' }) -> { - // Start of a row - "${'A' + y}" - } - ((y + yOffset) % 2 == 0 && x % 6 == 5) || ((y + yOffset) % 2 == 1 && x % 6 == 2) -> { - // Start of a bloom - bloomIndex = blooms.getValue(bloomType).puzzleClues.size + 1 - "${bloomType.name[0]}${bloomIndex}" + companion object { + internal fun createPuzzle( + title: String, + creator: String, + copyright: String, + description: String, + solutionGrid: List>, + rows: List>, + light: List, + medium: List, + dark: List, + lightColor: String, + mediumColor: String, + darkColor: String, + addWordCount: Boolean, + addHyphenated: Boolean, + hasHtmlClues: Boolean, + rowsListTitle: String, + bloomsListTitle: String, + emptyCellType: Puzzle.CellType, + bloomTypeForCoordinate: (Int, Int) -> BloomType, + isStartOfBloom: (Int, Int) -> Boolean, + getBloomCoordinates: (Int, Int) -> List, + ): Puzzle { + // Loop over the grid, adding the Puzzle clues for each bloom as we encounter them. + data class BloomData( + val color: String, + val clues: List, + val puzzleClues: MutableList = mutableListOf() + ) + + val blooms = mapOf( + BloomType.LIGHT to BloomData(lightColor, light), + BloomType.MEDIUM to BloomData(mediumColor, medium), + BloomType.DARK to BloomData(darkColor, dark) + ) + val bloomsWords = mutableListOf() + + val grid = solutionGrid.indices.map { y -> + solutionGrid[y].indices.map { x -> + val bloomType = bloomTypeForCoordinate(x, y) + + if (solutionGrid[y][x] == '.') { + Puzzle.Cell(cellType = emptyCellType) + } else { + var bloomIndex = 0 + val number = when { + (x == solutionGrid[y].indexOfFirst { it != '.' }) -> { + // Start of a row + "${'A' + y}" + } + isStartOfBloom(x, y) -> { + // Start of a bloom + bloomIndex = blooms.getValue(bloomType).puzzleClues.size + 1 + "${bloomType.name[0]}${bloomIndex}" + } + else -> "" } - else -> "" - } - val cell = Puzzle.Cell( - number = number, - solution = "${solutionGrid[y][x]}", - backgroundColor = blooms.getValue(bloomType).color - ) - if (bloomIndex > 0) { - val wordId = 1000 * (bloomType.ordinal + 1) + bloomIndex - bloomsWords.add( - Puzzle.Word( - wordId, listOf( - Puzzle.Coordinate(x = x - 2, y = y), - Puzzle.Coordinate(x = x - 1, y), - Puzzle.Coordinate(x = x, y = y), - Puzzle.Coordinate(x = x, y = y + 1), - Puzzle.Coordinate(x = x - 1, y = y + 1), - Puzzle.Coordinate(x = x - 2, y = y + 1), - ) - ) + val cell = Puzzle.Cell( + number = number, + solution = "${solutionGrid[y][x]}", + backgroundColor = blooms.getValue(bloomType).color ) - blooms[bloomType]?.puzzleClues!!.add( - Puzzle.Clue( - wordId, - number, - formatClue( - blooms.getValue(bloomType).clues[bloomIndex - 1], - addWordCount, addHyphenated + if (bloomIndex > 0) { + val wordId = 1000 * (bloomType.ordinal + 1) + bloomIndex + bloomsWords.add(Puzzle.Word(wordId, getBloomCoordinates(x, y))) + blooms[bloomType]?.puzzleClues!!.add( + Puzzle.Clue( + wordId, + number, + formatClue( + blooms.getValue(bloomType).clues[bloomIndex - 1], + addWordCount, addHyphenated + ) ) ) - ) + } + cell } - cell } - return cellMap.getValue(x to y) - } - - val grid = solutionGrid.indices.map { y -> - solutionGrid[y].indices.map { x -> - getOrCreateCell(x, y) } - } - - val (rowClues, rowsWords) = grid.mapIndexed { y, row -> - val letters = row.mapIndexedNotNull { x, cell -> - if (cell.cellType == Puzzle.CellType.BLOCK) null else Puzzle.Coordinate(x = x, y = y) - } - Puzzle.Clue(y + 1, "${y + 1}", rows[y].joinToString(" / ") { - formatClue(it, addWordCount, addHyphenated) - }) to Puzzle.Word(y + 1, letters) - }.unzip() - val bloomClues = listOf( - blooms.getValue(BloomType.LIGHT).puzzleClues, - blooms.getValue(BloomType.MEDIUM).puzzleClues, - blooms.getValue(BloomType.DARK).puzzleClues - ) - .flatten() - - return Puzzle( - title = title, - creator = creator, - copyright = copyright, - description = description, - grid = grid, - clues = listOf( - Puzzle.ClueList(getClueListTitle("Rows"), rowClues), - Puzzle.ClueList(getClueListTitle("Blooms"), bloomClues) - ), - words = rowsWords + bloomsWords.sortedBy { it.id }, - hasHtmlClues = hasHtmlClues, - ) - } + val (rowClues, rowsWords) = grid.mapIndexed { y, row -> + val letters = row.mapIndexedNotNull { x, cell -> + if (cell.cellType == emptyCellType) null else Puzzle.Coordinate(x = x, y = y) + } + Puzzle.Clue(y + 1, "${'A' + y}", rows[y].joinToString(" / ") { + formatClue(it, addWordCount, addHyphenated) + }) to Puzzle.Word(y + 1, letters) + }.unzip() - private fun getClueListTitle(title: String): String = if (hasHtmlClues) "$title" else title + val bloomClues = listOf( + blooms.getValue(BloomType.LIGHT).puzzleClues, + blooms.getValue(BloomType.MEDIUM).puzzleClues, + blooms.getValue(BloomType.DARK).puzzleClues + ).flatten() - private fun formatClue( - entry: Entry, - addWordCount: Boolean, - addHyphenated: Boolean - ): String { - val suffixes = mutableListOf() - if (addWordCount) { - val wordCount = entry.answer.count { it == ' ' } + 1 - if (wordCount > 1) { - suffixes.add("$wordCount wds.") - } - } - if (addHyphenated && entry.answer.contains('-')) { - suffixes.add("hyph.") + return Puzzle( + title = title, + creator = creator, + copyright = copyright, + description = description, + grid = grid, + clues = listOf( + Puzzle.ClueList(if (hasHtmlClues) "$rowsListTitle" else rowsListTitle, rowClues), + Puzzle.ClueList(if (hasHtmlClues) "$bloomsListTitle" else bloomsListTitle, bloomClues) + ), + words = rowsWords + bloomsWords.sortedBy { it.id }, + hasHtmlClues = hasHtmlClues, + ) } - if (suffixes.isEmpty()) { - return entry.clue - } - return "${entry.clue}: ${suffixes.joinToString(", ")}" - } - - private enum class BloomType { - LIGHT, - MEDIUM, - DARK; - companion object { - fun forCoordinate(x: Int, y: Int, hasShortFirstRow: Boolean): BloomType { - val baseOffset = if (x % 6 < 3) 3 else 0 - val yOffset = if (hasShortFirstRow) baseOffset else baseOffset + 1 - return when (floor((y + yOffset) / 2.0).roundToInt() % 3) { - 0 -> MEDIUM - 1 -> DARK - else -> LIGHT + private fun formatClue( + entry: Entry, + addWordCount: Boolean, + addHyphenated: Boolean + ): String { + val suffixes = mutableListOf() + if (addWordCount) { + val wordCount = entry.answer.count { it == ' ' } + 1 + if (wordCount > 1) { + suffixes.add("$wordCount wds.") } } + if (addHyphenated && entry.answer.contains('-')) { + suffixes.add("hyph.") + } + if (suffixes.isEmpty()) { + return entry.clue + } + return "${entry.clue}: ${suffixes.joinToString(", ")}" } } } \ No newline at end of file diff --git a/src/commonTest/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrowsTest.kt b/src/commonTest/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrowsTest.kt new file mode 100644 index 00000000..488ce5cc --- /dev/null +++ b/src/commonTest/kotlin/com/jeffpdavidson/kotwords/model/HeartsAndArrowsTest.kt @@ -0,0 +1,49 @@ +package com.jeffpdavidson.kotwords.model + +import com.jeffpdavidson.kotwords.formats.CrosswordCompilerApplet +import com.jeffpdavidson.kotwords.readStringResource +import com.jeffpdavidson.kotwords.util.trimmedLines +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class HeartsAndArrowsTest { + @Test + fun jpzGeneration() = runTest { + val heartsAndArrows = HeartsAndArrows( + title = "Test title", + creator = "Test creator", + copyright = "Test copyright", + description = "Test description", + solutionGrid = listOf( + "..AA......BB.".toList(), + "..AAACC...BBB".toList(), + "DDAAACCCEEBBB".toList(), + "DDD..CCCEEE..".toList(), + "DDD.....EEE..".toList(), + ), + arrows = listOf( + listOf(RowsGarden.Entry("Row 1 Clue", "AABB")), + listOf(RowsGarden.Entry("Row 2 Clue 1", "AAAC"), RowsGarden.Entry("Row 2 Clue 2", "CBBB")), + listOf(RowsGarden.Entry("Row 3 Clue 1", "DDAAAC"), RowsGarden.Entry("Row 2 Clue 2", "CCEEBBB")), + listOf(RowsGarden.Entry("Row 4 Clue 1", "DDD"), RowsGarden.Entry("Row 2 Clue 2", "CCCEEE")), + listOf(RowsGarden.Entry("Row 5 Clue", "DDDEEE")), + ), + light = listOf( + RowsGarden.Entry("Light 1 Clue", "BBBBBBBB"), + RowsGarden.Entry("Light 2 Clue", "CCCCCCCC"), + RowsGarden.Entry("Light 3 Clue", "DDDDDDDD"), + ), + medium = listOf( + RowsGarden.Entry("Medium 1 Clue", "EEEEEEEE"), + ), + dark = listOf( + RowsGarden.Entry("Dark 1 Clue", "AAAAAAAA"), + ), + ) + val puzzle = heartsAndArrows.asPuzzle() + + val expected = readStringResource(HeartsAndArrowsTest::class, "hearts-and-arrows/hearts-and-arrows.jpz") + assertEquals(expected, puzzle.asJpz().toXmlString()) + } +} \ No newline at end of file diff --git a/src/commonTest/resources/hearts-and-arrows/hearts-and-arrows.jpz b/src/commonTest/resources/hearts-and-arrows/hearts-and-arrows.jpz new file mode 100644 index 00000000..0ce3f675 --- /dev/null +++ b/src/commonTest/resources/hearts-and-arrows/hearts-and-arrows.jpz @@ -0,0 +1 @@ +Congratulations! The puzzle is solved correctly.Test titleTest creatorTest copyrightTest description<b>Arrows</b>Row 1 ClueRow 2 Clue 1 / Row 2 Clue 2Row 3 Clue 1 / Row 2 Clue 2Row 4 Clue 1 / Row 2 Clue 2Row 5 Clue<b>Hearts</b>Light 1 ClueLight 2 ClueLight 3 ClueMedium 1 ClueDark 1 Clue \ No newline at end of file diff --git a/src/commonTest/resources/rows-garden/rows-garden-mini.jpz b/src/commonTest/resources/rows-garden/rows-garden-mini.jpz index 2f039929..ca722f4a 100644 --- a/src/commonTest/resources/rows-garden/rows-garden-mini.jpz +++ b/src/commonTest/resources/rows-garden/rows-garden-mini.jpz @@ -1 +1 @@ -All done!Test TitleTest Author© 2020 Kotwords<b>Rows</b>Row 1Row 2 Clue 1 / Row 2 Clue 2Row 3 Clue 1 / Row 3 Clue 2Row 4 Clue 1 / Row 4 Clue 2Row 5 Clue 1 / Row 5 Clue 2Row 6 Clue 1 / Row 6 Clue 2Row 7<b>Blooms</b>Light 1Light 2Light 3Light 4Light 5Medium 1Medium 2Medium 3Medium 4Medium 5Dark 1Dark 2Dark 3Dark 4Dark 5 \ No newline at end of file +All done!Test TitleTest Author© 2020 Kotwords<b>Rows</b>Row 1Row 2 Clue 1 / Row 2 Clue 2Row 3 Clue 1 / Row 3 Clue 2Row 4 Clue 1 / Row 4 Clue 2Row 5 Clue 1 / Row 5 Clue 2Row 6 Clue 1 / Row 6 Clue 2Row 7<b>Blooms</b>Light 1Light 2Light 3Light 4Light 5Medium 1Medium 2Medium 3Medium 4Medium 5Dark 1Dark 2Dark 3Dark 4Dark 5 \ No newline at end of file diff --git a/src/commonTest/resources/rows-garden/rows-garden.ipuz b/src/commonTest/resources/rows-garden/rows-garden.ipuz index 0783c1af..7f6689ad 100644 --- a/src/commonTest/resources/rows-garden/rows-garden.ipuz +++ b/src/commonTest/resources/rows-garden/rows-garden.ipuz @@ -1 +1 @@ -{"version":"http://ipuz.org/v2","kind":["http://ipuz.org/crossword#1"],"title":"Test Title","copyright":"© 2020 Kotwords","author":"Test Author","notes":"Test Notes","dimensions":{"width":21,"height":12},"puzzle":[["#","#","#",{"cell":"A","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M1","style":{"color":"C3C8FA"}},"#","#","#",{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M2","style":{"color":"C3C8FA"}},"#","#","#",{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M3","style":{"color":"C3C8FA"}},"#","#","#"],[{"cell":"B","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L1","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L2","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L3","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L4","style":{"color":"FFFFFF"}}],[{"cell":"C","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D1","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D2","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D3","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}}],[{"cell":"D","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M4","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M5","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M6","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M7","style":{"color":"C3C8FA"}}],[{"cell":"E","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L5","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L6","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L7","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}}],[{"cell":"F","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D4","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D5","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D6","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D7","style":{"color":"5765F7"}}],[{"cell":"G","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M8","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M9","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M10","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}}],[{"cell":"H","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L8","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L9","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L10","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L11","style":{"color":"FFFFFF"}}],[{"cell":"I","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D8","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D9","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D10","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}}],[{"cell":"J","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M11","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M12","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M13","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M14","style":{"color":"C3C8FA"}}],[{"cell":"K","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L12","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L13","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L14","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}}],["#","#","#",{"cell":"L","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#",{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#",{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#"]],"solution":[[null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],[null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null]],"clues":{"Rows":[{"number":"1","cells":[[4,1],[5,1],[6,1],[10,1],[11,1],[12,1],[16,1],[17,1],[18,1]],"clue":"Row 1"},{"number":"2","cells":[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2]],"clue":"Row 2 Clue 1 (space): 2 wds. / Row 2 Clue 2"},{"number":"3","cells":[[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3],[12,3],[13,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[21,3]],"clue":"Row 3 Clue 1 / Row 3 Clue 2 (hyphen): hyph."},{"number":"4","cells":[[1,4],[2,4],[3,4],[4,4],[5,4],[6,4],[7,4],[8,4],[9,4],[10,4],[11,4],[12,4],[13,4],[14,4],[15,4],[16,4],[17,4],[18,4],[19,4],[20,4],[21,4]],"clue":"Row 4 Clue 1 (spaces and hyphen): 3 wds., hyph. / Row 4 Clue 2"},{"number":"5","cells":[[1,5],[2,5],[3,5],[4,5],[5,5],[6,5],[7,5],[8,5],[9,5],[10,5],[11,5],[12,5],[13,5],[14,5],[15,5],[16,5],[17,5],[18,5],[19,5],[20,5],[21,5]],"clue":"Row 5 Clue 1 (italics) / \"Row 5\" Clue 2 (quotes)"},{"number":"6","cells":[[1,6],[2,6],[3,6],[4,6],[5,6],[6,6],[7,6],[8,6],[9,6],[10,6],[11,6],[12,6],[13,6],[14,6],[15,6],[16,6],[17,6],[18,6],[19,6],[20,6],[21,6]],"clue":"Row 6 Clue 1 / Row 6 Clue 2 :{}[],&*#?|-<>=!%@ (special characters)"},{"number":"7","cells":[[1,7],[2,7],[3,7],[4,7],[5,7],[6,7],[7,7],[8,7],[9,7],[10,7],[11,7],[12,7],[13,7],[14,7],[15,7],[16,7],[17,7],[18,7],[19,7],[20,7],[21,7]],"clue":"Row 7 Clue 1 / Row 7 Clue 2"},{"number":"8","cells":[[1,8],[2,8],[3,8],[4,8],[5,8],[6,8],[7,8],[8,8],[9,8],[10,8],[11,8],[12,8],[13,8],[14,8],[15,8],[16,8],[17,8],[18,8],[19,8],[20,8],[21,8]],"clue":"Row 8 Clue 1 / Row 8 Clue 2"},{"number":"9","cells":[[1,9],[2,9],[3,9],[4,9],[5,9],[6,9],[7,9],[8,9],[9,9],[10,9],[11,9],[12,9],[13,9],[14,9],[15,9],[16,9],[17,9],[18,9],[19,9],[20,9],[21,9]],"clue":"Row 9 Clue 1 / Row 9 Clue 2"},{"number":"10","cells":[[1,10],[2,10],[3,10],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[10,10],[11,10],[12,10],[13,10],[14,10],[15,10],[16,10],[17,10],[18,10],[19,10],[20,10],[21,10]],"clue":"Row 10 Clue 1 / Row 10 Clue 2"},{"number":"11","cells":[[1,11],[2,11],[3,11],[4,11],[5,11],[6,11],[7,11],[8,11],[9,11],[10,11],[11,11],[12,11],[13,11],[14,11],[15,11],[16,11],[17,11],[18,11],[19,11],[20,11],[21,11]],"clue":"Row 11 Clue 1 / Row 11 Clue 2"},{"number":"12","cells":[[4,12],[5,12],[6,12],[10,12],[11,12],[12,12],[16,12],[17,12],[18,12]],"clue":"Row 12"}],"Blooms":[{"number":"L1","cells":[[1,2],[2,2],[3,2],[3,3],[2,3],[1,3]],"clue":"Light 1"},{"number":"L2","cells":[[7,2],[8,2],[9,2],[9,3],[8,3],[7,3]],"clue":"Light 2 (space): 2 wds."},{"number":"L3","cells":[[13,2],[14,2],[15,2],[15,3],[14,3],[13,3]],"clue":"Light 3 (hyphen): hyph."},{"number":"L4","cells":[[19,2],[20,2],[21,2],[21,3],[20,3],[19,3]],"clue":"Light 4"},{"number":"L5","cells":[[4,5],[5,5],[6,5],[6,6],[5,6],[4,6]],"clue":"Light 5"},{"number":"L6","cells":[[10,5],[11,5],[12,5],[12,6],[11,6],[10,6]],"clue":"Light 6"},{"number":"L7","cells":[[16,5],[17,5],[18,5],[18,6],[17,6],[16,6]],"clue":"Light 7"},{"number":"L8","cells":[[1,8],[2,8],[3,8],[3,9],[2,9],[1,9]],"clue":"Light 8"},{"number":"L9","cells":[[7,8],[8,8],[9,8],[9,9],[8,9],[7,9]],"clue":"Light 9"},{"number":"L10","cells":[[13,8],[14,8],[15,8],[15,9],[14,9],[13,9]],"clue":"Light 10"},{"number":"L11","cells":[[19,8],[20,8],[21,8],[21,9],[20,9],[19,9]],"clue":"Light 11"},{"number":"L12","cells":[[4,11],[5,11],[6,11],[6,12],[5,12],[4,12]],"clue":"Light 12"},{"number":"L13","cells":[[10,11],[11,11],[12,11],[12,12],[11,12],[10,12]],"clue":"Light 13"},{"number":"L14","cells":[[16,11],[17,11],[18,11],[18,12],[17,12],[16,12]],"clue":"Light 14"},{"number":"M1","cells":[[4,1],[5,1],[6,1],[6,2],[5,2],[4,2]],"clue":"Medium 1"},{"number":"M2","cells":[[10,1],[11,1],[12,1],[12,2],[11,2],[10,2]],"clue":"Medium 2"},{"number":"M3","cells":[[16,1],[17,1],[18,1],[18,2],[17,2],[16,2]],"clue":"Medium 3"},{"number":"M4","cells":[[1,4],[2,4],[3,4],[3,5],[2,5],[1,5]],"clue":"Medium 4"},{"number":"M5","cells":[[7,4],[8,4],[9,4],[9,5],[8,5],[7,5]],"clue":"Medium 5"},{"number":"M6","cells":[[13,4],[14,4],[15,4],[15,5],[14,5],[13,5]],"clue":"Medium 6"},{"number":"M7","cells":[[19,4],[20,4],[21,4],[21,5],[20,5],[19,5]],"clue":"Medium 7"},{"number":"M8","cells":[[4,7],[5,7],[6,7],[6,8],[5,8],[4,8]],"clue":"Medium 8"},{"number":"M9","cells":[[10,7],[11,7],[12,7],[12,8],[11,8],[10,8]],"clue":"Medium 9"},{"number":"M10","cells":[[16,7],[17,7],[18,7],[18,8],[17,8],[16,8]],"clue":"Medium 10"},{"number":"M11","cells":[[1,10],[2,10],[3,10],[3,11],[2,11],[1,11]],"clue":"Medium 11"},{"number":"M12","cells":[[7,10],[8,10],[9,10],[9,11],[8,11],[7,11]],"clue":"Medium 12"},{"number":"M13","cells":[[13,10],[14,10],[15,10],[15,11],[14,11],[13,11]],"clue":"Medium 13"},{"number":"M14","cells":[[19,10],[20,10],[21,10],[21,11],[20,11],[19,11]],"clue":"Medium 14"},{"number":"D1","cells":[[4,3],[5,3],[6,3],[6,4],[5,4],[4,4]],"clue":"Dark 1"},{"number":"D2","cells":[[10,3],[11,3],[12,3],[12,4],[11,4],[10,4]],"clue":"Dark 2"},{"number":"D3","cells":[[16,3],[17,3],[18,3],[18,4],[17,4],[16,4]],"clue":"Dark 3"},{"number":"D4","cells":[[1,6],[2,6],[3,6],[3,7],[2,7],[1,7]],"clue":"Dark 4"},{"number":"D5","cells":[[7,6],[8,6],[9,6],[9,7],[8,7],[7,7]],"clue":"Dark 5"},{"number":"D6","cells":[[13,6],[14,6],[15,6],[15,7],[14,7],[13,7]],"clue":"Dark 6"},{"number":"D7","cells":[[19,6],[20,6],[21,6],[21,7],[20,7],[19,7]],"clue":"Dark 7"},{"number":"D8","cells":[[4,9],[5,9],[6,9],[6,10],[5,10],[4,10]],"clue":"Dark 8"},{"number":"D9","cells":[[10,9],[11,9],[12,9],[12,10],[11,10],[10,10]],"clue":"Dark 9"},{"number":"D10","cells":[[16,9],[17,9],[18,9],[18,10],[17,10],[16,10]],"clue":"Dark 10"}]}} \ No newline at end of file +{"version":"http://ipuz.org/v2","kind":["http://ipuz.org/crossword#1"],"title":"Test Title","copyright":"© 2020 Kotwords","author":"Test Author","notes":"Test Notes","dimensions":{"width":21,"height":12},"puzzle":[["#","#","#",{"cell":"A","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M1","style":{"color":"C3C8FA"}},"#","#","#",{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M2","style":{"color":"C3C8FA"}},"#","#","#",{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M3","style":{"color":"C3C8FA"}},"#","#","#"],[{"cell":"B","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L1","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L2","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L3","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L4","style":{"color":"FFFFFF"}}],[{"cell":"C","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D1","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D2","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D3","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}}],[{"cell":"D","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M4","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M5","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M6","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M7","style":{"color":"C3C8FA"}}],[{"cell":"E","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L5","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L6","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L7","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}}],[{"cell":"F","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D4","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D5","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D6","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D7","style":{"color":"5765F7"}}],[{"cell":"G","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M8","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M9","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M10","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}}],[{"cell":"H","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L8","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L9","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L10","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L11","style":{"color":"FFFFFF"}}],[{"cell":"I","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D8","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D9","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"D10","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}}],[{"cell":"J","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M11","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M12","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M13","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"5765F7"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"M14","style":{"color":"C3C8FA"}}],[{"cell":"K","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L12","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L13","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"L14","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}},{"cell":"0","style":{"color":"C3C8FA"}}],["#","#","#",{"cell":"L","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#",{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#",{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},{"cell":"0","style":{"color":"FFFFFF"}},"#","#","#"]],"solution":[[null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],["A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A","A"],[null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null,"A","A","A",null,null,null]],"clues":{"Rows":[{"number":"A","cells":[[4,1],[5,1],[6,1],[10,1],[11,1],[12,1],[16,1],[17,1],[18,1]],"clue":"Row 1"},{"number":"B","cells":[[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2],[12,2],[13,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2]],"clue":"Row 2 Clue 1 (space): 2 wds. / Row 2 Clue 2"},{"number":"C","cells":[[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3],[12,3],[13,3],[14,3],[15,3],[16,3],[17,3],[18,3],[19,3],[20,3],[21,3]],"clue":"Row 3 Clue 1 / Row 3 Clue 2 (hyphen): hyph."},{"number":"D","cells":[[1,4],[2,4],[3,4],[4,4],[5,4],[6,4],[7,4],[8,4],[9,4],[10,4],[11,4],[12,4],[13,4],[14,4],[15,4],[16,4],[17,4],[18,4],[19,4],[20,4],[21,4]],"clue":"Row 4 Clue 1 (spaces and hyphen): 3 wds., hyph. / Row 4 Clue 2"},{"number":"E","cells":[[1,5],[2,5],[3,5],[4,5],[5,5],[6,5],[7,5],[8,5],[9,5],[10,5],[11,5],[12,5],[13,5],[14,5],[15,5],[16,5],[17,5],[18,5],[19,5],[20,5],[21,5]],"clue":"Row 5 Clue 1 (italics) / \"Row 5\" Clue 2 (quotes)"},{"number":"F","cells":[[1,6],[2,6],[3,6],[4,6],[5,6],[6,6],[7,6],[8,6],[9,6],[10,6],[11,6],[12,6],[13,6],[14,6],[15,6],[16,6],[17,6],[18,6],[19,6],[20,6],[21,6]],"clue":"Row 6 Clue 1 / Row 6 Clue 2 :{}[],&*#?|-<>=!%@ (special characters)"},{"number":"G","cells":[[1,7],[2,7],[3,7],[4,7],[5,7],[6,7],[7,7],[8,7],[9,7],[10,7],[11,7],[12,7],[13,7],[14,7],[15,7],[16,7],[17,7],[18,7],[19,7],[20,7],[21,7]],"clue":"Row 7 Clue 1 / Row 7 Clue 2"},{"number":"H","cells":[[1,8],[2,8],[3,8],[4,8],[5,8],[6,8],[7,8],[8,8],[9,8],[10,8],[11,8],[12,8],[13,8],[14,8],[15,8],[16,8],[17,8],[18,8],[19,8],[20,8],[21,8]],"clue":"Row 8 Clue 1 / Row 8 Clue 2"},{"number":"I","cells":[[1,9],[2,9],[3,9],[4,9],[5,9],[6,9],[7,9],[8,9],[9,9],[10,9],[11,9],[12,9],[13,9],[14,9],[15,9],[16,9],[17,9],[18,9],[19,9],[20,9],[21,9]],"clue":"Row 9 Clue 1 / Row 9 Clue 2"},{"number":"J","cells":[[1,10],[2,10],[3,10],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[10,10],[11,10],[12,10],[13,10],[14,10],[15,10],[16,10],[17,10],[18,10],[19,10],[20,10],[21,10]],"clue":"Row 10 Clue 1 / Row 10 Clue 2"},{"number":"K","cells":[[1,11],[2,11],[3,11],[4,11],[5,11],[6,11],[7,11],[8,11],[9,11],[10,11],[11,11],[12,11],[13,11],[14,11],[15,11],[16,11],[17,11],[18,11],[19,11],[20,11],[21,11]],"clue":"Row 11 Clue 1 / Row 11 Clue 2"},{"number":"L","cells":[[4,12],[5,12],[6,12],[10,12],[11,12],[12,12],[16,12],[17,12],[18,12]],"clue":"Row 12"}],"Blooms":[{"number":"L1","cells":[[1,2],[2,2],[3,2],[3,3],[2,3],[1,3]],"clue":"Light 1"},{"number":"L2","cells":[[7,2],[8,2],[9,2],[9,3],[8,3],[7,3]],"clue":"Light 2 (space): 2 wds."},{"number":"L3","cells":[[13,2],[14,2],[15,2],[15,3],[14,3],[13,3]],"clue":"Light 3 (hyphen): hyph."},{"number":"L4","cells":[[19,2],[20,2],[21,2],[21,3],[20,3],[19,3]],"clue":"Light 4"},{"number":"L5","cells":[[4,5],[5,5],[6,5],[6,6],[5,6],[4,6]],"clue":"Light 5"},{"number":"L6","cells":[[10,5],[11,5],[12,5],[12,6],[11,6],[10,6]],"clue":"Light 6"},{"number":"L7","cells":[[16,5],[17,5],[18,5],[18,6],[17,6],[16,6]],"clue":"Light 7"},{"number":"L8","cells":[[1,8],[2,8],[3,8],[3,9],[2,9],[1,9]],"clue":"Light 8"},{"number":"L9","cells":[[7,8],[8,8],[9,8],[9,9],[8,9],[7,9]],"clue":"Light 9"},{"number":"L10","cells":[[13,8],[14,8],[15,8],[15,9],[14,9],[13,9]],"clue":"Light 10"},{"number":"L11","cells":[[19,8],[20,8],[21,8],[21,9],[20,9],[19,9]],"clue":"Light 11"},{"number":"L12","cells":[[4,11],[5,11],[6,11],[6,12],[5,12],[4,12]],"clue":"Light 12"},{"number":"L13","cells":[[10,11],[11,11],[12,11],[12,12],[11,12],[10,12]],"clue":"Light 13"},{"number":"L14","cells":[[16,11],[17,11],[18,11],[18,12],[17,12],[16,12]],"clue":"Light 14"},{"number":"M1","cells":[[4,1],[5,1],[6,1],[6,2],[5,2],[4,2]],"clue":"Medium 1"},{"number":"M2","cells":[[10,1],[11,1],[12,1],[12,2],[11,2],[10,2]],"clue":"Medium 2"},{"number":"M3","cells":[[16,1],[17,1],[18,1],[18,2],[17,2],[16,2]],"clue":"Medium 3"},{"number":"M4","cells":[[1,4],[2,4],[3,4],[3,5],[2,5],[1,5]],"clue":"Medium 4"},{"number":"M5","cells":[[7,4],[8,4],[9,4],[9,5],[8,5],[7,5]],"clue":"Medium 5"},{"number":"M6","cells":[[13,4],[14,4],[15,4],[15,5],[14,5],[13,5]],"clue":"Medium 6"},{"number":"M7","cells":[[19,4],[20,4],[21,4],[21,5],[20,5],[19,5]],"clue":"Medium 7"},{"number":"M8","cells":[[4,7],[5,7],[6,7],[6,8],[5,8],[4,8]],"clue":"Medium 8"},{"number":"M9","cells":[[10,7],[11,7],[12,7],[12,8],[11,8],[10,8]],"clue":"Medium 9"},{"number":"M10","cells":[[16,7],[17,7],[18,7],[18,8],[17,8],[16,8]],"clue":"Medium 10"},{"number":"M11","cells":[[1,10],[2,10],[3,10],[3,11],[2,11],[1,11]],"clue":"Medium 11"},{"number":"M12","cells":[[7,10],[8,10],[9,10],[9,11],[8,11],[7,11]],"clue":"Medium 12"},{"number":"M13","cells":[[13,10],[14,10],[15,10],[15,11],[14,11],[13,11]],"clue":"Medium 13"},{"number":"M14","cells":[[19,10],[20,10],[21,10],[21,11],[20,11],[19,11]],"clue":"Medium 14"},{"number":"D1","cells":[[4,3],[5,3],[6,3],[6,4],[5,4],[4,4]],"clue":"Dark 1"},{"number":"D2","cells":[[10,3],[11,3],[12,3],[12,4],[11,4],[10,4]],"clue":"Dark 2"},{"number":"D3","cells":[[16,3],[17,3],[18,3],[18,4],[17,4],[16,4]],"clue":"Dark 3"},{"number":"D4","cells":[[1,6],[2,6],[3,6],[3,7],[2,7],[1,7]],"clue":"Dark 4"},{"number":"D5","cells":[[7,6],[8,6],[9,6],[9,7],[8,7],[7,7]],"clue":"Dark 5"},{"number":"D6","cells":[[13,6],[14,6],[15,6],[15,7],[14,7],[13,7]],"clue":"Dark 6"},{"number":"D7","cells":[[19,6],[20,6],[21,6],[21,7],[20,7],[19,7]],"clue":"Dark 7"},{"number":"D8","cells":[[4,9],[5,9],[6,9],[6,10],[5,10],[4,10]],"clue":"Dark 8"},{"number":"D9","cells":[[10,9],[11,9],[12,9],[12,10],[11,10],[10,10]],"clue":"Dark 9"},{"number":"D10","cells":[[16,9],[17,9],[18,9],[18,10],[17,10],[16,10]],"clue":"Dark 10"}]}} \ No newline at end of file diff --git a/src/commonTest/resources/rows-garden/rows-garden.jpz b/src/commonTest/resources/rows-garden/rows-garden.jpz index 03c3cd96..eda217d8 100644 --- a/src/commonTest/resources/rows-garden/rows-garden.jpz +++ b/src/commonTest/resources/rows-garden/rows-garden.jpz @@ -1 +1 @@ -All done!Test TitleTest Author© 2020 KotwordsTest Notes<b>Rows</b>Row 1Row 2 Clue 1 (space): 2 wds. / Row 2 Clue 2Row 3 Clue 1 / Row 3 Clue 2 (hyphen): hyph.Row 4 Clue 1 (spaces and hyphen): 3 wds., hyph. / Row 4 Clue 2Row 5 Clue 1 (italics) / "Row 5" Clue 2 (quotes)Row 6 Clue 1 / Row 6 Clue 2 :{}[],&*#?|-<>=!%@ (special characters)Row 7 Clue 1 / Row 7 Clue 2Row 8 Clue 1 / Row 8 Clue 2Row 9 Clue 1 / Row 9 Clue 2Row 10 Clue 1 / Row 10 Clue 2Row 11 Clue 1 / Row 11 Clue 2Row 12<b>Blooms</b>Light 1Light 2 (space): 2 wds.Light 3 (hyphen): hyph.Light 4Light 5Light 6Light 7Light 8Light 9Light 10Light 11Light 12Light 13Light 14Medium 1Medium 2Medium 3Medium 4Medium 5Medium 6Medium 7Medium 8Medium 9Medium 10Medium 11Medium 12Medium 13Medium 14Dark 1Dark 2Dark 3Dark 4Dark 5Dark 6Dark 7Dark 8Dark 9Dark 10 \ No newline at end of file +All done!Test TitleTest Author© 2020 KotwordsTest Notes<b>Rows</b>Row 1Row 2 Clue 1 (space): 2 wds. / Row 2 Clue 2Row 3 Clue 1 / Row 3 Clue 2 (hyphen): hyph.Row 4 Clue 1 (spaces and hyphen): 3 wds., hyph. / Row 4 Clue 2Row 5 Clue 1 (italics) / "Row 5" Clue 2 (quotes)Row 6 Clue 1 / Row 6 Clue 2 :{}[],&*#?|-<>=!%@ (special characters)Row 7 Clue 1 / Row 7 Clue 2Row 8 Clue 1 / Row 8 Clue 2Row 9 Clue 1 / Row 9 Clue 2Row 10 Clue 1 / Row 10 Clue 2Row 11 Clue 1 / Row 11 Clue 2Row 12<b>Blooms</b>Light 1Light 2 (space): 2 wds.Light 3 (hyphen): hyph.Light 4Light 5Light 6Light 7Light 8Light 9Light 10Light 11Light 12Light 13Light 14Medium 1Medium 2Medium 3Medium 4Medium 5Medium 6Medium 7Medium 8Medium 9Medium 10Medium 11Medium 12Medium 13Medium 14Dark 1Dark 2Dark 3Dark 4Dark 5Dark 6Dark 7Dark 8Dark 9Dark 10 \ No newline at end of file diff --git a/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/HeartsAndArrowsForm.kt b/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/HeartsAndArrowsForm.kt new file mode 100644 index 00000000..d87f0c13 --- /dev/null +++ b/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/HeartsAndArrowsForm.kt @@ -0,0 +1,145 @@ +package com.jeffpdavidson.kotwords.web + +import com.jeffpdavidson.kotwords.KotwordsInternal +import com.jeffpdavidson.kotwords.model.HeartsAndArrows +import com.jeffpdavidson.kotwords.model.Puzzle +import com.jeffpdavidson.kotwords.model.RowsGarden +import com.jeffpdavidson.kotwords.util.trimmedLines +import com.jeffpdavidson.kotwords.web.html.FormFields +import com.jeffpdavidson.kotwords.web.html.Html +import kotlinx.html.InputType +import kotlinx.html.div +import kotlinx.html.dom.append +import kotlinx.html.p + +/** Form to convert Hearts and Arrows puzzles into digital puzzle files. */ +@JsExport +@KotwordsInternal +class HeartsAndArrowsForm { + private val form = PuzzleFileForm("hearts-and-arrows", ::createPuzzle) + private val solutionGrid: FormFields.TextBoxField = FormFields.TextBoxField("solution-grid") + private val arrowClues: FormFields.TextBoxField = FormFields.TextBoxField("arrow-clues") + private val arrowAnswers: FormFields.TextBoxField = FormFields.TextBoxField("arrow-answers") + private val lightClues: FormFields.TextBoxField = FormFields.TextBoxField("light-clues") + private val lightAnswers: FormFields.TextBoxField = FormFields.TextBoxField("light-answers") + private val mediumClues: FormFields.TextBoxField = FormFields.TextBoxField("medium-clues") + private val mediumAnswers: FormFields.TextBoxField = FormFields.TextBoxField("medium-answers") + private val darkClues: FormFields.TextBoxField = FormFields.TextBoxField("dark-clues") + private val darkAnswers: FormFields.TextBoxField = FormFields.TextBoxField("dark-answers") + private val addAnnotations: FormFields.CheckBoxField = FormFields.CheckBoxField("add-annotations") + private val lightHeartColor: FormFields.InputField = FormFields.InputField("light-heart-color") + private val mediumHeartColor: FormFields.InputField = FormFields.InputField("medium-heart-color") + private val darkHeartColor: FormFields.InputField = FormFields.InputField("dark-heart-color") + + init { + Html.renderPage { + append.p { + +"Note: heart colors are determined by making the top-left heart dark, cycling between light, medium, " + +"and dark in the band of hearts extending east-southeast from there, and then ensuring that no two " + +"adjacent hearts have the same color." + } + form.render(this, bodyBlock = { + solutionGrid.render(this, "Solution grid") { + rows = "16" + placeholder = "The solution grid. Use periods to represent empty cells." + } + div(classes = "form-row") { + arrowClues.render(this, "Arrow clues", flexCols = 6) { + rows = "16" + placeholder = + "The clues for each arrow; one line per arrow. Separate multiple clues for an arrow " + + "with a /." + } + arrowAnswers.render(this, "Arrow answers", flexCols = 6) { + rows = "16" + placeholder = + "The answers for each arrow; one line per arrow. Separate multiple answers for an " + + "arrow with a /." + } + } + div(classes = "form-row") { + lightClues.render(this, "Light clues", flexCols = 6) { + rows = "8" + placeholder = + "The light heart clues; one answer per line." + } + lightAnswers.render(this, "Light answers", flexCols = 6) { + rows = "8" + placeholder = + "The light heart answers; one answer per line." + } + } + div(classes = "form-row") { + mediumClues.render(this, "Medium clues", flexCols = 6) { + rows = "8" + placeholder = + "The medium heart clues; one answer per line." + } + mediumAnswers.render(this, "Medium answers", flexCols = 6) { + rows = "8" + placeholder = + "The medium heart answers; one answer per line." + } + } + div(classes = "form-row") { + darkClues.render(this, "Dark clues", flexCols = 6) { + rows = "8" + placeholder = + "The dark heart clues; one answer per line." + } + darkAnswers.render(this, "Dark answers", flexCols = 6) { + rows = "8" + placeholder = + "The dark heart answers; one answer per line." + } + } + }, advancedOptionsBlock = { + addAnnotations.render(this, "Add clue annotations (e.g. \"hyph.\", \"2 wds.\")") { + checked = true + } + div(classes = "form-row") { + lightHeartColor.render(this, "Light heart color", flexCols = 4) { + type = InputType.color + value = "#FFFFFF" + } + mediumHeartColor.render(this, "Medium heart color", flexCols = 4) { + type = InputType.color + value = "#F4BABA" + } + darkHeartColor.render(this, "Dark heart color", flexCols = 4) { + type = InputType.color + value = "#E06666" + } + } + }) + } + } + + private suspend fun createPuzzle(): Puzzle { + val heartsAndArrows = HeartsAndArrows( + title = form.title, + creator = form.creator, + copyright = form.copyright, + description = form.description, + solutionGrid = solutionGrid.value.trimmedLines().map { it.toList() }, + arrows = arrowClues.value.trimmedLines().zip(arrowAnswers.value.uppercase().trimmedLines()) + .map { (clues, answers) -> + clues.split("/") + .zip(answers.split("/")) + .map { (clue, answer) -> RowsGarden.Entry(clue.trim(), answer.trim()) } + }, + light = lightClues.value.trimmedLines().zip(lightAnswers.value.uppercase().trimmedLines()) + .map { (clue, answer) -> RowsGarden.Entry(clue, answer) }, + medium = mediumClues.value.trimmedLines().zip(mediumAnswers.value.uppercase().trimmedLines()) + .map { (clue, answer) -> RowsGarden.Entry(clue, answer) }, + dark = darkClues.value.trimmedLines().zip(darkAnswers.value.uppercase().trimmedLines()) + .map { (clue, answer) -> RowsGarden.Entry(clue, answer) }, + lightHeartColor = lightHeartColor.value, + mediumHeartColor = mediumHeartColor.value, + darkHeartColor = darkHeartColor.value, + addHyphenated = addAnnotations.value, + addWordCount = addAnnotations.value, + ) + return heartsAndArrows.asPuzzle() + } +} \ No newline at end of file diff --git a/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/Index.kt b/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/Index.kt index ffba7e52..6e512226 100644 --- a/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/Index.kt +++ b/src/jsMain/kotlin/com/jeffpdavidson/kotwords/web/Index.kt @@ -18,6 +18,7 @@ object Index { "Crossword", "Crosswordle", "Eight Tracks", + "Hearts and Arrows", "Helter Skelter", "Jelly Roll", "Labyrinth", diff --git a/patchwork.html b/src/jsMain/resources/hearts-and-arrows.html similarity index 89% rename from patchwork.html rename to src/jsMain/resources/hearts-and-arrows.html index 74c8016b..6cb7f4cf 100644 --- a/patchwork.html +++ b/src/jsMain/resources/hearts-and-arrows.html @@ -3,13 +3,13 @@ - Patchwork + Hearts and Arrows
-

Patchwork

+

Hearts and Arrows


@@ -25,7 +25,7 @@

Patchwork

- \ No newline at end of file diff --git a/src/jsMain/resources/icons/hearts-and-arrows.png b/src/jsMain/resources/icons/hearts-and-arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..5f218079986d3f201f6070a8db9c503b77d7f951 GIT binary patch literal 1201 zcmeAS@N?(olHy`uVBq!ia0y~yV5|XQ4xj+T%bs{KAjMhW5n0T@pr;JNj1^1m%YZ6c zGM$|RJe{2t3X1a6GILTH7&Io>zVAK6t6pSLQbgk0va%iqz10q!Fsq)Osl_ zWsyS>%Qn|Njy9V!U(Wg7>AXfo^T_if!fde2&^_Za!`s=-?nvh3R8e$t<4p}o zP;^@D*3~KU_PEU-YlFzskrs_&PHAaMIM%e@Aor%EtcBX&K${f{@L`N7H7Ma z*~%KRXBV6bUCLJT0JNU8=L!k9c&nKA@l(R2F%<`njxgN@xNA D4d_=& literal 0 HcmV?d00001