From bbfe11764a47a745e06e90601ec641c898064f1b Mon Sep 17 00:00:00 2001 From: David Lotts Date: Wed, 2 Mar 2022 03:28:42 -0500 Subject: [PATCH] Added test, and enums -- slick, but kind of gratuitous --- 74_Rock_Scissors_Paper/rust/Cargo.toml | 4 +- 74_Rock_Scissors_Paper/rust/src/main.rs | 119 +++++++++++++++++------- 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/74_Rock_Scissors_Paper/rust/Cargo.toml b/74_Rock_Scissors_Paper/rust/Cargo.toml index 5f4b4b85..c07af4e5 100644 --- a/74_Rock_Scissors_Paper/rust/Cargo.toml +++ b/74_Rock_Scissors_Paper/rust/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" [dependencies] text_io = "0.1.10" #num-integer = "0.1" -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" +strum = "0.24" +strum_macros = "0.24" \ No newline at end of file diff --git a/74_Rock_Scissors_Paper/rust/src/main.rs b/74_Rock_Scissors_Paper/rust/src/main.rs index e32f373e..899c235f 100644 --- a/74_Rock_Scissors_Paper/rust/src/main.rs +++ b/74_Rock_Scissors_Paper/rust/src/main.rs @@ -1,11 +1,13 @@ /* * Rock paper scissors - * Port from book _Basic Computer Games_ - * By David Lotts + * Originally from the wonderful book: _Basic Computer Games_ + * Port to Rust By David Lotts */ -use rand::Rng; +use rand::{prelude::ThreadRng, Rng}; //Large code. Switch to https://crates.io/crates/oorandom use std::io::{self, Write}; -use text_io::{try_read}; +use strum::EnumCount; +use strum_macros::{Display, EnumCount, FromRepr}; +use text_io::try_read; fn main() { let mut computer_wins = 0; let mut human_wins = 0; @@ -25,10 +27,10 @@ fn main() { for game_number in 1..=qty_games { println!(); println!("GAME NUMBER {}", game_number); - let my_choice: i32 = rng.gen_range(1..=3); // basic: X=INT(RND(1)*3+1) + //let my_choice: i32 = rng.gen_range(1..=3); // basic: X=INT(RND(1)*3+1) let mut your_choice = 0; loop { - println!("3=ROCK...2=SCISSORS...1=PAPER"); + println!("3=ROCK...2=SCISSORS...1=PAPER"); input_int("1...2...3...WHAT'S YOUR CHOICE", &mut your_choice); // interesting validation in original BASIC: IF (K-1)*(K-2)*(K-3)==0 if (1..=3).contains(&your_choice) { @@ -36,61 +38,106 @@ fn main() { } println!("INVALID."); } + // Convert number to enum. Note the type change. Really it is a new variable. + let your_choice = Choice::from_repr((your_choice-1) as usize).unwrap(); + let my_choice = Choice::new_random(&mut rng); + println!("THIS IS MY CHOICE..."); - println!( - "...{}", - match my_choice { - 1 => "PAPER", - 2 => "SCISSORS", - 3 => "ROCK", - _ => "um, what?", + println!("...{}", my_choice.to_string()); + let winner = Winner::decide_winner(my_choice, your_choice); + println!("{}", match winner { + Winner::Tie => { + "TIE GAME. NO WINNER." } - ); - if my_choice == your_choice { - //THEN 250 - println!("TIE GAME. NO WINNER."); - } else { - //if (my_choice == 1 && your_choice == 3) - // || (my_choice > your_choice) - if 1 == (3 + my_choice - your_choice) % 3 - { - println!("WOW! I WIN!!!"); - computer_wins = computer_wins + 1; - } else { - println!("YOU WIN!!!"); - human_wins = human_wins + 1; + Winner::Computer => { + computer_wins = computer_wins + 1; "WOW! I WIN!!!" } - } + Winner::Human => { + human_wins = human_wins + 1; "YOU WIN!!!" + } + }) } println!(); println!("HERE IS THE FINAL GAME SCORE:"); println!("I HAVE WON {} GAME(S).", computer_wins); println!("YOU HAVE WON {} GAME(S).", human_wins); - println!("AND {} GAME(S) ENDED IN A TIE.", qty_games - (computer_wins + human_wins)); + println!( + "AND {} GAME(S) ENDED IN A TIE.", + qty_games - (computer_wins + human_wins) + ); println!(); println!("THANKS FOR PLAYING!!"); } +#[derive(FromRepr, Debug, PartialEq, EnumCount, Display)] +enum Choice { + PAPER, + SCISSORS, + ROCK, +} + +impl Choice { + /// Returns randomly selected paper..rock. + fn new_random(rng: &mut ThreadRng) -> Choice { + Choice::from_repr((rng).gen_range(0..Choice::COUNT)).unwrap() + } +} + +#[derive(FromRepr, Debug, PartialEq, EnumCount)] +pub enum Winner { + Human, + Computer, + Tie, +} + +impl Winner { + /// take opponent's choices and decide the winner + // I really learned alot about enums here, and now you can too! + // Originally I broke this out for auto testing. + fn decide_winner(my_choice: Choice, your_choice: Choice) -> Winner { + let my_choice = my_choice as u8; + let your_choice = your_choice as u8; + if my_choice == your_choice { + return Winner::Tie; + } + // wordy but clear way: + // if (my_choice == 1 && your_choice == 3) || (my_choice > your_choice) + // consice but opaque way: + if 1 == (3 + my_choice - your_choice) % 3 { + return Winner::Computer + } + return Winner::Human + } +} + +/// print the prompt, wait for a number and newline. Loop if invalid. fn input_int(prompt: &str, number: &mut i32) { loop { print!("{} ? ", prompt); io::stdout().flush().unwrap(); - match try_read!() { - Ok(n) => { + if let Ok(n) = try_read!() { *number = n; return; - } - Err(_) => println!("{}", prompt), } } } - // TODO break out the winner decider and test it. #[cfg(test)] mod tests { #[test] fn winner_test() { - assert_eq!(true,true); + use super::*; + use Winner::*; + use Choice::*; //decide_winner(my_choice=computer, your_choice=human) + assert_eq!(Winner::decide_winner(PAPER ,PAPER ), Tie); + assert_eq!(Winner::decide_winner(PAPER ,SCISSORS), Human); + assert_eq!(Winner::decide_winner(PAPER ,ROCK), Computer); + assert_eq!(Winner::decide_winner(SCISSORS,PAPER ), Computer); + assert_eq!(Winner::decide_winner(SCISSORS,SCISSORS), Tie); + assert_eq!(Winner::decide_winner(SCISSORS,ROCK), Human); + assert_eq!(Winner::decide_winner(ROCK ,PAPER ), Human); + assert_eq!(Winner::decide_winner(ROCK ,SCISSORS), Computer); + assert_eq!(Winner::decide_winner(ROCK ,ROCK), Tie); } -} \ No newline at end of file +}