Skip to main content

timecat/utils/
string_utils.rs

1use super::*;
2
3#[inline]
4pub fn remove_double_spaces_and_trim(s: &str) -> String {
5    s.trim()
6        .chars()
7        .dedup_by(|&c1, &c2| c1 == ' ' && c2 == ' ')
8        .join("")
9}
10
11#[inline]
12pub fn simplify_fen(fen: &str) -> String {
13    remove_double_spaces_and_trim(fen)
14}
15
16pub fn flip_board_fen(fen: &str) -> Result<String> {
17    // TODO: ep square not flipped.
18    let fen = remove_double_spaces_and_trim(fen);
19    let (position_fen, rest_fen) = fen.split_once(' ').ok_or_else(|| TimecatError::BadFen {
20        fen: fen.to_string().into(),
21    })?;
22    Ok(format!(
23        "{} {rest_fen}",
24        position_fen
25            .chars()
26            .map(|c| match c {
27                c if c.is_uppercase() => c.to_ascii_lowercase(),
28                c if c.is_lowercase() => c.to_ascii_uppercase(),
29                _ => c,
30            })
31            .collect::<String>(),
32    ))
33}
34
35#[cfg(feature = "colored")]
36impl<T: ToString> CustomColorize for T {
37    fn colorize(&self, style_functions: &[ColoredStringFunction]) -> String {
38        let self_string = self.to_string();
39        if style_functions.is_empty() || !GLOBAL_TIMECAT_STATE.is_colored_output() {
40            return self_string;
41        }
42        let mut colorized_string = self_string.into();
43        for &func in style_functions {
44            colorized_string = func(colorized_string);
45        }
46        colorized_string.to_string()
47    }
48}
49
50#[cfg(not(feature = "colored"))]
51impl<T: ToString> CustomColorize for T {
52    #[inline]
53    fn colorize(&self, _: &[ColoredStringFunction]) -> String {
54        self.to_string()
55    }
56}
57
58impl StringifyScore for Score {
59    fn stringify_score_console<'a>(self) -> Cow<'a, str> {
60        if self == INFINITY {
61            return Cow::Borrowed("INFINITY");
62        }
63        if self == -INFINITY {
64            return Cow::Borrowed("-INFINITY");
65        }
66        if is_checkmate(self) {
67            let mut mate_string = String::from(if self.is_positive() { "M" } else { "-M" });
68            let mate_distance = (CHECKMATE_SCORE - self.abs() + 1) / 2;
69            write_unchecked!(mate_string, "{}", mate_distance);
70            return mate_string.colorize(CHECKMATE_SCORE_STYLE).into();
71        }
72        let to_return = self as f64 / PAWN_VALUE as f64;
73        if to_return.is_integer() {
74            format!("{}", to_return as i32).into()
75        } else {
76            format!("{:.2}", to_return).into()
77        }
78    }
79
80    fn stringify_score_uci<'a>(self) -> Cow<'a, str> {
81        if self == INFINITY {
82            return Cow::Borrowed("inf");
83        }
84        if self == -INFINITY {
85            return Cow::Borrowed("-inf");
86        }
87        if is_checkmate(self) {
88            let mut mate_string = String::from("mate ");
89            let mut mate_distance = (CHECKMATE_SCORE - self.abs() + 1) / 2;
90            if self.is_negative() {
91                mate_distance = -mate_distance;
92            }
93            write_unchecked!(mate_string, "{}", mate_distance);
94            return mate_string.into();
95        }
96        format!("cp {}", (self as i32 * 100) / PAWN_VALUE as i32).into()
97    }
98
99    #[inline]
100    fn stringify_score<'a>(self) -> Cow<'a, str> {
101        if GLOBAL_TIMECAT_STATE.is_in_console_mode() {
102            self.stringify_score_console()
103        } else {
104            self.stringify_score_uci()
105        }
106    }
107}
108
109impl Stringify for Score {
110    #[inline]
111    fn stringify<'a>(&self) -> Cow<'a, str> {
112        self.stringify_score()
113    }
114}
115
116impl Stringify for TimecatError {
117    #[inline]
118    fn stringify<'a>(&self) -> Cow<'a, str> {
119        self.stringify_with_optional_raw_input(None).into()
120    }
121}
122
123impl StringifyMove for Move {
124    fn uci<'a>(self) -> Cow<'a, str> {
125        self.to_string().into()
126    }
127
128    fn algebraic<'a>(self, position: &ChessPosition, long: bool) -> Result<Cow<'a, str>> {
129        Ok(self.algebraic_and_new_position(position, long)?.0)
130    }
131
132    fn stringify_move<'a>(self, position: &ChessPosition) -> Result<Cow<'a, str>> {
133        Some(self).stringify_move(position)
134    }
135}
136
137impl StringifyMove for Option<Move> {
138    fn uci<'a>(self) -> Cow<'a, str> {
139        self.map_or(Cow::Borrowed("0000"), |m| m.uci())
140    }
141
142    fn algebraic<'a>(self, position: &ChessPosition, long: bool) -> Result<Cow<'a, str>> {
143        self.map_or(Ok(Cow::Borrowed("--")), |valid_or_null_move| {
144            valid_or_null_move.algebraic(position, long)
145        })
146    }
147
148    fn stringify_move<'a>(self, position: &ChessPosition) -> Result<Cow<'a, str>> {
149        if GLOBAL_TIMECAT_STATE.is_in_console_mode() {
150            self.algebraic(position, GLOBAL_TIMECAT_STATE.use_long_algebraic_notation())
151        } else {
152            Ok(self.uci())
153        }
154    }
155}
156
157impl StringifyHash for u64 {
158    fn stringify_hash(&self) -> String {
159        format!("{:X}", self)
160    }
161}
162
163impl Stringify for Duration {
164    fn stringify<'a>(&self) -> Cow<'a, str> {
165        if GLOBAL_TIMECAT_STATE.is_in_uci_mode() {
166            return self.as_millis().to_string().into();
167        }
168        if self < &Self::from_secs(1) {
169            return format!("{} ms", self.as_millis()).into();
170        }
171        let precision = 3;
172        let total_secs = self.as_secs_f64();
173        for (threshold, unit) in [(86400.0, "day"), (3600.0, "hr"), (60.0, "min")] {
174            if total_secs >= threshold {
175                let time_unit = total_secs as u128 / threshold as u128;
176                let secs = total_secs % threshold;
177                let mut string = format!("{} {}", time_unit, unit);
178                if time_unit > 1 {
179                    string.push('s');
180                }
181                if secs >= 10.0_f64.powi(-(precision as i32)) {
182                    string.push(' ');
183                    string += &Self::from_secs_f64(secs).stringify();
184                }
185                return string.into();
186            }
187        }
188        let total_secs_rounded = total_secs.round();
189        let mut string = if (total_secs - total_secs_rounded).abs() < 1e-5 {
190            format!("{} sec", total_secs_rounded)
191        } else {
192            format!("{:.1$} sec", total_secs, precision)
193        };
194        if total_secs > 1.0 {
195            string.push('s');
196        }
197        string.into()
198    }
199}
200
201macro_rules! implement_stringify {
202    ($($type:ty),+ $(,)?) => {
203        $(
204            impl Stringify for $type {
205                fn stringify<'a>(&self) -> Cow<'a, str> {
206                    self.to_string().into()
207                }
208            }
209        )*
210    };
211}
212
213implement_stringify!(Move, ValidOrNullMove, WeightedMove, Color, PieceType, Piece);
214
215impl<T: Stringify> Stringify for Option<T> {
216    fn stringify<'a>(&self) -> Cow<'a, str> {
217        self.as_ref()
218            .map_or(Cow::Borrowed(STRINGIFY_NONE), |t| t.stringify())
219    }
220}
221
222impl<T: Stringify, E: Error> Stringify for std::result::Result<T, E> {
223    fn stringify<'a>(&self) -> Cow<'a, str> {
224        match self {
225            Ok(t) => format!("Ok({})", t.stringify()).into(),
226            Err(e) => format!("Err({})", e).into(),
227        }
228    }
229}
230
231impl<T: Stringify> Stringify for [T] {
232    fn stringify<'a>(&self) -> Cow<'a, str> {
233        format!("[{}]", self.iter().map(|t| t.stringify()).join(", ")).into()
234    }
235}
236
237impl<T: Stringify> Stringify for Vec<T> {
238    fn stringify<'a>(&self) -> Cow<'a, str> {
239        self.as_slice().stringify()
240    }
241}