-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #63 from ILikeYourHat/single-line-format-parser
Add support for single line format parser
- Loading branch information
Showing
9 changed files
with
312 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/kotlin/io/github/ilikeyourhat/kudoku/parsing/EmptyFieldIndicator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package io.github.ilikeyourhat.kudoku.parsing | ||
|
||
enum class EmptyFieldIndicator(val value: Char) { | ||
ZERO('0'), | ||
DOT('.'), | ||
X('X'), | ||
ASTERISK('*'), | ||
UNDERSCORE('_'), | ||
SPACE(' ') | ||
} |
72 changes: 72 additions & 0 deletions
72
src/main/kotlin/io/github/ilikeyourhat/kudoku/parsing/SingleLineSudokuParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package io.github.ilikeyourhat.kudoku.parsing | ||
|
||
import io.github.ilikeyourhat.kudoku.model.Sudoku | ||
import io.github.ilikeyourhat.kudoku.type.Classic12x12 | ||
import io.github.ilikeyourhat.kudoku.type.Classic16x16 | ||
import io.github.ilikeyourhat.kudoku.type.Classic25x25 | ||
import io.github.ilikeyourhat.kudoku.type.Classic4x4 | ||
import io.github.ilikeyourhat.kudoku.type.Classic6x6 | ||
import io.github.ilikeyourhat.kudoku.type.Classic9x9 | ||
|
||
@Suppress("MagicNumber") | ||
class SingleLineSudokuParser { | ||
|
||
private val typeMap = mapOf( | ||
16 to Classic4x4, | ||
36 to Classic6x6, | ||
81 to Classic9x9, | ||
144 to Classic12x12, | ||
256 to Classic16x16, | ||
625 to Classic25x25 | ||
) | ||
|
||
fun toText( | ||
sudoku: Sudoku, | ||
emptyFieldIndicator: EmptyFieldIndicator | ||
): String { | ||
require(sudoku.isSupported()) { "Unsupported sudoku type: ${sudoku.type.name}" } | ||
|
||
return sudoku.allFields | ||
.map { encodeValue(it.value, emptyFieldIndicator) } | ||
.joinToString("") | ||
} | ||
|
||
fun fromText(text: String): Sudoku { | ||
val type = typeMap[text.length] | ||
?: throw IllegalArgumentException("Unsupported sudoku type with input length ${text.length}") | ||
|
||
val values = text | ||
.map { decodeValue(it) } | ||
.toList() | ||
return Sudoku(type, values) | ||
} | ||
|
||
private fun Sudoku.isSupported(): Boolean { | ||
return typeMap.containsValue(type) | ||
} | ||
|
||
private fun encodeValue(value: Int, emptyFieldIndicator: EmptyFieldIndicator): Char { | ||
return when (value) { | ||
0 -> emptyFieldIndicator.value | ||
in 1..9 -> value.digitToChar() | ||
in 10..25 -> 'A'.plus(value - 10) | ||
else -> throw IllegalArgumentException("Value $value is not supported") | ||
} | ||
} | ||
|
||
private fun decodeValue(value: Char): Int { | ||
return when (value) { | ||
in '1'..'9' -> value.digitToInt() | ||
in 'A'..LAST_SUPPORTED_LETTER_UPPERCASE -> value.code - 'A'.code + 10 | ||
in 'a'..LAST_SUPPORTED_LETTER_LOWERCASE -> value.code - 'a'.code + 10 | ||
in EMPTY_FIELD_INDICATORS -> 0 | ||
else -> throw IllegalArgumentException("Value $value is not supported") | ||
} | ||
} | ||
|
||
private companion object { | ||
const val LAST_SUPPORTED_LETTER_UPPERCASE = 'P' | ||
const val LAST_SUPPORTED_LETTER_LOWERCASE = 'p' | ||
val EMPTY_FIELD_INDICATORS = EmptyFieldIndicator.entries.map { it.value } | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/kotlin/io/github/ilikeyourhat/kudoku/parsing/SudokuExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.github.ilikeyourhat.kudoku.parsing | ||
|
||
import io.github.ilikeyourhat.kudoku.model.Sudoku | ||
|
||
fun Sudoku.toSingleLineString(emptyFieldIndicator: EmptyFieldIndicator = EmptyFieldIndicator.ZERO): String { | ||
return SingleLineSudokuParser().toText(this, emptyFieldIndicator) | ||
} |
2 changes: 1 addition & 1 deletion
2
...ku/parsing/text/SudokuTextFormatParser.kt → .../kudoku/parsing/SudokuTextFormatParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
src/test/kotlin/io/github/ilikeyourhat/kudoku/integration/parsing/SingleLineFormatTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package io.github.ilikeyourhat.kudoku.integration.parsing | ||
|
||
import io.github.ilikeyourhat.kudoku.Kudoku | ||
import io.github.ilikeyourhat.kudoku.model.Sudoku | ||
import io.github.ilikeyourhat.kudoku.parsing.EmptyFieldIndicator | ||
import io.github.ilikeyourhat.kudoku.parsing.toSingleLineString | ||
import io.github.ilikeyourhat.kudoku.type.Classic4x4 | ||
import io.kotest.matchers.equals.shouldBeEqual | ||
import org.junit.jupiter.api.Test | ||
|
||
class SingleLineFormatTest { | ||
|
||
@Test | ||
fun `should handle simple encoding`() { | ||
val sudoku = Sudoku( | ||
Classic4x4, | ||
listOf( | ||
0, 0, 1, 0, | ||
3, 0, 0, 0, | ||
0, 4, 0, 0, | ||
1, 0, 4, 0 | ||
) | ||
) | ||
|
||
sudoku.toSingleLineString() | ||
.shouldBeEqual("0010300004001040") | ||
sudoku.toSingleLineString(emptyFieldIndicator = EmptyFieldIndicator.DOT) | ||
.shouldBeEqual("..1.3....4..1.4.") | ||
} | ||
|
||
@Test | ||
fun `should handle simple decoding`() { | ||
val expectedSudoku = Sudoku( | ||
Classic4x4, | ||
listOf( | ||
0, 0, 1, 0, | ||
3, 0, 0, 0, | ||
0, 4, 0, 0, | ||
1, 0, 4, 0 | ||
) | ||
) | ||
|
||
Kudoku.createFromSingleLineString("0010300004001040") | ||
.shouldBeEqual(expectedSudoku) | ||
Kudoku.createFromSingleLineString("..1.3....4..1.4.") | ||
.shouldBeEqual(expectedSudoku) | ||
} | ||
} |
158 changes: 158 additions & 0 deletions
158
src/test/kotlin/io/github/ilikeyourhat/kudoku/parsing/SingleLineSudokuParserTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package io.github.ilikeyourhat.kudoku.parsing | ||
|
||
import io.github.ilikeyourhat.kudoku.model.Sudoku | ||
import io.github.ilikeyourhat.kudoku.type.BUILD_IN_TYPES | ||
import io.github.ilikeyourhat.kudoku.type.Classic4x4 | ||
import io.github.ilikeyourhat.kudoku.type.SamuraiClassic21x21 | ||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.matchers.booleans.shouldBeTrue | ||
import io.kotest.matchers.collections.shouldHaveSize | ||
import io.kotest.matchers.equals.shouldBeEqual | ||
import io.kotest.matchers.throwable.shouldHaveMessage | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.CsvSource | ||
import org.junit.jupiter.params.provider.ValueSource | ||
|
||
class SingleLineSudokuParserTest { | ||
|
||
private val parser = SingleLineSudokuParser() | ||
|
||
@ParameterizedTest | ||
@ValueSource( | ||
strings = [ | ||
"0010300004001040", | ||
"..1.3....4..1.4.", | ||
"XX1X3XXXX4XX1X4X", | ||
"**1*3****4**1*4*", | ||
"__1_3____4__1_4_", | ||
" 1 3 4 1 4 " | ||
] | ||
) | ||
fun `should handle multiple empty field indicators`(encodedSudoku: String) { | ||
val sudoku = Sudoku( | ||
Classic4x4, | ||
listOf( | ||
0, 0, 1, 0, | ||
3, 0, 0, 0, | ||
0, 4, 0, 0, | ||
1, 0, 4, 0 | ||
) | ||
) | ||
|
||
parser.fromText(encodedSudoku) | ||
.shouldBeEqual(sudoku) | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource( | ||
value = [ | ||
"classic_4x4, 1234", | ||
"classic_6x6, 123456", | ||
"classic_9x9, 123456789", | ||
"classic_12x12, 123456789ABC", | ||
"classic_12x12, 123456789abc", | ||
"classic_16x16, 123456789ABCDEFG", | ||
"classic_16x16, 123456789abcdefg", | ||
"classic_25x25, 123456789ABCDEFGHIJKLMNOP", | ||
"classic_25x25, 123456789abcdefghijklmnop" | ||
] | ||
) | ||
fun `should decode different sudoku types`(type: String, possibleValues: String) { | ||
val encodedSudoku = possibleValues.repeat(possibleValues.length) | ||
|
||
val sudoku = parser.fromText(encodedSudoku) | ||
|
||
sudoku.type.name | ||
.shouldBeEqual(type) | ||
sudoku.isCompleted() | ||
.shouldBeTrue() | ||
sudoku.values() | ||
.distinct() | ||
.shouldHaveSize(possibleValues.length) | ||
} | ||
|
||
@Test | ||
fun `should throw exception when decoding wrong length`() { | ||
val encodedSudoku = "1234123412" | ||
|
||
shouldThrow<IllegalArgumentException> { | ||
parser.fromText(encodedSudoku) | ||
}.shouldHaveMessage("Unsupported sudoku type with input length 10") | ||
} | ||
|
||
@Test | ||
fun `should throw exception when decoding unsupported value`() { | ||
val encodedSudoku = "..1.&....4..1.4." | ||
|
||
shouldThrow<IllegalArgumentException> { | ||
parser.fromText(encodedSudoku) | ||
}.shouldHaveMessage("Value & is not supported") | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource( | ||
value = [ | ||
"ZERO|0010300004001040", | ||
"DOT|..1.3....4..1.4.", | ||
"X|XX1X3XXXX4XX1X4X", | ||
"ASTERISK|**1*3****4**1*4*", | ||
"UNDERSCORE|__1_3____4__1_4_", | ||
"SPACE| 1 3 4 1 4 " | ||
], | ||
delimiter = '|', | ||
ignoreLeadingAndTrailingWhitespace = false | ||
) | ||
fun `should handle multiple empty field indicators`( | ||
emptyFieldIndicator: EmptyFieldIndicator, | ||
expectedString: String | ||
) { | ||
val sudoku = Sudoku( | ||
Classic4x4, | ||
listOf( | ||
0, 0, 1, 0, | ||
3, 0, 0, 0, | ||
0, 4, 0, 0, | ||
1, 0, 4, 0 | ||
) | ||
) | ||
|
||
parser.toText(sudoku, emptyFieldIndicator) | ||
.shouldBeEqual(expectedString) | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource( | ||
strings = [ | ||
"classic_4x4", | ||
"classic_6x6", | ||
"classic_9x9", | ||
"classic_12x12", | ||
"classic_16x16", | ||
"classic_25x25" | ||
] | ||
) | ||
fun `should encode different sudoku types`(typeName: String) { | ||
val type = BUILD_IN_TYPES.single { it.name == typeName } | ||
val values = (1..type.maxValue) | ||
.flatMap { (1..type.maxValue) } | ||
val sudoku = Sudoku(type, values) | ||
|
||
val possibleValues = "123456789ABCDEFGHIJKLMNOP" | ||
.slice(0 until type.maxValue) | ||
|
||
val encodedSudoku = parser.toText(sudoku, EmptyFieldIndicator.ZERO) | ||
|
||
encodedSudoku | ||
.shouldBeEqual(possibleValues.repeat(possibleValues.length)) | ||
} | ||
|
||
@Test | ||
fun `should throw exception when encoding unsupported type`() { | ||
val sudoku = Sudoku(SamuraiClassic21x21) | ||
|
||
shouldThrow<IllegalArgumentException> { | ||
parser.toText(sudoku, EmptyFieldIndicator.ZERO) | ||
}.shouldHaveMessage("Unsupported sudoku type: samurai_classic_21x21") | ||
} | ||
} |
1 change: 0 additions & 1 deletion
1
src/test/kotlin/io/github/ilikeyourhat/kudoku/parsing/SudokuTextFormatParserTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters