aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDylan <boss@tehbox.org>2026-11-09 16:56:33 +1300
committerDylan <boss@tehbox.org>2026-11-09 16:56:33 +1300
commit76fde82da0ef09d3635362e1fbf2a289c3d2c6dd (patch)
treef8f03ea61cf339bf76de293de481a34a39679abe /src
parent478e7568f5eda9780d1409bb8dccc86221126281 (diff)
downloadtigers-and-sheep-76fde82da0ef09d3635362e1fbf2a289c3d2c6dd.tar.gz
tigers-and-sheep-76fde82da0ef09d3635362e1fbf2a289c3d2c6dd.zip
feat: Colour the game result and show scores to 5 significant figures
The game-over line is bold green for a win, naming the winning player, and bold yellow for a draw. The last-turn panel shows a minimax player's score to 5 significant figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/game.rs44
-rw-r--r--src/player.rs39
2 files changed, 77 insertions, 6 deletions
diff --git a/src/game.rs b/src/game.rs
index 716446d..74b96e5 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -212,14 +212,15 @@ impl GameHandler
Some(o) =>
{
let block = panel().title_bottom(" Game over ");
- let result = match o
+ // A win in green, a draw in yellow
+ let (result, colour) = match o
{
- Outcome::Tiger => format!("{} win!", side_name(Turn::Tiger)),
- Outcome::Sheep => format!("{} win!", side_name(Turn::Sheep)),
- Outcome::Draw => "Draw by repetition!".to_string(),
+ Outcome::Tiger => (format!("{} ({}) win!", side_name(Turn::Tiger), players[1].name()), Color::Green),
+ Outcome::Sheep => (format!("{} ({}) win!", side_name(Turn::Sheep), players[0].name()), Color::Green),
+ Outcome::Draw => ("Draw by repetition!".to_string(), Color::Yellow),
};
let text = vec![
- Line::styled(result, Style::new().add_modifier(Modifier::BOLD)),
+ Line::styled(result, Style::new().fg(colour).add_modifier(Modifier::BOLD)),
Line::from("Click a move to revert to it, or press q to quit."),
];
frame.render_widget(Paragraph::new(text).block(block), current_pane);
@@ -498,5 +499,38 @@ mod tests
let terminal = draw(&mut game, &[&mut sheep, &mut tiger]);
let screen = format!("{:?}", terminal.backend().buffer());
assert!(screen.contains("Draw by repetition!"), "{screen}");
+ assert_result_style(&terminal, "Draw by repetition!", Color::Yellow);
+ }
+
+ #[test]
+ fn game_over_panel_shows_the_winner_in_green()
+ {
+ // Only 15 sheep left, so the tigers have won
+ let mut game = movement_game();
+ game.start.set((0, 1), PieceType::Empty);
+ assert_eq!(game.outcome(), Some(Outcome::Tiger));
+ let mut sheep = LocalPlayer::default();
+ let mut tiger = ClaudePlayer::new(std::time::Duration::from_millis(50));
+ let terminal = draw(&mut game, &[&mut sheep, &mut tiger]);
+ assert_result_style(&terminal, "Tigers (Claude) win!", Color::Green);
+ }
+
+ /// Every cell of `text` on screen is bold and in `colour`.
+ fn assert_result_style(terminal: &Terminal<TestBackend>, text: &str, colour: Color)
+ {
+ let buf = terminal.backend().buffer();
+ let area = buf.area;
+ let found = (0..area.height).find_map(|y|
+ {
+ let row: String = (0..area.width).map(|x| buf[(x, y)].symbol()).collect();
+ row.find(text).map(|i| (row[..i].chars().count() as u16, y))
+ });
+ let (x0, y) = found.unwrap_or_else(|| panic!("{text} isn't on screen"));
+ for x in x0..x0 + text.chars().count() as u16
+ {
+ let cell = &buf[(x, y)];
+ assert_eq!(cell.fg, colour, "{text} at column {x}");
+ assert!(cell.modifier.contains(Modifier::BOLD), "{text} at column {x}");
+ }
}
}
diff --git a/src/player.rs b/src/player.rs
index 1766d24..4ac7a10 100644
--- a/src/player.rs
+++ b/src/player.rs
@@ -581,7 +581,7 @@ impl<P: MinimaxPlayer> Player for P
}
else
{
- format!("{:+.1} (+ favours tigers)", r.score)
+ format!("{} (+ favours tigers)", significant(r.score, 5))
};
Paragraph::new(format!("Played {}\nSearched to depth {}\nScore: {score}", r.mv, r.depth))
.wrap(Wrap { trim: true }).render(area, buf);
@@ -589,12 +589,49 @@ impl<P: MinimaxPlayer> Player for P
}
+/// `x` to `digits` significant figures, with its sign: 1234.5678 to 5 is "+1234.6", 0.012345678
+/// is "+0.012346", and 1234567 is "+1234600".
+fn significant(x: f32, digits: usize) -> String
+{
+ if x == 0.0
+ {
+ return format!("{:.*}", digits.saturating_sub(1), 0.0);
+ }
+ // The power of ten of the first significant digit
+ let magnitude = (x.abs() as f64).log10().floor() as i32;
+ let decimals = digits as i32 - 1 - magnitude;
+ if decimals >= 0
+ {
+ // Rounding can carry into a new digit (9.99999 to 10.000), which needs one decimal fewer
+ let rounded = format!("{:+.*}", decimals as usize, x);
+ let carried = rounded.trim_start_matches(['+', '-']).trim_start_matches(['0', '.']).chars().filter(char::is_ascii_digit).count() > digits;
+ if carried && decimals > 0 { format!("{:+.*}", decimals as usize - 1, x) } else { rounded }
+ }
+ else
+ {
+ let unit = 10f64.powi(-decimals);
+ format!("{:+.0}", (x as f64 / unit).round() * unit)
+ }
+}
+
#[cfg(test)]
mod tests
{
use super::*;
use crate::board::PieceType;
+ #[test]
+ fn significant_keeps_five_figures()
+ {
+ assert_eq!(significant(1234.5678, 5), "+1234.6");
+ assert_eq!(significant(-212.0, 5), "-212.00");
+ assert_eq!(significant(0.012345678, 5), "+0.012346");
+ assert_eq!(significant(-0.5, 5), "-0.50000");
+ assert_eq!(significant(1234567.0, 5), "+1234600");
+ assert_eq!(significant(9.999999, 5), "+10.000");
+ assert_eq!(significant(0.0, 5), "0.0000");
+ }
+
/// A minimax player with a cheap evaluation that still varies a lot between positions.
struct TestPlayer
{