use crate::utils::extract_cstring_with_offset;
use crate::Error;
use std::usize;
#[derive(Debug)]
pub struct CrosswordData {
pub width: u8,
pub height: u8,
pub total_clues: u8,
pub horizontal_clues: Vec<CrosswordClue>,
pub vertical_clues: Vec<CrosswordClue>,
pub crossword_data: Vec<Vec<CrosswordBox>>,
}
impl CrosswordData {
pub fn load(bytes: &[u8]) -> Result<CrosswordData, Error> {
let mut offset = 0;
if bytes.len() < 3 {
return Err(Error::UnexpectedEndOfFile);
}
let width = bytes[offset];
offset += 1;
let height = bytes[offset];
offset += 1;
let total_clues = bytes[offset];
offset += 1;
if width == 0 || height == 0 {
return Err(Error::WidthOrHeightIsZero);
}
if total_clues == 0 {
return Err(Error::TotalCluesIsZero);
}
let mut horizontal_clues = Vec::new();
while bytes[offset] != 0x00 {
let (clue, new_offset) = parse_crossword_clue(bytes, offset);
horizontal_clues.push(clue);
offset = new_offset;
}
offset += 1;
let mut vertical_clues = Vec::new();
while bytes[offset] != 0x00 {
let (clue, new_offset) = parse_crossword_clue(bytes, offset);
vertical_clues.push(clue);
offset = new_offset;
}
offset += 1;
if horizontal_clues.len() + vertical_clues.len() != total_clues as usize {
return Err(Error::ClueCountMismatch {
expected: total_clues,
found: (horizontal_clues.len() + vertical_clues.len()) as u32,
});
};
let expected_bytes = offset as usize + (width * height) as usize * 2;
if expected_bytes != bytes.len() {
return Err(Error::NotEnoughCrosswordBytes {
expected: expected_bytes as u32,
found: bytes.len() as u32,
});
}
let mut crossword_data: Vec<Vec<CrosswordBox>> = Vec::new();
for _ in 0..height {
let mut row: Vec<CrosswordBox> = Vec::new();
for _ in 0..width {
let number = bytes[offset];
let value = match CrosswordBoxValue::from_byte(bytes[offset + 1]) {
Ok(value) => value,
Err(err) => return Err(err),
};
let crossword_box = match CrosswordBox::new(number, value) {
Ok(crossword_box) => crossword_box,
Err(err) => return Err(err),
};
row.push(crossword_box);
offset += 2;
}
crossword_data.push(row);
}
Ok(CrosswordData {
width,
height,
total_clues,
horizontal_clues,
vertical_clues,
crossword_data,
})
}
pub fn new(
width: u8,
height: u8,
horizontal_clues: Vec<CrosswordClue>,
vertical_clues: Vec<CrosswordClue>,
crossword_data: Vec<Vec<CrosswordBox>>,
) -> Result<CrosswordData, Error> {
if crossword_data.len() != height as usize {
return Err(Error::HeightCrosswordDataMismatch {
height,
crossword_height: crossword_data.len() as u32,
});
}
for row in &crossword_data {
if row.len() != width as usize {
return Err(Error::WidthCrosswordDataMismatch {
width,
crossword_width: row.len() as u32,
});
}
}
let mut numbers_in_crossword: Vec<u8> = Vec::new();
for row in &crossword_data {
for item in row {
if numbers_in_crossword.contains(&item.number) && item.number != 0 {
return Err(Error::DuplicateNumber {
number: item.number,
});
}
numbers_in_crossword.push(item.number);
}
}
let mut clue_numbers: Vec<u8> = Vec::new();
for clue in &vertical_clues {
if !numbers_in_crossword.contains(&clue.number) {
return Err(Error::VerticalClueContainsInvalidNumber {
number: clue.number,
});
}
if clue_numbers.contains(&clue.number) {
return Err(Error::VerticalClueContainsDuplicate {
number: clue.number,
});
}
clue_numbers.push(clue.number);
}
clue_numbers.clear();
for clue in &horizontal_clues {
if !numbers_in_crossword.contains(&clue.number) {
return Err(Error::HorizontalClueContainsInvalidNumber {
number: clue.number,
});
}
if clue_numbers.contains(&clue.number) {
return Err(Error::HorizontalClueContainsDuplicate {
number: clue.number,
});
}
clue_numbers.push(clue.number);
}
Ok(CrosswordData {
width,
height,
total_clues: horizontal_clues.len() as u8 + vertical_clues.len() as u8,
vertical_clues,
horizontal_clues,
crossword_data,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(self.width);
bytes.push(self.height);
bytes.push(self.total_clues);
for clue in &self.horizontal_clues {
bytes.extend(clue.to_bytes());
}
bytes.push(0);
for clue in &self.vertical_clues {
bytes.extend(clue.to_bytes());
}
bytes.push(0);
for row in &self.crossword_data {
for item in row {
bytes.extend(item.to_bytes());
}
}
return bytes;
}
}
#[derive(Debug, Clone)]
pub struct CrosswordClue {
pub number: u8,
pub value: String,
}
impl CrosswordClue {
pub fn new(number: u8, value: &str) -> CrosswordClue {
CrosswordClue {
number,
value: value.to_string(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(self.number);
bytes.extend(self.value.as_bytes());
bytes.push(0);
bytes
}
}
#[derive(Debug, Clone)]
pub struct CrosswordBox {
pub number: u8,
pub value: CrosswordBoxValue,
}
impl CrosswordBox {
pub fn new(number: u8, value: CrosswordBoxValue) -> Result<CrosswordBox, Error> {
match value {
CrosswordBoxValue::Letter(value) => {
if !value.is_ascii() {
return Err(Error::NonAsciiCharacter);
}
if !value.is_alphabetic() {
return Err(Error::NonAlphabeticCharacter);
}
if value.is_lowercase() {
return Err(Error::NonUppercaseCharacter);
}
}
_ => {}
}
Ok(CrosswordBox { number, value })
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(self.number);
bytes.push(self.value.to_byte());
return bytes;
}
}
#[derive(Debug, Clone)]
pub enum CrosswordBoxValue {
Empty,
Solid,
Letter(char),
}
impl CrosswordBoxValue {
pub fn to_byte(&self) -> u8 {
match self {
CrosswordBoxValue::Empty => 0x20, CrosswordBoxValue::Solid => 0x23, CrosswordBoxValue::Letter(letter) => *letter as u8,
}
}
pub fn to_string(&self) -> String {
match self {
CrosswordBoxValue::Empty => " ".to_string(),
CrosswordBoxValue::Solid => "#".to_string(),
CrosswordBoxValue::Letter(letter) => letter.to_string(),
}
}
pub fn from_byte(byte: u8) -> Result<Self, Error> {
match byte {
0x20 => Ok(CrosswordBoxValue::Empty), 0x23 => Ok(CrosswordBoxValue::Solid), b if b.is_ascii_alphabetic() => Ok(CrosswordBoxValue::Letter(b as char)),
_ => Err(Error::InvalidCrosswordBoxByte { found: byte }),
}
}
}
fn parse_crossword_clue(bytes: &[u8], start: usize) -> (CrosswordClue, usize) {
let number = bytes[start];
let (value, end_offset) = extract_cstring_with_offset(bytes, start + 1); let clue = CrosswordClue::new(number, &value);
(clue, end_offset)
}