add screen abstraction

This commit is contained in:
Michael Zhang 2019-08-12 21:17:26 -05:00
parent b0e9a1bc0b
commit 681c53eb80
No known key found for this signature in database
GPG key ID: 5BAEFE5D04F0CE6C
7 changed files with 239 additions and 86 deletions

View file

@ -4,6 +4,11 @@ version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"] authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018" edition = "2018"
[profile.release]
opt-level = 'z'
lto = true
panic = 'abort'
[dependencies] [dependencies]
glium = "0.25" glium = "0.25"
image = "0.21" image = "0.21"

View file

@ -6,9 +6,11 @@ use glium::{Display, Frame};
use crate::animations::AnimationState; use crate::animations::AnimationState;
use crate::enums::{Board, PushDir}; use crate::enums::{Board, PushDir};
use crate::keymap::Keymap;
use crate::level::Level; use crate::level::Level;
use crate::renderer::Renderer; use crate::renderer::Renderer;
use crate::resources::Resources; use crate::resources::Resources;
use crate::screens::{MenuScreen, Screen, ScreenStack};
const SEGMENT_VERT: &str = include_str!("../shaders/segment.vert"); const SEGMENT_VERT: &str = include_str!("../shaders/segment.vert");
const SEGMENT_FRAG: &str = include_str!("../shaders/segment.frag"); const SEGMENT_FRAG: &str = include_str!("../shaders/segment.frag");
@ -17,16 +19,11 @@ const CELL_FRAG: &str = include_str!("../shaders/cell.frag");
const SEGMENT_IMAGE: &[u8] = include_bytes!("../textures/segment.png"); const SEGMENT_IMAGE: &[u8] = include_bytes!("../textures/segment.png");
const LEVEL_TUTORIAL: &str = include_str!("../levels/tutorial.json");
const LEVEL_TUTORIAL2: &str = include_str!("../levels/tutorial2.json");
pub struct Game<'a> { pub struct Game<'a> {
pub resources: Resources, pub resources: Resources,
pub display: &'a Display, pub display: &'a Display,
levels: Vec<Level>, keymap: Keymap,
current_level: usize, screen_stack: ScreenStack,
keymap: HashMap<VirtualKeyCode, bool>,
animations: AnimationState,
} }
impl<'a> Game<'a> { impl<'a> Game<'a> {
@ -42,17 +39,14 @@ impl<'a> Game<'a> {
.load_shader(display, "cell", &CELL_VERT, &CELL_FRAG) .load_shader(display, "cell", &CELL_VERT, &CELL_FRAG)
.unwrap(); .unwrap();
let levels = vec![ // bruh
Level::from_json(&LEVEL_TUTORIAL), let screen_stack = ScreenStack::with(MenuScreen::new());
Level::from_json(&LEVEL_TUTORIAL2),
];
Game { Game {
resources, resources,
display, display,
levels, keymap: Keymap::new(),
current_level: 0, screen_stack,
keymap: HashMap::new(),
animations: AnimationState::new(),
} }
} }
@ -63,9 +57,9 @@ impl<'a> Game<'a> {
WindowEvent::KeyboardInput { input, .. } => { WindowEvent::KeyboardInput { input, .. } => {
if let Some(code) = &input.virtual_keycode { if let Some(code) = &input.virtual_keycode {
if let ElementState::Pressed = &input.state { if let ElementState::Pressed = &input.state {
self.keymap.insert(*code, true); self.keymap.pressed(*code);
} else { } else {
self.keymap.insert(*code, false); self.keymap.release(*code);
} }
} }
} }
@ -78,77 +72,11 @@ impl<'a> Game<'a> {
Renderer::new(self, target) Renderer::new(self, target)
} }
pub fn get_current_level(&self) -> &Level {
self.levels.get(self.current_level).unwrap()
}
pub fn get_current_level_mut(&mut self) -> &mut Level {
self.levels.get_mut(self.current_level).unwrap()
}
pub fn is_pressed(&self, code: VirtualKeyCode) -> bool {
if let Some(true) = self.keymap.get(&code) {
true
} else {
false
}
}
pub fn update(&mut self, delta: Duration) { pub fn update(&mut self, delta: Duration) {
macro_rules! shit { self.screen_stack.update(delta, &self.keymap);
($key:expr, $board:expr, $direction:expr) => {
if self.is_pressed($key) {
println!("pushed: {:?}", $key);
let level = self.get_current_level_mut();
let result = level.try_move($board, $direction);
self.animations.begin_move_transition(result);
self.keymap.insert($key, false);
}
};
}
if self.animations.is_animating {
// println!("animating. {:?}", self.animations.progress);
self.animations.make_progress(delta);
// we just finished!
if !self.animations.is_animating {
// apply the changes to the entities
// this indirection is used to dodge a concurrent borrow
let change_set = if let Some(Ok(change_set)) = &self.animations.last_move_result {
Some(change_set.clone())
} else {
None
};
if let Some(change_set) = change_set {
let level = self.get_current_level_mut();
level.apply_change_set(change_set.clone());
self.check_win_condition();
}
}
} else {
shit!(VirtualKeyCode::W, Board::Left, PushDir::Up);
shit!(VirtualKeyCode::A, Board::Left, PushDir::Left);
shit!(VirtualKeyCode::S, Board::Left, PushDir::Down);
shit!(VirtualKeyCode::D, Board::Left, PushDir::Right);
shit!(VirtualKeyCode::I, Board::Right, PushDir::Up);
shit!(VirtualKeyCode::J, Board::Right, PushDir::Left);
shit!(VirtualKeyCode::K, Board::Right, PushDir::Down);
shit!(VirtualKeyCode::L, Board::Right, PushDir::Right);
}
}
fn check_win_condition(&mut self) {
let level = self.get_current_level();
if level.check_win_condition() {
// go on to the next level
self.current_level += 1;
}
} }
pub fn render(&self, renderer: &mut Renderer) { pub fn render(&self, renderer: &mut Renderer) {
let level = self.get_current_level(); self.screen_stack.render(renderer);
level.render(renderer, &self.animations);
} }
} }

