use ggez::graphics::spritebatch::{SpriteBatch, SpriteIdx}; use ggez::graphics::{DrawParam, Drawable, Image}; use ggez::{Context, GameError}; use specs::WorldExt; use std::path::Path; #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum Color { Red, Green, Blue, } impl From for ggez::graphics::Color { fn from(color: Color) -> ggez::graphics::Color { match color { Color::Red => [1.0, 0.0, 0.0, 1.0].into(), Color::Green => [0.0, 1.0, 0.0, 1.0].into(), Color::Blue => [0.0, 0.0, 1.0, 1.0].into(), } } } /// The `Tile` trait is used to identify which tile we care about on a /// tilesheet, and also to provide a sensible 'blank' tile. The tile /// identifiers we care about should at least be `Clone`, but often /// they'll be (wrappers over) small identifiers (like a `u8` or a /// `u16`) and could probably be `Copy`. pub trait Tile: Clone + Send + Sync { fn to_location(self) -> [f32;4]; fn blank() -> Self; } /// This represents a character from DOS codepage 437 #[derive(Debug, Copy, Clone)] pub struct CP437(u8); impl CP437 { pub fn from_u8(ch: u8) -> CP437 { CP437(ch) } pub fn from_char(ch: char) -> CP437 { CP437(match ch as u32 { // happy faces 0x263A => 1, 0x263B => 2, // card suits 0x2665 => 3, 0x2666 => 4, 0x2663 => 5, 0x2660 => 6, 0x2022 => 7, // fill this in some time later // standard ASCII mappings 0x20 ..= 0x7e => ch as u8, 0x2302 => 0x7f, _ => panic!("Character {} does not have a CP437 equivalent", ch), }) } } impl Tile for CP437 { fn to_location(self) -> [f32;4] { const TILE_SIZE: f32 = 1.0 / 16.0; let u = f32::from(self.0 % 16) * TILE_SIZE; let v = f32::from(self.0 / 16) * TILE_SIZE; [u, v, TILE_SIZE, TILE_SIZE] } fn blank() -> Self { CP437(0) } } pub struct Board { size: Size, contents: Vec<(Idx, SpriteIdx)>, tileset: Tileset, } pub struct Tileset { tile_size: Size, batch: SpriteBatch, idx: std::marker::PhantomData, } impl Tileset { pub fn from_file( ctx: &mut Context, tile_size: impl Into, file: impl AsRef, ) -> Result, GameError> { let tile_size = tile_size.into(); let image = Image::new(ctx, file)?; let mut batch = SpriteBatch::new(image); batch.set_filter(ggez::graphics::FilterMode::Nearest); Ok(Tileset { tile_size, batch, idx: std::marker::PhantomData, }) } fn to_screen(&self, coord: impl Into) -> [f32; 2] { let Coord { x, y } = coord.into(); [ (x * self.tile_size.width) as f32, (y * self.tile_size.height) as f32, ] } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct Size { width: usize, height: usize, } impl From<[usize; 2]> for Size { fn from([width, height]: [usize; 2]) -> Size { Size { width, height } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct Coord { x: usize, y: usize, } impl From<[usize; 2]> for Coord { fn from([x, y]: [usize; 2]) -> Coord { Coord { x, y } } } impl Board { pub fn new(size: impl Into, mut tileset: Tileset) -> Board { let size = size.into(); let mut contents = Vec::new(); for y in 0..size.height { for x in 0..size.width { let param = DrawParam::new() .src(Idx::blank().to_location().into()) .dest(tileset.to_screen([x, y])); let idx = tileset.batch.add(param); contents.push((Idx::blank(), idx)); } } Board { size, contents, tileset, } } // fn sprite_location(ch: u8) -> [f32; 4] { // let u = f32::from(ch % 16) * TILE_SIZE; // let v = f32::from(ch / 16) * TILE_SIZE; // [u, v, TILE_SIZE, TILE_SIZE] // } pub fn draw(&self, ctx: &mut Context) -> Result<(), ggez::GameError> { self.tileset.batch.draw(ctx, DrawParam::new()) } pub fn set(&mut self, at: impl Into, ch: Idx) { let at = at.into(); let idx = at.x + at.y * self.size.width; let param = DrawParam::new() .src(ch.to_location().into()) .dest(self.tileset.to_screen(at)); self.tileset.batch.set(self.contents[idx].1, param).unwrap(); } pub fn set_with_color( &mut self, at: impl Into, ch: Idx, color: impl Into, ) { let at = at.into(); let idx = at.x + at.y * self.size.width; let param = DrawParam::new() .src(ch.to_location().into()) .dest(self.tileset.to_screen(at)) .color(color.into()); self.tileset.batch.set(self.contents[idx].1, param).unwrap(); } pub fn get(&self, at: impl Into) -> Idx { let at = at.into(); let idx = at.x + at.y * self.size.width; self.contents[idx].0.clone() } pub fn clear(&mut self) { for (n, contents) in self.contents.iter_mut().enumerate() { let x = n % self.size.width; let y = n / self.size.width; let param = DrawParam::new() .src(Idx::blank().to_location().into()) .dest(self.tileset.to_screen([x, y])); contents.0 = Idx::blank(); self.tileset.batch.set(contents.1, param).unwrap(); } } } impl Board { pub fn print(&mut self, at: impl Into, msg: &str) { let Coord { x, y } = at.into(); for (idx, ch) in msg.chars().enumerate() { self.set([idx + x, y], CP437::from_char(ch)); } } } pub struct Game { pub world: specs::World, idx: std::marker::PhantomData, } impl Game { pub fn create(_name: &str, _author: &str, board: Board) -> Result, ggez::GameError> { let mut world = specs::World::new(); world.insert(board); Ok(Game { world, idx: std::marker::PhantomData, }) } pub fn set(&mut self, at: impl Into, ch: Idx) { self.world.fetch_mut::>().set(at, ch) } pub fn set_with_color( &mut self, at: impl Into, ch: Idx, color: impl Into, ) { self.world .fetch_mut::>() .set_with_color(at, ch, color) } pub fn get(&self, at: impl Into) -> Idx { self.world.fetch::>().get(at) } } impl Game { pub fn print(&mut self, at: impl Into, msg: &str) { self.world.fetch_mut::>().print(at, msg) } } impl ggez::event::EventHandler for Game { fn draw(&mut self, ctx: &mut Context) -> Result<(), ggez::GameError> { ggez::graphics::clear(ctx, ggez::graphics::BLACK); self.world.fetch::>().draw(ctx)?; ggez::graphics::present(ctx) } fn update(&mut self, _ctx: &mut Context) -> Result<(), ggez::GameError> { Ok(()) } }