solverforge_solver/heuristic/move/
composite.rs

1//! CompositeMove - applies two moves in sequence by arena indices.
2//!
3//! This move stores indices into two arenas. The moves themselves
4//! live in their respective arenas - CompositeMove just references them.
5//!
6//! # Zero-Erasure Design
7//!
8//! No cloning, no boxing - just typed arena indices.
9
10use std::fmt::Debug;
11use std::marker::PhantomData;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::{Move, MoveArena};
17
18/// A move that applies two moves in sequence via arena indices.
19///
20/// The moves live in separate arenas. CompositeMove stores the indices
21/// and arena references needed to execute both moves.
22///
23/// # Type Parameters
24/// * `S` - The planning solution type
25/// * `M1` - The first move type
26/// * `M2` - The second move type
27pub struct CompositeMove<S, M1, M2>
28where
29    S: PlanningSolution,
30    M1: Move<S>,
31    M2: Move<S>,
32{
33    index_1: usize,
34    index_2: usize,
35    _phantom: PhantomData<(S, M1, M2)>,
36}
37
38impl<S, M1, M2> CompositeMove<S, M1, M2>
39where
40    S: PlanningSolution,
41    M1: Move<S>,
42    M2: Move<S>,
43{
44    /// Creates a new composite move from two arena indices.
45    pub fn new(index_1: usize, index_2: usize) -> Self {
46        Self {
47            index_1,
48            index_2,
49            _phantom: PhantomData,
50        }
51    }
52
53    /// Returns the first move's arena index.
54    pub fn index_1(&self) -> usize {
55        self.index_1
56    }
57
58    /// Returns the second move's arena index.
59    pub fn index_2(&self) -> usize {
60        self.index_2
61    }
62
63    /// Checks if this composite move is doable given both arenas.
64    pub fn is_doable_with_arenas<D: ScoreDirector<S>>(
65        &self,
66        arena_1: &MoveArena<M1>,
67        arena_2: &MoveArena<M2>,
68        score_director: &D,
69    ) -> bool {
70        let m1 = arena_1.get(self.index_1);
71        let m2 = arena_2.get(self.index_2);
72
73        match (m1, m2) {
74            (Some(m1), Some(m2)) => m1.is_doable(score_director) || m2.is_doable(score_director),
75            _ => false,
76        }
77    }
78
79    /// Executes both moves using the arenas.
80    pub fn do_move_with_arenas<D: ScoreDirector<S>>(
81        &self,
82        arena_1: &MoveArena<M1>,
83        arena_2: &MoveArena<M2>,
84        score_director: &mut D,
85    ) {
86        if let Some(m1) = arena_1.get(self.index_1) {
87            m1.do_move(score_director);
88        }
89        if let Some(m2) = arena_2.get(self.index_2) {
90            m2.do_move(score_director);
91        }
92    }
93}
94
95impl<S, M1, M2> Clone for CompositeMove<S, M1, M2>
96where
97    S: PlanningSolution,
98    M1: Move<S>,
99    M2: Move<S>,
100{
101    fn clone(&self) -> Self {
102        *self
103    }
104}
105
106impl<S, M1, M2> Copy for CompositeMove<S, M1, M2>
107where
108    S: PlanningSolution,
109    M1: Move<S>,
110    M2: Move<S>,
111{
112}
113
114impl<S, M1, M2> Debug for CompositeMove<S, M1, M2>
115where
116    S: PlanningSolution,
117    M1: Move<S>,
118    M2: Move<S>,
119{
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("CompositeMove")
122            .field("index_1", &self.index_1)
123            .field("index_2", &self.index_2)
124            .finish()
125    }
126}