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