fix and improve undo/redo

This commit is contained in:
mxhagen 2025-04-01 22:04:24 +02:00
parent 5ba57837d0
commit 90dabbdfda

View File

@ -19,8 +19,8 @@ pub struct State {
pub difficulty: Difficulty,
pub start_time: time::Instant,
pub undo_stack: Vec<UndoStep>,
pub redo_stack: Vec<UndoStep>,
pub undo_stack: Vec<DiffStep>,
pub redo_stack: Vec<DiffStep>,
}
impl State {
@ -102,19 +102,33 @@ impl State {
if *self.current_cell() == self.preselection {
self.delete_current_cell();
} else {
let old_num = *self.current_cell();
*self.current_cell() = self.preselection;
self.delete_colliding_marks(self.preselection, self.cur_row, self.cur_col);
let affected =
self.delete_colliding_marks(self.preselection, self.cur_row, self.cur_col);
self.push_to_undos_invalidating_redos(DiffStep::Edit(
old_num,
(self.cur_row, self.cur_col),
affected,
self.preselection,
))
}
}
}
pub fn delete_current_cell(&mut self) {
if self.current_cell_is_modifiable() {
*self.current_cell() = 0;
let cur = self.current_cell();
let old = *cur;
*cur = 0;
self.push_to_undo_stack(UndoStep::Replace(
self.preselection,
(self.cur_row as u8, self.cur_col as u8),
self.push_to_undos_invalidating_redos(DiffStep::Edit(
old,
(self.cur_row, self.cur_col),
vec![],
0,
));
}
}
@ -122,25 +136,26 @@ impl State {
/// deletes all marks of `num` in it's row, column and block.
/// used to automatically remove marks when placing a number that
/// invalidates those marks.
pub fn delete_colliding_marks(&mut self, num: u8, row: usize, col: usize) {
let mut deleted = [None; 27];
let mut pos = 0;
///
/// returns a vector of the affected marks board positions
pub fn delete_colliding_marks(
&mut self,
num: u8,
row: usize,
col: usize,
) -> Vec<(usize, usize)> {
let mut deleted = Vec::new();
for i in 0..9 {
if self.markups[i][col][num as usize - 1] {
deleted[pos] = Some((i as u8, col as u8));
pos += 1;
deleted.push((i, col));
}
if self.markups[row][i][num as usize - 1] {
deleted[pos] = Some((row as u8, i as u8));
pos += 1;
deleted.push((row, i));
}
// TODO: avoid doubly including marks that are both in the same block AND row/col
if self.markups[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3][num as usize - 1] {
deleted[pos] = Some((
row as u8 / 3 * 3 + i as u8 / 3,
col as u8 / 3 * 3 + i as u8 % 3,
));
pos += 1;
deleted.push((row / 3 * 3 + i / 3, col / 3 * 3 + i % 3));
}
self.markups[i][col][num as usize - 1] = false;
@ -148,11 +163,7 @@ impl State {
self.markups[row / 3 * 3 + i / 3][col / 3 * 3 + i % 3][num as usize - 1] = false;
}
self.push_to_undo_stack(UndoStep::Unplace(
self.preselection,
(row as u8, col as u8),
deleted,
));
deleted
}
pub fn toggle_current_mark(&mut self) {
@ -168,11 +179,13 @@ impl State {
return;
}
let marked = self.markups[self.cur_row][self.cur_col][self.preselection as usize - 1];
self.markups[self.cur_row][self.cur_col][self.preselection as usize - 1] = false;
self.push_to_undo_stack(UndoStep::Remark(
self.push_to_undos_invalidating_redos(DiffStep::Mark(
self.preselection,
(self.cur_row as u8, self.cur_col as u8),
(self.cur_row, self.cur_col),
marked,
));
}
@ -181,11 +194,13 @@ impl State {
return;
}
let marked = self.markups[self.cur_row][self.cur_col][self.preselection as usize - 1];
self.markups[self.cur_row][self.cur_col][self.preselection as usize - 1] = true;
self.push_to_undo_stack(UndoStep::Unmark(
self.push_to_undos_invalidating_redos(DiffStep::Mark(
self.preselection,
(self.cur_row as u8, self.cur_col as u8),
(self.cur_row, self.cur_col),
marked,
));
}
@ -303,59 +318,54 @@ impl State {
/// undo an action that has been taken
pub fn undo(&mut self) {
use UndoStep::*;
if let Some(undo_step) = self.undo_stack.pop() {
self.redo_stack.push(undo_step);
match undo_step {
Unplace(num, (row, col), deleted_marks) => {
self.board[row as usize][col as usize] = 0;
for (r, c) in deleted_marks.iter().take_while(|x| x.is_some()).flatten() {
self.markups[*r as usize][*c as usize][num as usize - 1] = true;
}
}
Replace(num, (row, col)) => {
self.board[row as usize][col as usize] = num;
}
Remark(num, (row, col)) => {
self.markups[row as usize][col as usize][num as usize - 1] = true;
}
Unmark(num, (row, col)) => {
self.markups[row as usize][col as usize][num as usize - 1] = false;
}
}
}
self.apply_diff(DiffType::Undo);
}
/// redo an action if one was taken and undone.
pub fn redo(&mut self) {
use UndoStep::*;
self.apply_diff(DiffType::Redo);
}
if let Some(redo_step) = self.redo_stack.pop() {
self.undo_stack.push(redo_step);
match redo_step {
Unplace(num, (row, col), deleted_marks) => {
self.board[row as usize][col as usize] = num;
for (r, c) in deleted_marks.iter().take_while(|x| x.is_some()).flatten() {
self.markups[*r as usize][*c as usize][num as usize - 1] = false;
/// apply a undo/redo step diff and get back the inverse step
pub fn apply_diff(&mut self, diff_type: DiffType) {
use DiffStep::*;
use DiffType::*;
let (used_stack, other_stack) = match diff_type {
Redo => (&mut self.redo_stack, &mut self.undo_stack),
Undo => (&mut self.undo_stack, &mut self.redo_stack),
};
if let Some(diff) = used_stack.pop() {
match diff {
Edit(original, (r, c), marks, replacement) => {
self.board[r][c] = original;
let affected_mark_num = match diff_type {
Redo => original,
Undo => replacement,
};
if affected_mark_num != 0 {
marks.iter().for_each(|&(r, c)| {
self.markups[r][c][affected_mark_num as usize - 1] =
!self.markups[r][c][affected_mark_num as usize - 1]
});
}
other_stack.push(DiffStep::Edit(replacement, (r, c), marks, original));
}
Replace(_, (row, col)) => {
self.board[row as usize][col as usize] = 0;
}
Remark(num, (row, col)) => {
self.markups[row as usize][col as usize][num as usize - 1] = false;
}
Unmark(num, (row, col)) => {
self.markups[row as usize][col as usize][num as usize - 1] = true;
Mark(num, (r, c), mark) => {
let old_mark = self.markups[r][c][num as usize - 1];
self.markups[r][c][num as usize - 1] = mark;
other_stack.push(DiffStep::Mark(self.preselection, (r, c), old_mark));
}
}
}
}
/// pushes a move done by the player to the undo stack
/// this additionally invalidates the redo stack
pub fn push_to_undo_stack(&mut self, undo_step: UndoStep) {
/// additionally invalidating the redo stack
pub fn push_to_undos_invalidating_redos(&mut self, undo_step: DiffStep) {
self.undo_stack.push(undo_step);
self.redo_stack.clear();
}
@ -380,18 +390,17 @@ pub enum Mode {
Go,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum UndoStep {
/// unplace num at (row, col) and re-mark
/// (row, col) if there were removed marks
Unplace(u8, (u8, u8), [Option<(u8, u8)>; 27]),
#[derive(Debug, PartialEq, Clone)]
pub enum DiffStep {
/// `Edit(num, position, affected_mark_positions, new_num)`
Edit(u8, (usize, usize), Vec<(usize, usize)>, u8),
/// re-place num at (row, col)
Replace(u8, (u8, u8)),
/// unmark num at (row, col)
Unmark(u8, (u8, u8)),
/// re-mark num at (row, col)
Remark(u8, (u8, u8)),
/// `Mark(num, position, mark)`
Mark(u8, (usize, usize), bool),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DiffType {
Undo,
Redo,
}