Files
checkers/src/ui/macroquad/game/board.rs

476 lines
13 KiB
Rust

use std::{
ops::{Index, IndexMut},
slice::IterMut,
};
use log::debug;
use macroquad::{
color::{BLACK, Color, GREEN, RED, WHITE},
input::{is_mouse_button_pressed, mouse_position},
math::{Rect, vec2},
shapes::{draw_circle, draw_circle_lines, draw_rectangle},
};
use uuid::Uuid;
use crate::logic::{
board::{
board_view::BoardView,
field::Field,
pawn::{CapturePath, MovePath},
},
math::position::Position,
};
const MOVE_HIGHLITGHT_COLOR: Color = GREEN;
const CAPTURE_HIGHLITGHT_COLOR: Color = RED;
#[derive(Default)]
struct GuiField {
rect: Rect,
field: Field,
pos: Position,
is_clickable: bool,
color: Color,
is_blac: bool,
}
#[derive(Default)]
struct GuiFieldVec {
gui_fields: Vec<GuiField>,
}
impl GuiFieldVec {
pub fn push(&mut self, input: GuiField) {
self.gui_fields.push(input);
}
pub fn clear(&mut self) {
self.gui_fields.clear();
}
pub fn size(&self) -> usize {
self.gui_fields.len()
}
}
impl<'a> IntoIterator for &'a GuiFieldVec {
type Item = &'a GuiField;
type IntoIter = std::slice::Iter<'a, GuiField>;
fn into_iter(self) -> Self::IntoIter {
self.gui_fields.iter()
}
}
impl<'a> IntoIterator for &'a mut GuiFieldVec {
type Item = &'a mut GuiField;
type IntoIter = IterMut<'a, GuiField>;
fn into_iter(self) -> Self::IntoIter {
self.gui_fields.iter_mut()
}
}
impl Index<Position> for GuiFieldVec {
type Output = GuiField;
fn index(&self, index: Position) -> &Self::Output {
self.gui_fields
.iter()
.find(|field| field.pos == index)
.unwrap()
}
}
impl IndexMut<Position> for GuiFieldVec {
fn index_mut(&mut self, index: Position) -> &mut Self::Output {
self.gui_fields
.iter_mut()
.find(|field| field.pos == index)
.unwrap()
}
}
#[derive(Default, Debug)]
enum BoardState {
#[default]
View,
ChoosePawn,
ChooseMove(Position),
ChooseCapture(Position),
}
pub struct Board {
rect: Rect,
board_view: BoardView,
fields: GuiFieldVec,
state: BoardState,
my_id: Uuid,
is_my_turn: bool,
available_captures: Option<Vec<CapturePath>>,
current_deepness: usize,
current_capture_path: Vec<Position>,
choosen_capture: Option<CapturePath>,
available_moves: Option<Vec<MovePath>>,
choosen_move: Option<MovePath>,
field_size: f32,
}
impl Default for Board {
fn default() -> Self {
Self::new(Rect::default(), Uuid::default())
}
}
impl Board {
pub fn new(rect: Rect, player_id: Uuid) -> Self {
Self {
rect,
board_view: BoardView::default(),
fields: GuiFieldVec::default(),
state: BoardState::default(),
my_id: player_id,
is_my_turn: false,
available_captures: None,
current_capture_path: vec![],
current_deepness: 0,
choosen_capture: None,
available_moves: None,
choosen_move: None,
field_size: 32.0,
}
}
pub fn update(&mut self) {
self.update_dimensions();
self.reset_color();
let clicked_field = self.get_clicked_field();
debug!("Current state: {:?}", self.state);
match self.state {
BoardState::View => {
if self.is_my_turn {
self.state = BoardState::ChoosePawn;
}
}
BoardState::ChoosePawn => {
if let Some(clicked_field) = clicked_field {
self.delegate_move(clicked_field.pos);
}
if self.is_back_move_wanted_by_player() {
self.state = BoardState::View;
}
self.highlight_current_moves();
}
BoardState::ChooseMove(from) => {
if self.is_back_move_wanted_by_player() {
self.state = BoardState::ChoosePawn;
}
self.highlight_move_paths(from);
if let Some(field) = &self.get_clicked_field() {
if let Some(available_moves) = &self.available_moves {
if available_moves.iter().any(|available_move| {
available_move.from == from
&& available_move.available_steps.contains(&field.pos)
}) {
self.choosen_move = Some(MovePath {
from,
available_steps: vec![field.pos],
});
self.state = BoardState::View;
}
}
}
}
BoardState::ChooseCapture(from) => {
if self.is_back_move_wanted_by_player() {
self.reset_capture();
return;
}
let Some(available_captures) = &self.available_captures else {
return;
};
let possible_moves: Vec<_> = available_captures
.iter()
.filter_map(|capture| {
if capture.from == from
&& capture.steps.starts_with(&self.current_capture_path)
{
Some(&capture.steps)
} else {
None
}
})
.collect();
let current_highlight: Vec<_> = possible_moves
.iter()
.filter_map(|possible_move| possible_move.get(self.current_deepness))
.copied()
.collect();
if let Some(clicked_field) = &self.get_clicked_field().map(|field| field.pos) {
if possible_moves.iter().any(|possible_move| {
if let Some(valid_move) = possible_move.get(self.current_deepness) {
valid_move == clicked_field
} else {
false
}
}) {
self.current_capture_path.push(*clicked_field);
self.current_deepness += 1;
}
}
if current_highlight.is_empty() {
debug!("Capture path complete!");
self.choosen_capture = available_captures
.iter()
.find(|capture| {
capture.from == from && capture.steps == self.current_capture_path
})
.cloned();
self.reset_capture();
}
self.highlight_capture_path(current_highlight);
}
}
}
fn reset_capture(&mut self) {
self.current_capture_path.clear();
self.current_deepness = 0;
self.state = BoardState::View;
}
fn delegate_move(&mut self, from: Position) {
if let Some(capture_path) = &self.available_captures
&& capture_path.iter().any(|path| path.from == from)
{
self.state = BoardState::ChooseCapture(from);
return;
}
if let Some(available_moves) = &self.available_moves
&& available_moves.iter().any(|path| path.from == from)
{
self.state = BoardState::ChooseMove(from);
return;
}
}
pub fn get_choosen_capture(&mut self) -> Option<CapturePath> {
self.choosen_capture.take()
}
pub fn get_choosen_move(&mut self) -> Option<MovePath> {
self.choosen_move.take()
}
pub fn update_board_view(&mut self, board_view: BoardView) {
self.board_view = board_view;
}
pub fn update_available_captures(&mut self, captures: Vec<CapturePath>) {
self.available_captures = Some(captures);
self.available_moves = None;
}
pub fn update_available_moves(&mut self, moves: Vec<MovePath>) {
self.state = BoardState::ChoosePawn;
self.available_moves = Some(moves);
self.available_captures = None;
}
pub fn update_board_rect(&mut self, rect: Rect) -> bool {
if self.rect != rect {
self.rect = rect;
return true;
}
false
}
/// Recreate fields vec with new dimensions. Should be used when new board_view arrives
pub fn repopulate(&mut self) {
self.fields.clear();
self.field_size = self.rect.w / self.board_view.size as f32;
for field in &self.board_view {
let pos: Position = field.position;
let is_row_even: bool = pos.row % 2 == 0;
let is_column_even: bool = pos.column % 2 == 0;
let is_field_black: bool = is_row_even ^ is_column_even;
let abs_x = self.rect.x + pos.column as f32 * self.field_size;
let abs_y = self.rect.y + pos.row as f32 * self.field_size;
let rect = Rect {
x: abs_x,
y: abs_y,
w: self.field_size,
h: self.field_size,
};
self.fields.push(GuiField {
rect,
field: field.clone(),
is_clickable: false,
pos,
color: if is_field_black { BLACK } else { WHITE },
is_blac: is_field_black,
});
}
}
/// Updates dimensions of graphical squares
pub fn update_dimensions(&mut self) {
self.field_size = self.rect.w / self.board_view.size as f32;
for field in &mut self.fields {
let pos: Position = field.pos;
let abs_x = self.rect.x + pos.column as f32 * self.field_size;
let abs_y = self.rect.y + pos.row as f32 * self.field_size;
let new_rect = Rect {
x: abs_x,
y: abs_y,
w: self.field_size,
h: self.field_size,
};
field.rect = new_rect;
}
}
fn reset_color(&mut self) {
for field in &mut self.fields {
field.color = if field.is_blac { BLACK } else { WHITE };
}
}
pub fn draw(&self) {
for field in &self.fields {
draw_rectangle(
field.rect.x,
field.rect.y,
field.rect.w,
field.rect.h,
field.color,
);
if field.is_clickable {
draw_rectangle(
field.rect.x,
field.rect.y,
field.rect.w,
field.rect.h,
GREEN,
);
}
if let Some(pawn) = &field.field.pawn {
let color = if pawn.owner == self.my_id { WHITE } else { RED }; // TODO! This should
// be customizable
let center = field.rect.center();
let r = field.rect.w * 0.5;
let thickness = 0.5;
draw_circle(center.x, center.y, r, color);
draw_circle_lines(center.x, center.y, r, thickness, BLACK);
}
}
}
fn get_clicked_field(&self) -> Option<&GuiField> {
let (mouse_x, mouse_y) = mouse_position();
let mouse_point = vec2(mouse_x, mouse_y);
if is_mouse_button_pressed(macroquad::input::MouseButton::Left) {
for field in &self.fields {
if field.rect.contains(mouse_point) {
return Some(field);
}
}
}
None
}
fn is_back_move_wanted_by_player(&self) -> bool {
is_mouse_button_pressed(macroquad::input::MouseButton::Right)
}
pub fn set_my_turn(&mut self, is_my_turn: bool) {
self.is_my_turn = is_my_turn;
}
fn highlight_current_moves(&mut self) {
if let Some(available_captures) = &self.available_captures {
for capture_path in available_captures {
self.fields[capture_path.from].color = MOVE_HIGHLITGHT_COLOR;
}
return;
}
if let Some(available_moves) = &self.available_moves {
for available_move in available_moves {
self.fields[available_move.from].color = MOVE_HIGHLITGHT_COLOR;
}
return;
};
}
fn highlight_move_paths(&mut self, from: Position) {
let Some(available_moves) = &self.available_moves else {
return;
};
for available_move in available_moves {
if available_move.from == from {
for step in &available_move.available_steps {
self.fields[*step].color = MOVE_HIGHLITGHT_COLOR;
}
}
}
}
fn highlight_capture_path(&mut self, fields: Vec<Position>) {
for field in fields {
self.fields[field].color = CAPTURE_HIGHLITGHT_COLOR;
}
}
}