You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 lines
8.6 KiB
Rust

use bevy::prelude::*;
use crate::{deck::Card, menu::UiMessage, GameState};
pub struct PlayPlugin;
impl Plugin for PlayPlugin {
fn build(&self, app: &mut App) {
app.add_event::<ServeCards>()
.add_observer(serve_cards)
.add_systems(OnEnter(GameState::Main), deal_cards)
.add_systems(Update, deal_cards.run_if(set_added))
.add_systems(Update, spin_cards);
}
}
/// Marker for cards which the player has selected
#[derive(Component)]
pub(crate) struct Selected;
/// Where in the deck a card is
#[derive(Component)]
pub(crate) struct DeckOrder(pub u8);
/// Where on the board/table/play-space a card is
#[derive(Component)]
pub(crate) struct PlayLocation {
pub x: u8,
pub y: u8,
}
/// Which captured set a card is associated with
#[derive(Component)]
pub(crate) struct SetNumber(pub u8);
/// Debug system rotating selected cards for visual flare
fn spin_cards(mut query: Query<&mut Transform, (With<Card>, With<Selected>)>, time: Res<Time>) {
let dt = time.delta().as_secs_f32();
query.iter_mut().for_each(|mut t| {
t.rotate_z(dt * 0.5);
});
}
/// Observer system for adding/removing selected components when a card is clicked
pub(crate) fn toggle_selected(
trigger: Trigger<Pointer<Click>>,
mut commands: Commands,
query: Query<&Selected>,
) {
commands.trigger(UiMessage("".into()));
let e = trigger.entity();
if query.contains(e) {
commands.entity(e).remove::<Selected>();
} else {
commands.entity(e).insert(Selected);
}
}
/// Observer system for resetting rotation when a card is de-selected
pub(crate) fn reset_rotation(
trigger: Trigger<OnRemove, Selected>,
mut transforms: Query<&mut Transform>,
) {
let mut t = transforms.get_mut(trigger.entity()).unwrap();
t.rotation = Quat::default();
}
/// Check a set when the "Set" button is clicked
pub(crate) fn check_set(
_trigger: Trigger<Pointer<Click>>,
mut query: Query<(Entity, &Card, &mut Visibility, &mut Transform), With<Selected>>,
sets: Query<&SetNumber>,
mut commands: Commands,
) {
let mut cards = query.iter();
if cards.len() == 3 {
let ((_, a, _, _), (_, b, _, _), (_, c, _, _)) = (
cards.next().unwrap(),
cards.next().unwrap(),
cards.next().unwrap(),
);
if is_set((a, b, c)) {
query
.iter_mut()
.for_each(|(entity, _, mut visibility, mut transform)| {
*visibility = Visibility::Hidden;
*transform = Transform::default();
let set_number = sets.iter().len() as u8 / 3 + 1;
commands
.entity(entity)
.remove::<PlayLocation>()
.remove::<Selected>()
.insert(SetNumber(set_number));
});
commands.trigger(UiMessage("Yipee!".into()));
}
} else if cards.len() > 3 {
commands.trigger(UiMessage("Too many cards!".into()));
} else if cards.len() < 3 {
commands.trigger(UiMessage("Not enough cards!".into()));
}
}
/// Helper function to determine if three cards constitute a set
fn is_set((a, b, c): (&Card, &Card, &Card)) -> bool {
let color = {
((a.color == b.color) && (b.color == c.color) && (c.color == a.color))
|| ((a.color != b.color) && (b.color != c.color) && (c.color != a.color))
};
let number = {
((a.number == b.number) && (b.number == c.number) && (c.number == a.number))
|| ((a.number != b.number) && (b.number != c.number) && (c.number != a.number))
};
let pattern = {
((a.pattern == b.pattern) && (b.pattern == c.pattern) && (c.pattern == a.pattern))
|| ((a.pattern != b.pattern) && (b.pattern != c.pattern) && (c.pattern != a.pattern))
};
let shape = {
((a.shape == b.shape) && (b.shape == c.shape) && (c.shape == a.shape))
|| ((a.shape != b.shape) && (b.shape != c.shape) && (c.shape != a.shape))
};
color && number && pattern && shape
}
#[derive(Event, Clone)]
struct ServeCards;
/// When requested, fill in empty spots on the board with cards
fn serve_cards(
_trigger: Trigger<ServeCards>,
in_deck: Query<(Entity, &Card, &DeckOrder)>,
spots: Query<&PlayLocation>,
mut commands: Commands,
) {
info!(
"Serving cards from deck ({} cards left)",
in_deck.iter().len()
);
let mut n = in_deck.iter().len().saturating_sub(1);
// Iterate over every x, y play location
for this_x in 0..=3 {
for this_y in 0..=3 {
// If this spot does not have a card
if spots
.iter()
.find(|PlayLocation { x, y }| *x == (this_x as u8) && *y == (this_y as u8))
.is_none()
{
// If we have more than 9 cards on the board
let candidate = in_deck
.iter()
.find(|(_entity, _deck_card, order)| order.0 == n as u8);
n = n.saturating_sub(1);
// If that search was fruitful, pull the card from the deck and play it
if let Some((e, _, _)) = candidate {
commands
.entity(e)
.remove::<DeckOrder>()
.insert(PlayLocation {
x: this_x,
y: this_y,
});
}
}
}
}
}
/// Trigger dealing cards when the game starts
fn deal_cards(mut commands: Commands) {
commands.trigger(ServeCards);
}
fn set_added(query: Query<Entity, Added<SetNumber>>) -> bool {
!query.is_empty()
}
/// When a card is added to the board, place it on the screen
pub(crate) fn place_card(
trigger: Trigger<OnAdd, PlayLocation>,
mut query: Query<(&PlayLocation, &mut Visibility, &mut Transform)>,
) {
let (PlayLocation { x, y }, mut visibility, mut transform) =
query.get_mut(trigger.entity()).unwrap();
// Set it's transform based on it's placement
let card_size = [100.0, 160.0];
let offset = Vec2::new(card_size[0] * 4.0, card_size[1] * 4.0) / 2.5;
transform.translation = Vec3::new(
((*x as f32) * card_size[0]) - offset.x,
((*y as f32) * card_size[1]) - offset.y,
0.0,
);
// Set it to visible
*visibility = Visibility::Inherited;
}
pub(crate) fn check_for_sets(
_trigger: Trigger<Pointer<Click>>,
cards: Query<(Entity, &Card), With<PlayLocation>>,
selected: Query<Entity, With<Selected>>,
mut commands: Commands,
) {
let candidate = cards
// Iterate over all combinations of cards on the board
.iter_combinations()
// Only find combinations including currently selected cards
.filter(|[(ea, _ca), (eb, _cb), (ec, _cc)]| {
// If selected is empty, proceed
match selected.iter().len() {
0 => true,
1..=3 => {
let a = selected.contains(*ea);
let b = selected.contains(*eb);
let c = selected.contains(*ec);
match selected.iter().len() {
1 => a || b || c,
2 => a && b || b && c || a && c,
3 => a && b && c,
_ => panic!("WTF?"),
}
}
_ => false,
}
})
// return the first valid set
.find_map(|[(ea, ca), (eb, cb), (ec, cc)]| {
if is_set((ca, cb, cc)) {
info!("\n\t{}\n\t{}\n\t{}", ca, cb, cc);
Some((ea, eb, ec))
} else {
None
}
});
if let Some((a, b, c)) = candidate {
// Message for adding
match selected.iter().len() {
0 => commands.trigger(UiMessage("First one's free!".into())),
1 => commands.trigger(UiMessage("This one is tricky!".into())),
2 => commands.trigger(UiMessage("Threes company!".into())),
_ => commands.trigger(UiMessage("I don't feel so good...".into())),
};
// Add the selected component to the next entity
if !selected.contains(a) {
commands.entity(a).insert(Selected);
} else if !selected.contains(b) {
commands.entity(b).insert(Selected);
} else if !selected.contains(c) {
commands.entity(c).insert(Selected);
} else {
commands.trigger(UiMessage("Lock it in!".into()));
}
} else {
commands.trigger(UiMessage("I'm stumped!".into()));
}
}