27
src/keymap.rs Normal file
View file

@ -0,0 +1,27 @@
use std::collections::HashMap;
use glium::glutin::VirtualKeyCode;
pub struct Keymap(HashMap<VirtualKeyCode, bool>);
impl Keymap {
pub fn new() -> Self {
Keymap(HashMap::new())
}
pub fn pressed(&mut self, code: VirtualKeyCode) {
self.0.insert(code, true);
}
pub fn release(&mut self, code: VirtualKeyCode) {
self.0.insert(code, false);
}
pub fn is_pressed(&self, code: VirtualKeyCode) -> bool {
if let Some(true) = self.0.get(&code) {
true
} else {
false
}
}
}

View file

@ -9,9 +9,11 @@ mod color;
mod data; mod data;
mod enums; mod enums;
mod game; mod game;
mod keymap;
mod level; mod level;
mod renderer; mod renderer;
mod resources; mod resources;
mod screens;
use std::time::Instant; use std::time::Instant;

25
src/screens/menu.rs Normal file
View file

@ -0,0 +1,25 @@
use std::time::Duration;
use glium::glutin::VirtualKeyCode;
use crate::keymap::Keymap;
use crate::screens::{PlayScreen, Screen, ScreenAction};
pub struct MenuScreen;
impl Screen for MenuScreen {
fn update(&mut self, delta: Duration, keymap: &Keymap) -> ScreenAction {
if keymap.is_pressed(VirtualKeyCode::Space) {
let play_screen = PlayScreen::new();
ScreenAction::Push(Box::new(play_screen))
} else {
ScreenAction::None
}
}
}
impl MenuScreen {
pub fn new() -> MenuScreen {
MenuScreen
}
}

63
src/screens/mod.rs Normal file
View file

