Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored BruteForceSolver for better readability #56

Merged
merged 1 commit into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading