Skip to content

Commit 203145c

Browse files
authored
generic undo (#175)
1 parent 214724e commit 203145c

File tree

148 files changed

+3360
-2872
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+3360
-2872
lines changed

client/Cargo.lock

+43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/local_client/bin/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub async fn run(mut game: Game, features: &Features) {
5656
GameSyncRequest::None => {}
5757
GameSyncRequest::ExecuteAction(a) => {
5858
let p = game.active_player();
59-
execute_action(&mut game, a, p);
59+
game = execute_action(game, a, p);
6060
state.show_player = game.active_player();
6161
sync_result = GameSyncResult::Update;
6262
}

server/Cargo.lock

+43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ itertools = "0.14.0"
2626
quad-rand = "0.2.3"
2727
console_error_panic_hook = "0.1.7"
2828
num = "0.4"
29+
json-patch = "4.0.0"

server/src/ability_initializer.rs

+4-32
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use crate::content::custom_phase_actions::{
2-
AdvanceRequest, CurrentEventHandler, CurrentEventRequest, CurrentEventResponse,
3-
CurrentEventType, MultiRequest, PaymentRequest, PlayerRequest, PositionRequest,
4-
ResourceRewardRequest, SelectedStructure, StructuresRequest, UnitTypeRequest, UnitsRequest,
2+
AdvanceRequest, CurrentEventHandler, CurrentEventRequest, CurrentEventResponse, MultiRequest,
3+
PaymentRequest, PlayerRequest, PositionRequest, ResourceRewardRequest, SelectedStructure,
4+
StructuresRequest, UnitTypeRequest, UnitsRequest,
55
};
66
use crate::events::{Event, EventOrigin};
7-
use crate::player_events::{CurrentEvent, PlayerCommands};
7+
use crate::player_events::CurrentEvent;
88
use crate::position::Position;
99
use crate::resource_pile::ResourcePile;
10-
use crate::undo::UndoContext;
1110
use crate::unit::UnitType;
1211
use crate::{content::custom_actions::CustomActionType, game::Game, player_events::PlayerEvents};
1312
use std::collections::HashMap;
@@ -39,16 +38,6 @@ impl<'a, C, V> SelectedChoice<'a, C, V> {
3938
details,
4039
}
4140
}
42-
43-
pub(crate) fn to_commands(
44-
&self,
45-
game: &mut Game,
46-
gain: impl Fn(&mut PlayerCommands, &Game, &C) + 'static + Clone,
47-
) {
48-
game.with_commands(self.player_index, |commands, game| {
49-
gain(commands, game, &self.choice);
50-
});
51-
}
5241
}
5342

5443
pub struct AbilityListeners {
@@ -228,10 +217,6 @@ pub(crate) trait AbilityInitializerSetup: Sized {
228217
.as_mut()
229218
.expect("current missing")
230219
.response = None;
231-
if can_undo(&current.event_type) {
232-
game.undo_context_stack
233-
.push(UndoContext::Event(Box::new(current)));
234-
}
235220
let r = c.request.clone();
236221
let a = action.clone();
237222
phase.player.handler = None;
@@ -758,16 +743,3 @@ pub(crate) fn join_ability_initializers(setup: Vec<AbilityInitializer>) -> Abili
758743
}
759744
})
760745
}
761-
762-
fn can_undo(event_type: &CurrentEventType) -> bool {
763-
use CurrentEventType::*;
764-
matches!(
765-
event_type,
766-
Advance(_)
767-
| TurnStart
768-
| Construct(_)
769-
| Recruit(_)
770-
| InfluenceCultureResolution(_)
771-
| ExploreResolution(_)
772-
)
773-
}

server/src/action.rs

