Skip to content

Commit

Permalink
Refactored BruteForceSolver for better readability
Browse files Browse the repository at this point in the history
  • Loading branch information
ILikeYourHat committed Dec 20, 2024
1 parent 42361bb commit a6a7075
Show file tree
Hide file tree
Showing 32 changed files with 211 additions and 243 deletions.
5 changes: 5 additions & 0 deletions src/main/kotlin/io/github/ilikeyourhat/kudoku/Kudoku.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.github.ilikeyourhat.kudoku.rating.DeductionBasedRater
import io.github.ilikeyourhat.kudoku.rating.Difficulty
import io.github.ilikeyourhat.kudoku.solving.SolutionCount
import io.github.ilikeyourhat.kudoku.solving.SudokuSolver
import io.github.ilikeyourhat.kudoku.solving.bruteforce.BruteForceSolver
import io.github.ilikeyourhat.kudoku.solving.sat.SatSolver
import io.github.ilikeyourhat.kudoku.type.BUILD_IN_TYPES
import kotlin.random.Random
Expand All @@ -25,6 +26,10 @@ object Kudoku {
return SatSolver()
}

fun bruteForceSolver(): SudokuSolver {
return BruteForceSolver()
}

fun create(
type: SudokuType,
difficulty: Difficulty? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ interface SudokuType {
val name: String
val sizeX: Int
val sizeY: Int
val possibleValues: Int
val maxValue: Int
fun template(): String
fun divider(): RegionDivider
fun allPossibleValues(): List<Int> = IntRange(1, possibleValues).toList()
fun allPossibleValues(): List<Int> = IntRange(1, maxValue).toList()
}
Original file line number Diff line number Diff line change
@@ -1,100 +1,66 @@
@file:Suppress("UnsafeCallOnNullableType")

package io.github.ilikeyourhat.kudoku.solving.bruteforce

import io.github.ilikeyourhat.kudoku.model.Field
import io.github.ilikeyourhat.kudoku.model.Point
import io.github.ilikeyourhat.kudoku.model.Region
import io.github.ilikeyourhat.kudoku.model.Sudoku
import io.github.ilikeyourhat.kudoku.model.SudokuType
import io.github.ilikeyourhat.kudoku.solving.SudokuSolver
import kotlin.random.Random

class BruteForceSolver : SudokuSolver {

internal enum class Direction {
FORWARD, BACKWARD
}
class BruteForceSolver(
private val random: Random? = null
) : SudokuSolver {

override fun solve(sudoku: Sudoku): Sudoku {
return Command(sudoku).findSolution()
val result = sudoku.copy()
val lookup = RegionLookup(result)
val fields = result.allFields
.filter { it.isEmpty }
.applyRandomOrder()

runAlgorithm(result.type, fields, lookup)
return result
}

private class Command(private val origin: Sudoku) {
private val sudoku: Sudoku = origin.copy()
private val regionLookup: Map<Point, List<Region>> = createRegionLookup(sudoku)
private val iterator: ListIterator<Field> = sudoku.allFields.listIterator()

private var currentDirection = Direction.FORWARD
private lateinit var currentField: Field

fun findSolution(): Sudoku {
while (canMove()) {
move()
if (isOriginFieldEmpty()) {
setNextValuesAndCheckGrid()
private fun runAlgorithm(
type: SudokuType,
fields: List<Field>,
lookup: RegionLookup
) {
var position = 0
loop@ while (fields.indices.contains(position)) {
val field = fields[position]
for (value in field.value..<type.maxValue) {
field.set(value + 1)
if (lookup.isGridCorrectAfterChange(field)) {
position++
continue@loop
}
}
return sudoku
}

private fun canMove(): Boolean {
return canMoveForward() || canMoveBackward()
}

private fun canMoveForward(): Boolean {
return currentDirection == Direction.FORWARD && iterator.hasNext()
}

private fun canMoveBackward(): Boolean {
return currentDirection == Direction.BACKWARD && iterator.hasPrevious()
}

private fun move() {
currentField = if (currentDirection == Direction.FORWARD) {
iterator.next()
} else {
iterator.previous()
}
field.clear()
position--
}
}

private fun changeDirection(newDirection: Direction) {
if (currentDirection != newDirection) {
currentDirection = newDirection
/*
* Alternating calls to ListIterator.next() and ListIterator.previous() will return the same element
* repeatedly. So to compensate this and return the real next/previous element in the next move, we must
* move one step further after every direction change.
*/
move()
}
private fun List<Field>.applyRandomOrder(): List<Field> {
return if (random != null) {
this.shuffled(random)
} else {
this
}
}

private fun isOriginFieldEmpty(): Boolean {
val x = currentField.x
val y = currentField.y
return origin.atField(x, y).isEmpty
}
private class RegionLookup(sudoku: Sudoku) {

private fun setNextValuesAndCheckGrid() {
val currentNumber = currentField.value()
for (i in currentNumber + 1..sudoku.type.possibleValues) {
currentField.set(i)
if (isGridCorrectAfterChange()) {
changeDirection(Direction.FORWARD)
return
private val regionsMap = sudoku.allFields
.associate { field ->
field.position to sudoku.regions.filter { region ->
region.contains(field)
}
}
currentField.clear()
changeDirection(Direction.BACKWARD)
}

private fun isGridCorrectAfterChange(): Boolean {
return regionLookup.getValue(currentField.position)
fun isGridCorrectAfterChange(field: Field): Boolean {
return regionsMap.getValue(field.position)
.all { it.isValid() }
}

private fun createRegionLookup(sudoku: Sudoku): Map<Point, List<Region>> {
return sudoku.allFields
.associate { field -> field.position to sudoku.regions.filter { it.contains(field) } }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class DeductionSolverV2 : DeductionSolver() {
NakedSinglesAlgorithm.Factory(),
HiddenSinglesAlgorithm.Factory()
)
for (i in 2..type.possibleValues / 2) {
for (i in 2..type.maxValue / 2) {
algorithms.add(NakedValuesAlgorithm.Factory(i))
algorithms.add(HiddenValuesAlgorithm.Factory(i))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DeductionSolverV3 : DeductionSolver() {
NakedSinglesAlgorithm.Factory(),
HiddenSinglesAlgorithm.Factory()
)
for (i in 2..type.possibleValues / 2) {
for (i in 2..type.maxValue / 2) {
algorithms.add(NakedValuesAlgorithm.Factory(i))
algorithms.add(HiddenValuesAlgorithm.Factory(i))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class SatSolver : SudokuSolver, SudokuSolutionChecker {

private class Command(private val sudoku: Sudoku) {

private val indexEncoder = IndexEncoder(sudoku.sizeX(), sudoku.sizeY(), sudoku.type.possibleValues)
private val indexEncoder = IndexEncoder(sudoku.sizeX(), sudoku.sizeY(), sudoku.type.maxValue)
private val engine = SatEngine()

fun solve(): Sudoku {
Expand Down Expand Up @@ -61,7 +61,7 @@ class SatSolver : SudokuSolver, SudokuSolutionChecker {
}

private fun addCausesForRegions(sudoku: Sudoku) {
for (possibleValue in 1..sudoku.type.possibleValues) {
for (possibleValue in 1..sudoku.type.maxValue) {
for (region in sudoku.regions) {
val list = mutableListOf<Int>()
for ((position) in region) {
Expand All @@ -75,7 +75,7 @@ class SatSolver : SudokuSolver, SudokuSolutionChecker {

private fun createValues(field: Field): List<Int> {
val list = mutableListOf<Int>()
for (value in 1..sudoku.type.possibleValues) {
for (value in 1..sudoku.type.maxValue) {
val index = indexEncoder.encode(field.position(), value)
list.add(index)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Butterfly12x12 : SudokuType {
override val name = "butterfly_12x12"
override val sizeX = 12
override val sizeY = 12
override val possibleValues = 9
override val maxValue = 9

override fun divider(): RegionDivider {
return RegionDivider()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic16x16 : SudokuType {
override val name = "classic_16x16"
override val sizeX = 16
override val sizeY = 16
override val possibleValues = 16
override val maxValue = 16

override fun template() = """
_,_,_,_ _,_,_,_ _,_,_,_ _,_,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic25x25 : SudokuType {
override val name = "classic_25x25"
override val sizeX = 25
override val sizeY = 25
override val possibleValues = 25
override val maxValue = 25

override fun template() = """
_,_,_,_,_ _,_,_,_,_ _,_,_,_,_ _,_,_,_,_ _,_,_,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic4x4 : SudokuType {
override val name = "classic_4x4"
override val sizeX = 4
override val sizeY = 4
override val possibleValues = 4
override val maxValue = 4

override fun template() = """
_,_ _,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic6x6Horizontal : SudokuType {
override val name = "classic_6x6_horizontal"
override val sizeX = 6
override val sizeY = 6
override val possibleValues = 6
override val maxValue = 6

override fun template() = """
_,_,_ _,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic6x6Vertical : SudokuType {
override val name = "classic_6x6_vertical"
override val sizeX = 6
override val sizeY = 6
override val possibleValues = 6
override val maxValue = 6

override fun template() = """
_,_ _,_ _,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Classic9x9 : SudokuType {
override val name = "classic_9x9"
override val sizeX = 9
override val sizeY = 9
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
_,_,_ _,_,_ _,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object DoubleBackslash15x15 : SudokuType {
override val name = "double_backslash_15x15"
override val sizeX = 15
override val sizeY = 15
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
_,_,_ _,_,_ _,_,_ #,#,# #,#,#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object DoubleDiagonal9x9 : SudokuType {
override val name = "double_diagonal_9x9"
override val sizeX = 9
override val sizeY = 9
override val possibleValues = 9
override val maxValue = 9

override fun template() = Classic9x9.template()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object DoubleSlash15x15 : SudokuType {
override val name = "double_slash_15x15"
override val sizeX = 15
override val sizeY = 15
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
#,#,# #,#,# _,_,_ _,_,_ _,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object SamuraiButterfly30x30 : SudokuType {
override val name = "samurai_butterfly_30x30"
override val sizeX = 30
override val sizeY = 30
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
_,_,_ _,_,_ _,_,_ _,_,_ #,#,# #,#,# _,_,_ _,_,_ _,_,_ _,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object SamuraiClassic21x21 : SudokuType {
override val name = "samurai_classic_21x21"
override val sizeX = 21
override val sizeY = 21
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
_,_,_ _,_,_ _,_,_ #,#,# _,_,_ _,_,_ _,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object SamuraiClassic40x40 : SudokuType {
override val name = "samurai_classic_40x40"
override val sizeX = 40
override val sizeY = 40
override val possibleValues = 16
override val maxValue = 16

override fun template() = """
_,_,_,_ _,_,_,_ _,_,_,_ _,_,_,_ #,#,#,# #,#,#,# _,_,_,_ _,_,_,_ _,_,_,_ _,_,_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Square1x1 : SudokuType {
override val name = "square_1x1"
override val sizeX = 1
override val sizeY = 1
override val possibleValues = 1
override val maxValue = 1

override fun divider(): RegionDivider {
return RegionDivider()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Square2x2 : SudokuType {
override val name = "square_2x2"
override val sizeX = 2
override val sizeY = 2
override val possibleValues = 2
override val maxValue = 2

override fun template() = """
_,_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object TripleBackslash15x15 : SudokuType {
override val name = "triple_backslash_15x15"
override val sizeX = 15
override val sizeY = 15
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
_,_,_ _,_,_ _,_,_ #,#,# #,#,#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object TripleSlash15x15 : SudokuType {
override val name = "triple_slash_15x15"
override val sizeX = 15
override val sizeY = 15
override val possibleValues = 9
override val maxValue = 9

override fun template() = """
#,#,# #,#,# _,_,_ _,_,_ _,_,_
Expand Down
Loading

0 comments on commit a6a7075

Please sign in to comment.