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);
+}