Primer commit

This commit is contained in:
Guilleag01
2026-01-05 18:25:51 +01:00
commit fd0972102c
6 changed files with 2005 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "wordle_solver"
version = "0.1.0"

4
Cargo.toml Normal file
View File

@@ -0,0 +1,4 @@
[package]
name = "wordle_solver"
version = "0.1.0"
edition = "2024"

1
src/lib.rs Normal file
View File

@@ -0,0 +1 @@
pub mod words;

467
src/main.rs Normal file
View File

@@ -0,0 +1,467 @@
use std::{
collections::{HashMap, HashSet},
io::{Write, stdin, stdout},
};
use wordle_solver::words::WORDS;
const PRIMES: [u128; 54] = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
enum LetterState {
#[default]
Gray,
Yellow,
Green,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
struct Result([LetterState; 5]);
fn main() {
let all_words: Vec<&str> = HashSet::from(WORDS).iter().copied().collect();
let mut current_words = all_words.clone();
let mut hmap = HashMap::new();
loop {
let old_length = current_words.len();
println!("Palabras restantes: {}", old_length);
let mut word = String::from(" ");
while !all_words.contains(&word.trim()) {
word = "".to_string();
print!("Palabra: ");
stdout().flush().unwrap();
stdin().read_line(&mut word).unwrap();
}
let mut res_string = String::default();
print!("Resultado: ");
stdout().flush().unwrap();
stdin().read_line(&mut res_string).unwrap();
let res = res_from_string(res_string);
get_remaining_words(&word, &res, &mut current_words);
let new_length = current_words.len();
let eliminadas = old_length - new_length;
println!(
" Palabras eliminadas: {} ({:.2}%)",
eliminadas,
eliminadas as f32 * 100_f32 / old_length as f32
);
if current_words.len() == 1 {
println!("{}", current_words[0]);
break;
}
let l = all_words.len();
let mut words_score = all_words
.iter()
.enumerate()
.map(|(i, w)| {
print!("\r{:.2}%", 100_f32 * i as f32 / l as f32);
stdout().flush().unwrap();
(*w, get_score(w, &current_words, &mut hmap))
})
.collect::<Vec<(&str, f32)>>();
print!("\r");
stdout().flush().unwrap();
words_score.sort_by(|a, b| {
if (a.1 - 0.5).abs() > (b.1 - 0.5).abs() {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
});
for (w, s) in words_score.iter().take(5) {
println!(" {}: {}", w, s);
}
if current_words.len() < 6 {
println!(" Palabras posibles: ");
for w in current_words.clone() {
println!(" {}", w);
}
}
}
}
fn res_from_string(s: String) -> Result {
let mut r = Result::default();
for (i, l) in r.0.iter_mut().enumerate() {
match s.chars().nth(i).unwrap() {
'g' => *l = LetterState::Gray,
'v' => *l = LetterState::Green,
'a' => *l = LetterState::Yellow,
_ => panic!("Bad letter {i}: {:?}", s.chars().nth(i).unwrap()),
}
}
r
}
fn get_score(word: &str, words: &[&str], hmap: &mut HashMap<u128, Result>) -> f32 {
let mut possible = 0_f32;
let l = words.len();
let other_words = Vec::from(words);
let mut score_hmap: HashMap<(&str, Result), f32> = HashMap::new();
for real_word in other_words.clone() {
// let hmap = HashMap::new();
let res = get_result(word, real_word, hmap);
if let Some(score) = score_hmap.get(&(word, res)) {
possible += score;
} else {
let c = get_number_rem_words(word, &res, &other_words);
score_hmap.insert((word, res), c as f32 / l as f32);
possible += c as f32 / l as f32;
}
}
possible / l as f32
}
#[inline]
fn get_remaining_words(word: &str, result: &Result, words: &mut Vec<&str>) {
*words = words
.iter()
.copied()
.filter(|&s| is_possible(word, result, s) && *s != word[..5])
.collect();
}
fn get_number_rem_words(word: &str, result: &Result, words: &[&str]) -> usize {
let mut count = 0;
for possible_word in words {
if is_possible(word, result, possible_word) && *possible_word != word {
count += 1;
}
}
count
}
fn get_result(guess: &str, real_acc: &str, hmap: &mut HashMap<u128, Result>) -> Result {
let hash = hash_words(guess, real_acc);
if let Some(r) = hmap.get(&hash) {
return *r;
}
let real = real_acc;
assert!(
guess.chars().count() == 5,
"Guessed word length is not 5: {:?} length: {}",
guess,
guess.chars().count()
);
assert!(
real.chars().count() == 5,
"Real word length is not 5: {:?} length: {}",
real.chars(),
real.chars().count()
);
let mut res = [LetterState::Gray; 5];
let mut count_g: HashMap<char, u8> = HashMap::new();
let mut count_r: HashMap<char, u8> = HashMap::new();
for c in guess.chars() {
*count_g.entry(c).or_insert(0) += 1;
}
for c in real.chars() {
*count_r.entry(c).or_insert(0) += 1;
}
for (i, letter) in guess
.chars()
.collect::<Vec<char>>()
.iter()
.enumerate()
.rev()
{
let l = guess.chars().nth(i).unwrap();
*count_g.entry(guess.chars().nth(i).unwrap()).or_insert(0) -= 1;
if real.chars().nth(i).unwrap() == *letter {
res[i] = LetterState::Green;
*count_r.entry(l).or_insert(0) -= 1;
} else if real.contains(*letter) && count_g.get(&l).unwrap() < count_r.get(&l).unwrap() {
res[i] = LetterState::Yellow;
}
}
let r = Result(res);
hmap.insert(hash, r);
r
}
fn is_possible(guessed: &str, result: &Result, to_check: &str) -> bool {
let mut count_g: HashMap<char, u8> = HashMap::new();
let mut count_r: HashMap<char, u8> = HashMap::new();
for c in guessed.chars() {
*count_g.entry(c).or_insert(0) += 1;
}
for c in to_check.chars() {
*count_r.entry(c).or_insert(0) += 1;
}
for i in (0..5).rev() {
let letter = guessed.chars().nth(i).unwrap();
*count_g.entry(letter).or_insert(0) -= 1;
match result.0[i] {
LetterState::Gray => {
if to_check.contains(letter)
&& count_g.get(&letter).unwrap() < count_r.get(&letter).unwrap()
{
return false;
}
}
LetterState::Yellow => {
if !to_check.contains(letter) || to_check.chars().nth(i).unwrap() == letter {
return false;
}
}
LetterState::Green => {
*count_r.entry(letter).or_insert(0) -= 1;
if to_check.chars().nth(i).unwrap() != letter {
return false;
}
}
}
}
true
}
fn hash_words(word: &str, real: &str) -> u128 {
let mut acc = 1;
for i in 0..5 {
let mut v = word.chars().nth(i).unwrap().to_ascii_lowercase() as usize - 97;
if v > 26 {
v = 26;
}
acc *= PRIMES[v];
}
for i in 0..5 {
let mut v = real.chars().nth(i).unwrap().to_ascii_lowercase() as usize - 70;
if v > 53 {
v = 53;
}
acc *= PRIMES[v];
}
acc
}
/* --------------- TESTS --------------- */
#[test]
fn test_get_result_aureo_manga() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("aureo"), "manga", &mut hmap),
Result([
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_aabbb_manga() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("aabbb"), "manga", &mut hmap),
Result([
LetterState::Yellow,
LetterState::Green,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_ababb_manga() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("ababb"), "manga", &mut hmap),
Result([
LetterState::Yellow,
LetterState::Gray,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_abaab_manga() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("abaab"), "manga", &mut hmap),
Result([
LetterState::Yellow,
LetterState::Gray,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_hucha_mucha() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("hucha"), "mucha", &mut hmap),
Result([
LetterState::Gray,
LetterState::Green,
LetterState::Green,
LetterState::Green,
LetterState::Green
])
);
}
#[test]
fn test_get_result_gafea_salto() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("gafea"), "salto", &mut hmap),
Result([
LetterState::Gray,
LetterState::Green,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_botox_salto() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("botox"), "salto", &mut hmap),
Result([
LetterState::Gray,
LetterState::Yellow,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray
])
);
}
#[test]
fn test_get_result_ulula_valor() {
let mut hmap = HashMap::new();
assert_eq!(
get_result(&String::from("ulula"), "valor", &mut hmap),
Result([
LetterState::Gray,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray,
LetterState::Yellow
])
);
}
#[test]
fn test_is_possible_hucha_mucha() {
assert!(is_possible(
"hucha",
&Result([
LetterState::Gray,
LetterState::Green,
LetterState::Green,
LetterState::Green,
LetterState::Green
]),
"mucha"
));
}
#[test]
fn test_is_possible_aureo_bureo() {
assert!(is_possible(
"aureo",
&Result([
LetterState::Gray,
LetterState::Green,
LetterState::Green,
LetterState::Green,
LetterState::Green
]),
"bureo"
));
}
#[test]
fn test_is_possible_izaba_zureo() {
assert!(!is_possible(
"izaba",
&Result([
LetterState::Gray,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray
]),
"zureo"
));
}
#[test]
fn test_is_possible_gafea_salto() {
assert!(is_possible(
"gafea",
&Result([
LetterState::Gray,
LetterState::Green,
LetterState::Gray,
LetterState::Gray,
LetterState::Gray
]),
"salto"
));
}
#[test]
fn test_is_possible_botox_salto() {
assert!(is_possible(
"botox",
&Result([
LetterState::Gray,
LetterState::Yellow,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray
]),
"salto"
));
}
#[test]
fn test_is_possible_ulula_valor() {
assert!(is_possible(
"ulula",
&Result([
LetterState::Gray,
LetterState::Yellow,
LetterState::Gray,
LetterState::Gray,
LetterState::Yellow
]),
"valor"
));
}

1525
src/words.rs Normal file

File diff suppressed because it is too large Load Diff