initial commit

This commit is contained in:
RingOfStorms (Joshua Bell) 2022-01-27 13:39:02 -06:00
commit 592dd1473d
11 changed files with 818817 additions and 0 deletions

194
src/main.rs Normal file
View file

@ -0,0 +1,194 @@
use rand::seq::SliceRandom;
use regex::Regex;
use std::collections::HashSet;
use std::fs::{read_dir, File, OpenOptions};
use std::io::{stdin, BufRead, BufReader, Write};
mod utils;
mod wordle;
use utils::str_unique_by_characters;
use wordle::{worlde_game_make_guess, WordleState};
fn generate_wordle_dictionary() -> HashSet<String> {
let existing_path = "./dictionaries/output/five_letter_words.txt";
let existing = File::open(&existing_path);
let paths: Vec<_> = read_dir("./dictionaries")
.unwrap()
.map(|p| p.unwrap())
.filter(|p| p.file_type().unwrap().is_file())
.collect();
// Only regenerate five letter word dictionary if there are new or changed dictionaries
let regenerate = existing.map_or(true, |e| {
let modified = e.metadata().unwrap().modified().unwrap();
paths
.iter()
.any(|dict| dict.metadata().unwrap().modified().unwrap().gt(&modified))
});
println!("Regenerating five letter word dictionary: {}", regenerate);
let mut five_letter_words = HashSet::new();
if regenerate {
let _ = File::create(&existing_path); // create if not exist
let mut f_writer = OpenOptions::new()
.write(true)
.truncate(true)
.open(&existing_path)
.unwrap();
let five_re = Regex::new(r"^(\w{5})(?:\s|$)").unwrap();
paths.iter().for_each(|p| {
println!("Loading five letter words from: {}", p.path().display());
let f = File::open(p.path()).unwrap();
let f = BufReader::new(f);
f.lines().map(|l| l.unwrap()).for_each(|line| {
match five_re.captures(&line) {
Some(x) => {
let word = x.get(1).unwrap().as_str().to_uppercase();
// println!("Capture: {} from {}", word, line);
five_letter_words.insert(word.clone());
f_writer.write(format!("{}\n", &word).as_ref()).unwrap();
}
None => (),
}
})
});
f_writer.flush().unwrap();
} else {
println!(
"Loading five letter word dictionary from: {}",
&existing_path
);
let dictionary_f = File::open(&existing_path).unwrap();
let dictionary_f = BufReader::new(dictionary_f);
dictionary_f.lines().for_each(|l| {
five_letter_words.insert(l.unwrap());
});
}
five_letter_words
}
fn get_input(prompt: &str) -> String {
let mut input = String::new();
println!("{prompt}");
stdin()
.read_line(&mut input)
.expect("Failed to read input.");
input.trim().to_string()
}
fn main() {
let wordle_dict = generate_wordle_dictionary();
// println!("Wordle dict size: {}", wordle_dict.len());
let unique_words: Vec<_> = wordle_dict
.iter()
.filter(|x| str_unique_by_characters(x))
.map(|w| w)
.collect();
let worlde_answer = unique_words.choose(&mut rand::thread_rng()).unwrap();
let mut wordle_game_state = WordleState::new(worlde_answer.as_str());
loop {
let mut input;
loop {
input = get_input("Guess a word:").to_uppercase();
if input.len() == 5 && wordle_dict.contains(&input) {
break;
}
if input.eq("!ANS") || input.eq("!ANSWER") {
println!("Answer: {worlde_answer}");
} else {
println!("Invalid input {input}, try a different one...");
}
}
worlde_game_make_guess(input.as_ref(), &mut wordle_game_state);
print!("Board:\n{}", wordle_game_state);
if wordle_game_state.game_over() {
break;
}
}
match wordle_game_state.won() {
true => println!("YOU WON!"),
false => println!("YOU LOST!"),
}
}
/*
use std::fs::File;
use std::io::{BufWriter, Write};
fn main() {
let data = "Some data!";
let f = File::create("/tmp/foo").expect("Unable to create file");
let mut f = BufWriter::new(f);
f.write_all(data.as_bytes()).expect("Unable to write data");
}
*/
/*
use lazy_static::lazy_static;
use regex::Regex;
fn extract_login(input: &str) -> Option<&str> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?x)
^(?P<login>[^@\s]+)@
([[:word:]]+\.)*
[[:word:]]+$
").unwrap();
}
RE.captures(input).and_then(|cap| {
cap.name("login").map(|login| login.as_str())
})
}
fn main() {
assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
assert_eq!(
extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
Some(r"sdf+sdsfsd.as.sdsd")
);
assert_eq!(extract_login(r"More@Than@One@at.com"), None);
assert_eq!(extract_login(r"Not an email@email"), None);
}
*/
/*
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let f = File::open("/etc/hosts").expect("Unable to open file");
let f = BufReader::new(f);
for line in f.lines() {
let line = line.expect("Unable to read line");
println!("Line: {}", line);
}
}
*/
/*
use std::fs;
fn main() {
let paths = fs::read_dir("./").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display())
}
}
*/

