use std::f32::consts::TAU;
use bevy::prelude::*;
use rand::{Rng, RngCore};
use serde::{Deserialize, Serialize};
use crate::tween::Tween;
use crate::consts::CELL_SIZE;
use crate::{consts::CANDIDATE_SIZE, gameplay::Gameplay};
use super::anim::Anim;
use super::board_cell::BoardCell;
use super::colors::Colors;
use super::error_cell::ErrorCell;
use super::shapes::Shapes;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cell(u16);
impl Default for Cell {
fn default() -> Self {
Cell(0b0001_1111_1111_0000)
}
}
impl Cell {
pub fn value(&self) -> u8 {
(self.0 & 0b0000_0000_0000_1111) as u8
}
pub fn set_value(&mut self, value: u8) -> bool {
let current = self.value();
if value > 9 || value == current {
false
} else if value == 0 {
self.0 &= 0b0001_1111_1111_0000;
true
} else if self.is_candidate_set(value) && current == 0 {
self.0 |= value as u16;
true
} else {
false
}
}
pub fn toggle_candidate(&mut self, value: u8) -> bool {
if value == 0 || value > 9 {
false
} else if self.is_candidate_set(value) {
self.do_clean_candidate(value)
} else {
self.do_set_candidate(value)
}
}
pub fn is_candidate_set(&self, value: u8) -> bool {
if value == 0 || value > 9 {
false
} else {
self.0 & (1 << (value + 3)) != 0
}
}
pub fn clean_candidate(&mut self, value: u8) -> bool {
if self.is_candidate_set(value) {
self.do_clean_candidate(value)
} else {
false
}
}
fn do_set_candidate(&mut self, value: u8) -> bool {
if value == 0 || value > 9 {
false
} else {
self.0 |= 1 << (value + 3);
true
}
}
fn do_clean_candidate(&mut self, value: u8) -> bool {
if value == 0 || value > 9 {
false
} else {
self.0 &= 0b0001_1111_1111_1111 ^ (1 << (value + 3));
true
}
}
pub fn render(&self,
x: f32, y: f32,
ix: usize, iy: usize,
commands: &mut Commands,
shapes: &Res<Shapes>,
colors: &Res<Colors>,
anims: Vec<Anim>,
) {
let value = self.value();
if value == 0 {
self.render_candidates(x, y, ix, iy, commands, shapes, colors, anims, false);
} else {
self.render_value(value, x, y, ix, iy, commands, shapes, colors, anims);
}
}
fn render_value(
&self,
value: u8,
xf: f32, yf: f32,
ix: usize, iy: usize,
commands: &mut Commands,
shapes: &Res<Shapes>,
colors: &Res<Colors>,
anims: Vec<Anim>,
) {
let mut subcommands = commands.spawn((
BoardCell,
Gameplay,
Mesh2d(shapes.cell.clone_weak()),
MeshMaterial2d(colors.get(value as usize).clone_weak()),
));
if let Some(&Anim::Set { value, .. }) = anims.first() {
let x = xf + (((value - 1) % 3) as f32 - 1.0) * CANDIDATE_SIZE;
let y = yf + (((value - 1) / 3) as f32 - 1.0) * CANDIDATE_SIZE;
let scale = CANDIDATE_SIZE / CELL_SIZE;
let tween: Tween = Tween::value()
.xy(x, y)
.scale(scale)
.xyf(xf, yf)
.into();
let mut scale = Vec3::splat(scale);
scale.z = 1.0;
subcommands.insert(
Transform {
translation: Vec3::new(x, y, 4.0),
scale,
..default()
}
);
subcommands.insert(tween);
self.render_candidates(xf, yf, ix, iy, commands, shapes, colors, anims, true);
} else {
subcommands.insert(Transform::from_xyz(xf, yf, 1.0));
}
}
fn render_candidates(
&self,
x: f32, y: f32,
ix: usize, iy: usize,
commands: &mut Commands,
shapes: &Res<Shapes>,
colors: &Res<Colors>,
anims: Vec<Anim>,
delete: bool,
) {
let shape = &shapes.cell_candidate;
for i in 0..9 {
let ax = x + ((i % 3) as f32 - 1.0) * CANDIDATE_SIZE;
let ay = y + ((i / 3) as f32 - 1.0) * CANDIDATE_SIZE;
if self.is_candidate_set(i as u8 + 1) {
let color = colors.get(i + 1);
let mut subcommand = commands.spawn((
BoardCell,
Gameplay,
Mesh2d(shape.clone_weak()),
MeshMaterial2d(color.clone_weak()),
Transform::from_xyz(ax, ay, 1.0),
));
if delete {
subcommand.insert(Tween::delete());
}
for &anim in anims.iter() {
if let Anim::SetCandidate { x: aix, y: aiy, value } = anim {
if ix == aix && iy == aiy && value as usize == i + 1 {
let tween: Tween = Tween::value()
.xy(x, y)
.xyf(ax, ay)
.scale(CELL_SIZE / CANDIDATE_SIZE)
.scalef(1.0)
.into();
subcommand.insert(tween);
}
}
}
} else {
for &anim in anims.iter() {
if let Anim::UnsetCandidate { x: aix, y: aiy, value } = anim {
if ix == aix && iy == aiy && value as usize == i + 1 {
let color = colors.get(value as usize);
let mut rng = rand::rng();
let rays = rng.next_u32() as usize;
let rays = rays % 10 + 3;
let rotation: f32 = rng.random();
let rotation = rotation * TAU / (rays as f32);
for anglei in 0..rays {
let angle = rotation + TAU * anglei as f32 / (rays as f32);
let xf = 100.0 * angle.cos() + ax;
let yf = 100.0 * angle.sin() + ay;
let tween: Tween = Tween::value()
.xy(ax, ay)
.scale(1.0)
.scalef(0.0)
.xyf(xf, yf)
.remove_entity(true)
.into();
commands.spawn((
BoardCell,
Gameplay,
Mesh2d(shape.clone_weak()),
MeshMaterial2d(color.clone_weak()),
Transform::from_xyz(ax, ay, 2.0),
tween,
));
}
}
}
}
}
}
if self.0 == 0 {
let shape = &shapes.rect;
commands.spawn((
BoardCell,
Gameplay,
ErrorCell,
Mesh2d(shape.clone_weak()),
MeshMaterial2d(colors.get(1).clone_weak()),
Transform::from_xyz(x, y, 1.0),
));
}
if let Some(Anim::Unset { value, .. }) = anims.first() {
let xf = x + (((value - 1) % 3) as f32 - 1.0) * CANDIDATE_SIZE;
let yf = y + (((value - 1) / 3) as f32 - 1.0) * CANDIDATE_SIZE;
let scalef = CANDIDATE_SIZE / CELL_SIZE;
let tween: Tween = Tween::value()
.xy(x, y)
.scale(1.0)
.scalef(scalef)
.xyf(xf, yf)
.remove_entity(true)
.into();
commands.spawn((
BoardCell,
Gameplay,
Mesh2d(shapes.cell.clone_weak()),
MeshMaterial2d(colors.get(*value as usize).clone_weak()),
Transform {
translation: Vec3::new(x, y, 1.0),
scale: Vec3::ONE,
..default()
},
tween,
));
}
}
}
impl From<&Cell> for u16 {
fn from(cell: &Cell) -> Self {
cell.0
}
}