Skip to content

Commit

Permalink
Merge pull request #53 from teogor/feature/add-default-mappers-sudoku…
Browse files Browse the repository at this point in the history
…-encoding-decoding

Enable easier string conversion with default mappers in Sudoku board functions
  • Loading branch information
teogor authored Feb 21, 2024
2 parents 85af5db + 51ab562 commit bf5688a
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package dev.teogor.sudoklify.demo.gen

import dev.teogor.sudoklify.core.io.toToken
import dev.teogor.sudoklify.ktx.toBoardCell
import java.util.regex.Pattern

class SudokuDecoder {
Expand All @@ -35,7 +35,7 @@ class SudokuDecoder {
if (cellContent == " ") {
"-"
} else {
cellContent.toInt().toToken()
cellContent.toInt().toBoardCell()
}
puzzleBuilder.append(parsedNumber)
}
Expand Down
114 changes: 114 additions & 0 deletions demo/src/test/kotlin/dev/teogor/sudoklify/demo/SudokuDemoTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dev.teogor.sudoklify.demo

import dev.teogor.sudoklify.common.types.Board
import dev.teogor.sudoklify.common.types.Difficulty
import dev.teogor.sudoklify.common.types.SudokuType
import dev.teogor.sudoklify.core.generation.difficulty
import dev.teogor.sudoklify.core.generation.generateSudoku
import dev.teogor.sudoklify.core.generation.seed
import dev.teogor.sudoklify.core.generation.seeds
import dev.teogor.sudoklify.core.generation.sudokuParamsBuilder
import dev.teogor.sudoklify.core.generation.sudokuType
import dev.teogor.sudoklify.core.util.toBoard
import dev.teogor.sudoklify.core.util.toSequenceString
import dev.teogor.sudoklify.ktx.createSeed
import dev.teogor.sudoklify.seeds.combinedSeeds
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class SudokuDemoTest {

@Test
fun `test Verify Solution Generation for 4x4 Sudoku`() {
val expectedSolution = "3214412314322341"

val sudokuParams = sudokuParamsBuilder {
seeds { combinedSeeds }
seed { createSeed(0L) }
sudokuType { SudokuType.Sudoku4x4 }
difficulty { Difficulty.EASY }
}
val sudoku = sudokuParams.generateSudoku()

assertEquals(expectedSolution, sudoku.solution.toSequenceString())
}

@Test
fun `test Verify Solution Generation for 9x9 Sudoku`() {
val expectedSolution =
"395126784178549623642783915531678249964215378827934561486352197759861432213497856"

val sudokuParams = sudokuParamsBuilder {
seeds { combinedSeeds }
seed { createSeed(0L) }
sudokuType { SudokuType.Sudoku9x9 }
difficulty { Difficulty.EASY }
}
val sudoku = sudokuParams.generateSudoku()

assertEquals(expectedSolution, sudoku.solution.toSequenceString())
}

@Test
fun `test Verify Solution Generation for 16x16 Sudoku`() {
val expectedSolution =
"15101714111269161352834151017141112691613528341441181016131512231956731669182547141512111310121410168315137914625115246127114310161115981381113195104151226714163697145134111081231161521061631315714411128152947513263816115911121014912151141416102581336717391562111681351441011221584161261131497103115161312515108311142147961113107914251561613412813521234791161081611415"

val sudokuParams = sudokuParamsBuilder {
seeds { combinedSeeds }
seed { createSeed(0L) }
sudokuType { SudokuType.Sudoku16x16 }
difficulty { Difficulty.EASY }
}
val sudoku = sudokuParams.generateSudoku()

assertEquals(expectedSolution, sudoku.solution.toSequenceString())
}

@Test
fun `test Verify Solution Generation for 25x25 Sudoku`() {
val expectedSolution =
"62281315211101618205914112571724122342319207102125931711622113232441952151612814181232123722813191718154211410616205249112511195916142420234825212103221182113176715212332581713414225201292151810196247111162320178526741219141122918115132521162410391214313252281410762115111623172418192025324720111813623412181525102211416195229172411142213120231521181631782519410259712672116151918935102424251281420622111231713510211973262291281420181325161523174124111412231112152524821101719796452201318162321618122023191110113942451781437156252122817423920211618131115256124122210197253141062518216111917241132352079122132281415422913221191618203724511236178124101415251174231612101452125922081922153716111318241514191062249122317211135242511182320168741824141725512215631971623131189122102021258115181572411714610162219209231332124121651211102423192015197184213132281425176218220174512213716112210612324251481519139115924146182191125218131716472051223322101159241461821911252181317164720512233221019136722810152516242313141211291741821520"

val sudokuParams = sudokuParamsBuilder {
seeds { combinedSeeds }
seed { createSeed(0L) }
sudokuType { SudokuType.Sudoku25x25 }
difficulty { Difficulty.EASY }
}
val sudoku = sudokuParams.generateSudoku()

assertEquals(expectedSolution, sudoku.solution.toSequenceString())
}

@Test
fun `test Verify Puzzle Solutions Against Generated Puzzles`() {
combinedSeeds.forEach { sudoku ->
val isValid = comparePuzzles(
puzzle = sudoku.puzzle.toBoard(sudoku.sudokuType),
solution = sudoku.solution.toBoard(sudoku.sudokuType),
)

assertEquals(true, isValid, "Invalid puzzle for seed ${sudoku.solution}")
}
}

private fun comparePuzzles(puzzle: Board, solution: Board): Boolean {
if (puzzle.size != solution.size || puzzle[0].size != solution[0].size) {
return false
}

for (row in puzzle.indices) {
for (col in 0..<puzzle[row].size) {
val puzzleValue = puzzle[row][col]
val solvedValue = solution[row][col]

if (puzzleValue != "-" && puzzleValue != solvedValue) {
return false
}
}
}

return true
}
}
2 changes: 2 additions & 0 deletions sudoklify-ktx/api/sudoklify-ktx.api
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public final class dev/teogor/sudoklify/ktx/SeedExtensionsKt {
public final class dev/teogor/sudoklify/ktx/SudokuBoardExtensionsKt {
public static final fun getCells (Ljava/lang/String;)Ljava/util/ArrayList;
public static final fun mapIndexedToSudokuBoard (Ljava/lang/String;Ldev/teogor/sudoklify/common/types/SudokuType;Lkotlin/jvm/functions/Function3;)Ljava/util/List;
public static final fun mapToSudokuBoard (Ljava/lang/String;Ldev/teogor/sudoklify/common/types/SudokuType;)Ljava/util/List;
public static final fun mapToSudokuBoard (Ljava/lang/String;Ldev/teogor/sudoklify/common/types/SudokuType;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun mapToSudokuString (Ljava/util/List;)Ljava/lang/String;
public static final fun mapToSudokuString (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Ljava/lang/String;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,35 @@
package dev.teogor.sudoklify.ktx

import dev.teogor.sudoklify.common.InternalSudoklifyApi
import dev.teogor.sudoklify.common.types.BoardCell
import dev.teogor.sudoklify.common.types.SudokuType

/**
* Converts a two-dimensional list of integers representing a Sudoku board to a
* string representation.
*
* @receiver The two-dimensional list of integers representing the Sudoku board.
* @return The string representation of the Sudoku board, where each cell is encoded as a
* [BoardCell] using its default encoding.
*/
fun List<List<Int>>.mapToSudokuString(): String {
return flatMap { cells ->
cells.map { cell ->
cell.toBoardCell()
}
}.joinToString("")
}

/**
* Converts a two-dimensional list of elements to a string representation, applying a custom
* value mapper to each element.
*
* @receiver The two-dimensional list of elements.
* @param valueMapper A function that maps each element of the list to an integer to be used
* in the string representation.
* @return The string representation of the Sudoku board, where each cell is encoded using
* the provided [valueMapper].
*/
inline fun <T> List<List<T>>.mapToSudokuString(crossinline valueMapper: T.() -> Int): String {
return flatMap { cells ->
cells.map { cell ->
Expand All @@ -27,6 +54,34 @@ inline fun <T> List<List<T>>.mapToSudokuString(crossinline valueMapper: T.() ->
}.joinToString("")
}

/**
* Converts a string representation of a Sudoku board to a two-dimensional list of integers.
*
* @receiver The string representing the Sudoku board.
* @param sudokuType The type of Sudoku board (e.g., 4x4, 9x9), used to determine the size
* and structure of the board.
* @return A two-dimensional list of integers representing the Sudoku board, where each cell
* is converted to an integer using its default decoding.
*/
@OptIn(InternalSudoklifyApi::class)
fun String.mapToSudokuBoard(sudokuType: SudokuType): List<List<Int>> {
return getCells()
.chunked(sudokuType.cells)
.map { row -> row.map { it.toInt() } }
}

/**
* Converts a string representation of a Sudoku board to a two-dimensional list of custom
* elements, applying a custom value mapper to each cell value.
*
* @receiver The string representing the Sudoku board.
* @param sudokuType The type of Sudoku board (e.g., 4x4, 9x9), used to determine the size
* and structure of the board.
* @param valueMapper A function that maps each integer value in the string representation
* to a custom element.
* @return A two-dimensional list of custom elements representing the Sudoku board, where
* each cell is decoded and mapped using the provided [valueMapper].
*/
@OptIn(InternalSudoklifyApi::class)
inline fun <T> String.mapToSudokuBoard(
sudokuType: SudokuType,
Expand All @@ -37,6 +92,18 @@ inline fun <T> String.mapToSudokuBoard(
.map { row -> row.map { valueMapper(it.toInt()) } }
}

/**
* Converts a string representation of a Sudoku board to a two-dimensional list of custom
* elements, allowing access to row and column indices during mapping.
*
* @receiver The string representing the Sudoku board.
* @param sudokuType The type of Sudoku board (e.g., 4x4, 9x9), used to determine the size
* and structure of the board.
* @param valueMapper A function that maps each integer value in the string representation
* to a custom element, additionally providing the row and column indices of the cell.
* @return A two-dimensional list of custom elements representing the Sudoku board, where
* each cell is decoded and mapped using the provided [valueMapper], incorporating its row and column indices.
*/
@OptIn(InternalSudoklifyApi::class)
inline fun <T> String.mapIndexedToSudokuBoard(
sudokuType: SudokuType,
Expand All @@ -51,6 +118,13 @@ inline fun <T> String.mapIndexedToSudokuBoard(
}
}

/**
* Extracts individual cell values from the string representation of a Sudoku board.
*
* @receiver The string representing the Sudoku board.
* @return An `ArrayList<String>` containing each cell value extracted from the string,
* preserving their original representation (e.g., "A1", "5", "-").
*/
@InternalSudoklifyApi
fun String.getCells(): ArrayList<String> {
val regex = Regex("([A-I][a-z]+)|-|[A-I]")
Expand Down

0 comments on commit bf5688a

Please sign in to comment.