mod crabs;
mod gulls;
mod sweeps;
pub use crabs::{CrabSprite, interpolate_crabs, sync_crab_sprites};
pub(crate) use crabs::{
WALK_FPS, body_color, claw_color, coop_claws, crab_body, crab_claw, mirrored, resting_shell,
shade_of, shell_color, sprite_side, walk_bob,
};
pub use gulls::{GullArrival, GullSprite, fly_in_gulls, interpolate_gulls, sync_gull_sprites};
pub use sweeps::{SweepSprite, draw_sweeps};
pub(crate) type LiveCreature = (CrabSprite, crabs::Crowding, GullSprite);
pub fn spawn_creatures(
commands: &mut Commands,
board: &crate::sim::Board,
art: &crate::app::art::Art,
) {
for crab in board.crabs() {
crabs::spawn_crab(commands, art, board, crab);
}
for gull in board.gulls() {
gulls::spawn_gull(commands, art, board, gull);
}
}
use crate::app::layout::TILE;
use bevy::prelude::*;
#[derive(Component)]
pub struct CreatureShadow;
fn smoothing_alpha(
ticks: u64,
watch: &mut (u64, f32),
delta_secs: f32,
fixed_time: &Time<Fixed>,
) -> f32 {
let (last_tick, frozen_for) = watch;
if ticks == *last_tick {
*frozen_for += delta_secs;
} else {
*last_tick = ticks;
*frozen_for = 0.0;
}
if *frozen_for > fixed_time.timestep().as_secs_f32() * 1.5 {
1.0
} else {
fixed_time.overstep_fraction()
}
}
#[derive(bevy::ecs::system::SystemParam)]
pub struct Tween<'w, 's> {
time: Res<'w, Time>,
fixed_time: Res<'w, Time<Fixed>>,
watch: Local<'s, (u64, f32)>,
round: crate::app::round_state::RoundState<'w>,
round_clock: Res<'w, crate::app::round_state::RoundClock>,
}
fn tweened_pos(prev: Vec2, curr: Vec2, alpha: f32) -> Vec2 {
if prev.distance(curr) > TILE * 2.0 {
curr
} else {
prev.lerp(curr, alpha)
}
}
fn unturned(parent: &Transform, world: Vec2, z: f32) -> Vec3 {
let local = parent.rotation.inverse() * world.extend(0.0);
Vec3::new(local.x, local.y, z)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::layout;
#[test]
fn a_shadow_holds_its_bearing_through_every_turn() {
for dir in [
crate::sim::Direction::Up,
crate::sim::Direction::Down,
crate::sim::Direction::Left,
crate::sim::Direction::Right,
] {
let parent = Transform::from_rotation(layout::dir_rotation(dir));
let local = unturned(&parent, layout::SUN, -0.5);
let world = parent.rotation * local;
assert!(
world.truncate().distance(layout::SUN) < 1e-4,
"{dir:?} bent the sun to {:?}",
world.truncate()
);
assert_eq!(local.z, -0.5, "and the layer is left alone");
}
}
fn fixed_clock(overstep: f32) -> Time<Fixed> {
let mut fixed = Time::<Fixed>::from_hz(30.0);
fixed.accumulate_overstep(fixed.timestep().mul_f32(overstep));
fixed
}
#[test]
fn the_sim_counts_as_frozen_only_past_one_and_a_half_periods() {
let fixed = fixed_clock(0.4);
let period = fixed.timestep().as_secs_f32();
let between = fixed.overstep_fraction();
assert!((0.0..1.0).contains(&between), "a fraction, got {between}");
let mut watch = (5u64, 0.0f32);
let mut alpha = 0.0;
for _ in 0..3 {
alpha = smoothing_alpha(5, &mut watch, period * 0.4, &fixed);
}
assert_eq!(
alpha, between,
"1.2 periods without a tick still interpolates"
);
let alpha = smoothing_alpha(5, &mut watch, period * 0.4, &fixed);
assert_eq!(alpha, 1.0, "1.6 periods without a tick holds still");
let alpha = smoothing_alpha(6, &mut watch, period * 0.4, &fixed);
assert_eq!(alpha, between, "a fresh tick resets the frozen count");
assert_eq!(watch, (6, 0.0), "the watch follows the new tick");
}
}