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

Include undo steps when saving grids. #241

Merged
merged 1 commit into from
Nov 10, 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- The undo steps a a grid are included when it gets saved. This means that the undo steps get lost
no longer it the grid gets loaded, including autosaved grids.

### Changed

- Show the game solved dialog if the grid shown at the start of the app has alredy been solved.
- Show the game solved dialog if the grid shown at the start of the app has already been solved.
- Build against Android 15 (that is SDK 35). Proper edge to edge support has still to come.

### Deprecated

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import org.koin.core.component.inject
import org.piepmeyer.gauguin.R
import org.piepmeyer.gauguin.databinding.ActivityMainBinding
import org.piepmeyer.gauguin.game.Game
import org.piepmeyer.gauguin.undo.UndoListener
import org.piepmeyer.gauguin.undo.UndoManager

class MainBottomAppBarService(
private val mainActivity: MainActivity,
Expand Down Expand Up @@ -37,10 +35,9 @@ class MainBottomAppBarService(
}
}

val undoListener = UndoListener { undoPossible -> undoButton.isEnabled = undoPossible }
val undoList = UndoManager(undoListener)

game.undoManager = undoList
game.undoManager.addListener { undoPossible ->
undoButton.isEnabled = undoPossible
}

binding.hint.setOnClickListener { mainActivity.checkProgress() }
undoButton.setOnClickListener { game.undoOneStep() }
Expand All @@ -66,9 +63,7 @@ class MainBottomAppBarService(
binding.hint.show()

undoButton.visibility = View.VISIBLE
undoButton.isEnabled = false

eraserButton?.visibility = View.VISIBLE
undoButton.isEnabled = game.undoManager.undoPossible()

solveHelperMenuItems().forEach { it.setVisible(true) }
}
Expand Down
12 changes: 4 additions & 8 deletions gauguin-core/src/main/kotlin/org/piepmeyer/gauguin/CoreModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant
import org.piepmeyer.gauguin.preferences.ApplicationPreferences
import org.piepmeyer.gauguin.preferences.StatisticsManager
import org.piepmeyer.gauguin.undo.UndoManager
import java.io.File

