use std::error::Error; use std::fmt::Display; use std::fs::File; use std::io::{BufReader, prelude::*}; #[derive(Debug)] pub struct Crossword { data: Vec>, 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 { 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> { let mut input_buffer = BufReader::new(file); let mut data: Vec> = 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>) -> 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"); } }