use super::{Context, Keys, PlayerId, Screen};
use crate::Pointer;
use std::time::Duration;
pub(crate) const STEP: Duration = Duration::from_nanos(16_666_667);
pub(crate) const DT: f32 = 1.0 / 60.0;
pub trait Game {
fn update(&mut self, _ctx: &mut Context<'_>) -> Next {
Next::Stay
}
fn draw(&self, _screen: &mut Screen) {}
}
pub enum Next {
Stay,
Switch(Box<dyn Game>),
Push(Box<dyn Game>),
Pop,
Quit,
}
impl Next {
pub fn stay() -> Next {
Next::Stay
}
pub fn switch(game: impl Game + 'static) -> Next {
Next::Switch(Box::new(game))
}
pub fn push(game: impl Game + 'static) -> Next {
Next::Push(Box::new(game))
}
pub fn pop() -> Next {
Next::Pop
}
pub fn quit() -> Next {
Next::Quit
}
}
pub(crate) struct Scenes {
stack: Vec<Box<dyn Game>>,
}
impl Scenes {
pub(crate) fn new(first: Box<dyn Game>) -> Scenes {
Scenes { stack: vec![first] }
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.stack.len()
}
pub(crate) fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub(crate) fn apply(&mut self, next: Next) {
match next {
Next::Switch(game) => {
if let Some(top) = self.stack.last_mut() {
*top = game;
} else {
self.stack.push(game);
}
}
Next::Push(game) => self.stack.push(game),
Next::Pop => {
self.stack.pop();
}
Next::Quit => self.stack.clear(),
Next::Stay => {}
}
}
pub(crate) fn draw(&self, screen: &mut Screen, tick: u64) {
screen.set_clock(tick as f32 * DT, tick);
for scene in &self.stack {
scene.draw(screen);
}
screen.finish_frame();
}
pub(crate) fn step(
&mut self,
keys: &mut Keys,
pointer: &mut Pointer,
tick: u64,
size: (u32, u32),
seed: u64,
) -> bool {
keys.tick(time_of(tick));
let next = match self.stack.last_mut() {
Some(top) => {
let mut ctx = Context {
keys: &*keys,
pointer: &*pointer,
dt: DT,
tick,
size,
seed,
players: 1,
me: PlayerId(0),
};
top.update(&mut ctx)
}
None => return false,
};
self.apply(next);
keys.end_tick();
pointer.end_frame();
!self.is_empty()
}
}
pub(crate) fn time_of(tick: u64) -> Duration {
Duration::from_nanos((STEP.as_nanos() as u64).saturating_mul(tick))
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use terminal_graphics::Colour;
type Log = Rc<RefCell<Vec<&'static str>>>;
struct Probe {
name: &'static str,
log: Log,
}
impl Probe {
fn boxed(name: &'static str, log: &Log) -> Box<dyn Game> {
Box::new(Probe {
name,
log: Rc::clone(log),
})
}
}
impl Game for Probe {
fn update(&mut self, _ctx: &mut Context<'_>) -> Next {
self.log.borrow_mut().push(self.name);
Next::Stay
}
}
fn one_update(scenes: &mut Scenes) -> bool {
scenes.step(&mut Keys::new(), &mut Pointer::default(), 0, (10, 5), 1)
}
fn take(log: &Log) -> Vec<&'static str> {
std::mem::take(&mut *log.borrow_mut())
}
#[test]
fn the_frame_is_finished_after_every_scene_draws_so_pixels_show_above_text() {
struct Floor;
impl Game for Floor {
fn draw(&self, screen: &mut Screen) {
screen.pixel(0, 0, Colour::Red);
}
}
struct Words;
impl Game for Words {
fn draw(&self, screen: &mut Screen) {
screen.text(0, 0, "xy", Colour::White);
}
}
let mut scenes = Scenes::new(Box::new(Floor));
scenes.apply(Next::push(Words));
let mut screen = Screen::new(2, 1);
screen.clear(Colour::Blue);
scenes.draw(&mut screen, 0);
let cell = screen.display().pixel(0, 0).unwrap();
assert_eq!(cell.character(), '\u{2580}', "the pixel is above the text");
assert_eq!(screen.display().pixel(1, 0).unwrap().character(), 'y');
}
#[test]
fn switch_replaces_the_top_scene() {
let log: Log = Rc::default();
let mut scenes = Scenes::new(Probe::boxed("menu", &log));
scenes.apply(Next::switch(Probe {
name: "play",
log: Rc::clone(&log),
}));
assert_eq!(scenes.len(), 1);
one_update(&mut scenes);
assert_eq!(take(&log), vec!["play"]);
}
#[test]
fn push_keeps_the_scene_below_and_only_the_top_updates() {
let log: Log = Rc::default();
let mut scenes = Scenes::new(Probe::boxed("play", &log));
scenes.apply(Next::push(Probe {
name: "pause",
log: Rc::clone(&log),
}));
assert_eq!(scenes.len(), 2);
one_update(&mut scenes);
assert_eq!(take(&log), vec!["pause"], "play is paused, not updated");
}
#[test]
fn pop_returns_to_the_scene_below() {
let log: Log = Rc::default();
let mut scenes = Scenes::new(Probe::boxed("play", &log));
scenes.apply(Next::push(Probe {
name: "pause",
log: Rc::clone(&log),
}));
scenes.apply(Next::pop());
assert_eq!(scenes.len(), 1);
one_update(&mut scenes);
assert_eq!(take(&log), vec!["play"]);
}
struct Leave;
impl Game for Leave {
fn update(&mut self, _ctx: &mut Context<'_>) -> Next {
Next::pop()
}
}
#[test]
fn popping_the_last_scene_ends_the_game() {
let mut scenes = Scenes::new(Box::new(Leave));
assert!(!one_update(&mut scenes), "step reports the game is over");
assert!(scenes.is_empty());
}
#[test]
fn quit_empties_the_whole_stack() {
let log: Log = Rc::default();
let mut scenes = Scenes::new(Probe::boxed("play", &log));
scenes.apply(Next::push(Probe {
name: "pause",
log: Rc::clone(&log),
}));
scenes.apply(Next::quit());
assert!(scenes.is_empty());
}
struct Painter {
name: &'static str,
log: Log,
}
impl Game for Painter {
fn draw(&self, _screen: &mut Screen) {
self.log.borrow_mut().push(self.name);
}
}
#[test]
fn draw_calls_every_scene_from_the_bottom_up() {
let log: Log = Rc::default();
let mut scenes = Scenes::new(Box::new(Painter {
name: "play",
log: Rc::clone(&log),
}));
scenes.apply(Next::push(Painter {
name: "pause",
log: Rc::clone(&log),
}));
scenes.draw(&mut Screen::new(4, 2), 0);
assert_eq!(take(&log), vec!["play", "pause"]);
}
struct ClockSeen(Rc<Cell<(f32, u64)>>);
impl Game for ClockSeen {
fn draw(&self, screen: &mut Screen) {
self.0.set((screen.time(), screen.tick()));
}
}
#[test]
fn draw_sets_the_screen_clock_from_the_tick() {
let seen = Rc::new(Cell::new((0.0, 0)));
let scenes = Scenes::new(Box::new(ClockSeen(Rc::clone(&seen))));
scenes.draw(&mut Screen::new(4, 2), 90);
let (time, tick) = seen.get();
assert_eq!(tick, 90);
assert!((time - 1.5).abs() < 1e-5, "time {time}");
}
}