@ -0,0 +1,63 @@
mod menu;
mod play;
use std::sync::Arc;
use std::time::Duration;
use crate::keymap::Keymap;
use crate::renderer::Renderer;
pub use self::menu::MenuScreen;
pub use self::play::PlayScreen;
pub trait Screen {
fn update(&mut self, delta: Duration, keymap: &Keymap) -> ScreenAction {
ScreenAction::None
}
fn render(&self, renderer: &mut Renderer) {}
}
pub enum ScreenAction {
None,
Push(Box<dyn Screen>),
}
pub struct ScreenStack(Vec<Box<dyn Screen>>);
impl ScreenStack {
pub fn with<S: 'static + Screen>(screen: S) -> Self {
let mut stack = Vec::<Box<Screen>>::new();
stack.push(Box::new(screen));
ScreenStack(stack)
}
pub fn top(&self) -> impl AsRef<dyn Screen + 'static> + '_ {
self.0.last().unwrap()
}
pub fn top_mut(&mut self) -> impl AsMut<dyn Screen + 'static> + '_ {
self.0.last_mut().unwrap()
}
pub fn update(&mut self, delta: Duration, keymap: &Keymap) {
let result = {
let mut screen = self.top_mut();
let screen = screen.as_mut();
screen.update(delta, keymap)
};
match result {
ScreenAction::None => (),
ScreenAction::Push(new_screen) => {
println!("pushed new screen");
self.0.push(new_screen);
}
}
}
pub fn render(&self, renderer: &mut Renderer) {
let mut screen = self.top();
let screen = screen.as_ref();
screen.render(renderer)
}
}

103
src/screens/play.rs Normal file
View file

@ -0,0 +1,103 @@
use std::time::Duration;
use glium::glutin::VirtualKeyCode;
use crate::animations::AnimationState;
use crate::enums::{Board, PushDir};
use crate::keymap::Keymap;
use crate::level::Level;
use crate::renderer::Renderer;
use crate::screens::{Screen, ScreenAction};
const LEVEL_TUTORIAL: &str = include_str!("../../levels/tutorial.json");
const LEVEL_TUTORIAL2: &str = include_str!("../../levels/tutorial2.json");
pub struct PlayScreen {
animations: AnimationState,
levels: Vec<Level>,
current_level: usize,
}
impl Screen for PlayScreen {
fn update(&mut self, delta: Duration, keymap: &Keymap) -> ScreenAction {
macro_rules! shit {
($key:expr, $board:expr, $direction:expr) => {
if keymap.is_pressed($key) {
println!("pushed: {:?}", $key);
let level = self.get_current_level_mut();
let result = level.try_move($board, $direction);
self.animations.begin_move_transition(result);
}
};
}
if self.animations.is_animating {
// println!("animating. {:?}", self.animations.progress);
self.animations.make_progress(delta);
// we just finished!
if !self.animations.is_animating {
// apply the changes to the entities
// this indirection is used to dodge a concurrent borrow
let change_set = if let Some(Ok(change_set)) = &self.animations.last_move_result {
Some(change_set.clone())
} else {
None
};
if let Some(change_set) = change_set {
let level = self.get_current_level_mut();
level.apply_change_set(change_set.clone());
self.check_win_condition();
}
}
} else {
shit!(VirtualKeyCode::W, Board::Left, PushDir::Up);
shit!(VirtualKeyCode::A, Board::Left, PushDir::Left);
shit!(VirtualKeyCode::S, Board::Left, PushDir::Down);
shit!(VirtualKeyCode::D, Board::Left, PushDir::Right);
shit!(VirtualKeyCode::I, Board::Right, PushDir::Up);
shit!(VirtualKeyCode::J, Board::Right, PushDir::Left);
shit!(VirtualKeyCode::K, Board::Right, PushDir::Down);
shit!(VirtualKeyCode::L, Board::Right, PushDir::Right);
}
ScreenAction::None
}
fn render(&self, renderer: &mut Renderer) {
let level = self.get_current_level();
level.render(renderer, &self.animations);
}
}
impl PlayScreen {
pub fn get_current_level(&self) -> &Level {
self.levels.get(self.current_level).unwrap()
}
pub fn get_current_level_mut(&mut self) -> &mut Level {
self.levels.get_mut(self.current_level).unwrap()
}
pub fn new() -> PlayScreen {
let levels = vec![
Level::from_json(&LEVEL_TUTORIAL),
Level::from_json(&LEVEL_TUTORIAL2),
];
PlayScreen {
levels,
current_level: 0,
animations: AnimationState::new(),
}
}
fn check_win_condition(&mut self) {
let level = self.get_current_level();
if level.check_win_condition() {
// go on to the next level
self.current_level += 1;
}
}
}