Skip to content

Instantly share code, notes, and snippets.

@cboudereau
Last active May 17, 2023 15:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cboudereau/1f3ea7acd6d6712746c6d9f0e258deff to your computer and use it in GitHub Desktop.
Save cboudereau/1f3ea7acd6d6712746c6d9f0e258deff to your computer and use it in GitHub Desktop.
Tennis score kata in Rust having KISS and "make illegal states unrepresentable"
#[derive(Debug, Clone, Copy, PartialEq)]
enum Point {
Love,
Fifteen,
Thirty,
Forty,
}
impl Point {
fn next(&self) -> Self {
use Point::{Fifteen, Forty, Love, Thirty};
match self {
Love => Fifteen,
Fifteen => Thirty,
Thirty | Forty => Forty,
}
}
}
#[derive(Debug, PartialEq)]
enum Player {
Left,
Right,
}
#[derive(Debug, PartialEq)]
enum Score {
Points { left: Point, right: Point },
FifteenAll,
ThirtyAll,
Deuce,
Advantage(Player),
Game(Player),
}
impl Default for Score {
fn default() -> Self {
Score::Points {
left: Point::Love,
right: Point::Love,
}
}
}
impl Player {
fn win(self, score: Score) -> Score {
use Player::{Left, Right};
use Point::{Fifteen, Forty, Love, Thirty};
use Score::{Advantage, Deuce, FifteenAll, Game, Points, ThirtyAll};
match (&self, &score) {
(_, Game(_)) => score,
(Left, Advantage(Left)) | ( Left, Points { left: Forty, right: _ }) => Game(Left),
(Right, Advantage(Right)) | ( Right, Points { left: _, right: Forty }) => Game(Right),
(Left, Points { left: Love, right: Fifteen, }) | (Right, Points { left: Fifteen, right: Love }) => FifteenAll,
(Left, FifteenAll) => Points { left: Thirty, right: Fifteen, },
(Right, FifteenAll) => Points { left: Fifteen, right: Thirty },
(Left, Points { left: Fifteen, right: Thirty }) | (Right, Points { left: Thirty, right: Fifteen }) => Score::ThirtyAll,
(Left, ThirtyAll) => Points { left: Forty, right: Thirty },
(Right, ThirtyAll) => Points { left: Thirty, right: Forty },
(Left, Points { left: Thirty, right: Forty }) | (Right, Points { left: Forty, right: Thirty }) => Score::Deuce,
(_, Deuce) => Advantage(self),
(Left, Advantage(Right)) | (Right, Advantage(Left)) => Deuce,
(Left, Points { left, right }) => Points { left: left.next(), right: right.to_owned() },
(Right, Points { left, right }) => Points { left: left.to_owned(), right: right.next() },
}
}
}
#[cfg(test)]
mod tests {
use super::Point::{Fifteen, Forty, Love, Thirty};
use super::Score::{Advantage, Deuce, FifteenAll, Game, Points, ThirtyAll};
use super::*;
#[test]
fn left_player_always_wins_use_case() {
let score = Score::default();
assert_eq!(
score,
Points {
left: Love,
right: Love
}
);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Fifteen,
right: Love
}
);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Thirty,
right: Love
}
);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Forty,
right: Love
}
);
let score = Player::Left.win(score);
assert_eq!(score, Game(Player::Left));
}
#[test]
fn left_wins_the_game_but_the_right_player_was_close_to_win_use_case() {
let score = Score::default();
assert_eq!(
score,
Points {
left: Love,
right: Love
}
);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Fifteen,
right: Love
}
);
let score = Player::Right.win(score);
assert_eq!(score, FifteenAll);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Thirty,
right: Fifteen
}
);
let score = Player::Right.win(score);
assert_eq!(score, ThirtyAll);
let score = Player::Left.win(score);
assert_eq!(
score,
Points {
left: Forty,
right: Thirty
}
);
let score = Player::Right.win(score);
assert_eq!(score, Deuce);
let score = Player::Left.win(score);
assert_eq!(score, Advantage(Player::Left));
let score = Player::Right.win(score);
assert_eq!(score, Deuce);
let score = Player::Right.win(score);
assert_eq!(score, Advantage(Player::Right));
let score = Player::Left.win(score);
assert_eq!(score, Deuce);
let score = Player::Left.win(score);
assert_eq!(score, Advantage(Player::Left));
let score = Player::Left.win(score);
assert_eq!(score, Game(Player::Left));
}
#[test]
fn right_wins_the_game_but_the_left_player_was_close_to_win_use_case() {
let score = Score::default();
assert_eq!(
score,
Points {
left: Love,
right: Love
}
);
let score = Player::Right.win(score);
assert_eq!(
score,
Points {
left: Love,
right: Fifteen
}
);
let score = Player::Left.win(score);
assert_eq!(score, FifteenAll);
let score = Player::Right.win(score);
assert_eq!(
score,
Points {
left: Fifteen,
right: Thirty
}
);
let score = Player::Left.win(score);
assert_eq!(score, ThirtyAll);
let score = Player::Right.win(score);
assert_eq!(
score,
Points {
left: Thirty,
right: Forty
}
);
let score = Player::Left.win(score);
assert_eq!(score, Deuce);
let score = Player::Right.win(score);
assert_eq!(score, Advantage(Player::Right));
let score = Player::Left.win(score);
assert_eq!(score, Deuce);
let score = Player::Left.win(score);
assert_eq!(score, Advantage(Player::Left));
let score = Player::Right.win(score);
assert_eq!(score, Deuce);
let score = Player::Right.win(score);
assert_eq!(score, Advantage(Player::Right));
let score = Player::Right.win(score);
assert_eq!(score, Game(Player::Right));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment