From fa283882c17c5daf8ca81089d6cdbcfa09f6c322 Mon Sep 17 00:00:00 2001 From: Will Crichton Date: Fri, 31 May 2024 18:18:20 -0700 Subject: [PATCH] Guessing game --- .github/workflows/ci.yaml | 39 ++++ .gitignore | 1 + book/.gitignore | 1 + book/book.toml | 9 + book/src/SUMMARY.md | 3 + book/src/chapter_1.md | 147 ++++++++++++ book/styles.css | 47 ++++ rs/Cargo.lock | 227 +++++++++++++++++++ rs/Cargo.toml | 3 + rs/crates/guessing-game-grader/.gitignore | 1 + rs/crates/guessing-game-grader/Cargo.toml | 10 + rs/crates/guessing-game-grader/src/grader.rs | 183 +++++++++++++++ rs/crates/guessing-game-grader/src/main.rs | 10 + rs/crates/guessing-game/.gitignore | 1 + rs/crates/guessing-game/Cargo.toml | 7 + rs/crates/guessing-game/src/main.rs | 33 +++ 16 files changed, 722 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 book/.gitignore create mode 100644 book/book.toml create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/chapter_1.md create mode 100644 book/styles.css create mode 100644 rs/Cargo.lock create mode 100644 rs/Cargo.toml create mode 100644 rs/crates/guessing-game-grader/.gitignore create mode 100644 rs/crates/guessing-game-grader/Cargo.toml create mode 100644 rs/crates/guessing-game-grader/src/grader.rs create mode 100644 rs/crates/guessing-game-grader/src/main.rs create mode 100644 rs/crates/guessing-game/.gitignore create mode 100644 rs/crates/guessing-game/Cargo.toml create mode 100644 rs/crates/guessing-game/src/main.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..390da83 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: + - "**" + tags-ignore: + - "v*" + pull_request: + branches: + - "**" + +jobs: + CI: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install mdbook + run: | + mkdir bin + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin + echo "$(pwd)/bin" >> ${GITHUB_PATH} + shell: bash + + - name: Test Rust + run: cargo test + working-directory: rs + + - name: Build book + run: mdbook build + working-directory: book + + - name: Deploy to Github Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: book/book diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1de5659 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000..17aede3 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["Will Crichton"] +language = "en" +multilingual = false +src = "src" +title = "Rust It Yourself" + +[output.html] +additional-css = ["styles.css"] diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..7390c82 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/book/src/chapter_1.md b/book/src/chapter_1.md new file mode 100644 index 0000000..7870fd3 --- /dev/null +++ b/book/src/chapter_1.md @@ -0,0 +1,147 @@ +# Programming a Guessing Game + +Our first project will be to implement a guessing game from scratch. The game should work like this: +- The program will greet the player. +- It will generate a random integer between 1 and 100. +- It will then prompt the player to enter a guess. +- After a guess is entered, the program will indicate whether the guess is too low or too high. +- If the guess is correct, the game will print a congratulatory message and exit. +- Otherwise, the game keeps going. + +## Setting Up a New Project + +First, setup a new project: + +```console +$ cargo new guessing_game +$ cd guessing_game +``` + +
+ What does this do? + + The first command, `cargo new`, takes the name of the project (`guessing_game`) as the first argument. The second command changes to the new project’s directory. +
+ +Then run the project: + +```console +$ cargo run +``` + +It should print `Hello world!`. + +
+ Why does it print this? + + This is the default behavior for new binaries. Look at `src/main.rs`. +
+ +## Processing a Guess + +Open `src/main.rs` and look at the `main` function. Right now, it contains the one line: + +```rust +println!("Hello, world!"); +``` + + +Task 1: greet the player when they run the binary by printing exactly this text: + +```console +$ cargo run +Guess the number! +Please input your guess. +``` + + +For this first project, we will check your work with a provided testing binary called `guessing-game-grader`. Before making your changes, try running it to observe the failing test: + +```console +$ guessing-game-grader +✗ Task 1: Prints the right initial strings + The diff is: + H̶e̶l̶l̶o̶,̶ ̶w̶o̶r̶l̶d̶!̶ + Guess the number! + Please input your guess. +``` + +Once you make the correct edit, the grader should show the test as passing and move on to the next test, like this: + +```console +$ guessing-game-grader +✓ Task 1: Prints the right initial strings +✗ Task 2: Accepts an input + [...] +``` + +
+ I need a hint! + + TODO +
+ +
+ I need the solution! + + TODO +
+ +Once you're ready, move on to the next task. + + +Task 2: read a string from the command line and print it out, like this: + +```console +$ cargo run +Guess the number! +Please input your guess. +15 +You guessed: 15 +``` + + +The key method is [`Stdin::read_line`]. A core skill in the Rust It Yourself series will be learning to read documentation of unfamiliar methods, so let's read the documentation for `read_line`: + +
+ How was I supposed to know that Stdin::read_line is the key method? + + TODO (Google, StackOverflow, ChatGPT, top-down search of the docs, ...) +
+ +> ```rust +> pub fn read_line(&self, buf: &mut String) -> Result +> ``` +> Locks this handle and reads a line of input, appending it to the specified buffer. +> For detailed semantics of this method, see the documentation on [`BufRead::read_line`]. +> ##### Examples +> ```rust +> use std::io; +> +> let mut input = String::new(); +> match io::stdin().read_line(&mut input) { +> Ok(n) => { +> println!("{n} bytes read"); +> println!("{input}"); +> } +> Err(error) => println!("error: {error}"), +> } +> ``` + +The first line is the **type signature** of `read_line`. It says: "I am a method that takes two arguments: an immutable reference to myself ([`Stdin`]), and a mutable reference to a [`String`]. I return a type called `Result`." + +This type signature and example use Rust features we haven't discussed yet. That's expected — another core skill in the Rust It Yourself series is working with code that uses features you don't fully understand. So let's try and learn something from this example anyway. + +The `read_line` method demonstrates two key aspects of Rust: +1. **Mutability:** Rust requires you to be more explicit about when data is mutated in-place. Here, calling `read_line` mutates `input` in-place. That is represented by the fact that the second argument is not a plain `String`, but instead a mutable reference `&mut String`. Additionally, the variable `input` must be declared as `mut` so we are allowed to mutate it. +2. **Error handling:** Rust does not have a concept of `undefined` (as in JS) or `None` (as in Python) or `NULL` (as in C++) or `nil` (as in Go). Rust also does not have exceptions. Instead, to represent "this operation can succeed and return `X` or fail and return `Y`", Rust uses enums in combination with pattern matching via operators like `match`. Unlike enums in other languages, Rust's enums can have fields (similar to tagged unions in C). + +Now, try copy-pasting this example into the bottom of your `main` function. Run the code (with `cargo run`), see how it works, and try editing the example so it completes Task 2. + + + +[`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html +[`Stdin`]: https://doc.rust-lang.org/std/io/struct.Stdin.html +[`Stdin::read_line`]: https://doc.rust-lang.org/std/io/struct.Stdin.html#method.read_line +[`BufRead::read_line`]: https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line +[`String`]: https://doc.rust-lang.org/std/string/struct.String.html \ No newline at end of file diff --git a/book/styles.css b/book/styles.css new file mode 100644 index 0000000..2ccd8ba --- /dev/null +++ b/book/styles.css @@ -0,0 +1,47 @@ +.ayu { + --mdbook-highlight: #46480f; +} + +.coal { + --mdbook-highlight: #46480f; +} + +.light { + --mdbook-highlight: #ffffc1; +} + +.navy { + --mdbook-highlight: #46480f; +} + +.rust { + --mdbook-highlight: #fdf2ca; +} + +summary { + font-size: 80%; + font-style: italic; + display: flex; + justify-content: end; + cursor: pointer; +} + +task { + display: block; + margin-block: 1em; + border: 1px solid var(--searchresults-border-color); + background: var(--mdbook-highlight); + padding: 0.5em; +} + +task > :last-child { + margin-bottom: 0; +} + +task > span { + font-weight: bold; +} + +li { + margin-bottom: 1em; +} \ No newline at end of file diff --git a/rs/Cargo.lock b/rs/Cargo.lock new file mode 100644 index 0000000..f8f7d4e --- /dev/null +++ b/rs/Cargo.lock @@ -0,0 +1,227 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "guessing-game" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "guessing-game-grader" +version = "0.1.0" +dependencies = [ + "anyhow", + "colored", + "prettydiff", + "textwrap", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettydiff" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abec3fb083c10660b3854367697da94c674e9e82aa7511014dc958beeb7215e9" +dependencies = [ + "owo-colors", + "pad", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/rs/Cargo.toml b/rs/Cargo.toml new file mode 100644 index 0000000..85ecff1 --- /dev/null +++ b/rs/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["crates/*"] \ No newline at end of file diff --git a/rs/crates/guessing-game-grader/.gitignore b/rs/crates/guessing-game-grader/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rs/crates/guessing-game-grader/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rs/crates/guessing-game-grader/Cargo.toml b/rs/crates/guessing-game-grader/Cargo.toml new file mode 100644 index 0000000..35130f3 --- /dev/null +++ b/rs/crates/guessing-game-grader/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "guessing-game-grader" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.86" +colored = "2.1.0" +prettydiff = { version = "0.7.0", default-features = false } +textwrap = "0.16.1" diff --git a/rs/crates/guessing-game-grader/src/grader.rs b/rs/crates/guessing-game-grader/src/grader.rs new file mode 100644 index 0000000..b6bc866 --- /dev/null +++ b/rs/crates/guessing-game-grader/src/grader.rs @@ -0,0 +1,183 @@ +use std::{ + io::{Read, Write}, + path::PathBuf, + process::{Command, Stdio}, + thread::sleep, + time::{Duration, Instant}, +}; + +use colored::Colorize; + +type PropResult = Result<(), String>; + +trait Property { + fn name(&self) -> String; + fn satisfies(&self, input: &str) -> PropResult; +} + +impl Property for (S, F) +where + S: AsRef, + F: Fn(&str) -> PropResult, +{ + fn name(&self) -> String { + self.0.as_ref().to_string() + } + + fn satisfies(&self, output: &str) -> PropResult { + self.1(output) + } +} + +type TestSet = (&'static str, Vec>); + +struct Spec { + tests: Vec, +} + +fn guessing_game_spec() -> Spec { + fn parts_to_props(parts: &'static [(&'static str, &'static str)]) -> Vec> { + parts + .iter() + .enumerate() + .map(|(i, (name, contents))| { + Box::new((name, move |output: &str| { + let prefix = parts[..i] + .iter() + .map(|(_, s)| *s) + .collect::>() + .join(""); + let output_fragment = if i > 0 { + output.strip_prefix(&prefix).unwrap() + } else { + &output + }; + if output_fragment.starts_with(contents) { + Ok(()) + } else { + let diff = prettydiff::diff_lines(output_fragment.trim_end(), contents.trim_end()); + let diff_indent = textwrap::indent(&diff.format(), " "); + let err_msg = format!("The diff is:\n{diff_indent}"); + Err(err_msg) + } + })) as Box + }) + .collect() + } + + let happy_path_test = ( + "101\n", + parts_to_props(&[ + ( + "Task 1: Prints the right initial strings", + "Guess the number!\nPlease input your guess.\n", + ), + ("Task 2: Accepts an input", "You guessed: 101\n"), + ("Task 3: Indicates a direction", "Too big!\n"), + ]), + ); + + let error_handling = ( + "foobar\n", + parts_to_props(&[ + ( + "Prints the right initial strings", + "Guess the number!\nPlease input your guess.\n", + ), + ("Handles an invalid input", "Please type a number!"), + ]), + ); + + Spec { + tests: vec![happy_path_test, error_handling], + } +} + +pub struct Grader {} + +fn run_timeout(timeout: Duration, mut f: impl FnMut() -> bool) -> Result<(), String> { + let start = Instant::now(); + loop { + if f() { + return Ok(()); + } else if start.elapsed() > timeout { + return Err("Binary timed out".to_string()); + } else { + sleep(Duration::from_millis(10)); + } + } +} + +impl Grader { + pub fn new() -> Self { + Grader {} + } + + fn exec(&self, input: &str) -> Result { + let mut build_cmd = Command::new("cargo"); + build_cmd + .arg("build") + .spawn() + .map_err(|e| e.to_string())? + .wait() + .map_err(|e| e.to_string())?; + + let mut cmd = Command::new("cargo"); + cmd.args(["run", "-q"]); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + + let mut child = cmd.spawn().map_err(|e| e.to_string())?; + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = child.stdout.take().unwrap(); + + stdin + .write_all(input.as_bytes()) + .map_err(|e| e.to_string())?; + + let _ = run_timeout(Duration::from_millis(500), || { + child.try_wait().unwrap().is_some() + }); + + child.kill().map_err(|e| e.to_string())?; + + run_timeout(Duration::from_millis(500), || { + child.try_wait().unwrap().is_some() + })?; + + let mut stdout_buf = String::new(); + stdout + .read_to_string(&mut stdout_buf) + .map_err(|e| e.to_string())?; + Ok(stdout_buf) + } + + pub fn grade(&mut self) { + let spec = guessing_game_spec(); + for (input, props) in spec.tests { + let output = match self.exec(input) { + Ok(output) => output, + Err(e) => { + println!( + "{}\n{}", + "✗ Binary failed to execute".red(), + textwrap::indent(&e, " ") + ); + return; + } + }; + + for prop in props { + let name = prop.name(); + match prop.satisfies(&output) { + Ok(()) => println!("{} {}", "✓".green(), name.green()), + Err(err) => { + let err_indent = textwrap::indent(&err, " "); + eprintln!("{} {}\n{err_indent}", "✗".red(), name.red(),); + return; + } + } + } + } + } +} diff --git a/rs/crates/guessing-game-grader/src/main.rs b/rs/crates/guessing-game-grader/src/main.rs new file mode 100644 index 0000000..cdbd2a3 --- /dev/null +++ b/rs/crates/guessing-game-grader/src/main.rs @@ -0,0 +1,10 @@ +use anyhow::Result; + +mod grader; + +fn main() -> Result<()> { + let mut grader = grader::Grader::new(); + grader.grade(); + + Ok(()) +} diff --git a/rs/crates/guessing-game/.gitignore b/rs/crates/guessing-game/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rs/crates/guessing-game/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rs/crates/guessing-game/Cargo.toml b/rs/crates/guessing-game/Cargo.toml new file mode 100644 index 0000000..c05742d --- /dev/null +++ b/rs/crates/guessing-game/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "guessing-game" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8.5" diff --git a/rs/crates/guessing-game/src/main.rs b/rs/crates/guessing-game/src/main.rs new file mode 100644 index 0000000..56cade3 --- /dev/null +++ b/rs/crates/guessing-game/src/main.rs @@ -0,0 +1,33 @@ +use std::{cmp::Ordering, io}; + +use rand::Rng; + +fn main() { + println!("Guess the number!"); + println!("Please input your guess."); + + let secret_number = rand::thread_rng().gen_range(1..=100); + + loop { + let mut guess = String::new(); + io::stdin() + .read_line(&mut guess) + .expect("Failed to read line"); + + let Ok(guess) = guess.trim().parse::() else { + println!("Please type a number!"); + continue; + }; + + println!("You guessed: {guess}"); + + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => { + println!("You win!"); + break; + } + } + } +}