Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
meikpiep committed Dec 17, 2024
1 parent 51bbced commit b084f80
Show file tree
Hide file tree
Showing 101 changed files with 2,961 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import androidx.appcompat.widget.Toolbar
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.piepmeyer.gauguin.R
import org.piepmeyer.gauguin.difficulty.human.HumanSolver
import org.piepmeyer.gauguin.game.Game
import org.piepmeyer.gauguin.game.GameSolveService

class BottomAppBarItemClickListener(
private val mainActivity: MainActivity,
) : Toolbar.OnMenuItemClickListener, KoinComponent {
) : Toolbar.OnMenuItemClickListener,
KoinComponent {
private val game: Game by inject()
private val gameSolveService: GameSolveService by inject()

Expand All @@ -23,6 +25,19 @@ class BottomAppBarItemClickListener(
R.id.menu_reveal_cell -> gameSolveService.revealSelectedCell()
R.id.menu_reveal_cage -> gameSolveService.revealSelectedCage()
R.id.menu_show_solution -> gameSolveService.solveGrid()
R.id.menu_debug_solve_by_human_solver_from_start -> {
val solver = HumanSolver(game.grid)
solver.prepareGrid()
solver.solveAndCalculateDifficulty()

game.gridUI.invalidate()
}
R.id.menu_debug_solve_by_human_solver_from_here -> {
val solver = HumanSolver(game.grid)
solver.solveAndCalculateDifficulty()

game.gridUI.invalidate()
}
}

return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
Expand All @@ -19,9 +20,12 @@ import org.piepmeyer.gauguin.databinding.FragmentMainGameTopBinding
import org.piepmeyer.gauguin.difficulty.DisplayableGameDifficulty
import org.piepmeyer.gauguin.difficulty.GameDifficulty
import org.piepmeyer.gauguin.difficulty.GameDifficultyRater
import org.piepmeyer.gauguin.difficulty.human.HumanSolver
import org.piepmeyer.gauguin.game.Game
import org.piepmeyer.gauguin.game.GameLifecycle
import org.piepmeyer.gauguin.game.PlayTimeListener
import org.piepmeyer.gauguin.grid.Grid
import org.piepmeyer.gauguin.grid.GridCage
import org.piepmeyer.gauguin.preferences.ApplicationPreferences
import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelBalloon
import org.piepmeyer.gauguin.ui.difficulty.MainGameDifficultyLevelFragment
Expand Down Expand Up @@ -142,6 +146,45 @@ class GameTopFragment :

binding.playtime.text = Utils.displayableGameDuration(game.grid.playTime)
}

if (resources.getBoolean(R.bool.debuggable)) {
lifecycleScope.launch(Dispatchers.Default) {
val grid = Grid(game.grid.variant)

game.grid.cages.forEach {
val newCage = GridCage(it.id, grid.options.showOperators, it.action, it.cageType)

it.cells.forEach { newCage.addCell(grid.getCell(it.cellNumber)) }

newCage.result = it.result

grid.addCage(newCage)
}

game.grid.cells.forEach {
val newCell = grid.getCell(it.cellNumber)

newCell.value = it.value
}

val solver = HumanSolver(grid)
solver.prepareGrid()
val solverResult = solver.solveAndCalculateDifficulty()

var text = binding.difficulty.text as String + " (${solverResult.difficulty}"

if (!solverResult.success) {
text += "!"
}
text += ")"

launch(Dispatchers.Main) {
if (!binding.difficulty.text.contains(' ')) {
binding.difficulty.text = text
}
}
}
}
}

