use rand::{Rng, prelude::{thread_rng, ThreadRng}}; use std::{io, fmt::Display, str::FromStr}; //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 { /** * 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 { //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; } /** * 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]; //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) { //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"); } 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 { //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; } } } } } } fn main() { //DATA let mut rng = thread_rng(); let num_colors: usize; let num_positions: usize; let num_rounds: usize; let num_guesses: usize; let total_posibilities: usize; 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", "", 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); 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 { //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 //round loop loop { //loop condition 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); //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; print_scores(human_score,computer_score); break; //break from loop } else { //didn't println!("YOU HAVE {} BLACKS AND {} WHITES.", blacks, whites); //increment moves num_moves += 1; } } //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 //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 }; //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 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 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 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(&guess.code); //compare with computer guess 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; } } } } /** * print the welcome message */ fn welcome() { println!(" MASTERMIND CREATIVE COMPUTING MORRISTOWN, NEW JERSEY "); } /** * 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 */ 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 }; }