Skip to main content

solverforge_core/score/
bendable.rs

1/* BendableScore - Compile-time configurable multi-level score
2
3Uses const generics for zero-erasure. Level counts are determined at compile time.
4*/
5
6use std::cmp::Ordering;
7use std::fmt;
8use std::ops::{Add, Neg, Sub};
9
10use super::traits::Score;
11use super::ScoreLevel;
12
13/* A score with a configurable number of hard and soft levels.
14
15Level counts are const generic parameters, enabling Copy and zero heap allocation.
16
17# Type Parameters
18
19* `H` - Number of hard score levels
20* `S` - Number of soft score levels
21
22# Examples
23
24```
25use solverforge_core::score::{BendableScore, Score};
26
27// Create a score with 2 hard levels and 3 soft levels
28let score: BendableScore<2, 3> = BendableScore::of([-1, -2], [-10, -20, -30]);
29
30assert_eq!(score.hard_levels_count(), 2);
31assert_eq!(score.soft_levels_count(), 3);
32assert!(!score.is_feasible());  // Negative hard scores
33```
34*/
35#[derive(Clone, Copy, PartialEq, Eq, Hash)]
36pub struct BendableScore<const H: usize, const S: usize> {
37    hard: [i64; H],
38    soft: [i64; S],
39}
40
41impl<const H: usize, const S: usize> BendableScore<H, S> {
42    pub const fn of(hard: [i64; H], soft: [i64; S]) -> Self {
43        BendableScore { hard, soft }
44    }
45
46    pub const fn zero() -> Self {
47        BendableScore {
48            hard: [0; H],
49            soft: [0; S],
50        }
51    }
52
53    pub const fn hard_levels_count(&self) -> usize {
54        H
55    }
56
57    pub const fn soft_levels_count(&self) -> usize {
58        S
59    }
60
61    /// Returns the hard score at the given level.
62    ///
63    /// # Panics
64    /// Panics if the level is out of bounds.
65    pub const fn hard_score(&self, level: usize) -> i64 {
66        self.hard[level]
67    }
68
69    /// Returns the soft score at the given level.
70    ///
71    /// # Panics
72    /// Panics if the level is out of bounds.
73    pub const fn soft_score(&self, level: usize) -> i64 {
74        self.soft[level]
75    }
76
77    pub const fn hard_scores(&self) -> &[i64; H] {
78        &self.hard
79    }
80
81    pub const fn soft_scores(&self) -> &[i64; S] {
82        &self.soft
83    }
84
85    pub const fn one_hard(level: usize) -> Self {
86        let mut hard = [0; H];
87        hard[level] = 1;
88        BendableScore { hard, soft: [0; S] }
89    }
90
91    pub const fn one_soft(level: usize) -> Self {
92        let mut soft = [0; S];
93        soft[level] = 1;
94        BendableScore { hard: [0; H], soft }
95    }
96}
97
98impl<const H: usize, const S: usize> Default for BendableScore<H, S> {
99    fn default() -> Self {
100        Self::zero()
101    }
102}
103
104impl<const H: usize, const S: usize> Score for BendableScore<H, S> {
105    fn is_feasible(&self) -> bool {
106        self.hard.iter().all(|&s| s >= 0)
107    }
108
109    fn zero() -> Self {
110        BendableScore::zero()
111    }
112
113    fn levels_count() -> usize {
114        H + S
115    }
116
117    fn level_number(&self, index: usize) -> i64 {
118        if index < H {
119            self.hard[index]
120        } else if index < H + S {
121            self.soft[index - H]
122        } else {
123            panic!(
124                "BendableScore<{}, {}> has {} levels, got index {}",
125                H,
126                S,
127                H + S,
128                index
129            )
130        }
131    }
132
133    fn from_level_numbers(levels: &[i64]) -> Self {
134        assert!(levels.len() >= H + S, "Not enough levels provided");
135        let mut hard = [0; H];
136        let mut soft = [0; S];
137        hard.copy_from_slice(&levels[..H]);
138        soft.copy_from_slice(&levels[H..H + S]);
139        BendableScore { hard, soft }
140    }
141
142    fn multiply(&self, multiplicand: f64) -> Self {
143        let mut hard = [0; H];
144        let mut soft = [0; S];
145        for (i, item) in hard.iter_mut().enumerate().take(H) {
146            *item = (self.hard[i] as f64 * multiplicand).round() as i64;
147        }
148        for (i, item) in soft.iter_mut().enumerate().take(S) {
149            *item = (self.soft[i] as f64 * multiplicand).round() as i64;
150        }
151        BendableScore { hard, soft }
152    }
153
154    fn divide(&self, divisor: f64) -> Self {
155        let mut hard = [0; H];
156        let mut soft = [0; S];
157        for (i, item) in hard.iter_mut().enumerate().take(H) {
158            *item = (self.hard[i] as f64 / divisor).round() as i64;
159        }
160        for (i, item) in soft.iter_mut().enumerate().take(S) {
161            *item = (self.soft[i] as f64 / divisor).round() as i64;
162        }
163        BendableScore { hard, soft }
164    }
165
166    fn abs(&self) -> Self {
167        let mut hard = [0; H];
168        let mut soft = [0; S];
169        for (i, item) in hard.iter_mut().enumerate().take(H) {
170            *item = self.hard[i].abs();
171        }
172        for (i, item) in soft.iter_mut().enumerate().take(S) {
173            *item = self.soft[i].abs();
174        }
175        BendableScore { hard, soft }
176    }
177
178    fn level_label(index: usize) -> ScoreLevel {
179        if index < H {
180            ScoreLevel::Hard
181        } else if index < H + S {
182            ScoreLevel::Soft
183        } else {
184            panic!(
185                "BendableScore<{}, {}> has {} levels, got index {}",
186                H,
187                S,
188                H + S,
189                index
190            )
191        }
192    }
193
194    #[inline]
195    fn to_scalar(&self) -> f64 {
196        let total_levels = H + S;
197        let mut scalar = 0.0;
198        for (i, &v) in self.hard.iter().enumerate() {
199            let weight = 10_f64.powi(((total_levels - 1 - i) * 6) as i32);
200            scalar += v as f64 * weight;
201        }
202        for (i, &v) in self.soft.iter().enumerate() {
203            let weight = 10_f64.powi(((S - 1 - i) * 6) as i32);
204            scalar += v as f64 * weight;
205        }
206        scalar
207    }
208}
209
210impl<const H: usize, const S: usize> Ord for BendableScore<H, S> {
211    fn cmp(&self, other: &Self) -> Ordering {
212        // Compare hard scores first (highest priority first)
213        for i in 0..H {
214            match self.hard[i].cmp(&other.hard[i]) {
215                Ordering::Equal => continue,
216                ord => return ord,
217            }
218        }
219
220        // Then compare soft scores
221        for i in 0..S {
222            match self.soft[i].cmp(&other.soft[i]) {
223                Ordering::Equal => continue,
224                ord => return ord,
225            }
226        }
227
228        Ordering::Equal
229    }
230}
231
232impl<const H: usize, const S: usize> PartialOrd for BendableScore<H, S> {
233    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
234        Some(self.cmp(other))
235    }
236}
237
238impl<const H: usize, const S: usize> Add for BendableScore<H, S> {
239    type Output = Self;
240
241    fn add(self, other: Self) -> Self {
242        let mut hard = [0; H];
243        let mut soft = [0; S];
244        for (i, item) in hard.iter_mut().enumerate().take(H) {
245            *item = self.hard[i] + other.hard[i];
246        }
247        for (i, item) in soft.iter_mut().enumerate().take(S) {
248            *item = self.soft[i] + other.soft[i];
249        }
250        BendableScore { hard, soft }
251    }
252}
253
254impl<const H: usize, const S: usize> Sub for BendableScore<H, S> {
255    type Output = Self;
256
257    fn sub(self, other: Self) -> Self {
258        let mut hard = [0; H];
259        let mut soft = [0; S];
260        for (i, item) in hard.iter_mut().enumerate().take(H) {
261            *item = self.hard[i] - other.hard[i];
262        }
263        for (i, item) in soft.iter_mut().enumerate().take(S) {
264            *item = self.soft[i] - other.soft[i];
265        }
266        BendableScore { hard, soft }
267    }
268}
269
270impl<const H: usize, const S: usize> Neg for BendableScore<H, S> {
271    type Output = Self;
272
273    fn neg(self) -> Self {
274        let mut hard = [0; H];
275        let mut soft = [0; S];
276        for (i, item) in hard.iter_mut().enumerate().take(H) {
277            *item = -self.hard[i];
278        }
279        for (i, item) in soft.iter_mut().enumerate().take(S) {
280            *item = -self.soft[i];
281        }
282        BendableScore { hard, soft }
283    }
284}
285
286impl<const H: usize, const S: usize> fmt::Debug for BendableScore<H, S> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        write!(
289            f,
290            "BendableScore(hard: {:?}, soft: {:?})",
291            self.hard, self.soft
292        )
293    }
294}
295
296impl<const H: usize, const S: usize> fmt::Display for BendableScore<H, S> {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        let hard_str: Vec<String> = self.hard.iter().map(|s| s.to_string()).collect();
299        let soft_str: Vec<String> = self.soft.iter().map(|s| s.to_string()).collect();
300
301        write!(
302            f,
303            "[{}]hard/[{}]soft",
304            hard_str.join("/"),
305            soft_str.join("/")
306        )
307    }
308}