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 to_level_numbers(&self) -> Vec<i64> {
118        let mut levels = Vec::with_capacity(H + S);
119        levels.extend_from_slice(&self.hard);
120        levels.extend_from_slice(&self.soft);
121        levels
122    }
123
124    fn from_level_numbers(levels: &[i64]) -> Self {
125        assert!(levels.len() >= H + S, "Not enough levels provided");
126        let mut hard = [0; H];
127        let mut soft = [0; S];
128        hard.copy_from_slice(&levels[..H]);
129        soft.copy_from_slice(&levels[H..H + S]);
130        BendableScore { hard, soft }
131    }
132
133    fn multiply(&self, multiplicand: f64) -> Self {
134        let mut hard = [0; H];
135        let mut soft = [0; S];
136        for (i, item) in hard.iter_mut().enumerate().take(H) {
137            *item = (self.hard[i] as f64 * multiplicand).round() as i64;
138        }
139        for (i, item) in soft.iter_mut().enumerate().take(S) {
140            *item = (self.soft[i] as f64 * multiplicand).round() as i64;
141        }
142        BendableScore { hard, soft }
143    }
144
145    fn divide(&self, divisor: f64) -> Self {
146        let mut hard = [0; H];
147        let mut soft = [0; S];
148        for (i, item) in hard.iter_mut().enumerate().take(H) {
149            *item = (self.hard[i] as f64 / divisor).round() as i64;
150        }
151        for (i, item) in soft.iter_mut().enumerate().take(S) {
152            *item = (self.soft[i] as f64 / divisor).round() as i64;
153        }
154        BendableScore { hard, soft }
155    }
156
157    fn abs(&self) -> Self {
158        let mut hard = [0; H];
159        let mut soft = [0; S];
160        for (i, item) in hard.iter_mut().enumerate().take(H) {
161            *item = self.hard[i].abs();
162        }
163        for (i, item) in soft.iter_mut().enumerate().take(S) {
164            *item = self.soft[i].abs();
165        }
166        BendableScore { hard, soft }
167    }
168
169    fn level_label(index: usize) -> ScoreLevel {
170        if index < H {
171            ScoreLevel::Hard
172        } else if index < H + S {
173            ScoreLevel::Soft
174        } else {
175            panic!(
176                "BendableScore<{}, {}> has {} levels, got index {}",
177                H,
178                S,
179                H + S,
180                index
181            )
182        }
183    }
184
185    #[inline]
186    fn to_scalar(&self) -> f64 {
187        let total_levels = H + S;
188        let mut scalar = 0.0;
189        for (i, &v) in self.hard.iter().enumerate() {
190            let weight = 10_f64.powi(((total_levels - 1 - i) * 6) as i32);
191            scalar += v as f64 * weight;
192        }
193        for (i, &v) in self.soft.iter().enumerate() {
194            let weight = 10_f64.powi(((S - 1 - i) * 6) as i32);
195            scalar += v as f64 * weight;
196        }
197        scalar
198    }
199}
200
201impl<const H: usize, const S: usize> Ord for BendableScore<H, S> {
202    fn cmp(&self, other: &Self) -> Ordering {
203        // Compare hard scores first (highest priority first)
204        for i in 0..H {
205            match self.hard[i].cmp(&other.hard[i]) {
206                Ordering::Equal => continue,
207                ord => return ord,
208            }
209        }
210
211        // Then compare soft scores
212        for i in 0..S {
213            match self.soft[i].cmp(&other.soft[i]) {
214                Ordering::Equal => continue,
215                ord => return ord,
216            }
217        }
218
219        Ordering::Equal
220    }
221}
222
223impl<const H: usize, const S: usize> PartialOrd for BendableScore<H, S> {
224    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
225        Some(self.cmp(other))
226    }
227}
228
229impl<const H: usize, const S: usize> Add for BendableScore<H, S> {
230    type Output = Self;
231
232    fn add(self, other: Self) -> Self {
233        let mut hard = [0; H];
234        let mut soft = [0; S];
235        for (i, item) in hard.iter_mut().enumerate().take(H) {
236            *item = self.hard[i] + other.hard[i];
237        }
238        for (i, item) in soft.iter_mut().enumerate().take(S) {
239            *item = self.soft[i] + other.soft[i];
240        }
241        BendableScore { hard, soft }
242    }
243}
244
245impl<const H: usize, const S: usize> Sub for BendableScore<H, S> {
246    type Output = Self;
247
248    fn sub(self, other: Self) -> Self {
249        let mut hard = [0; H];
250        let mut soft = [0; S];
251        for (i, item) in hard.iter_mut().enumerate().take(H) {
252            *item = self.hard[i] - other.hard[i];
253        }
254        for (i, item) in soft.iter_mut().enumerate().take(S) {
255            *item = self.soft[i] - other.soft[i];
256        }
257        BendableScore { hard, soft }
258    }
259}
260
261impl<const H: usize, const S: usize> Neg for BendableScore<H, S> {
262    type Output = Self;
263
264    fn neg(self) -> Self {
265        let mut hard = [0; H];
266        let mut soft = [0; S];
267        for (i, item) in hard.iter_mut().enumerate().take(H) {
268            *item = -self.hard[i];
269        }
270        for (i, item) in soft.iter_mut().enumerate().take(S) {
271            *item = -self.soft[i];
272        }
273        BendableScore { hard, soft }
274    }
275}
276
277impl<const H: usize, const S: usize> fmt::Debug for BendableScore<H, S> {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(
280            f,
281            "BendableScore(hard: {:?}, soft: {:?})",
282            self.hard, self.soft
283        )
284    }
285}
286
287impl<const H: usize, const S: usize> fmt::Display for BendableScore<H, S> {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        let hard_str: Vec<String> = self.hard.iter().map(|s| s.to_string()).collect();
290        let soft_str: Vec<String> = self.soft.iter().map(|s| s.to_string()).collect();
291
292        write!(
293            f,
294            "[{}]hard/[{}]soft",
295            hard_str.join("/"),
296            soft_str.join("/")
297        )
298    }
299}