0.1.0-0-pre.10

This commit is contained in:
2026-09-16 10:21:09 +02:00
parent a3d5572c37
commit 88eda8390b
16 changed files with 573 additions and 37 deletions

View File

@@ -1,17 +1,18 @@
// file: crates/games/game-reflex-poc/src/state.rs
// version: 3
// version: 4
/// Minimal deterministic state used to validate the first game boundary.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
/// Deterministic playable state for the Reflex POC.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ReflexState {
score: u64,
target: engine_v1_common::NormalizedRect,
}
impl ReflexState {
/// Creates a fresh Reflex state.
#[must_use]
pub const fn new() -> Self {
return Self { score: 0 };
pub fn new() -> Self {
return Self { score: 0, target: target_for_index(0) };
}
/// Returns the current score.
@@ -20,19 +21,58 @@ impl ReflexState {
return self.score;
}
/// Registers one successful reflex action.
pub const fn register_success(&mut self) {
/// Returns the current normalized target rectangle.
#[must_use]
pub const fn target(self) -> engine_v1_common::NormalizedRect {
return self.target;
}
/// Registers one successful reflex action and relocates the target.
pub fn register_success(&mut self) {
self.score = self.score.saturating_add(1);
self.target = target_for_index(self.score);
return;
}
}
impl Default for ReflexState {
fn default() -> Self {
return Self::new();
}
}
impl engine_v1_common::EngineGame for ReflexState {
fn update(&mut self, _frame: engine_v1_common::EngineFrame, input: engine_v1_common::InputState) {
if input.is_active(engine_v1_common::GameAction::Primary) {
if !input.is_active(engine_v1_common::GameAction::Primary) {
return;
}
let pointer = input.pointer();
if !pointer.active() {
return;
}
if self.target.contains(pointer.x(), pointer.y()) {
self.register_success();
}
return;
}
fn scene(&self) -> engine_v1_common::EngineScene {
let target = engine_v1_common::RenderRect::new(self.target, engine_v1_common::RenderColor::rgb(255, 196, 64));
return engine_v1_common::EngineScene::empty(engine_v1_common::RenderColor::rgb(18, 18, 24)).with_rect(target);
}
}
fn target_for_index(index: u64) -> engine_v1_common::NormalizedRect {
return match index % 8 {
0 => engine_v1_common::NormalizedRect::new(0.12, 0.12, 0.24, 0.14),
1 => engine_v1_common::NormalizedRect::new(0.62, 0.16, 0.24, 0.14),
2 => engine_v1_common::NormalizedRect::new(0.34, 0.34, 0.24, 0.14),
3 => engine_v1_common::NormalizedRect::new(0.08, 0.52, 0.24, 0.14),
4 => engine_v1_common::NormalizedRect::new(0.66, 0.54, 0.24, 0.14),
5 => engine_v1_common::NormalizedRect::new(0.38, 0.72, 0.24, 0.14),
6 => engine_v1_common::NormalizedRect::new(0.14, 0.78, 0.24, 0.14),
_ => engine_v1_common::NormalizedRect::new(0.62, 0.76, 0.24, 0.14),
};
}
#[cfg(test)]

View File

@@ -1,20 +1,24 @@
// file: crates/games/game-reflex-poc/unit_tests/state.rs
// version: 1
// version: 2
#[test]
fn success_increments_score() {
game_logging_lib::with_test_tracing("success_increments_score", || {
fn success_increments_score_and_moves_target() {
game_logging_lib::with_test_tracing("success_increments_score_and_moves_target", || {
let mut state = crate::ReflexState::new();
let initial_target = state.target();
state.register_success();
assert_eq!(state.score(), 1);
assert_ne!(state.target(), initial_target);
});
}
#[test]
fn engine_update_maps_primary_action_to_success() {
game_logging_lib::with_test_tracing("engine_update_maps_primary_action_to_success", || {
fn primary_pointer_inside_target_scores_once() {
game_logging_lib::with_test_tracing("primary_pointer_inside_target_scores_once", || {
let mut state = crate::ReflexState::new();
let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true);
let target = state.target();
let pointer = engine_v1_common::PointerState::normalized(true, target.x() + target.width() / 2.0, target.y() + target.height() / 2.0);
let input = engine_v1_common::InputState::none().with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true);
engine_v1_common::EngineGame::update(
&mut state,
engine_v1_common::EngineFrame::new(0, std::time::Duration::from_millis(16), std::time::Duration::ZERO),
@@ -23,3 +27,28 @@ fn engine_update_maps_primary_action_to_success() {
assert_eq!(state.score(), 1);
});
}
#[test]
fn primary_pointer_outside_target_does_not_score() {
game_logging_lib::with_test_tracing("primary_pointer_outside_target_does_not_score", || {
let mut state = crate::ReflexState::new();
let pointer = engine_v1_common::PointerState::normalized(true, 0.95, 0.95);
let input = engine_v1_common::InputState::none().with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true);
engine_v1_common::EngineGame::update(
&mut state,
engine_v1_common::EngineFrame::new(0, std::time::Duration::from_millis(16), std::time::Duration::ZERO),
input,
);
assert_eq!(state.score(), 0);
});
}
#[test]
fn scene_contains_current_target() {
game_logging_lib::with_test_tracing("scene_contains_current_target", || {
let state = crate::ReflexState::new();
let scene = engine_v1_common::EngineGame::scene(&state);
let first = scene.rectangles()[0];
assert_eq!(first, Some(engine_v1_common::RenderRect::new(state.target(), engine_v1_common::RenderColor::rgb(255, 196, 64))));
});
}