22
src/utils.rs Normal file
View file

@ -0,0 +1,22 @@
pub fn str_unique_by_characters(s: &str) -> bool {
s.chars()
.enumerate()
.find_map(|(i, c)| {
s.chars()
.enumerate()
.skip(i + 1)
.find(|(_, other)| c == *other)
.map(|(j, _)| (i, j, c))
})
.is_none()
}
pub fn str_to_five_char(s: &str) -> [char; 5] {
s.chars()
.take(5)
.enumerate()
.fold([' '; 5], |mut arr, (i, c)| {
arr[i] = c;
arr
})
}

165
src/wordle.rs Normal file
View file

@ -0,0 +1,165 @@
use ansi_term::Color::{Green, Yellow, RGB};
use ansi_term::Colour;
use std::fmt;
use std::fmt::Formatter;
use crate::utils::str_to_five_char;
const GRAY: Colour = RGB(188, 188, 188);
const DARK_GRAY: Colour = RGB(117, 117, 117);
#[derive(Copy, PartialEq, Debug)]
pub enum CharState {
EXCLUDES,
CONTAINS,
POSITIONED,
}
impl Clone for CharState {
fn clone(&self) -> Self {
match self {
CharState::EXCLUDES => CharState::EXCLUDES,
CharState::CONTAINS => CharState::CONTAINS,
CharState::POSITIONED => CharState::POSITIONED,
}
}
}
pub struct WordleState {
word: [char; 5],
guesses: [Option<[char; 5]>; 5],
guess_states: [Option<[CharState; 5]>; 5],
}
impl WordleState {
pub fn new(word: &str) -> Self {
WordleState {
word: str_to_five_char(word),
guesses: [None; 5],
guess_states: [None; 5],
}
}
pub fn get_turn(&self) -> Option<usize> {
self.guesses.iter().position(|guess| guess.is_none())
}
pub fn game_over(&self) -> bool {
self.get_turn().is_none() || self.won()
}
pub fn won(&self) -> bool {
let last_index = self.get_turn().map(|t| t - 1).unwrap_or(4);
!self.guess_states[last_index]
.unwrap()
.iter()
.any(|s| !CharState::POSITIONED.eq(s))
}
pub fn set_guess(&mut self, index: usize, guess: [char; 5]) {
self.guesses[index] = Option::from(guess);
self.calculate_guess_states(false);
}
// pub fn calculate_missing_guess_states(&mut self) {
// self.calculate_guess_states(true)
// }
pub fn calculate_guess_states(&mut self, skip_existing: bool) {
self.guesses.iter().enumerate().for_each(|(i, g)| {
if g.is_some() && (!skip_existing || self.guess_states[i].is_none()) {
// EXCLUDES is default so anything at the end is considered excluded
let mut guess_state = [CharState::EXCLUDES; 5];
let mut guess = g.unwrap().map(|c| Option::from(c));
let mut word = self.word.map(|c| Option::from(c));
// POSITIONED
for char_index in 0..5 {
if guess[char_index].unwrap() == word[char_index].unwrap() {
guess[char_index] = None;
word[char_index] = None;
guess_state[char_index] = CharState::POSITIONED;
}
}
// CONTAINS
for char_index in 0..5 {
if guess[char_index].is_some() {
for word_index in 0..5 {
if word[word_index].is_some()
&& guess[char_index].unwrap() == word[word_index].unwrap()
{
word[word_index] = None;
guess[char_index] = None;
guess_state[char_index] = CharState::CONTAINS;
break;
}
}
}
}
self.guess_states[i] = Option::from(guess_state);
}
});
}
}
impl fmt::Display for WordleState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.guesses.iter().enumerate().for_each(|(i, g)| {
f.write_str(
match g {
None => format!("{}\n", GRAY.paint("_ _ _ _ _")),
Some(guess) => match self.guess_states[i] {
None => format!(
"{}\n",
GRAY.paint(
guess
.iter()
.map(|c| format!("{} ", c.to_string()))
.collect::<String>()
)
),
Some(_) => format!(
"{}\n",
guess
.iter()
.enumerate()
.fold(String::new(), |mut str, (i_c, c)| {
str.push_str(
format!(
"{} ",
match self.guess_states[i].unwrap()[i_c] {
CharState::EXCLUDES =>
DARK_GRAY.paint(c.to_string()),
CharState::CONTAINS => Yellow.paint(c.to_string()),
CharState::POSITIONED => Green.paint(c.to_string()),
}
)
.as_str(),
);
str
})
),
},
}
.as_str(),
)
.unwrap()
});
Ok(())
}
}
pub fn worlde_game_make_guess(guess: &str, state: &mut WordleState) {
let turn = state.guesses.iter().position(|guess| guess.is_none());
match turn {
Some(t) => println!("Current turn index: {}", t),
None => println!("Game is over"),
}
if turn.is_some() {
let turn = turn.unwrap();
state.set_guess(turn, str_to_five_char(guess));
}
}