use clap::{value_parser, Parser, ValueEnum};
use colored::Colorize;
use rand::seq::SliceRandom;
use std::{collections::HashMap, error::Error, io::Write};
#[macro_export]
macro_rules! newln {
($repeat:expr) => {
for _ in 0..$repeat {
println!();
}
};
() => {
println!();
};
}
#[macro_export]
macro_rules! sleep {
($millis:expr) => {
std::thread::sleep(std::time::Duration::from_millis($millis));
};
}
#[macro_export]
macro_rules! flush {
() => {
std::io::stdout().flush().expect("Failed to flush stdout");
};
}
#[macro_export]
macro_rules! clear {
() => {
print!("{}[2J", 27 as char);
};
}
#[macro_export]
macro_rules! warning {
($msg:literal) => {
newln!();
println!("{}{}", "WARNING: ".bold().red(), $msg.red());
newln!();
sleep!(4000);
};
}
pub const GAME_ONGOING: i32 = 31;
pub const GAME_RESTART: i32 = 32;
pub const GAME_OVER: i32 = 33;
const DEFAULT_LIST: ListType = ListType::Gpt;
pub const WORDS_4E: &str = include_str!("../word_lists/4E.txt");
pub const WORDS_4M: &str = include_str!("../word_lists/4M.txt");
pub const WORDS_4H: &str = include_str!("../word_lists/4H.txt");
pub const WORDS_5E: &str = include_str!("../word_lists/5E.txt");
pub const WORDS_5M: &str = include_str!("../word_lists/5M.txt");
pub const WORDS_5H: &str = include_str!("../word_lists/5H.txt");
pub const WORDS_6E: &str = include_str!("../word_lists/6E.txt");
pub const WORDS_6M: &str = include_str!("../word_lists/6M.txt");
pub const WORDS_6H: &str = include_str!("../word_lists/6H.txt");
pub const WORDS_7E: &str = include_str!("../word_lists/7E.txt");
pub const WORDS_7M: &str = include_str!("../word_lists/7M.txt");
pub const WORDS_7H: &str = include_str!("../word_lists/7H.txt");
pub const WORDS_8E: &str = include_str!("../word_lists/8E.txt");
pub const WORDS_8M: &str = include_str!("../word_lists/8M.txt");
pub const WORDS_8H: &str = include_str!("../word_lists/8H.txt");
pub const WORDS_MASTER: &str = include_str!("../word_lists/Master.txt");
pub trait Game {
fn new(args: Args) -> Self;
fn start(&mut self);
fn do_loop(&mut self) -> Result<i32, Box<dyn Error>>;
fn finish(self);
}
#[allow(dead_code)]
enum ListType {
Gpt,
Webster
}
#[derive(ValueEnum, Clone, Debug)]
pub enum Mints {
Wordle,
Hangman,
Anagrams,
Minesweeper
}
#[derive(ValueEnum, Clone, Debug)]
pub enum Difficulty {
Easy,
Medium,
Hard,
}
#[derive(Parser, Debug, Clone)]
pub struct Args {
#[arg(help = "The game to play.")]
pub game: Mints,
#[arg(short = 'g', long = "guesses")]
pub guesses: Option<i32>,
#[arg(short = 'l', long = "letters")]
pub letters: Option<i32>,
#[arg(short = 't', long = "timer", value_parser = value_parser!(i32).range(1..=300))]
pub timer: Option<i32>,
#[arg(short = 'd', long = "difficulty")]
#[clap(value_enum)]
pub difficulty: Option<Difficulty>,
}
pub fn choose_random_word(words: &[String]) -> String {
let mut rng = rand::thread_rng();
words
.choose(&mut rng)
.cloned()
.expect("Failed to pick random word")
}
pub fn load_word_list(letters: i32, diff: &Difficulty) -> Vec<String> {
let txt = match letters {
4 => match diff {
Difficulty::Easy => WORDS_4E,
Difficulty::Medium => WORDS_4M,
Difficulty::Hard => WORDS_4H,
},
5 => match diff {
Difficulty::Easy => WORDS_5E,
Difficulty::Medium => WORDS_5M,
Difficulty::Hard => WORDS_5H,
},
6 => match diff {
Difficulty::Easy => WORDS_6E,
Difficulty::Medium => WORDS_6M,
Difficulty::Hard => WORDS_6H,
},
7 => match diff {
Difficulty::Easy => WORDS_7E,
Difficulty::Medium => WORDS_7M,
Difficulty::Hard => WORDS_7H,
},
8 => match diff {
Difficulty::Easy => WORDS_8E,
Difficulty::Medium => WORDS_8M,
Difficulty::Hard => WORDS_8H,
},
_ => unreachable!(),
};
let words = sanitise_gpt_list(txt, letters);
words
}
fn sanitise_gpt_list(list: &str, letters: i32) -> Vec<String> {
let mut repeat_map = HashMap::new();
for line in list.lines() {
for word in line.split_ascii_whitespace().map(|s| s.trim()) {
if word.len() == letters as usize && word.chars().all(|c| c.is_ascii_alphabetic()) {
*repeat_map.entry(word.to_ascii_uppercase()).or_insert(0) += 1;
}
}
}
let mut webster_list = Vec::new();
for word in repeat_map.keys() {
if webster::dictionary(word).is_some() {
webster_list.push(word.clone());
}
}
let gpt_list: Vec<String> = repeat_map.keys().map(|s| s.to_owned()).collect();
match DEFAULT_LIST {
ListType::Gpt => gpt_list,
ListType::Webster => webster_list,
}
}
pub fn word_exists(letters: i32, word: &String) -> bool {
sanitise_gpt_list(WORDS_MASTER, letters).contains(word)
|| load_word_list(letters, &Difficulty::Easy).contains(word)
|| load_word_list(letters, &Difficulty::Medium).contains(word)
|| load_word_list(letters, &Difficulty::Hard).contains(word)
}
pub fn define(word: &String) -> String {
webster::dictionary(word)
.unwrap_or("No definition found!")
.to_string()
}
pub fn hint(word: &String) -> String {
let word = word.to_ascii_lowercase();
let definition = define(&word);
let filler = "_".repeat(word.len());
definition.replace(&word, &format!("[{filler}]"))
}
pub fn titled_loading_screen(header: &str, color: &str, ms: usize) {
let middle = if let Some((_, terminal_size::Height(h))) = terminal_size::terminal_size() {
(h as usize / 2) - 3
} else {
0 };
let hold_time = ms / 4;
let print_time = ((ms / 4) * 3) / header.len();
let header = header.to_ascii_uppercase();
clear!();
for (i, _) in header.chars().enumerate() {
print!(
"{}",
terminal_fonts::to_block_string(&header[0..=i]).color(color)
);
newln!(middle);
flush!();
if i == header.len() - 1 {
sleep!(hold_time as u64);
} else {
sleep!(print_time as u64);
clear!();
}
newln!();
}
}