1use std::cmp::Ordering;
4use std::fmt;
5use std::ops::{Add, Neg, Sub};
6
7use super::traits::{ParseableScore, Score, ScoreParseError};
8
9#[derive(Clone, PartialEq, Eq, Hash)]
27pub struct BendableScore {
28 hard_scores: Vec<i64>,
29 soft_scores: Vec<i64>,
30}
31
32impl BendableScore {
33 pub fn of(hard_scores: Vec<i64>, soft_scores: Vec<i64>) -> Self {
35 BendableScore {
36 hard_scores,
37 soft_scores,
38 }
39 }
40
41 pub fn zero_with_levels(hard_levels: usize, soft_levels: usize) -> Self {
43 BendableScore {
44 hard_scores: vec![0; hard_levels],
45 soft_scores: vec![0; soft_levels],
46 }
47 }
48
49 pub fn hard_levels_count(&self) -> usize {
51 self.hard_scores.len()
52 }
53
54 pub fn soft_levels_count(&self) -> usize {
56 self.soft_scores.len()
57 }
58
59 pub fn hard_score(&self, level: usize) -> i64 {
64 self.hard_scores[level]
65 }
66
67 pub fn soft_score(&self, level: usize) -> i64 {
72 self.soft_scores[level]
73 }
74
75 pub fn hard_scores(&self) -> &[i64] {
77 &self.hard_scores
78 }
79
80 pub fn soft_scores(&self) -> &[i64] {
82 &self.soft_scores
83 }
84
85 pub fn one_hard(hard_levels: usize, soft_levels: usize, level: usize) -> Self {
87 let mut hard_scores = vec![0; hard_levels];
88 hard_scores[level] = 1;
89 BendableScore {
90 hard_scores,
91 soft_scores: vec![0; soft_levels],
92 }
93 }
94
95 pub fn one_soft(hard_levels: usize, soft_levels: usize, level: usize) -> Self {
97 let mut soft_scores = vec![0; soft_levels];
98 soft_scores[level] = 1;
99 BendableScore {
100 hard_scores: vec![0; hard_levels],
101 soft_scores,
102 }
103 }
104
105 fn ensure_compatible(&self, other: &Self) {
106 assert_eq!(
107 self.hard_scores.len(),
108 other.hard_scores.len(),
109 "Incompatible hard levels: {} vs {}",
110 self.hard_scores.len(),
111 other.hard_scores.len()
112 );
113 assert_eq!(
114 self.soft_scores.len(),
115 other.soft_scores.len(),
116 "Incompatible soft levels: {} vs {}",
117 self.soft_scores.len(),
118 other.soft_scores.len()
119 );
120 }
121}
122
123impl Default for BendableScore {
124 fn default() -> Self {
125 BendableScore::zero_with_levels(1, 1)
127 }
128}
129
130impl Score for BendableScore {
131 fn is_feasible(&self) -> bool {
132 self.hard_scores.iter().all(|&s| s >= 0)
133 }
134
135 fn zero() -> Self {
136 BendableScore::default()
137 }
138
139 fn levels_count() -> usize {
140 0
143 }
144
145 fn to_level_numbers(&self) -> Vec<i64> {
146 let mut levels = self.hard_scores.clone();
147 levels.extend(self.soft_scores.iter());
148 levels
149 }
150
151 fn from_level_numbers(levels: &[i64]) -> Self {
152 let mid = levels.len() / 2;
154 BendableScore::of(levels[..mid].to_vec(), levels[mid..].to_vec())
155 }
156
157 fn multiply(&self, multiplicand: f64) -> Self {
158 BendableScore {
159 hard_scores: self
160 .hard_scores
161 .iter()
162 .map(|&s| (s as f64 * multiplicand).round() as i64)
163 .collect(),
164 soft_scores: self
165 .soft_scores
166 .iter()
167 .map(|&s| (s as f64 * multiplicand).round() as i64)
168 .collect(),
169 }
170 }
171
172 fn divide(&self, divisor: f64) -> Self {
173 BendableScore {
174 hard_scores: self
175 .hard_scores
176 .iter()
177 .map(|&s| (s as f64 / divisor).round() as i64)
178 .collect(),
179 soft_scores: self
180 .soft_scores
181 .iter()
182 .map(|&s| (s as f64 / divisor).round() as i64)
183 .collect(),
184 }
185 }
186
187 fn abs(&self) -> Self {
188 BendableScore {
189 hard_scores: self.hard_scores.iter().map(|&s| s.abs()).collect(),
190 soft_scores: self.soft_scores.iter().map(|&s| s.abs()).collect(),
191 }
192 }
193}
194
195impl Ord for BendableScore {
196 fn cmp(&self, other: &Self) -> Ordering {
197 self.ensure_compatible(other);
198
199 for (a, b) in self.hard_scores.iter().zip(other.hard_scores.iter()) {
201 match a.cmp(b) {
202 Ordering::Equal => continue,
203 other => return other,
204 }
205 }
206
207 for (a, b) in self.soft_scores.iter().zip(other.soft_scores.iter()) {
209 match a.cmp(b) {
210 Ordering::Equal => continue,
211 other => return other,
212 }
213 }
214
215 Ordering::Equal
216 }
217}
218
219impl PartialOrd for BendableScore {
220 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
221 Some(self.cmp(other))
222 }
223}
224
225impl Add for BendableScore {
226 type Output = Self;
227
228 fn add(self, other: Self) -> Self {
229 self.ensure_compatible(&other);
230 BendableScore {
231 hard_scores: self
232 .hard_scores
233 .iter()
234 .zip(other.hard_scores.iter())
235 .map(|(a, b)| a + b)
236 .collect(),
237 soft_scores: self
238 .soft_scores
239 .iter()
240 .zip(other.soft_scores.iter())
241 .map(|(a, b)| a + b)
242 .collect(),
243 }
244 }
245}
246
247impl Sub for BendableScore {
248 type Output = Self;
249
250 fn sub(self, other: Self) -> Self {
251 self.ensure_compatible(&other);
252 BendableScore {
253 hard_scores: self
254 .hard_scores
255 .iter()
256 .zip(other.hard_scores.iter())
257 .map(|(a, b)| a - b)
258 .collect(),
259 soft_scores: self
260 .soft_scores
261 .iter()
262 .zip(other.soft_scores.iter())
263 .map(|(a, b)| a - b)
264 .collect(),
265 }
266 }
267}
268
269impl Neg for BendableScore {
270 type Output = Self;
271
272 fn neg(self) -> Self {
273 BendableScore {
274 hard_scores: self.hard_scores.iter().map(|&s| -s).collect(),
275 soft_scores: self.soft_scores.iter().map(|&s| -s).collect(),
276 }
277 }
278}
279
280impl fmt::Debug for BendableScore {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 write!(
283 f,
284 "BendableScore(hard: {:?}, soft: {:?})",
285 self.hard_scores, self.soft_scores
286 )
287 }
288}
289
290impl fmt::Display for BendableScore {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 let hard_str: Vec<String> = self.hard_scores.iter().map(|s| s.to_string()).collect();
294 let soft_str: Vec<String> = self.soft_scores.iter().map(|s| s.to_string()).collect();
295
296 write!(
297 f,
298 "[{}]hard/[{}]soft",
299 hard_str.join("/"),
300 soft_str.join("/")
301 )
302 }
303}
304
305impl ParseableScore for BendableScore {
306 fn parse(s: &str) -> Result<Self, ScoreParseError> {
307 let s = s.trim();
308
309 let parts: Vec<&str> = s.split("hard/").collect();
311 if parts.len() != 2 {
312 return Err(ScoreParseError {
313 message: format!(
314 "Invalid BendableScore format '{}': expected '[...]hard/[...]soft'",
315 s
316 ),
317 });
318 }
319
320 let hard_part = parts[0]
321 .trim()
322 .strip_prefix('[')
323 .and_then(|s| s.strip_suffix(']'))
324 .ok_or_else(|| ScoreParseError {
325 message: format!("Hard score part '{}' must be wrapped in brackets", parts[0]),
326 })?;
327
328 let soft_part = parts[1]
329 .trim()
330 .strip_suffix("soft")
331 .and_then(|s| s.strip_prefix('['))
332 .and_then(|s| s.strip_suffix(']'))
333 .ok_or_else(|| ScoreParseError {
334 message: format!(
335 "Soft score part '{}' must be wrapped in brackets and end with 'soft'",
336 parts[1]
337 ),
338 })?;
339
340 let hard_scores: Result<Vec<i64>, _> = hard_part
341 .split('/')
342 .filter(|s| !s.is_empty())
343 .map(|s| {
344 s.trim().parse::<i64>().map_err(|e| ScoreParseError {
345 message: format!("Invalid hard score '{}': {}", s, e),
346 })
347 })
348 .collect();
349
350 let soft_scores: Result<Vec<i64>, _> = soft_part
351 .split('/')
352 .filter(|s| !s.is_empty())
353 .map(|s| {
354 s.trim().parse::<i64>().map_err(|e| ScoreParseError {
355 message: format!("Invalid soft score '{}': {}", s, e),
356 })
357 })
358 .collect();
359
360 Ok(BendableScore::of(hard_scores?, soft_scores?))
361 }
362
363 fn to_string_repr(&self) -> String {
364 format!("{}", self)
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 #[test]
373 fn test_creation() {
374 let score = BendableScore::of(vec![-1, -2], vec![-10, -20, -30]);
375 assert_eq!(score.hard_levels_count(), 2);
376 assert_eq!(score.soft_levels_count(), 3);
377 assert_eq!(score.hard_score(0), -1);
378 assert_eq!(score.hard_score(1), -2);
379 assert_eq!(score.soft_score(2), -30);
380 }
381
382 #[test]
383 fn test_feasibility() {
384 let feasible = BendableScore::of(vec![0, 0], vec![-10, -20]);
385 let infeasible = BendableScore::of(vec![0, -1], vec![0, 0]);
386
387 assert!(feasible.is_feasible());
388 assert!(!infeasible.is_feasible());
389 }
390
391 #[test]
392 fn test_comparison() {
393 let s1 = BendableScore::of(vec![-1, 0], vec![0]);
395 let s2 = BendableScore::of(vec![0, -100], vec![-1000]);
396 assert!(s2 > s1);
397
398 let s3 = BendableScore::of(vec![0, -10], vec![0]);
400 let s4 = BendableScore::of(vec![0, -5], vec![-100]);
401 assert!(s4 > s3);
402 }
403
404 #[test]
405 fn test_arithmetic() {
406 let s1 = BendableScore::of(vec![-1], vec![-10, -20]);
407 let s2 = BendableScore::of(vec![-2], vec![-5, -10]);
408
409 let sum = s1.clone() + s2.clone();
410 assert_eq!(sum.hard_scores(), &[-3]);
411 assert_eq!(sum.soft_scores(), &[-15, -30]);
412
413 let neg = -s1;
414 assert_eq!(neg.hard_scores(), &[1]);
415 assert_eq!(neg.soft_scores(), &[10, 20]);
416 }
417
418 #[test]
419 fn test_parse() {
420 let score = BendableScore::parse("[0/-1]hard/[-10/-20/-30]soft").unwrap();
421 assert_eq!(score.hard_scores(), &[0, -1]);
422 assert_eq!(score.soft_scores(), &[-10, -20, -30]);
423 }
424
425 #[test]
426 fn test_display() {
427 let score = BendableScore::of(vec![0, -1], vec![-10, -20]);
428 assert_eq!(format!("{}", score), "[0/-1]hard/[-10/-20]soft");
429 }
430}