From 9be7b102d79beb628e18788d91e873654fb85734 Mon Sep 17 00:00:00 2001 From: Michael Bradley Date: Sun, 18 May 2025 22:54:54 -0400 Subject: [PATCH] Extract main.rs --- src/lib.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 226 +--------------------------------------------------- 2 files changed, 228 insertions(+), 224 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..064a8fe --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,226 @@ +use std::{ + f32::consts::PI, + hash::{DefaultHasher, Hash, Hasher}, + ops::Range, +}; + +use avian2d::{math::Vector, prelude::*}; +use bevy::{ + color::palettes::{ + css::WHITE, + tailwind::{LIME_400, RED_400}, + }, + input::common_conditions::input_pressed, + prelude::*, +}; +use bevy_rand::prelude::{EntropyPlugin, GlobalEntropy, WyRand}; +use clap::Parser; +use rand::Rng; +use wyrand::WyRand as LocalRng; + +#[derive(Parser)] +#[command(version, about)] +pub struct AppSettings { + #[arg(short, long, default_value = "")] + seed: String, +} + +const BALL_COUNT: u8 = 32; +const BALL_SIZES: Range = 10.0..25.0; +const DIMENSION_SIZES: Range = 500.0..2000.0; + +#[derive(Clone, Resource)] +struct Seed(u64); + +impl From for Seed { + fn from(value: String) -> Self { + Self(value.parse::().unwrap_or_else(|_| { + let mut state = DefaultHasher::new(); + value.hash(&mut state); + state.finish() + })) + } +} + +impl From for [u8; 8] { + fn from(value: Seed) -> Self { + value.0.to_le_bytes() + } +} + +#[derive(Resource)] +struct PlayableArea(f32, f32); + +#[derive(Resource)] +struct PlayerSize(f32); + +pub fn run_app(settings: AppSettings) -> AppExit { + let seed = Seed::from(settings.seed); + App::new() + .insert_resource(Gravity(Vector::ZERO)) + .insert_resource(seed.clone()) + .add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Window { + title: "Distributed physics test".into(), + fit_canvas_to_parent: true, + ..default() + } + .into(), + ..default() + }), + PhysicsPlugins::default().with_length_unit(50.0), + EntropyPlugin::::with_seed(seed.into()), + )) + .add_systems( + Startup, + ( + setup_pseudo_random, + setup_ui, + (setup_player, setup_balls, setup_walls).after(setup_pseudo_random), + ), + ) + .add_systems( + Update, + (move_player, quit.run_if(input_pressed(KeyCode::KeyQ))), + ) + .add_systems(PostUpdate, move_camera) + .run() +} + +fn setup_pseudo_random(mut commands: Commands, mut rng: GlobalEntropy) { + commands.insert_resource(PlayerSize(rng.random_range(BALL_SIZES))); + commands.insert_resource(PlayableArea( + rng.random_range(DIMENSION_SIZES), + rng.random_range(DIMENSION_SIZES), + )); +} + +fn setup_ui(mut commands: Commands) { + commands.spawn((Name::new("Camera"), Camera2d, IsDefaultUiCamera)); +} + +#[derive(Component, Default)] +#[require(Collider, Mesh2d, MeshMaterial2d, Restitution = Restitution::new(1.0), RigidBody, TransformInterpolation, Transform)] +struct GameObject; + +#[derive(Component, Default)] +#[require(GameObject, RigidBody = RigidBody::Dynamic)] +struct Ball; + +#[derive(Component, Default)] +#[require(Ball)] +struct Player; + +fn setup_player( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + radius: Res, +) { + let circle = Circle::new(radius.0); + commands.spawn(( + Player, + Collider::from(circle), + Mesh2d(meshes.add(circle)), + MeshMaterial2d(materials.add(Color::from(LIME_400))), + )); +} + +fn setup_balls( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + region: Res, +) { + let mut random = LocalRng::new(Default::default()); + for _ in 0..BALL_COUNT { + let circle = Circle::new(random.random_range(BALL_SIZES)); + commands.spawn(( + Ball, + Collider::from(circle), + Mesh2d(meshes.add(circle)), + MeshMaterial2d(materials.add(Color::from(RED_400))), + Transform::from_xyz( + random.random_range( + (-region.0 / 2.0 + circle.radius)..(region.0 / 2.0 - circle.radius), + ), + random.random_range( + (-region.1 / 2.0 + circle.radius)..(region.1 / 2.0 - circle.radius), + ), + 0.0, + ), + )); + } +} + +#[derive(Component, Default)] +#[require(GameObject, RigidBody = RigidBody::Static)] +struct Wall; + +fn setup_walls( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, + region: Res, +) { + let thickness = 20.0; + + for i in 0..4 { + let (offset, length) = if i % 2 == 0 { + (region.0, region.1 + thickness) + } else { + (region.1, region.0 + thickness) + }; + + let mut transform = Transform::from_xyz(0.0, offset / 2.0, 0.0); + transform.rotate_around( + Vec3::ZERO, + Quat::from_rotation_z(((i + 1) as f32) * PI / 2.0), + ); + + commands.spawn(( + Wall, + Collider::rectangle(length, thickness), + Mesh2d(meshes.add(Rectangle::new(length, thickness))), + MeshMaterial2d(materials.add(Color::from(WHITE))), + transform, + )); + } +} + +fn move_player( + time: Res