private fun setStarsByDifficulty(difficulty: GameDifficulty?) {
Expand Down
15 changes: 14 additions & 1 deletion gauguin-app/src/main/res/menu/bottom_app_bar.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
Expand Down Expand Up @@ -35,4 +36,16 @@
android:title="@string/main_activity_application_bar_item_reveal_all_cells"
app:showAsAction="never" />

<item
android:id="@+id/menu_debug_solve_by_human_solver_from_start"
android:title="Solve via human solver (from start)"
app:showAsAction="never"
tools:ignore="HardcodedText" />

<item
android:id="@+id/menu_debug_solve_by_human_solver_from_here"
android:title="Solve via human solver (from here)"
app:showAsAction="never"
tools:ignore="HardcodedText" />

</menu>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.piepmeyer.gauguin.difficulty.human

import org.piepmeyer.gauguin.creation.cage.GridCageType
import org.piepmeyer.gauguin.grid.Grid

class FillSingleCage {
fun fillCells(grid: Grid): Int {
val cagesToBeFilled = grid.cages.filter { it.cageType == GridCageType.SINGLE && !it.getCell(0).isUserValueSet }

var filledCells = 0

cagesToBeFilled.forEach {
grid.setUserValueAndRemovePossibles(it.getCell(0), it.result)

filledCells++
}

return filledCells
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.piepmeyer.gauguin.difficulty.human

import org.piepmeyer.gauguin.grid.Grid
import org.piepmeyer.gauguin.grid.GridCage
import org.piepmeyer.gauguin.grid.GridCell

data class GridLine(
private val grid: Grid,
val type: GridLineType,
val lineNumber: Int,
) {
fun contains(cell: GridCell): Boolean =
when (type) {
GridLineType.COLUMN -> cell.column == lineNumber
GridLineType.ROW -> cell.row == lineNumber
}

fun cells(): List<GridCell> = grid.cells.filter { contains(it) }

fun cages(): Set<GridCage> =
grid.cells
.filter { contains(it) }
.map { it.cage!! }
.toSet()

override fun toString(): String = "GridLine type=$type, lineNumber=$lineNumber"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.piepmeyer.gauguin.difficulty.human

enum class GridLineType {
COLUMN,
ROW,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.piepmeyer.gauguin.difficulty.human

import org.piepmeyer.gauguin.grid.Grid

class GridLines(
private val grid: Grid,
) {
fun allLines(): Set<GridLine> {
val lines = mutableSetOf<GridLine>()

for (column in 0..<grid.gridSize.width) {
lines += GridLine(grid, GridLineType.COLUMN, column)
}

for (row in 0..<grid.gridSize.height) {
lines += GridLine(grid, GridLineType.ROW, row)
}

return lines
}

fun linesWithEachPossibleValue(): Set<GridLine> {
val lines = mutableSetOf<GridLine>()

if (grid.gridSize.height == grid.gridSize.largestSide()) {
for (column in 0..<grid.gridSize.width) {
lines += GridLine(grid, GridLineType.COLUMN, column)
}
}

if (grid.gridSize.width == grid.gridSize.largestSide()) {
for (row in 0..<grid.gridSize.height) {
lines += GridLine(grid, GridLineType.ROW, row)
}
}

return lines
}

fun adjacentlinesWithEachPossibleValue(numberOfLines: Int): Set<Set<GridLine>> {
val lines = mutableSetOf<Set<GridLine>>()

if (grid.gridSize.height == grid.gridSize.largestSide()) {
for (column in 0..<grid.gridSize.width - numberOfLines + 1) {
lines +=
(column..<column + numberOfLines).map { GridLine(grid, GridLineType.COLUMN, it) }.toSet()
}
}

if (grid.gridSize.width == grid.gridSize.largestSide()) {
for (row in 0..<grid.gridSize.height - numberOfLines + 1) {
lines +=
setOf(
(row..<row + numberOfLines).map { GridLine(grid, GridLineType.ROW, it) }.toSet(),
)
}
}

return lines.toSet()
}

fun adjacentlines(numberOfLines: Int): Set<Set<GridLine>> {
val lines = mutableSetOf<Set<GridLine>>()

for (column in 0..<grid.gridSize.width - numberOfLines + 1) {
lines +=
(column..<column + numberOfLines).map { GridLine(grid, GridLineType.COLUMN, it) }.toSet()
}

for (row in 0..<grid.gridSize.height - numberOfLines + 1) {
lines +=
setOf(
(row..<row + numberOfLines).map { GridLine(grid, GridLineType.ROW, it) }.toSet(),
)
}

return lines.toSet()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.piepmeyer.gauguin.difficulty.human

import io.github.oshai.kotlinlogging.KotlinLogging
import org.piepmeyer.gauguin.grid.Grid
import org.piepmeyer.gauguin.grid.GridCell

private val logger = KotlinLogging.logger {}

class HumanSolver(
private val grid: Grid,
private val validate: Boolean = false,
) {
private val humanSolverStrategy =
HumanSolverStrategies.entries

private val cache = PossiblesCache(grid)

fun solveAndCalculateDifficulty(): HumanSolverResult {
var progress: HumanSolverStep
var success = true
var difficulty = FillSingleCage().fillCells(grid) * 1

cache.initialize()

do {
cache.validateEntries()
progress = doProgress()

if (progress.success) {
difficulty += progress.difficulty
} else if (!grid.isSolved()) {
success = false

logger.info { "Sad about grid:\n$grid" }
}
} while (progress.success && !grid.isSolved())

return HumanSolverResult(success, difficulty)
}

private fun doProgress(): HumanSolverStep {
humanSolverStrategy.forEach {
val progress = it.solver.fillCells(grid, cache)

if (progress) {
logger.info { "Added ${it.difficulty} from ${it.solver::class.simpleName}" }

if (validate &&
(grid.numberOfMistakes() != 0 || grid.cells.any { !it.isUserValueSet && it.possibles.isEmpty() })
) {
logger.error { "Last step introduced errors." }
throw IllegalStateException("Found a grid with wrong values.")
}

return HumanSolverStep(true, it.difficulty)
}
}

return HumanSolverStep(false, 0)
}

fun prepareGrid() {
grid.cells.forEach {
it.possibles = grid.variant.possibleDigits
it.userValue = GridCell.NO_VALUE_SET
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.piepmeyer.gauguin.difficulty.human

data class HumanSolverResult(
val success: Boolean,
val difficulty: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.piepmeyer.gauguin.difficulty.human

data class HumanSolverStep(
val success: Boolean,
val difficulty: Int,
)
Loading

0 comments on commit b084f80

Please sign in to comment.