Compare commits

..

2 commits

Author SHA1 Message Date
6a151397de
Add function documentation
All checks were successful
CI / Formatting (push) Successful in 1m30s
2025-05-19 22:19:04 -04:00
fc95857824
Make more random
At least, make the stuff that doesn't need to be deterministic non-deterministic
2025-05-19 21:23:58 -04:00
9 changed files with 38 additions and 12 deletions

View file

@ -10,6 +10,7 @@
"despawn", "despawn",
"Despawns", "Despawns",
"lerp", "lerp",
"PRNG",
"recip", "recip",
"respawns", "respawns",
"timestep", "timestep",

View file

@ -37,5 +37,5 @@ log = { version = "*", features = [
"max_level_debug", "max_level_debug",
"release_max_level_warn", "release_max_level_warn",
] } ] }
rand = { version = "0.9.1", default-features = false, features = ["std"] } rand = { version = "0.9.1", default-features = false, features = ["std", "thread_rng"] }
wyrand = "0.3.2" wyrand = "0.3.2"

View file

@ -1,4 +1,5 @@
pub mod objects; mod objects;
mod rng;
pub mod runtime; pub mod runtime;
pub mod seed; pub mod seed;
pub mod setup; pub mod setup;

View file

@ -1,18 +1,22 @@
use avian2d::prelude::*; use avian2d::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
/// Basic implementation of a physics object
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(Collider, Mesh2d, MeshMaterial2d<ColorMaterial>, Restitution = Restitution::new(1.0), RigidBody, TransformInterpolation, Transform)] #[require(Collider, Mesh2d, MeshMaterial2d<ColorMaterial>, Restitution = Restitution::new(1.0), RigidBody, TransformInterpolation, Transform)]
struct GameObject; struct GameObject;
/// A basic ball with which to interact
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(GameObject, RigidBody = RigidBody::Dynamic)] #[require(GameObject, RigidBody = RigidBody::Dynamic)]
pub struct Ball; pub struct Ball;
/// The controllable ball
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(Ball)] #[require(Ball)]
pub struct Player; pub struct Player;
/// The static objects bounding the playable area
#[derive(Component, Default)] #[derive(Component, Default)]
#[require(GameObject, RigidBody = RigidBody::Static)] #[require(GameObject, RigidBody = RigidBody::Static)]
pub struct Wall; pub struct Wall;

7
src/game/rng.rs Normal file
View file

@ -0,0 +1,7 @@
use rand::random;
use wyrand::WyRand;
/// Initialize a `WyRand` using `rand`'s thread-local random number generator
pub fn thread_rng() -> WyRand {
WyRand::new(random())
}

View file

