-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 51c0d90
Showing
7 changed files
with
421 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "mastermind-rs" | ||
version = "1.0.0" | ||
authors = ["Sergey Lavrent"] | ||
edition = "2021" | ||
description = "A TUI game based on Mastermind/Bulls & Cows" | ||
repository = "https://github.com/hiimsergey/mastermind-rs" | ||
license = "GPL-3.0" | ||
keywords = ["tui", "game", "boardgame", "board-game"] | ||
categories = ["command-line-utilities", "games"] | ||
|
||
[dependencies] | ||
fastrand = "2.0.2" | ||
|
||
[dependencies.cursive] | ||
version = "0.20.0" | ||
default-features = false | ||
# this one is best compatible with Unix and Windows, in my experience | ||
features = ["crossterm-backend"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# mastermind-rs | ||
A TUI game of "Mastermind/Master Mind" or "Bulls & Cows" written in Rust. | ||
|
||
Utilizes the [cursive](https://github.com/gyscos/cursive) crate for building the User Interface. | ||
|
||
![screenshot](https://github.com/hiimsergey/mastermind-rs/assets/91432388/3e8f193b-f958-400f-a9a8-3c0e9466b1b7) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
use cursive::Cursive; | ||
use cursive::views::{EditView, ListView, ScrollView, TextView}; | ||
use fastrand; | ||
use crate::{util::CodeError, windows}; | ||
|
||
/// Generates the code the player should guess as a `String` | ||
pub fn get_code(code_length: u8, digit_number: u8) -> String { | ||
let mut result = String::new(); | ||
for _ in 0..code_length { | ||
// Pushes a random digit within the specified bounds | ||
result = format!("{result}{}", fastrand::u8(1..=digit_number)); | ||
} | ||
result | ||
} | ||
|
||
/// Function that runs right after the player submits their guess. | ||
/// Depending on the validity of the guess, warns about the error or pushes the | ||
/// guess to the list. | ||
pub fn submit_guess(s: &mut Cursive, guess: &str, code: &str, digit_number: u8) { | ||
if let Err(err) = get_code_validitiy(guess, code, digit_number) { | ||
windows::guess_error(s, err); | ||
// Empties the input bar | ||
s.call_on_name("input", |v: &mut EditView| { v.set_content(""); }); | ||
} else { | ||
s.call_on_name("list", |v: &mut ScrollView<ListView>| { | ||
// Calculates and formats the number of the guess in the list | ||
let guess_number = format!("{}.", v.get_inner().len() + 1); | ||
v.get_inner_mut().add_child("", TextView::new( | ||
format!( | ||
"{:<5}{guess}{:>len_fmt$}", | ||
guess_number, | ||
compare_guess(guess, code), | ||
len_fmt = guess.len() + 4 | ||
) | ||
)); | ||
}); | ||
|
||
if guess == code { | ||
s.call_on_name("input", |v: &mut EditView| v.disable()); | ||
windows::win(s, code); | ||
} | ||
} | ||
} | ||
|
||
/// Checks whether the guess is valid in terms of length and characters. | ||
/// Returns `Ok(())` if valid and the respective `CodeError` member otherwise. | ||
fn get_code_validitiy(guess: &str, code: &str, digit_number: u8) -> Result<(), CodeError> { | ||
if guess.len() < code.len() { return Err(CodeError::Short); } | ||
if guess.parse::<u32>().is_err() { return Err(CodeError::NaN); } | ||
|
||
for c in guess.chars() { | ||
if !(1..=digit_number).contains(&(c.to_digit(10).unwrap() as u8)) { | ||
return Err(CodeError::DigitLimit); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Generates the feedback consisting of `!`, `?` and `.` as a `String` | ||
fn compare_guess(guess: &str, code: &str) -> String { | ||
// Shadow with a vector because it's more suited for further computation | ||
let mut guess: Vec<char> = guess.chars().collect(); | ||
let mut result = String::new(); | ||
|
||
for i in 0..code.len() { | ||
if code.chars().nth(i).unwrap() == guess[i] { | ||
guess[i] = ' '; | ||
// Prepends `!` to `result` | ||
result = format!("!{result}"); | ||
continue; | ||
} | ||
// Checks if there's a matching character on another index, assuming it | ||
// doesn't belong to a "bull-pair" (`!`) | ||
for k in 0..code.len() { | ||
if code.chars().nth(i).unwrap() == guess[k] | ||
&& code.chars().nth(k).unwrap() != guess[k] { | ||
guess[k] = ' '; | ||
result += "?"; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// Repeatedly fills `result` with `.` until it's as long as the code | ||
result += &".".repeat(code.len() - result.len()); | ||
result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mod logic; mod util; mod windows; | ||
|
||
fn main() { | ||
let mut siv = cursive::default(); | ||
windows::menu(&mut siv); | ||
siv.run(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use cursive::theme::{BaseColor, Color}; | ||
use cursive::view::Nameable; | ||
use cursive::views::{DummyView, LinearLayout, SliderView, TextView}; | ||
|
||
/// Signals the reason why the guess provided by the player is invalid | ||
pub enum CodeError { | ||
/// The guess contains digits outside of the specified `digit_number` setting | ||
DigitLimit, | ||
/// The guess contains characters other than letters | ||
NaN, | ||
/// The guess is shorter than the specified `code_length` setting | ||
Short | ||
} | ||
|
||
pub fn text_banner() -> TextView { | ||
TextView::new( | ||
" _ _ _ | ||
_ __ ___ __ _ ___| |_ ___ _ __ _ __ ___ (_)_ __ __| | | ||
| '_ ` _ \\ / _` / __| __/ _ \\ '__| '_ ` _ \\| | '_ \\ / _` | | ||
| | | | | | (_| \\__ \\ || __/ | | | | | | | | | | | (_| | | ||
|_| |_| |_|\\__,_|___/\\__\\___|_| |_| |_| |_|_|_| |_|\\__,_| | ||
" | ||
) | ||
} | ||
|
||
pub fn text_about() -> TextView { | ||
TextView::new( | ||
"A little game written for the sake of experience in writing | ||
Rust code. Also my first project using a User Interface | ||
library. Rewritten from scratch at v1.0.0. | ||
<https://github.com/hiimsergey/mastermind-rs> | ||
Utilizes the \"cursive\" crate for building TUIs. | ||
<https://crates.io/crates/cursive> | ||
v1.0.0 GPL-3.0 Licence" | ||
).style(Color::Dark(BaseColor::Blue)) | ||
} | ||
|
||
pub fn text_rules_1() -> TextView { | ||
TextView::new( | ||
" | ||
Use the arrow keys or the mouse to navigate. | ||
Press q to close windows and Esc to quit the game. | ||
" | ||
) | ||
} | ||
|
||
pub fn text_rules_2() -> TextView { | ||
TextView::new( | ||
"A random code is generated based on your settings: | ||
" | ||
).style(Color::Dark(BaseColor::Blue)) | ||
} | ||
|
||
pub fn text_rules_3() -> TextView { | ||
TextView::new( | ||
"1. \"Digit number\" sets the amount of different | ||
characters to feature. | ||
2. \"Code length\" sets the length of the generated | ||
code. | ||
" | ||
).style(Color::Dark(BaseColor::Magenta)) | ||
} | ||
|
||
pub fn text_rules_4() -> TextView { | ||
TextView::new( | ||
"You try to guess it by filling in the input box. | ||
The game gives you feedback: | ||
" | ||
).style(Color::Dark(BaseColor::Blue)) | ||
} | ||
|
||
pub fn text_rules_5() -> TextView { | ||
TextView::new( | ||
"1. An exclamation mark means that one character in | ||
your guess is right. | ||
2. A question mark means that one character is | ||
featured in the code but on another position. | ||
3. A dot means that a character isn't featured at | ||
all." | ||
).style(Color::Dark(BaseColor::Magenta)) | ||
} | ||
|
||
pub fn text_settings_ingame(digit_number: u8, code_length: u8) -> TextView { | ||
TextView::new( | ||
format!( | ||
"Digit number: {digit_number} | ||
Code length: {code_length}" | ||
) | ||
).style(Color::Dark(BaseColor::Blue)) | ||
} | ||
|
||
pub fn text_quit() -> TextView { | ||
TextView::new("Do you want to\nquit the game?") | ||
.style(Color::Dark(BaseColor::Red)) | ||
} | ||
|
||
/// Returns a slider belonging to the specified `setting` with the text on the side | ||
/// with the specified `description` | ||
pub fn slider_setting(setting: &'static str, description: &str) -> LinearLayout { | ||
let slider = SliderView::horizontal(8).value(2).on_change(|s, n| { | ||
s.call_on_name(setting, |v: &mut TextView| { | ||
v.set_content(format!("{}", n + 2)); | ||
}); | ||
}); | ||
|
||
LinearLayout::horizontal() | ||
.child(TextView::new(description)) | ||
.child(slider).child(DummyView) | ||
.child(TextView::new("4").with_name(setting)) | ||
} |
Oops, something went wrong.