refactor to modules; update player based on new prototype
This commit is contained in:
parent
04f86f6aa6
commit
6a0251dc0c
11
Cargo.toml
11
Cargo.toml
|
@ -7,7 +7,14 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ggez = "^0.5.1"
|
env_logger = "0.8.3"
|
||||||
toml = "^0.5.6"
|
image = "0.23.14"
|
||||||
|
log = "0.4.14"
|
||||||
|
pixels = "0.3.0"
|
||||||
|
rodio = "^0.11.0"
|
||||||
|
rodio-xm = { git = "https://tinybird.dev/max/rodio-xm/", branch = "master" }
|
||||||
serde = "^1.0.114"
|
serde = "^1.0.114"
|
||||||
serde_derive = "^1.0.114"
|
serde_derive = "^1.0.114"
|
||||||
|
toml = "^0.5.6"
|
||||||
|
winit = "0.24.0"
|
||||||
|
winit_input_helper = "0.9.0"
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# to do
|
||||||
|
|
||||||
|
## game data structure
|
||||||
|
|
||||||
|
### parser
|
||||||
|
|
||||||
|
* ~move tests into their respective modules where appropriate~
|
||||||
|
|
||||||
|
## players
|
||||||
|
|
||||||
|
* ~graphics~
|
||||||
|
* re-use player avatar drawing function as generic image drawing function
|
||||||
|
* text (how?)
|
||||||
|
* support older graphics adaptors
|
||||||
|
|
||||||
|
### linux
|
||||||
|
|
||||||
|
* ~get working~
|
||||||
|
|
||||||
|
### windows
|
||||||
|
|
||||||
|
* ~try to compile~
|
||||||
|
* does not work on my acer laptop (2012)
|
||||||
|
* does not work on my windows 8 VM
|
||||||
|
* works on my computer via wine!
|
||||||
|
|
||||||
|
### future: arm/raspberry pi?
|
||||||
|
|
||||||
|
### web
|
||||||
|
|
||||||
|
will need:
|
||||||
|
|
||||||
|
* base64 decoding
|
||||||
|
* zip parsing
|
||||||
|
* webgl or something?
|
||||||
|
* audio??
|
||||||
|
|
||||||
|
## editor
|
||||||
|
|
||||||
|
* build something in egui
|
||||||
|
* can we do a web version that works with zip files?
|
||||||
|
* investigate zip compression/decompression in rust
|
|
@ -1,302 +1,301 @@
|
||||||
use ggez;
|
#[windows_subsystem = "windows"]
|
||||||
|
|
||||||
// Next we need to actually `use` the pieces of ggez that we are going
|
use log::error;
|
||||||
// to need frequently.
|
use pixels::{Error, SurfaceTexture, PixelsBuilder};
|
||||||
use ggez::event::{KeyCode, KeyMods, EventsLoop};
|
use pixels::wgpu::BackendBit;
|
||||||
use ggez::{event, graphics, Context, GameResult};
|
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||||
|
use winit::event::{Event, VirtualKeyCode};
|
||||||
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// We'll bring in some things from `std` to help us in the future.
|
#[derive(Clone, Debug)]
|
||||||
use std::time::{Duration, Instant};
|
struct Image {
|
||||||
|
pixels: [u8; 64]
|
||||||
use ggez::graphics::{Rect};
|
|
||||||
use ggez::conf::FullscreenType;
|
|
||||||
|
|
||||||
// The first thing we want to do is set up some constants that will help us out later.
|
|
||||||
|
|
||||||
// Here we define the size of our game board in terms of how many grid
|
|
||||||
// cells it will take up. We choose to make a 30 x 20 game board.
|
|
||||||
const GRID_SIZE: (u8, u8) = (16, 9);
|
|
||||||
// dimension, i.e. 8×8 (square)
|
|
||||||
const GRID_CELL_SIZE: u8 = 8;
|
|
||||||
|
|
||||||
const UPDATES_PER_SECOND: f32 = 0.4;
|
|
||||||
// And we get the milliseconds of delay that this update rate corresponds to.
|
|
||||||
const MILLIS_PER_UPDATE: u64 = (1.0 / UPDATES_PER_SECOND * 1000.0) as u64;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
struct GridPosition { x: u8, y: u8 }
|
|
||||||
|
|
||||||
impl GridPosition {
|
|
||||||
/// We make a standard helper function so that we can create a new `GridPosition`
|
|
||||||
/// more easily.
|
|
||||||
pub fn new(x: u8, y: u8) -> Self {
|
|
||||||
GridPosition { x, y }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We'll make another helper function that takes one grid position and returns a new one after
|
struct Game {
|
||||||
/// making one move in the direction of `dir`. We use our `SignedModulo` trait
|
width: usize,
|
||||||
/// above, which is now implemented on `i16` because it satisfies the trait bounds,
|
height: usize,
|
||||||
/// to automatically wrap around within our grid size if the move would have otherwise
|
player_position: (u8, u8),
|
||||||
/// moved us off the board to the top, bottom, left, or right.
|
player_avatar: Image,
|
||||||
pub fn new_from_move(pos: GridPosition, dir: Direction) -> Self {
|
palette: [[u8; 4]; 4],
|
||||||
match dir {
|
current_music: Option<String>,
|
||||||
Direction::Up => GridPosition::new(pos.x, pos.y - 1),
|
music: HashMap<String, rodio::Sink>,
|
||||||
Direction::Down => GridPosition::new(pos.x, pos.y + 1),
|
|
||||||
Direction::Left => GridPosition::new(pos.x - 1, pos.y),
|
|
||||||
Direction::Right => GridPosition::new(pos.x + 1, pos.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We implement the `From` trait, which in this case allows us to convert easily between
|
impl Game {
|
||||||
/// a GridPosition and a ggez `graphics::Rect` which fills that grid cell.
|
fn draw(&self, screen: &mut [u8]) {
|
||||||
/// Now we can just call `.into()` on a `GridPosition` where we want a
|
// clear screen
|
||||||
/// `Rect` that represents that grid cell.
|
for pixel in screen.chunks_exact_mut(4) {
|
||||||
impl From<GridPosition> for graphics::Rect {
|
pixel.copy_from_slice(&self.palette[0]);
|
||||||
fn from(pos: GridPosition) -> Self {
|
}
|
||||||
graphics::Rect::new_i32(
|
|
||||||
pos.x as i32 * GRID_CELL_SIZE as i32,
|
let (player_x, player_y) = self.player_position;
|
||||||
pos.y as i32 * GRID_CELL_SIZE as i32,
|
|
||||||
GRID_CELL_SIZE as i32,
|
// each row of player avatar
|
||||||
GRID_CELL_SIZE as i32,
|
for (tile_y, row) in self.player_avatar.pixels.chunks(8).enumerate() {
|
||||||
|
for (tile_x, pixel) in row.iter().enumerate() {
|
||||||
|
let colour = self.palette[*pixel as usize];
|
||||||
|
|
||||||
|
for (v, value) in colour.iter().enumerate() {
|
||||||
|
screen[
|
||||||
|
(
|
||||||
|
// player vertical offset (number of lines above)
|
||||||
|
(player_y as usize * 8 * (8 * self.width as usize))
|
||||||
|
+
|
||||||
|
// player horizontal offset; number of pixels to the left
|
||||||
|
(player_x as usize * 8)
|
||||||
|
+
|
||||||
|
// tile vertical offset; number of lines within tile
|
||||||
|
(tile_y as usize * (8 * self.width as usize))
|
||||||
|
+
|
||||||
|
(tile_x as usize) // tile horizontal offset
|
||||||
)
|
)
|
||||||
|
* 4 // we're dealing with rgba values so multiply everything by 4
|
||||||
|
+ v // value offset: which of the rgba values?
|
||||||
|
] = value.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(u8, u8)> for GridPosition {
|
|
||||||
fn from(pos: (u8, u8)) -> Self {
|
|
||||||
GridPosition { x: pos.0, y: pos.1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Next we create an enum that will represent all the possible
|
|
||||||
/// directions that our snake could move.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
enum Direction {
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Direction {
|
|
||||||
/// We also create a helper function that will let us convert between a
|
|
||||||
/// `ggez` `Keycode` and the `Direction` that it represents. Of course,
|
|
||||||
/// not every keycode represents a direction, so we return `None` if this
|
|
||||||
/// is the case.
|
|
||||||
pub fn from_keycode(key: KeyCode) -> Option<Direction> {
|
|
||||||
match key {
|
|
||||||
KeyCode::Up => Some(Direction::Up),
|
|
||||||
KeyCode::Down => Some(Direction::Down),
|
|
||||||
KeyCode::Left => Some(Direction::Left),
|
|
||||||
KeyCode::Right => Some(Direction::Right),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Avatar {
|
// fn update(&self) {
|
||||||
pos: GridPosition,
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Avatar {
|
fn main() -> Result<(), Error> {
|
||||||
pub fn new(pos: GridPosition) -> Self {
|
env_logger::init();
|
||||||
Avatar {
|
|
||||||
pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main update function for our snake which gets called every time
|
let mut game = Game {
|
||||||
/// we want to update the game state.
|
width: 16,
|
||||||
fn update(&mut self) {
|
height: 9,
|
||||||
|
player_position: (8, 4),
|
||||||
}
|
player_avatar: Image { pixels: [
|
||||||
|
0,0,0,2,2,0,0,0,
|
||||||
/// Again, note that this approach to drawing is fine for the limited scope of this
|
0,0,0,2,2,0,0,0,
|
||||||
/// example, but larger scale games will likely need a more optimized render path
|
0,0,1,1,1,0,2,0,
|
||||||
/// using SpriteBatch or something similar that batches draw calls.
|
0,1,1,1,1,1,0,0,
|
||||||
fn draw(&self, ctx: &mut Context, multiplier: &u8) -> GameResult<()> {
|
2,0,1,1,1,0,0,0,
|
||||||
let dimension = (GRID_CELL_SIZE * multiplier) as f32;
|
0,0,3,3,3,0,0,0,
|
||||||
|
0,0,3,0,3,0,0,0,
|
||||||
// And then we do the same for the head, instead making it fully red to distinguish it.
|
0,0,3,0,3,0,0,0,
|
||||||
let rectangle = graphics::Mesh::new_rectangle(
|
]},
|
||||||
ctx,
|
palette: [
|
||||||
graphics::DrawMode::fill(),
|
[0xff, 0x7f, 0x7f, 0xff],
|
||||||
ggez::graphics::Rect {
|
[0xff, 0xb2, 0x7f, 0xff],
|
||||||
x: (self.pos.x as u16 * GRID_CELL_SIZE as u16 * *multiplier as u16) as f32,
|
[0xff, 0xe9, 0x7f, 0xff],
|
||||||
y: (self.pos.y as u16 * GRID_CELL_SIZE as u16 * *multiplier as u16) as f32,
|
[0x00, 0x7f, 0x7f, 0x46],
|
||||||
w: dimension,
|
],
|
||||||
h: dimension,
|
current_music: None,
|
||||||
},
|
music: HashMap::new(),
|
||||||
[1.0, 0.5, 0.0, 1.0].into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
graphics::draw(ctx, &rectangle, (ggez::mint::Point2 { x: 0.0, y: 0.0 },))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Now we have the heart of our game, the GameState. This struct
|
|
||||||
/// will implement ggez's `EventHandler` trait and will therefore drive
|
|
||||||
/// everything else that happens in our game.
|
|
||||||
struct GameState {
|
|
||||||
avatar: Avatar,
|
|
||||||
/// And we track the last time we updated so that we can limit
|
|
||||||
/// our update rate.
|
|
||||||
last_update: Instant,
|
|
||||||
/// integer multiples for scaling the display
|
|
||||||
size_multiplier: u8,
|
|
||||||
fullscreen: bool,
|
|
||||||
scene_width: u8,
|
|
||||||
scene_height: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GameState {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let avatar = Avatar {
|
|
||||||
pos: (GRID_SIZE.0 / 4, GRID_SIZE.1 / 2).into(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GameState {
|
let event_loop = EventLoop::new();
|
||||||
avatar,
|
let mut input = WinitInputHelper::new();
|
||||||
last_update: Instant::now(),
|
|
||||||
size_multiplier: 4,
|
|
||||||
fullscreen: false,
|
|
||||||
scene_width: 16,
|
|
||||||
scene_height: 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_fullscreen(&mut self) {
|
let (window, p_width, p_height, mut _hidpi_factor) = create_window(
|
||||||
self.fullscreen = !self.fullscreen;
|
"pixels test",
|
||||||
}
|
(game.width * 8) as f64,
|
||||||
}
|
(game.height * 8) as f64,
|
||||||
|
&event_loop
|
||||||
/// Now we implement EventHandler for GameState. This provides an interface
|
|
||||||
/// that ggez will call automatically when different events happen.
|
|
||||||
impl event::EventHandler for GameState {
|
|
||||||
/// Update will happen on every frame before it is drawn. This is where we update
|
|
||||||
/// our game state to react to whatever is happening in the game world.
|
|
||||||
fn update(&mut self, _ctx: &mut Context) -> GameResult {
|
|
||||||
// First we check to see if enough time has elapsed since our last update based on
|
|
||||||
// the update rate we defined at the top.
|
|
||||||
if Instant::now() - self.last_update >= Duration::from_millis(MILLIS_PER_UPDATE) {
|
|
||||||
self.avatar.update();
|
|
||||||
|
|
||||||
// If we updated, we set our last_update to be now
|
|
||||||
self.last_update = Instant::now();
|
|
||||||
}
|
|
||||||
// Finally we return `Ok` to indicate we didn't run into any errors
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// draw is where we should actually render the game's current state.
|
|
||||||
fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
|
||||||
graphics::clear(ctx, [0.1, 0.1, 0.1, 1.0].into());
|
|
||||||
|
|
||||||
// todo draw the whole game, not just the avatar
|
|
||||||
self.avatar.draw(ctx, &self.size_multiplier)?;
|
|
||||||
|
|
||||||
// Finally we call graphics::present to cycle the GPU's framebuffer and display
|
|
||||||
// the new frame we just drew.
|
|
||||||
graphics::present(ctx)?;
|
|
||||||
|
|
||||||
// We yield the current thread until the next update
|
|
||||||
ggez::timer::yield_now();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// key_down_event gets fired when a key gets pressed.
|
|
||||||
fn key_down_event(
|
|
||||||
&mut self,
|
|
||||||
ctx: &mut Context,
|
|
||||||
keycode: KeyCode,
|
|
||||||
modifier: KeyMods,
|
|
||||||
_repeat: bool,
|
|
||||||
) {
|
|
||||||
if let Some(dir) = Direction::from_keycode(keycode) {
|
|
||||||
match dir {
|
|
||||||
Direction::Up => {
|
|
||||||
if self.avatar.pos.y > 0 {
|
|
||||||
self.avatar.pos.y -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Direction::Right => {
|
|
||||||
if self.avatar.pos.x < GRID_SIZE.0 - 1 {
|
|
||||||
self.avatar.pos.x += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Direction::Down => {
|
|
||||||
// if y is less than 8 it's ok to increment it
|
|
||||||
if self.avatar.pos.y < GRID_SIZE.1 - 1 {
|
|
||||||
self.avatar.pos.y += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Direction::Left => {
|
|
||||||
if self.avatar.pos.x > 0 {
|
|
||||||
self.avatar.pos.x -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo handle plus/minus keys
|
|
||||||
if keycode == KeyCode::Add {
|
|
||||||
self.size_multiplier += 1;
|
|
||||||
} else if keycode == KeyCode::Subtract && self.size_multiplier > 1 {
|
|
||||||
self.size_multiplier -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if keycode == KeyCode::F11 || (modifier == KeyMods::ALT && keycode == KeyCode::Return) {
|
|
||||||
self.toggle_fullscreen();
|
|
||||||
|
|
||||||
if self.fullscreen {
|
|
||||||
graphics::set_fullscreen(ctx, FullscreenType::True).unwrap();
|
|
||||||
} else {
|
|
||||||
graphics::set_fullscreen(ctx, FullscreenType::Windowed).unwrap();
|
|
||||||
graphics::set_drawable_size(
|
|
||||||
ctx,
|
|
||||||
(self.scene_width * self.size_multiplier) as f32,
|
|
||||||
(self.scene_height * self.size_multiplier) as f32
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo change window size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_setup(x: u8, y: u8, multiplier: u8) -> (Context, EventsLoop) {
|
|
||||||
let x = (x as u16 * GRID_CELL_SIZE as u16 * multiplier as u16) as f32;
|
|
||||||
let y = (y as u16 * GRID_CELL_SIZE as u16 * multiplier as u16) as f32;
|
|
||||||
|
|
||||||
// Here we use a ContextBuilder to setup metadata about our game. First the title and author
|
|
||||||
let (ctx, events_loop) = ggez::ContextBuilder::new("snake", "Gray Olson")
|
|
||||||
// Next we set up the window. This title will be displayed in the title bar of the window.
|
|
||||||
.window_setup(
|
|
||||||
ggez::conf::WindowSetup::default().title("Write your game's title here")
|
|
||||||
)
|
|
||||||
// Now we get to set the size of the window, which we use our SCREEN_SIZE constant from earlier to help with
|
|
||||||
.window_mode(ggez::conf::WindowMode::default().dimensions(x, y))
|
|
||||||
// And finally we attempt to build the context and create the window. If it fails, we panic with the message
|
|
||||||
// "Failed to build ggez context"
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(ctx, events_loop)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> GameResult {
|
|
||||||
// Next we create a new instance of our GameState struct, which implements EventHandler
|
|
||||||
let state = &mut GameState::new();
|
|
||||||
|
|
||||||
let (mut ctx, mut events_loop) = window_setup(
|
|
||||||
GRID_SIZE.0,
|
|
||||||
GRID_SIZE.1,
|
|
||||||
state.size_multiplier
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// And finally we actually run our game, passing in our context and state.
|
let surface_texture = SurfaceTexture::new(p_width, p_height, &window);
|
||||||
event::run(&mut ctx, &mut events_loop, state)
|
|
||||||
|
let mut pixels = PixelsBuilder::new(
|
||||||
|
(game.width * 8) as u32, (game.height * 8) as u32, surface_texture
|
||||||
|
)
|
||||||
|
.wgpu_backend(BackendBit::GL | BackendBit::PRIMARY)
|
||||||
|
.enable_vsync(false)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let device = rodio::default_output_device().unwrap();
|
||||||
|
|
||||||
|
let source = rodio_xm::XMSource::from_bytes(
|
||||||
|
include_bytes!("../ninety degrees.xm")
|
||||||
|
);
|
||||||
|
|
||||||
|
let sink = rodio::Sink::new(&device);
|
||||||
|
sink.append(source);
|
||||||
|
sink.pause();
|
||||||
|
|
||||||
|
game.music.insert(":ninety degrees".into(), sink);
|
||||||
|
|
||||||
|
let source = rodio_xm::XMSource::from_bytes(
|
||||||
|
include_bytes!("../orn_keygentheme2001.xm")
|
||||||
|
);
|
||||||
|
|
||||||
|
let sink = rodio::Sink::new(&device);
|
||||||
|
sink.append(source);
|
||||||
|
sink.pause();
|
||||||
|
|
||||||
|
game.music.insert("orn_keygentheme2001".into(), sink);
|
||||||
|
|
||||||
|
game.current_music = None;
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
// The one and only event that winit_input_helper doesn't have for us...
|
||||||
|
if let Event::RedrawRequested(_) = event {
|
||||||
|
game.draw(pixels.get_frame());
|
||||||
|
|
||||||
|
if pixels
|
||||||
|
.render()
|
||||||
|
.map_err(|e| error!("pixels.render() failed: {:?}", e))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For everything else, for let winit_input_helper collect events to build its state.
|
||||||
|
// It returns `true` when it is time to update our game state and request a redraw.
|
||||||
|
if input.update(&event) {
|
||||||
|
// Close events
|
||||||
|
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::M) {
|
||||||
|
// pause the current tune
|
||||||
|
if game.current_music.is_some() {
|
||||||
|
game.music.get(game.current_music.as_ref().unwrap()).unwrap().pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
if game.current_music.is_none() || game.current_music.as_ref().unwrap() == "orn_keygentheme2001" {
|
||||||
|
// play the first tune
|
||||||
|
game.current_music = Some(":ninety degrees".into());
|
||||||
|
game.music.get(game.current_music.as_ref().unwrap()).unwrap().play();
|
||||||
|
} else {
|
||||||
|
// play the second tune
|
||||||
|
game.current_music = Some("orn_keygentheme2001".into());
|
||||||
|
game.music.get(game.current_music.as_ref().unwrap()).unwrap().play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Left) {
|
||||||
|
let (x, y) = game.player_position;
|
||||||
|
if x > 0 {
|
||||||
|
game.player_position = (x - 1, y);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Right) {
|
||||||
|
let (x, y) = game.player_position;
|
||||||
|
if x < game.width as u8 - 1 {
|
||||||
|
game.player_position = (x + 1, y);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Up) {
|
||||||
|
let (x, y) = game.player_position;
|
||||||
|
if y > 0 {
|
||||||
|
game.player_position = (x, y - 1);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Down) {
|
||||||
|
let (x, y) = game.player_position;
|
||||||
|
if y < game.height as u8 - 1 {
|
||||||
|
game.player_position = (x, y + 1);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust high DPI factor
|
||||||
|
if let Some(factor) = input.scale_factor_changed() {
|
||||||
|
_hidpi_factor = factor;
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the window
|
||||||
|
if let Some(size) = input.window_resized() {
|
||||||
|
pixels.resize_surface(size.width, size.height);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
*control_flow = ControlFlow::Wait;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn window_builder(title: &str, event_loop: &EventLoop<()>) -> winit::window::Window {
|
||||||
|
winit::window::WindowBuilder::new()
|
||||||
|
.with_visible(false)
|
||||||
|
.with_title(title).build(&event_loop).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn window_builder(title: &str, event_loop: &EventLoop<()>) -> winit::window::Window {
|
||||||
|
use winit::platform::windows::WindowBuilderExtWindows;
|
||||||
|
|
||||||
|
winit::window::WindowBuilder::new()
|
||||||
|
.with_drag_and_drop(false)
|
||||||
|
.with_visible(false)
|
||||||
|
.with_title(title).build(&event_loop).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a window for the game.
|
||||||
|
///
|
||||||
|
/// Automatically scales the window to cover about 2/3 of the monitor height.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Tuple of `(window, surface, width, height, hidpi_factor)`
|
||||||
|
/// `width` and `height` are in `PhysicalSize` units.
|
||||||
|
fn create_window(
|
||||||
|
title: &str,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
event_loop: &EventLoop<()>,
|
||||||
|
) -> (winit::window::Window, u32, u32, f64) {
|
||||||
|
let window = window_builder(title, event_loop);
|
||||||
|
|
||||||
|
let hidpi_factor = window.scale_factor();
|
||||||
|
|
||||||
|
// Get dimensions
|
||||||
|
|
||||||
|
let (monitor_width, monitor_height) = {
|
||||||
|
if let Some(monitor) = window.current_monitor() {
|
||||||
|
let size = monitor.size().to_logical(hidpi_factor);
|
||||||
|
(size.width, size.height)
|
||||||
|
} else {
|
||||||
|
(width, height)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let scale = (monitor_height / height * 2.0 / 3.0).round().max(1.0);
|
||||||
|
|
||||||
|
// Resize, center, and display the window
|
||||||
|
let min_size: winit::dpi::LogicalSize<f64> =
|
||||||
|
PhysicalSize::new(width, height).to_logical(hidpi_factor);
|
||||||
|
|
||||||
|
let default_size =
|
||||||
|
LogicalSize::new(width * scale, height * scale);
|
||||||
|
|
||||||
|
let center = LogicalPosition::new(
|
||||||
|
(monitor_width - width * scale) / 2.0,
|
||||||
|
(monitor_height - height * scale) / 2.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.set_inner_size(default_size);
|
||||||
|
window.set_min_inner_size(Some(min_size));
|
||||||
|
window.set_outer_position(center);
|
||||||
|
window.set_visible(true);
|
||||||
|
|
||||||
|
let size = default_size.to_physical::<f64>(hidpi_factor);
|
||||||
|
|
||||||
|
(
|
||||||
|
window,
|
||||||
|
size.width.round() as u32,
|
||||||
|
size.height.round() as u32,
|
||||||
|
hidpi_factor,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Colour {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
pub alpha: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Colour {
|
||||||
|
pub fn from(colours: Vec<u8>) -> Colour {
|
||||||
|
const ZERO: u8 = 0;
|
||||||
|
Colour {
|
||||||
|
red: *colours.get(0).unwrap_or(&ZERO),
|
||||||
|
green: *colours.get(1).unwrap_or(&ZERO),
|
||||||
|
blue: *colours.get(2).unwrap_or(&ZERO),
|
||||||
|
alpha: *colours.get(3).unwrap_or(&255),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
vec![self.red, self.green, self.blue, self.alpha]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Colour;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_colour_from_intermediate() {
|
||||||
|
let output = Colour::from(vec![64, 128, 192, 255]);
|
||||||
|
let expected = Colour { red: 64, green: 128, blue: 192, alpha: 255 };
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_colour_to_intermediate() {
|
||||||
|
let output = Colour { red: 64, green: 128, blue: 192, alpha: 255 }.to_vec();
|
||||||
|
let expected = vec![64, 128, 192, 255];
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
/// used in the window title bar
|
||||||
|
name: Option<String>,
|
||||||
|
width: u8,
|
||||||
|
height: u8,
|
||||||
|
/// animation rate in milliseconds
|
||||||
|
tick: u64,
|
||||||
|
/// if this is not specified, the game will pick the first room it finds
|
||||||
|
starting_room: Option<String>,
|
||||||
|
/// major / minor
|
||||||
|
version: (u8, u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_from_toml() {
|
||||||
|
let output: Config = toml::from_str(include_str!("test-resources/basic/game.toml")).unwrap();
|
||||||
|
let expected = Config {
|
||||||
|
name: Some("Write your game's title here".to_string()),
|
||||||
|
width: 16,
|
||||||
|
height: 9,
|
||||||
|
tick: 400,
|
||||||
|
starting_room: Some("example room".to_string()),
|
||||||
|
version: (0, 0)
|
||||||
|
};
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_to_toml() {
|
||||||
|
let output = toml::to_string(&Config {
|
||||||
|
name: Some("Write your game's title here".to_string()),
|
||||||
|
width: 16,
|
||||||
|
height: 9,
|
||||||
|
tick: 400,
|
||||||
|
starting_room: Some("example room".to_string()),
|
||||||
|
version: (0, 0)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let expected = include_str!("test-resources/basic/game.toml");
|
||||||
|
assert_eq!(&output, expected);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Image {
|
||||||
|
pub name: String,
|
||||||
|
/// colour indexes - todo convert to [u8; 64]?
|
||||||
|
pub pixels: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
fn from(intermediate: IntermediateImage) -> Image {
|
||||||
|
Image {
|
||||||
|
name: intermediate.name.to_owned(),
|
||||||
|
pixels: intermediate.pixels.split_whitespace().collect::<String>().chars().map(
|
||||||
|
|char|char as u8
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// for toml purposes
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct IntermediateImages {
|
||||||
|
/// singular so each image is named "image" instead of "images" in toml
|
||||||
|
image: Vec<IntermediateImage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateImages {
|
||||||
|
fn to_images(&self) -> Vec<Image> {
|
||||||
|
self.image.iter().map(|intermediate|
|
||||||
|
Image::from(intermediate.clone())
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct IntermediateImage {
|
||||||
|
name: String,
|
||||||
|
pixels: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateImage {
|
||||||
|
// todo refactor
|
||||||
|
fn from(image: Image) -> IntermediateImage {
|
||||||
|
let mut string = "\n".to_string();
|
||||||
|
|
||||||
|
let sqrt = (image.pixels.len() as f64).sqrt() as usize;
|
||||||
|
for line in image.pixels.chunks(sqrt) {
|
||||||
|
for pixel in line {
|
||||||
|
string.push_str(&format!("{}", *pixel));
|
||||||
|
}
|
||||||
|
string.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
IntermediateImage {
|
||||||
|
name: image.name.to_owned(),
|
||||||
|
/// todo wtf? I guess this crate doesn't handle multiline strings correctly
|
||||||
|
pixels: format!("\"\"{}\"\"", string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
// #[test]
|
||||||
|
// fn test_image_from_toml() {
|
||||||
|
// let str = include_str!("test-resources/basic/images.toml");
|
||||||
|
// let output: Image = toml::from_str(str).unwrap();
|
||||||
|
// let expected = crate::mock::image::avatar();
|
||||||
|
// assert_eq!(output, expected);
|
||||||
|
// }
|
||||||
|
}
|
436
src/lib.rs
436
src/lib.rs
|
@ -1,83 +1,18 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
use serde_derive::{Serialize, Deserialize};
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
mod colour;
|
||||||
pub struct Colour {
|
mod config;
|
||||||
red: u8,
|
mod image;
|
||||||
green: u8,
|
mod mock;
|
||||||
blue: u8,
|
mod palette;
|
||||||
alpha: u8,
|
mod room;
|
||||||
}
|
|
||||||
|
|
||||||
impl Colour {
|
pub use colour::Colour;
|
||||||
pub fn from(colours: Vec<u8>) -> Colour {
|
pub use palette::Palette;
|
||||||
const ZERO: u8 = 0;
|
pub use palette::IntermediatePalette;
|
||||||
Colour {
|
use crate::config::Config;
|
||||||
red: *colours.get(0).unwrap_or(&ZERO),
|
|
||||||
green: *colours.get(1).unwrap_or(&ZERO),
|
|
||||||
blue: *colours.get(2).unwrap_or(&ZERO),
|
|
||||||
alpha: *colours.get(3).unwrap_or(&255),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
|
||||||
vec![self.red, self.green, self.blue, self.alpha]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Palette {
|
|
||||||
name: String,
|
|
||||||
colours: Vec<Colour>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Palette {
|
|
||||||
pub fn from(name: &str, toml: &str) -> Self {
|
|
||||||
let intermediate: IntermediatePalette = toml::from_str(toml).unwrap();
|
|
||||||
|
|
||||||
println!("palette name: {}", name);
|
|
||||||
|
|
||||||
for colour in &intermediate.colours {
|
|
||||||
println!("palette colour: {}{}{}", colour[0], colour[1], colour[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Palette {
|
|
||||||
name: name.to_string(),
|
|
||||||
colours: intermediate.colours.iter().map(|vec| {
|
|
||||||
Colour::from(vec.clone())
|
|
||||||
}).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_file(path: String) -> Self {
|
|
||||||
// todo get name without extension
|
|
||||||
let name = "blah";
|
|
||||||
let toml = fs::read_to_string(path).unwrap();
|
|
||||||
Self::from(name, &toml)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// for toml purposes
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct IntermediatePalette {
|
|
||||||
colours: Vec<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// for toml purposes
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct IntermediatePalettes {
|
|
||||||
/// singular so each palette section is named "palette" instead of "palettes" in toml
|
|
||||||
palette: Vec<IntermediatePalette>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntermediatePalettes {
|
|
||||||
pub fn from_dir() -> Self {
|
|
||||||
Self {
|
|
||||||
palette: vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, Hash, PartialEq)]
|
#[derive(Eq, Hash, PartialEq)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
|
@ -85,66 +20,6 @@ pub struct Position {
|
||||||
y: u8,
|
y: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Image {
|
|
||||||
name: String,
|
|
||||||
/// colour indexes
|
|
||||||
pixels: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Image {
|
|
||||||
fn from(intermediate: IntermediateImage) -> Image {
|
|
||||||
Image {
|
|
||||||
name: intermediate.name.to_owned(),
|
|
||||||
pixels: intermediate.pixels.split_whitespace().collect::<String>().chars().map(
|
|
||||||
|char|char as u8
|
|
||||||
).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// for toml purposes
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct IntermediateImages {
|
|
||||||
/// singular so each image is named "image" instead of "images" in toml
|
|
||||||
image: Vec<IntermediateImage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntermediateImages {
|
|
||||||
fn to_images(&self) -> Vec<Image> {
|
|
||||||
self.image.iter().map(|intermediate|
|
|
||||||
Image::from(intermediate.clone())
|
|
||||||
).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
struct IntermediateImage {
|
|
||||||
name: String,
|
|
||||||
pixels: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntermediateImage {
|
|
||||||
// todo refactor
|
|
||||||
fn from(image: Image) -> IntermediateImage {
|
|
||||||
let mut string = "\n".to_string();
|
|
||||||
|
|
||||||
let sqrt = (image.pixels.len() as f64).sqrt() as usize;
|
|
||||||
for line in image.pixels.chunks(sqrt) {
|
|
||||||
for pixel in line {
|
|
||||||
string.push_str(&format!("{}", *pixel));
|
|
||||||
}
|
|
||||||
string.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
IntermediateImage {
|
|
||||||
name: image.name.to_owned(),
|
|
||||||
/// todo wtf? I guess this crate doesn't handle multiline strings correctly
|
|
||||||
pixels: format!("\"\"{}\"\"", string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(Serialize, Deserialize)]
|
// #[derive(Serialize, Deserialize)]
|
||||||
// pub struct Thing {
|
// pub struct Thing {
|
||||||
// name: Option<String>,
|
// name: Option<String>,
|
||||||
|
@ -153,42 +28,6 @@ impl IntermediateImage {
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
||||||
pub struct Room {
|
|
||||||
name: String,
|
|
||||||
width: u8,
|
|
||||||
height: u8,
|
|
||||||
/// thing names and their positions
|
|
||||||
background: HashMap<Position, String>,
|
|
||||||
foreground: HashMap<Position, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct IntermediateRoom {
|
|
||||||
name: String,
|
|
||||||
background: Vec<String>,
|
|
||||||
foreground: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntermediateRoom {
|
|
||||||
fn from(room: Room) -> IntermediateRoom {
|
|
||||||
fn hashmap_to_vec(hash: HashMap<Position, String>, width: u8, height: u8) -> Vec<String> {
|
|
||||||
let mut thing_ids = Vec::new();
|
|
||||||
|
|
||||||
while thing_ids.len() < (width * height) as usize {
|
|
||||||
thing_ids.push(String::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
thing_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
IntermediateRoom {
|
|
||||||
name: "".to_string(),
|
|
||||||
background: vec![],
|
|
||||||
foreground: vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(Serialize, Deserialize)]
|
// #[derive(Serialize, Deserialize)]
|
||||||
// pub enum DataType {
|
// pub enum DataType {
|
||||||
// Image,
|
// Image,
|
||||||
|
@ -261,18 +100,6 @@ impl IntermediateRoom {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
/// used in the window title bar
|
|
||||||
name: Option<String>,
|
|
||||||
width: u8,
|
|
||||||
height: u8,
|
|
||||||
/// animation rate in milliseconds
|
|
||||||
tick: u64,
|
|
||||||
/// if this is not specified, the game will pick the first room it finds
|
|
||||||
starting_room: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -280,225 +107,36 @@ pub struct Game {
|
||||||
// variables: Vec<Variable>,
|
// variables: Vec<Variable>,
|
||||||
// triggers: HashMap<String, ScriptCollection>,
|
// triggers: HashMap<String, ScriptCollection>,
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// #[derive(Debug)]
|
|
||||||
// pub struct GameParseError;
|
|
||||||
//
|
|
||||||
// impl Game {
|
|
||||||
// pub fn from(s: &str) -> Result<Game, GameParseError> {
|
|
||||||
// let result = toml::from_str(s);
|
|
||||||
// if result.is_ok() {
|
|
||||||
// Ok(result.unwrap())
|
|
||||||
// } else {
|
|
||||||
// Err(GameParseError)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
mod mock {
|
#[derive(Debug)]
|
||||||
pub(crate) mod image {
|
pub struct GameParseError;
|
||||||
use crate::Image;
|
|
||||||
|
|
||||||
pub fn bg() -> Image {
|
impl Game {
|
||||||
Image {
|
pub fn from(path: String) -> Result<Game, GameParseError> {
|
||||||
name: "bg".to_string(),
|
let path = PathBuf::from(path);
|
||||||
pixels: vec![
|
|
||||||
0,0,0,0,0,0,0,0,
|
let mut palettes_dir = path.clone();
|
||||||
0,0,0,0,0,0,0,0,
|
palettes_dir.push("palettes");
|
||||||
0,0,0,0,0,0,0,0,
|
let palette_files = palettes_dir.read_dir()
|
||||||
0,0,0,0,0,0,0,0,
|
.expect("couldn't find any palettes");
|
||||||
0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,
|
for file in palette_files {
|
||||||
0,0,0,0,0,0,0,0,
|
let file = file.unwrap();
|
||||||
0,0,0,0,0,0,0,0,
|
println!("palette found: {:?}", file.file_name());
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block() -> Image {
|
// todo load config
|
||||||
Image {
|
let mut game_config = path.clone();
|
||||||
name: "block".to_string(),
|
game_config.push("game.toml");
|
||||||
pixels: vec![
|
let config = fs::read_to_string(game_config)
|
||||||
1,1,1,1,1,1,1,1,
|
.expect("Couldn't load game config");
|
||||||
1,0,0,0,0,0,0,1,
|
let config: Config = toml::from_str(&config)
|
||||||
1,0,0,0,0,0,0,1,
|
.expect("Couldn't parse game config");
|
||||||
1,0,0,1,1,0,0,1,
|
|
||||||
1,0,0,1,1,0,0,1,
|
|
||||||
1,0,0,0,0,0,0,1,
|
|
||||||
1,0,0,0,0,0,0,1,
|
|
||||||
1,1,1,1,1,1,1,1,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn avatar() -> Image {
|
Ok(
|
||||||
Image {
|
Game {
|
||||||
name: "avatar".to_string(),
|
config,
|
||||||
pixels: vec![
|
}
|
||||||
0,0,0,2,2,0,0,0,
|
)
|
||||||
0,0,0,2,2,0,0,0,
|
|
||||||
0,0,0,2,2,0,0,0,
|
|
||||||
0,0,2,2,2,2,0,0,
|
|
||||||
0,2,2,2,2,2,2,0,
|
|
||||||
2,0,2,2,2,2,0,2,
|
|
||||||
0,0,2,0,0,2,0,0,
|
|
||||||
0,0,2,0,0,2,0,0,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cat() -> Image {
|
|
||||||
Image {
|
|
||||||
name: "cat".to_string(),
|
|
||||||
pixels: vec![
|
|
||||||
0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,
|
|
||||||
0,2,0,2,0,0,0,2,
|
|
||||||
0,2,2,2,0,0,0,2,
|
|
||||||
0,2,2,2,0,0,2,0,
|
|
||||||
0,2,2,2,2,2,0,0,
|
|
||||||
0,0,2,2,2,2,0,0,
|
|
||||||
0,0,2,0,0,2,0,0,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) mod palette {
|
|
||||||
use crate::{Palette, Colour, IntermediatePalette};
|
|
||||||
|
|
||||||
pub(crate) fn intermediate() -> IntermediatePalette {
|
|
||||||
IntermediatePalette {
|
|
||||||
colours: vec![
|
|
||||||
vec![0,0,0,0],
|
|
||||||
vec![0,81,104,255],
|
|
||||||
vec![118,159,155,255],
|
|
||||||
vec![155,155,155,255],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn default() -> Palette {
|
|
||||||
Palette {
|
|
||||||
name: "blueprint".to_string(),
|
|
||||||
colours: vec![
|
|
||||||
Colour { red: 0, green: 0, blue: 0, alpha: 0 },
|
|
||||||
Colour { red: 0, green: 81, blue: 104, alpha: 255 },
|
|
||||||
Colour { red: 118, green: 159, blue: 155, alpha: 255 },
|
|
||||||
Colour { red: 155, green: 155, blue: 155, alpha: 255 },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::{Game, Config, Palette, Colour, Image, IntermediatePalettes, IntermediateImage, IntermediateImages};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_from_toml() {
|
|
||||||
let output: Config = toml::from_str(include_str!("test-resources/basic/game.toml")).unwrap();
|
|
||||||
let expected = Config {
|
|
||||||
name: Some("Write your game's title here".to_string()),
|
|
||||||
width: 16,
|
|
||||||
height: 9,
|
|
||||||
tick: 400,
|
|
||||||
starting_room: Some("example room".to_string())
|
|
||||||
};
|
|
||||||
assert_eq!(output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_to_toml() {
|
|
||||||
let output = toml::to_string(&Config {
|
|
||||||
name: Some("Write your game's title here".to_string()),
|
|
||||||
width: 16,
|
|
||||||
height: 9,
|
|
||||||
tick: 400,
|
|
||||||
starting_room: Some("example room".to_string())
|
|
||||||
}).unwrap();
|
|
||||||
let expected = include_str!("test-resources/basic/game.toml");
|
|
||||||
assert_eq!(&output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_palette_from_toml() {
|
|
||||||
let output = Palette::from(
|
|
||||||
"blueprint",
|
|
||||||
include_str!("test-resources/basic/palettes/blueprint.toml")
|
|
||||||
);
|
|
||||||
let expected = crate::mock::palette::default();
|
|
||||||
assert_eq!(output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_palette_to_toml() {
|
|
||||||
let intermediate = crate::mock::palette::intermediate();
|
|
||||||
let output = toml::to_string(&intermediate).unwrap();
|
|
||||||
let expected = include_str!("test-resources/basic/palettes/blueprint.toml");
|
|
||||||
assert_eq!(&output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_image_from_toml() {
|
|
||||||
// let str = include_str!("test-resources/basic/images.toml");
|
|
||||||
// let output: Image = toml::from_str(str).unwrap();
|
|
||||||
// let expected = crate::mock::image::avatar();
|
|
||||||
// assert_eq!(output, expected);
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_colour_from_intermediate() {
|
|
||||||
let output = Colour::from(vec![64, 128, 192, 255]);
|
|
||||||
let expected = Colour { red: 64, green: 128, blue: 192, alpha: 255 };
|
|
||||||
assert_eq!(output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_colour_to_intermediate() {
|
|
||||||
let output = Colour { red: 64, green: 128, blue: 192, alpha: 255 }.to_vec();
|
|
||||||
let expected = vec![64, 128, 192, 255];
|
|
||||||
assert_eq!(output, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_images_from_intermediate() {
|
|
||||||
// let str = include_str!("test-resources/basic/images.toml");
|
|
||||||
// let output: Vec<IntermediateImage> = toml::from_str(str).unwrap();
|
|
||||||
// print!("{}", output.len());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_images_to_toml() {
|
|
||||||
// let images = IntermediateImages {
|
|
||||||
// image: vec![
|
|
||||||
// IntermediateImage::from(crate::mock::image::bg()),
|
|
||||||
// IntermediateImage::from(crate::mock::image::block()),
|
|
||||||
// IntermediateImage::from(crate::mock::image::avatar()),
|
|
||||||
// IntermediateImage::from(crate::mock::image::cat()),
|
|
||||||
// ]
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// let output = toml::to_string(&images).unwrap();
|
|
||||||
// let expected = include_str!("test-resources/basic/images.toml");
|
|
||||||
//
|
|
||||||
// // I think this is failing because one has escaped quotation marks and one has normal ones(??)
|
|
||||||
// assert_eq!(output, expected);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_images_from_toml() {
|
|
||||||
// let str = include_str!("test-resources/basic/images.toml");
|
|
||||||
// let output: IntermediateImages = toml::from_str(str).unwrap();
|
|
||||||
// let output = output.to_images();
|
|
||||||
// let expected = vec![
|
|
||||||
// crate::mock::image::bg(),
|
|
||||||
// crate::mock::image::block(),
|
|
||||||
// crate::mock::image::avatar(),
|
|
||||||
// crate::mock::image::cat(),
|
|
||||||
// ];
|
|
||||||
// assert_eq!(output, expected);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
pub(crate) mod image {
|
||||||
|
use crate::image::Image;
|
||||||
|
|
||||||
|
pub fn bg() -> Image {
|
||||||
|
Image {
|
||||||
|
name: "bg".to_string(),
|
||||||
|
pixels: vec![
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block() -> Image {
|
||||||
|
Image {
|
||||||
|
name: "block".to_string(),
|
||||||
|
pixels: vec![
|
||||||
|
1,1,1,1,1,1,1,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,0,0,1,1,0,0,1,
|
||||||
|
1,0,0,1,1,0,0,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,0,0,0,0,0,0,1,
|
||||||
|
1,1,1,1,1,1,1,1,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avatar() -> Image {
|
||||||
|
Image {
|
||||||
|
name: "avatar".to_string(),
|
||||||
|
pixels: vec![
|
||||||
|
0,0,0,2,2,0,0,0,
|
||||||
|
0,0,0,2,2,0,0,0,
|
||||||
|
0,0,0,2,2,0,0,0,
|
||||||
|
0,0,2,2,2,2,0,0,
|
||||||
|
0,2,2,2,2,2,2,0,
|
||||||
|
2,0,2,2,2,2,0,2,
|
||||||
|
0,0,2,0,0,2,0,0,
|
||||||
|
0,0,2,0,0,2,0,0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cat() -> Image {
|
||||||
|
Image {
|
||||||
|
name: "cat".to_string(),
|
||||||
|
pixels: vec![
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,
|
||||||
|
0,2,0,2,0,0,0,2,
|
||||||
|
0,2,2,2,0,0,0,2,
|
||||||
|
0,2,2,2,0,0,2,0,
|
||||||
|
0,2,2,2,2,2,0,0,
|
||||||
|
0,0,2,2,2,2,0,0,
|
||||||
|
0,0,2,0,0,2,0,0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod palette {
|
||||||
|
use crate::{Palette, Colour, IntermediatePalette};
|
||||||
|
|
||||||
|
pub(crate) fn intermediate() -> IntermediatePalette {
|
||||||
|
IntermediatePalette {
|
||||||
|
colours: vec![
|
||||||
|
vec![0,0,0,0],
|
||||||
|
vec![0,81,104,255],
|
||||||
|
vec![118,159,155,255],
|
||||||
|
vec![155,155,155,255],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default() -> Palette {
|
||||||
|
Palette {
|
||||||
|
name: "blueprint".to_string(),
|
||||||
|
colours: vec![
|
||||||
|
Colour { red: 0, green: 0, blue: 0, alpha: 0 },
|
||||||
|
Colour { red: 0, green: 81, blue: 104, alpha: 255 },
|
||||||
|
Colour { red: 118, green: 159, blue: 155, alpha: 255 },
|
||||||
|
Colour { red: 155, green: 155, blue: 155, alpha: 255 },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::colour::Colour;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Palette {
|
||||||
|
pub name: String,
|
||||||
|
pub colours: Vec<Colour>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Palette {
|
||||||
|
pub fn from(name: &str, toml: &str) -> Self {
|
||||||
|
let intermediate: IntermediatePalette = toml::from_str(toml).unwrap();
|
||||||
|
|
||||||
|
println!("palette name: {}", name);
|
||||||
|
|
||||||
|
for colour in &intermediate.colours {
|
||||||
|
println!("palette colour: {}{}{}", colour[0], colour[1], colour[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Palette {
|
||||||
|
name: name.to_string(),
|
||||||
|
colours: intermediate.colours.iter().map(|vec| {
|
||||||
|
Colour::from(vec.clone())
|
||||||
|
}).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_file(path: String) -> Self {
|
||||||
|
// todo get name without extension
|
||||||
|
let name = "blah";
|
||||||
|
let toml = fs::read_to_string(path).unwrap();
|
||||||
|
Self::from(name, &toml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// for toml purposes
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct IntermediatePalette {
|
||||||
|
pub colours: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// for toml purposes
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct IntermediatePalettes {
|
||||||
|
/// singular so each palette section is named "palette" instead of "palettes" in toml
|
||||||
|
palette: Vec<IntermediatePalette>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediatePalettes {
|
||||||
|
pub fn from_dir() -> Self {
|
||||||
|
Self {
|
||||||
|
palette: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::Palette;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_palette_from_toml() {
|
||||||
|
let output = Palette::from(
|
||||||
|
"blueprint",
|
||||||
|
include_str!("test-resources/basic/palettes/blueprint.toml")
|
||||||
|
);
|
||||||
|
let expected = crate::mock::palette::default();
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_palette_to_toml() {
|
||||||
|
let intermediate = crate::mock::palette::intermediate();
|
||||||
|
let output = toml::to_string(&intermediate).unwrap();
|
||||||
|
let expected = include_str!("test-resources/basic/palettes/blueprint.toml");
|
||||||
|
assert_eq!(&output, expected);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_derive::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::Position;
|
||||||
|
|
||||||
|
pub struct Room {
|
||||||
|
pub name: String,
|
||||||
|
pub width: u8,
|
||||||
|
pub height: u8,
|
||||||
|
/// thing names and their positions
|
||||||
|
pub background: HashMap<Position, String>,
|
||||||
|
pub foreground: HashMap<Position, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// todo &str?
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct IntermediateRoom {
|
||||||
|
name: String,
|
||||||
|
background: Vec<String>,
|
||||||
|
foreground: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntermediateRoom {
|
||||||
|
fn from(room: Room) -> IntermediateRoom {
|
||||||
|
fn hashmap_to_vec(hash: HashMap<Position, String>, width: u8, height: u8) -> Vec<String> {
|
||||||
|
let mut thing_ids = Vec::new();
|
||||||
|
|
||||||
|
while thing_ids.len() < (width * height) as usize {
|
||||||
|
thing_ids.push(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
thing_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
IntermediateRoom {
|
||||||
|
name: "".to_string(),
|
||||||
|
background: vec![],
|
||||||
|
foreground: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue