From 5b0e9849bf03e012264f4bb98924788792b70f54 Mon Sep 17 00:00:00 2001 From: Hamed Mahmoudkhani Date: Wed, 5 Aug 2026 01:14:53 +0200 Subject: [PATCH] feat: added rust implementation of Bowling --- 14_Bowling/rust/Cargo.lock | 77 +++++++++++++ 14_Bowling/rust/Cargo.toml | 7 ++ 14_Bowling/rust/src/main.rs | 213 ++++++++++++++++++++++++++++++++++++ README.md | 8 +- 4 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 14_Bowling/rust/Cargo.lock create mode 100644 14_Bowling/rust/Cargo.toml create mode 100644 14_Bowling/rust/src/main.rs diff --git a/14_Bowling/rust/Cargo.lock b/14_Bowling/rust/Cargo.lock new file mode 100644 index 000000000..54f98ac40 --- /dev/null +++ b/14_Bowling/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 = "bowling" +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/14_Bowling/rust/Cargo.toml b/14_Bowling/rust/Cargo.toml new file mode 100644 index 000000000..159e7f1f9 --- /dev/null +++ b/14_Bowling/rust/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bowling" +version = "0.1.0" +edition = "2024" + +[dependencies] +rand = "0.10.0" diff --git a/14_Bowling/rust/src/main.rs b/14_Bowling/rust/src/main.rs new file mode 100644 index 000000000..0520cfca1 --- /dev/null +++ b/14_Bowling/rust/src/main.rs @@ -0,0 +1,213 @@ +//! Rust port of "Bowl" (14_Bowling) from _Basic Computer Games_ (1978). +use rand::{rngs::ThreadRng, RngExt}; +use std::io::{self, BufRead, Write}; + +/// Number of cells in the pin-accumulator array (`DIM C(15)` in BASIC). +const CELLS: usize = 15; +/// The first ten cells correspond to the ten real pins drawn on the diagram. +const PINS: usize = 10; + +fn main() { + println!("{:>34}BOWL", ""); + println!("{:>15}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", ""); + println!("\n\n"); + + println!("WELCOME TO THE ALLEY"); + println!("BRING YOUR FRIENDS"); + println!("OKAY LET'S FIRST GET ACUUAINTED"); + println!(); + + let mut game = BowlingGame::new(); + loop { + let answer = prompt_line("THE INSTRUCTIONS (Y/N)"); + if answer.starts_with('Y') { + print_instructions(); + } else if answer.starts_with('N') { + // fall through to the game + } else { + continue; + } + + game.play(); + + let again = prompt_line("DO YOU WANT ANOTHER GAME"); + if !again.starts_with('Y') { + break; + } + } +} + +fn print_instructions() { + println!("THE GAME OF BOWLING TAKES MIND AND SKILL.DURING THE GAME"); + println!("THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH"); + println!("OTHER PLAYERS[UP TO FOUR].YOU WILL BE PLAYING TEN FRAMES"); + println!("ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE"); + println!("PIN IS STANDING.AFTER THE GAME THE COMPUTER WILL SHOW YOUR"); + println!("SCORES ."); +} + +struct BowlingGame { + rng: ThreadRng, +} + +impl BowlingGame { + fn new() -> Self { + Self { rng: rand::rng() } + } + + /// Play one complete game (10 frames, all players), then print the scorecard. + fn play(&mut self) { + let players = loop { + // "FIRST OF ALL...HOW MANY ARE PLAYING" + let line = prompt_line("FIRST OF ALL...HOW MANY ARE PLAYING"); + if line.is_empty() { + // EOF on input — bail out of this game. + return; + } + if let Ok(n) = line.trim().parse::() { + if (1..=4).contains(&n) { + break n; + } + } + println!("?RE-ENTER"); + }; + + println!(); + println!("VERY GOOD..."); + + // One row per player: [ball1, ball2, status] for each of 10 frames. + let mut scores: Vec<[[u32; 3]; 10]> = (0..players).map(|_| [[0; 3]; 10]).collect(); + + for frame in 1..=10 { + for (player_idx, player_scores) in scores.iter_mut().enumerate() { + self.play_ball(frame, player_idx + 1, player_scores); + } + } + + print_scorecard(&scores); + } + + /// Roll the balls for one player in one frame, recording the result. + fn play_ball(&mut self, frame: usize, player: usize, scores: &mut [[u32; 3]; 10]) { + let mut prev_down = 0; // M: pins down after the previous ball this frame + let mut status = 0; // Q: 3 strike, 2 spare, 1 error, 0 otherwise + + let mut cells = [0u32; CELLS + 1]; + + for ball in 1..=2 { + // "TYPE ROLL TO GET THE BALL GOING." + let _ = prompt_line("TYPE ROLL TO GET THE BALL GOING"); + + // Knock down more pins: 20 random hits, each landing in cell + // (15*J - X) where J is the smallest bucket with X < 15*J. + self.roll_pins(&mut cells); + + // Draw the triangle diagram (only the first 10 cells are real pins). + print!("PLAYER:{} FRAME:{} BALL:{}\n", player, frame, ball); + print_pin_diagram(&cells); + + // Count pins down among the real pins (cells 1..=10). + let down = cells[1..=PINS].iter().filter(|&&c| c == 1).count() as u32; + + // Roll analysis. Order mirrors the original IF chain. + if down == prev_down { + println!("GUTTER!!"); + } + if ball == 1 && down == 10 { + println!("STRIKE!!!!!\x07\x07\x07\x07"); + status = 3; + } + if ball == 2 && down == 10 { + println!("SPARE!!!!"); + status = 2; + } + if ball == 2 && down < 10 { + println!("ERROR!!!"); + status = 1; + } + if ball == 1 && down < 10 { + println!("ROLL YOUR 2ND BALL"); + } + println!(); + + // Record the score for this ball: total pins down so far this frame. + scores[frame - 1][ball - 1] = down; + + if ball == 1 { + prev_down = down; + // On a strike, the frame ends; otherwise prompt the second ball. + if status == 3 { + scores[frame - 1][1] = 0; + break; + } + } + } + + scores[frame - 1][2] = status; + } + + fn roll_pins(&mut self, cells: &mut [u32]) { + for _ in 0..20 { + let x = (self.rng.random::() * 100.0) as usize; // INT(RND(1)*100) + // Find the smallest J in 1..=10 with x < 15*J. + let mut j = 1; + while j <= 10 && x >= 15 * j { + j += 1; + } + // j may exceed 10 if x >= 150 (impossible here since x < 100) — guard anyway. + if j <= 10 { + let idx = 15 * j - x; // 1..=15 + if idx >= 1 && idx <= CELLS { + cells[idx] = 1; + } + } + } + } +} + +/// Draw the standing/down pins as a triangle: '+' standing, 'O' down. +fn print_pin_diagram(cells: &[u32]) { + let mut k = 1; // pin index, 1-based (K in BASIC) + for i in 0..4 { + print!("{}", " ".repeat(i)); + for _ in 0..(4 - i) { + if cells[k] == 1 { + print!("O "); + } else { + print!("+ "); + } + k += 1; + } + println!(); + } + println!(); +} + +fn print_scorecard(scores: &[[[u32; 3]; 10]]) { + println!("FRAMES"); + for frame in 1..=10 { + print!("{} ", frame); + } + println!(); + for player_scores in scores { + for row in 0..3 { + for frame in 0..10 { + print!("{} ", player_scores[frame][row]); + } + println!(); + } + println!(); + } +} + +fn prompt_line(prompt: &str) -> String { + print!("{}? ", prompt); + let _ = io::stdout().lock().flush(); + + let mut buffer = String::new(); + // Treat EOF (or a read error) as an empty line so callers can decide what + // to do — for this game, an empty answer never matches Y/N, so the program + // simply exits its current loop instead of spinning forever on EOF. + let _ = io::stdin().lock().read_line(&mut buffer); + buffer.trim().to_uppercase() +} diff --git a/README.md b/README.md index 3b54d4471..dc3936e9a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ I have moved all [the original BASIC source code](http://www.vintage-basic.net/g Each project has subfolders corresponding to the languages we’d like to see the games ported to. This is based on the [2022 TIOBE index of top languages](https://www.tiobe.com/tiobe-index/) that are _**memory safe**_ and _**general purpose scripting languages**_ per [this post](https://discourse.codinghorror.com/t/-/7927/34): -1. C# +1. C# 2. Java 3. JavaScript 4. Kotlin @@ -40,7 +40,7 @@ If you wish to port one of the programs to a language not in our list – that i Feel free to begin converting these classic games into the above list of modern, memory safe languages. In fact, courtesy of @mojoaxel, you can even view the JavaScript versions in your web browser at -https://coding-horror.github.io/basic-computer-games/ + But first, a few guidelines: @@ -66,7 +66,7 @@ We want the general behavior of the original programs to be preserved, _however_ Please note that on the back of the Basic Computer Games book it says **Microsoft 8K Basic, Rev 4.0 was the version David Ahl used to test**, so that is the level of compatibility we are looking for.  QBasic on the DOS emulation is a later version of Basic but one that retains downwards compatibility so far in our testing. To verify behavior, try [running the programs in your browser](https://troypress.com/wp-content/uploads/user/js-basic/index.html) with [JS BASIC, effectively Applesoft BASIC](https://github.com/inexorabletash/jsbasic/). -### Have fun! +### Have fun Thank you for taking part in this project to update a classic programming book – one of the most influential programming books in computing history – for 2022 and beyond! @@ -91,7 +91,7 @@ NOTE: per [the official blog post announcement](https://blog.codinghorror.com/up | 11_Bombardment | x | x | x | | | x | x | x | x | x | | 12_Bombs_Away | x | x | x | | x | x | x | | x | x | | 13_Bounce | x | x | x | | | x | x | x | x | x | -| 14_Bowling | x | x | x | | | x | x | | | x | +| 14_Bowling | x | x | x | | | x | x | | x | x | | 15_Boxing | x | x | x | | | x | x | | | x | | 16_Bug | x | x | x | | | | x | x | | x | | 17_Bullfight | x | x | x | x | | | x | | | x |