diff --git a/Cargo.toml b/Cargo.toml index 9e3ce80..68d23bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shuftle-client-core" -version = "0.1.1" +version = "0.2.0" edition = "2024" authors = ["Sebastiano Giordano"] categories = ["game-development"] diff --git a/src/game_logic.rs b/src/game_logic.rs index bda439d..15726f1 100644 --- a/src/game_logic.rs +++ b/src/game_logic.rs @@ -10,7 +10,7 @@ use bevy::{ }; use shuftlib::{ core::{Suit, italian::ItalianRank}, - tressette::{Game, TressetteCard}, + tressette::{Game, MoveEffect, TressetteCard}, trick_taking::{PLAYERS, PlayerId}, }; use strum::IntoEnumIterator; @@ -64,12 +64,16 @@ impl Plugin for GameLogic { handle_restart_button, ), ) - .add_systems(Last, despawn_marked) + .add_systems(Last, despawn_marked.run_if(any_with_component::)) .init_resource::() .init_resource::() .init_resource::() .init_resource::() - .insert_resource(GameState(Game::new())); + .init_resource::() + .init_resource::() + .init_resource::() + .insert_resource(GameState(Game::new())) + .add_systems(Update, check_collection_timer); } } @@ -106,7 +110,10 @@ fn init_scene( // Spawn POV player (bottom) commands.spawn(( Name::new("Player 0"), - pov_player_transform(window), + Transform { + translation: player_position(window, 0), + ..default() + }, Player { id: PlayerId::PLAYER_0, cards_counter: 0, @@ -117,7 +124,10 @@ fn init_scene( // Spawn player 1 (right) commands.spawn(( Name::new("Player 1"), - player1_tranform(window), + Transform { + translation: player_position(window, 1), + ..default() + }, Player { id: PlayerId::PLAYER_1, cards_counter: 0, @@ -128,7 +138,10 @@ fn init_scene( // Spawn player 2 commands.spawn(( Name::new("Player 2"), - player2_transform(window), + Transform { + translation: player_position(window, 2), + ..default() + }, Player { id: PlayerId::PLAYER_2, cards_counter: 0, @@ -139,7 +152,10 @@ fn init_scene( // Spawn player 3 commands.spawn(( Name::new("Player 3"), - player3_transform(window), + Transform { + translation: player_position(window, 3), + ..default() + }, Player { id: PlayerId::PLAYER_3, cards_counter: 0, @@ -169,47 +185,29 @@ fn init_scene( commands.run_system(setup_game_sys.0); } -fn player3_transform(window: &Window) -> Transform { - Transform { - translation: Vec3 { - x: -window.width() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32, - y: 5. * CARD_WIDTH as f32 * 0.5, - ..default() - }, - ..default() - } -} - -fn player2_transform(window: &Window) -> Transform { - Transform { - translation: Vec3 { - x: -5. * CARD_WIDTH as f32 * 0.5, - y: window.height() * 0.5 - EDGE_MARGIN as f32 - CARD_HEIGHT as f32 * 0.5, - ..default() +fn player_position(window: &Window, player_id: usize) -> Vec3 { + match player_id { + 0 => Vec3 { + x: 0.0, + y: -window.height() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32 * 0.5, + z: 0.0, }, - ..default() - } -} - -fn player1_tranform(window: &Window) -> Transform { - Transform { - translation: Vec3 { + 1 => Vec3 { x: window.width() * 0.5 - EDGE_MARGIN as f32 - CARD_HEIGHT as f32 * 0.5, - y: CARD_WIDTH as f32 * 0.5 * 5., - ..default() + y: 0.0, + z: 0.0, }, - ..default() - } -} - -fn pov_player_transform(window: &Window) -> Transform { - Transform { - translation: Vec3 { + 2 => Vec3 { x: 0.0, - y: -window.height() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32 * 0.5, - ..default() + y: window.height() * 0.5 - EDGE_MARGIN as f32 - CARD_HEIGHT as f32 * 0.5, + z: 0.0, + }, + 3 => Vec3 { + x: -window.width() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32 * 0.5, + y: 0.0, + z: 0.0, }, - ..default() + _ => panic!("Invalid player id"), } } @@ -221,29 +219,7 @@ fn update_player_positions( let window = window.iter().next().unwrap(); for (mut transform, player) in players.iter_mut() { - match player.id.as_usize() { - 0 => { - transform.translation.x = 0.0; - transform.translation.y = - -window.height() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32 * 0.5; - } - 1 => { - transform.translation.x = - window.width() * 0.5 - EDGE_MARGIN as f32 - CARD_HEIGHT as f32 * 0.5; - transform.translation.y = 0.0; - } - 2 => { - transform.translation.x = 0.0; - transform.translation.y = - window.height() * 0.5 - EDGE_MARGIN as f32 - CARD_HEIGHT as f32 * 0.5; - } - 3 => { - transform.translation.x = - -window.width() * 0.5 + EDGE_MARGIN as f32 + CARD_HEIGHT as f32 * 0.5; - transform.translation.y = 0.0; - } - _ => panic!("This cannot happen"), - } + transform.translation = player_position(window, player.id.as_usize()); } } @@ -264,8 +240,6 @@ fn setup_game( mut query: Query<(Entity, &mut Player)>, non_pov_play_id: Res, ) { - info!("Setting up game"); - // Distribute cards from Game hands. let mut players: HashMap = HashMap::new(); for (entity, mut player) in query.iter_mut() { @@ -415,6 +389,7 @@ fn distribute_to_other( commands.entity(entity).add_children(&cards_ids); } const CARD_SPEED: f32 = 1000.0; +const COLLECTION_DELAY: f32 = 2.; /// This is called when the POV player clicks on one of their cards. fn select_play_card( @@ -424,7 +399,6 @@ fn select_play_card( (Entity, &mut Transform, &Card), (With, With, With), >, - handle_effect_id: Res, mut unselected_card_query: Query<(&mut Transform, &Card), (With, Without)>, moving_query: Query<(), With>, @@ -432,13 +406,11 @@ fn select_play_card( ) { // Only allow playing if it's Player 0's turn if game.0.current_player() != PlayerId::PLAYER_0 { - info!("Not Player's turn"); return; } // Prevent playing while animations are ongoing if !moving_query.is_empty() { - info!("Cannot play card while animations are in progress"); return; } @@ -459,8 +431,7 @@ fn select_play_card( .count(); let player_index = (game.0.trick_leader().as_usize() + num_played) % 4; match game.0.play_card(card.0) { - Ok(effect) => { - info!("Played card: {:?}, effect: {:?}", card.0, effect); + Ok(_effect) => { // Move to trick position let (x, y) = TRICK_POSITIONS[player_index]; commands @@ -498,18 +469,12 @@ fn move_to_target( let move_amount = moving.speed * time.delta_secs(); if move_amount >= distance { - // Snap to target transform.translation = moving.target; - - // Run the callback system if provided + commands.entity(entity).remove::(); if let Some(system_id) = moving.on_arrival { commands.run_system(system_id); } - - // Remove the component - commands.entity(entity).remove::(); } else { - // Continue moving transform.translation += direction.normalize() * move_amount; } } @@ -525,7 +490,6 @@ impl FromWorld for HandleEffectId { } fn handle_effect( non_pov_play_id: Res, - setup_game_id: Res, collect_cards_id: Res, font: Res, mut commands: Commands, @@ -535,35 +499,27 @@ fn handle_effect( let effect = game.0.history().last().unwrap().1; match effect { shuftlib::tressette::MoveEffect::CardPlayed => { - info!("Handling card played"); if game.0.current_player() != PlayerId::PLAYER_0 { commands.run_system(non_pov_play_id.0) } } - shuftlib::tressette::MoveEffect::TrickCompleted { winner } => { - info!("Handling card played"); - commands.run_system(collect_cards_id.0); - if winner != PlayerId::PLAYER_0 { - commands.run_system(non_pov_play_id.0) - } + shuftlib::tressette::MoveEffect::TrickCompleted { .. } => { + commands.insert_resource(CollectionTimer::new(collect_cards_id.0)); } shuftlib::tressette::MoveEffect::HandComplete { trick_winner: _, score, } => { - info!("Handling hand complete"); if let Ok(mut text) = score_text_query.single_mut() { *text = Text::new(format!("Score: {} - {}", score.0, score.1)); } - commands.run_system(collect_cards_id.0); - commands.run_system(setup_game_id.0); + commands.insert_resource(CollectionTimer::new(collect_cards_id.0)); } shuftlib::tressette::MoveEffect::GameOver { trick_winner: _, final_score, } => { - info!("Handling game over"); - commands.run_system(collect_cards_id.0); + commands.insert_resource(CollectionTimer::new(collect_cards_id.0)); if let Ok(mut text) = score_text_query.single_mut() { *text = Text::new(format!( "Final Score: {} - {}", @@ -608,14 +564,119 @@ struct RestartButton; struct CollectCardsId(SystemId); impl FromWorld for CollectCardsId { fn from_world(world: &mut World) -> Self { - let id = world.register_system(mark_cards_for_despawn); + let id = world.register_system(collect_cards); CollectCardsId(id) } } -fn mark_cards_for_despawn(mut query: Query>, mut commands: Commands) { - info!("Marking cards for despawn"); - for card in query.iter_mut() { - commands.entity(card).insert(ToDespawn); + +fn collect_cards( + query: Query>, + game: Res, + mark_for_despawn_and_continue_id: Res, + window: Query<&Window, With>, + mut cards_being_collected: ResMut, + mut commands: Commands, +) { + let effect = game.0.history().last().unwrap().1; + let winner = match effect { + MoveEffect::TrickCompleted { winner } => Some(winner), + MoveEffect::HandComplete { trick_winner, .. } => Some(trick_winner), + MoveEffect::GameOver { trick_winner, .. } => Some(trick_winner), + _ => None, + }; + + if let Some(winner) = winner { + let window = window.iter().next().unwrap(); + let mut count = 0; + for card in query.iter() { + commands.entity(card).insert(MovingTo { + target: player_position(window, winner.as_usize()), + speed: CARD_SPEED, + on_arrival: Some(mark_for_despawn_and_continue_id.0), + }); + count += 1; + } + cards_being_collected.0 = count; + } +} + +#[derive(Resource, Default)] +struct CardsBeingCollected(usize); + +#[derive(Resource, Default)] +struct CollectionTimer { + timer: Option, + callback: Option, +} + +impl CollectionTimer { + fn new(callback: SystemId) -> Self { + Self { + timer: Some(Timer::from_seconds(COLLECTION_DELAY, TimerMode::Once)), + callback: Some(callback), + } + } +} + +fn check_collection_timer( + mut timer: ResMut, + time: Res