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