class CoreModule(
Expand All @@ -31,7 +30,6 @@ class CoreModule(

Game(
grid,
UndoManager { },
initialGridView(grid),
get(StatisticsManager::class),
get(ApplicationPreferences::class),
Expand Down Expand Up @@ -80,15 +78,14 @@ class CoreModule(
}
}

private fun initialGameVariant(): GameVariant {
return GameVariant(
private fun initialGameVariant(): GameVariant =
GameVariant(
GridSize(6, 6),
GameOptionsVariant.createClassic(),
)
}

private fun initialGridView(grid: Grid): GridView {
return object : GridView {
private fun initialGridView(grid: Grid): GridView =
object : GridView {
override var grid: Grid
get() = grid
set(_) {
Expand All @@ -101,5 +98,4 @@ class CoreModule(
// dummy implementation
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import org.piepmeyer.gauguin.grid.GridView
import org.piepmeyer.gauguin.preferences.ApplicationPreferences
import org.piepmeyer.gauguin.preferences.StatisticsManager
import org.piepmeyer.gauguin.undo.UndoManager
import org.piepmeyer.gauguin.undo.UndoManagerImpl

private val logger = KotlinLogging.logger {}

data class Game(
val initalGrid: Grid,
var undoManager: UndoManager,
var gridUI: GridView,
@InjectedParam private val statisticsManager: StatisticsManager,
@InjectedParam private val applicationPreferences: ApplicationPreferences,
Expand All @@ -30,6 +30,8 @@ data class Game(
var grid: Grid = initalGrid
private set

val undoManager: UndoManager = UndoManagerImpl { grid }

fun enterFastFinishingMode() {
gameMode = FastFinishingGameMode(this)
gameModeListeners.forEach { it.changedGameMode() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlin.time.Duration.Companion.milliseconds

@Serializable
data class SavedGrid(
val version: Int = 1,
val variant: GameVariant,
val savedAtInMilliseconds: Long,
val playTimeInMilliseconds: Long,
Expand All @@ -19,6 +20,7 @@ data class SavedGrid(
val invalidCellNumbers: List<Int>,
val cheatedCellNumbers: List<Int>,
val cages: List<SavedCage>,
val undoSteps: List<SavedUndoStep> = emptyList(),
) {
fun toGrid(): Grid {
val grid = Grid(variant, savedAtInMilliseconds)
Expand Down Expand Up @@ -58,6 +60,8 @@ data class SavedGrid(
cage
}

grid.undoSteps.addAll(undoSteps.map { it.toUndoStep(grid) })

return grid
}

Expand All @@ -71,6 +75,10 @@ data class SavedGrid(
grid.cages.map {
SavedCage.fromCage(it)
}
val savedUndoSteps =
grid.undoSteps.map {
SavedUndoStep.fromUndoStep(it)
}

return SavedGrid(
variant = grid.variant,
Expand All @@ -84,6 +92,7 @@ data class SavedGrid(
invalidCellNumbers = grid.invalidsHighlighted().map { it.cellNumber },
cheatedCellNumbers = grid.cheatedHighlighted().map { it.cellNumber },
cages = savedCages,
undoSteps = savedUndoSteps,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.piepmeyer.gauguin.game.save

import kotlinx.serialization.Serializable
import org.piepmeyer.gauguin.grid.Grid
import org.piepmeyer.gauguin.undo.UndoStep

@Serializable
data class SavedUndoStep(
val cellNumber: Int,
val userValue: Int,
val possibles: Set<Int>,
val isBatch: Boolean,
) {
fun toUndoStep(grid: Grid) =
UndoStep(
cell = grid.cells[cellNumber],
userValue = userValue,
possibles = possibles,
isBatch = isBatch,
)

companion object {
fun fromUndoStep(undoStep: UndoStep) =
SavedUndoStep(
cellNumber = undoStep.cell.cellNumber,
userValue = undoStep.userValue,
possibles = undoStep.possibles,
isBatch = undoStep.isBatch,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.piepmeyer.gauguin.grid
import io.github.oshai.kotlinlogging.KotlinLogging
import org.piepmeyer.gauguin.options.GameOptionsVariant
import org.piepmeyer.gauguin.options.GameVariant
import org.piepmeyer.gauguin.undo.UndoStep
import kotlin.time.Duration

private val logger = KotlinLogging.logger {}
Expand All @@ -24,6 +25,8 @@ class Grid(
var creationDate: Long = 0
private set

val undoSteps = mutableListOf<UndoStep>()

constructor(variant: GameVariant, creationDate: Long) : this(variant) {
this.creationDate = creationDate
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,17 @@ package org.piepmeyer.gauguin.undo

import org.piepmeyer.gauguin.grid.GridCell

class UndoManager(private val listener: UndoListener) {
private val undoList = mutableListOf<UndoState>()
interface UndoManager {
fun addListener(listener: UndoListener)

fun clear() {
undoList.clear()
}
fun clear()

fun saveUndo(
cell: GridCell,
batch: Boolean,
) {
val undoState =
UndoState(
cell,
cell.userValue,
cell.possibles,
batch,
)
undoList.add(undoState)
listener.undoStateChanged(true)
}
)

fun restoreUndo() {
if (undoList.isNotEmpty()) {
val undoState = undoList.removeLast()
val cell = undoState.cell
cell.setUserValueIntern(undoState.userValue)
cell.possibles = undoState.possibles
cell.isLastModified = true
if (undoState.isBatch) {
restoreUndo()
}
}
if (undoList.isEmpty()) {
listener.undoStateChanged(false)
}
}
fun restoreUndo()

fun undoPossible(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.piepmeyer.gauguin.undo

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

class UndoManagerImpl(
private val gridHolder: () -> Grid,
) : UndoManager {
private val listeners = mutableListOf<UndoListener>()

private fun undoSteps() = gridHolder.invoke().undoSteps

override fun addListener(listener: UndoListener) {
listeners += listener
}

override fun clear() {
undoSteps().clear()
}

override fun saveUndo(
cell: GridCell,
batch: Boolean,
) {
val undoStep =
UndoStep(
cell,
cell.userValue,
cell.possibles,
batch,
)
undoSteps().add(undoStep)

listeners.forEach { it.undoStateChanged(true) }
}

override fun restoreUndo() {
if (undoSteps().isNotEmpty()) {
val undoState = undoSteps().removeAt(undoSteps().lastIndex)
val cell = undoState.cell
cell.setUserValueIntern(undoState.userValue)
cell.possibles = undoState.possibles
cell.isLastModified = true
if (undoState.isBatch) {
restoreUndo()
}
}
if (undoSteps().isEmpty()) {
listeners.forEach { it.undoStateChanged(false) }
}
}

override fun undoPossible() = undoSteps().isNotEmpty()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.piepmeyer.gauguin.undo

import org.piepmeyer.gauguin.grid.GridCell

data class UndoState(
data class UndoStep(
val cell: GridCell,
val userValue: Int,
val possibles: Set<Int>,
Expand Down
Loading