use std::cmp::Ordering;
use std::fmt;
use super::traits::{ParseableScore, Score, ScoreParseError};
use super::ScoreLevel;
const SCALE: i64 = 100_000;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HardSoftDecimalScore {
hard: i64,
soft: i64,
}
impl HardSoftDecimalScore {
pub const ZERO: HardSoftDecimalScore = HardSoftDecimalScore { hard: 0, soft: 0 };
pub const ONE_HARD: HardSoftDecimalScore = HardSoftDecimalScore {
hard: SCALE,
soft: 0,
};
pub const ONE_SOFT: HardSoftDecimalScore = HardSoftDecimalScore {
hard: 0,
soft: SCALE,
};
#[inline]
pub const fn of(hard: i64, soft: i64) -> Self {
HardSoftDecimalScore {
hard: hard * SCALE,
soft: soft * SCALE,
}
}
#[inline]
pub const fn of_scaled(hard: i64, soft: i64) -> Self {
HardSoftDecimalScore { hard, soft }
}
#[inline]
pub const fn of_hard(hard: i64) -> Self {
HardSoftDecimalScore {
hard: hard * SCALE,
soft: 0,
}
}
#[inline]
pub const fn of_soft(soft: i64) -> Self {
HardSoftDecimalScore {
hard: 0,
soft: soft * SCALE,
}
}
#[inline]
pub const fn of_hard_scaled(hard: i64) -> Self {
HardSoftDecimalScore { hard, soft: 0 }
}
#[inline]
pub const fn of_soft_scaled(soft: i64) -> Self {
HardSoftDecimalScore { hard: 0, soft }
}
#[inline]
pub const fn hard_scaled(&self) -> i64 {
self.hard
}
#[inline]
pub const fn soft_scaled(&self) -> i64 {
self.soft
}
pub const fn hard_score(&self) -> HardSoftDecimalScore {
HardSoftDecimalScore::of_scaled(self.hard, 0)
}
pub const fn soft_score(&self) -> HardSoftDecimalScore {
HardSoftDecimalScore::of_scaled(0, self.soft)
}
#[inline]
pub const fn has_hard_component(&self) -> bool {
self.hard != 0
}
}
impl Score for HardSoftDecimalScore {
#[inline]
fn is_feasible(&self) -> bool {
self.hard >= 0
}
#[inline]
fn zero() -> Self {
HardSoftDecimalScore::ZERO
}
#[inline]
fn levels_count() -> usize {
2
}
fn to_level_numbers(&self) -> Vec<i64> {
vec![self.hard, self.soft]
}
fn from_level_numbers(levels: &[i64]) -> Self {
assert_eq!(
levels.len(),
2,
"HardSoftDecimalScore requires exactly 2 levels"
);
HardSoftDecimalScore::of_scaled(levels[0], levels[1])
}
impl_score_scale!(HardSoftDecimalScore { hard, soft } => of_scaled);
fn level_label(index: usize) -> ScoreLevel {
match index {
0 => ScoreLevel::Hard,
1 => ScoreLevel::Soft,
_ => panic!("HardSoftDecimalScore has 2 levels, got index {}", index),
}
}
#[inline]
fn to_scalar(&self) -> f64 {
self.hard as f64 * 1_000_000.0 + self.soft as f64
}
}
impl Ord for HardSoftDecimalScore {
fn cmp(&self, other: &Self) -> Ordering {
match self.hard.cmp(&other.hard) {
Ordering::Equal => self.soft.cmp(&other.soft),
other => other,
}
}
}
impl_score_ops!(HardSoftDecimalScore { hard, soft } => of_scaled);
impl fmt::Debug for HardSoftDecimalScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"HardSoftDecimalScore({:.3}, {:.3})",
self.hard as f64 / SCALE as f64,
self.soft as f64 / SCALE as f64
)
}
}
impl fmt::Display for HardSoftDecimalScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn format_score_part(scaled: i64) -> String {
if scaled % SCALE == 0 {
(scaled / SCALE).to_string()
} else {
let value = scaled as f64 / SCALE as f64;
let formatted = format!("{:.6}", value);
formatted
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}
}
write!(
f,
"{}hard/{}soft",
format_score_part(self.hard),
format_score_part(self.soft)
)
}
}
impl ParseableScore for HardSoftDecimalScore {
fn parse(s: &str) -> Result<Self, ScoreParseError> {
let s = s.trim();
let parts: Vec<&str> = s.split('/').collect();
if parts.len() != 2 {
return Err(ScoreParseError {
message: format!(
"Invalid HardSoftDecimalScore format '{}': expected 'Xhard/Ysoft'",
s
),
});
}
let hard_str = parts[0]
.trim()
.strip_suffix("hard")
.ok_or_else(|| ScoreParseError {
message: format!("Hard score part '{}' must end with 'hard'", parts[0]),
})?;
let soft_str = parts[1]
.trim()
.strip_suffix("soft")
.ok_or_else(|| ScoreParseError {
message: format!("Soft score part '{}' must end with 'soft'", parts[1]),
})?;
let hard_float = hard_str.parse::<f64>().map_err(|e| ScoreParseError {
message: format!("Invalid hard score '{}': {}", hard_str, e),
})?;
let soft_float = soft_str.parse::<f64>().map_err(|e| ScoreParseError {
message: format!("Invalid soft score '{}': {}", soft_str, e),
})?;
let hard = (hard_float * SCALE as f64).round() as i64;
let soft = (soft_float * SCALE as f64).round() as i64;
Ok(HardSoftDecimalScore::of_scaled(hard, soft))
}
fn to_string_repr(&self) -> String {
format!("{}", self)
}
}