euv-engine 0.28.21

A high-performance 2D game engine built on the euv framework, featuring ECS, fixed-timestep game loop, canvas rendering, physics, collision detection, sprite animation, and audio.
Documentation
use super::*;

fn frame(duration: f64) -> SpriteFrame {
    SpriteFrame::new(
        Rect::from_center(Vector2D::new(0.0, 0.0), 16.0, 16.0),
        duration,
    )
}

fn animation(durations: &[f64], mode: AnimationMode) -> SpriteAnimation {
    let frames: Vec<SpriteFrame> = durations.iter().map(|d: &f64| frame(*d)).collect();
    SpriteAnimation::new(String::from("run"), frames, mode)
}

#[test]
fn a_new_animator_starts_paused_on_the_first_frame() {
    let animator: Animator = Animator::create();
    assert_eq!(
        animator.get_state(),
        AnimationState::Paused,
        "an animator must be told to play before it moves"
    );
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "and it starts at frame zero"
    );
}

#[test]
fn a_new_animator_has_no_animation_attached() {
    let animator: Animator = Animator::create();
    assert_eq!(
        animator.try_get_current_animation(),
        None,
        "nothing is loaded until play() hands over an animation"
    );
    assert_eq!(
        animator.current_frame_source(),
        None,
        "so there is no frame rectangle to report yet"
    );
}

#[test]
fn pausing_a_paused_animator_changes_nothing() {
    let mut animator: Animator = Animator::create();
    animator.pause();
    assert_eq!(
        animator.get_state(),
        AnimationState::Paused,
        "pause is idempotent on an already paused animator"
    );
}

#[test]
fn resume_moves_a_paused_animator_into_playing() {
    let mut animator: Animator = Animator::create();
    animator.resume();
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "resume is what actually starts playback"
    );
}

#[test]
fn resume_leaves_an_already_playing_animator_alone() {
    let mut animator: Animator = Animator::create();
    animator.resume();
    animator.resume();
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "resume must not flip a finished animation back into play"
    );
}

#[test]
fn stop_returns_the_animator_to_paused_at_frame_zero() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "it advanced before the stop"
    );
    animator.stop();
    assert_eq!(
        animator.get_state(),
        AnimationState::Paused,
        "stop halts playback"
    );
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "and rewinds to the first frame"
    );
}

#[test]
fn play_attaches_the_animation_and_starts_at_frame_zero() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.2], AnimationMode::Loop));
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "play starts playback"
    );
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "starting at the first frame"
    );
}

#[test]
fn play_forwards_in_the_positive_direction() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    assert_eq!(
        animator.get_direction(),
        1,
        "a freshly played animation runs forwards"
    );
}

#[test]
fn a_frame_source_is_reported_once_an_animation_is_loaded() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    let observed: Option<Rect> = animator.current_frame_source();
    assert!(
        observed.is_some(),
        "a loaded animation reports its current rectangle"
    );
}

#[test]
fn update_before_the_frame_duration_keeps_the_same_frame() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.5, 0.5], AnimationMode::Loop));
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "an update shorter than the frame does not advance"
    );
}

#[test]
fn update_past_the_frame_duration_advances() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "reaching the duration rolls into the next frame"
    );
}

#[test]
fn a_looping_animation_wraps_at_the_end() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    animator.update(0.1);
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "after the last frame a loop returns to the first"
    );
}

#[test]
fn a_looping_animation_never_finishes() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1], AnimationMode::Loop));
    for _ in 0..10 {
        animator.update(0.1);
    }
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "a loop has no end state no matter how long it runs"
    );
}

#[test]
fn a_once_animation_finishes_after_its_last_frame() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Once));
    animator.update(0.1);
    animator.update(0.1);
    assert_eq!(
        animator.get_state(),
        AnimationState::Finished,
        "playing through the last frame ends the animation"
    );
}

#[test]
fn a_once_animation_stops_on_the_last_frame() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Once));
    animator.update(0.1);
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "the last frame is where a once animation comes to rest"
    );
}

#[test]
fn a_ping_pong_animation_reverses_after_the_last_frame() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1, 0.1], AnimationMode::PingPong));
    animator.update(0.1);
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        2,
        "two steps land on the last frame"
    );
    assert_eq!(
        animator.get_direction(),
        1,
        "and it is still heading forwards"
    );
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "the step off the end steps back rather than wrapping"
    );
    assert_eq!(
        animator.get_direction(),
        -1,
        "and the direction flips to match"
    );
}

#[test]
fn a_ping_pong_animation_comes_back_to_the_first_frame_still_heading_back() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1, 0.1], AnimationMode::PingPong));
    for _ in 0..4 {
        animator.update(0.1);
    }
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "a full there-and-back ends on the first frame"
    );
    assert_eq!(
        animator.get_direction(),
        -1,
        "having just arrived at the first frame while going backwards, it is \
         still pointed backwards and will reverse on the next step"
    );
}

#[test]
fn a_ping_pong_animation_alternates_direction_at_each_edge() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1, 0.1], AnimationMode::PingPong));
    for _ in 0..5 {
        animator.update(0.1);
    }
    assert_eq!(
        animator.get_direction(),
        1,
        "stepping off the first frame while going backwards reverses it again"
    );
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "and the step leaves the frame it was on"
    );
}

#[test]
fn a_ping_pong_animation_never_finishes() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::PingPong));
    for _ in 0..8 {
        animator.update(0.1);
    }
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "ping-pong has no end state"
    );
}

#[test]
fn an_animation_with_no_frames_does_not_advance() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[], AnimationMode::Loop));
    animator.update(1.0);
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "an empty animation has nothing to step to"
    );
}

#[test]
fn a_paused_animator_ignores_updates() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.1], AnimationMode::Loop));
    animator.pause();
    animator.update(1.0);
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "a paused animator must not advance no matter how big the delta"
    );
}

#[test]
fn an_update_with_no_animation_is_a_no_op() {
    let mut animator: Animator = Animator::create();
    animator.resume();
    animator.update(1.0);
    assert_eq!(
        animator.get_state(),
        AnimationState::Playing,
        "a playing animator with no animation just does nothing"
    );
}

#[test]
fn each_frame_keeps_its_own_duration() {
    let mut animator: Animator = Animator::create();
    animator.play(animation(&[0.1, 0.5], AnimationMode::Loop));
    animator.update(0.1);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "the short frame elapsed first"
    );
    animator.update(0.2);
    assert_eq!(
        animator.get_current_frame_index(),
        1,
        "a fifth of a second is well short of the long frame's half second"
    );
    animator.update(0.3);
    assert_eq!(
        animator.get_current_frame_index(),
        0,
        "together the two updates reach half a second, so the long frame elapses and the loop wraps"
    );
}

#[test]
fn the_default_animation_mode_is_looping() {
    let observed: AnimationMode = AnimationMode::default();
    assert_eq!(
        observed,
        AnimationMode::Loop,
        "sprite sheets repeat by default"
    );
}

#[test]
fn the_default_animation_state_is_playing() {
    let observed: AnimationState = AnimationState::default();
    assert_eq!(
        observed,
        AnimationState::Playing,
        "a struct default is not a runtime one"
    );
}