Skip to content

Commit

Permalink
implement a quick non-tui version of the game
Browse files Browse the repository at this point in the history
  • Loading branch information
scottnm committed Jan 23, 2021
1 parent 7e08e66 commit 93181fa
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 29 deletions.
5 changes: 2 additions & 3 deletions src/dict.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::randwrapper::RangeRng;
use crate::randwrapper::{select_rand, RangeRng};

// Each dict chunk represents all words of the same length from our src dict. This partitioning is a
// quick optimization since the cracker game will only concern itself with words of the same length.
Expand All @@ -20,7 +20,6 @@ impl EnglishDictChunk {
}

pub fn get_random_word(&self, rng: &mut dyn RangeRng<usize>) -> String {
let word_index = rng.gen_range(0, self.word_set.len());
self.word_set[word_index].clone()
select_rand(&self.word_set, rng).clone()
}
}
92 changes: 73 additions & 19 deletions src/game.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Work breakdown
// - constrain the number of words generated to only as much as would fit in two panes
// - select a word to be the solution word
// - run a window-less gameloop which lets us input words and get back the results of "N matching chars to solution"
// - setup a better word selection algorithm which results in more common letters
// - setup the win-lose condition that you only have 4 guesses
// - render two panes
// - place the words throughout the pane w/ filler text that goes in between words
Expand All @@ -16,7 +15,7 @@
// - SFX

use crate::dict;
use crate::randwrapper::{RangeRng, ThreadRangeRng};
use crate::randwrapper::{select_rand, RangeRng, ThreadRangeRng};
use std::str::FromStr;

const TITLE: &str = "FONV: Terminal Cracker";
Expand Down Expand Up @@ -76,28 +75,83 @@ pub fn generate_words(difficulty: Difficulty, rng: &mut dyn RangeRng<usize>) ->
}

pub fn run_game(difficulty: Difficulty) {
// setup the window
let window = pancurses::initscr();
pancurses::noecho(); // prevent key inputs rendering to the screen
pancurses::cbreak();
pancurses::curs_set(0);
pancurses::set_title(TITLE);
window.nodelay(true); // don't block waiting for key inputs (we'll poll)
window.keypad(true); // let special keys be captured by the program (i.e. esc/backspace/del/arrow keys)

// Generate a random set of words based on the difficulty
let mut rng = ThreadRangeRng::new();
let rand_words = generate_words(difficulty, &mut rng);

// TODO just open a stub window for now. We'll write the game soon.
window.clear();
// For the sake of keeping the windowing around let's dump those words in a window
{
// setup the window
let window = pancurses::initscr();
pancurses::noecho(); // prevent key inputs rendering to the screen
pancurses::cbreak();
pancurses::curs_set(0);
pancurses::set_title(TITLE);
window.nodelay(true); // don't block waiting for key inputs (we'll poll)
window.keypad(true); // let special keys be captured by the program (i.e. esc/backspace/del/arrow keys)

// TODO just open a stub window for now. We'll write the game soon.
window.clear();

window.mvaddstr(0, 0, format!("{:?}", difficulty));
for (i, rand_word) in rand_words.iter().enumerate() {
window.mvaddstr(i as i32 + 1, 0, rand_word);
}

window.mvaddstr(0, 0, format!("{:?}", difficulty));
for (i, rand_word) in rand_words.iter().enumerate() {
window.mvaddstr(i as i32 + 1, 0, rand_word);
window.refresh();
std::thread::sleep(std::time::Duration::from_millis(3000));
pancurses::endwin();
}

window.refresh();
std::thread::sleep(std::time::Duration::from_millis(3000));
// now let's run a mock game_loop
run_game_from_line_console(&rand_words, &mut rng);
}

fn run_game_from_line_console(words: &[String], rng: &mut dyn RangeRng<usize>) {
// Select an answer
let solution = select_rand(words, rng);

println!("Solution: {}", solution);
for word in words {
let matching_char_count = crate::utils::matching_char_count_ignore_case(&solution, word);
println!(" {} ({}/{})", word, matching_char_count, solution.len());
}

// On each game loop iteration...
let mut remaining_guess_count = 4;
while remaining_guess_count > 0 {
// Let the user provide a guess
println!("\nGuess? ");
let next_guess: String = text_io::read!("{}");

// Check for a win
if &next_guess == solution {
break;
}

// Validate the non-winning word in the guess list
// TODO: won't be necessary when they can only select from a preset set of words
if !words.iter().any(|w| w.eq_ignore_ascii_case(&next_guess)) {
println!("Not a word in the list!");
continue;
}

// Print the matching character count as a hint for the next guess
let matching_char_count =
crate::utils::matching_char_count_ignore_case(&solution, &next_guess);

println!("{} / {} chars match!", matching_char_count, solution.len());

// let the user know how many attempts they have left
remaining_guess_count -= 1;
println!("{} attempts left", remaining_guess_count);
}

if remaining_guess_count > 0 {
println!("Correct!");
} else {
println!("Failed!");
}
}

#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod dict;
mod game;
mod randwrapper;
mod solver;
mod utils;

#[derive(Debug)]
enum Mode {
Expand Down Expand Up @@ -51,7 +52,7 @@ fn parse_cmdline_args() -> Result<CmdlineArgs, &'static str> {
fn print_usage_and_exit(err_msg: &str) -> ! {
println!("USAGE:");
println!(" fonv_cracker.exe --solver input_file [guess matching_char_count]+");
println!(" fonv_cracker.exe --game");
println!(" fonv_cracker.exe --game difficulty");
println!("Input err: {}", err_msg);
std::process::exit(1);
}
Expand Down
5 changes: 5 additions & 0 deletions src/randwrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ pub trait RangeRng<T: PartialOrd> {
fn gen_range(&mut self, lower: T, upper: T) -> T;
}

pub fn select_rand<'a, T>(seq: &'a [T], rng: &mut dyn RangeRng<usize>) -> &'a T {
let index = rng.gen_range(0, seq.len());
&seq[index]
}

pub struct ThreadRangeRng {
rng: rand::rngs::ThreadRng,
}
Expand Down
8 changes: 2 additions & 6 deletions src/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,8 @@ where
S: AsRef<str>,
{
for i in (0..passwords.len()).rev() {
let matching_count = passwords[i]
.as_ref()
.chars()
.zip(guess.word.chars())
.filter(|(a, b)| a == b)
.count();
let matching_count =
crate::utils::matching_char_count_ignore_case(passwords[i].as_ref(), &guess.word);
if matching_count != guess.char_count {
passwords.swap_remove(i);
}
Expand Down
10 changes: 10 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub fn matching_char_count_ignore_case(a: &str, b: &str) -> usize {
fn chars_eq_ignore_case((a, b): &(char, char)) -> bool {
a.to_ascii_lowercase() == b.to_ascii_lowercase()
}

a.chars()
.zip(b.chars())
.filter(chars_eq_ignore_case)
.count()
}

0 comments on commit 93181fa

Please sign in to comment.