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)) -> String { let mut word = String::new(); for i in 0..num_of_letters { let x = self.x as isize + (i as isize * direction.1); let y = self.y as isize + (i as isize * direction.0); if x < 0 || x >= self.crossword.width as isize || y < 0 || y >= self.crossword.height as isize { break; } word.push(self.value_by_index(x as usize, y as usize)); } word } pub fn n(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (-1, 0)) } pub fn ne(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (-1, 1)) } pub fn e(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (0, 1)) } pub fn se(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (1, 1)) } pub fn s(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (1, 0)) } pub fn sw(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (1, -1)) } pub fn w(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (0, -1)) } pub fn nw(&self, num_of_letters: usize) -> String { self.take(num_of_letters, (-1, -1)) } } 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"); } #[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"); } #[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"); } #[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"); } #[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"); } #[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"); } #[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"); } #[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"); } }