|
| 1 | +# Green and Yellow Game |
| 2 | + |
| 3 | +In this assignment we will implement the game "Green and Yellow". It’s like Wordle, but with numerical digits instead of letters. But for legal reasons it’s also entirely unlike Wordle, nor remotely similar to the 1970’s board-game "Mastermind". |
| 4 | + |
| 5 | +## After completing this exercise you are able to |
| 6 | + |
| 7 | +- Work with rust slices and vectors |
| 8 | +- Accept input from stdin |
| 9 | +- Iterate through arrays and slices |
| 10 | +- Generate random numbers |
| 11 | + |
| 12 | +## Prerequisites |
| 13 | + |
| 14 | +For completing this exercise you need to have: |
| 15 | + |
| 16 | +- basic Rust programming skills |
| 17 | +- the Rust Syntax Cheat Sheet |
| 18 | + |
| 19 | +## Task |
| 20 | + |
| 21 | +1. Create a new binary crate called `green-yellow` |
| 22 | +1. Copy all the test cases into into your `main.rs` |
| 23 | +1. Define a function `fn calc_green_and_yellow(guess: &[u8; 4], secret: &[u8; 4]) -> String` that implements the following rules: |
| 24 | + - Return a string containing four Unicode characters |
| 25 | + - For every item in guess, if `guess[i] == secret[i]`, then position `i` in the output String should be a green block (`🟩`) |
| 26 | + - Then, for every item in guess, if `guess[i]` is in `secret` somewhere, and hasn't already been matched, then position `i` in the output String should be a yellow block (`🟨`) |
| 27 | + - If any of the guesses do not appear in the secret, then that position in the output String should be a grey block (`⬜`) |
| 28 | +2. Ensure all the test cases pass! |
| 29 | +3. Write a main function that implements the following: |
| 30 | + - Generate 4 random digits - our 'secret' |
| 31 | + - Go into a loop |
| 32 | + - Read a string from *Standard In* and trim the whitespace off it |
| 33 | + - Parse that string into a guess, containing four digits (give an error if the user makes a mistake) |
| 34 | + - Run the calculation routine above and print the coloured blocks |
| 35 | + - Exit if all the blocks are green |
| 36 | +4. Play the game |
| 37 | + |
| 38 | +If you need it, we have provided a [complete solution](../../exercise-solutions/green-yellow/src/bin/complete.rs) for this exercise. |
| 39 | + |
| 40 | +Your test cases are: |
| 41 | + |
| 42 | +```rust |
| 43 | +#[test] |
| 44 | +fn all_wrong() { |
| 45 | + assert_eq!( |
| 46 | + &calc_green_and_yellow(&[5, 6, 7, 8], &[1, 2, 3, 4]), |
| 47 | + "⬜⬜⬜⬜" |
| 48 | + ); |
| 49 | +} |
| 50 | + |
| 51 | +#[test] |
| 52 | +fn all_green() { |
| 53 | + assert_eq!( |
| 54 | + &calc_green_and_yellow(&[1, 2, 3, 4], &[1, 2, 3, 4]), |
| 55 | + "🟩🟩🟩🟩" |
| 56 | + ); |
| 57 | +} |
| 58 | + |
| 59 | +#[test] |
| 60 | +fn one_wrong() { |
| 61 | + assert_eq!( |
| 62 | + &calc_green_and_yellow(&[1, 2, 3, 5], &[1, 2, 3, 4]), |
| 63 | + "🟩🟩🟩⬜" |
| 64 | + ); |
| 65 | +} |
| 66 | + |
| 67 | +#[test] |
| 68 | +fn all_yellow() { |
| 69 | + assert_eq!( |
| 70 | + &calc_green_and_yellow(&[4, 3, 2, 1], &[1, 2, 3, 4]), |
| 71 | + "🟨🟨🟨🟨" |
| 72 | + ); |
| 73 | +} |
| 74 | + |
| 75 | +#[test] |
| 76 | +fn one_wrong_but_duplicate() { |
| 77 | + assert_eq!( |
| 78 | + &calc_green_and_yellow(&[1, 2, 3, 1], &[1, 2, 3, 4]), |
| 79 | + "🟩🟩🟩⬜" |
| 80 | + ); |
| 81 | +} |
| 82 | + |
| 83 | +#[test] |
| 84 | +fn one_right_others_duplicate() { |
| 85 | + assert_eq!( |
| 86 | + &calc_green_and_yellow(&[1, 1, 1, 1], &[1, 2, 3, 4]), |
| 87 | + "🟩⬜⬜⬜" |
| 88 | + ); |
| 89 | +} |
| 90 | + |
| 91 | +#[test] |
| 92 | +fn two_right_two_swapped() { |
| 93 | + assert_eq!( |
| 94 | + &calc_green_and_yellow(&[1, 2, 2, 2], &[2, 2, 2, 1]), |
| 95 | + "🟨🟩🟩🟨" |
| 96 | + ); |
| 97 | +} |
| 98 | + |
| 99 | +#[test] |
| 100 | +fn two_wrong_two_swapped() { |
| 101 | + assert_eq!( |
| 102 | + &calc_green_and_yellow(&[1, 3, 3, 2], &[2, 2, 2, 1]), |
| 103 | + "🟨⬜⬜🟨" |
| 104 | + ); |
| 105 | +} |
| 106 | + |
| 107 | +#[test] |
| 108 | +fn a_bit_of_everything() { |
| 109 | + assert_eq!( |
| 110 | + &calc_green_and_yellow(&[1, 9, 4, 3], &[1, 2, 3, 4]), |
| 111 | + "🟩⬜🟨🟨" |
| 112 | + ); |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +## Knowledge |
| 117 | + |
| 118 | +### Generating Random Numbers |
| 119 | + |
| 120 | +There are no random number generators in the standard library - you have to use the `rand` crate. |
| 121 | + |
| 122 | +You will need to change `Cargo.toml` to depend on the `rand` crate - we suggest version `0.8`. |
| 123 | + |
| 124 | +You need a random number generator (call `rand::thread_rng()`), and using that you can generate a number out of a given range with `gen_range`. See <https://docs.rs/rand> for more details. |
| 125 | + |
| 126 | +### Reading from the Console |
| 127 | + |
| 128 | +You need to grab a standard input handle with `std::io::stdin()`. This implments the `std::io::Write` trait, so you can call `read_to_string(&mut some_string)` and get a line of text into your `some_string: String` variable. |
| 129 | + |
| 130 | +### Parsing Strings into Integers |
| 131 | + |
| 132 | +Strings have a `parse()` method, which returns a `Result`, because of course the user may not have typed in a proper digit. The `parse()` function works out what you are trying to create based on context - so if you want a `u8`, try `let x: u8 = my_str.parse().unwrap()`. Or you can say `let x = my_str.parse::<u8>().unwrap()`. Of course, try and do something better than unwrap! |
| 133 | + |
| 134 | +## Step-by-Step-Solution |
| 135 | + |
| 136 | +In general, we also recommend to use the Rust documentation to figure out things you are missing to familiarize yourself with it. If you ever feel completely stuck or that you haven’t understood something, please hail the trainers quickly. |
| 137 | + |
| 138 | +### Step 1: New Project |
| 139 | + |
| 140 | +Create a new binary Cargo project, check the build and see if it runs. |
| 141 | + |
| 142 | +<details> |
| 143 | + <summary>Solution</summary> |
| 144 | + |
| 145 | +```shell |
| 146 | +cargo new green-yellow |
| 147 | +cd fizzbuzz |
| 148 | +cargo run |
| 149 | +``` |
| 150 | + |
| 151 | +</details> |
| 152 | + |
| 153 | +### Step 2: Generate some squares |
| 154 | + |
| 155 | +Get `calc_green_and_yellow` to just generate grey blocks. We put them in an `Vec` first, as that's easier to index than a string. |
| 156 | + |
| 157 | +Call the function from `main()` to avoid the warning about it being unused. |
| 158 | + |
| 159 | +<details> |
| 160 | + <summary>Solution</summary> |
| 161 | + |
| 162 | +```rust ignore |
| 163 | +{{#include ../../exercise-solutions/green-yellow/src/bin/step2.rs:3:7}} |
| 164 | +``` |
| 165 | + |
| 166 | +</details> |
| 167 | + |
| 168 | +### Step 3: Check for green squares |
| 169 | + |
| 170 | +You need to go through every pair of items in the input arrays and check if they are the same. If so, set the output square to be green. |
| 171 | + |
| 172 | +<details> |
| 173 | + <summary>Solution</summary> |
| 174 | + |
| 175 | +```rust ignore |
| 176 | +{{#include ../../exercise-solutions/green-yellow/src/bin/step3.rs:3:13}} |
| 177 | +``` |
| 178 | + |
| 179 | +</details> |
| 180 | + |
| 181 | +### Step 4: Check for yellow squares |
| 182 | + |
| 183 | +This gets a little more tricky. |
| 184 | + |
| 185 | +We need to loop through every item in the guess array and compare it to every item in the secret array. But! We must make sure we ignore any values we already 'used up' when we produced the green squares. |
| 186 | + |
| 187 | +Let's do this by copying the input, so we can make it mutable, and mark off any values used in the green-square-loop by setting them to zero. |
| 188 | + |
| 189 | +<details> |
| 190 | + <summary>Solution</summary> |
| 191 | + |
| 192 | +```rust ignore |
| 193 | +{{#include ../../exercise-solutions/green-yellow/src/bin/step4.rs:3:25}} |
| 194 | +``` |
| 195 | + |
| 196 | +</details> |
| 197 | + |
| 198 | +### Step 5: Get some random numbers |
| 199 | + |
| 200 | +Add `rand = "0.8"` to your Cargo.toml, and make a random number generator with `rand::thread_rng()` (Random Number Generator). You will also have to `use rand::Rng;` to bring the trait into scope. |
| 201 | + |
| 202 | +Call `your_rng.gen_range()` in a loop. |
| 203 | + |
| 204 | +<details> |
| 205 | + <summary>Solution</summary> |
| 206 | + |
| 207 | +```rust ignore |
| 208 | +{{#include ../../exercise-solutions/green-yellow/src/bin/step5.rs:29:38}} |
| 209 | +``` |
| 210 | + |
| 211 | +</details> |
| 212 | + |
| 213 | +### Step 6: Make the game loop |
| 214 | + |
| 215 | +We a loop to handle each guess the user makes. |
| 216 | + |
| 217 | +For each guess we need to read from Standard Input (using `std::io::stdin()` and its `read_line()`) method. |
| 218 | + |
| 219 | +You will need to `trim` and then `split` the input, then `parse` each piece into a digit. |
| 220 | + |
| 221 | +* If the digit doesn't parse, `continue` the loop. |
| 222 | +* If the digit parses but it out of range, `continue` the loop. |
| 223 | +* If you get the wrong number of digits, `continue` the loop. |
| 224 | +* If the guess matches the secret, then break out of the loop and congratulate the winner. |
| 225 | +* Otherwise run the guess through our calculation function and print the squares. |
| 226 | + |
| 227 | +<details> |
| 228 | + <summary>Solution</summary> |
| 229 | + |
| 230 | +```rust ignore |
| 231 | +{{#include ../../exercise-solutions/green-yellow/src/bin/step6.rs:41:72}} |
| 232 | +``` |
| 233 | + |
| 234 | +</details> |
0 commit comments