diff --git a/glloyd_and_dbetteridge/.idea/.gitignore b/glloyd_and_dbetteridge/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/glloyd_and_dbetteridge/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/glloyd_and_dbetteridge/.idea/glloyd_and_dbetteridge.iml b/glloyd_and_dbetteridge/.idea/glloyd_and_dbetteridge.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/glloyd_and_dbetteridge/.idea/glloyd_and_dbetteridge.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/glloyd_and_dbetteridge/.idea/modules.xml b/glloyd_and_dbetteridge/.idea/modules.xml new file mode 100644 index 0000000..c7d89c9 --- /dev/null +++ b/glloyd_and_dbetteridge/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/glloyd_and_dbetteridge/.idea/vcs.xml b/glloyd_and_dbetteridge/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/glloyd_and_dbetteridge/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/glloyd_and_dbetteridge/Cargo.lock b/glloyd_and_dbetteridge/Cargo.lock new file mode 100644 index 0000000..113946d --- /dev/null +++ b/glloyd_and_dbetteridge/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tennis" +version = "0.1.0" +dependencies = [ + "rstest", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] diff --git a/glloyd_and_dbetteridge/Cargo.toml b/glloyd_and_dbetteridge/Cargo.toml new file mode 100644 index 0000000..e446a79 --- /dev/null +++ b/glloyd_and_dbetteridge/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tennis" +version = "0.1.0" +edition = "2024" + +[lib] +name = "tennis" + +[dependencies] +rstest = "0.25.0" diff --git a/glloyd_and_dbetteridge/src/lib.rs b/glloyd_and_dbetteridge/src/lib.rs new file mode 100644 index 0000000..87b3db6 --- /dev/null +++ b/glloyd_and_dbetteridge/src/lib.rs @@ -0,0 +1,143 @@ +use std::fmt::Display; + +#[derive(PartialEq, Copy, Clone)] +pub enum PlayerScore { + Love, + Fifteen, + Thirty, + Forty, +} + +impl Display for PlayerScore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let score_text = match self { + Self::Love => "love", + Self::Fifteen => "15", + Self::Thirty => "30", + Self::Forty => "40", + }; + write!(f, "{}", score_text) + } +} + +impl PlayerScore { + fn next(self) -> Self { + match self { + Self::Love => Self::Fifteen, + Self::Fifteen => Self::Thirty, + Self::Thirty => Self::Forty, + Self::Forty => unreachable!("Cannot advance from Forty in regular scoring"), + } + } +} + +#[derive(PartialEq, Copy, Clone)] +pub enum Game { + Deuce, + Advantage(Player), + Win(Player), + Scores(PlayerScore, PlayerScore), +} + +#[derive(PartialEq, Copy, Clone)] +pub enum Player { + One, + Two, +} + +impl Display for Player { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let player_text = match self { + Player::One => "Player 1", + Player::Two => "Player 2", + }; + write!(f, "{}", player_text) + } +} + +impl Default for Game { + fn default() -> Self { + Self::Scores(PlayerScore::Love, PlayerScore::Love) + } +} + +impl Display for Game { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Deuce => f.write_str("Deuce"), + Self::Advantage(player) => write!(f, "Advantage {}", player), + Self::Win(player) => write!(f, "{} has won", player), + Self::Scores(player1_score, player2_score) if player1_score == player2_score => write!(f, "{}-all", player1_score), + Self::Scores(player1_score, player2_score) => write!(f, "{}-{}", player1_score, player2_score), + } + } +} + +impl Game { + fn advance_score(self, player: Player) -> Self { + match (player, self) { + (x, Self::Advantage(y)) if x != y => Self::Deuce, + (Player::One, Self::Scores(PlayerScore::Thirty, PlayerScore::Forty)) => Self::Deuce, + (Player::Two, Self::Scores(PlayerScore::Forty, PlayerScore::Thirty)) => Self::Deuce, + + (x, Self::Deuce) => Self::Advantage(x), + + (x, Self::Advantage(y)) if x == y => Self::Win(x), + (Player::One, Self::Scores(PlayerScore::Forty, _)) => Self::Win(Player::One), + (Player::Two, Self::Scores(_, PlayerScore::Forty)) => Self::Win(Player::Two), + + (Player::One, Self::Scores(player1_score, player2_score)) => { + Self::Scores(player1_score.next(), player2_score) + } + (Player::Two, Self::Scores(player1_score, player2_score)) => { + Self::Scores(player1_score, player2_score.next()) + } + (_, x) => x, + } + } + + pub fn score_point(&mut self, player: Player) { + *self = self.advance_score(player); + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + use rstest::rstest; + + #[rstest] + #[case(vec!(0,0), "love-all")] + #[case(vec!(1,0), "15-love")] + #[case(vec!(2,0), "30-love")] + #[case(vec!(0,1), "love-15")] + #[case(vec!(3,0), "40-love")] + #[case(vec!(4,0), "Player 1 has won")] + #[case(vec!(3,3), "Deuce")] + #[case(vec!(3,3,1), "Advantage Player 1")] + #[case(vec!(3,3,2), "Player 1 has won")] + #[case(vec!(3,3,1,1), "Deuce")] + #[case(vec!(0,4), "Player 2 has won")] + #[case(vec!(1,1), "15-all")] + #[case(vec!(2,2), "30-all")] + #[case(vec!(1,2), "15-30")] + #[case(vec!(2,1), "30-15")] + #[case(vec!(3,3,0,1), "Advantage Player 2")] + #[case(vec!(3,3,0,2), "Player 2 has won")] + #[case(vec!(0,3,1), "15-40")] + #[case(vec!(3,3,1,1,0,1), "Advantage Player 2")] + #[case(vec!(3,3,1,1,1,1,0,2), "Player 2 has won")] + fn test_tennis_scoring(#[case] sequence: Vec, #[case] expected: &str) { + let mut game = Game::default(); + + let player_cycle = [Player::One, Player::Two].iter().cycle(); + for (&n, player) in sequence.iter().zip(player_cycle) { + for _ in 0..n { + game.score_point(*player); + } + } + + assert_eq!(game.to_string(), expected); + } +} diff --git a/glloyd_and_dbetteridge/src/main.rs b/glloyd_and_dbetteridge/src/main.rs new file mode 100644 index 0000000..237dac4 --- /dev/null +++ b/glloyd_and_dbetteridge/src/main.rs @@ -0,0 +1,12 @@ +use tennis::{Game, Player}; + +fn main() { + let mut game = Game::default(); + println!("Starting game: {}", game); + + game.score_point(Player::One); + println!("After player 1 scores: {}", game); + + game.score_point(Player::Two); + println!("After player 2 scores: {}", game); +}