harden engine rules, position invariants, and undo history

- make Position represent only valid board squares and use checked offsets
- make move endpoints fallible and remove unchecked coordinate arithmetic
- validate moves against generated legal moves, including mandatory capture
- track pending multi-captures and restrict continuation to the same piece
- derive pending state after undo/redo and preserve redo on invalid moves
- fix circular buffer wraparound, mixed iteration, and element drops
- adapt GTK and WASM to fallible endpoints and apply errors
- add rule, promotion, chain, history, and coordinate regression tests
This commit is contained in:
2026-10-19 19:43:05 +00:00
parent 5ab62eaabc
commit dc0ad17a95
8 changed files with 750 additions and 461 deletions
+8 -6
View File
@@ -252,7 +252,7 @@ impl App {
let available_moves: Vec<Position> = self
.available_moves
.iter()
.map(Move::get_end_position)
.filter_map(Move::get_end_position)
.collect();
for i in 0..DraughtsBoard::rows() {
for j in 0..DraughtsBoard::columns() {
@@ -274,7 +274,8 @@ impl App {
ctx.fill_rect(cell.tl().x(), cell.tl().y(), cell.width(), cell.height());
if (i + j) % 2 != 0 {
let current_player = Player::White;
let position = Position::from_index((map_row(current_player, j), i));
let position = Position::new(map_row(current_player, j) as u8, i as u8)
.expect("board loop only visits valid squares");
let piece = self.game.piece_at(position);
if piece.is_present() {
piece_drawer.draw_piece(ctx, &self.xform, cell.clone(), piece)?;
@@ -412,13 +413,14 @@ fn main() -> Result<(), JsValue> {
let selected_move = app
.available_moves
.iter()
.find(|mv| mv.get_end_position() == position)
.find(|mv| mv.get_end_position() == Some(position))
.map(Move::clone);
if app.selected_piece.is_some() && selected_move.is_some() {
let mv = selected_move.unwrap();
app.game.check_and_apply_move(&mv).unwrap();
app.selected_piece = None;
app.available_moves.clear();
if app.game.check_and_apply_move(&mv).is_ok() {
app.selected_piece = None;
app.available_moves.clear();
}
} else {
app.selected_piece = Some(position);
let mut available_moves = Vec::<Move>::new();