From 2291f074d3f83768ca48e8fa222fa6a04308b885 Mon Sep 17 00:00:00 2001 From: AnthonyMichaelTDM <68485672+AnthonyMichaelTDM@users.noreply.github.com> Date: Sun, 6 Mar 2022 20:59:17 -0800 Subject: [PATCH 1/2] rust port of 66_mastermind started human as code breaker part done --- 60_Mastermind/rust/Cargo.toml | 9 + 60_Mastermind/rust/README.md | 3 + 60_Mastermind/rust/src/main.rs | 311 +++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 60_Mastermind/rust/Cargo.toml create mode 100644 60_Mastermind/rust/README.md create mode 100644 60_Mastermind/rust/src/main.rs diff --git a/60_Mastermind/rust/Cargo.toml b/60_Mastermind/rust/Cargo.toml new file mode 100644 index 00000000..1c2935d4 --- /dev/null +++ b/60_Mastermind/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/60_Mastermind/rust/README.md b/60_Mastermind/rust/README.md new file mode 100644 index 00000000..f84e546c --- /dev/null +++ b/60_Mastermind/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/) by Anthony Rubick [AnthonyMichaelTDM](https://github.com/AnthonyMichaelTDM) \ No newline at end of file diff --git a/60_Mastermind/rust/src/main.rs b/60_Mastermind/rust/src/main.rs new file mode 100644 index 00000000..d1fad3c0 --- /dev/null +++ b/60_Mastermind/rust/src/main.rs @@ -0,0 +1,311 @@ +use rand::{Rng, prelude::{thread_rng, ThreadRng}}; +use std::{io, fmt::Display, str::FromStr, iter::OnceWith}; + +//DATA +const COLORS: [&str;8] = ["Black ", "White ","Red ","Green ","Orange ","Yellow ", "Purple ", "Tan "]; //all available colors +const LETTERS: &str = "BWRGOYPT"; //letters representing the above colors + +struct CODE { + code: Vec, //maybe use a char array later, idk + +} +impl CODE { + /** + * generates and returns a random CODE with the given parameters + */ + fn new_random(rng: &mut ThreadRng, num_colors: &usize, num_positions: &usize) -> CODE { + //data + let mut code = CODE{code: Vec::new()}; + //generate random combination of colors + for _i in 0..*num_positions { + code.code.push(rng.gen_range(0..*num_colors)); + } + return code; + } + /** + * returns a code from the parsed string + */ + fn new_from_string(input_string: String, num_colors: usize) -> Option { + let valid_chars = &LETTERS[0..num_colors]; + //DATA + let new_code = CODE{ + code: + input_string.to_ascii_uppercase().chars() //get an iterator with all the chars in input string converted to uppercase + .filter( |c| { valid_chars.contains(*c)}) //remove chars that aren't in LETTERS + .map( |x| -> usize {valid_chars.find(x).expect("invalid character")})//convert all the chars into usizes representing their index in LETTERS + .collect() //wrap this iterator up into a vector + }; + //if code is empty, return None, otherwise return Some(code) + if new_code.code.is_empty() {return None;} + else {return Some(new_code);} + } + + /** + * returns a string containing the code represented as characters + */ + fn _as_human_readible_chars(&self) -> String { + return self.code.iter().map(|i|->char{LETTERS.chars().nth(*i).expect("index out of bounds")}).collect(); + } + /** + * returns a string containing the code represented as words + */ + fn _as_human_readible_words(&self) -> String { + return self.code.iter().map(|i|->&str{COLORS.iter().nth(*i).expect("index out of bounds")}).collect(); + } +} +struct GUESS { + code: CODE, + blacks: usize, + whites: usize, +} +impl GUESS { + /** + * create a new guess, and evaluate it + */ + fn new(code: CODE) -> GUESS { + return GUESS{code:code, blacks:0,whites:0 }; + } + + /** + * evaulates itself for the number of black and white pegs it should have for a given answer + */ + fn evaluate(&mut self, answer:&CODE) { + if self.code.code.len() != answer.code.len() { + panic!("only codes of the same length can be compared"); + } + let tmp_code = self.code.code.clone(); //copy the our code so we can modify it without changing the actual code + let mut tmp_answer: Vec<_> = answer.code.clone().iter().map(|x| Some(*x)).collect(); //copy the our code so we can modify it without changing the actual code, also wrap it for reasons + //same value same position O(N) + let trimmed_code: Vec = tmp_code.iter().enumerate().filter_map(|e| + if Some(e.1) != answer.code.get(e.0) { + Some(*e.1) + } + else { + tmp_answer[e.0] = None; + None + }).collect(); //filters tmp_code, removing all values that are in the same position as in the answer + self.blacks = self.code.code.len() - trimmed_code.len(); + //same value, wrong position + self.whites = trimmed_code.iter().filter(|i| tmp_answer.contains(&Some(**i))).count(); + } +} + +fn main() { + //DATA + let mut rng = thread_rng(); + let num_colors: usize; + let num_positions: usize; + let num_rounds: usize; + let total_posibilities: usize; + + let mut num_moves: usize = 1; + let mut answer: CODE; + let mut guess: GUESS; + let mut guesses: Vec = Vec::new(); + + let mut human_score: usize = 0; + let mut computer_score: usize = 0; + + //print welcome message + welcome(); + + //ask user for a number of colors, positions, and rounds + num_colors = get_number_from_user_input("NUMBER OF COLORS", "", 1, COLORS.len()); + num_positions = get_number_from_user_input("NUMBER OF POSITIONS", "", 1, 10); + num_rounds = get_number_from_user_input("NUMBER OF ROUNDS", "", 1, 10); + + //print number of posibilities + total_posibilities = num_colors.pow(num_positions as u32); + println!("\nTOTAL POSSIBILITIES = {}\n", total_posibilities); + + //print color letter table + print_color_letter_table(num_colors); + + //game loop + for round_num in 1..=num_rounds { + //print round number + println!("\n\nROUND NUMBER: {}", round_num); + + + //human player is code-breaker, computer is code-maker + //generate a combination + answer = CODE::new_random(&mut rng, &num_colors, &num_positions); + println!("CODE: {:?}", answer._as_human_readible_chars()); //this is for troubleshooting, prints the code converted back into characters + + //round loop + loop { + //loop condition + if num_moves > 10 { + println!("YOU RAN OUT OF MOVES! THAT'S ALL YOU GET!"); + println!("THE ACTUAL COMBINATION WAS: {}", answer._as_human_readible_chars()); + human_score += num_moves; + break; + } + + + //input loop + guess = GUESS::new(loop { + println!("\nMOVE # {} GUESS: ", num_moves); + + //get player move + let mut raw_input = String::new(); //temp variable to store user input + io::stdin().read_line(&mut raw_input).expect("CANNOT READ INPUT!"); //read user input from standard input and store it to raw_input + + //attempt to parse input + if raw_input.trim().eq_ignore_ascii_case("board") { + //print the board state + print_board(&guesses); + continue; //run loop again + } + else if raw_input.trim().eq_ignore_ascii_case("quit") { + //quit the game + println!("QUITTER! MY COMBINATION WAS: {}\nGOOD BYE", answer._as_human_readible_words()); + return; //exit the game + } + else { + //parse input for a code + match CODE::new_from_string(raw_input, num_colors) { + Some(code) => { + //ensure code is correct length + if code.code.len() != num_positions { // if not + println!("BAD NUMBER OF POSITIONS."); + continue; //run loop again + } + else {break code;}//break with the code + }, + None => continue, //run loop again + } + } + }); + + //evaluate guess + guess.evaluate(&answer); + let blacks = guess.blacks; + let whites = guess.whites; + //add guess to the list of guesses + guesses.push(guess); + + //tell human the results + if blacks >= num_positions { //guessed it correctly + println!("YOU GUESSED IT IN {} MOVES!", num_moves); + human_score += num_moves; + break; //break from loop + } else { //didn't + println!("YOU HAVE {} BLACKS AND {} WHITES.", blacks, whites); + //increment moves + num_moves += 1; + } + } + + //reset some things in preparation for computer play + let mut possibilities = vec![true; total_posibilities]; + guesses.clear(); + num_moves = 0; + + //computer is code-breaker, human player is code-maker + println!("\nNOW I GUESS. THINK OF A COMBINATION.\nHIT RETURN WHEN READY: "); + //prompt user to give a valid combination #730 + //input loop + answer = loop { + let mut raw_input = String::new(); //temp variable to store user input + io::stdin().read_line(&mut raw_input).expect("CANNOT READ INPUT!"); //read user input from standard input and store it to raw_input + if let Some(code) = CODE::new_from_string(raw_input, LETTERS.len()) {break code;} //attempt to create a code from the user input, if successful break the loop returning the code + println!("INVALID CODE. TRY AGAIN"); //if unsuccessful, this is printed and the loop runs again + }; + //println!("CODE: {:?}", answer._as_human_readible_chars()); //this is for troubleshooting, prints the code converted back into characters + //println!("CODE: {:?}", answer._as_human_readible_words()); //this is for troubleshooting, prints the code converted back into words + + //round loop + for computer_move in 1..=10 { + //randomly generate a guess //770 + + //if it's possible, use it //780 + //if it's not possible: + // search all possibilities after guess, use first valid one //790 + // search all possibilities before guess, use first valid one //820 + // if none where found, tell the user and start over #850 + + //convert guess into something readible #890 + //print it #940 + //ask user for feedback, #980 + + //if we got it, end #990 + //if we didn't, eliminate the combinations that don't work + + } + + } + +} + +/** + * print the welcome message + */ +fn welcome() { + println!(" + MASTERMIND + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + + "); +} + +/** + * print the color - letter table + * only prints the first num_colors pairs + */ +fn print_color_letter_table(num_colors: usize) { + println!("COLOR\tLETTER"); + println!("=====\t======"); + for i in 0..num_colors { + println!("{}\t{}", COLORS[i], &LETTERS[i..i+1]); + } +} + +fn print_board(guesses: &Vec) { + println!("BOARD"); + println!("MOVE\tGUESS\t\tBLACK\tWhite"); + for guess in guesses.iter().enumerate() { + println!("{}\t{}\t\t{}\t{}", guess.0,guess.1.code._as_human_readible_chars(),guess.1.blacks,guess.1.whites); + } + +} + +/** + * gets a number from user input + * pass an empty &str for error_message if you don't want one printed + * pass a min lower than the max to have minimun and maximun bounds + * pass a min higher than the max to only have a minumum bound + * pass a min equal to the max to only have a maximun bound + */ +fn get_number_from_user_input(prompt: &str, error_message: &str, min:T, max:T) -> T { + //DATA + let mut raw_input = String::new(); // temporary variable for user input that can be parsed later + + //input loop + return loop { + + //print prompt + println!("{}", prompt); + //read user input from standard input, and store it to raw_input + raw_input.clear(); //clear input + io::stdin().read_line(&mut raw_input).expect( "CANNOT READ INPUT!"); + + //from input, try to read a number + if let Ok(i) = raw_input.trim().parse() { + //what bounds must the input fall into + if min < max { //have a min and max bound: [min,max] + if i >= min && i <= max {//is input valid, within bounds + break i; //exit the loop with the value i, returning it + } else {println!("ONLY BETWEEN {} AND {}, PLEASE!", min, max);} //print error message specific to this case + } else if min > max { //only a min bound: [min, infinity) + if i >= min {break i;} else {println!("NO LESS THAN {}, PLEASE!", min);} + } else { //only a max bound: (-infinity, max] + if i <= max {break i;} else {println!("NO MORE THAN {}, PLEASE!", max);} + } + continue; //continue to the next loop iteration + }; + //this is only reached if a number couldn't be parsed from the input + if !error_message.is_empty() {println!("{}",error_message);} //if they gave an error message to use, print it + }; +} \ No newline at end of file From 84555d2ac8d2333a35c357782a7c21e544b31ae9 Mon Sep 17 00:00:00 2001 From: AnthonyMichaelTDM <68485672+AnthonyMichaelTDM@users.noreply.github.com> Date: Mon, 7 Mar 2022 00:13:59 -0800 Subject: [PATCH 2/2] finished rust port of 66_mastermind --- 60_Mastermind/rust/src/main.rs | 200 +++++++++++++++++++++++++-------- 1 file changed, 155 insertions(+), 45 deletions(-) diff --git a/60_Mastermind/rust/src/main.rs b/60_Mastermind/rust/src/main.rs index d1fad3c0..61b2bc76 100644 --- a/60_Mastermind/rust/src/main.rs +++ b/60_Mastermind/rust/src/main.rs @@ -1,5 +1,5 @@ use rand::{Rng, prelude::{thread_rng, ThreadRng}}; -use std::{io, fmt::Display, str::FromStr, iter::OnceWith}; +use std::{io, fmt::Display, str::FromStr}; //DATA const COLORS: [&str;8] = ["Black ", "White ","Red ","Green ","Orange ","Yellow ", "Purple ", "Tan "]; //all available colors @@ -10,20 +10,46 @@ struct CODE { } impl CODE { + /** + * create generic, empty code + */ + fn new() -> CODE { + return CODE{code: Vec::new()}; + } /** * generates and returns a random CODE with the given parameters */ - fn new_random(rng: &mut ThreadRng, num_colors: &usize, num_positions: &usize) -> CODE { + fn new_random(rng: &mut ThreadRng, num_colors: usize, num_positions: usize) -> CODE { //data let mut code = CODE{code: Vec::new()}; //generate random combination of colors - for _i in 0..*num_positions { - code.code.push(rng.gen_range(0..*num_colors)); + for _i in 0..num_positions { + code.code.push(rng.gen_range(0..num_colors)); } return code; } /** - * returns a code from the parsed string + * converts input_int from base 10 to base num_colors to generate the code + * input_int must be between 0 and num_colors.pow(num_positions) + */ + fn new_from_int(mut input_int: usize, num_colors: usize, num_positions: usize) -> CODE { + //DATA + let mut converted_number:Vec<_> = Vec::new(); + assert!(2 <= num_colors && num_colors <= 36); //if num_colors is outside of this range, things break later on + + //convert input_int into a code by effectively converting input_int from base 10 to base n where n is num_colors, uses some fancy stuff to do this + loop { + converted_number.push(std::char::from_digit((input_int % num_colors) as u32, num_colors as u32).unwrap()); // + input_int /= num_colors; + if input_int == 0 {break} + } + + while converted_number.len() < num_positions {converted_number.push('0');} // fill remaining space with zero's + let converted_number: Vec<_> = converted_number.iter().rev().map(|e| e.to_digit(num_colors as u32).unwrap() as usize).collect(); //reverse the vector and convert it to integers + return CODE{code: converted_number}; + } + /** + * returns a code parsed from the passed string */ fn new_from_string(input_string: String, num_colors: usize) -> Option { let valid_chars = &LETTERS[0..num_colors]; @@ -70,23 +96,29 @@ impl GUESS { * evaulates itself for the number of black and white pegs it should have for a given answer */ fn evaluate(&mut self, answer:&CODE) { + //data + let mut consumed = vec![false;answer.code.len()]; + if self.code.code.len() != answer.code.len() { panic!("only codes of the same length can be compared"); } - let tmp_code = self.code.code.clone(); //copy the our code so we can modify it without changing the actual code - let mut tmp_answer: Vec<_> = answer.code.clone().iter().map(|x| Some(*x)).collect(); //copy the our code so we can modify it without changing the actual code, also wrap it for reasons - //same value same position O(N) - let trimmed_code: Vec = tmp_code.iter().enumerate().filter_map(|e| - if Some(e.1) != answer.code.get(e.0) { - Some(*e.1) + + for i in 0..answer.code.len() { + if self.code.code[i] == answer.code[i] { //correct value correct place + self.blacks += 1; + consumed[i] = true; } else { - tmp_answer[e.0] = None; - None - }).collect(); //filters tmp_code, removing all values that are in the same position as in the answer - self.blacks = self.code.code.len() - trimmed_code.len(); - //same value, wrong position - self.whites = trimmed_code.iter().filter(|i| tmp_answer.contains(&Some(**i))).count(); + //check for correct value incorrect place, don't count positions that are already exact matches + for j in 0..answer.code.len() { + if !consumed[j] && self.code.code[i] == answer.code[j] && self.code.code[j] != answer.code[j] { + self.whites += 1; + consumed[j] = true; + break; + } + } + } + } } } @@ -96,13 +128,9 @@ fn main() { let num_colors: usize; let num_positions: usize; let num_rounds: usize; + let num_guesses: usize; let total_posibilities: usize; - let mut num_moves: usize = 1; - let mut answer: CODE; - let mut guess: GUESS; - let mut guesses: Vec = Vec::new(); - let mut human_score: usize = 0; let mut computer_score: usize = 0; @@ -111,8 +139,10 @@ fn main() { //ask user for a number of colors, positions, and rounds num_colors = get_number_from_user_input("NUMBER OF COLORS", "", 1, COLORS.len()); - num_positions = get_number_from_user_input("NUMBER OF POSITIONS", "", 1, 10); + num_positions = get_number_from_user_input("NUMBER OF POSITIONS", "", 2, 10); num_rounds = get_number_from_user_input("NUMBER OF ROUNDS", "", 1, 10); + num_guesses = get_number_from_user_input("NUMBER OF GUESSES", "", 10, 0); + //print number of posibilities total_posibilities = num_colors.pow(num_positions as u32); @@ -123,26 +153,33 @@ fn main() { //game loop for round_num in 1..=num_rounds { + //data + let mut num_moves: usize = 1; + let mut answer: CODE; + let mut guess: GUESS; + let mut guesses: Vec = Vec::new(); + let mut all_possibilities = vec![true; total_posibilities]; + + //print round number println!("\n\nROUND NUMBER: {}", round_num); - //human player is code-breaker, computer is code-maker //generate a combination - answer = CODE::new_random(&mut rng, &num_colors, &num_positions); - println!("CODE: {:?}", answer._as_human_readible_chars()); //this is for troubleshooting, prints the code converted back into characters + answer = CODE::new_random(&mut rng, num_colors, num_positions); + //println!("CODE: {:?}", answer._as_human_readible_chars()); //this is for troubleshooting, prints the code converted back into characters //round loop loop { //loop condition - if num_moves > 10 { + if num_moves > num_guesses { println!("YOU RAN OUT OF MOVES! THAT'S ALL YOU GET!"); println!("THE ACTUAL COMBINATION WAS: {}", answer._as_human_readible_chars()); human_score += num_moves; + print_scores(human_score,computer_score); break; } - //input loop guess = GUESS::new(loop { println!("\nMOVE # {} GUESS: ", num_moves); @@ -189,6 +226,7 @@ fn main() { if blacks >= num_positions { //guessed it correctly println!("YOU GUESSED IT IN {} MOVES!", num_moves); human_score += num_moves; + print_scores(human_score,computer_score); break; //break from loop } else { //didn't println!("YOU HAVE {} BLACKS AND {} WHITES.", blacks, whites); @@ -197,11 +235,6 @@ fn main() { } } - //reset some things in preparation for computer play - let mut possibilities = vec![true; total_posibilities]; - guesses.clear(); - num_moves = 0; - //computer is code-breaker, human player is code-maker println!("\nNOW I GUESS. THINK OF A COMBINATION.\nHIT RETURN WHEN READY: "); //prompt user to give a valid combination #730 @@ -209,33 +242,103 @@ fn main() { answer = loop { let mut raw_input = String::new(); //temp variable to store user input io::stdin().read_line(&mut raw_input).expect("CANNOT READ INPUT!"); //read user input from standard input and store it to raw_input - if let Some(code) = CODE::new_from_string(raw_input, LETTERS.len()) {break code;} //attempt to create a code from the user input, if successful break the loop returning the code + + //attempt to create a code from the user input, if successful break the loop returning the code + if let Some(code) = CODE::new_from_string(raw_input, num_colors) { + if code.code.len() == num_positions {break code;} //exit loop with code + else {println!("CODE MUST HAVE {} POSITIONS", num_positions);continue;} //tell them to try again + } + println!("INVALID CODE. TRY AGAIN"); //if unsuccessful, this is printed and the loop runs again }; - //println!("CODE: {:?}", answer._as_human_readible_chars()); //this is for troubleshooting, prints the code converted back into characters - //println!("CODE: {:?}", answer._as_human_readible_words()); //this is for troubleshooting, prints the code converted back into words + + //reset some things in preparation for computer play + guesses.clear(); + num_moves = 0; + //let num_colors = *answer.code.iter().max().unwrap(); //figure out the number of colors from the code | Commented bc we're enforcing that the computer cracks the same size code as the human + //let num_positions = answer.code.len(); //figure out the number of positions from the code | Commented bc we're enforcing that the computer cracks the same size code as the human //round loop - for computer_move in 1..=10 { - //randomly generate a guess //770 + loop { + //loop condition + if num_moves > num_guesses { + println!("I USED UP ALL MY MOVES!"); + println!("I GUESS MY CPU IS JUST HAVING AN OFF DAY."); + computer_score += num_moves; + print_scores(human_score,computer_score); + break; + } + //randomly generate a guess //770 + let mut guess_int = rng.gen_range(0..total_posibilities); + guess = GUESS::new(CODE::new()); //if it's possible, use it //780 - //if it's not possible: - // search all possibilities after guess, use first valid one //790 - // search all possibilities before guess, use first valid one //820 - // if none where found, tell the user and start over #850 - + if all_possibilities[guess_int] { + guess = GUESS::new(CODE::new_from_int(guess_int, num_colors, num_positions)); //create guess + } + else {//if it's not possible: + // search all possibilities after guess, use first valid one //790 + for g in guess_int..total_posibilities { + if all_possibilities[g] { + guess_int=g; + guess = GUESS::new(CODE::new_from_int(guess_int, num_colors, num_positions)); //create guess + break; + } + } + //if none was found + // search all possibilities before guess, use first valid one //820 + if guess.code.code.is_empty() { + for g in (0..guess_int).rev() { + if all_possibilities[g] { + guess_int=g; + guess = GUESS::new(CODE::new_from_int(guess_int, num_colors, num_positions)); //create guess + break; + } + } + } + // if none where found, tell the user and start over #850 + if guess.code.code.is_empty() { + println!("YOU HAVE GIVEN ME INCONSISTENT INFORMATION."); + println!("PLAY AGAIN, AND THIS TIME PLEASE BE MORE CAREFUL."); + return; //exit game + }; + } + //convert guess into something readible #890 //print it #940 + println!("MY GUESS IS: {}", guess.code._as_human_readible_chars()); //ask user for feedback, #980 + let blacks=get_number_from_user_input("BLACKS: ", "", 0, num_positions); + let whites=get_number_from_user_input("WHITES: ", "", 0, num_positions); //if we got it, end #990 - //if we didn't, eliminate the combinations that don't work + if blacks >= num_positions { //guessed it correctly + println!("I GOT IT IN {} MOVES!", num_moves); + computer_score += num_moves; + print_scores(human_score,computer_score); + break; //break from loop + } else { //didn't + all_possibilities[guess_int] = false; + //if we didn't, eliminate the combinations that don't work + //we know the number of black and white pegs for a valid answer, so eleminate all that get different amounts + all_possibilities.iter_mut().enumerate().for_each(|b| { + if *b.1 { //filter out ones we already know aren't possible + let mut tmp_guess = GUESS::new(CODE::new_from_int(b.0, num_colors, num_positions)); + tmp_guess.evaluate(&answer); + if blacks > tmp_guess.blacks || whites > tmp_guess.whites { //if number of blacks/whites is different, set it to false + *b.1 = false; + } + } + }); + + //increment moves + num_moves += 1; + } + } } - } /** @@ -250,6 +353,13 @@ fn welcome() { "); } +/** + * print scores + */ +fn print_scores(human_score:usize, computer_score:usize) { + println!("SCORE\n\tCOMPUTER: {}\n\tHUMAN: {}", computer_score, human_score); +} + /** * print the color - letter table * only prints the first num_colors pairs