RosettaCodeData/Task/Word-search/Rust/word-search.rs
2024-10-16 18:07:41 -07:00

186 lines
5.2 KiB
Rust

use once_cell::sync::Lazy;
use rand::rngs::ThreadRng;
use rand::seq::SliceRandom;
use regex::Regex;
use std::fs;
const DIRS: [[i32; 2]; 8] = [
[1, 0],
[0, 1],
[1, 1],
[1, -1],
[-1, 0],
[0, -1],
[-1, -1],
[-1, 1],
];
const NROWS: usize = 10;
const NCOLS: usize = NROWS;
const GRIDSIZE: usize = NROWS * NCOLS;
const MINWORDS: usize = 25;
struct Grid {
num_attempts: usize,
cells: Vec<Vec<u8>>,
solutions: Vec<String>,
}
fn new_grid() -> Grid {
return Grid {
num_attempts: 0,
cells: vec![vec![0_u8; NCOLS]; NROWS],
solutions: vec![String::new(); 0],
};
}
fn read_words(file_name: &str) -> Vec<String> {
let re = Regex::new(r"^[a-z]{3,10}$").unwrap();
let wordsfile = fs::read_to_string(file_name).unwrap().to_lowercase();
return wordsfile
.split_whitespace()
.filter(|w| re.is_match(w))
.map(|x| x.to_owned())
.collect::<Vec<String>>();
}
fn create_word_search(mut words: Vec<String>) -> Grid {
let mut rng = rand::thread_rng();
let mut gr = new_grid();
'outer: for i in 1..100 {
// up to 100 tries
gr = new_grid();
let message_len = place_message(&mut gr, "Rosetta Code", &mut rng);
let target = GRIDSIZE - message_len;
let mut cells_filled = 0_usize;
words.shuffle(&mut rng);
for word in &words {
cells_filled += try_place_word(&mut gr, &word, &mut rng);
if cells_filled == target {
if gr.solutions.len() >= MINWORDS {
gr.num_attempts = i;
break 'outer; // outer for
} else {
break; // inner for
}
}
}
}
return gr;
}
fn place_message(gr: &mut Grid, msg: &str, rng: &mut ThreadRng) -> usize {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^A-Z]").unwrap());
let mut smsg = msg.to_uppercase();
smsg = RE.replace(&&smsg, "").to_string();
let message_len = smsg.len();
if message_len > 0 && message_len < GRIDSIZE {
let gap_size = GRIDSIZE / message_len;
for i in 0..message_len {
let rpos = i * gap_size + (0..gap_size).collect::<Vec<usize>>().choose(rng).unwrap();
gr.cells[rpos / NCOLS][rpos % NCOLS] = smsg.as_bytes()[i];
}
return message_len;
}
return 0;
}
fn try_place_word(gr: &mut Grid, word: &str, rng: &mut ThreadRng) -> usize {
let binding = (0..DIRS.len()).collect::<Vec<usize>>();
let rand_dir = binding.choose(rng).unwrap();
let bindingp = (0..GRIDSIZE).collect::<Vec<usize>>();
let rand_pos = bindingp.choose(rng).unwrap();
for dir in 0..DIRS.len() {
let rdir = (dir + rand_dir) % DIRS.len();
for pos in 0..GRIDSIZE {
let rpos = (pos + rand_pos) % GRIDSIZE;
let letters_placed = try_location(gr, word, rdir, rpos);
if letters_placed > 0 {
return letters_placed;
}
}
}
return 0;
}
fn try_location(gr: &mut Grid, word: &str, dir: usize, pos: usize) -> usize {
let r = pos / NCOLS;
let c = pos % NCOLS;
let le = word.len();
// check bounds
if (DIRS[dir][0] == 1 && (le + c) > NCOLS)
|| (DIRS[dir][0] == -1 && (le - 1) > c)
|| (DIRS[dir][1] == 1 && (le + r) > NROWS)
|| (DIRS[dir][1] == -1 && (le - 1) > r)
{
return 0;
}
let mut overlaps = 0;
// check cells
let mut rr: i32 = r.try_into().unwrap();
let mut cc: i32 = c.try_into().unwrap();
for i in 0..le {
if gr.cells[rr as usize][cc as usize] != 0
&& gr.cells[rr as usize][cc as usize] != word.as_bytes()[i]
{
return 0;
}
cc += DIRS[dir][0];
rr += DIRS[dir][1];
}
// place
rr = r.try_into().unwrap();
cc = c.try_into().unwrap();
for i in 0..le {
if gr.cells[rr as usize][cc as usize] == word.as_bytes()[i] {
overlaps += 1;
} else {
gr.cells[rr as usize][cc as usize] = word.as_bytes()[i];
}
if i < le - 1 {
cc += DIRS[dir][0];
rr += DIRS[dir][1];
}
}
let letters_placed = le - overlaps;
if letters_placed > 0 {
let sol = format!("{:>10} ({},{})({},{})", word, c, r, cc, rr);
gr.solutions.push(sol);
}
return letters_placed;
}
fn print_result(gr: Grid) {
if gr.num_attempts == 0 {
println!("\nWord search puzzle: No grid to display\n");
return;
}
let size = gr.solutions.len();
println!(
"Word search puzzle solution:\n Attempts: {}",
gr.num_attempts
);
println!(" Number of words: {}", size);
println!("\n 0 1 2 3 4 5 6 7 8 9");
for r in 0..NROWS {
print!("\n{} ", r);
for c in 0..NCOLS {
print!(" {} ", gr.cells[r][c] as char);
}
}
println!("\n");
for i in (0..size - 1).step_by(2) {
println!("{} {}", gr.solutions[i], gr.solutions[i + 1]);
}
if size % 2 == 1 {
println!("{}", gr.solutions[size - 1]);
}
}
fn main() {
let dict_path = "unixdict.txt";
print_result(create_word_search(read_words(dict_path)));
}