diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0227c2 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# learning_rust + +This is my learning journey in the Rust programming Language, and I aim to complete some projects for fun, that will help me cement my knowledge of the core concepts of rust. I've been doing some developing on my own machine, and thought I'd share my coding journey. Hopefully, this code may prove useful for others, or they may just like the programmes I write. Some of it in the beginning, will simply be me going through the 'Rust Programming Book', although I aim to do some more interesting projects as well. + +## Don't forget.. + +When programming in Rust, learn to love the extremely opinionated compiler! +It may seem like it's being a bit of a pain, but it's actually helping you to write better code. It's a bit like having a very strict teacher, who is actually trying to help you learn, even if it doesn't feel like it at the time. + +Most people will start learning coding with Python, a much easier language to start with. Very forgiving, as it hides some of the more difficult concepts behind its declarative nature. Python is a great langauage, and has a huge following in the AI or scientific space, but if you want to get, 'close to the metal', you'll need to learn a language like Rust, C or C++. + +I used to spend hours in a debugger reading machine code, so the concepts of how the hardware works, and why I might choose the fast stack memory, as opposed to the slower heap memory, are not new to me. + +## So, why Rust? + +Low enough to the hardware to be interesting, safe enough that I can, with the help of the very strict compiler, actually make decent progress. It's high enough level in its abstractions to save me from having to manually do everything, and if your code compiles, it should be correct. +(I guess after x86_64 assembly language, anything is going to seem high level!) + +Rust has now reached a decent stage of maturity in many ways, it's even being written in to the Linux kernel, and gaining use in many large Software organisations. The power and speed of C and C++, with the safety of a language like Python. diff --git a/ascii_hangman/Cargo.lock b/ascii_hangman/Cargo.lock new file mode 100644 index 0000000..5ee5383 --- /dev/null +++ b/ascii_hangman/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ascii_hangman" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[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 = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/ascii_hangman/Cargo.toml b/ascii_hangman/Cargo.toml new file mode 100644 index 0000000..28a15e4 --- /dev/null +++ b/ascii_hangman/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ascii_hangman" +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" diff --git a/ascii_hangman/README.md b/ascii_hangman/README.md new file mode 100644 index 0000000..90fe481 --- /dev/null +++ b/ascii_hangman/README.md @@ -0,0 +1,13 @@ +# An Ascii art Hangman game Written in rust. + +This is a simple hangman game that can be played in the terminal. +The source code text (main.rs in the src folder) has been heavily +commented to help anyone who is new to rust understand the code. + +It uses the `rand` crate to generate random words from a list of words +and the std library to handle user input and output. +If you want to know the details of how the game works, you can read the +source code and the extensive comments. + +I have also included an optimised binary file in this root directory +called `Ascii_Hangman` which can be run directly from the terminal. diff --git a/ascii_hangman/ascii_hangman b/ascii_hangman/ascii_hangman new file mode 100644 index 0000000..818206e --- /dev/null +++ b/ascii_hangman/ascii_hangman @@ -0,0 +1,257 @@ +/* Hangman game written in Rust +Using Ascii art for the gallows +Fred Sheehan 2/3/2024 v.1.0 +*/ + +// Importing the necessary libraries +use rand::seq::SliceRandom; +use std::io::{self, Write}; + +// All Rust programmes have to have a main function +fn main() { + /* + Print the welcome message to console the \n is + used to create a new line before the message \t + would be used if we wanted to create a tab spacing + */ + println!("\n Welcome to Hangman!"); + + /* Wordlist used for our hangman game stored in a + vector, this is similar to an array, but more flexible + */ + let words = vec![ + "apple", + "banana", + "orange", + "grape", + "peach", + "strawberry", + "potato", + "cucumber", + "carrot", + "broccoli", + "asparagus", + "spinach", + "lettuce", + "cabbage", + "onion", + "garlic", + "celery", + "mushroom", + "pineapple", + "mango", + "kiwi", + "plum", + "apricot", + "pear", + "lemon", + "lime", + "avocado", + "coconut", + "walnut", + "almond", + "peanut", + ]; + + // Maximum number of incorrect guesses allowed + let max_guesses = 15; + + // Choose a random word using the rand library + // to first create a random number + let mut rng = rand::thread_rng(); + + // And then use that random number to choose the word from the list + // We then unwrap it to get the individual characters in it + let word = words.choose(&mut rng).unwrap(); + + // Take the random word length and create a vector of + // underscores the same length to display to the user + // as they guess the word + let mut guessed_letters = vec!['_'; word.len()]; + + // Initialise a counter for the number of incorrect tries taken + let mut incorrect_guesses = 0; + + // Create a new Vector to store the users guesssed characters + let mut guessed_chars = Vec::new(); + + // create a variable to count how many parts are displayed in our gallows + // this allows us to end the game earlier than max guesses allows + let mut parts_displayed = 0; + + // Create a variable to hold a boolean value to check if the + // game is over and use this to control our game loop + let mut game_over = false; + + // Create our starting empty gallows image stored as a tuple + let mut gallows = [" ____", " |", " |", " |", " |", "_|______"]; + + /* While our boolean value is false, keep the game running using a while + loop. This is the actual game loop + */ + while !game_over { + // Print 'Current word: ' with any correctly guessed letters + println!( + "Current Word: {}", + guessed_letters.iter().collect::() + ); + + // Print a line of the characters that have been guessed already + println!("Guessed Characters: {:?}", guessed_chars); + + // Print the number of incorrect guesses remaining + println!( + "Incorrect Guesses Remaining: {}", + // By subtracting incorrect_guesses from max_guesses + max_guesses - incorrect_guesses + ); + + // Print the empty gallows tuple at the start of the game + for line in &gallows { + println!("{}", line); + } + + /* If the guessed letters vector still contains any underscores + then the game is still ongoing + */ + if guessed_letters.iter().any(|&x| x == '_') { + println!("Keep guessing!"); + } + + // If the number of incorrect_guesses is equal to the max_guesses + if incorrect_guesses == max_guesses { + println!("You ran out of guesses! The word was: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game using the + // game_replay function below + game_replay(); + } + + // If the guessed letters vector contains no underscores + // then the player has guessed the correct word + + if guessed_letters.iter().all(|&x| x != '_') { + println!("Congratulations, you guessed the word: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game + game_replay(); + } + + // Ask the user to input a guessed letter + print!("Enter a letter: "); + + // Flush the print buffer to ensure the message is displayed + io::stdout().flush().unwrap(); + + // Create a new string to store the user input + let mut guess = String::new(); + + // Read the user input with the standard input and store it in the + // guess variable as characters (unwrap it) + io::stdin().read_line(&mut guess).unwrap(); + + // Check we have a valid input from the user using the match function + let guess = match guess.trim().chars().next() { + Some(c) => c, + None => { + println!("Invalid input!"); + continue; + } + }; + + // Check if the guessed letter is already in the guessed_chars vector + if guessed_chars.contains(&guess) { + // If it is, print a message to the user and continue the loop + println!("You already guessed that letter!"); + continue; + } + + // Add the guessed letter to the guessed_chars vector + guessed_chars.push(guess); + + // Then Check if the guessed letter is in the word, if it is, + // update the guessed_letters vector with the correct letter + let mut found_char = false; + for (i, c) in word.chars().enumerate() { + if c == guess { + guessed_letters[i] = c; + found_char = true; + } + } + + // If the guessed letter is not in the word + if !found_char { + // Increment the incorrect_guesses counter + incorrect_guesses += 1; + + // Call the update_gallows function to update the gallows + // based on the number of incorrect guesses + + update_gallows(&mut gallows, &mut parts_displayed, incorrect_guesses); + + // Increment the parts_displayed counter + parts_displayed += 1; + } + + // If the parts_displayed counter reaches 8, the gallows is complete + // and the player has lost the game + + if parts_displayed == 8 { + println!("You were hanged! The word was: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game + game_replay(); + } + } +} + +/* +Function using the match function to update the gallows based on the number +of incorrect guesses, and _parts_displayed of the gallows shown. Note the +underscore prepending the _parts_displayed variable used to indicate to the rust compiler that the parameter is not called directly, else it would throw a warning about the parameter not being used. This is a common convention in Rust. +*/ +fn update_gallows(gallows: &mut [&str; 6], _parts_displayed: &mut usize, incorrect_guesses: usize) { + match incorrect_guesses { + 1 => gallows[1] = " | |", + 2 => gallows[2] = " | O", + 3 => gallows[3] = " | |", + 4 => gallows[3] = " | /|", + 5 => gallows[3] = " | /|\\", + 6 => gallows[4] = " | |", + 7 => gallows[4] = " | / \\", + _ => {} + } +} + +// Function that asks the user if they want to play again +fn game_replay() { + print!("Play again? (y/n): "); + + // Flush the output to the console + io::stdout().flush().unwrap(); + + // Create a mutable string to store the users response + let mut response = String::new(); + + // Using the stdin function from the io module, read the users input + io::stdin().read_line(&mut response).unwrap(); + + // If the user enters "y" (trim removes whitespace from user input) + // then call the main function again to start a new game. + if response.trim() == "y" { + main(); + } else { + // Otherwise print "Goodbye!" and exit the program + println!("Goodbye!"); + std::process::exit(0); + } +} diff --git a/ascii_hangman/src/main.rs b/ascii_hangman/src/main.rs new file mode 100644 index 0000000..818206e --- /dev/null +++ b/ascii_hangman/src/main.rs @@ -0,0 +1,257 @@ +/* Hangman game written in Rust +Using Ascii art for the gallows +Fred Sheehan 2/3/2024 v.1.0 +*/ + +// Importing the necessary libraries +use rand::seq::SliceRandom; +use std::io::{self, Write}; + +// All Rust programmes have to have a main function +fn main() { + /* + Print the welcome message to console the \n is + used to create a new line before the message \t + would be used if we wanted to create a tab spacing + */ + println!("\n Welcome to Hangman!"); + + /* Wordlist used for our hangman game stored in a + vector, this is similar to an array, but more flexible + */ + let words = vec![ + "apple", + "banana", + "orange", + "grape", + "peach", + "strawberry", + "potato", + "cucumber", + "carrot", + "broccoli", + "asparagus", + "spinach", + "lettuce", + "cabbage", + "onion", + "garlic", + "celery", + "mushroom", + "pineapple", + "mango", + "kiwi", + "plum", + "apricot", + "pear", + "lemon", + "lime", + "avocado", + "coconut", + "walnut", + "almond", + "peanut", + ]; + + // Maximum number of incorrect guesses allowed + let max_guesses = 15; + + // Choose a random word using the rand library + // to first create a random number + let mut rng = rand::thread_rng(); + + // And then use that random number to choose the word from the list + // We then unwrap it to get the individual characters in it + let word = words.choose(&mut rng).unwrap(); + + // Take the random word length and create a vector of + // underscores the same length to display to the user + // as they guess the word + let mut guessed_letters = vec!['_'; word.len()]; + + // Initialise a counter for the number of incorrect tries taken + let mut incorrect_guesses = 0; + + // Create a new Vector to store the users guesssed characters + let mut guessed_chars = Vec::new(); + + // create a variable to count how many parts are displayed in our gallows + // this allows us to end the game earlier than max guesses allows + let mut parts_displayed = 0; + + // Create a variable to hold a boolean value to check if the + // game is over and use this to control our game loop + let mut game_over = false; + + // Create our starting empty gallows image stored as a tuple + let mut gallows = [" ____", " |", " |", " |", " |", "_|______"]; + + /* While our boolean value is false, keep the game running using a while + loop. This is the actual game loop + */ + while !game_over { + // Print 'Current word: ' with any correctly guessed letters + println!( + "Current Word: {}", + guessed_letters.iter().collect::() + ); + + // Print a line of the characters that have been guessed already + println!("Guessed Characters: {:?}", guessed_chars); + + // Print the number of incorrect guesses remaining + println!( + "Incorrect Guesses Remaining: {}", + // By subtracting incorrect_guesses from max_guesses + max_guesses - incorrect_guesses + ); + + // Print the empty gallows tuple at the start of the game + for line in &gallows { + println!("{}", line); + } + + /* If the guessed letters vector still contains any underscores + then the game is still ongoing + */ + if guessed_letters.iter().any(|&x| x == '_') { + println!("Keep guessing!"); + } + + // If the number of incorrect_guesses is equal to the max_guesses + if incorrect_guesses == max_guesses { + println!("You ran out of guesses! The word was: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game using the + // game_replay function below + game_replay(); + } + + // If the guessed letters vector contains no underscores + // then the player has guessed the correct word + + if guessed_letters.iter().all(|&x| x != '_') { + println!("Congratulations, you guessed the word: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game + game_replay(); + } + + // Ask the user to input a guessed letter + print!("Enter a letter: "); + + // Flush the print buffer to ensure the message is displayed + io::stdout().flush().unwrap(); + + // Create a new string to store the user input + let mut guess = String::new(); + + // Read the user input with the standard input and store it in the + // guess variable as characters (unwrap it) + io::stdin().read_line(&mut guess).unwrap(); + + // Check we have a valid input from the user using the match function + let guess = match guess.trim().chars().next() { + Some(c) => c, + None => { + println!("Invalid input!"); + continue; + } + }; + + // Check if the guessed letter is already in the guessed_chars vector + if guessed_chars.contains(&guess) { + // If it is, print a message to the user and continue the loop + println!("You already guessed that letter!"); + continue; + } + + // Add the guessed letter to the guessed_chars vector + guessed_chars.push(guess); + + // Then Check if the guessed letter is in the word, if it is, + // update the guessed_letters vector with the correct letter + let mut found_char = false; + for (i, c) in word.chars().enumerate() { + if c == guess { + guessed_letters[i] = c; + found_char = true; + } + } + + // If the guessed letter is not in the word + if !found_char { + // Increment the incorrect_guesses counter + incorrect_guesses += 1; + + // Call the update_gallows function to update the gallows + // based on the number of incorrect guesses + + update_gallows(&mut gallows, &mut parts_displayed, incorrect_guesses); + + // Increment the parts_displayed counter + parts_displayed += 1; + } + + // If the parts_displayed counter reaches 8, the gallows is complete + // and the player has lost the game + + if parts_displayed == 8 { + println!("You were hanged! The word was: {}", word); + + // Set the game over boolean to true + game_over = true; + + // And ask if the player wants another game + game_replay(); + } + } +} + +/* +Function using the match function to update the gallows based on the number +of incorrect guesses, and _parts_displayed of the gallows shown. Note the +underscore prepending the _parts_displayed variable used to indicate to the rust compiler that the parameter is not called directly, else it would throw a warning about the parameter not being used. This is a common convention in Rust. +*/ +fn update_gallows(gallows: &mut [&str; 6], _parts_displayed: &mut usize, incorrect_guesses: usize) { + match incorrect_guesses { + 1 => gallows[1] = " | |", + 2 => gallows[2] = " | O", + 3 => gallows[3] = " | |", + 4 => gallows[3] = " | /|", + 5 => gallows[3] = " | /|\\", + 6 => gallows[4] = " | |", + 7 => gallows[4] = " | / \\", + _ => {} + } +} + +// Function that asks the user if they want to play again +fn game_replay() { + print!("Play again? (y/n): "); + + // Flush the output to the console + io::stdout().flush().unwrap(); + + // Create a mutable string to store the users response + let mut response = String::new(); + + // Using the stdin function from the io module, read the users input + io::stdin().read_line(&mut response).unwrap(); + + // If the user enters "y" (trim removes whitespace from user input) + // then call the main function again to start a new game. + if response.trim() == "y" { + main(); + } else { + // Otherwise print "Goodbye!" and exit the program + println!("Goodbye!"); + std::process::exit(0); + } +}