From 18f37973405296dde080d1c8491382c0fdbc8cf4 Mon Sep 17 00:00:00 2001 From: "Elijah C. Voigt" Date: Sat, 3 Feb 2024 22:27:37 -0800 Subject: [PATCH] Adding endgame tracking --- src/display3d.rs | 1 + src/game.rs | 196 +++++++++++++++++++++++++++++++++++++---------- src/main.rs | 1 + 3 files changed, 157 insertions(+), 41 deletions(-) diff --git a/src/display3d.rs b/src/display3d.rs index fbecc29..891c20a 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -795,6 +795,7 @@ fn select( }) .iter() .for_each(|&board_index| { + info!("Board index selected: {:?}", board_index); selections .send(game::Selection(board_index.clone())); }); diff --git a/src/game.rs b/src/game.rs index 2d47957..34c55c6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -28,8 +28,11 @@ impl Plugin for GamePlugin { show_valid_moves.run_if(any_component_added::), hide_valid_moves.run_if(any_component_removed::()), manage_score.run_if(any_component_added::), + check_endgame.run_if(resource_changed::()), ), ) + .add_systems(OnEnter(GameState::Endgame), set_endgame) + .add_systems(OnExit(GameState::Endgame), clear_endgame) .add_systems( PreUpdate, asserts:: @@ -151,15 +154,15 @@ impl Score { /// 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 | | | | -/// +--+--+--+--+--+--+--+--+ +/// +--+--+--+-----+--+--+--+ +/// a | | | | l | d| Q| Q| +/// +--+--+--+--l--+--+--+--+ +/// b |d |p |p | l | p| d| Q| +/// +--+--+--+--l--+--+--+--+ +/// c |Q |d |p | l | p| p| d| +/// +--+--+--+--l--+--+--+--+ +/// d |Q |Q |d | l | | | | +/// +--+--+--+-----+--+--+--+ /// ```` #[derive(Debug, Resource)] pub(crate) struct Board { @@ -203,6 +206,11 @@ pub(crate) struct BoardIndex { pub y: usize, } +#[derive(Debug, Component, PartialEq, Clone, Default, Copy, Eq, Hash)] +pub(crate) struct Previous { + board_index: BoardIndex +} + #[derive(Debug, Component, PartialEq, Clone, Copy)] pub(crate) enum Side { A, @@ -226,6 +234,31 @@ impl Board { self.inner[y][x].as_ref() } + /// Show all pieces on one side of the board + /// OPTIMIZE: This is only used to tell if a side is empty, so it is more work than we need to do. + pub(crate) fn on(&self, side: Side) -> Vec<(&Piece, BoardIndex)> { + match side { + Side::A => { + // X: 0..3, Y: 0..3 + (0..=3).flat_map(|x| { + (0..=3).map(move |y| { + self.at(BoardIndex { x, y }).map(|p| (p, BoardIndex { x, y })) + }) + }).filter_map(|r| r) + .collect() + } + Side::B => { + // X: 4..7, Y: 0..3 + (4..=7).flat_map(|x| { + (0..=3).map(move |y| { + self.at(BoardIndex { x, y }).map(|p| (p, BoardIndex { x, y })) + }) + }).filter_map(|r| r) + .collect() + } + } + } + /// Returns a list of all pieces on the board with their location pub(crate) fn pieces(&self) -> Vec<(BoardIndex, Piece)> { self.inner @@ -299,7 +332,7 @@ impl Board { } /// Returns the possible moves the piece at this tile can make. - /// !!TODO: exclude pieces on your own side!! + /// TODO: Implement "no jumping" over pieces pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet { let BoardIndex { x, y } = current_board_index; @@ -346,12 +379,8 @@ impl Board { false } }; - if !rejection { - // If all tests pass, this is a valid move - Some(this_board_index) - } else { - None - } + // If all tests pass, this is a valid move + (!rejection).then_some(this_board_index) } else { None } @@ -449,8 +478,8 @@ fn setup_board(mut commands: Commands) { inner: vec![ vec![ Some(Queen), - Some(Queen), - Some(Drone), + None, // Some(Queen), + None, // Some(Drone), None, None, None, @@ -458,24 +487,24 @@ fn setup_board(mut commands: Commands) { None, ], vec![ - Some(Queen), - Some(Drone), - Some(Pawn), - None, - None, - Some(Pawn), - Some(Pawn), - Some(Drone), + None, // Some(Queen), + None, // Some(Drone), + None, // Some(Pawn), + None, // None, + None, // None, + None, // Some(Pawn), + None, // Some(Pawn), + None, // Some(Drone), ], vec![ - Some(Drone), - Some(Pawn), - Some(Pawn), - None, - None, - Some(Pawn), - Some(Drone), - Some(Queen), + None, // Some(Drone), + None, // Some(Pawn), + None, // Some(Pawn), + None, // None, + None, // None, + None, // Some(Pawn), + None, // Some(Drone), + None, // Some(Queen), ], vec![ None, @@ -483,8 +512,8 @@ fn setup_board(mut commands: Commands) { None, None, None, - Some(Drone), - Some(Queen), + None, // Some(Drone), + None, // Some(Queen), Some(Queen), ], ], @@ -542,7 +571,91 @@ pub(crate) fn update_board( *played = false; } -// We only track 3D (hack, for now) to prevent duplicates +// Track the last spot that a piece was at +fn track_previous_move( + events: Query<&BoardIndex, (With, Changed)>, + mut commands: Commands, +) { + todo!() +} + +#[derive(Debug, Component)] +struct Endgame; + +fn check_endgame( + board: Res, + mut next_state: ResMut>, +) { + if board.on(Side::A).is_empty() || board.on(Side::B).is_empty() { + warn!("The game is over!"); + next_state.set(GameState::Endgame); + } +} + +fn set_endgame( + score: Res, + mut commands: Commands, +) { + commands + .spawn(( + Endgame, + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + flex_direction: FlexDirection::Column, + position_type: PositionType::Absolute, + ..default() + }, + background_color: Color::NONE.into(), + visibility: Visibility::Inherited, + ..default() + }, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "S C O R E", + TextStyle { + font_size: 48.0, + color: Color::ORANGE_RED, + ..default() + }, + )); + parent.spawn(TextBundle::from_section( + format!("BLUE {}", score.b), + TextStyle { + font_size: 32.0, + color: Color::BLUE, + ..default() + }, + )); + parent.spawn(TextBundle::from_section( + format!("RED {}", score.a), + TextStyle { + font_size: 32.0, + color: Color::RED, + ..default() + }, + )); + }); +} + +fn clear_endgame( + query: Query>, + mut commands: Commands, +) { + query.iter().for_each(|e| { + commands.entity(e).despawn_recursive(); + }) +} + +/// We only track 3D (hack, for now) to prevent duplicates +/// TODO: We can calculate this declaratively: +/// * All pieces without a BoardIndex are "captured" +/// * All captured pieces have their captured side preserved +/// We can iterate over these pieces and calculate the score on the fly fn manage_score( events: Query<&Side, (Added, With)>, mut debug_info: ResMut, @@ -579,13 +692,14 @@ fn handle_selection( mut commands: Commands, mut audio_event: EventWriter, mut done: Local, // Tracks if moves/audio submitted already even if multiple pieces (2d/3d) are moved. - mut latest: Local, // Tracks the last one worked on + mut latest: Local>, // Tracks the last one worked on ) { selections.read().for_each(|Selection(index)| { + // Skip indexes already processed - if *index != *latest { + if Some(*index) != *latest { // Set the latest index to the current index - *latest = *index; + *latest = Some(*index); // Reset the "done" marker *done = false; @@ -635,7 +749,7 @@ fn handle_selection( } }); *done = false; - *latest = BoardIndex::default(); + *latest = None; } /// Triggered when right-mouse-button clicked diff --git a/src/main.rs b/src/main.rs index 568a04b..1dc3d59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,6 +74,7 @@ pub enum GameState { Menu, Credits, Play, + Endgame, } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States, Component)]