feat(engine): right-click highlight timer and visual hint glow (#5, #6)

Task #5: Add RightClickHighlightTimer(1.5 s) so destination highlights
auto-despawn after 1.5 s. Existing clear-on-state-change and
clear-on-pause logic still fires early when a move is made or the game
is paused. Three unit tests cover timer countdown behaviour.

Task #6: Add HintVisualEvent emitted on H key. Source card gets
HintHighlight + HintHighlightTimer(2 s) for a yellow glow. Destination
PileMarker gets HintPileHighlight with a gold tint (Color::srgb(1.0,
0.85, 0.1)) that restores the original colour when the 2 s timer
expires. Five unit tests cover timer expiry and colour invariants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-28 17:36:23 +00:00
parent 03227f8c77
commit 8cd28cfb29
5 changed files with 287 additions and 11 deletions
+13 -4
View File
@@ -28,13 +28,13 @@ use solitaire_core::game_state::GameState;
use solitaire_core::pile::PileType;
use solitaire_core::rules::{can_place_on_foundation, can_place_on_tableau};
use crate::card_plugin::{CardEntity, HintHighlight, TABLEAU_FAN_FRAC};
use crate::card_plugin::{CardEntity, HintHighlight, HintHighlightTimer, TABLEAU_FAN_FRAC};
use crate::feedback_anim_plugin::ShakeAnim;
use solitaire_core::game_state::DrawMode;
use crate::challenge_plugin::CHALLENGE_UNLOCK_LEVEL;
use crate::events::{
DrawRequestEvent, ForfeitEvent, InfoToastEvent, MoveRejectedEvent, MoveRequestEvent,
NewGameConfirmEvent, NewGameRequestEvent, StateChangedEvent, UndoRequestEvent,
DrawRequestEvent, ForfeitEvent, HintVisualEvent, InfoToastEvent, MoveRejectedEvent,
MoveRequestEvent, NewGameConfirmEvent, NewGameRequestEvent, StateChangedEvent, UndoRequestEvent,
};
use crate::game_plugin::GameMutation;
use crate::pause_plugin::PausedResource;
@@ -61,6 +61,7 @@ impl Plugin for InputPlugin {
.add_event::<NewGameConfirmEvent>()
.add_event::<InfoToastEvent>()
.add_event::<ForfeitEvent>()
.add_event::<HintVisualEvent>()
.add_systems(
Update,
(
@@ -94,6 +95,7 @@ struct KeyboardEvents<'w> {
info_toast: EventWriter<'w, InfoToastEvent>,
draw: EventWriter<'w, DrawRequestEvent>,
forfeit: EventWriter<'w, ForfeitEvent>,
hint_visual: EventWriter<'w, HintVisualEvent>,
}
#[allow(clippy::too_many_arguments)]
@@ -237,7 +239,8 @@ fn handle_keyboard(
for (entity, card_entity, _sprite) in card_entities.iter() {
if card_entity.card_id == card_id {
commands.entity(entity)
.insert(HintHighlight { remaining: 1.5 })
.insert(HintHighlight { remaining: 2.0 })
.insert(HintHighlightTimer(2.0))
.insert(Sprite {
color: Color::srgba(1.0, 1.0, 0.4, 1.0),
custom_size: Some(layout_res.0.card_size),
@@ -246,6 +249,12 @@ fn handle_keyboard(
break;
}
}
// Emit HintVisualEvent so the destination pile
// marker is also tinted gold for 2 s.
ev.hint_visual.send(HintVisualEvent {
source_card_id: card_id,
dest_pile: to.clone(),
});
}
// Fire an informational toast describing where the hinted card
// should move so the player always sees the suggestion in text.