Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
meikpiep committed Jan 1, 2025
1 parent 3033f62 commit 744a7c7
Show file tree
Hide file tree
Showing 102 changed files with 3,002 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,54 @@
package org.piepmeyer.gauguin.difficulty.human

import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import org.piepmeyer.gauguin.game.save.SaveGame
import java.io.File
import java.nio.file.Files
import java.util.stream.Collectors
import kotlin.io.path.isDirectory
import kotlin.io.path.name

private val logger = KotlinLogging.logger {}

class HumanDifficultyBalanceTest :
FunSpec({
test("balancing") {
val savedGames =
Files
.list(File("src/test/resources/difficulty-balancing").toPath())
.collect(Collectors.toList())
.filter { !it.isDirectory() }

val namesToGrids =
savedGames
.associateWith {
val grid = SaveGame.createWithFile(it.toFile()).restore()!!
grid.clearUserValues()
grid.addPossiblesAtNewGame()

grid
}.mapKeys { it.key.name }

val namesToDifficulties =
namesToGrids.mapValues {
logger.info { it.key + "..." }

val result = HumanSolver(it.value).solveAndCalculateDifficulty()

withClue(it.key) {
result.success shouldBe true
}

result.difficulty
}

namesToDifficulties.entries
.sortedBy { it.value }
.forEach {
logger.info { "${it.value} -> ${it.key}" }
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.piepmeyer.gauguin.difficulty.human

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import org.piepmeyer.gauguin.creation.MergingCageGridCalculator
import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler
import org.piepmeyer.gauguin.creation.SeedRandomizerMock
import org.piepmeyer.gauguin.grid.GridSize
import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant

class HumanDifficultySolverHandpickedTest :
FunSpec({
test("seed random grid should be solved") {
val randomizer = SeedRandomizerMock(16)

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(3, 6),
GameOptionsVariant.createClassic(),
),
randomizer,
RandomPossibleDigitsShuffler(randomizer.random),
)

val grid = calculator.calculate()
grid.cells.forEach { it.possibles = grid.variant.possibleDigits }

val solver = HumanSolver(grid, true)

solver.solveAndCalculateDifficulty()

println(grid.toString())

grid.isSolved() shouldBe true
}

test("merging algo 4x4 wrong solution") {
val randomizer = SeedRandomizerMock(6096)

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(4, 4),
GameOptionsVariant.createClassic(),
),
randomizer,
RandomPossibleDigitsShuffler(randomizer.random),
)

val grid = calculator.calculate()
grid.cells.forEach { it.possibles = grid.variant.possibleDigits }

val solver = HumanSolver(grid)

solver.solveAndCalculateDifficulty()

println(grid.toString())

grid.isSolved() shouldBe true
}

test("merging algo 4x4 DetectPossiblesBreakingOtherCagesPossiblesDualLines bug") {
val randomizer = SeedRandomizerMock(36)

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(5, 5),
GameOptionsVariant.createClassic(),
),
randomizer,
RandomPossibleDigitsShuffler(randomizer.random),
)

val grid = calculator.calculate()
grid.cells.forEach { it.possibles = grid.variant.possibleDigits }

val solver = HumanSolver(grid)

solver.solveAndCalculateDifficulty()

println(grid.toString())

grid.isSolved() shouldBe true
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.piepmeyer.gauguin.difficulty.human

import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import org.piepmeyer.gauguin.creation.RandomCageGridCalculator
import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler
import org.piepmeyer.gauguin.creation.SeedRandomizerMock
import org.piepmeyer.gauguin.grid.GridSize
import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant

class HumanDifficultySolverPerformanceTest :
FunSpec({
for (seed in 0..9) {
withClue("seed $seed") {

test("seed random grid should be solved") {
val randomizer = SeedRandomizerMock(1)

val calculator =
RandomCageGridCalculator(
GameVariant(
GridSize(11, 11),
GameOptionsVariant.createClassic(),
),
randomizer,
RandomPossibleDigitsShuffler(randomizer.random),
)

val grid = calculator.calculate()
grid.cells.forEach { it.possibles = grid.variant.possibleDigits }

val solver = HumanSolver(grid)

val solverResult = solver.solveAndCalculateDifficulty()

println(grid.toString())

if (!grid.isSolved()) {
if (grid.numberOfMistakes() != 0) {
throw IllegalStateException("Found a grid with wrong values.")
}
}

grid.isSolved() shouldBe true

solverResult.difficulty shouldBeGreaterThan 0
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.piepmeyer.gauguin.difficulty.human

import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import org.piepmeyer.gauguin.creation.MergingCageGridCalculator
import org.piepmeyer.gauguin.creation.RandomPossibleDigitsShuffler
import org.piepmeyer.gauguin.creation.SeedRandomizerMock
import org.piepmeyer.gauguin.grid.GridSize
import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant

class HumanDifficultySolverTest :
FunSpec({
for (seed in 0..999) {
// 10_000 of 4x4, random: 4 left unsolved
// 10_000 of 4x4, merge: 19 left unsolved
// 10_000 of 5x5, merge: 134 left unsolved
// 10_000 of 2x4, merge: no (!) left unsolved
// 1_000 of 3x6, merge: 119 left unsolved
// 1_000 of 6x6, merge: 90 left unsolved
// 100 of 9x9, merge: 50 left unsolved
withClue("seed $seed") {
test("seed random grid should be solved") {
val randomizer = SeedRandomizerMock(seed)

val calculator =
MergingCageGridCalculator(
GameVariant(
GridSize(6, 6),
GameOptionsVariant.createClassic(),
),
randomizer,
RandomPossibleDigitsShuffler(randomizer.random),
)

val grid = calculator.calculate()
grid.cells.forEach { it.possibles = grid.variant.possibleDigits }

val solver = HumanSolver(grid, true)

val solverResult = solver.solveAndCalculateDifficulty()

println(grid.toString())

if (!grid.isSolved()) {
if (grid.numberOfMistakes() != 0) {
throw IllegalStateException("Found a grid with wrong values.")
}
grid.isActive = true
grid.startedToBePlayed = true
grid.description = "${grid.gridSize.width}x${grid.gridSize.height}-$seed"
/*val saveGame =
SaveGame.createWithFile(
File(
SaveGame.SAVEGAME_NAME_PREFIX +
"${grid.numberOfMistakes()}-${grid.gridSize.width}x${grid.gridSize.height}-$seed.yml",
),
)
saveGame.save(grid)*/
}

grid.isSolved() shouldBe true

solverResult.difficulty shouldBeGreaterThan 0
}
}
}
})
Loading

0 comments on commit 744a7c7

Please sign in to comment.