Implement undo functionality
Not quite sure about its usability though
This commit is contained in:
@@ -127,10 +127,8 @@ fun PuzzleGrid(
|
||||
allOptions.map { Toggleable(it, item.options.contains(it)) }
|
||||
}
|
||||
LaunchedEffect(item) {
|
||||
item.optionsRemovedListeners.add { removed ->
|
||||
options
|
||||
.filter { removed.contains(it.item) }
|
||||
.forEach { it.enabled = false }
|
||||
item.optionsChangedListeners.add { enabled ->
|
||||
options.forEach { it.enabled = enabled.contains(it.item) }
|
||||
}
|
||||
item.selectionChangedListeners.add {
|
||||
selection = it
|
||||
@@ -151,9 +149,17 @@ fun PuzzleGrid(
|
||||
},
|
||||
selectedItem = selection,
|
||||
onSelectItem = {
|
||||
item.selection = it
|
||||
if (it != null) {
|
||||
grid.snapshot()
|
||||
item.selection = it
|
||||
row.cleanupOptions()
|
||||
} else {
|
||||
while (item.selection != null) {
|
||||
if (!grid.undo()) break
|
||||
}
|
||||
options.forEach {
|
||||
it.enabled = item.options.contains(it.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ class Game(
|
||||
row.forEachIndexed { index, gameCell ->
|
||||
if (index == position.index) {
|
||||
gameCell.selection = position.item
|
||||
gameCell.mutable = false
|
||||
} else {
|
||||
gameCell.options.remove(position.item)
|
||||
}
|
||||
@@ -23,7 +24,7 @@ class Game(
|
||||
}
|
||||
grid.forEach { row ->
|
||||
row.forEach { cell ->
|
||||
cell.optionsRemovedListeners.add {
|
||||
cell.optionsChangedListeners.add {
|
||||
if (onStartListeners.isNotEmpty()) {
|
||||
onStartListeners.forEach { it() }
|
||||
onStartListeners.clear()
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
package ch.dissem.yaep.domain
|
||||
|
||||
class GameCell<C : ItemClass<C>>(
|
||||
selection: Item<C>?=null,
|
||||
selection: Item<C>? = null,
|
||||
val solution: Item<C>? = null,
|
||||
options: Collection<Item<C>>
|
||||
options: Collection<Item<C>>,
|
||||
/*** If mutable is false, the cell will silently ignore all changes to 'selection'. */
|
||||
var mutable: Boolean = true
|
||||
) {
|
||||
val selectionChangedListeners = mutableListOf<(Item<C>?) -> Unit>()
|
||||
val optionsRemovedListeners = mutableListOf<(Collection<Item<C>>) -> Unit>()
|
||||
val selectionChangedListeners: MutableList<(Item<C>?) -> Unit> =
|
||||
mutableListOf<(Item<C>?) -> Unit>()
|
||||
val optionsChangedListeners: MutableList<(Collection<Item<C>>) -> Unit> =
|
||||
mutableListOf<(Collection<Item<C>>) -> Unit>()
|
||||
|
||||
var selection: Item<C>? = selection
|
||||
set(value) {
|
||||
field = value
|
||||
selectionChangedListeners.forEach { listener -> listener(value) }
|
||||
if (mutable) {
|
||||
field = value
|
||||
selectionChangedListeners.forEach { listener -> listener(value) }
|
||||
}
|
||||
}
|
||||
val options = ObservableSet(options.toMutableSet()) { optionsRemovedListeners.forEach { listener -> listener(it) } }
|
||||
val options: ObservableSet<Item<C>> = ObservableSet(options.toMutableSet()) { before, after ->
|
||||
optionsChangedListeners.forEach { listener ->
|
||||
listener(after)
|
||||
}
|
||||
}
|
||||
|
||||
private val snapshots = ArrayDeque<Pair<Item<C>?, Collection<Item<C>>>>()
|
||||
|
||||
fun snapshot() {
|
||||
snapshots.addLast(selection to options.toSet())
|
||||
}
|
||||
|
||||
fun undo(): Boolean {
|
||||
val (selection, options) = snapshots.removeLastOrNull() ?: return false
|
||||
this.selection = selection
|
||||
this.options.clear()
|
||||
this.options.addAll(options)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun <C : ItemClass<C>> GameCell<C>?.mayBe(item: Item<C>, mayHaveSelection: Boolean = true) =
|
||||
@@ -23,5 +47,5 @@ fun <C : ItemClass<C>> GameCell<C>?.mayBe(item: Item<C>, mayHaveSelection: Boole
|
||||
fun <C : ItemClass<C>> GameCell<C>?.isA(item: Item<C>) =
|
||||
this != null && selection == item
|
||||
|
||||
fun <C : ItemClass<C>> GameCell<C>?.hasNoSelection() = this != null && this.selection == null
|
||||
fun <C : ItemClass<C>> GameCell<C>?.hasNoSelection(): Boolean = this != null && this.selection == null
|
||||
|
||||
|
||||
@@ -5,20 +5,20 @@ class GameRow<C : ItemClass<C>>(
|
||||
val options: List<Item<C>>,
|
||||
val cells: List<GameCell<C>>
|
||||
) : List<GameCell<C>> by cells {
|
||||
var isSolved = false
|
||||
var isSolved: Boolean = false
|
||||
private set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
onSolvedListener()
|
||||
}
|
||||
}
|
||||
var onSolvedListener = {}
|
||||
var onSolvedListener: () -> Unit = {}
|
||||
|
||||
fun onSolved(listener: () -> Unit) {
|
||||
onSolvedListener = listener
|
||||
}
|
||||
|
||||
fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element }
|
||||
fun indexOf(element: C): Int = indexOfFirst { it.selection?.itemType == element }
|
||||
|
||||
fun cleanupOptions() {
|
||||
if (isSolved && all {
|
||||
@@ -64,4 +64,19 @@ class GameRow<C : ItemClass<C>>(
|
||||
}
|
||||
}
|
||||
|
||||
fun snapshot() {
|
||||
cells.forEach { it.snapshot() }
|
||||
}
|
||||
|
||||
fun undo(): Boolean {
|
||||
val didChange = cells.map { it.undo() }.reduce { a, b -> a || b }
|
||||
if (didChange) {
|
||||
isSolved = all {
|
||||
it.solution != null
|
||||
&& it.options.size == 1
|
||||
&& it.options.single() == it.selection
|
||||
}
|
||||
}
|
||||
return didChange
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ class Grid(
|
||||
fun cleanupOptions() {
|
||||
forEach { row -> row.cleanupOptions() }
|
||||
}
|
||||
|
||||
fun snapshot() {
|
||||
forEach { row -> row.snapshot() }
|
||||
}
|
||||
|
||||
fun undo(): Boolean {
|
||||
return map { row -> row.undo() }.reduce { a, b -> a || b }
|
||||
}
|
||||
}
|
||||
|
||||
fun List<List<Item<ItemClass<*>>>>.toGrid() = Grid(
|
||||
|
||||
@@ -2,28 +2,62 @@ package ch.dissem.yaep.domain
|
||||
|
||||
class ObservableSet<E> private constructor(
|
||||
private val mutableSet: MutableSet<E>,
|
||||
private val onElementRemoved: (Set<E>) -> Unit
|
||||
private val onElementChanged: (before: Set<E>, after: Set<E>) -> Unit
|
||||
) : MutableSet<E> by mutableSet {
|
||||
constructor(elements: Collection<E>, onElementRemoved: (Set<E>) -> Unit) : this(
|
||||
constructor(
|
||||
elements: Collection<E>,
|
||||
onElementChanged: (before: Set<E>, after: Set<E>) -> Unit
|
||||
) : this(
|
||||
elements.toMutableSet(),
|
||||
onElementRemoved
|
||||
onElementChanged
|
||||
)
|
||||
|
||||
private inner class ObservableIterator(
|
||||
private val delegate: MutableIterator<E>
|
||||
) : MutableIterator<E> by delegate {
|
||||
|
||||
override fun remove() {
|
||||
val previous = mutableSet.toSet()
|
||||
delegate.remove()
|
||||
onElementChanged(previous, mutableSet.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
override fun iterator(): MutableIterator<E> {
|
||||
return ObservableIterator(mutableSet.iterator())
|
||||
}
|
||||
|
||||
override fun add(element: E): Boolean {
|
||||
return observe(setOf(element)) { mutableSet.add(element) }
|
||||
}
|
||||
|
||||
|
||||
override fun remove(element: E): Boolean {
|
||||
return observeRemoval(setOf(element)) { mutableSet.remove(element) }
|
||||
return observe(setOf(element)) { mutableSet.remove(element) }
|
||||
}
|
||||
|
||||
override fun addAll(elements: Collection<E>): Boolean {
|
||||
return observe(elements.toSet()) { mutableSet.addAll(elements) }
|
||||
}
|
||||
|
||||
override fun removeAll(elements: Collection<E>): Boolean =
|
||||
observeRemoval(elements.toSet(), mutableSet::removeAll)
|
||||
observe(elements.toSet(), mutableSet::removeAll)
|
||||
|
||||
override fun retainAll(elements: Collection<E>): Boolean =
|
||||
observeRemoval(elements.toSet(), mutableSet::retainAll)
|
||||
observe(elements.toSet(), mutableSet::retainAll)
|
||||
|
||||
private fun observeRemoval(values: Set<E>, operation: (Set<E>) -> Boolean): Boolean {
|
||||
val removed = operation(values)
|
||||
if (removed) {
|
||||
onElementRemoved(values)
|
||||
override fun clear() {
|
||||
return observe(setOf()) { mutableSet.clear() }
|
||||
}
|
||||
|
||||
private fun <R> observe(values: Set<E>, operation: (Set<E>) -> R): R {
|
||||
val previous = mutableSet.toSet()
|
||||
val result = operation(values)
|
||||
val changed = !(previous.containsAll(mutableSet) && previous.size == mutableSet.size)
|
||||
if (changed) {
|
||||
val new = mutableSet.toSet()
|
||||
onElementChanged(previous, new)
|
||||
}
|
||||
return removed
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user