diff --git a/assets/martian.tweak.toml b/assets/martian.tweak.toml index da8cba5..e761046 100644 --- a/assets/martian.tweak.toml +++ b/assets/martian.tweak.toml @@ -269,4 +269,12 @@ QueenRed = "QueenRedPieceIdle" fast_movement_speed = 1.0 fast_text_speed = 999.0 fast_dissolve_speed = 1.0 -fast_light_speed = 1.0 \ No newline at end of file +fast_light_speed = 1.0 + +############## +### Game rules +############## +# 0: Disabled +# -1: No limit +# N: Some value in-between +undo_limit = -1 \ No newline at end of file diff --git a/src/display3d.rs b/src/display3d.rs index 64836ad..ca185e6 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -1117,7 +1117,7 @@ fn capture_piece_end( mut events: RemovedComponents, mut query: Query<(Entity, &Dissolvable, &Side, &mut Transform), With>, mut commands: Commands, - + board: Res, score: Res, ) { events.read().for_each(|e| { @@ -1128,7 +1128,7 @@ fn capture_piece_end( .entity(entity) .insert(Dissolving::In(dissolvable.duration)) .remove::() - .insert(Captured); + .insert(Captured { epoch: board.current_epoch() - 1 }); } }); } diff --git a/src/game.rs b/src/game.rs index 64a0dc7..ef1a710 100644 --- a/src/game.rs +++ b/src/game.rs @@ -12,7 +12,10 @@ impl Plugin for GamePlugin { .add_systems(OnEnter(GameState::Play), hide_valid_moves) .add_systems( Update, - manage_state_entities::().run_if(state_changed::), + ( + manage_state_entities::().run_if(state_changed::), + undo_move.run_if(just_pressed(KeyCode::KeyU)), + ) ) .add_systems( Update, @@ -183,7 +186,9 @@ pub(crate) struct BeingCaptured; /// Marker for component which is captured #[derive(Debug, Component)] -pub(crate) struct Captured; +pub(crate) struct Captured { + pub epoch: usize, +} #[derive(Debug, Component)] pub(crate) struct Promoted; @@ -253,6 +258,7 @@ pub(crate) struct Board { #[derive(Debug, Default, Event, Clone)] pub(crate) struct Move { pub epoch: usize, + pub piece: Option, pub from: BoardIndex, pub to: Option, pub move_type: MoveType, @@ -437,6 +443,7 @@ impl Board { if self.inner[to.y][to.x].is_some() { moves.push(Move { epoch, + piece: self.at(to).copied(), from: to, to: None, move_type: move_type.clone(), @@ -446,6 +453,7 @@ impl Board { // Capture the intended move in the moves ledger moves.push(Move { epoch, + piece: self.at(from).copied(), from, to: Some(to), move_type: move_type.clone(), @@ -478,6 +486,29 @@ impl Board { } } + /// Undo the last move + fn undo_move(&mut self) -> Vec { + let latest_epoch = self.current_epoch() - 1; + + let mut moves = vec![]; + + while let Some(last) = self.moves.last() { + if last.epoch == latest_epoch { + let last = self.moves.pop().unwrap(); + self.inner[last.from.y][last.from.x] = last.piece; + if let Some(to) = last.to { + self.inner[to.y][to.x] = None; + } + moves.push(last); + } else { + break; + } + } + + info!("Remaining moves: {:?}", self.moves); + return moves; + } + /// Returns the Side of a piece pub(crate) fn side(BoardIndex { x, .. }: BoardIndex) -> Result { match x { @@ -576,6 +607,7 @@ impl Board { Some(MoveType::Capture) } None => { + info!("Last move: {:?}", self.moves.last()); // move is valid if it does not un-do the previous move for this piece match self.moves.last() { Some(previous) => { @@ -1505,4 +1537,58 @@ fn assert_piece_consistency( info!("Active: {} | being captured: {} | captured: {}", active_count, being_captured_count, captured_count); let total_count = active_count + being_captured_count + captured_count; assert_eq!(total_count, 18, "Pieces does does not add up!"); +} + +// When user presses 'u' last move is undone +fn undo_move( + mut board: ResMut, + mut active_pieces: Query<&mut BoardIndex, With>, + captured_pieces: Query<(Entity, &Captured), With>, + turn: Res>, + mut next_turn: ResMut>, + mut commands: Commands, +) { + // Keep track of the current side in case we need to go back + let mut side = turn.get().0; + + board + .undo_move() + .iter() + .for_each(|Move { epoch, from, to, .. }| { + // If we have any moves to do, go back to the opposite side + if side == turn.get().0 { + side = !turn.get().0; + } + + info!("Reverting move {:?} {:?} -> {:?}", epoch, from, to); + match to { Some(to_idx) => { info!("Moving piece back from {:?}", to_idx); + // Find piece currently at "to_idx" and update it's position to "from" + active_pieces + .iter_mut() + .filter(|idx| { + (idx.x, idx.y) == (to_idx.x, to_idx.y) + }) + .for_each(|mut idx| { + *idx = *from + }); + }, + None => { + captured_pieces + .iter() + .find_map(|(entity, captured)| { + (captured.epoch == *epoch).then_some(entity) + }) + .iter() + .for_each(|entity| { + commands + .entity(*entity) + .remove::() + .insert(*from); + }); + } + } + }); + + // Set the turn state (may be a no-op) + next_turn.set(TurnState(side)); } \ No newline at end of file