@ -6,6 +6,7 @@ use bevy::{
use super::objects::Player; use super::objects::Player;
/// Move the player character based on the keyboard input
pub fn move_player( pub fn move_player(
time: Res<Time>, time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
@ -31,10 +32,12 @@ pub fn move_player(
Ok(()) Ok(())
} }
/// Neatly exit game
pub fn quit(mut exit: EventWriter<AppExit>) { pub fn quit(mut exit: EventWriter<AppExit>) {
exit.write(AppExit::Success); exit.write(AppExit::Success);
} }
/// Follow the player character with the camera
pub fn move_camera( pub fn move_camera(
mut camera: Single<&mut Transform, (Without<Player>, With<IsDefaultUiCamera>)>, mut camera: Single<&mut Transform, (Without<Player>, With<IsDefaultUiCamera>)>,
player: Single<&Transform, (With<Player>, Without<IsDefaultUiCamera>)>, player: Single<&Transform, (With<Player>, Without<IsDefaultUiCamera>)>,
@ -42,6 +45,7 @@ pub fn move_camera(
camera.translation = camera.translation.lerp(player.translation, 0.05); camera.translation = camera.translation.lerp(player.translation, 0.05);
} }
/// Adjust the camera zoom based on the scroll wheel input
pub fn zoom_camera( pub fn zoom_camera(
mut camera: Single<&mut Projection, With<IsDefaultUiCamera>>, mut camera: Single<&mut Projection, With<IsDefaultUiCamera>>,
scroll: Res<AccumulatedMouseScroll>, scroll: Res<AccumulatedMouseScroll>,

View file

@ -2,10 +2,12 @@ use std::hash::{DefaultHasher, Hash, Hasher};
use bevy::prelude::*; use bevy::prelude::*;
/// Value with which to initialize the PRNG
#[derive(Clone, Resource)] #[derive(Clone, Resource)]
pub struct Seed(u64); pub struct Seed(u64);
impl From<String> for Seed { impl From<String> for Seed {
/// Attempt to parse as an integer, fall back to hashing string
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self(value.parse::<u64>().unwrap_or_else(|_| { Self(value.parse::<u64>().unwrap_or_else(|_| {
let mut state = DefaultHasher::new(); let mut state = DefaultHasher::new();
@ -16,6 +18,7 @@ impl From<String> for Seed {
} }
impl From<Seed> for [u8; 8] { impl From<Seed> for [u8; 8] {
/// Convert to a u8 array for ingestion by random number generator
fn from(value: Seed) -> Self { fn from(value: Seed) -> Self {
value.0.to_le_bytes() value.0.to_le_bytes()
} }

View file

@ -9,21 +9,26 @@ use bevy::{
prelude::*, prelude::*,
}; };
use bevy_rand::prelude::{GlobalEntropy, WyRand}; use bevy_rand::prelude::{GlobalEntropy, WyRand};
use rand::Rng; use rand::Rng as _;
use wyrand::WyRand as LocalRng;
use super::objects::{Ball, Player, Wall}; use super::{
objects::{Ball, Player, Wall},
rng::thread_rng,
};
const BALL_COUNT: u8 = 32; const BALL_COUNT: u8 = 32;
const BALL_SIZES: Range<f32> = 10.0..25.0; const BALL_SIZES: Range<f32> = 10.0..25.0;
const DIMENSION_SIZES: Range<f32> = 500.0..2000.0; const DIMENSION_SIZES: Range<f32> = 500.0..2000.0;
/// The size of the playable area (x, y)
#[derive(Resource)] #[derive(Resource)]
pub struct PlayableArea(f32, f32); pub struct PlayableArea(f32, f32);
/// The size of the player character
#[derive(Resource)] #[derive(Resource)]
pub struct PlayerSize(f32); pub struct PlayerSize(f32);
/// Initialize deterministic values
pub fn setup_pseudo_random(mut commands: Commands, mut rng: GlobalEntropy<WyRand>) { pub fn setup_pseudo_random(mut commands: Commands, mut rng: GlobalEntropy<WyRand>) {
commands.insert_resource(PlayerSize(rng.random_range(BALL_SIZES))); commands.insert_resource(PlayerSize(rng.random_range(BALL_SIZES)));
commands.insert_resource(PlayableArea( commands.insert_resource(PlayableArea(
@ -36,6 +41,7 @@ pub fn setup_ui(mut commands: Commands) {
commands.spawn((Name::new("Camera"), Camera2d, IsDefaultUiCamera)); commands.spawn((Name::new("Camera"), Camera2d, IsDefaultUiCamera));
} }
/// Create the playable character
pub fn setup_player( pub fn setup_player(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
@ -51,13 +57,14 @@ pub fn setup_player(
)); ));
} }
/// Create a random distribution of balls in the playable area
pub fn setup_balls( pub fn setup_balls(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
region: Res<PlayableArea>, region: Res<PlayableArea>,
) { ) {
let mut random = LocalRng::new(Default::default()); let mut random = thread_rng();
for _ in 0..BALL_COUNT { for _ in 0..BALL_COUNT {
let circle = Circle::new(random.random_range(BALL_SIZES)); let circle = Circle::new(random.random_range(BALL_SIZES));
commands.spawn(( commands.spawn((
@ -66,18 +73,15 @@ pub fn setup_balls(
Mesh2d(meshes.add(circle)), Mesh2d(meshes.add(circle)),
MeshMaterial2d(materials.add(Color::from(RED_400))), MeshMaterial2d(materials.add(Color::from(RED_400))),
Transform::from_xyz( Transform::from_xyz(
random.random_range( random.random::<f32>() * (region.0 - 2.0 * circle.radius) + circle.radius,
(-region.0 / 2.0 + circle.radius)..(region.0 / 2.0 - circle.radius), random.random::<f32>() * (region.1 - 2.0 * circle.radius) + circle.radius,
),
random.random_range(
(-region.1 / 2.0 + circle.radius)..(region.1 / 2.0 - circle.radius),
),
0.0, 0.0,
), ),
)); ));
} }
} }
/// Create the 4 walls that enclose the playable area
pub fn setup_walls( pub fn setup_walls(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,

View file

@ -10,6 +10,8 @@ use game::{
setup::{setup_balls, setup_player, setup_pseudo_random, setup_ui, setup_walls}, setup::{setup_balls, setup_player, setup_pseudo_random, setup_ui, setup_walls},
}; };
/// The initial configuration passed to the game's setup functions.
/// Also functions as a Bevy plugin to pass the configuration into the app.
#[derive(Parser)] #[derive(Parser)]
#[command(version, about)] #[command(version, about)]
pub struct AppSettings { pub struct AppSettings {