summaryrefslogtreecommitdiff
path: root/2026/games_submissions/rpc2dot0/src
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-08-29 04:06:51 -0400
committerLLLL Colonq <llll@colonq>2026-08-29 04:06:51 -0400
commit761a3172bf4c15aed3836e4d1cd6f815b651b0e0 (patch)
treecc90491529e83349d84d42a8e9982e5f0f38338e /2026/games_submissions/rpc2dot0/src
parent0d2d8cd897e94bd94e0b3ae7c6ce9aa580a44a0a (diff)
Fix
Diffstat (limited to '2026/games_submissions/rpc2dot0/src')
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets.rs38
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/audio/crunch.wavbin123650 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/audio/explosion.wavbin13387 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/audio/fart.wavbin382542 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/audio/footsteps.wavbin140588 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/audio/test.wavbin924 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/meshes/square.obj15
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/shaders/flat/frag.glsl11
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/shaders/flat/vert.glsl4
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/background_main.pngbin572 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/computer.pngbin3616 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/disquette.pngbin445 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/landing.pngbin12693 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu.pngbin1499 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu_lurking.pngbin949 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mrgreen.pngbin1492 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mrred.pngbin1506 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mryellow.pngbin2489 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/mute.pngbin544 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/textures/unmute.pngbin545 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4bin1695306 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/confused_c.mp4bin1213146 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4bin580466 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4bin1635459 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4bin737983 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4bin1440141 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4bin541533 -> 0 bytes
-rw-r--r--2026/games_submissions/rpc2dot0/src/collision.rs99
-rw-r--r--2026/games_submissions/rpc2dot0/src/color.rs25
-rw-r--r--2026/games_submissions/rpc2dot0/src/computer.rs183
-rw-r--r--2026/games_submissions/rpc2dot0/src/computer/os.rs117
-rw-r--r--2026/games_submissions/rpc2dot0/src/constants.rs109
-rw-r--r--2026/games_submissions/rpc2dot0/src/disquette.rs184
-rw-r--r--2026/games_submissions/rpc2dot0/src/effects.rs58
-rw-r--r--2026/games_submissions/rpc2dot0/src/entity.rs90
-rw-r--r--2026/games_submissions/rpc2dot0/src/errors.rs7
-rw-r--r--2026/games_submissions/rpc2dot0/src/game.rs869
-rw-r--r--2026/games_submissions/rpc2dot0/src/lib.rs40
-rw-r--r--2026/games_submissions/rpc2dot0/src/math.rs55
-rw-r--r--2026/games_submissions/rpc2dot0/src/message.rs26
-rw-r--r--2026/games_submissions/rpc2dot0/src/random.rs107
-rw-r--r--2026/games_submissions/rpc2dot0/src/state.rs484
42 files changed, 0 insertions, 2521 deletions
diff --git a/2026/games_submissions/rpc2dot0/src/assets.rs b/2026/games_submissions/rpc2dot0/src/assets.rs
deleted file mode 100644
index fa160a1..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-teleia_macros::generate_assets!("src/assets/");
-
-pub const AUDIO_ASSETS: &[(&str, &[u8])] = &[
- ("crunch", include_bytes!("assets/audio/crunch.wav")),
- ("fart", include_bytes!("assets/audio/fart.wav")),
- ("explosion", include_bytes!("assets/audio/explosion.wav")),
-];
-
-pub const VIDEO_ASSETS: &[(&str, &[u8])] = &[
- (
- "seks_is_bad.mp4",
- include_bytes!("assets/video/adzzz/seks_is_bad.mp4"),
- ),
- (
- "seks_is_gud.mp4",
- include_bytes!("assets/video/adzzz/seks_is_gud.mp4"),
- ),
- (
- "pinnesse_c.mp4",
- include_bytes!("assets/video/adzzz/pinnesse_c.mp4"),
- ),
- (
- "hyped_c.mp4",
- include_bytes!("assets/video/adzzz/hyped_c.mp4"),
- ),
- (
- "GG_win_c.mp4",
- include_bytes!("assets/video/adzzz/GG_win_c.mp4"),
- ),
- (
- "game_over_c.mp4",
- include_bytes!("assets/video/adzzz/game_over_c.mp4"),
- ),
- (
- "confused_c.mp4",
- include_bytes!("assets/video/adzzz/confused_c.mp4"),
- ),
-];
diff --git a/2026/games_submissions/rpc2dot0/src/assets/audio/crunch.wav b/2026/games_submissions/rpc2dot0/src/assets/audio/crunch.wav
deleted file mode 100644
index f11334f..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/audio/crunch.wav
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/audio/explosion.wav b/2026/games_submissions/rpc2dot0/src/assets/audio/explosion.wav
deleted file mode 100644
index c3e867f..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/audio/explosion.wav
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/audio/fart.wav b/2026/games_submissions/rpc2dot0/src/assets/audio/fart.wav
deleted file mode 100644
index ebb001d..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/audio/fart.wav
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/audio/footsteps.wav b/2026/games_submissions/rpc2dot0/src/assets/audio/footsteps.wav
deleted file mode 100644
index 57bfddf..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/audio/footsteps.wav
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/audio/test.wav b/2026/games_submissions/rpc2dot0/src/assets/audio/test.wav
deleted file mode 100644
index 0eabe85..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/audio/test.wav
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/meshes/square.obj b/2026/games_submissions/rpc2dot0/src/assets/meshes/square.obj
deleted file mode 100644
index 7acdef6..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/meshes/square.obj
+++ /dev/null
@@ -1,15 +0,0 @@
-# Blender 3.6.2
-# www.blender.org
-o Cube
-v -1.000000 -1.000000 0.000000
-v 1.000000 -1.000000 0.000000
-v -1.000000 1.000000 0.000000
-v 1.000000 1.000000 0.000000
-vn -0.0000 -0.0000 -1.0000
-vt 0.0 1.0
-vt 1.0 0.0
-vt 0.0 0.0
-vt 1.0 1.0
-s 0
-f 3/1/1 2/2/1 1/3/1
-f 3/1/1 4/4/1 2/2/1
diff --git a/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/frag.glsl b/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/frag.glsl
deleted file mode 100644
index 6ebdfa9..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/frag.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-uniform sampler2D texture_data;
-
-void main()
-{
- vec2 tcfull = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y);
- vec4 texel = texture(texture_data, tcfull);
- if (texel.a != 1.0) {
- discard;
- }
- frag_color = texel;
-}
diff --git a/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/vert.glsl b/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/vert.glsl
deleted file mode 100644
index 6e796cc..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/shaders/flat/vert.glsl
+++ /dev/null
@@ -1,4 +0,0 @@
-void main()
-{
- default_main();
-} \ No newline at end of file
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/background_main.png b/2026/games_submissions/rpc2dot0/src/assets/textures/background_main.png
deleted file mode 100644
index 82b3ebe..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/background_main.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/computer.png b/2026/games_submissions/rpc2dot0/src/assets/textures/computer.png
deleted file mode 100644
index be5c723..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/computer.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/disquette.png b/2026/games_submissions/rpc2dot0/src/assets/textures/disquette.png
deleted file mode 100644
index 12399f4..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/disquette.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/landing.png b/2026/games_submissions/rpc2dot0/src/assets/textures/landing.png
deleted file mode 100644
index 229e6ad..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/landing.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu.png
deleted file mode 100644
index d16a717..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu_lurking.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu_lurking.png
deleted file mode 100644
index 3217129..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mrbleu_lurking.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mrgreen.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mrgreen.png
deleted file mode 100644
index 2eb7210..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mrgreen.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mrred.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mrred.png
deleted file mode 100644
index 2c4bc18..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mrred.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mryellow.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mryellow.png
deleted file mode 100644
index 9784a8f..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mryellow.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/mute.png b/2026/games_submissions/rpc2dot0/src/assets/textures/mute.png
deleted file mode 100644
index b4b81dd..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/mute.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/textures/unmute.png b/2026/games_submissions/rpc2dot0/src/assets/textures/unmute.png
deleted file mode 100644
index 278d227..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/textures/unmute.png
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4
deleted file mode 100644
index c24a0d9..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/confused_c.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/confused_c.mp4
deleted file mode 100644
index 0c7756d..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/confused_c.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4
deleted file mode 100644
index bccb605..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4
deleted file mode 100644
index 909c56a..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4
deleted file mode 100644
index 38cdc53..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4
deleted file mode 100644
index fb9d490..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4 b/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4
deleted file mode 100644
index 7aac4d5..0000000
--- a/2026/games_submissions/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4
+++ /dev/null
Binary files differ
diff --git a/2026/games_submissions/rpc2dot0/src/collision.rs b/2026/games_submissions/rpc2dot0/src/collision.rs
deleted file mode 100644
index aaae9a7..0000000
--- a/2026/games_submissions/rpc2dot0/src/collision.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-use crate::color::EntityColor;
-use crate::constants::{MAX_SPEED, MIN_SPEED, SPEED_COLLIDE_MULT};
-use crate::entity::Entity;
-use crate::math::BoundingCircle;
-use crate::random::RandomState;
-
-pub fn detect_collisions(entities: &[Entity], buffer: &mut Vec<(usize, usize)>) {
- buffer.clear();
- let len = entities.len();
-
- for i in 0..len {
- if !entities[i].alive {
- continue;
- }
- for j in (i + 1)..len {
- if !entities[j].alive {
- continue;
- }
- if entities[i].overlaps_with(&entities[j]) {
- buffer.push((i, j));
- }
- }
- }
-}
-
-pub fn process_collisions(
- entities: &mut Vec<Entity>,
- rng: &mut RandomState,
- _next_entity_id: &mut u32,
- collision_buffer: &mut Vec<(usize, usize)>,
- elapsed_ms: f32,
- _events: &mut Vec<String>,
-) {
- detect_collisions(entities, collision_buffer);
-
- let new_entities = Vec::new();
-
- for &(i, j) in collision_buffer.iter() {
- if i >= entities.len() || j >= entities.len() {
- continue;
- }
- if !entities[i].alive || !entities[j].alive {
- continue;
- }
-
- let color_a = entities[i].color;
- let color_b = entities[j].color;
-
- match (color_a, color_b) {
- (EntityColor::Green, EntityColor::Green) => {
- if (rng.next_u32() & 1) == 0 {
- entities[i].color = EntityColor::Yellow;
- entities[j].color = EntityColor::Red;
- entities[j].spawn_time_ms = Some(elapsed_ms);
- } else {
- entities[i].color = EntityColor::Red;
- entities[i].spawn_time_ms = Some(elapsed_ms);
- entities[j].color = EntityColor::Yellow;
- }
- }
- (EntityColor::Red, EntityColor::Red) => {
- let a = &entities[i];
- let b = &entities[j];
- let collision_point = (a.pos + b.pos) * 0.5;
-
- entities[i].alive = false;
- entities[j].alive = false;
-
- let best_green_idx = entities
- .iter()
- .enumerate()
- .filter(|(_, e)| e.alive && e.color == EntityColor::Green)
- .min_by(|(_, e1), (_, e2)| {
- e1.pos
- .distance_squared(collision_point)
- .partial_cmp(&e2.pos.distance_squared(collision_point))
- .unwrap_or(std::cmp::Ordering::Equal)
- })
- .map(|(idx, _)| idx);
-
- if let Some(idx) = best_green_idx {
- entities[idx].alive = false;
- }
- }
- _ => {
- let a_speed = (entities[i].speed * SPEED_COLLIDE_MULT).clamp(MIN_SPEED, MAX_SPEED);
- let b_speed = (entities[j].speed * SPEED_COLLIDE_MULT).clamp(MIN_SPEED, MAX_SPEED);
- entities[i].speed = a_speed;
- entities[j].speed = b_speed;
- entities[i].normalize_velocity();
- entities[j].normalize_velocity();
- }
- }
- }
-
- if !new_entities.is_empty() {
- entities.extend(new_entities);
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/color.rs b/2026/games_submissions/rpc2dot0/src/color.rs
deleted file mode 100644
index ed81ec4..0000000
--- a/2026/games_submissions/rpc2dot0/src/color.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use crate::assets;
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-
-pub enum EntityColor {
- Red = 0,
- Green = 1,
- Bleu = 2,
- Yellow = 3,
-}
-
-pub trait ColorExt {
- fn texture(&self) -> assets::Texture;
-}
-
-impl ColorExt for EntityColor {
- fn texture(&self) -> assets::Texture {
- match self {
- EntityColor::Red => assets::Texture::Mrred,
- EntityColor::Green => assets::Texture::Mrgreen,
- EntityColor::Bleu => assets::Texture::Mrbleu,
- EntityColor::Yellow => assets::Texture::Mryellow,
- }
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/computer.rs b/2026/games_submissions/rpc2dot0/src/computer.rs
deleted file mode 100644
index e25fb65..0000000
--- a/2026/games_submissions/rpc2dot0/src/computer.rs
+++ /dev/null
@@ -1,183 +0,0 @@
-use crate::constants::*;
-use crate::disquette::Disquette;
-use crate::game::Game;
-use crate::math::ContainsPoint;
-use crate::math::Rectangle;
-use crate::random::RandomState;
-
-pub mod os;
-
-use os::Os;
-use teleia::context;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ComputerPhase {
- Off,
- Booting,
- On,
- ShutDown,
-}
-
-impl From<ComputerPhase> for &'static str {
- fn from(value: ComputerPhase) -> Self {
- match value {
- ComputerPhase::Off => "Off",
- ComputerPhase::Booting => "Booting",
- ComputerPhase::On => "On",
- ComputerPhase::ShutDown => "ShutDown",
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum PowerButtonAction {
- On,
- Off,
-}
-
-impl From<PowerButtonAction> for &'static str {
- fn from(value: PowerButtonAction) -> Self {
- match value {
- PowerButtonAction::On => "On",
- PowerButtonAction::Off => "Off",
- }
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct Computer {
- pub coord: glam::Vec2,
- pub phase: ComputerPhase,
- pub disquette: Disquette,
- pub os: Os,
-}
-
-impl Computer {
- pub fn new(
- logical_width: f32,
- logical_height: f32,
- rng: &mut RandomState,
- difficulty: f32,
- ) -> Self {
- const HALF_W: f32 = COMPUTER_WIDTH / 2.0;
- const HALF_H: f32 = COMPUTER_HEIGHT / 2.0;
- let x = rng.next_range(HALF_W, logical_width - HALF_W);
- let y = rng.next_range(HALF_H, logical_height - HALF_H);
- log::info!("eatyourgreens:: Computer new at ({}, {}) ", x, y);
- let mut temp = Self {
- coord: glam::Vec2::new(x, y),
- phase: ComputerPhase::Off,
- disquette: Disquette {
- coord: glam::Vec2::ZERO,
- vel: glam::Vec2::ZERO,
- size: 0.0,
- phase: crate::disquette::DisquettePhase::Free,
- },
- os: Os::from(ComputerPhase::Off),
- };
-
- let hitzone = temp.hit_zone(difficulty);
- temp.disquette = Disquette::new(logical_width, logical_height, rng, Some(hitzone));
- temp
- }
-
- pub fn disquette_inserted(&self) -> bool {
- self.disquette.is_inserted()
- }
-
- pub fn boot(&mut self) {
- log::info!("eatyourgreens:: computer boot");
- self.phase = ComputerPhase::Booting;
- self.os = Os::from(self.phase);
- }
-
- pub fn on(&mut self, ctx: &context::Context) {
- log::info!("eatyourgreens:: computer on");
- self.phase = ComputerPhase::On;
- self.os.set(self.phase);
- self.os.os_play(&ctx);
- }
-
- pub fn shutdown(&mut self) {
- log::info!("eatyourgreens:: computer shutdown");
- self.phase = ComputerPhase::Off;
- self.os.set(self.phase);
- }
-
- pub fn already_used(&self) -> bool {
- self.phase != ComputerPhase::Off
- }
-
- //
- // hitzone calculation
- //
- // computer pos is (x, y)so its center.
- // first find the top-left corner of the computer bbox.
- // second define "hitzone" as the inner rectangle offset from that top-left corner.
-
- // us diffculity to shrink the area, so user msut aim better at higher difficulties
-
- // topleft(x -width/2 , y-height/2)
- // +------------------------------------------+ ---
- // | | |
- // | TRIGGER_OFFSET_Y | |
- // | v | | COMPUTER_HEIGHT
- // | -> +----------------------------+ --- | |
- // | ^ | diff_y | | | |
- // | | | -> +---------------+ --- | | | |
- // | | | ^ | | | | | TRIGGER_HEIGHT
- // | | | | | Actual Hit |actual_| | | |
- // | | | | | Zone (shrunk) |height | | | |
- // | | | | +---------------+ --- | | | |
- // | | | diff_x | | | |
- // | | +----------------------------+ --- | |
- // | | | |
- // | TRIGGER_OFFSET_X | |
- // +------------------------------------------+ ---
- // |--------------COMPUTER_WIDTH--------------|
-
- pub fn hit_zone(&self, difficulty: f32) -> Rectangle {
- let topleft = self.coord - glam::Vec2::new(COMPUTER_WIDTH / 2.0, COMPUTER_HEIGHT / 2.0);
-
- let size_multiplier = 1.0 / difficulty.max(1.0);
- let actual_size = glam::Vec2::new(TRIGGER_WIDTH, TRIGGER_HEIGHT) * size_multiplier;
-
- let diff = (glam::Vec2::new(TRIGGER_WIDTH, TRIGGER_HEIGHT) - actual_size) / 2.0;
-
- let trigger_pos = topleft + glam::Vec2::new(TRIGGER_OFFSET_X, TRIGGER_OFFSET_Y) + diff;
-
- Rectangle::from_min_max(trigger_pos, trigger_pos + actual_size)
- }
-
- pub fn check_power_button(
- &self,
- point: glam::Vec2,
- difficulty: f32,
- ) -> Option<PowerButtonAction> {
- let topleft = self.coord - glam::Vec2::new(COMPUTER_WIDTH / 2.0, COMPUTER_HEIGHT / 2.0);
- let pb_pos = topleft + glam::Vec2::new(POWER_BUTTON_OFFSET_X, POWER_BUTTON_OFFSET_Y);
- let pb_size = glam::Vec2::new(POWER_BUTTON_WIDTH, POWER_BUTTON_HEIGHT);
-
- let pb_rect = Rectangle::from_min_max(pb_pos, pb_pos + pb_size);
-
- if pb_rect.contains(point) {
- if difficulty > 1.0 {
- let half_width = POWER_BUTTON_WIDTH / 2.0;
- if point.x < pb_pos.x + half_width {
- Some(PowerButtonAction::Off)
- } else {
- Some(PowerButtonAction::On)
- }
- } else {
- Some(PowerButtonAction::On)
- }
- } else {
- None
- }
- }
-
- pub fn contains_disquette(&self, disquette: &Disquette, difficulty: f32) -> bool {
- let hitzone = self.hit_zone(difficulty);
- hitzone.contains(disquette.coord)
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/computer/os.rs b/2026/games_submissions/rpc2dot0/src/computer/os.rs
deleted file mode 100644
index 797e07e..0000000
--- a/2026/games_submissions/rpc2dot0/src/computer/os.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use std::fmt::Debug;
-
-use crate::computer::ComputerPhase;
-use crate::message::Message;
-use crate::{assets::VIDEO_ASSETS, random::RandomState};
-use crate::{constants::*, game};
-use teleia::context;
-use web_sys::{Blob, BlobPropertyBag, Url};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub struct Os {
- state: OsState,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum OsState {
- Booting,
- On,
- ShutDown,
- Off,
-}
-
-// TODO ???
-impl From<OsState> for ComputerPhase {
- fn from(value: OsState) -> Self {
- match value {
- OsState::Booting => ComputerPhase::Booting,
- OsState::On => ComputerPhase::On,
- OsState::ShutDown => ComputerPhase::ShutDown,
- OsState::Off => ComputerPhase::Off,
- }
- }
-}
-
-impl From<ComputerPhase> for OsState {
- fn from(value: ComputerPhase) -> Self {
- match value {
- ComputerPhase::Off => OsState::Off,
- ComputerPhase::Booting => OsState::Booting,
- ComputerPhase::On => OsState::On,
- ComputerPhase::ShutDown => OsState::ShutDown,
- }
- }
-}
-
-impl Os {
- pub fn new() -> Self {
- Self {
- state: OsState::Off,
- }
- }
- pub fn from(phase: ComputerPhase) -> Self {
- Self {
- state: OsState::from(phase),
- }
- }
-
- pub fn set(&mut self, phase: ComputerPhase) {
- self.state = OsState::from(phase);
- }
-}
-
-impl Default for Os {
- fn default() -> Self {
- Self {
- state: OsState::Off,
- }
- }
-}
-
-impl Os {
- pub fn boot(rng: &mut RandomState) {
- let idx = rng.next_range(0.0, VIDEO_ASSETS.len() as f32) as usize;
- let bytes = VIDEO_ASSETS[idx].1;
-
- // huh ? `of1` nice api name
- let parts = js_sys::Array::of1(&js_sys::Uint8Array::from(bytes));
- let blob_props = BlobPropertyBag::new();
- blob_props.set_type("video/mp4");
-
- let blob = match Blob::new_with_u8_array_sequence_and_options(&parts, &blob_props) {
- Ok(b) => b,
- Err(_) => {
- log::error!("eatyourgreens:: failed to create blob");
- return;
- }
- };
-
- let url = match Url::create_object_url_with_blob(&blob) {
- Ok(s) => s,
- Err(_) => {
- log::error!("eatyourgreens:: failed to create object URL");
- return;
- }
- };
- if let Some(window) = web_sys::window() {
- let msg = Message {
- op: "play_video",
- verb: None,
- win: None,
- score: None,
- video_url: Some(url),
- };
- Message::post(&window, &msg);
- } else {
- log::error!("eatyourgreens:: failed to get window and send os msg");
- }
- }
-
- pub fn os_play(&self, ctx: &context::Context) {
- log::debug!("eatyourgreens:: os play game:");
- }
-
- pub fn os_quit() {
- log::debug!("eatyourgreens:: os quit");
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/constants.rs b/2026/games_submissions/rpc2dot0/src/constants.rs
deleted file mode 100644
index 0e16b2b..0000000
--- a/2026/games_submissions/rpc2dot0/src/constants.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use crate::color::EntityColor;
-use wasm_bindgen::prelude::wasm_bindgen;
-
-pub const HEIGHT: u32 = 160;
-pub const WIDTH: u32 = 240;
-pub const LANDING_TEXTURE_HEIGHT: i32 = 160;
-pub const LANDING_TEXTURE_WIDTH: i32 = 240;
-pub const MUTE_ICON_SIZE: f32 = 16.0;
-pub const MUTE_ICON_MARGIN: f32 = 0.0;
-
-pub const GAME_DURATION_MS: u32 = 15_000;
-pub const NB_GREENS: i32 = 15;
-pub const INITIAL_SPAWN: u32 = 8;
-pub const MAX_ENTITIES: u32 = 40;
-pub const BASE_SPEED: f32 = 24.0;
-pub const SPEED_COLLIDE_MULT: f32 = 2.0;
-pub const ENTITY_RADIUS: f32 = 4.8;
-pub const INITIAL_SPAWN_SPREAD_MS: f32 = 1_000.0;
-pub const SPAWN_RATE_MIN_MS: u32 = 500;
-pub const SPAWN_RATE_MAX_MS: u32 = 1_200;
-pub const DAMPING_FACTOR: f32 = 0.98;
-pub const JITTER_STRENGTH: f32 = 0.12;
-pub const MIN_SPEED: f32 = 2.6;
-pub const MAX_SPEED: f32 = 64.0;
-pub const SPAWN_PADDING: f32 = 1.0;
-pub const ENTITY_SCALE_FACTOR: f32 = 1.5;
-pub const RED_COUNTDOWN_MS: f32 = 3_000.0;
-pub const ROGUE_DURATION_MS: f32 = 3_000.0;
-pub const PACMAN_CHASE_PROB: f64 = 0.30;
-pub const PACMAN_CHASE_SPEED_MULT: f32 = 1.5;
-
-pub const BASELINE_RATIOS: [f32; 4] = {
- let mut b = [0.0f32; 4];
- b[EntityColor::Red as usize] = 0.10;
- b[EntityColor::Green as usize] = 0.60;
- b[EntityColor::Bleu as usize] = 0.10;
- b[EntityColor::Yellow as usize] = 0.20;
- b
-};
-
-pub const fn get_baseline_ratio(color: EntityColor, difficulty: f32) -> f32 {
- const SHIFT_PER_LEVEL: f32 = 0.15;
- const GREEN_FLOOR: f32 = 0.05;
-
- let green_base = BASELINE_RATIOS[EntityColor::Green as usize];
- let red_base = BASELINE_RATIOS[EntityColor::Red as usize];
-
- // how much we can actually shift without breaching the green floor.
- let max_shift = green_base - GREEN_FLOOR;
- let shift = (SHIFT_PER_LEVEL * (difficulty - 1.0).max(0.0)).min(max_shift);
-
- match color {
- EntityColor::Green => green_base - shift,
- EntityColor::Red => red_base + shift,
- // Bleu and Yellow stay fixed; only the Green/Red tension is modulated.
- c => BASELINE_RATIOS[c as usize],
- }
-}
-
-// UI constants
-pub const TITLE_HEADING_TEXT_COLOR: glam::Vec3 = glam::Vec3::new(0.105, 0.262, 0.196);
-pub const TITLE_DESCRIPTION_TEXT_COLOR: glam::Vec3 = glam::Vec3::new(0.184, 0.321, 0.2);
-pub const START_BUTTON_TEXT_COLOR: glam::Vec3 = glam::Vec3::new(1.0, 1.0, 1.0);
-pub const TITLE_START_BUTTON_LABEL: &str = "START";
-pub fn title_score_goal() -> String {
- format!("Score {} or more to win", NB_GREENS)
-}
-pub const TITLE_CLICK_START_HINT: &str = "Click START to begin the 15s* round.";
-//pub const TITLE_ROUND_WARNING: &str = "(*in this computer seconds might be longer, TBI)";
-pub const TITLE_SCORING_HINT: &str = "Green gives +1, all other colors give -1.";
-pub const TITLE_HINT: &str = "hint: booting the computer is optional.";
-
-pub const HUD_TEXT_COLOR: glam::Vec3 = glam::Vec3::new(0.2, 0.2, 0.2);
-pub const HUD_SCORE_LABEL_PREFIX: &str = "Score: ";
-pub const HUD_TIME_LABEL_PREFIX: &str = "Time: ";
-pub const HUD_TIME_LABEL_SUFFIX: &str = "s";
-pub const HUD_FPS_LABEL_PREFIX: &str = "FPS: ";
-pub const HUD_SCORE_POSITION: glam::Vec2 = glam::Vec2::new(2.6, 6.6);
-pub const HUD_FPS_POSITION: glam::Vec2 = glam::Vec2::new(2.6, 20.0);
-pub const RESULT_DISPLAY_DELAY_MS: f64 = 2000.0;
-#[wasm_bindgen]
-pub fn get_result_display_delay_ms() -> f64 {
- RESULT_DISPLAY_DELAY_MS
-}
-
-// UI rendering colors and uniforms
-pub const RED_COUNTDOWN_BAR_COLOR: glam::Vec4 = glam::Vec4::new(1.0, 0.266, 0.266, 1.0);
-pub const BUTTON_FILL_COLOR: glam::Vec4 = glam::Vec4::new(0.0, 0.0, 1.0, 0.9);
-pub const GAME_OVER_OVERLAY_COLOR: glam::Vec4 = glam::Vec4::new(1.0, 1.0, 1.0, 0.82);
-pub const UI_UNIFORM_COLOR_NAME: &str = "color";
-pub const UI_UNIFORM_OPACITY_NAME: &str = "opacity";
-pub const FLASH_SCREEN_BASE_COLOR: glam::Vec3 = glam::Vec3::new(1.0, 0.0, 0.0);
-pub const GAME_OVER_TEXT_COLOR: glam::Vec3 = glam::Vec3::new(0.105, 0.262, 0.196);
-pub const GAME_OVER_TITLE_TEXT: &str = "Game Over";
-pub const GAME_OVER_RESULT_PREFIX: &str = "Result: ";
-
-// computer
-pub const COMPUTER_WIDTH: f32 = 42.6;
-pub const COMPUTER_HEIGHT: f32 = 42.6;
-pub const TRIGGER_OFFSET_X: f32 = 9.8;
-pub const TRIGGER_OFFSET_Y: f32 = 4.0;
-pub const TRIGGER_WIDTH: f32 = 8.0;
-pub const TRIGGER_HEIGHT: f32 = 6.1;
-pub const POWER_BUTTON_OFFSET_X: f32 = 28.8;
-pub const POWER_BUTTON_OFFSET_Y: f32 = 2.6;
-pub const POWER_BUTTON_WIDTH: f32 = 9.6;
-pub const POWER_BUTTON_HEIGHT: f32 = 5.8;
-
-// Subgame
diff --git a/2026/games_submissions/rpc2dot0/src/disquette.rs b/2026/games_submissions/rpc2dot0/src/disquette.rs
deleted file mode 100644
index 7940d17..0000000
--- a/2026/games_submissions/rpc2dot0/src/disquette.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-use crate::math::{ContainsPoint, Rectangle};
-use crate::random::RandomState;
-use teleia::state;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum DisquettePhase {
- Free,
- Inserted,
-}
-
-impl From<DisquettePhase> for &'static str {
- fn from(value: DisquettePhase) -> Self {
- match value {
- DisquettePhase::Free => "Free",
- DisquettePhase::Inserted => "Inserted",
- }
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct Disquette {
- pub coord: glam::Vec2,
- pub vel: glam::Vec2,
- pub size: f32,
- pub phase: DisquettePhase,
-}
-
-impl Disquette {
- pub fn new(
- logical_width: f32,
- logical_height: f32,
- rng: &mut RandomState,
- computer_hitzone: Option<Rectangle>,
- ) -> Self {
- const SIZE: f32 = 6.4;
- const HALF: f32 = SIZE / 2.0;
- log::info!(
- "eatyourgreens:: Disquette new at ({}, {})",
- logical_width,
- logical_height
- );
-
- let x = rng.next_range(HALF, logical_width - HALF);
- let y = rng.next_range(HALF, logical_height - HALF);
- let mut coord = glam::Vec2::new(x, y);
-
- // prevent spawning inside the computer hitzone
- // this is a bit stupidos, but it works for now.
- // @TODO: find a better way to do this. like using rng api (random.rs) where you can
- // specify the excluded area, or something less retarded.
- if let Some(hitzone) = computer_hitzone {
- // wasting few cpu cycles doesn't hurt anybody
- while hitzone.contains(coord) {
- coord.x = rng.next_range(HALF, logical_width - HALF);
- coord.y = rng.next_range(HALF, logical_height - HALF);
- }
- }
-
- Self {
- coord,
- vel: glam::Vec2::new(
- // -/+ 21.3 as value cause it works, yeah idc
- if rng.next_bool(0.5) { 21.3 } else { -21.3 },
- if rng.next_bool(0.5) { 21.3 } else { -21.3 },
- ),
- size: SIZE,
- phase: DisquettePhase::Free,
- }
- }
-
- pub fn is_inserted(&self) -> bool {
- self.phase == DisquettePhase::Inserted
- }
-
- pub fn update(
- &mut self,
- width: f32,
- height: f32,
- keys: &state::Keys,
- delta_ms: f32,
- difficulty: f32,
- computer_hitzone: Option<Rectangle>,
- ) {
- let dt = delta_ms / 1000.0;
- let impulse = 106.7 * difficulty;
-
- let driven_by_user = keys.left() || keys.right() || keys.up() || keys.down();
-
- if keys.left() {
- self.vel.x -= impulse * dt;
- }
- if keys.right() {
- self.vel.x += impulse * dt;
- }
- if keys.up() {
- self.vel.y -= impulse * dt;
- }
- if keys.down() {
- self.vel.y += impulse * dt;
- }
-
- // speed limiting
- // @TODO : hum value below was adjusted manualy it might
- // be wrong with game rendering speed? ??
- let max_speed = 53.3 * difficulty;
- let speed = self.vel.length();
- if speed > max_speed {
- self.vel = self.vel.normalize() * max_speed;
- }
-
- self.coord += self.vel * dt;
-
- let half = self.size / 2.0;
- let min_bounds = glam::Vec2::splat(half);
- let max_bounds = glam::Vec2::new(width - half, height - half);
-
- // boundary collision and bouncing
- // left edge : clamp position and ensure it moves right
- if self.coord.x < min_bounds.x {
- self.coord.x = min_bounds.x;
- // .abs() guarantees velocity is positive (moving right)
- self.vel.x = self.vel.x.abs();
- }
- // right edge : clamp position and ensure it moves left
- else if self.coord.x > max_bounds.x {
- self.coord.x = max_bounds.x;
- // -.abs() guarantees velocity is negative (moving left)
- self.vel.x = -self.vel.x.abs();
- }
-
- // top and bottom edges
- if self.coord.y < min_bounds.y {
- self.coord.y = min_bounds.y;
- // .abs() guarantees velocity is positive (moving down)
- self.vel.y = self.vel.y.abs();
- } else if self.coord.y > max_bounds.y {
- self.coord.y = max_bounds.y;
- // -.abs() guarantees velocity is negative (moving up)
- self.vel.y = -self.vel.y.abs();
- }
-
- // bounce off computer hitzone unless user is actively driving it in
- if !driven_by_user && let Some(hitzone) = computer_hitzone {
- let prev_pos = self.coord - self.vel * dt;
-
- if hitzone.contains(self.coord) {
- let was_outside = !hitzone.contains(prev_pos);
- if was_outside {
- let was_left = prev_pos.x < hitzone.min.x;
- let was_right = prev_pos.x > hitzone.max.x;
- let was_top = prev_pos.y < hitzone.min.y;
- let was_bottom = prev_pos.y > hitzone.max.y;
-
- if was_left || was_right {
- // reverse horizontal velocity to bounce off
- self.vel.x = -self.vel.x;
- // +/- 0.1 is a tiny extra push away from the hitzone.
- // this prevent the disquette from getting stuck exactly on the boundary
- // and repeatedly triggers collisions
- // this bug took 2h from my life, fuck you disquette, you floppy disk :/.
- self.coord.x = if was_left {
- hitzone.min.x - 0.1
- } else {
- hitzone.max.x + 0.1
- };
- }
- if was_top || was_bottom {
- // reverse vertical velocity to bounce off
- self.vel.y = -self.vel.y;
- // +/- 0.1 adds a tiny gap to safely escape the hitzone
- self.coord.y = if was_top {
- hitzone.min.y - 0.1
- } else {
- hitzone.max.y + 0.1
- };
- }
- }
- }
- }
- }
-}
-
-// the hours spent on this zone/bouncing detection bs, I should've done something more productive, but
-// whatever.
diff --git a/2026/games_submissions/rpc2dot0/src/effects.rs b/2026/games_submissions/rpc2dot0/src/effects.rs
deleted file mode 100644
index 02b33ef..0000000
--- a/2026/games_submissions/rpc2dot0/src/effects.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use crate::color::EntityColor;
-use crate::constants::*;
-
-#[derive(Debug, Clone)]
-pub struct PacmanChase {
- pub speed: f32,
-}
-
-impl PacmanChase {
- pub fn new(speed: f32) -> Self {
- Self { speed }
- }
-}
-
-impl Default for PacmanChase {
- fn default() -> Self {
- Self::new(BASE_SPEED)
- }
-}
-#[derive(Debug, Clone)]
-pub struct Rogue {
- pub until_ms: f32,
- pub hover_color: EntityColor,
-}
-
-impl Rogue {
- pub fn new(until_ms: f32, hover_color: EntityColor) -> Self {
- Self {
- until_ms,
- hover_color,
- }
- }
-}
-
-impl Default for Rogue {
- fn default() -> Self {
- Self::new(ROGUE_DURATION_MS, EntityColor::Yellow)
- }
-}
-
-pub struct Explosion {
- pub x: f32,
- pub y: f32,
- pub age_ms: f32,
- pub duration_ms: f32,
- pub radius: f32,
-}
-impl Explosion {
- pub fn new(x: f32, y: f32) -> Self {
- Self {
- x,
- y,
- age_ms: 0.0,
- duration_ms: 400.0,
- radius: 8.0,
- }
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/entity.rs b/2026/games_submissions/rpc2dot0/src/entity.rs
deleted file mode 100644
index 3b4200f..0000000
--- a/2026/games_submissions/rpc2dot0/src/entity.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-use crate::color::EntityColor;
-use crate::constants::{DAMPING_FACTOR, MIN_SPEED};
-use crate::effects::{PacmanChase, Rogue};
-use crate::math::{BoundingCircle, Vec2};
-
-pub trait Update {
- fn update(&mut self, delta_ms: f32);
-}
-
-#[derive(Debug, Clone)]
-pub struct Entity {
- pub _id: u32,
- pub pos: Vec2,
- pub vel: Vec2,
- pub speed: f32,
- pub color: EntityColor,
- pub radius: f32,
- pub alive: bool,
- pub spawn_time_ms: Option<f32>,
- pub rogue: Option<Rogue>,
- pub pacman_chase: Option<PacmanChase>,
-}
-
-impl Entity {
- pub fn new(id: u32, pos: Vec2, vel: Vec2, color: EntityColor, speed: f32, radius: f32) -> Self {
- let mut entity = Self {
- _id: id,
- pos,
- vel,
- speed,
- color,
- radius,
- alive: true,
- spawn_time_ms: None,
- rogue: None,
- pacman_chase: None,
- };
- entity.normalize_velocity();
- entity
- }
-
- pub fn normalize_velocity(&mut self) {
- let length = self.vel.length();
- if length <= f32::EPSILON {
- self.vel = Vec2::new(1.0, 0.0);
- } else {
- self.vel = self.vel / length * self.speed;
- }
- }
-
- pub fn apply_jitter(&mut self, jitter: Vec2) {
- self.vel += jitter;
- self.normalize_velocity();
- }
-
- pub fn dampen_speed(&mut self, max_speed: f32) {
- self.speed = (self.speed * DAMPING_FACTOR).clamp(MIN_SPEED, max_speed);
- self.normalize_velocity();
- }
-
- pub fn bounds_bounce(&mut self, width: f32, height: f32) {
- let min_bounds = Vec2::splat(self.radius);
- let max_bounds = Vec2::new(width, height) - min_bounds;
-
- let hit_min = self.pos.cmple(min_bounds);
- let hit_max = self.pos.cmpge(max_bounds);
-
- self.pos = self.pos.clamp(min_bounds, max_bounds);
-
- self.vel = Vec2::select(hit_min, self.vel.abs(), self.vel);
- self.vel = Vec2::select(hit_max, -self.vel.abs(), self.vel);
- }
-}
-
-impl Update for Entity {
- fn update(&mut self, delta_ms: f32) {
- let dt = delta_ms / 1000.0;
- self.pos += self.vel * dt;
- }
-}
-
-impl BoundingCircle for Entity {
- fn center(&self) -> Vec2 {
- self.pos
- }
-
- fn radius(&self) -> f32 {
- self.radius
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/errors.rs b/2026/games_submissions/rpc2dot0/src/errors.rs
deleted file mode 100644
index 71a67cf..0000000
--- a/2026/games_submissions/rpc2dot0/src/errors.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use crate::color::EntityColor;
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum ClickOutcome {
- Hit(EntityColor),
- Miss,
-}
diff --git a/2026/games_submissions/rpc2dot0/src/game.rs b/2026/games_submissions/rpc2dot0/src/game.rs
deleted file mode 100644
index 7650e72..0000000
--- a/2026/games_submissions/rpc2dot0/src/game.rs
+++ /dev/null
@@ -1,869 +0,0 @@
-use std::collections::HashMap;
-
-use teleia::renderer::UberFlags;
-use teleia::*;
-
-use crate::assets;
-use crate::assets::AUDIO_ASSETS;
-use crate::color::{ColorExt, EntityColor};
-use crate::computer::os::Os;
-use crate::computer::*;
-use crate::constants::*;
-use crate::disquette::*;
-use crate::effects::*;
-use crate::errors::*;
-use crate::math::*;
-use crate::message::Message;
-use crate::state::{GamePhase, GameState};
-
-pub struct Game {
- renderer: renderer::Renderer<assets::Assets>,
- state: GameState,
- mouse: glam::Vec2,
- explosions: Vec<Explosion>,
- greens_wiped_flash: f32,
- computer: Computer,
- start_button_rect: Option<Rectangle>,
- is_muted: bool,
- mute_button_rect: Option<Rectangle>,
- is_embedded: bool,
- last_time_ms: Option<f64>,
- finish_time_ms: Option<f64>,
- done_sent: bool,
-}
-
-impl Game {
- pub fn new(ctx: &context::Context, is_embedded: bool) -> Self {
- log::info!("eatyourgreens:: entering new Game");
- let mut state = GameState::new();
- let computer = Computer::new(
- ctx.render_width,
- ctx.render_height,
- &mut state.rng,
- state.difficulty,
- );
- Self {
- renderer: renderer::Renderer::new(ctx, assets::Assets::new),
- state,
- mouse: glam::Vec2::ZERO,
- explosions: Vec::new(),
- greens_wiped_flash: 0.0,
- computer,
- start_button_rect: None,
- is_muted: false,
- mute_button_rect: None,
- is_embedded,
- last_time_ms: None,
- finish_time_ms: None,
- done_sent: false,
- }
- }
-
- pub fn set_difficulty(&mut self, difficulty: f32) {
- log::info!("eatyourgreens:: set_difficulty: {}", difficulty);
- self.state.set_difficulty(difficulty);
- }
-
- pub fn receive_op(&mut self, ctx: &context::Context, st: &mut state::State, op: String) {
- log::info!(
- "eatyourgreens:: receive op: {} (phase: {:?})",
- op,
- self.state.phase()
- );
- match self.state.phase() {
- GamePhase::Waiting | GamePhase::Finished => {
- if self.is_embedded && op == "start" {
- self.reset_round(ctx, st);
- const VERB: &str = "eat your greens";
- self.send_op("started", Some(VERB), None, None, None);
- } else {
- log::warn!("eatyourgreens:: received unexpected op: {} (waiting) ", op);
- }
- }
- GamePhase::Computer(computer_phase) => {
- let currentop = op.as_str();
- match computer_phase {
- ComputerPhase::Booting => {
- if currentop == "video_finished" {
- self.state.set_phase(GamePhase::Computer(ComputerPhase::On));
- self.computer.on(ctx);
- } else {
- log::warn!(
- "eatyourgreens:: received unexpected op: {}, Phase {:?} (Computer)",
- op,
- computer_phase
- );
- }
- }
- ComputerPhase::On => {
- if currentop == "computing" {
- // nothing to do in here
- // we wait for os-micro-game to send shutdown
- log::info!("eatyourgreens:: computer computing. do nothing.");
- } else if currentop == "shutdown" {
- log::info!(
- "eatyourgreens:: computer shutdown. Transition to main game."
- );
- self.computer.shutdown();
- self.state.set_phase(GamePhase::Playing);
- } else {
- log::warn!(
- "eatyourgreens:: received unexpected op: {}, Phase {:?} (Computer)",
- op,
- computer_phase
- );
- }
- }
-
- _ => {
- // we shouldn't be receiving events while computer is off or shutdown
- log::warn!(
- "eatyourgreens:: received unexpected op: {}, Phase {:?} (Computer)",
- op,
- computer_phase
- );
- }
- }
- }
- _ => {
- log::warn!(
- "eatyourgreens:: received unexpected op: {} for phase: {:?}",
- op,
- self.state.phase()
- );
- }
- }
- }
-
- fn reset_round(&mut self, ctx: &context::Context, st: &mut state::State) {
- log::info!("eatyourgreens:: reset_round");
- self.state.start_round();
-
- let computer = Computer::new(
- ctx.render_width,
- ctx.render_height,
- &mut self.state.rng,
- self.state.difficulty,
- );
-
- self.computer = computer;
- self.explosions.clear();
- self.greens_wiped_flash = 0.0;
- if self.is_muted {
- st.audio.stop_music();
- }
- self.last_time_ms = None;
- self.finish_time_ms = None;
- self.done_sent = false;
- }
-
- fn push_explosion(&mut self, x: f32, y: f32) {
- self.explosions.push(Explosion::new(x, y));
- }
-
- fn update_explosions(&mut self, delta_ms: f32) {
- for exp in &mut self.explosions {
- exp.age_ms += delta_ms;
- }
- self.explosions.retain(|e| e.age_ms < e.duration_ms);
- }
-
- fn handle_state_event(&mut self, event: String, st: &mut state::State) {
- log::info!("eatyourgreens:: handle_state_event: {}", event);
- if let Some(coords) = event.strip_prefix("explode:") {
- if !self.is_muted {
- st.audio.play_sfx("explosion");
- }
- let (x, y): (f32, f32) = parse_coords(coords);
- self.push_explosion(x, y);
- self.greens_wiped_flash = 300.0;
- return;
- }
-
- if let Some(coords) = event.strip_prefix("click_red:") {
- if !self.is_muted {
- st.audio.play_sfx("explosion");
- }
- let (x, y) = parse_coords(coords);
- self.push_explosion(x, y);
- return;
- }
- }
-
- fn render_title(&mut self, ctx: &context::Context, st: &mut state::State) {
- self.renderer
- .bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::SPRITE);
- self.renderer
- .bind_texture(ctx, st, assets::Texture::Landing);
- self.renderer.set_texture_offset(
- ctx,
- st,
- 1,
- 1,
- LANDING_TEXTURE_WIDTH,
- LANDING_TEXTURE_HEIGHT,
- );
- self.renderer
- .set_position_2d(ctx, st, glam::Vec2::ZERO, st.render_dims);
- self.renderer.set_f32(ctx, st, UI_UNIFORM_OPACITY_NAME, 0.5);
- self.renderer.render_square(ctx, st);
-
- let center_x: f32 = ctx.render_width / 2.0;
- let mut y: f32 = ctx.render_height * 0.3;
- let line_height: f32 = self.renderer.font_char_height(st) + 1.0;
-
- self.draw_text_centered(
- ctx,
- st,
- center_x,
- y,
- TITLE_HEADING_TEXT_COLOR,
- &title_score_goal(),
- );
-
- y += line_height;
- self.draw_text_centered(
- ctx,
- st,
- center_x,
- y,
- TITLE_HEADING_TEXT_COLOR,
- TITLE_CLICK_START_HINT,
- );
-
- y += line_height * 2.0;
- if !self.is_embedded {
- self.draw_button_centered(
- ctx,
- st,
- center_x,
- y,
- START_BUTTON_TEXT_COLOR,
- TITLE_START_BUTTON_LABEL,
- );
- } else {
- self.start_button_rect = None;
- }
-
- y += line_height * 3.0;
- self.draw_text_centered(
- ctx,
- st,
- center_x,
- y,
- TITLE_DESCRIPTION_TEXT_COLOR,
- TITLE_SCORING_HINT,
- );
-
- y += line_height;
- self.draw_text_centered(
- ctx,
- st,
- center_x,
- y,
- TITLE_DESCRIPTION_TEXT_COLOR,
- TITLE_HINT,
- );
-
- self.draw_mute_icon(ctx, st);
- }
-
- fn render_background(&mut self, ctx: &context::Context, st: &mut state::State) {
- ctx.clear_color(glam::Vec4::ZERO);
- ctx.clear();
- self.renderer.texture_screen(
- ctx,
- st,
- assets::Texture::BackgroundMain,
- glam::Vec2::ZERO,
- glam::Vec2::new(ctx.render_width, ctx.render_height),
- );
- }
-
- fn render_entities(&mut self, ctx: &context::Context, st: &mut state::State) {
- let mapped_mouse = self.map_mouse_to_render(ctx, st);
- let elapsed_ms = self.state.elapsed_ms();
- for entity in self.state.entities() {
- if !entity.alive {
- continue;
- }
-
- let size: f32 = entity.radius * 2.0;
- let pos: glam::Vec2 = glam::Vec2::new(entity.pos.x, entity.pos.y);
- let dims: glam::Vec2 = glam::Vec2::new(size, size);
- let topleft: glam::Vec2 = pos - dims / 2.0;
-
- let mut display_color: EntityColor = entity.color;
- if display_color == EntityColor::Green
- && let Some(rogue) = &entity.rogue
- && elapsed_ms < rogue.until_ms
- && entity.pos.distance_squared(mapped_mouse) <= entity.radius * entity.radius
- {
- display_color = rogue.hover_color;
- }
-
- let texture = if entity.color == EntityColor::Bleu && entity.pacman_chase.is_some() {
- assets::Texture::MrbleuLurking
- } else {
- display_color.texture()
- };
-
- self.renderer
- .texture_screen(ctx, st, texture, topleft, dims);
- }
- }
-
- fn render_red_countdowns(&mut self, ctx: &context::Context, st: &mut state::State) {
- self.renderer.bind_uber_2d(ctx, st, UberFlags::empty());
- self.renderer
- .set_vec4(ctx, st, UI_UNIFORM_COLOR_NAME, RED_COUNTDOWN_BAR_COLOR);
-
- for entity in self.state.entities() {
- if !entity.alive || entity.color != EntityColor::Red {
- continue;
- }
-
- if let Some(spawn_time) = entity.spawn_time_ms {
- let elapsed = self.state.elapsed_ms() - spawn_time;
- if elapsed < RED_COUNTDOWN_MS {
- let remaining = RED_COUNTDOWN_MS - elapsed;
- let fraction = remaining / RED_COUNTDOWN_MS;
- let bar_width = 10.6;
- let bar_height = 1.6;
- let pos = glam::Vec2::new(
- entity.pos.x - bar_width / 2.0,
- entity.pos.y - entity.radius - 3.2,
- );
-
- self.renderer.set_position_2d(
- ctx,
- st,
- pos,
- glam::Vec2::new(bar_width * fraction, bar_height),
- );
- self.renderer.render_square(ctx, st);
- }
- }
- }
- }
-
- fn render_explosions(&mut self, ctx: &context::Context, st: &mut state::State) {
- self.renderer.bind_uber_2d(ctx, st, UberFlags::empty());
-
- for exp in &self.explosions {
- let t = exp.age_ms / exp.duration_ms;
- let alpha = 1.0 - t;
- let r = exp.radius * (0.5 + t * 0.5);
- let color = glam::Vec4::new(1.0, (200.0 - t * 200.0) / 255.0, 0.0, alpha);
- self.renderer.set_vec4(ctx, st, "color", color);
-
- let size = r * 2.0;
- let pos = glam::Vec2::new(exp.x - r, exp.y - r);
- self.renderer
- .set_position_2d(ctx, st, pos, glam::Vec2::new(size, size));
- self.renderer.render_square(ctx, st);
- }
- }
-
- fn render_flash(&mut self, ctx: &context::Context, st: &mut state::State) {
- if self.greens_wiped_flash > 0.0 {
- let flash_alpha = (self.greens_wiped_flash / 300.0) * 0.3;
- let flash_color = glam::Vec4::new(
- FLASH_SCREEN_BASE_COLOR.x,
- FLASH_SCREEN_BASE_COLOR.y,
- FLASH_SCREEN_BASE_COLOR.z,
- flash_alpha,
- );
- self.renderer.color_screen(
- ctx,
- st,
- flash_color,
- glam::Vec2::ZERO,
- glam::Vec2::new(ctx.render_width, ctx.render_height),
- );
- }
- }
-
- fn render_computer(&mut self, ctx: &context::Context, st: &mut state::State) {
- let dims = glam::Vec2::new(COMPUTER_WIDTH, COMPUTER_HEIGHT);
- let topleft = self.computer.coord - dims / 2.0;
- self.renderer
- .texture_screen(ctx, st, assets::Texture::Computer, topleft, dims);
- }
-
- fn render_disquette(&mut self, ctx: &context::Context, st: &mut state::State) {
- if self.computer.already_used() || self.computer.disquette_inserted() {
- return;
- }
-
- let size = self.computer.disquette.size;
- let dims = glam::Vec2::splat(size);
- let topleft = self.computer.disquette.coord - dims / 2.0;
- self.renderer
- .texture_screen(ctx, st, assets::Texture::Disquette, topleft, dims);
- }
-
- fn render_hud(&mut self, ctx: &context::Context, st: &mut state::State) {
- self.renderer.text_colored_screen(
- ctx,
- st,
- HUD_SCORE_POSITION,
- HUD_TEXT_COLOR,
- &format!(
- "{}{}",
- HUD_SCORE_LABEL_PREFIX,
- format_args!("{}/{}", self.state.consumer_score(), NB_GREENS)
- ),
- );
-
- let remaining_secs = (self.state.remaining_ms() / 1000.0).ceil() as i32;
- let time_str = format!(
- "{}{}{}",
- HUD_TIME_LABEL_PREFIX, remaining_secs, HUD_TIME_LABEL_SUFFIX
- );
- let time_width = self.renderer.font_char_width(st) * time_str.len() as f32;
-
- self.renderer.text_colored_screen(
- ctx,
- st,
- glam::Vec2::new(ctx.render_width - time_width - 2.6, 6.6),
- HUD_TEXT_COLOR,
- &time_str,
- );
-
- self.renderer.text_colored_screen(
- ctx,
- st,
- HUD_FPS_POSITION,
- HUD_TEXT_COLOR,
- &format!("{}{}", HUD_FPS_LABEL_PREFIX, st.fps),
- );
- }
-
- fn render_game_over(&mut self, ctx: &context::Context, st: &mut state::State) {
- st.audio.stop_music();
- self.renderer.color_screen(
- ctx,
- st,
- GAME_OVER_OVERLAY_COLOR,
- glam::Vec2::ZERO,
- glam::Vec2::new(ctx.render_width, ctx.render_height),
- );
-
- self.draw_text_centered(
- ctx,
- st,
- ctx.render_width / 2.0,
- ctx.render_height / 2.0 - 8.0,
- GAME_OVER_TEXT_COLOR,
- GAME_OVER_TITLE_TEXT,
- );
-
- let status_text: &str = self.state.round_result().into();
-
- self.draw_text_centered(
- ctx,
- st,
- ctx.render_width / 2.0,
- ctx.render_height / 2.0 + 5.3,
- GAME_OVER_TEXT_COLOR,
- &format!("{}{}", GAME_OVER_RESULT_PREFIX, status_text),
- );
- self.draw_text_centered(
- ctx,
- st,
- ctx.render_width / 2.0,
- ctx.render_height / 2.0 + 14.6,
- GAME_OVER_TEXT_COLOR,
- &format!(
- "{}{}",
- HUD_SCORE_LABEL_PREFIX,
- format_args!(
- "{}/{}",
- self.state.consumer_score(),
- self.state.required_greens()
- )
- ),
- );
- }
-}
-
-impl teleia::state::Game for Game {
- fn initialize(&mut self, _ctx: &context::Context, st: &mut state::State) -> Erm<()> {
- log::info!("eatyourgreens:: entering initialize");
-
- st.keybindings.insert(
- state::Keycode::new(winit::keyboard::KeyCode::ArrowUp),
- state::Key::Up,
- );
- st.keybindings.insert(
- state::Keycode::new(winit::keyboard::KeyCode::ArrowDown),
- state::Key::Down,
- );
- st.keybindings.insert(
- state::Keycode::new(winit::keyboard::KeyCode::ArrowLeft),
- state::Key::Left,
- );
- st.keybindings.insert(
- state::Keycode::new(winit::keyboard::KeyCode::ArrowRight),
- state::Key::Right,
- );
-
- log::info!("eatyourgreens:: exiting initialize");
- Ok(())
- }
-
- fn initialize_audio(
- &self,
- _ctx: &context::Context,
- _st: &state::State,
- actx: &audio::Context,
- ) -> HashMap<String, audio::Audio> {
- let mut map = HashMap::new();
- for (name, data) in AUDIO_ASSETS {
- map.insert(name.to_string(), audio::Audio::new(actx, data));
- }
- map
- }
-
- fn mouse_move(
- &mut self,
- _ctx: &context::Context,
- _st: &mut state::State,
- x: i32,
- y: i32,
- ) -> Erm<()> {
- self.mouse = glam::Vec2::new(x as f32, y as f32);
- Ok(())
- }
-
- fn mouse_press(&mut self, ctx: &context::Context, st: &mut state::State) -> Erm<()> {
- let mapped_mouse = self.map_mouse_to_render(ctx, st);
- /*log::info!(
- "eatyourgreens:: mouse at ({:?}), mapped to ({:?})",
- self.mouse,
- mapped_mouse
- );*/
-
- if !self.computer.already_used()
- && self.computer.disquette_inserted()
- && let Some(action) = self
- .computer
- .check_power_button(mapped_mouse, self.state.difficulty)
- {
- log::info!("eatyourgreens:: clicked power button {:?}", action);
- if action == PowerButtonAction::On {
- self.computer.boot();
- self.state
- .set_phase(GamePhase::Computer(ComputerPhase::Booting));
- Os::boot(&mut self.state.rng);
- //log::info!("eatyourgreens:: blah2");
- }
- }
-
- if let Some(rect) = self.mute_button_rect
- && rect.contains(mapped_mouse)
- {
- self.is_muted = !self.is_muted;
- if self.is_muted {
- st.audio.stop_music();
- } else {
- //play something
- }
- return Ok(());
- }
- match self.state.phase() {
- GamePhase::Waiting => {
- if let Some(rect) = self.start_button_rect
- && rect.contains(mapped_mouse)
- {
- self.reset_round(ctx, st);
- }
- }
- GamePhase::Playing => {
- if let Some(ClickOutcome::Hit(color)) =
- self.state.click_at(mapped_mouse.x, mapped_mouse.y)
- {
- log::info!("eatyourgreens:: Hit entity of color {:?}", color);
- match color {
- EntityColor::Green => {
- if !self.is_muted {
- st.audio.play_sfx("crunch");
- }
- }
- _ => {
- if !self.is_muted {
- st.audio.play_sfx("fart");
- }
- }
- }
- }
- }
- GamePhase::Finished => {
- if !self.is_embedded {
- self.reset_round(ctx, st);
- if !self.is_muted {
- st.audio.stop_music();
- }
- }
- }
- GamePhase::Computer(_) => {
- //nothing to do here we yielded to DA COMPUTTTA
- }
- #[allow(unreachable_patterns)]
- _ => unreachable!(),
- }
-
- Ok(())
- }
-
- fn update(&mut self, ctx: &context::Context, st: &mut state::State) -> Erm<()> {
- let now_ms = teleia::state::now(ctx) * 1000.0;
- let last_time = self.last_time_ms.unwrap_or(now_ms);
- self.last_time_ms = Some(now_ms);
-
- if let GamePhase::Computer(ComputerPhase::On) = self.state.phase() {
- // @TODO: play OS microgame here. For now, simulate its end immediately.
- self.receive_op(ctx, st, "shutdown".to_string());
- }
-
- if self.state.phase() != GamePhase::Playing && self.state.phase() != GamePhase::Finished {
- return Ok(());
- }
-
- if self.state.phase() == GamePhase::Playing {
- // cap maximum time jump to 250ms,
- // sometime the game lags when embedded in a Wcolonq wrapper, not sure why
- // probbaly these copypasta all over the place make's it buggy
- let real_delta = (now_ms - last_time).clamp(0.0, 250.0);
- let dt = real_delta as f32;
-
- let disquette_pos = if !self.computer.disquette.is_inserted() {
- Some(self.computer.disquette.coord)
- } else {
- None
- };
- self.state.update_entities(
- dt,
- ctx.render_width,
- ctx.render_height,
- self.computer.disquette.is_inserted(),
- disquette_pos,
- );
- self.state.advance_time(dt);
- self.update_explosions(dt);
-
- if self.greens_wiped_flash > 0.0 {
- self.greens_wiped_flash = (self.greens_wiped_flash - dt).max(0.0);
- }
-
- let events = self.state.drain_events();
- let mut disquette_consumed = false;
- for event in events {
- if event == "disquette_consumed" {
- disquette_consumed = true;
- } else {
- self.handle_state_event(event, st);
- }
- }
-
- if disquette_consumed {
- let hitzone = self.computer.hit_zone(self.state.difficulty);
- self.computer.disquette = crate::disquette::Disquette::new(
- ctx.render_width,
- ctx.render_height,
- &mut self.state.rng,
- Some(hitzone),
- );
- }
-
- if !self.computer.disquette.is_inserted() {
- let hitzone = self.computer.hit_zone(self.state.difficulty);
- self.computer.disquette.update(
- ctx.render_width,
- ctx.render_height,
- &st.keys,
- dt,
- self.state.difficulty,
- Some(hitzone),
- );
-
- if self
- .computer
- .contains_disquette(&self.computer.disquette, self.state.difficulty)
- {
- self.computer.disquette.phase = DisquettePhase::Inserted;
- }
- }
- }
-
- if self.state.phase() == GamePhase::Finished && self.is_embedded && !self.done_sent {
- let finish_at = *self.finish_time_ms.get_or_insert(now_ms);
-
- if now_ms - finish_at >= RESULT_DISPLAY_DELAY_MS {
- self.send_op(
- "done",
- None,
- Some(self.state.round_result().into()),
- Some(self.state.consumer_score()),
- None,
- );
- self.done_sent = true;
- }
- }
-
- Ok(())
- }
-
- fn render(&mut self, ctx: &context::Context, st: &mut state::State) -> Erm<()> {
- if self.state.phase() == GamePhase::Waiting {
- self.render_title(ctx, st);
- return Ok(());
- }
-
- self.render_background(ctx, st);
-
- self.draw_mute_icon(ctx, st);
- self.render_computer(ctx, st);
- self.render_entities(ctx, st);
- self.render_red_countdowns(ctx, st);
- self.render_explosions(ctx, st);
- self.render_flash(ctx, st);
- self.render_disquette(ctx, st);
-
- self.render_hud(ctx, st);
-
- if self.state.phase() == GamePhase::Finished {
- self.render_game_over(ctx, st);
- }
-
- Ok(())
- }
-}
-
-impl Game {
- fn draw_text_centered(
- &mut self,
- ctx: &context::Context,
- st: &mut state::State,
- center_x: f32,
- y: f32,
- color: glam::Vec3,
- text: &str,
- ) {
- let w = self.renderer.font_char_width(st) * text.len() as f32;
- let x = (center_x - w / 2.0).round();
- self.renderer
- .text_colored_screen(ctx, st, glam::Vec2::new(x, y), color, text);
- }
-
- fn draw_button_centered(
- &mut self,
- ctx: &context::Context,
- st: &mut state::State,
- center_x: f32,
- y: f32,
- color: glam::Vec3,
- text: &str,
- ) {
- let text_width = self.renderer.font_char_width(st) * text.len() as f32;
- let padding_x = 4.8;
- let padding_y = 3.2;
- let char_height = self.renderer.font_char_height(st);
- let height = char_height + padding_y * 2.0;
- let width = text_width + padding_x * 2.0;
- let x = (center_x - width / 2.0).round();
-
- let border_thickness = 1.0;
- let inner_color = BUTTON_FILL_COLOR;
-
- self.renderer.color_screen(
- ctx,
- st,
- inner_color,
- glam::Vec2::new(x + border_thickness, y + border_thickness),
- glam::Vec2::new(
- width - border_thickness * 2.0,
- height - border_thickness * 2.0,
- ),
- );
-
- self.draw_text_centered(
- ctx,
- st,
- center_x,
- y + (height - char_height) / 2.0,
- color,
- text,
- );
-
- self.start_button_rect = Some(Rectangle::from_xywh(x, y, width, height));
- }
-
- fn draw_mute_icon(&mut self, ctx: &context::Context, st: &mut state::State) {
- let tex = if self.is_muted {
- assets::Texture::Mute
- } else {
- assets::Texture::Unmute
- };
-
- let size = glam::Vec2::new(MUTE_ICON_SIZE, MUTE_ICON_SIZE);
- let topleft = glam::Vec2::new(
- MUTE_ICON_MARGIN,
- ctx.render_height - MUTE_ICON_SIZE - MUTE_ICON_MARGIN,
- );
- self.renderer.texture_screen(ctx, st, tex, topleft, size);
-
- self.mute_button_rect = Some(Rectangle::from_xywh(topleft.x, topleft.y, size.x, size.y));
- }
-
- // mapping of raw physical mouse coordinates to logical internal rendering coordinates.
- // 1) subtract screen offsets to adjust to window margins.
- // 2) scale the position by the ratio of the logical render resolution to the
- // physical screen dimensions.
- // 3) round and clamp the result point to guarantee it falls strictly within
- // the valid rendering bounds [0, render_width] and [0, render_height].
- fn map_mouse_to_render(&self, ctx: &context::Context, st: &state::State) -> glam::Vec2 {
- let x =
- ((self.mouse.x - st.screen.offsets.x) * ctx.render_width / st.screen.dims.x).round();
- let y =
- ((self.mouse.y - st.screen.offsets.y) * ctx.render_height / st.screen.dims.y).round();
- glam::Vec2::new(
- x.clamp(0.0, ctx.render_width),
- y.clamp(0.0, ctx.render_height),
- )
- }
-
- pub(crate) fn send_op(
- &self,
- op: &str,
- verb: Option<&str>,
- win: Option<bool>,
- score: Option<i32>,
- video_url: Option<String>,
- ) {
- // we send_op only if embedded
- if !self.is_embedded {
- return;
- }
- if let Some(window) = web_sys::window()
- && let Some(parent) = window.parent().ok().flatten()
- {
- log::info!(
- "eatyourgreens:: send op: {} with verb {:?} win {:?} score {:?}",
- op,
- verb,
- win,
- score
- );
- let msg = Message {
- op,
- verb,
- win,
- score,
- video_url,
- };
- Message::post(&parent, &msg);
- }
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/lib.rs b/2026/games_submissions/rpc2dot0/src/lib.rs
deleted file mode 100644
index 31e4d1e..0000000
--- a/2026/games_submissions/rpc2dot0/src/lib.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use crate::constants::{HEIGHT, WIDTH};
-use wasm_bindgen::prelude::*;
-mod assets;
-pub(crate) mod collision;
-pub(crate) mod color;
-pub(crate) mod computer;
-pub(crate) mod constants;
-pub(crate) mod disquette;
-pub(crate) mod effects;
-pub(crate) mod entity;
-pub(crate) mod errors;
-mod game;
-pub(crate) mod math;
-pub(crate) mod message;
-pub(crate) mod random;
-pub(crate) mod state;
-
-#[wasm_bindgen]
-pub fn main_js(is_embedded: bool) {
- teleia::run(
- WIDTH,
- HEIGHT,
- teleia::Options::FULLSCREEN_MOUSE,
- move |ctx| {
- let game = game::Game::new(ctx, is_embedded);
- game.send_op("ready", None, None, None, None);
- game
- },
- );
-}
-
-#[wasm_bindgen]
-pub fn set_difficulty_js(difficulty: f32) {
- teleia::contextualize(|_ctx, _st, game: &mut game::Game| game.set_difficulty(difficulty))
-}
-
-#[wasm_bindgen]
-pub fn receive_op_js(op: String) {
- teleia::contextualize(|ctx, st, game: &mut game::Game| game.receive_op(ctx, st, op.clone()))
-}
diff --git a/2026/games_submissions/rpc2dot0/src/math.rs b/2026/games_submissions/rpc2dot0/src/math.rs
deleted file mode 100644
index 555204f..0000000
--- a/2026/games_submissions/rpc2dot0/src/math.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-pub use glam::Vec2;
-
-pub trait BoundingCircle {
- fn center(&self) -> Vec2;
- fn radius(&self) -> f32;
-
- // Intersection between two bounding circles.
- // compares squared distance to squared sum of radiuSUS (*insert mrGreen.png*),
- // tl;dr: a naive collision detection.
- fn overlaps_with(&self, other: &impl BoundingCircle) -> bool {
- let radius_sum = self.radius() + other.radius();
- self.center().distance_squared(other.center()) <= radius_sum * radius_sum
- }
-}
-
-#[derive(Clone, Copy)]
-pub struct Rectangle {
- pub min: Vec2,
- pub max: Vec2,
-}
-
-pub trait ContainsPoint {
- fn contains(&self, point: Vec2) -> bool;
-}
-
-impl ContainsPoint for Rectangle {
- fn contains(&self, point: Vec2) -> bool {
- point.cmpge(self.min).all() && point.cmple(self.max).all()
- }
-}
-
-impl Rectangle {
- pub const fn from_xywh(x: f32, y: f32, width: f32, height: f32) -> Self {
- Self {
- min: Vec2::new(x, y),
- max: Vec2::new(x + width, y + height),
- }
- }
-
- pub const fn from_min_max(min: Vec2, max: Vec2) -> Self {
- Self { min, max }
- }
-
- #[allow(dead_code)]
- pub fn overlaps(&self, other: &Rectangle) -> bool {
- self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
- }
-}
-
-pub fn parse_coords(coords: &str) -> (f32, f32) {
- let mut parts = coords.split(',');
- let x: f32 = parts.next().unwrap_or("0").parse().unwrap_or(0.0);
- let y: f32 = parts.next().unwrap_or("0").parse().unwrap_or(0.0);
- (x, y)
-}
diff --git a/2026/games_submissions/rpc2dot0/src/message.rs b/2026/games_submissions/rpc2dot0/src/message.rs
deleted file mode 100644
index d960b67..0000000
--- a/2026/games_submissions/rpc2dot0/src/message.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use serde::Serialize;
-use std::fmt::Debug;
-
-#[derive(Serialize, Debug)]
-pub struct Message<'a> {
- pub op: &'a str,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub verb: Option<&'a str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub win: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub score: Option<i32>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub video_url: Option<String>,
-}
-
-impl<'a> Message<'a> {
- pub fn post<MSG>(window: &web_sys::Window, msg: &MSG)
- where
- MSG: Serialize + Debug,
- {
- log::info!("eatyourgreens:: post message: {:?}", msg);
- let js_value = serde_wasm_bindgen::to_value(msg).unwrap();
- let _ = window.post_message(&js_value, "*");
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/random.rs b/2026/games_submissions/rpc2dot0/src/random.rs
deleted file mode 100644
index 14a9f78..0000000
--- a/2026/games_submissions/rpc2dot0/src/random.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use crate::color::EntityColor;
-use crate::constants::get_baseline_ratio;
-use crate::math::Vec2;
-
-use rand::{Rng, RngCore, SeedableRng};
-use rand_chacha::ChaCha8Rng;
-
-#[derive(Debug, Clone)]
-pub struct RandomState {
- rng: ChaCha8Rng,
-}
-
-impl RandomState {
- /// RandomState::new(0x67676767) when debugging
- pub fn new(seed: usize) -> Self {
- log::info!("eatyourgreens:: RandomState new seed is {:?}", seed);
- Self {
- rng: ChaCha8Rng::seed_from_u64(seed as u64),
- }
- }
-
- pub fn next_u32(&mut self) -> u32 {
- self.rng.next_u32()
- }
-
- pub fn next_f32(&mut self) -> f32 {
- self.rng.r#gen::<f32>()
- }
-
- pub fn next_range(&mut self, min: f32, max: f32) -> f32 {
- self.rng.gen_range(min..max)
- }
-
- pub fn next_unit_vector(&mut self) -> Vec2 {
- let angle = self.rng.gen_range(0.0..std::f32::consts::TAU);
- Vec2::new(angle.cos(), angle.sin())
- }
-
- pub fn next_bool(&mut self, p: f64) -> bool {
- self.rng.gen_bool(p)
- }
-
- // this is used for rogue colors,
- // when used for random spawn, there is a risk
- // of color overcrowding. (like too much green or whatever)
- pub fn random_color(&mut self) -> EntityColor {
- let next_color_prob = self.next_f32();
- match next_color_prob {
- x if x < 0.60 => EntityColor::Green,
- x if x < 0.80 => EntityColor::Yellow,
- x if x < 0.90 => EntityColor::Red,
- _ => EntityColor::Bleu,
- }
- }
-
- // don't give it too much toughts this is designed by a clanker (cause too much math hurts my soul, I am not sorry)
- // coded by yours truely something within the spirit of
- // wiki :
- // https://en.wikipedia.org/wiki/Fitness_proportionate_selection
- // https://en.wikipedia.org/wiki/Reservoir_sampling#Weighted_random_sampling
-
- // we try to have a basline that is adjustable by difficulty
- // then we have weights for our biased random picker
- // used to spanwing entities
- // seed compatiable aka : reproducible behaviour with a fixed D and S (D=difficulty, S=seed)
- /*
- difficulty 1.0 => Green: 0.60 Red: 0.10 (baseline, no shift)
- difficulty 1.5 => Green: 0.525 Red: 0.175 (shift = 0.075)
- difficulty 2.0 => Green: 0.45 Red: 0.25 (shift = 0.15)
- difficulty 3.0 => Green: 0.30 Red: 0.40 (shift = 0.30)
- difficulty 5.0 => Green: 0.05 Red: 0.65 (capped: Green can't go below 5%)
- */
- // not tested and I won't test it, fuck that.
- pub fn random_color_biased(&mut self, counts: [u32; 4], difficulty: f32) -> EntityColor {
- let baseline: [f32; 4] = {
- let mut b = [0.0f32; 4];
- b[EntityColor::Red as usize] = get_baseline_ratio(EntityColor::Red, difficulty);
- b[EntityColor::Green as usize] = get_baseline_ratio(EntityColor::Green, difficulty);
- b[EntityColor::Bleu as usize] = get_baseline_ratio(EntityColor::Bleu, difficulty);
- b[EntityColor::Yellow as usize] = get_baseline_ratio(EntityColor::Yellow, difficulty);
- b
- };
-
- let total = counts.iter().sum::<u32>().max(1) as f32;
-
- let mut weights = [0.0f32; 4];
- for i in 0..4 {
- let actual_ratio = counts[i] as f32 / total;
- weights[i] = (2.0 * baseline[i] - actual_ratio).max(0.05);
- }
- let sum: f32 = weights.iter().sum();
- let roll: f32 = self.next_f32() * sum;
- let mut cumulative: f32 = 0.0f32;
- for (i, &w) in weights.iter().enumerate() {
- cumulative += w;
- if roll < cumulative {
- return match i {
- 0 => EntityColor::Red,
- 1 => EntityColor::Green,
- 2 => EntityColor::Bleu,
- _ => EntityColor::Yellow,
- };
- }
- }
- unreachable!("Should never happen")
- }
-}
diff --git a/2026/games_submissions/rpc2dot0/src/state.rs b/2026/games_submissions/rpc2dot0/src/state.rs
deleted file mode 100644
index c084bfc..0000000
--- a/2026/games_submissions/rpc2dot0/src/state.rs
+++ /dev/null
@@ -1,484 +0,0 @@
-use crate::collision::process_collisions;
-use crate::color::EntityColor;
-use crate::computer::ComputerPhase;
-use crate::constants::{
- BASE_SPEED, ENTITY_RADIUS, ENTITY_SCALE_FACTOR, GAME_DURATION_MS, INITIAL_SPAWN,
- INITIAL_SPAWN_SPREAD_MS, JITTER_STRENGTH, MAX_ENTITIES, MAX_SPEED, PACMAN_CHASE_PROB,
- PACMAN_CHASE_SPEED_MULT, RED_COUNTDOWN_MS, ROGUE_DURATION_MS, SPAWN_PADDING, SPAWN_RATE_MAX_MS,
- SPAWN_RATE_MIN_MS,
-};
-use crate::effects::{PacmanChase, Rogue};
-use crate::entity::{Entity, Update};
-use crate::errors::ClickOutcome;
-use crate::math::Vec2;
-use crate::random::RandomState;
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum GamePhase {
- Waiting,
- Playing,
- Computer(ComputerPhase),
- Finished,
-}
-
-impl GamePhase {
- #[allow(dead_code)]
- pub fn phase(&self) -> &'static str {
- match self {
- GamePhase::Waiting => "waiting",
- GamePhase::Playing => "playing",
- GamePhase::Computer(_) => "computer",
- GamePhase::Finished => "finished",
- }
- }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum RoundResult {
- Ok,
- Ko,
-}
-
-impl From<RoundResult> for &'static str {
- fn from(value: RoundResult) -> Self {
- match value {
- RoundResult::Ok => "Ok",
- RoundResult::Ko => "Ko",
- }
- }
-}
-
-impl From<RoundResult> for bool {
- fn from(value: RoundResult) -> Self {
- match value {
- RoundResult::Ok => true,
- RoundResult::Ko => false,
- }
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct GameState {
- consumer_score: i32,
- pub(crate) elapsed_ms: f32,
- phase: GamePhase,
- pub(crate) entities: Vec<Entity>,
- pub(crate) next_entity_id: u32,
- pub(crate) rng: RandomState,
- spawn_timer_ms: f32,
- initial_spawn_remaining: u32,
- next_spawn_delay_ms: f32,
- pub(crate) collision_buffer: Vec<(usize, usize)>,
- pub(crate) events: Vec<String>,
- pub(crate) difficulty: f32,
-}
-
-impl Default for GameState {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl GameState {
- pub fn new() -> Self {
- Self {
- consumer_score: 0,
- elapsed_ms: 0.0,
- phase: GamePhase::Waiting,
- entities: Vec::new(),
- next_entity_id: 1,
- #[cfg(debug_assertions)]
- rng: RandomState::new(0x67676767),
- #[cfg(not(debug_assertions))]
- rng: RandomState::new((js_sys::Date::now() as u64) as usize),
- spawn_timer_ms: 0.0,
- initial_spawn_remaining: INITIAL_SPAWN,
- next_spawn_delay_ms: (INITIAL_SPAWN_SPREAD_MS / INITIAL_SPAWN as f32).max(1.0),
- collision_buffer: Vec::new(),
- events: Vec::new(),
- difficulty: 1.0,
- }
- }
-
- pub fn set_difficulty(&mut self, difficulty: f32) {
- self.difficulty = difficulty;
- }
-
- pub fn game_duration_ms(&self) -> f32 {
- GAME_DURATION_MS as f32 / self.difficulty
- }
-
- pub fn required_greens(&self) -> i32 {
- 5.max((15.0 / self.difficulty.sqrt()).round() as i32)
- }
-
- pub const fn base_speed(&self) -> f32 {
- BASE_SPEED * self.difficulty
- }
-
- pub fn max_speed(&self) -> f32 {
- MAX_SPEED * self.difficulty
- }
-
- pub fn spawn_rate_min_ms(&self) -> u32 {
- (SPAWN_RATE_MIN_MS as f32 / self.difficulty) as u32
- }
-
- pub fn spawn_rate_max_ms(&self) -> u32 {
- (SPAWN_RATE_MAX_MS as f32 / self.difficulty) as u32
- }
-
- pub fn entity_radius(&self) -> f32 {
- ENTITY_RADIUS / (1.0 + (self.difficulty - 1.0) * 0.25)
- }
-
- pub fn start_round(&mut self) {
- self.consumer_score = 0;
- self.elapsed_ms = 0.0;
- self.phase = GamePhase::Playing;
- self.entities.clear();
- self.next_entity_id = 1;
- self.spawn_timer_ms = 0.0;
- self.initial_spawn_remaining = INITIAL_SPAWN;
- self.next_spawn_delay_ms = (INITIAL_SPAWN_SPREAD_MS / INITIAL_SPAWN as f32).max(1.0);
- self.events.clear();
- }
- pub fn set_phase(&mut self, phase: GamePhase) {
- self.phase = phase;
- }
- /*
- pub fn boot_computer(&mut self) {
- if self.phase == GamePhase::Playing {
- self.phase = GamePhase::Computer(ComputerPhase::Booting);
- }
- }
-
- pub fn shutdown_computer(&mut self) {
- if self.phase == GamePhase::Computer(ComputerPhase::On) {
- self.phase = GamePhase::Computer(ComputerPhase::ShutDown);
- }
- }
- */
-
- pub fn spawn_entity(
- &mut self,
- pos: Vec2,
- vel: Vec2,
- color: EntityColor,
- disquette_inserted: bool,
- ) {
- if self.get_entity_count() >= MAX_ENTITIES {
- return;
- }
- let speed = self.base_speed();
- let radius = self.entity_radius() * ENTITY_SCALE_FACTOR;
- let mut entity = Entity::new(self.next_entity_id, pos, vel, color, speed, radius);
- if color == EntityColor::Red {
- entity.spawn_time_ms = Some(self.elapsed_ms);
- }
- if color == EntityColor::Green {
- let base_prob = 0.25;
- let prob = (base_prob * self.difficulty).min(0.85);
- if self.rng.next_bool(prob as f64) {
- let rogue_duration = ROGUE_DURATION_MS / self.difficulty;
- let mut hover_color = self.rng.random_color();
- while hover_color == EntityColor::Green {
- hover_color = self.rng.random_color();
- }
- entity.rogue = Some(Rogue::new(self.elapsed_ms + rogue_duration, hover_color));
- }
- }
-
- if !disquette_inserted
- && color == EntityColor::Bleu
- && self.rng.next_bool(PACMAN_CHASE_PROB)
- {
- let chase_speed = self.base_speed() * PACMAN_CHASE_SPEED_MULT;
- entity.pacman_chase = Some(PacmanChase::new(chase_speed));
- }
- self.next_entity_id = self.next_entity_id.wrapping_add(1);
- self.entities.push(entity);
- }
- pub fn update_entities(
- &mut self,
- delta_ms: f32,
- area_width: f32,
- area_height: f32,
- disquette_inserted: bool,
- disquette_pos: Option<Vec2>,
- ) {
- self.update_spawning(delta_ms, area_width, area_height, disquette_inserted);
-
- let current_max_speed = self.max_speed();
- for entity in &mut self.entities {
- if !entity.alive {
- continue;
- }
- if let Some(chase) = &entity.pacman_chase {
- if let Some(target) = disquette_pos {
- let dx = target.x - entity.pos.x;
- let dy = target.y - entity.pos.y;
- let dist = (dx * dx + dy * dy).sqrt();
- if dist > f32::EPSILON {
- entity.vel.x = (dx / dist) * chase.speed;
- entity.vel.y = (dy / dist) * chase.speed;
- }
- } else {
- entity.apply_jitter(Vec2::new(
- self.rng.next_range(-JITTER_STRENGTH, JITTER_STRENGTH),
- self.rng.next_range(-JITTER_STRENGTH, JITTER_STRENGTH),
- ));
- }
- } else {
- entity.apply_jitter(Vec2::new(
- self.rng.next_range(-JITTER_STRENGTH, JITTER_STRENGTH),
- self.rng.next_range(-JITTER_STRENGTH, JITTER_STRENGTH),
- ));
- }
-
- entity.update(delta_ms);
- entity.dampen_speed(current_max_speed);
- entity.bounds_bounce(area_width, area_height);
- }
- if let Some(target) = disquette_pos {
- const DISQUETTE_HALF_SIZE: f32 = 3.2;
- let mut consumed = false;
- for entity in &self.entities {
- if entity.alive
- && entity.pacman_chase.is_some()
- && entity.pos.distance_squared(target)
- <= (entity.radius + DISQUETTE_HALF_SIZE).powi(2)
- {
- consumed = true;
- break;
- }
- }
- if consumed {
- self.events.push("disquette_consumed".to_string());
- }
- }
-
- process_collisions(
- &mut self.entities,
- &mut self.rng,
- &mut self.next_entity_id,
- &mut self.collision_buffer,
- self.elapsed_ms,
- &mut self.events,
- );
-
- // Check red countdown timers (explode if expired)
- let mut greens_to_kill = false;
- for entity in &mut self.entities {
- if entity.alive
- && entity.color == EntityColor::Red
- && let Some(spawn_time) = entity.spawn_time_ms
- && self.elapsed_ms >= spawn_time + RED_COUNTDOWN_MS
- {
- entity.alive = false;
- self.events
- .push(format!("explode:{},{}", entity.pos.x, entity.pos.y));
- greens_to_kill = true;
- }
- }
- if greens_to_kill {
- for entity in &mut self.entities {
- if entity.alive && entity.color == EntityColor::Green {
- entity.alive = false;
- }
- }
- }
-
- self.entities.retain(|e| e.alive);
- }
-
- pub fn entities(&self) -> &[Entity] {
- &self.entities
- }
-
- fn schedule_next_spawn(&mut self) {
- self.next_spawn_delay_ms = if self.initial_spawn_remaining > 0 {
- (INITIAL_SPAWN_SPREAD_MS / INITIAL_SPAWN as f32).max(1.0)
- } else {
- self.rng
- .next_range(
- self.spawn_rate_min_ms() as f32,
- self.spawn_rate_max_ms() as f32,
- )
- .round()
- .max(1.0)
- };
- }
-
- fn position_overlaps(&self, pos: Vec2) -> bool {
- let scaled_radius = self.entity_radius() * ENTITY_SCALE_FACTOR;
- self.entities.iter().any(|entity| {
- let distance_sq = entity.pos.distance_squared(pos);
- distance_sq < (entity.radius + scaled_radius + SPAWN_PADDING).powi(2)
- })
- }
-
- fn generate_spawn_position(&mut self, area_width: f32, area_height: f32) -> Vec2 {
- let scaled_radius = self.entity_radius() * ENTITY_SCALE_FACTOR;
- const MAX_ATTEMPTS: usize = 16;
- for _ in 0..MAX_ATTEMPTS {
- let pos = Vec2::new(
- self.rng
- .next_range(scaled_radius, area_width - scaled_radius),
- self.rng
- .next_range(scaled_radius, area_height - scaled_radius),
- );
- if !self.position_overlaps(pos) {
- return pos;
- }
- }
- Vec2::new(
- self.rng
- .next_range(scaled_radius, area_width - scaled_radius),
- self.rng
- .next_range(scaled_radius, area_height - scaled_radius),
- )
- }
-
- fn spawn_random_entity(&mut self, area_width: f32, area_height: f32, disquette_inserted: bool) {
- if self.get_entity_count() >= MAX_ENTITIES {
- return;
- }
-
- let pos = self.generate_spawn_position(area_width, area_height);
- let dir = self.rng.next_unit_vector();
- let vel = dir * self.base_speed();
-
- let mut color_counts = [0u32; 4];
- for entity in &self.entities {
- if entity.alive {
- color_counts[entity.color as usize] += 1;
- }
- }
- let color = self.rng.random_color_biased(color_counts, self.difficulty);
- self.spawn_entity(pos, vel, color, disquette_inserted);
- }
-
- fn update_spawning(
- &mut self,
- delta_ms: f32,
- area_width: f32,
- area_height: f32,
- disquette_inserted: bool,
- ) {
- if self.phase != GamePhase::Playing || area_width <= 0.0 || area_height <= 0.0 {
- return;
- }
-
- self.spawn_timer_ms += delta_ms;
- while self.spawn_timer_ms >= self.next_spawn_delay_ms {
- self.spawn_timer_ms -= self.next_spawn_delay_ms;
- if self.get_entity_count() >= MAX_ENTITIES {
- self.spawn_timer_ms = 0.0;
- break;
- }
-
- if self.initial_spawn_remaining > 0 {
- self.spawn_random_entity(area_width, area_height, disquette_inserted);
- self.initial_spawn_remaining = self.initial_spawn_remaining.saturating_sub(1);
- } else {
- self.spawn_random_entity(area_width, area_height, disquette_inserted);
- }
-
- self.schedule_next_spawn();
- }
- }
-
- fn entity_at_point(&self, point: Vec2) -> Option<usize> {
- self.entities
- .iter()
- .enumerate()
- .rev()
- .find(|(_, e)| e.alive && e.pos.distance_squared(point) <= e.radius * e.radius)
- .map(|(idx, _)| idx)
- }
-
- pub fn click_at(&mut self, x: f32, y: f32) -> Option<ClickOutcome> {
- //log::info!("eatyourgreens:: click_at(x={x},y={y})");
- if self.phase != GamePhase::Playing {
- return None;
- }
-
- let target = self.entity_at_point(Vec2::new(x, y));
- if let Some(idx) = target {
- let color = self.entities[idx].color;
-
- let mut effective_color = color;
- if color == EntityColor::Green
- && let Some(rogue) = &self.entities[idx].rogue
- && self.elapsed_ms < rogue.until_ms
- {
- effective_color = rogue.hover_color;
- }
-
- self.entities[idx].alive = false;
- match effective_color {
- EntityColor::Green => self.change_score(1),
- EntityColor::Yellow | EntityColor::Red | EntityColor::Bleu => self.change_score(-1),
- }
-
- if effective_color == EntityColor::Red {
- self.events.push(format!("click_red:{},{}", x, y));
- }
- return Some(ClickOutcome::Hit(effective_color));
- }
- Some(ClickOutcome::Miss)
- }
-
- pub fn consumer_score(&self) -> i32 {
- self.consumer_score
- }
-
- pub fn elapsed_ms(&self) -> f32 {
- self.elapsed_ms
- }
-
- pub fn remaining_ms(&self) -> f32 {
- self.game_duration_ms() - self.elapsed_ms
- }
-
- pub fn phase(&self) -> GamePhase {
- self.phase
- }
-
- pub fn round_result(&self) -> RoundResult {
- if self.consumer_score >= self.required_greens() {
- RoundResult::Ok
- } else {
- RoundResult::Ko
- }
- }
-
- pub fn advance_time(&mut self, delta_ms: f32) {
- if self.phase != GamePhase::Playing {
- return;
- }
-
- self.elapsed_ms += delta_ms;
- let duration = self.game_duration_ms();
- if self.elapsed_ms >= duration {
- self.elapsed_ms = duration;
- self.phase = GamePhase::Finished;
- }
- }
-
- pub fn drain_events(&mut self) -> Vec<String> {
- std::mem::take(&mut self.events)
- }
-
- pub fn get_entity_count(&self) -> u32 {
- self.entities.iter().filter(|e| e.alive).count() as u32
- }
-
- pub fn change_score(&mut self, delta: i32) {
- if self.phase != GamePhase::Playing {
- return;
- }
- self.consumer_score = self.consumer_score.saturating_add(delta);
- }
-}