|
|
|
@ -28,8 +28,11 @@ impl Plugin for GamePlugin {
|
|
|
|
show_valid_moves.run_if(any_component_added::<Selected>),
|
|
|
|
show_valid_moves.run_if(any_component_added::<Selected>),
|
|
|
|
hide_valid_moves.run_if(any_component_removed::<Selected>()),
|
|
|
|
hide_valid_moves.run_if(any_component_removed::<Selected>()),
|
|
|
|
manage_score.run_if(any_component_added::<Captured>),
|
|
|
|
manage_score.run_if(any_component_added::<Captured>),
|
|
|
|
|
|
|
|
check_endgame.run_if(resource_changed::<Board>()),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.add_systems(OnEnter(GameState::Endgame), set_endgame)
|
|
|
|
|
|
|
|
.add_systems(OnExit(GameState::Endgame), clear_endgame)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
PreUpdate,
|
|
|
|
PreUpdate,
|
|
|
|
asserts::<display3d::Display3d>
|
|
|
|
asserts::<display3d::Display3d>
|
|
|
|
@ -151,15 +154,15 @@ impl Score {
|
|
|
|
/// The board is setup like this:
|
|
|
|
/// The board is setup like this:
|
|
|
|
/// ```text
|
|
|
|
/// ```text
|
|
|
|
/// 0 1 2 3 4 5 6 7
|
|
|
|
/// 0 1 2 3 4 5 6 7
|
|
|
|
/// +--+--+--+--+--+--+--+--+
|
|
|
|
/// +--+--+--+-----+--+--+--+
|
|
|
|
/// a | | | | I | d| Q| Q|
|
|
|
|
/// a | | | | l | d| Q| Q|
|
|
|
|
/// +--+--+--+--+--+--+--+--+
|
|
|
|
/// +--+--+--+--l--+--+--+--+
|
|
|
|
/// b |d |p |p | I | p| d| Q|
|
|
|
|
/// b |d |p |p | l | p| d| Q|
|
|
|
|
/// +--+--+--+--+--+--+--+--+
|
|
|
|
/// +--+--+--+--l--+--+--+--+
|
|
|
|
/// c |Q |d |p | I | p| p| d|
|
|
|
|
/// c |Q |d |p | l | p| p| d|
|
|
|
|
/// +--+--+--+--+--+--+--+--+
|
|
|
|
/// +--+--+--+--l--+--+--+--+
|
|
|
|
/// d |Q |Q |d | I | | | |
|
|
|
|
/// d |Q |Q |d | l | | | |
|
|
|
|
/// +--+--+--+--+--+--+--+--+
|
|
|
|
/// +--+--+--+-----+--+--+--+
|
|
|
|
/// ````
|
|
|
|
/// ````
|
|
|
|
#[derive(Debug, Resource)]
|
|
|
|
#[derive(Debug, Resource)]
|
|
|
|
pub(crate) struct Board {
|
|
|
|
pub(crate) struct Board {
|
|
|
|
@ -203,6 +206,11 @@ pub(crate) struct BoardIndex {
|
|
|
|
pub y: usize,
|
|
|
|
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)]
|
|
|
|
#[derive(Debug, Component, PartialEq, Clone, Copy)]
|
|
|
|
pub(crate) enum Side {
|
|
|
|
pub(crate) enum Side {
|
|
|
|
A,
|
|
|
|
A,
|
|
|
|
@ -226,6 +234,31 @@ impl Board {
|
|
|
|
self.inner[y][x].as_ref()
|
|
|
|
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
|
|
|
|
/// Returns a list of all pieces on the board with their location
|
|
|
|
pub(crate) fn pieces(&self) -> Vec<(BoardIndex, Piece)> {
|
|
|
|
pub(crate) fn pieces(&self) -> Vec<(BoardIndex, Piece)> {
|
|
|
|
self.inner
|
|
|
|
self.inner
|
|
|
|
@ -299,7 +332,7 @@ impl Board {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the possible moves the piece at this tile can make.
|
|
|
|
/// 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<BoardIndex> {
|
|
|
|
pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> {
|
|
|
|
let BoardIndex { x, y } = current_board_index;
|
|
|
|
let BoardIndex { x, y } = current_board_index;
|
|
|
|
|
|
|
|
|
|
|
|
@ -346,12 +379,8 @@ impl Board {
|
|
|
|
false
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
if !rejection {
|
|
|
|
// If all tests pass, this is a valid move
|
|
|
|
// If all tests pass, this is a valid move
|
|
|
|
(!rejection).then_some(this_board_index)
|
|
|
|
Some(this_board_index)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -449,8 +478,8 @@ fn setup_board(mut commands: Commands) {
|
|
|
|
inner: vec![
|
|
|
|
inner: vec![
|
|
|
|
vec![
|
|
|
|
vec![
|
|
|
|
Some(Queen),
|
|
|
|
Some(Queen),
|
|
|
|
Some(Queen),
|
|
|
|
None, // Some(Queen),
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
@ -458,24 +487,24 @@ fn setup_board(mut commands: Commands) {
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
],
|
|
|
|
],
|
|
|
|
vec![
|
|
|
|
vec![
|
|
|
|
Some(Queen),
|
|
|
|
None, // Some(Queen),
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
None,
|
|
|
|
None, // None,
|
|
|
|
None,
|
|
|
|
None, // None,
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
vec![
|
|
|
|
vec![
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
None,
|
|
|
|
None, // None,
|
|
|
|
None,
|
|
|
|
None, // None,
|
|
|
|
Some(Pawn),
|
|
|
|
None, // Some(Pawn),
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
Some(Queen),
|
|
|
|
None, // Some(Queen),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
vec![
|
|
|
|
vec![
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
@ -483,8 +512,8 @@ fn setup_board(mut commands: Commands) {
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
Some(Drone),
|
|
|
|
None, // Some(Drone),
|
|
|
|
Some(Queen),
|
|
|
|
None, // Some(Queen),
|
|
|
|
Some(Queen),
|
|
|
|
Some(Queen),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
@ -542,7 +571,91 @@ pub(crate) fn update_board(
|
|
|
|
*played = false;
|
|
|
|
*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<Piece>, Changed<BoardIndex>)>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
todo!()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
|
|
|
struct Endgame;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn check_endgame(
|
|
|
|
|
|
|
|
board: Res<Board>,
|
|
|
|
|
|
|
|
mut next_state: ResMut<NextState<GameState>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
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<Score>,
|
|
|
|
|
|
|
|
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<Entity, With<Endgame>>,
|
|
|
|
|
|
|
|
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(
|
|
|
|
fn manage_score(
|
|
|
|
events: Query<&Side, (Added<Captured>, With<display3d::Display3d>)>,
|
|
|
|
events: Query<&Side, (Added<Captured>, With<display3d::Display3d>)>,
|
|
|
|
mut debug_info: ResMut<debug::DebugInfo>,
|
|
|
|
mut debug_info: ResMut<debug::DebugInfo>,
|
|
|
|
@ -579,13 +692,14 @@ fn handle_selection(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut audio_event: EventWriter<AudioEvent>,
|
|
|
|
mut audio_event: EventWriter<AudioEvent>,
|
|
|
|
mut done: Local<bool>, // Tracks if moves/audio submitted already even if multiple pieces (2d/3d) are moved.
|
|
|
|
mut done: Local<bool>, // Tracks if moves/audio submitted already even if multiple pieces (2d/3d) are moved.
|
|
|
|
mut latest: Local<BoardIndex>, // Tracks the last one worked on
|
|
|
|
mut latest: Local<Option<BoardIndex>>, // Tracks the last one worked on
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
selections.read().for_each(|Selection(index)| {
|
|
|
|
selections.read().for_each(|Selection(index)| {
|
|
|
|
|
|
|
|
|
|
|
|
// Skip indexes already processed
|
|
|
|
// Skip indexes already processed
|
|
|
|
if *index != *latest {
|
|
|
|
if Some(*index) != *latest {
|
|
|
|
// Set the latest index to the current index
|
|
|
|
// Set the latest index to the current index
|
|
|
|
*latest = *index;
|
|
|
|
*latest = Some(*index);
|
|
|
|
// Reset the "done" marker
|
|
|
|
// Reset the "done" marker
|
|
|
|
*done = false;
|
|
|
|
*done = false;
|
|
|
|
|
|
|
|
|
|
|
|
@ -635,7 +749,7 @@ fn handle_selection(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
*done = false;
|
|
|
|
*done = false;
|
|
|
|
*latest = BoardIndex::default();
|
|
|
|
*latest = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Triggered when right-mouse-button clicked
|
|
|
|
/// Triggered when right-mouse-button clicked
|
|
|
|
|