initial
This commit is contained in:
commit
2d8a62bc86
14 changed files with 1586 additions and 0 deletions
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_file = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
1151
Cargo.lock
generated
Normal file
1151
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "wedge"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glium = "0.25"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
json5 = "0.2"
|
||||||
|
nalgebra = "0.18"
|
||||||
|
nalgebra-glm = "0.4"
|
25
levels/tutorial.json
Normal file
25
levels/tutorial.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"dimensions": [3, 7],
|
||||||
|
"player1": {
|
||||||
|
"position": [1, 6],
|
||||||
|
"color": [66, 134, 244],
|
||||||
|
},
|
||||||
|
"player2": {
|
||||||
|
"position": [1, 6],
|
||||||
|
"color": [244, 83, 65],
|
||||||
|
},
|
||||||
|
"goal1": [1, 0],
|
||||||
|
"goal2": [1, 0],
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"movable": true,
|
||||||
|
"push_dir": 0,
|
||||||
|
"position": [1, 3],
|
||||||
|
"color": [255, 10, 100],
|
||||||
|
"segments": [
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[1, 0, 4, 0],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
shaders/cell.frag
Normal file
7
shaders/cell.frag
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
10
shaders/cell.vert
Normal file
10
shaders/cell.vert
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 point;
|
||||||
|
|
||||||
|
uniform mat4 target;
|
||||||
|
uniform mat4 projection;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = projection * target * vec4(point, 0.0, 1.0);
|
||||||
|
}
|
24
src/data.rs
Normal file
24
src/data.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PlayerData {
|
||||||
|
pub position: [u32; 2],
|
||||||
|
pub color: [u32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct BlockData {
|
||||||
|
pub movable: bool,
|
||||||
|
pub push_dir: u32,
|
||||||
|
pub position: [u32; 2],
|
||||||
|
pub color: [u32; 3],
|
||||||
|
pub segments: Vec<[u32; 4]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct LevelData {
|
||||||
|
pub dimensions: [u32; 2],
|
||||||
|
pub player1: PlayerData,
|
||||||
|
pub player2: PlayerData,
|
||||||
|
pub goal1: [u32; 2],
|
||||||
|
pub goal2: [u32; 2],
|
||||||
|
pub blocks: Vec<BlockData>,
|
||||||
|
}
|
51
src/enums.rs
Normal file
51
src/enums.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Board {
|
||||||
|
Left = 0,
|
||||||
|
Right = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Board {
|
||||||
|
fn from(n: u32) -> Self {
|
||||||
|
match n {
|
||||||
|
0 => Board::Left,
|
||||||
|
1 => Board::Right,
|
||||||
|
_ => panic!("expecting 0 or 1, got {}", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum MotionDir {
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum PushDir {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Shape {
|
||||||
|
Full = 0,
|
||||||
|
TopRight = 1,
|
||||||
|
TopLeft = 2,
|
||||||
|
BottomLeft = 3,
|
||||||
|
BottomRight = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Shape {
|
||||||
|
fn from(n: u32) -> Self {
|
||||||
|
match n {
|
||||||
|
0 => Shape::Full,
|
||||||
|
1 => Shape::TopRight,
|
||||||
|
2 => Shape::TopLeft,
|
||||||
|
3 => Shape::BottomLeft,
|
||||||
|
4 => Shape::BottomRight,
|
||||||
|
_ => panic!("expecting 0..4, got {}", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/game.rs
Normal file
51
src/game.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use glium::{Display, Frame};
|
||||||
|
|
||||||
|
use crate::level::Level;
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
use crate::resources::Resources;
|
||||||
|
|
||||||
|
const CELL_VERT: &str = include_str!("../shaders/cell.vert");
|
||||||
|
const CELL_FRAG: &str = include_str!("../shaders/cell.frag");
|
||||||
|
|
||||||
|
const LEVEL_TUTORIAL: &str = include_str!("../levels/tutorial.json");
|
||||||
|
|
||||||
|
pub struct Game<'a> {
|
||||||
|
pub resources: Resources,
|
||||||
|
pub display: &'a Display,
|
||||||
|
levels: Vec<Level>,
|
||||||
|
current_level: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Game<'a> {
|
||||||
|
pub fn new(display: &'a Display) -> Game {
|
||||||
|
let mut resources = Resources::default();
|
||||||
|
resources
|
||||||
|
.load_shader(display, "cell", &CELL_VERT, &CELL_FRAG)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let levels = vec![Level::from_json(&LEVEL_TUTORIAL)];
|
||||||
|
Game {
|
||||||
|
resources,
|
||||||
|
display,
|
||||||
|
levels,
|
||||||
|
current_level: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_renderer<'b>(&self, target: &'b mut Frame) -> Renderer<'b, '_> {
|
||||||
|
Renderer::new(self, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_level(&self) -> &Level {
|
||||||
|
self.levels.iter().nth(self.current_level).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, delta: Duration) {}
|
||||||
|
|
||||||
|
pub fn render(&self, renderer: &mut Renderer) {
|
||||||
|
let level = self.get_current_level();
|
||||||
|
level.render(renderer);
|
||||||
|
}
|
||||||
|
}
|
75
src/level.rs
Normal file
75
src/level.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::data::LevelData;
|
||||||
|
use crate::enums::{Board, Shape};
|
||||||
|
use crate::renderer::Renderer;
|
||||||
|
use crate::{GAME_HEIGHT, GAME_WIDTH};
|
||||||
|
|
||||||
|
pub struct Level {
|
||||||
|
dimensions: (u32, u32),
|
||||||
|
move_stack: VecDeque<()>,
|
||||||
|
blocks: Vec<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Block {
|
||||||
|
segments: Vec<Segment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Segment(u32, u32, Shape, Board);
|
||||||
|
|
||||||
|
impl Level {
|
||||||
|
pub fn from_json(data: impl AsRef<str>) -> Level {
|
||||||
|
let data: LevelData = json5::from_str(data.as_ref()).unwrap();
|
||||||
|
println!("{:?}", data);
|
||||||
|
|
||||||
|
let blocks = data
|
||||||
|
.blocks
|
||||||
|
.iter()
|
||||||
|
.map(|block| {
|
||||||
|
let segments = block
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.map(|segment| {
|
||||||
|
Segment(segment[0], segment[1], segment[2].into(), segment[3].into())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Block { segments }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Level {
|
||||||
|
dimensions: (data.dimensions[0], data.dimensions[1]),
|
||||||
|
move_stack: VecDeque::new(),
|
||||||
|
blocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
(scale, 0, yoff)
|
||||||
|
} else {
|
||||||
|
let scale = GAME_HEIGHT / (self.dimensions.1 + 4);
|
||||||
|
let xoff = GAME_WIDTH / 2 - (2 * self.dimensions.0 + 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)) {
|
||||||
|
let left_off = (offset.0 + 2 * scale, offset.1 + 2 * scale);
|
||||||
|
let right_off = (
|
||||||
|
offset.0 + (4 + self.dimensions.0) * scale,
|
||||||
|
offset.1 + 2 * scale,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.render_cell(left_off, 50, Shape::Full);
|
||||||
|
}
|
||||||
|
}
|
62
src/main.rs
Normal file
62
src/main.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate glium;
|
||||||
|
extern crate nalgebra_glm as glm;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
mod data;
|
||||||
|
mod enums;
|
||||||
|
mod game;
|
||||||
|
mod level;
|
||||||
|
mod renderer;
|
||||||
|
mod resources;
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use glium::glutin::{
|
||||||
|
dpi::PhysicalSize, ContextBuilder, Event, EventsLoop, WindowBuilder, WindowEvent,
|
||||||
|
};
|
||||||
|
use glium::{Display, Rect};
|
||||||
|
|
||||||
|
use crate::game::Game;
|
||||||
|
|
||||||
|
const GAME_WIDTH: u32 = 1024;
|
||||||
|
const GAME_HEIGHT: u32 = 768;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut events_loop = EventsLoop::new();
|
||||||
|
let primary_monitor = events_loop.get_primary_monitor();
|
||||||
|
let dpi_factor = primary_monitor.get_hidpi_factor();
|
||||||
|
let dimensions: PhysicalSize = (GAME_WIDTH, GAME_HEIGHT).into();
|
||||||
|
|
||||||
|
let wb = WindowBuilder::new()
|
||||||
|
.with_dimensions(dimensions.to_logical(dpi_factor))
|
||||||
|
.with_resizable(false)
|
||||||
|
.with_title("wedge");
|
||||||
|
let cb = ContextBuilder::new();
|
||||||
|
let display = Display::new(wb, cb, &events_loop).unwrap();
|
||||||
|
|
||||||
|
let game = Game::new(&display);
|
||||||
|
|
||||||
|
let mut closed = false;
|
||||||
|
let mut prev = Instant::now();
|
||||||
|
while !closed {
|
||||||
|
let now = Instant::now();
|
||||||
|
let delta = now - prev;
|
||||||
|
|
||||||
|
events_loop.poll_events(|event| match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => closed = true,
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut target = display.draw();
|
||||||
|
let mut renderer = game.create_renderer(&mut target);
|
||||||
|
game.render(&mut renderer);
|
||||||
|
target.finish().unwrap();
|
||||||
|
|
||||||
|
prev = now;
|
||||||
|
}
|
||||||
|
}
|
76
src/renderer.rs
Normal file
76
src/renderer.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use glium::draw_parameters::{Blend, DrawParameters};
|
||||||
|
use glium::index::{NoIndices, PrimitiveType};
|
||||||
|
use glium::{Display, Frame, Program, Surface, VertexBuffer};
|
||||||
|
use nalgebra::Matrix4;
|
||||||
|
|
||||||
|
use crate::enums::Shape;
|
||||||
|
use crate::game::Game;
|
||||||
|
use crate::{GAME_HEIGHT, GAME_WIDTH};
|
||||||
|
|
||||||
|
pub struct Renderer<'a, 'b> {
|
||||||
|
target: &'a mut Frame,
|
||||||
|
display: &'b Display,
|
||||||
|
program: &'b Program,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Renderer<'a, 'b> {
|
||||||
|
pub fn new(game: &'b Game, target: &'a mut Frame) -> Self {
|
||||||
|
let program = game.resources.get_shader("cell").unwrap();
|
||||||
|
Renderer {
|
||||||
|
target,
|
||||||
|
display: &game.display,
|
||||||
|
program,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_cell(&mut self, location: (u32, u32), scale: u32, shape: Shape) {
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct Vertex {
|
||||||
|
point: [f32; 2],
|
||||||
|
}
|
||||||
|
implement_vertex!(Vertex, point);
|
||||||
|
|
||||||
|
let indices = NoIndices(PrimitiveType::TrianglesList);
|
||||||
|
let mut vertices = Vec::<Vertex>::new();
|
||||||
|
match shape {
|
||||||
|
Shape::Full => {
|
||||||
|
vertices.push(Vertex { point: [0.0, 0.0] });
|
||||||
|
vertices.push(Vertex { point: [1.0, 0.0] });
|
||||||
|
vertices.push(Vertex { point: [0.0, 1.0] });
|
||||||
|
vertices.push(Vertex { point: [1.0, 1.0] });
|
||||||
|
vertices.push(Vertex { point: [0.0, 1.0] });
|
||||||
|
vertices.push(Vertex { point: [1.0, 0.0] });
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
vertices.push(Vertex { point: [0.0, 0.0] });
|
||||||
|
vertices.push(Vertex { point: [1.0, 0.0] });
|
||||||
|
vertices.push(Vertex { point: [0.0, 1.0] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let vertex_buffer = VertexBuffer::new(self.display, &vertices).unwrap();
|
||||||
|
|
||||||
|
let projection =
|
||||||
|
glm::ortho::<f32>(0.0, GAME_WIDTH as f32, GAME_HEIGHT as f32, 0.0, -1.0, 1.0);
|
||||||
|
let mut matrix = Matrix4::<f32>::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());
|
||||||
|
|
||||||
|
let uniforms = uniform! {
|
||||||
|
target: *matrix.as_ref(),
|
||||||
|
projection: *projection.as_ref(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.target
|
||||||
|
.draw(
|
||||||
|
&vertex_buffer,
|
||||||
|
&indices,
|
||||||
|
&self.program,
|
||||||
|
&uniforms,
|
||||||
|
&DrawParameters {
|
||||||
|
blend: Blend::alpha_blending(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
28
src/resources.rs
Normal file
28
src/resources.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use glium::{Display, Program, ProgramCreationError, Texture2d};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Resources {
|
||||||
|
textures: HashMap<String, Texture2d>,
|
||||||
|
shaders: HashMap<String, Program>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resources {
|
||||||
|
pub fn load_shader(
|
||||||
|
&mut self,
|
||||||
|
display: &Display,
|
||||||
|
name: impl AsRef<str>,
|
||||||
|
vertex: &str,
|
||||||
|
fragment: &str,
|
||||||
|
) -> Result<(), ProgramCreationError> {
|
||||||
|
let name = name.as_ref().to_owned();
|
||||||
|
let program = Program::from_source(display, vertex, fragment, None)?;
|
||||||
|
self.shaders.insert(name, program);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shader(&self, name: impl AsRef<str>) -> Option<&Program> {
|
||||||
|
self.shaders.get(name.as_ref())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue