344 lines
11 KiB
Rust
344 lines
11 KiB
Rust
use std::error::Error;
|
|
use std::fmt::Display;
|
|
use std::fs::File;
|
|
use std::io::{BufReader, prelude::*};
|
|
|
|
#[derive(Debug)]
|
|
pub struct Crossword {
|
|
data: Vec<Vec<char>>,
|
|
width: usize,
|
|
height: usize,
|
|
}
|
|
|
|
pub struct CrosswordCell<'a> {
|
|
x: usize,
|
|
y: usize,
|
|
crossword: &'a Crossword,
|
|
}
|
|
|
|
impl<'a> CrosswordCell<'a> {
|
|
fn value_by_index(&self, x: usize, y: usize) -> char {
|
|
// x and y are reverse because we store by row
|
|
//
|
|
// data[ROW][COLUMN]
|
|
self.crossword.data[y][x]
|
|
}
|
|
|
|
pub fn value(&self) -> char {
|
|
self.value_by_index(self.x, self.y)
|
|
}
|
|
|
|
fn take(&self, num_of_letters: usize, direction: (isize, isize), start: isize) -> String {
|
|
let mut word = String::new();
|
|
|
|
let end = start + num_of_letters as isize;
|
|
|
|
for i in start..end {
|
|
let x = self.x as isize + (i * direction.1);
|
|
let y = self.y as isize + (i * direction.0);
|
|
if x < 0
|
|
|| x >= self.crossword.width as isize
|
|
|| y < 0
|
|
|| y >= self.crossword.height as isize
|
|
{
|
|
if i < 0 {
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
word.push(self.value_by_index(x as usize, y as usize));
|
|
}
|
|
|
|
word
|
|
}
|
|
|
|
const DIRECTION_N: (isize, isize) = (-1, 0);
|
|
pub fn n(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_N, 0)
|
|
}
|
|
|
|
pub fn n2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_N, start)
|
|
}
|
|
|
|
const DIRECTION_NE: (isize, isize) = (-1, 1);
|
|
pub fn ne(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_NE, 0)
|
|
}
|
|
|
|
pub fn ne2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_NE, start)
|
|
}
|
|
|
|
const DIRECTION_E: (isize, isize) = (0, 1);
|
|
pub fn e(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_E, 0)
|
|
}
|
|
|
|
pub fn e2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_E, start)
|
|
}
|
|
|
|
const DIRECTION_SE: (isize, isize) = (1, 1);
|
|
pub fn se(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_SE, 0)
|
|
}
|
|
|
|
pub fn se2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_SE, start)
|
|
}
|
|
|
|
const DIRECTION_S: (isize, isize) = (1, 0);
|
|
pub fn s(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_S, 0)
|
|
}
|
|
|
|
pub fn s2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_S, start)
|
|
}
|
|
|
|
const DIRECTION_SW: (isize, isize) = (1, -1);
|
|
pub fn sw(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_SW, 0)
|
|
}
|
|
|
|
pub fn sw2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_SW, start)
|
|
}
|
|
|
|
const DIRECTION_W: (isize, isize) = (0, -1);
|
|
pub fn w(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_W, 0)
|
|
}
|
|
|
|
pub fn w2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_W, start)
|
|
}
|
|
|
|
const DIRECTION_NW: (isize, isize) = (-1, -1);
|
|
pub fn nw(&self, num_of_letters: usize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_NW, 0)
|
|
}
|
|
|
|
pub fn nw2(&self, num_of_letters: usize, start: isize) -> String {
|
|
self.take(num_of_letters, CrosswordCell::DIRECTION_NW, start)
|
|
}
|
|
}
|
|
|
|
pub struct CrosswordIterator<'a> {
|
|
crossword: &'a Crossword,
|
|
position: usize,
|
|
}
|
|
|
|
impl<'a> Iterator for CrosswordIterator<'a> {
|
|
type Item = CrosswordCell<'a>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let y = self.position / self.crossword.width;
|
|
let x = self.position - y * self.crossword.width;
|
|
self.position += 1;
|
|
|
|
if x < self.crossword.width && y < self.crossword.height {
|
|
Some(CrosswordCell {
|
|
x,
|
|
y,
|
|
crossword: self.crossword,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Crossword {
|
|
pub fn new(file: File) -> Result<Crossword, Box<dyn Error>> {
|
|
let mut input_buffer = BufReader::new(file);
|
|
|
|
let mut data: Vec<Vec<char>> = Vec::new();
|
|
|
|
let mut first_line = String::new();
|
|
input_buffer.read_line(&mut first_line)?;
|
|
let first_line = first_line.trim_end().to_owned();
|
|
let width = first_line.len();
|
|
data.push(first_line.chars().collect());
|
|
|
|
for line in input_buffer.lines() {
|
|
let line = line?;
|
|
if line.len() != width {
|
|
println!("{}", line);
|
|
Err("Line length of input are not consistent")?;
|
|
}
|
|
data.push(line.chars().collect());
|
|
}
|
|
let height = data.len();
|
|
|
|
Ok(Crossword {
|
|
data,
|
|
width,
|
|
height,
|
|
})
|
|
}
|
|
|
|
fn from_data(data: Vec<Vec<char>>) -> Crossword {
|
|
let height = data.len();
|
|
let width = data[0].len();
|
|
Crossword {
|
|
data,
|
|
width,
|
|
height,
|
|
}
|
|
}
|
|
|
|
pub fn width(&self) -> usize {
|
|
self.width
|
|
}
|
|
|
|
pub fn height(&self) -> usize {
|
|
self.height
|
|
}
|
|
|
|
pub fn iter_cells(&self) -> CrosswordIterator {
|
|
CrosswordIterator {
|
|
crossword: self,
|
|
position: 0,
|
|
}
|
|
}
|
|
|
|
pub fn cell(&self, x: usize, y: usize) -> CrosswordCell {
|
|
CrosswordCell {
|
|
x,
|
|
y,
|
|
crossword: self,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Crossword {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
for row in &self.data {
|
|
let row = String::from_iter(row.iter());
|
|
writeln!(f, "{}", row)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
// Note this useful idiom: importing names from outer (for mod tests) scope.
|
|
use super::*;
|
|
|
|
fn small_test_crossword() -> Crossword {
|
|
Crossword::from_data(vec![
|
|
vec!['1', '2', '3'],
|
|
vec!['4', '5', '6'],
|
|
vec!['7', '8', '9'],
|
|
])
|
|
}
|
|
|
|
#[test]
|
|
fn test_north() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).n(0), "");
|
|
assert_eq!(crossword.cell(1, 1).n(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).n(2), "52");
|
|
assert_eq!(crossword.cell(1, 1).n(3), "52");
|
|
assert_eq!(crossword.cell(1, 1).n2(3, 1), "2");
|
|
assert_eq!(crossword.cell(1, 1).n2(3, 0), "52");
|
|
assert_eq!(crossword.cell(1, 1).n2(3, -1), "852");
|
|
assert_eq!(crossword.cell(1, 1).n2(3, -2), "85");
|
|
}
|
|
|
|
#[test]
|
|
fn test_northeast() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).ne(0), "");
|
|
assert_eq!(crossword.cell(1, 1).ne(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).ne(2), "53");
|
|
assert_eq!(crossword.cell(1, 1).ne(3), "53");
|
|
assert_eq!(crossword.cell(1, 1).ne2(3, 1), "3");
|
|
assert_eq!(crossword.cell(1, 1).ne2(3, 0), "53");
|
|
assert_eq!(crossword.cell(1, 1).ne2(3, -1), "753");
|
|
assert_eq!(crossword.cell(1, 1).ne2(3, -2), "75");
|
|
}
|
|
|
|
#[test]
|
|
fn test_east() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).e(0), "");
|
|
assert_eq!(crossword.cell(1, 1).e(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).e(2), "56");
|
|
assert_eq!(crossword.cell(1, 1).e(3), "56");
|
|
assert_eq!(crossword.cell(1, 1).e2(3, 1), "6");
|
|
assert_eq!(crossword.cell(1, 1).e2(3, 0), "56");
|
|
assert_eq!(crossword.cell(1, 1).e2(3, -1), "456");
|
|
assert_eq!(crossword.cell(1, 1).e2(3, -2), "45");
|
|
}
|
|
|
|
#[test]
|
|
fn test_southeast() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).se(0), "");
|
|
assert_eq!(crossword.cell(1, 1).se(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).se(2), "59");
|
|
assert_eq!(crossword.cell(1, 1).se(3), "59");
|
|
assert_eq!(crossword.cell(1, 1).se2(3, 1), "9");
|
|
assert_eq!(crossword.cell(1, 1).se2(3, 0), "59");
|
|
assert_eq!(crossword.cell(1, 1).se2(3, -1), "159");
|
|
assert_eq!(crossword.cell(1, 1).se2(3, -2), "15");
|
|
}
|
|
|
|
#[test]
|
|
fn test_south() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).s(0), "");
|
|
assert_eq!(crossword.cell(1, 1).s(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).s(2), "58");
|
|
assert_eq!(crossword.cell(1, 1).s(3), "58");
|
|
assert_eq!(crossword.cell(1, 1).s2(3, 1), "8");
|
|
assert_eq!(crossword.cell(1, 1).s2(3, 0), "58");
|
|
assert_eq!(crossword.cell(1, 1).s2(3, -1), "258");
|
|
assert_eq!(crossword.cell(1, 1).s2(3, -2), "25");
|
|
}
|
|
|
|
#[test]
|
|
fn test_southwest() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).sw(0), "");
|
|
assert_eq!(crossword.cell(1, 1).sw(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).sw(2), "57");
|
|
assert_eq!(crossword.cell(1, 1).sw(3), "57");
|
|
assert_eq!(crossword.cell(1, 1).sw2(3, 1), "7");
|
|
assert_eq!(crossword.cell(1, 1).sw2(3, 0), "57");
|
|
assert_eq!(crossword.cell(1, 1).sw2(3, -1), "357");
|
|
assert_eq!(crossword.cell(1, 1).sw2(3, -2), "35");
|
|
}
|
|
|
|
#[test]
|
|
fn test_west() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).w(0), "");
|
|
assert_eq!(crossword.cell(1, 1).w(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).w(2), "54");
|
|
assert_eq!(crossword.cell(1, 1).w(3), "54");
|
|
assert_eq!(crossword.cell(1, 1).w2(3, 1), "4");
|
|
assert_eq!(crossword.cell(1, 1).w2(3, 0), "54");
|
|
assert_eq!(crossword.cell(1, 1).w2(3, -1), "654");
|
|
assert_eq!(crossword.cell(1, 1).w2(3, -2), "65");
|
|
}
|
|
|
|
#[test]
|
|
fn test_northwest() {
|
|
let crossword = small_test_crossword();
|
|
assert_eq!(crossword.cell(1, 1).nw(0), "");
|
|
assert_eq!(crossword.cell(1, 1).nw(1), "5");
|
|
assert_eq!(crossword.cell(1, 1).nw(2), "51");
|
|
assert_eq!(crossword.cell(1, 1).nw(3), "51");
|
|
assert_eq!(crossword.cell(1, 1).nw2(3, 1), "1");
|
|
assert_eq!(crossword.cell(1, 1).nw2(3, 0), "51");
|
|
assert_eq!(crossword.cell(1, 1).nw2(3, -1), "951");
|
|
assert_eq!(crossword.cell(1, 1).nw2(3, -2), "95");
|
|
}
|
|
}
|