From 98637a16c2375d50c716cd6e021a3a0523e70288 Mon Sep 17 00:00:00 2001 From: Hamed Mahmoudkhani Date: Wed, 5 Aug 2026 00:45:52 +0200 Subject: [PATCH] feat: added rust implementation of Basketball --- 07_Basketball/rust/Cargo.lock | 77 ++++++ 07_Basketball/rust/Cargo.toml | 7 + 07_Basketball/rust/src/main.rs | 444 +++++++++++++++++++++++++++++++++ README.md | 2 +- 4 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 07_Basketball/rust/Cargo.lock create mode 100644 07_Basketball/rust/Cargo.toml create mode 100644 07_Basketball/rust/src/main.rs diff --git a/07_Basketball/rust/Cargo.lock b/07_Basketball/rust/Cargo.lock new file mode 100644 index 000000000..5d92a1cf5 --- /dev/null +++ b/07_Basketball/rust/Cargo.lock @@ -0,0 +1,77 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "basketball" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" diff --git a/07_Basketball/rust/Cargo.toml b/07_Basketball/rust/Cargo.toml new file mode 100644 index 000000000..3a4c7c80b --- /dev/null +++ b/07_Basketball/rust/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "basketball" +version = "0.1.0" +edition = "2024" + +[dependencies] +rand = "0.10.0" diff --git a/07_Basketball/rust/src/main.rs b/07_Basketball/rust/src/main.rs new file mode 100644 index 000000000..ca0d6792d --- /dev/null +++ b/07_Basketball/rust/src/main.rs @@ -0,0 +1,444 @@ +use rand::{RngExt, rngs::ThreadRng}; +use std::io::{self, Write}; + +fn main() { + println!("{:>30}BASKETBALL", ""); + println!("{:>15}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", ""); + println!("\n\n"); + + println!("THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH"); + println!(" CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS: 1. LONG"); + println!(" (30 FT.) JUMP SHOT; 2. SHORT (15 FT.) JUMP SHOT; 3. LAY"); + println!(" UP; 4. SET SHOT."); + println!("BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS"); + println!("FOLLOWS: 6. PRESS; 6.5 MAN-TO MAN; 7. ZONE; 7.5 NONE."); + println!("TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT."); + + let mut game = BasketballGame::new(); + game.play(); +} + +struct BasketballGame { + rng: ThreadRng, + // D is the defensive alignment. It is one of 6, 6.5, 7 or 7.5 (but the + // original code only validates that D >= 6). + defense: f64, + // The opponent's name. + opponent: String, + // Game clock. Incremented on most possessions; 50 ends the first half, + // 92 triggers the two-minute warning and 100 forces overtime or the + // final whistle. + time: i32, + // Score: index 0 is the opponent, index 1 is Dartmouth. + score: [i32; 2], +} + +impl BasketballGame { + fn new() -> Self { + let rng = rand::rng(); + + // Line 76: prompt for the starting defense. The original accepts any + // value >= 6, so we replicate that rather than restricting to the + // documented options. + let defense = loop { + print!("YOUR STARTING DEFENSE WILL BE"); + let d: f64 = read_input(); + if d >= 6.0 { + break d; + } + // BASIC line 76 falls through to 2010 which re-prompts for the + // defensive alignment; reproducing that behaviour here. + }; + println!(); + + print!("CHOOSE YOUR OPPONENT"); + let opponent = read_line().trim().to_string(); + + Self { + rng, + defense, + opponent, + time: 0, + score: [0, 0], + } + } + + fn play(&mut self) { + // Line 370: center jump to open each half/overtime period. + self.center_jump(); + } + + /// Line 370-420: simulates the center jump that starts each period. + fn center_jump(&mut self) { + println!("CENTER JUMP"); + if self.rng.random::() > 3.0 / 5.0 { + println!("DARTMOUTH CONTROLS THE TAP."); + println!(); + self.dartmouth_possession(); + } else { + println!("{} CONTROLS THE TAP.", self.opponent); + self.opponent_possession(); + } + } + + /// Lines 425-500: Dartmouth has the ball and asks the player for a shot. + fn dartmouth_possession(&mut self) { + let z = self.read_shot(); + // Line 460: half the time, when the clock has run out, we go to the + // end-of-game / overtime logic instead of resolving the shot. + if self.rng.random::() < 0.5 && self.time >= 100 { + self.end_of_game_or_overtime(); + return; + } + + match z { + 1 | 2 => self.dartmouth_jump_shot(), + _ => self.dartmouth_non_jump_shot(z), + } + } + + /// Line 430-455: read and validate the player's shot choice. + fn read_shot(&mut self) -> i32 { + loop { + print!("YOUR SHOT"); + let line = read_line(); + match line.trim().parse::() { + Ok(z) if (0..=4).contains(&z) => return z, + _ => print!("INCORRECT ANSWER. RETYPE IT. "), + } + } + } + + /// Lines 1040-1280: long or short jump shot for Dartmouth. + fn dartmouth_jump_shot(&mut self) { + self.advance_clock_with_two_minute_warning(); + println!("JUMP SHOT"); + + if self.rng.random::() <= 0.341 * self.defense / 8.0 { + println!("SHOT IS GOOD."); + self.add_dartmouth_score(); + self.opponent_possession(); + return; + } + if self.rng.random::() <= 0.682 * self.defense / 8.0 { + println!("SHOT IS OFF TARGET."); + if self.defense / 6.0 * self.rng.random::() > 0.45 { + println!("REBOUND TO {}", self.opponent); + self.opponent_possession(); + } else { + println!("DARTMOUTH CONTROLS THE REBOUND."); + if self.rng.random::() <= 0.4 { + println!(); + self.dartmouth_non_jump_shot(3); + } else if self.defense == 6.0 && self.rng.random::() > 0.6 { + // Line 5100-5130: pass stolen, easy lay-up for opponent. + println!("PASS STOLEN BY {} EASY LAYUP.", self.opponent); + self.add_opponent_score(); + self.dartmouth_possession(); + } else { + print!("BALL PASSED BACK TO YOU. "); + self.dartmouth_possession(); + } + } + return; + } + if self.rng.random::() <= 0.782 * self.defense / 8.0 { + print!("SHOT IS BLOCKED. BALL CONTROLLED BY "); + if self.rng.random::() > 0.5 { + println!("DARTMOUTH."); + self.dartmouth_possession(); + } else { + println!("{}.", self.opponent); + self.opponent_possession(); + } + return; + } + if self.rng.random::() <= 0.843 * self.defense / 8.0 { + println!("SHOOTER IS FOULED. TWO SHOTS."); + self.foul_shots(1); + self.opponent_possession(); + return; + } + println!("CHARGING FOUL. DARTMOUTH LOSES BALL."); + self.opponent_possession(); + } + + /// Lines 1300-1700: lay up (3) or set shot (4); also re-entered for a + /// rebound put-back. A shot value of 0 means the player wants to change + /// the defensive alignment (lines 2010-2040). + fn dartmouth_non_jump_shot(&mut self, z: i32) { + self.advance_clock_with_two_minute_warning(); + + if z == 0 { + self.change_defense(); + return; + } + + if z == 4 { + println!("SET SHOT."); + } else { + println!("LAY UP."); + } + + if 7.0 / self.defense * self.rng.random::() <= 0.4 { + println!("SHOT IS GOOD. TWO POINTS."); + self.add_dartmouth_score(); + self.opponent_possession(); + return; + } + if 7.0 / self.defense * self.rng.random::() <= 0.7 { + println!("SHOT IS OFF THE RIM."); + if self.rng.random::() > 2.0 / 3.0 { + println!("DARTMOUTH CONTROLS THE REBOUND."); + if self.rng.random::() > 0.4 { + println!("BALL PASSED BACK TO YOU."); + self.dartmouth_possession(); + } else { + self.dartmouth_non_jump_shot(3); + } + } else { + println!("{} CONTROLS THE REBOUND.", self.opponent); + self.opponent_possession(); + } + return; + } + if 7.0 / self.defense * self.rng.random::() <= 0.875 { + println!("SHOOTER FOULED. TWO SHOTS."); + self.foul_shots(1); + self.opponent_possession(); + return; + } + if 7.0 / self.defense * self.rng.random::() <= 0.925 { + println!("SHOT BLOCKED. {}'S BALL.", self.opponent); + self.opponent_possession(); + return; + } + println!("CHARGING FOUL. DARTMOUTH LOSES THE BALL."); + self.opponent_possession(); + } + + /// Lines 2010-2040: change the defensive alignment. Any value >= 6 is + /// accepted, matching the original (unvalidated) behaviour. + fn change_defense(&mut self) { + loop { + print!("YOUR NEW DEFENSIVE ALLIGNMENT IS"); + let d: f64 = read_input(); + if d >= 6.0 { + self.defense = d; + break; + } + } + self.dartmouth_possession(); + } + + /// Lines 3000-3810: opponent has the ball. Picks a shot type at random. + fn opponent_possession(&mut self) { + self.advance_clock(); + + let z1 = 10.0 / 4.0 * self.rng.random::() + 1.0; + if z1 > 2.0 { + self.opponent_non_jump_shot(z1); + } else { + self.opponent_jump_shot(); + } + } + + /// Lines 3040-3310: opponent jump shot. + fn opponent_jump_shot(&mut self) { + println!("JUMP SHOT."); + if 8.0 / self.defense * self.rng.random::() <= 0.35 { + println!("SHOT IS GOOD."); + self.add_opponent_score(); + self.dartmouth_possession(); + return; + } + if 8.0 / self.defense * self.rng.random::() <= 0.75 { + println!("SHOT IS OFF RIM."); + if self.defense / 6.0 * self.rng.random::() > 0.5 { + println!("{} CONTROLS THE REBOUND.", self.opponent); + if self.defense == 6.0 && self.rng.random::() > 0.75 { + // Line 5000-5030: ball stolen, easy lay-up for Dartmouth. + println!("BALL STOLEN. EASY LAY UP FOR DARTMOUTH."); + self.add_dartmouth_score(); + self.opponent_possession(); + } else if self.rng.random::() > 0.5 { + println!("PASS BACK TO {} GUARD.", self.opponent); + self.opponent_possession(); + } else { + self.opponent_non_jump_shot(3.0); + } + } else { + println!("DARTMOUTH CONTROLS THE REBOUND."); + self.dartmouth_possession(); + } + return; + } + if 8.0 / self.defense * self.rng.random::() <= 0.9 { + println!("PLAYER FOULED. TWO SHOTS."); + self.foul_shots(0); + self.dartmouth_possession(); + return; + } + println!("OFFENSIVE FOUL. DARTMOUTH'S BALL."); + self.dartmouth_possession(); + } + + /// Lines 3500-3810: opponent lay up or set shot. `z1` carries the + /// randomly chosen shot type so the put-back message can be re-shown. + fn opponent_non_jump_shot(&mut self, z1: f64) { + if z1 > 3.0 { + println!("SET SHOT."); + } else { + println!("LAY UP"); + } + if 7.0 / self.defense * self.rng.random::() <= 0.413 { + println!("SHOT IS GOOD."); + self.add_opponent_score(); + self.dartmouth_possession(); + return; + } + println!("SHOT IS MISSED."); + if self.defense / 6.0 * self.rng.random::() > 0.5 { + println!("{} CONTROLS THE REBOUND.", self.opponent); + if self.defense == 6.0 && self.rng.random::() > 0.75 { + println!("BALL STOLEN. EASY LAY UP FOR DARTMOUTH."); + self.add_dartmouth_score(); + self.opponent_possession(); + } else if self.rng.random::() > 0.5 { + self.opponent_non_jump_shot(z1); + } else { + println!("PASS BACK TO {} GUARD.", self.opponent); + self.opponent_possession(); + } + } else { + println!("DARTMOUTH CONTROLS THE REBOUND."); + self.dartmouth_possession(); + } + } + + /// Lines 4000-4110: two foul shots. `team` is 0 for the opponent (points + /// added to Dartmouth's opponent) or 1 for Dartmouth (points added to + /// Dartmouth). + fn foul_shots(&mut self, team: usize) { + if self.rng.random::() <= 0.49 { + println!("SHOOTER MAKES BOTH SHOTS."); + self.score[1 - team] += 2; + self.print_score(); + } else if self.rng.random::() <= 0.75 { + println!("SHOOTER MAKES ONE SHOT AND MISSES ONE."); + self.score[1 - team] += 1; + self.print_score(); + } else { + println!("BOTH SHOTS MISSED."); + self.print_score(); + } + } + + /// Line 6010: prints the running score. + fn print_score(&self) { + println!("SCORE: {} TO {}", self.score[1], self.score[0]); + } + + /// Line 7000: Dartmouth scores two points, then prints the score. + fn add_dartmouth_score(&mut self) { + self.score[1] += 2; + self.print_score(); + } + + /// Line 6000: opponent scores two points, then prints the score. + fn add_opponent_score(&mut self) { + self.score[0] += 2; + self.print_score(); + } + + /// Lines 1041/1301/3008: advance the clock and handle the end-of-half + /// break at T == 50. + fn advance_clock(&mut self) { + self.time += 1; + if self.time == 50 { + self.end_of_first_half(); + } + } + + /// Lines 1042-1046 / 1302-1304 / 3012-3015: advance the clock, handle the + /// end-of-half break at T == 50 and the two-minute warning at T == 92. + fn advance_clock_with_two_minute_warning(&mut self) { + self.time += 1; + if self.time == 50 { + self.end_of_first_half(); + } + if self.time == 92 { + self.two_minute_warning(); + } + } + + /// Lines 610-620: two-minute warning message. + fn two_minute_warning(&self) { + println!(); + println!(" *** TWO MINUTES LEFT IN THE GAME ***"); + println!(); + } + + /// Lines 8000-8020: end of the first half, then re-jump to start the + /// second half. + fn end_of_first_half(&mut self) { + println!(); + println!(" ***** END OF FIRST HALF *****"); + println!(); + println!( + "SCORE: DARTMOUTH:{} {}:{}", + self.score[1], self.opponent, self.score[0] + ); + println!(); + println!(); + self.center_jump(); + } + + /// Lines 490-520: the clock has expired. Either start overtime (if the + /// game is tied) or end the game. + fn end_of_game_or_overtime(&mut self) { + println!(); + if self.score[1] != self.score[0] { + println!(" ***** END OF GAME *****"); + println!( + "FINAL SCORE: DARTMOUTH:{} {}:{}", + self.score[1], self.opponent, self.score[0] + ); + return; + } + + println!(); + println!(" ***** END OF SECOND HALF *****"); + println!(); + println!("SCORE AT END OF REGULATION TIME:"); + println!( + " DARTMOUTH:{} {}:{}", + self.score[1], self.opponent, self.score[0] + ); + println!(); + println!("BEGIN TWO MINUTE OVERTIME PERIOD"); + self.time = 93; + self.center_jump(); + } +} + +/// Reads a line of input, flushing stdout first so the prompt is visible. +fn read_line() -> String { + print!("? "); + let _ = io::stdout().flush(); + + let mut buffer = String::new(); + let _ = io::stdin().read_line(&mut buffer); + buffer +} + +/// Reads a line and parses it as the requested numeric type, re-prompting on +/// any parse failure. +fn read_input() -> T { + loop { + let line = read_line(); + if let Ok(value) = line.trim().parse::() { + return value; + } + } +} diff --git a/README.md b/README.md index 3b54d4471..adb144cb4 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ NOTE: per [the official blog post announcement](https://blog.codinghorror.com/up | 04_Awari | x | x | x | | | x | x | x | x | x | | 05_Bagels | x | x | x | x | x | x | x | x | x | x | | 06_Banner | x | x | x | | | x | x | x | x | x | -| 07_Basketball | x | x | x | | | x | x | x | | x | +| 07_Basketball | x | x | x | | | x | x | x | x | x | | 08_Batnum | x | x | x | | | x | x | x | x | x | | 09_Battle | x | x | x | | | | x | | x | x | | 10_Blackjack | x | x | x | | | | x | x | x | x |