use std::{
array::from_fn,
fmt,
};
use bevy::prelude::*;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::consts::MAGICAL_AJUSTMENT_NUMBER;
use crate::gameplay::Gameplay;
use crate::{consts::CELL_SIZE, kennett::KennettConnector};
use super::anim::Anim;
use super::board_cell::BoardCell;
use super::cell::Cell;
use super::colors::Colors;
use super::cursor::Cursor;
use super::direction::Direction;
use super::level::Level;
use super::shapes::Shapes;
#[derive(Debug)]
pub struct InnerBoard {
cells: [Cell; 81],
highlight: usize,
done: bool,
stuck: bool,
}
impl InnerBoard {
fn recalculate(mut self) -> Self {
self.done = true;
self.stuck = false;
for idx in 0..81 {
if self.cells[idx].value() == 0 {
self.done = false;
}
let cell: u16 = (&self.cells[idx]).into();
if cell == 0 {
self.stuck = true;
}
}
self
}
pub fn done(&self) -> bool {
self.done
}
pub fn update(
&self,
transform: &mut Single<&mut Transform, With<Cursor>>,
) {
for idx in 0..81 {
if idx == self.highlight {
transform.translation.x = ((idx % 9) as f32 - 4.0) * CELL_SIZE;
transform.translation.y = ((idx / 9) as f32 - 4.0) * CELL_SIZE + MAGICAL_AJUSTMENT_NUMBER;
return;
}
}
}
pub fn size(&self) -> Vec2 {
Vec2::new(CELL_SIZE * 9.0, CELL_SIZE * 9.0)
}
pub fn rotate(&mut self, direction: Direction) {
match direction {
Direction::Up => self.rotate_vertically(6),
Direction::Down => self.rotate_vertically(3),
Direction::Left => self.rotate_horizontally(3),
Direction::Right => self.rotate_horizontally(6),
}
}
fn rotate_vertically(&mut self, lines: usize) {
let amount = lines * 9;
let mut new_border: [Cell; 81] = from_fn(|_| Cell::default());
for i in amount..81 {
new_border[i-amount] = self.cells[i].clone();
}
for i in 0..amount {
new_border[i+81-amount] = self.cells[i].clone();
}
self.cells = new_border;
}
fn rotate_horizontally(&mut self, cols: usize) {
let mut new_border: [Cell; 81] = from_fn(|_| Cell::default());
for x in cols..9 {
for y in 0..9 {
let si = x + y*9;
let ti = (x-cols) + y*9;
new_border[ti] = self.cells[si].clone();
}
}
for x in 0..cols {
for y in 0..9 {
let si = x + y*9;
let ti = (x+9-cols) + y*9;
new_border[ti] = self.cells[si].clone();
}
}
self.cells = new_border;
}
pub(super) fn toggle_candidate(&self, x: usize, y: usize, value: u8, actions: &mut Vec<Anim>) -> Option<Self> {
if self.stuck {
return None;
}
let mut board = self.clone();
if board.cells[x + y * 9].toggle_candidate(value) {
actions.push(if board.cell(x, y).is_candidate_set(value) {
Anim::SetCandidate { x, y, value }
} else {
Anim::UnsetCandidate { x, y, value }
});
Some(board)
} else {
None
}
}
pub(super) fn set_value(&self, x: usize, y: usize, value: u8, actions: &mut Vec<Anim>) -> Option<Self> {
if self.stuck {
return None;
}
let mut new_board = self.clone();
if new_board.cell_mut(x, y).set_value(value) {
new_board.clean_row(x, y, value, actions);
new_board.clean_column(x, y, value, actions);
new_board.clean_group(x, y, value, actions);
Some(new_board.recalculate())
} else {
None
}
}
pub fn render(
&self,
x: f32,
y: f32,
commands: &mut Commands,
cell_query: &Query<Entity, With<BoardCell>>,
cursor_query: &Query<Entity, With<Cursor>>,
shapes: &Res<Shapes>,
colors: &Res<Colors>,
anims: &Vec<Anim>,
) {
for entity in cell_query.iter() {
commands.entity(entity).despawn();
}
for entity in cursor_query.iter() {
commands.entity(entity).despawn();
}
let mut cursor_subcom = commands.spawn((
Gameplay,
Cursor,
Transform::from_xyz(x, y, 0.0),
));
if !self.stuck {
cursor_subcom.insert((
Mesh2d(shapes.rect.clone_weak()),
MeshMaterial2d(colors.highlight().clone_weak()),
));
}
for iy in 0..9 {
for ix in 0..9 {
let mut anims: Vec<Anim> = anims.iter()
.filter_map(|anim|
if anim.is_at(ix, iy) {
Some(anim.clone())
} else {
None
}
)
.collect()
;
anims.sort();
if let Some(anim) = anims.first() {
if !anim.is_candidate() {
let anim = anim.clone();
anims.clear();
anims.push(anim);
}
}
self.cell(ix, iy).render(
x + (ix as f32 - 4.0) * CELL_SIZE,
y + (iy as f32 - 4.0) * CELL_SIZE,
ix, iy,
commands,
shapes,
colors,
anims,
);
}
}
}
pub fn cell(&self, x: usize, y: usize) -> &Cell {
&self.cells[x + y * 9]
}
fn cell_mut(&mut self, x: usize, y: usize) -> &mut Cell {
&mut self.cells[x + y * 9]
}
pub fn highlight(&self) -> (usize, usize) {
(
self.highlight % 9,
self.highlight / 9,
)
}
pub fn set_highlight(&mut self, x: usize, y: usize) {
self.highlight = (x % 9) + (y % 9) * 9;
}
fn clean_row(&mut self, x: usize, y: usize, value: u8, actions: &mut Vec<Anim>) {
for ax in 0..9 {
if ax != x {
if self.cell_mut(ax, y).clean_candidate(value) {
actions.push(Anim::UnsetCandidate { x: ax, y, value });
}
}
}
}
fn clean_column(&mut self, x: usize, y: usize, value: u8, actions: &mut Vec<Anim>) {
for ay in 0..9 {
if ay != y {
if self.cell_mut(x, ay).clean_candidate(value) {
actions.push(Anim::UnsetCandidate { x, y: ay, value });
}
}
}
}
fn clean_group(&mut self, x: usize, y: usize, value: u8, actions: &mut Vec<Anim>) {
let gx = (x / 3) * 3;
let gy = (y / 3) * 3;
for ax in gx..(gx + 3) {
for ay in gy..(gy + 3) {
if ax != x || ay != y {
if self.cell_mut(ax, ay).clean_candidate(value) {
actions.push(Anim::UnsetCandidate { x: ax, y: ay, value });
}
}
}
}
}
}
impl Default for InnerBoard {
fn default() -> Self {
Self {
cells: std::array::from_fn(|_| Cell::default()),
highlight: 40,
done: false,
stuck: false,
}
}
}
impl Clone for InnerBoard {
fn clone(&self) -> Self {
Self {
cells: std::array::from_fn(|idx| self.cells[idx].clone()),
highlight: self.highlight,
done: self.done,
stuck: self.stuck,
}
}
}
impl TryFrom<Level> for InnerBoard {
type Error = std::io::Error;
fn try_from(level: Level) -> Result<InnerBoard, Self::Error> {
let cells = KennettConnector::generate(level)?;
let mut board = InnerBoard::default();
for (i, cell) in cells.into_iter().enumerate() {
let x = i % 9;
let y = i / 9;
board = match board.set_value(x, y, cell, &mut Vec::new()) {
Some(new_board) => new_board,
None => board,
};
}
Ok(board)
}
}
impl Serialize for InnerBoard {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("InnerBoard", 4)?;
s.serialize_field("cells", &self.cells.to_vec())?;
s.serialize_field("highlight", &self.highlight)?;
s.serialize_field("done", &self.done)?;
s.serialize_field("stuck", &self.stuck)?;
s.end()
}
}
impl<'de> Deserialize<'de> for InnerBoard {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
const FIELDS: &[&str] = &["cells", "highlight", "done", "stuck"];
deserializer.deserialize_struct("InnerBoard", FIELDS, InnerBoardVisitor)
}
}
struct InnerBoardVisitor;
impl<'de> Visitor<'de> for InnerBoardVisitor {
type Value = InnerBoard;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct InnerBoard")
}
fn visit_map<V>(self, mut map: V) -> Result<InnerBoard, V::Error>
where
V: MapAccess<'de>,
{
let mut cells = None;
let mut highlight = None;
let mut done = None;
let mut stuck = None;
while let Some(key) = map.next_key()? {
match key {
"cells" => {
if cells.is_some() {
return Err(de::Error::duplicate_field("cells"));
}
let vec: Vec<Cell> = map.next_value()?;
if vec.len() != 81 {
return Err(de::Error::invalid_length(vec.len(), &"81"));
}
let mut array = from_fn(|_| Cell::default());
for (i, cell) in vec.into_iter().enumerate() {
array[i] = cell;
}
cells = Some(array);
}
"highlight" => {
if highlight.is_some() {
return Err(de::Error::duplicate_field("highlight"));
}
highlight = Some(map.next_value()?);
}
"done" => {
if done.is_some() {
return Err(de::Error::duplicate_field("done"));
}
done = Some(map.next_value()?);
}
"stuck" => {
if stuck.is_some() {
return Err(de::Error::duplicate_field("stuck"));
}
stuck = Some(map.next_value()?);
}
_ => {
let _ = map.next_value::<de::IgnoredAny>()?;
}
}
}
let cells = cells
.ok_or_else(|| de::Error::missing_field("cells"))?;
let highlight = highlight
.ok_or_else(|| de::Error::missing_field("highlight"))?;
let done = done
.ok_or_else(|| de::Error::missing_field("done"))?;
let stuck = stuck
.ok_or_else(|| de::Error::missing_field("stuck"))?;
Ok(InnerBoard {
cells,
highlight,
done,
stuck,
})
}
}