From de99c5f7636a952025ea13de02bafd25bcb6bdec Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Mon, 17 Aug 2026 20:14:16 -0400 Subject: Fix broken --- 2026/games/rpc2dot0/src/assets.rs | 38 - 2026/games/rpc2dot0/src/assets/audio/crunch.wav | Bin 123650 -> 0 bytes 2026/games/rpc2dot0/src/assets/audio/explosion.wav | Bin 13387 -> 0 bytes 2026/games/rpc2dot0/src/assets/audio/fart.wav | Bin 382542 -> 0 bytes 2026/games/rpc2dot0/src/assets/audio/footsteps.wav | Bin 140588 -> 0 bytes 2026/games/rpc2dot0/src/assets/audio/test.wav | Bin 924 -> 0 bytes 2026/games/rpc2dot0/src/assets/meshes/square.obj | 15 - .../rpc2dot0/src/assets/shaders/flat/frag.glsl | 11 - .../rpc2dot0/src/assets/shaders/flat/vert.glsl | 4 - .../src/assets/textures/background_main.png | Bin 572 -> 0 bytes .../rpc2dot0/src/assets/textures/computer.png | Bin 3616 -> 0 bytes .../rpc2dot0/src/assets/textures/disquette.png | Bin 445 -> 0 bytes .../games/rpc2dot0/src/assets/textures/landing.png | Bin 12693 -> 0 bytes 2026/games/rpc2dot0/src/assets/textures/mrbleu.png | Bin 1499 -> 0 bytes .../src/assets/textures/mrbleu_lurking.png | Bin 949 -> 0 bytes .../games/rpc2dot0/src/assets/textures/mrgreen.png | Bin 1492 -> 0 bytes 2026/games/rpc2dot0/src/assets/textures/mrred.png | Bin 1506 -> 0 bytes .../rpc2dot0/src/assets/textures/mryellow.png | Bin 2489 -> 0 bytes 2026/games/rpc2dot0/src/assets/textures/mute.png | Bin 544 -> 0 bytes 2026/games/rpc2dot0/src/assets/textures/unmute.png | Bin 545 -> 0 bytes .../rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 | Bin 1695306 -> 0 bytes .../rpc2dot0/src/assets/video/adzzz/confused_c.mp4 | Bin 1213146 -> 0 bytes .../src/assets/video/adzzz/game_over_c.mp4 | Bin 580466 -> 0 bytes .../rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 | Bin 1635459 -> 0 bytes .../rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 | Bin 737983 -> 0 bytes .../src/assets/video/adzzz/seks_is_bad.mp4 | Bin 1440141 -> 0 bytes .../src/assets/video/adzzz/seks_is_gud.mp4 | Bin 541533 -> 0 bytes 2026/games/rpc2dot0/src/collision.rs | 99 --- 2026/games/rpc2dot0/src/color.rs | 25 - 2026/games/rpc2dot0/src/computer.rs | 183 ----- 2026/games/rpc2dot0/src/computer/os.rs | 117 --- 2026/games/rpc2dot0/src/constants.rs | 109 --- 2026/games/rpc2dot0/src/disquette.rs | 184 ----- 2026/games/rpc2dot0/src/effects.rs | 58 -- 2026/games/rpc2dot0/src/entity.rs | 90 --- 2026/games/rpc2dot0/src/errors.rs | 7 - 2026/games/rpc2dot0/src/game.rs | 869 --------------------- 2026/games/rpc2dot0/src/lib.rs | 40 - 2026/games/rpc2dot0/src/math.rs | 55 -- 2026/games/rpc2dot0/src/message.rs | 26 - 2026/games/rpc2dot0/src/random.rs | 107 --- 2026/games/rpc2dot0/src/state.rs | 484 ------------ 42 files changed, 2521 deletions(-) delete mode 100644 2026/games/rpc2dot0/src/assets.rs delete mode 100644 2026/games/rpc2dot0/src/assets/audio/crunch.wav delete mode 100644 2026/games/rpc2dot0/src/assets/audio/explosion.wav delete mode 100644 2026/games/rpc2dot0/src/assets/audio/fart.wav delete mode 100644 2026/games/rpc2dot0/src/assets/audio/footsteps.wav delete mode 100644 2026/games/rpc2dot0/src/assets/audio/test.wav delete mode 100644 2026/games/rpc2dot0/src/assets/meshes/square.obj delete mode 100644 2026/games/rpc2dot0/src/assets/shaders/flat/frag.glsl delete mode 100644 2026/games/rpc2dot0/src/assets/shaders/flat/vert.glsl delete mode 100644 2026/games/rpc2dot0/src/assets/textures/background_main.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/computer.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/disquette.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/landing.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mrbleu.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mrbleu_lurking.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mrgreen.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mrred.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mryellow.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/mute.png delete mode 100644 2026/games/rpc2dot0/src/assets/textures/unmute.png delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/confused_c.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4 delete mode 100644 2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4 delete mode 100644 2026/games/rpc2dot0/src/collision.rs delete mode 100644 2026/games/rpc2dot0/src/color.rs delete mode 100644 2026/games/rpc2dot0/src/computer.rs delete mode 100644 2026/games/rpc2dot0/src/computer/os.rs delete mode 100644 2026/games/rpc2dot0/src/constants.rs delete mode 100644 2026/games/rpc2dot0/src/disquette.rs delete mode 100644 2026/games/rpc2dot0/src/effects.rs delete mode 100644 2026/games/rpc2dot0/src/entity.rs delete mode 100644 2026/games/rpc2dot0/src/errors.rs delete mode 100644 2026/games/rpc2dot0/src/game.rs delete mode 100644 2026/games/rpc2dot0/src/lib.rs delete mode 100644 2026/games/rpc2dot0/src/math.rs delete mode 100644 2026/games/rpc2dot0/src/message.rs delete mode 100644 2026/games/rpc2dot0/src/random.rs delete mode 100644 2026/games/rpc2dot0/src/state.rs (limited to '2026/games/rpc2dot0/src') diff --git a/2026/games/rpc2dot0/src/assets.rs b/2026/games/rpc2dot0/src/assets.rs deleted file mode 100644 index fa160a1..0000000 --- a/2026/games/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/rpc2dot0/src/assets/audio/crunch.wav b/2026/games/rpc2dot0/src/assets/audio/crunch.wav deleted file mode 100644 index f11334f..0000000 Binary files a/2026/games/rpc2dot0/src/assets/audio/crunch.wav and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/audio/explosion.wav b/2026/games/rpc2dot0/src/assets/audio/explosion.wav deleted file mode 100644 index c3e867f..0000000 Binary files a/2026/games/rpc2dot0/src/assets/audio/explosion.wav and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/audio/fart.wav b/2026/games/rpc2dot0/src/assets/audio/fart.wav deleted file mode 100644 index ebb001d..0000000 Binary files a/2026/games/rpc2dot0/src/assets/audio/fart.wav and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/audio/footsteps.wav b/2026/games/rpc2dot0/src/assets/audio/footsteps.wav deleted file mode 100644 index 57bfddf..0000000 Binary files a/2026/games/rpc2dot0/src/assets/audio/footsteps.wav and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/audio/test.wav b/2026/games/rpc2dot0/src/assets/audio/test.wav deleted file mode 100644 index 0eabe85..0000000 Binary files a/2026/games/rpc2dot0/src/assets/audio/test.wav and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/meshes/square.obj b/2026/games/rpc2dot0/src/assets/meshes/square.obj deleted file mode 100644 index 7acdef6..0000000 --- a/2026/games/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/rpc2dot0/src/assets/shaders/flat/frag.glsl b/2026/games/rpc2dot0/src/assets/shaders/flat/frag.glsl deleted file mode 100644 index 6ebdfa9..0000000 --- a/2026/games/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/rpc2dot0/src/assets/shaders/flat/vert.glsl b/2026/games/rpc2dot0/src/assets/shaders/flat/vert.glsl deleted file mode 100644 index 6e796cc..0000000 --- a/2026/games/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/rpc2dot0/src/assets/textures/background_main.png b/2026/games/rpc2dot0/src/assets/textures/background_main.png deleted file mode 100644 index 82b3ebe..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/background_main.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/computer.png b/2026/games/rpc2dot0/src/assets/textures/computer.png deleted file mode 100644 index be5c723..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/computer.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/disquette.png b/2026/games/rpc2dot0/src/assets/textures/disquette.png deleted file mode 100644 index 12399f4..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/disquette.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/landing.png b/2026/games/rpc2dot0/src/assets/textures/landing.png deleted file mode 100644 index 229e6ad..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/landing.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mrbleu.png b/2026/games/rpc2dot0/src/assets/textures/mrbleu.png deleted file mode 100644 index d16a717..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mrbleu.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mrbleu_lurking.png b/2026/games/rpc2dot0/src/assets/textures/mrbleu_lurking.png deleted file mode 100644 index 3217129..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mrbleu_lurking.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mrgreen.png b/2026/games/rpc2dot0/src/assets/textures/mrgreen.png deleted file mode 100644 index 2eb7210..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mrgreen.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mrred.png b/2026/games/rpc2dot0/src/assets/textures/mrred.png deleted file mode 100644 index 2c4bc18..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mrred.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mryellow.png b/2026/games/rpc2dot0/src/assets/textures/mryellow.png deleted file mode 100644 index 9784a8f..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mryellow.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/mute.png b/2026/games/rpc2dot0/src/assets/textures/mute.png deleted file mode 100644 index b4b81dd..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/mute.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/textures/unmute.png b/2026/games/rpc2dot0/src/assets/textures/unmute.png deleted file mode 100644 index 278d227..0000000 Binary files a/2026/games/rpc2dot0/src/assets/textures/unmute.png and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 deleted file mode 100644 index c24a0d9..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/GG_win_c.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/confused_c.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/confused_c.mp4 deleted file mode 100644 index 0c7756d..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/confused_c.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4 deleted file mode 100644 index bccb605..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/game_over_c.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 deleted file mode 100644 index 909c56a..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/hyped_c.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 deleted file mode 100644 index 38cdc53..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/pinnesse_c.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4 deleted file mode 100644 index fb9d490..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_bad.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4 b/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4 deleted file mode 100644 index 7aac4d5..0000000 Binary files a/2026/games/rpc2dot0/src/assets/video/adzzz/seks_is_gud.mp4 and /dev/null differ diff --git a/2026/games/rpc2dot0/src/collision.rs b/2026/games/rpc2dot0/src/collision.rs deleted file mode 100644 index aaae9a7..0000000 --- a/2026/games/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, - rng: &mut RandomState, - _next_entity_id: &mut u32, - collision_buffer: &mut Vec<(usize, usize)>, - elapsed_ms: f32, - _events: &mut Vec, -) { - 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/rpc2dot0/src/color.rs b/2026/games/rpc2dot0/src/color.rs deleted file mode 100644 index ed81ec4..0000000 --- a/2026/games/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/rpc2dot0/src/computer.rs b/2026/games/rpc2dot0/src/computer.rs deleted file mode 100644 index e25fb65..0000000 --- a/2026/games/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 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 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 { - 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/rpc2dot0/src/computer/os.rs b/2026/games/rpc2dot0/src/computer/os.rs deleted file mode 100644 index 797e07e..0000000 --- a/2026/games/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 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 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/rpc2dot0/src/constants.rs b/2026/games/rpc2dot0/src/constants.rs deleted file mode 100644 index 0e16b2b..0000000 --- a/2026/games/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/rpc2dot0/src/disquette.rs b/2026/games/rpc2dot0/src/disquette.rs deleted file mode 100644 index 7940d17..0000000 --- a/2026/games/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 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, - ) -> 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, - ) { - 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/rpc2dot0/src/effects.rs b/2026/games/rpc2dot0/src/effects.rs deleted file mode 100644 index 02b33ef..0000000 --- a/2026/games/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/rpc2dot0/src/entity.rs b/2026/games/rpc2dot0/src/entity.rs deleted file mode 100644 index 3b4200f..0000000 --- a/2026/games/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, - pub rogue: Option, - pub pacman_chase: Option, -} - -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/rpc2dot0/src/errors.rs b/2026/games/rpc2dot0/src/errors.rs deleted file mode 100644 index 71a67cf..0000000 --- a/2026/games/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/rpc2dot0/src/game.rs b/2026/games/rpc2dot0/src/game.rs deleted file mode 100644 index 7650e72..0000000 --- a/2026/games/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, - state: GameState, - mouse: glam::Vec2, - explosions: Vec, - greens_wiped_flash: f32, - computer: Computer, - start_button_rect: Option, - is_muted: bool, - mute_button_rect: Option, - is_embedded: bool, - last_time_ms: Option, - finish_time_ms: Option, - 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 { - 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, - score: Option, - video_url: Option, - ) { - // 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/rpc2dot0/src/lib.rs b/2026/games/rpc2dot0/src/lib.rs deleted file mode 100644 index 31e4d1e..0000000 --- a/2026/games/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/rpc2dot0/src/math.rs b/2026/games/rpc2dot0/src/math.rs deleted file mode 100644 index 555204f..0000000 --- a/2026/games/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/rpc2dot0/src/message.rs b/2026/games/rpc2dot0/src/message.rs deleted file mode 100644 index d960b67..0000000 --- a/2026/games/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, - #[serde(skip_serializing_if = "Option::is_none")] - pub score: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub video_url: Option, -} - -impl<'a> Message<'a> { - pub fn post(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/rpc2dot0/src/random.rs b/2026/games/rpc2dot0/src/random.rs deleted file mode 100644 index 14a9f78..0000000 --- a/2026/games/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::() - } - - 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::().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/rpc2dot0/src/state.rs b/2026/games/rpc2dot0/src/state.rs deleted file mode 100644 index c084bfc..0000000 --- a/2026/games/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 for &'static str { - fn from(value: RoundResult) -> Self { - match value { - RoundResult::Ok => "Ok", - RoundResult::Ko => "Ko", - } - } -} - -impl From 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, - 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, - 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, - ) { - 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 { - 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 { - //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 { - 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); - } -} -- cgit v1.3.1