gridit 0.1.0

2D grid library utilizing the fun of iterators
Documentation
use ggez::graphics::{
    BlendMode, Color, DrawMode, DrawParam, Drawable, FillOptions, Mesh, Rect, StrokeOptions,
};
use ggez::mint::Point2;
use ggez::{graphics, Context, GameResult};

use gridit::{Grid, Position, PositionsEnumerator};

use crate::piece::Piece;

pub const WHITE: Color = Color::new(210. / 255., 171. / 255., 111. / 255., 1.0);
pub const BLACK: Color = Color::new(155. / 255., 111. / 255., 51. / 255., 1.0);
pub const SELECT: Color = Color::new(34. / 255., 201. / 255., 220. / 255., 1.0);
pub const HOVER: Color = Color::new(0.5, 0.5, 0.5, 0.7);

pub type BoardPiece = Option<Box<dyn Piece>>;

pub struct Board {
    pub grid: Grid<BoardPiece>,
    rect: Rect,
    selected_field: Option<Position>,
    hover_field: Option<Position>,
}

impl Board {
    pub fn new(grid: Grid<BoardPiece>, xy: (f32, f32), size: f32) -> Self {
        let rect = Rect::new(xy.0, xy.1, size, size);
        Self {
            grid,
            rect,
            selected_field: None,
            hover_field: None,
        }
    }

    pub fn contains_point(&self, point: Point2<f32>) -> bool {
        self.rect.contains(point)
    }

    pub fn set_rect(&mut self, rect: Rect) {
        self.rect = rect;
    }

    fn get_grid_position(&self, point: Point2<f32>) -> gridit::Position {
        let rect = self.rect;
        let bp = rect.point();
        let field_size = rect.w / 8.0;
        let point = Point2::from([point.x - bp.x, point.y - bp.y]);
        let px = (point.x / field_size) as usize;
        let py = (point.y / field_size) as usize;
        (px, py).into()
    }

    pub fn select_field(&mut self, point: Point2<f32>) {
        let clicked_pos = self.get_grid_position(point);
        let piece = self.grid.get_unchecked(clicked_pos);
        match (&piece, self.selected_field) {
            (Some(_piece), None) => {
                self.selected_field = Some(clicked_pos);
            }
            (_, Some(pos)) => {
                let selected_piece = self.grid.get_unchecked(pos);
                if let Some(piece) = selected_piece {
                    let pmoves = piece.possible_moves(&self.grid, pos);
                    if pmoves.contains(&clicked_pos) {
                        self.grid.move_to(pos, clicked_pos);
                    }
                    self.selected_field = None;
                }
            }
            (_, _) => (),
        }
    }

    pub fn unselect_field(&mut self) {
        self.selected_field = None;
    }

    pub fn hover_field(&mut self, point: Point2<f32>) {
        let clicked_pos = self.get_grid_position(point);
        if matches!(self.selected_field, Some(pos) if pos == clicked_pos) {
            self.hover_field = None
        } else {
            self.hover_field = Some(clicked_pos);
        }
    }

    pub fn unhover(&mut self) {
        self.hover_field = None;
    }

    pub fn set_field(&mut self, point: Point2<f32>, piece: Box<dyn Piece>) {
        let clicked_pos = self.get_grid_position(point);
        self.grid.set_unchecked(clicked_pos, Some(piece));
    }
}

impl Drawable for Board {
    fn draw(&self, ctx: &mut Context, _: DrawParam) -> GameResult<()> {
        let (bx, by) = (self.rect.x, self.rect.y);
        let rect_size = self.rect.w / 8.0;

        for (position, piece) in self.grid.iter().grid_positions() {
            let (x, y) = position.into();
            let bg_color = match (x + y) % 2 == 0 {
                true => WHITE,
                false => BLACK,
            };
            let (fx, fy) = (x as f32, y as f32);
            let rect_x = fx * rect_size + bx;
            let rect_y = fy * rect_size + by;
            let rect = Rect::new(rect_x, rect_y, rect_size, rect_size);
            let mrect =
                Mesh::new_rectangle(ctx, DrawMode::Fill(FillOptions::default()), rect, bg_color)?;
            graphics::draw(ctx, &mrect, DrawParam::default())?;

            if self.selected_field == Some(position) {
                let mrect =
                    Mesh::new_rectangle(ctx, DrawMode::Fill(FillOptions::default()), rect, SELECT)?;
                mrect.draw(ctx, DrawParam::default())?;
            }

            if self.hover_field == Some(position) {
                let mrect =
                    Mesh::new_rectangle(ctx, DrawMode::Fill(FillOptions::default()), rect, HOVER)?;
                mrect.draw(ctx, DrawParam::default())?;
            }

            if let Some(piece) = piece {
                let img = piece.image();
                let mut drect = rect;
                drect.scale(0.75, 0.75);
                let img_rect = img.dimensions();
                let sx = drect.w / img_rect.w;
                let sy = drect.h / img_rect.h;
                let iw = drect.w / 2.;
                let ih = drect.h / 2.;
                let rw = rect.w / 2.0;
                let rh = rect.h / 2.0;
                let dest = [rect.x + rw - iw, rect.y + rh - ih];
                img.draw(ctx, DrawParam::new().dest(dest).scale([sx, sy]))?;
            }
        }

        if let Some(select_position) = self.selected_field {
            let field = self.grid.get_unchecked(select_position);
            if let Some(piece) = field {
                let moves = piece.possible_moves(&self.grid, select_position);
                for mv in moves {
                    let (x, y) = mv.into();
                    let (fx, fy) = (x as f32, y as f32);
                    let cx = fx * rect_size + bx;
                    let cy = fy * rect_size + by;
                    let radius: f32 = rect_size / 10.;
                    let hs = rect_size / 2.;
                    let point: Point2<f32> = [cx + hs, cy + hs].into();

                    let cmesh = Mesh::new_circle(
                        ctx,
                        DrawMode::Fill(FillOptions::default()),
                        point,
                        radius,
                        1.,
                        SELECT,
                    )?;
                    cmesh.draw(ctx, DrawParam::default())?;
                }
            }
        }

        let mrect = Mesh::new_rectangle(
            ctx,
            DrawMode::Stroke(StrokeOptions::default()),
            self.rect,
            Color::BLACK,
        )?;
        graphics::draw(ctx, &mrect, DrawParam::default())?;

        Ok(())
    }

    fn dimensions(&self, _ctx: &mut Context) -> Option<Rect> {
        Some(self.rect)
    }

    fn set_blend_mode(&mut self, _mode: Option<BlendMode>) {}

    fn blend_mode(&self) -> Option<BlendMode> {
        None
    }
}