+32-41
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ use crate::incident::trigger_incident;
1111
use crate::log;
1212
use crate::map::Terrain::Unexplored;
1313
use crate::movement::{
14-
has_movable_units, move_units_destinations, stop_current_move, take_move_state, CurrentMove,
15-
MoveState,
14+
has_movable_units, move_units_destinations, take_move_state, CurrentMove, MoveState,
1615
};
1716
use crate::playing_actions::PlayingAction;
1817
use crate::recruit::on_recruit;
1918
use crate::resource::check_for_waste;
2019
use crate::resource_pile::ResourcePile;
2120
use crate::status_phase::play_status_phase;
22-
use crate::undo::{redo, undo, DisembarkUndoContext, UndoContext};
21+
use crate::undo::{clean_patch, redo, to_serde_value, undo};
2322
use crate::unit::MovementAction::{Move, Stop};
2423
use crate::unit::{get_current_move, MovementAction};
2524
use crate::wonder::draw_wonder_card;
@@ -78,36 +77,51 @@ impl Action {
7877
/// # Panics
7978
///
8079
/// Panics if the action is illegal
81-
pub fn execute_action(game: &mut Game, action: Action, player_index: usize) {
80+
#[must_use]
81+
pub fn execute_action(mut game: Game, action: Action, player_index: usize) -> Game {
82+
let add_undo = !matches!(&action, Action::Undo);
83+
let old = to_serde_value(&game);
84+
let old_player = game.active_player();
85+
game = execute_without_undo(game, action, player_index);
86+
let new = to_serde_value(&game);
87+
let new_player = game.active_player();
88+
let patch = json_patch::diff(&new, &old);
89+
if old_player != new_player {
90+
game.lock_undo(); // don't undo player change
91+
} else if add_undo && game.can_undo() {
92+
game.action_log[game.action_log_index - 1].undo = clean_patch(patch.0);
93+
}
94+
game
95+
}
96+
97+
fn execute_without_undo(mut game: Game, action: Action, player_index: usize) -> Game {
8298
assert!(player_index == game.active_player(), "Illegal action");
8399
if let Action::Undo = action {
84100
assert!(
85101
game.can_undo(),
86102
"actions revealing new information can't be undone"
87103
);
88-
undo(game, player_index);
89-
return;
104+
return undo(game);
90105
}
91106

92107
if matches!(action, Action::Redo) {
93-
assert!(game.can_redo(), "no action can be redone");
94-
redo(game, player_index);
95-
return;
108+
assert!(game.can_redo(), "action can't be redone");
109+
redo(&mut game, player_index);
110+
return game;
96111
}
97112

98-
add_log_item_from_action(game, &action);
99-
add_action_log_item(game, action.clone());
113+
add_log_item_from_action(&mut game, &action);
114+
add_action_log_item(&mut game, action.clone());
100115

101116
if let Some(s) = game.current_event_handler_mut() {
102117
s.response = action.custom_phase_event();
103118
let details = game.current_event().event_type.clone();
104-
execute_custom_phase_action(game, player_index, &details);
119+
execute_custom_phase_action(&mut game, player_index, &details);
105120
} else {
106-
execute_regular_action(game, action, player_index);
121+
execute_regular_action(&mut game, action, player_index);
107122
}
108-
check_for_waste(game);
109-
110-
game.action_log[game.action_log_index - 1].undo = std::mem::take(&mut game.undo_context_stack);
123+
check_for_waste(&mut game);
124+
game
111125
}
112126

113127
fn add_action_log_item(game: &mut Game, item: Action) {
@@ -138,7 +152,6 @@ pub(crate) fn execute_custom_phase_action(
138152
start_combat(game);
139153
}
140154
CombatRoundEnd(r) => {
141-
game.lock_undo();
142155
if let Some(c) = combat_round_end(game, r) {
143156
combat_loop(game, c);
144157
}
@@ -202,28 +215,14 @@ pub(crate) fn execute_movement_action(
202215
action: MovementAction,
203216
player_index: usize,
204217
) {
205-
let Some(GameState::Movement(saved_state)) = game.state_stack.last().cloned() else {
206-
panic!("game should be in a movement state");
207-
};
208-
let (starting_position, disembarked_units) = match action {
218+
match action {
209219
Move(m) => {
210220
let player = &game.players[player_index];
211221
let starting_position = player
212222
.get_unit(*m.units.first().expect(
213223
"instead of providing no units to move a stop movement actions should be done",
214224
))
215225
.position;
216-
let disembarked_units = m
217-
.units
218-
.iter()
219-
.filter_map(|unit| {
220-
let unit = player.get_unit(*unit);
221-
unit.carrier_id.map(|carrier_id| DisembarkUndoContext {
222-
unit_id: unit.id,
223-
carrier_id,
224-
})
225-
})
226-
.collect();
227226
match move_units_destinations(
228227
player,
229228
game,
@@ -283,26 +282,18 @@ pub(crate) fn execute_movement_action(
283282
starting_position,
284283
m.destination,
285284
);
286-
stop_current_move(game);
287-
return; // can't undo this action
285+
return;
288286
}
289287

290288
if move_with_possible_combat(game, player_index, starting_position, &m) {
291289
return;
292290
}
293-
294-
(Some(starting_position), disembarked_units)
295291
}
296292
Stop => {
297293
game.pop_state();
298294
return;
299295
}
300296
};
301-
game.push_undo_context(UndoContext::Movement {
302-
starting_position,
303-
move_state: saved_state,
304-
disembarked_units,
305-
});
306297

307298
let state = take_move_state(game);
308299
let all_moves_used =

server/src/advance.rs

-12
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,6 @@ pub(crate) fn gain_advance(game: &mut Game, player_index: usize, info: &AdvanceI
204204
}
205205
}
206206

207-
pub(crate) fn undo_advance(
208-
game: &mut Game,
209-
advance: &Advance,
210-
player_index: usize,
211-
was_custom_phase: bool,
212-
) {
213-
remove_advance(game, advance, player_index);
214-
if !was_custom_phase {
215-
game.players[player_index].incident_tokens += 1;
216-
}
217-
}
218-
219207
pub(crate) fn remove_advance(game: &mut Game, advance: &Advance, player_index: usize) {
220208
(advance.listeners.deinitializer)(game, player_index);
221209
(advance.listeners.undo_deinitializer)(game, player_index);

server/src/city.rs

-9
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,6 @@ impl City {
9494
self.angry_activation = false;
9595
}
9696

97-
pub fn undo_activate(&mut self) {
98-
self.activations -= 1;
99-
if self.angry_activation {
100-
self.angry_activation = false;
101-
} else if self.is_activated() {
102-
self.increase_mood_state();
103-
}
104-
}
105-
10697
#[must_use]
10798
pub fn is_activated(&self) -> bool {
10899
self.activations > 0

0 commit comments

Comments
 (0)