diff --git a/shaders/cell.frag b/shaders/cell.frag index 7700db0..816c381 100644 --- a/shaders/cell.frag +++ b/shaders/cell.frag @@ -11,7 +11,8 @@ const vec4 top = vec4(0.5, 0.5, 0.5, 1.0); const vec4 bot = vec4(0.4, 0.4, 0.4, 1.0); void main() { - outcolor = 0.2 * (bot * (1 - pos.y) + top * (1 - pos.x)) + 0.2; + outcolor = vec4(0.2 * (0.4 * (1 - pos.y) + 0.5 * (1 - pos.x)) + 0.2); + outcolor.w = 1.0; // if ((pos.x > -threshold && pos.x < threshold) // || (pos.y > -threshold && pos.y < threshold) // || (pos.x > 1.0-threshold && pos.x < 1.0+threshold) diff --git a/shaders/cell.vert b/shaders/cell.vert index 096ac61..a6b2207 100644 --- a/shaders/cell.vert +++ b/shaders/cell.vert @@ -9,4 +9,4 @@ uniform mat4 projection; void main() { pos = vec4(point, 0.0, 1.0); gl_Position = projection * target * pos; -} \ No newline at end of file +} diff --git a/src/data.rs b/src/data.rs index e470d90..6d57f31 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,6 +1,6 @@ #[derive(Debug, Deserialize)] pub struct PlayerData { - pub position: [u32; 2], + pub position: [i32; 2], pub color: [u32; 3], } @@ -8,9 +8,9 @@ pub struct PlayerData { pub struct BlockData { pub movable: bool, pub orientation: u32, - pub position: [u32; 2], + pub position: [i32; 2], pub color: [u32; 3], - pub segments: Vec<[u32; 4]>, + pub segments: Vec<[i32; 4]>, } #[derive(Debug, Deserialize)] diff --git a/src/enums.rs b/src/enums.rs index 1717b17..01ed491 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -1,11 +1,13 @@ -#[derive(Copy, Clone)] +use std::ops::Add; + +#[derive(Eq, PartialEq, Hash, PartialOrd, Copy, Clone)] pub enum Board { Left = 0, Right = 1, } -impl From for Board { - fn from(n: u32) -> Self { +impl From for Board { + fn from(n: i32) -> Self { match n { 0 => Board::Left, 1 => Board::Right, @@ -40,7 +42,27 @@ pub enum PushDir { Right, } -#[derive(Copy, Clone)] +impl PushDir { + pub fn as_pair(&self) -> (i32, i32) { + match self { + PushDir::Up => (0, -1), + PushDir::Down => (0, 1), + PushDir::Left => (-1, 0), + PushDir::Right => (1, 0), + } + } +} + +impl Add for (i32, i32, Board) { + type Output = (i32, i32, Board); + + fn add(self, rhs: PushDir) -> Self::Output { + let offset = rhs.as_pair(); + (self.0 + offset.0, self.1 + offset.1, self.2) + } +} + +#[derive(Copy, Clone, PartialOrd, PartialEq)] pub enum Shape { Full = 0, TopRight = 1, @@ -49,8 +71,8 @@ pub enum Shape { BottomRight = 4, } -impl From for Shape { - fn from(n: u32) -> Self { +impl From for Shape { + fn from(n: i32) -> Self { match n { 0 => Shape::Full, 1 => Shape::TopRight, @@ -61,3 +83,16 @@ impl From for Shape { } } } + +impl Shape { + pub fn is_opposite(&self, other: &Shape) -> bool { + use Shape::*; + match (self, other) { + (TopRight, BottomLeft) + | (BottomLeft, TopRight) + | (TopLeft, BottomRight) + | (BottomRight, TopLeft) => true, + _ => false, + } + } +} diff --git a/src/game.rs b/src/game.rs index 2247358..0970fb4 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,7 +1,10 @@ +use std::collections::HashMap; use std::time::Duration; +use glium::glutin::{ElementState, Event, VirtualKeyCode, WindowEvent}; use glium::{Display, Frame}; +use crate::enums::PushDir; use crate::level::Level; use crate::renderer::Renderer; use crate::resources::Resources; @@ -20,6 +23,7 @@ pub struct Game<'a> { pub display: &'a Display, levels: Vec, current_level: usize, + keymap: HashMap, } impl<'a> Game<'a> { @@ -41,6 +45,26 @@ impl<'a> Game<'a> { display, levels, current_level: 0, + keymap: HashMap::new(), + } + } + + pub fn handle_event(&mut self, event: Event) { + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => self.resources.window_dimensions = size.into(), + WindowEvent::KeyboardInput { input, .. } => { + if let Some(code) = &input.virtual_keycode { + if let ElementState::Pressed = &input.state { + self.keymap.insert(*code, true); + } else { + self.keymap.insert(*code, false); + } + } + } + _ => (), + }, + _ => (), } } @@ -52,7 +76,39 @@ impl<'a> Game<'a> { self.levels.iter().nth(self.current_level).unwrap() } - pub fn update(&mut self, delta: Duration) {} + pub fn get_current_level_mut(&mut self) -> &mut Level { + self.levels.iter_mut().nth(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) { + macro_rules! shit { + ($key:expr, $player:expr, $movement:expr) => { + if self.is_pressed(&$key) { + let mut level = self.get_current_level_mut(); + level.handle_movement($player, $movement); + self.keymap.insert($key, false); + } + }; + } + + shit!(VirtualKeyCode::W, true, PushDir::Up); + shit!(VirtualKeyCode::A, true, PushDir::Left); + shit!(VirtualKeyCode::S, true, PushDir::Down); + shit!(VirtualKeyCode::D, true, PushDir::Right); + + shit!(VirtualKeyCode::I, false, PushDir::Up); + shit!(VirtualKeyCode::J, false, PushDir::Left); + shit!(VirtualKeyCode::K, false, PushDir::Down); + shit!(VirtualKeyCode::L, false, PushDir::Right); + } pub fn render(&self, renderer: &mut Renderer) { let level = self.get_current_level(); diff --git a/src/level.rs b/src/level.rs index 07d22eb..3bd389c 100644 --- a/src/level.rs +++ b/src/level.rs @@ -1,26 +1,35 @@ -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use crate::data::LevelData; -use crate::enums::{Board, Orientation, Shape}; +use crate::enums::{Board, Orientation, PushDir, Shape}; use crate::renderer::Renderer; -use crate::{GAME_HEIGHT, GAME_WIDTH}; pub struct Level { dimensions: (u32, u32), move_stack: VecDeque<()>, + cell_map: CellMap, blocks: Vec, + player1: Player, + player2: Player, } #[derive(Clone)] pub struct Block { - position: (u32, u32), + movable: bool, + position: (i32, i32), color: (f32, f32, f32), orientation: Orientation, segments: Vec, } +#[derive(Copy, Clone, PartialOrd, PartialEq)] +pub struct Segment(i32, i32, Shape, Board); + #[derive(Copy, Clone)] -pub struct Segment(u32, u32, Shape, Board); +pub struct Player { + pub position: [i32; 2], + pub color: [u32; 3], +} impl Level { pub fn from_json(data: impl AsRef) -> Level { @@ -31,6 +40,7 @@ impl Level { .blocks .iter() .map(|block| { + let movable = block.movable; let position = (block.position[0], block.position[1]); let segments = block .segments @@ -46,6 +56,7 @@ impl Level { block.color[2] as f32 / 256.0, ); Block { + movable, position, color, segments, @@ -54,40 +65,138 @@ impl Level { }) .collect(); + let player1 = Player { + position: data.player1.position, + color: data.player1.color, + }; + let player2 = Player { + position: data.player2.position, + color: data.player2.color, + }; + Level { dimensions: (data.dimensions[0], data.dimensions[1]), move_stack: VecDeque::new(), + cell_map: CellMap::new(), blocks, + player1, + player2, } } + // player1: true -> player1, false -> player2 + // TODO: don't use a boolean here + pub fn handle_movement(&mut self, player1: bool, direction: PushDir) -> bool { + let mut player = if player1 { + &self.player1 + } else { + &self.player2 + }; + + // TODO: check out of bounds + let movement = direction.as_pair(); + let x = player.position[0] + movement.0; + let y = player.position[1] + movement.1; + + let result = self.can_move(player1, direction).clone(); + let mut player = if player1 { + &mut self.player1 + } else { + &mut self.player2 + }; + + if let Some(_) = result { + player.position[0] = x; + player.position[1] = y; + true + } else { + false + } + } + + // TODO: don't use a boolean here + pub fn can_move(&self, player1: bool, direction: PushDir) -> Option<()> { + // an absolute segment (as opposed to relative to a block) + #[derive(Copy, Clone, PartialOrd, PartialEq)] + struct ASegment(i32, i32, Shape, Board); + + fn can_push(src: Segment, dst: Segment) -> bool { + if src.3 != dst.3 { + return false; + } + + true + } + + let player = if player1 { + ( + self.player1.position[0], + self.player1.position[1], + Board::Left, + ) + } else { + ( + self.player2.position[0], + self.player2.position[1], + Board::Right, + ) + }; + + // check to make sure that the player isn't trying to go out of bounds + let target = player + direction; + if target.0 < 0 + || target.0 >= self.dimensions.0 as i32 + || target.1 < 0 + || target.1 >= self.dimensions.1 as i32 + { + return None; + } + + // check if we're sharing a triangle cell + if let CellContents::Double(a, b) = self.cell_map.get(player) {} + + // 08/06 pickup + // need to determine whether or not segment should hold a reference back to block or not? + // either way, segment in the cellmap should hold block information + // maybe cellmap should just carry a block index? seems hacky + // using refs to manage the whole thing is messy and probably doesn't work + // ??? + + Some(()) + } + pub fn render(&self, renderer: &mut Renderer) { - let playfield_ratio = (2 * self.dimensions.0 + 6) as f64 / (self.dimensions.1 + 4) as f64; - let screen_ratio = GAME_WIDTH as f64 / GAME_HEIGHT as f64; + // board positioning calculations + let playfield_ratio = (2 * self.dimensions.0 + 6) as f32 / (self.dimensions.1 + 4) as f32; + let screen_ratio = renderer.window.0 / renderer.window.1; + + let cols = self.dimensions.0 as i32; + let rows = self.dimensions.1 as i32; let (scale, xoff, yoff) = if playfield_ratio > screen_ratio { - let scale = GAME_WIDTH / (2 * self.dimensions.0 + 6); - let yoff = GAME_HEIGHT / 2 - (self.dimensions.1 + 4) * scale / 2; + let scale = renderer.window.0 as i32 / (2 * cols + 6); + let yoff = renderer.window.1 as i32 / 2 - (rows + 4) * scale / 2; (scale, 0, yoff) } else { - let scale = GAME_HEIGHT / (self.dimensions.1 + 4); - let xoff = GAME_WIDTH / 2 - (2 * self.dimensions.0 + 6) * scale / 2; + let scale = renderer.window.1 as i32 / (rows + 4); + let xoff = renderer.window.0 as i32 / 2 - (2 * cols + 6) * scale / 2; (scale, xoff, 0) }; self.render_boards(renderer, scale, (xoff, yoff)); } - fn render_boards(&self, renderer: &mut Renderer, scale: u32, offset: (u32, u32)) { + fn render_boards(&self, renderer: &mut Renderer, scale: i32, offset: (i32, i32)) { let left_off = (offset.0 + 2 * scale, offset.1 + 2 * scale); let right_off = ( - offset.0 + (4 + self.dimensions.0) * scale, + offset.0 + (4 + self.dimensions.0 as i32) * scale, offset.1 + 2 * scale, ); // render the grid - for x in 0..self.dimensions.0 { - for y in 0..self.dimensions.1 { + // TODO: do this in one single pass instead of once for each cell + for x in 0..self.dimensions.0 as i32 { + for y in 0..self.dimensions.1 as i32 { renderer.render_cell((left_off.0 + x * scale, left_off.1 + y * scale), scale); renderer.render_cell((right_off.0 + x * scale, right_off.1 + y * scale), scale); } @@ -105,5 +214,89 @@ impl Level { renderer.render_segment(location, scale, block.color, block.orientation, segment.2); } } + + // render player + self.render_player(renderer, &self.player1, scale, left_off); + self.render_player(renderer, &self.player2, scale, right_off); + } + + fn render_player( + &self, + renderer: &mut Renderer, + player: &Player, + scale: i32, + offset: (i32, i32), + ) { + let location = ( + offset.0 + player.position[0] * scale + 4, + offset.1 + player.position[1] * scale + 4, + ); + renderer.render_segment( + location, + (scale - 8), + ( + player.color[0] as f32, + player.color[1] as f32, + player.color[2] as f32, + ), + Orientation::Both, + Shape::Full, + ); + } +} + +struct CellMap(HashMap<(i32, i32, Board), CellContents>); + +#[derive(Copy, Clone)] +enum CellContents { + Empty, + Player, + Single(Segment), + + // invariant: .0 < .1 + Double(Segment, Segment), +} + +impl CellMap { + pub fn new() -> Self { + CellMap(HashMap::new()) + } + + pub fn get(&self, loc: (i32, i32, Board)) -> CellContents { + self.0 + .get(&loc) + .cloned() + .unwrap_or_else(|| CellContents::Empty) + } + + pub fn clear(&mut self, loc: (i32, i32, Board)) { + self.0.remove(&loc); + } + + pub fn add(&mut self, loc: (i32, i32, Board), segment: &Segment) -> bool { + let contents = self.get(loc).clone(); + match contents { + CellContents::Empty => { + // just add it like normal + self.0.insert(loc, CellContents::Single(*segment)); + true + } + CellContents::Single(existing) => { + if existing.2.is_opposite(&segment.2) { + self.0.insert( + loc, + if *segment < existing { + CellContents::Double(*segment, existing) + } else { + CellContents::Double(existing, *segment) + }, + ); + true + } else { + false + } + } + CellContents::Player | CellContents::Double(_, _) => false, + } } } diff --git a/src/main.rs b/src/main.rs index 98137b6..18624fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ fn main() { println!("size: {:?}", window.get_inner_size()); } - let game = Game::new(&display); + let mut game = Game::new(&display); let mut closed = false; let mut prev = Instant::now(); @@ -54,9 +54,11 @@ fn main() { event: WindowEvent::CloseRequested, .. } => closed = true, - _ => (), + _ => game.handle_event(event), }); + game.update(delta); + let mut target = display.draw(); target.clear(None, Some((0.0, 0.0, 0.0, 1.0)), true, None, None); let mut renderer = game.create_renderer(&mut target); diff --git a/src/renderer.rs b/src/renderer.rs index 4beaa40..72c1646 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -5,9 +5,9 @@ use nalgebra::{Matrix4, Vector4}; use crate::enums::{Orientation, Shape}; use crate::game::Game; -use crate::{GAME_HEIGHT, GAME_WIDTH}; pub struct Renderer<'a, 'b> { + pub window: (f32, f32), target: &'a mut Frame, display: &'b Display, cell_program: &'b Program, @@ -18,6 +18,10 @@ pub struct Renderer<'a, 'b> { impl<'a, 'b> Renderer<'a, 'b> { pub fn new(game: &'b Game, target: &'a mut Frame) -> Self { Renderer { + window: ( + game.resources.window_dimensions.0 as f32, + game.resources.window_dimensions.1 as f32, + ), target, display: &game.display, cell_program: game.resources.get_shader("cell").unwrap(), @@ -26,7 +30,7 @@ impl<'a, 'b> Renderer<'a, 'b> { } } - pub fn render_cell(&mut self, location: (u32, u32), scale: u32) { + pub fn render_cell(&mut self, location: (i32, i32), scale: i32) { #[derive(Copy, Clone)] struct Vertex { point: [f32; 2], @@ -43,8 +47,14 @@ impl<'a, 'b> Renderer<'a, 'b> { vertices.push(Vertex { point: [1.0, 0.0] }); let vertex_buffer = VertexBuffer::new(self.display, &vertices).unwrap(); - let projection = - glm::ortho::(0.0, GAME_WIDTH as f32, GAME_HEIGHT as f32, 0.0, -1.0, 1.0); + let projection = glm::ortho::( + 0.0, + self.window.0 as f32, + self.window.1 as f32, + 0.0, + -1.0, + 1.0, + ); let mut matrix = Matrix4::::identity(); matrix = matrix.append_nonuniform_scaling(&[scale as f32, scale as f32, 1.0].into()); matrix = matrix.append_translation(&[location.0 as f32, location.1 as f32, 0.0].into()); @@ -73,8 +83,8 @@ impl<'a, 'b> Renderer<'a, 'b> { pub fn render_segment( &mut self, - location: (u32, u32), - scale: u32, + location: (i32, i32), + scale: i32, color: (f32, f32, f32), orientation: Orientation, shape: Shape, @@ -175,8 +185,14 @@ impl<'a, 'b> Renderer<'a, 'b> { let vertex_buffer = VertexBuffer::new(self.display, &vertices).unwrap(); let tint = Vector4::from([color.0, color.1, color.2, 1.0f32]); - let projection = - glm::ortho::(0.0, GAME_WIDTH as f32, GAME_HEIGHT as f32, 0.0, -1.0, 1.0); + let projection = glm::ortho::( + 0.0, + self.window.0 as f32, + self.window.1 as f32, + 0.0, + -1.0, + 1.0, + ); let mut matrix = Matrix4::::identity(); matrix = matrix.append_nonuniform_scaling(&[scale as f32, scale as f32, 1.0].into()); matrix = matrix.append_translation(&[location.0 as f32, location.1 as f32, 0.0].into());