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

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
*.iml

150
Cargo.lock generated Normal file
View file

@ -0,0 +1,150 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[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.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[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.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "wordle"
version = "0.1.0"
dependencies = [
"ansi_term",
"rand",
"regex",
]

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "wordle"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ansi_term = "0.12.1"
rand = "0.8.4"
regex = "1.5.4"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

12974
dictionaries/worlde.txt Normal file

File diff suppressed because it is too large Load diff

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));
}
}