Skip to content

Commit

Permalink
Merge pull request #79 from teogor/feature/add-advanced-solver-module
Browse files Browse the repository at this point in the history
Integrate Sudoku Solver with Mistake Checking and Move Suggestion Capabilities
  • Loading branch information
teogor authored Sep 4, 2024
2 parents 3174d00 + 257685c commit 494f443
Show file tree
Hide file tree
Showing 21 changed files with 1,337 additions and 191 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ sizes and difficulty levels.
- Kotlin Multiplatform Support: Now available for JVM, JS(IR), WASM, iOS, macOS, Linux, TVOS, and WatchOS
platforms 🌍

## Documentation

- [Solver Documentation](https://teogor.dev/sudoklify/solver) - Detailed guide on using the `solver` package, including use cases and examples.

## Sudoklify: Redefining Puzzle Generation

Sudoklify reimagines the way Sudoku puzzles are generated, delivering a lightning-fast experience
Expand Down
18 changes: 11 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ sizes and difficulty levels.
- Kotlin Multiplatform Support: Now available for JVM, JS(IR), WASM, iOS, macOS, Linux, TVOS, and WatchOS
platforms 🌍

## Documentation

- [Solver Documentation](solver.md) - Detailed guide on using the `solver` package, including use cases and examples.

## Sudoklify: Redefining Puzzle Generation

Sudoklify reimagines the way Sudoku puzzles are generated, delivering a lightning-fast experience
Expand Down Expand Up @@ -128,9 +132,9 @@ puzzles.forEach { puzzle ->

**Note:** If you prefer manual dependency setup, follow the instructions in the "Manual Setup" section. Otherwise, jump to the "Version Catalog" section for centralized management.

For information on using the KAPT plugin, see the [KAPT documentation](https://kotlinlang.org/docs/kapt.html).
For information on using the KSP plugin, see the [KSP quick-start documentation](https://kotlinlang.org/docs/ksp-quickstart.html).
For more information about dependencies, see [Add Build Dependencies](https://developer.android.com/studio/build/dependencies).
For information on using the KAPT plugin, see the [KAPT documentation](https://kotlinlang.org/docs/kapt.html).
For information on using the KSP plugin, see the [KSP quick-start documentation](https://kotlinlang.org/docs/ksp-quickstart.html).
For more information about dependencies, see [Add Build Dependencies](https://developer.android.com/studio/build/dependencies).

### Adding Sudoklify Dependencies Manually

Expand All @@ -141,7 +145,7 @@ To use Sudoklify in your app, add the following dependencies to your app's `buil
```groovy title="build.gradle"
dependencies {
def teogorSudoklify = "1.0.0-beta04"

implementation "dev.teogor.sudoklify:sudoklify-common:$teogorSudoklify"
implementation "dev.teogor.sudoklify:sudoklify-core:$teogorSudoklify"
implementation "dev.teogor.sudoklify:sudoklify-io:$teogorSudoklify"
Expand All @@ -156,7 +160,7 @@ To use Sudoklify in your app, add the following dependencies to your app's `buil
```kotlin title="build.gradle.kts"
dependencies {
val teogorSudoklify = "1.0.0-beta04"

implementation("dev.teogor.sudoklify:sudoklify-common:$teogorSudoklify")
implementation("dev.teogor.sudoklify:sudoklify-core:$teogorSudoklify")
implementation("dev.teogor.sudoklify:sudoklify-io:$teogorSudoklify")
Expand All @@ -180,7 +184,7 @@ First, define the dependencies in the `libs.versions.toml` file:
```toml title="gradle/libs.versions.toml"
[versions]
teogor-sudoklify = "1.0.0-beta04"

[libraries]
teogor-sudoklify-common = { group = "dev.teogor.sudoklify", name = "sudoklify-common", version.ref = "teogor-sudoklify" }
teogor-sudoklify-core = { group = "dev.teogor.sudoklify", name = "sudoklify-core", version.ref = "teogor-sudoklify" }
Expand All @@ -195,7 +199,7 @@ First, define the dependencies in the `libs.versions.toml` file:
```toml title="gradle/libs.versions.toml"
[versions]
teogor-sudoklify = "1.0.0-beta04"

[libraries]
teogor-sudoklify-common = { module = "dev.teogor.sudoklify:sudoklify-common", version.ref = "teogor-sudoklify" }
teogor-sudoklify-core = { module = "dev.teogor.sudoklify:sudoklify-core", version.ref = "teogor-sudoklify" }
Expand Down
107 changes: 107 additions & 0 deletions docs/solver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Sudoklify Solver - Documentation

## Overview

**Sudoklify** is a powerful Kotlin library designed to assist in creating and solving Sudoku puzzles. The `solver` package provides a robust set of tools to help developers implement mistake-checking, suggest potential moves, and solve puzzles efficiently. This document outlines the key use cases of the solver package, providing practical examples to help you integrate it into your project.

## Table of Contents

- [Getting Started](#getting-started)
- [Core Components](#core-components)
- [MistakeCheckingMode](#mistakecheckingmode)
- [SudoklifyMoveAdvisor](#sudoklifymoveadvisor)
- [SudoklifySolverEngine](#sudoklifysolverengine)
- [SudokuGridProcessor](#sudokugridprocessor)
- [SudokuMove](#sudokumove)
- [Use Cases](#use-cases)
- [Checking for Mistakes](#checking-for-mistakes)
- [Suggesting the Next Move](#suggesting-the-next-move)
- [Solving a Sudoku Puzzle](#solving-a-sudoku-puzzle)
- [Examples](#examples)
- [Basic Mistake Checking](#basic-mistake-checking)
- [Move Suggestion](#move-suggestion)
- [Custom Grid Processor](#custom-grid-processor)
- [License](#license)

## Getting Started

To start using Sudoklify in your project, include the dependency in your build file:

```kotlin
dependencies {
implementation("dev.teogor.sudoklify:sudoklify-solver:$version")
}
```

## Core Components

### MistakeCheckingMode

`MistakeCheckingMode` is an enum that defines the modes for mistake checking:

- `NoChecking`: Disables mistake checking.
- `CheckViolations`: Checks for rule violations without comparing to the final solution.
- `CheckAgainstSolution`: Compares input directly against the final solution.

### SudoklifyMoveAdvisor

`SudoklifyMoveAdvisor` helps in suggesting the next possible move by analyzing the grid and identifying cells where only one value is possible.

### SudoklifySolverEngine

`SudoklifySolverEngine` is the core engine for solving Sudoku puzzles. It combines mistake checking, move advising, and other utilities to provide a comprehensive solution for Sudoku gameplay.

### SudokuGridProcessor

`SudokuGridProcessor` is an interface for processing grids. It allows conversion between different cell types, checking for mistakes, and updating grids.

### SudokuMove

`SudokuMove` represents a move suggestion in the puzzle, indicating the row, column, and value to be placed.

## Use Cases

### Checking for Mistakes

Using `SudoklifySolverEngine`, you can check for mistakes in the player's input. The engine will identify errors based on the selected `MistakeCheckingMode`.

### Suggesting the Next Move

The `SudoklifyMoveAdvisor` can suggest the next optimal move for the player, especially when they are stuck. This feature is useful for providing hints during gameplay.

### Solving a Sudoku Puzzle

The `SudoklifySolverEngine` can solve the entire puzzle based on the given grid and rules. This is particularly useful in automated testing or hint systems.

## Examples

### Basic Mistake Checking

```kotlin
val gridProcessor = createSudokuGridProcessor(
getValue = { cell -> cell.value },
isLocked = { cell -> cell.isLocked },
getSolution = { cell -> cell.solution },
isError = { cell -> cell.isError },
updateCell = { row, col, state, cell -> cell.copy(isError = state.isError) }
)

val puzzle: SudokuPuzzle = // Initialize with puzzle data

val solverEngine = SudoklifySolverEngine(gridProcessor, puzzle)

val checkedGrid = solverEngine.processGridMistakes(currentGrid)
```

### Move Suggestion

```kotlin
val advisor = SudoklifyMoveAdvisor(dimension = puzzle.dimension)
val nextMove = advisor.suggestNextMove(currentGrid)

if (nextMove != null) {
println("Suggested move: Place ${nextMove.value} at row ${nextMove.row}, column ${nextMove.col}")
} else {
println("No move suggestion available.")
}
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ nav:
- Home:
- Overview: index.md
- Seed: seed.md
- Solver: solver.md
- Components:
- Sudoku Dimension: dimension.md
- J-Encoding For Sudoku Cells: j-encoding-for-sudoku-cells.md
Expand Down
28 changes: 28 additions & 0 deletions sudoklify-common/api/sudoklify-common.api
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,36 @@ public final class dev/teogor/sudoklify/components/BoxCoordinatesKt {
public static final fun contains (Ldev/teogor/sudoklify/components/BoxCoordinates;II)Z
public static final fun expand (Ldev/teogor/sudoklify/components/BoxCoordinates;II)Ldev/teogor/sudoklify/components/BoxCoordinates;
public static final fun intersect (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/BoxCoordinates;)Ldev/teogor/sudoklify/components/BoxCoordinates;
public static final fun isAlternateBox (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isBottomEnd (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isBottomStart (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isTopEnd (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isTopStart (Ldev/teogor/sudoklify/components/BoxCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun toFormattedString (Ldev/teogor/sudoklify/components/BoxCoordinates;)Ljava/lang/String;
}

public final class dev/teogor/sudoklify/components/CellCoordinates {
public fun <init> (II)V
public final fun component1 ()I
public final fun component2 ()I
public final fun copy (II)Ldev/teogor/sudoklify/components/CellCoordinates;
public static synthetic fun copy$default (Ldev/teogor/sudoklify/components/CellCoordinates;IIILjava/lang/Object;)Ldev/teogor/sudoklify/components/CellCoordinates;
public fun equals (Ljava/lang/Object;)Z
public final fun getCol ()I
public final fun getPosition ()Lkotlin/Pair;
public final fun getRow ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/sudoklify/components/CellCoordinatesKt {
public static final fun isAlternateCell (Ldev/teogor/sudoklify/components/CellCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isBottomEnd (Ldev/teogor/sudoklify/components/CellCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isBottomStart (Ldev/teogor/sudoklify/components/CellCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isTopEnd (Ldev/teogor/sudoklify/components/CellCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
public static final fun isTopStart (Ldev/teogor/sudoklify/components/CellCoordinates;Ldev/teogor/sudoklify/components/Dimension;)Z
}

public final class dev/teogor/sudoklify/components/Difficulty : java/lang/Enum {
public static final field EASY Ldev/teogor/sudoklify/components/Difficulty;
public static final field HARD Ldev/teogor/sudoklify/components/Difficulty;
Expand Down Expand Up @@ -187,6 +214,7 @@ public final class dev/teogor/sudoklify/components/DimensionKt {
public static final fun getCellBoxColumnIndex (Ldev/teogor/sudoklify/components/Dimension;I)I
public static final fun getCellBoxRowIndex (Ldev/teogor/sudoklify/components/Dimension;I)I
public static final fun getCellColumnIndex (Ldev/teogor/sudoklify/components/Dimension;I)I
public static final fun getCellCoordinates (Ldev/teogor/sudoklify/components/Dimension;II)Ldev/teogor/sudoklify/components/CellCoordinates;
public static final fun getCellRowIndex (Ldev/teogor/sudoklify/components/Dimension;I)I
public static final fun isDigitValid (Ldev/teogor/sudoklify/components/Dimension;I)Z
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalSudoklifyApi::class)

package dev.teogor.sudoklify.components

import dev.teogor.sudoklify.ExperimentalSudoklifyApi

/**
* Data class representing the coordinates of a box in a Sudoku grid.
*
Expand Down Expand Up @@ -143,3 +147,57 @@ fun BoxCoordinates.intersect(other: BoxCoordinates): BoxCoordinates? {
null
}
}

/**
* Checks if the box is in the top-left corner of the Sudoku grid.
*
* @param dimension The dimension of the Sudoku grid.
* @return `true` if the box is in the top-left corner; otherwise, `false`.
*/
fun BoxCoordinates.isTopStart(dimension: Dimension): Boolean {
return topLeftRow == 0 && topLeftCol == 0
}

/**
* Checks if the box is in the top-right corner of the Sudoku grid.
*
* @param dimension The dimension of the Sudoku grid.
* @return `true` if the box is in the top-right corner; otherwise, `false`.
*/
fun BoxCoordinates.isTopEnd(dimension: Dimension): Boolean {
return topLeftRow == 0 && bottomRightCol == dimension.width - 1
}

/**
* Checks if the box is in the bottom-left corner of the Sudoku grid.
*
* @param dimension The dimension of the Sudoku grid.
* @return `true` if the box is in the bottom-left corner; otherwise, `false`.
*/
fun BoxCoordinates.isBottomStart(dimension: Dimension): Boolean {
return bottomRightRow == dimension.height - 1 && topLeftCol == 0
}

/**
* Checks if the box is in the bottom-right corner of the Sudoku grid.
*
* @param dimension The dimension of the Sudoku grid.
* @return `true` if the box is in the bottom-right corner; otherwise, `false`.
*/
fun BoxCoordinates.isBottomEnd(dimension: Dimension): Boolean {
return bottomRightRow == dimension.height - 1 && bottomRightCol == dimension.width - 1
}

/**
* Determines if the box should have a darker background color.
*
* @param dimension The dimension of the Sudoku grid.
* @return `true` if the box should have a darker background; otherwise, `false`.
*/
fun BoxCoordinates.isAlternateBox(dimension: Dimension): Boolean {
// Compute the box index based on its position
val boxIndex = (topLeftRow / dimension.boxHeight) * (dimension.width / dimension.boxWidth) + (topLeftCol / dimension.boxWidth)

// Use modulo operation to alternate the style
return boxIndex % 2 == 1
}
Loading

0 comments on commit 494f443

Please sign in to comment.