use crate::prelude::*; pub(crate) struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_event::() .init_resource::() .add_systems(Startup, setup_board) .add_systems( PostUpdate, ( debug_hovering .run_if(resource_exists::()) .run_if(resource_changed::()), debug_board.run_if(resource_exists::()), ), ); } } #[derive(Debug, Component, Clone)] pub(crate) enum Piece { Pawn, Drone, Queen, } // manually for the type. impl std::fmt::Display for Piece { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Piece::Queen => write!(f, "@"), Piece::Drone => write!(f, "^"), Piece::Pawn => write!(f, "*"), } } } #[derive(Debug)] pub(crate) enum GameError { NullMove, InvalidMove, } #[derive(Debug, Event)] pub(crate) enum GameEvent { SelectPiece, PlacePiece, } /// The board is setup like this: /// ```text /// 0 1 2 3 4 5 6 7 /// +--+--+--+--+--+--+--+--+ /// a | | | | I | d| Q| Q| /// +--+--+--+--+--+--+--+--+ /// b |d |p |p | I | p| d| Q| /// +--+--+--+--+--+--+--+--+ /// c |Q |d |p | I | p| p| d| /// +--+--+--+--+--+--+--+--+ /// d |Q |Q |d | I | | | | /// +--+--+--+--+--+--+--+--+ /// ```` #[derive(Debug, Resource)] pub(crate) struct Board { inner: Vec>>, } #[derive(Debug, Component, PartialEq, Clone)] pub(crate) struct BoardIndex { pub x: usize, pub y: usize, } impl Board { /// Returns the piece at the given location pub(crate) fn at(&self, BoardIndex { x, y }: &BoardIndex) -> Option { self.inner[*y][*x].clone() } /// Returns a list of all pieces on the board with their location pub(crate) fn pieces(&self) -> Vec<(BoardIndex, Piece)> { self.inner .iter() .enumerate() .flat_map(|(y, nested)| { nested.iter().enumerate().filter_map(move |(x, p)| { p.as_ref().map(|val| (BoardIndex { x, y }, val.clone())) }) }) .collect() } pub(crate) fn move_piece( &mut self, from: &BoardIndex, to: &BoardIndex, ) -> Result<(), GameError> { if from == to { Err(GameError::NullMove) } else if self.at(to).is_none() { self.at(from).map_or(Err(GameError::NullMove), |from_val| { // TODO: We can self.inner.swap(to, from) if board is single vec self.inner[to.y][to.x] = Some(from_val); self.inner[from.y][from.x] = None; Ok(()) }) } else { Err(GameError::InvalidMove) } } } impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.inner.iter().rev().for_each(|row| { let _ = write!(f, "+--+--+--+--+--+--+--+--+\n"); let _ = write!(f, "|"); row.iter().for_each(|piece| { let _ = match piece { Some(p) => write!(f, "{} |", p), None => write!(f, " |"), }; }); let _ = write!(f, "\n"); }); let _ = write!(f, "+--+--+--+--+--+--+--+--+"); Ok(()) } } #[derive(Debug, Default, Resource)] pub(crate) struct ActiveTile { pub idx: Option, } #[derive(Debug, Default, Component)] pub(crate) struct Selected; fn setup_board(mut commands: Commands) { use Piece::*; commands.insert_resource(Board { inner: vec![ vec![ Some(Queen), Some(Queen), Some(Drone), None, None, None, None, None, ], vec![ Some(Queen), Some(Drone), Some(Pawn), None, None, Some(Pawn), Some(Pawn), Some(Drone), ], vec![ Some(Drone), Some(Pawn), Some(Pawn), None, None, Some(Pawn), Some(Drone), Some(Queen), ], vec![ None, None, None, None, None, Some(Drone), Some(Queen), Some(Queen), ], ], }); } /// TODO: only run_if debug enabled fn debug_hovering( selected: Res, board: Res, mut debug_info: ResMut, ) { match &selected.idx { Some(idx) => debug_info.set( "hovering".into(), format!("{:?}@({},{})", board.at(&idx), idx.x, idx.y,), ), None => debug_info.set("selected".into(), format!("N/A")), }; } fn debug_board(board: Res, mut debug_info: ResMut) { debug_info.set("board".into(), format!("\n{}", *board)); }