use ggez::graphics::spritebatch::{SpriteBatch, SpriteIdx}; use ggez::graphics::{DrawParam, Drawable, Image}; use ggez::{Context, GameError}; 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(), } } } pub struct Board { size: Size, contents: Vec<(Idx, SpriteIdx)>, tileset: Tileset, } pub trait Tile: Copy { fn to_location(self) -> [f32;4]; fn blank() -> Self; } impl Tile for u8 { fn to_location(self) -> [f32;4] { const TILE_SIZE: f32 = 1.0 / 16.0; let u = f32::from(self % 16) * TILE_SIZE; let v = f32::from(self / 16) * TILE_SIZE; [u, v, TILE_SIZE, TILE_SIZE] } fn blank() -> Self { 0 } } 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(&mut self, at: impl Into) -> Idx { let at = at.into(); let idx = at.x + at.y * self.size.width; self.contents[idx].0 } 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], ch as u8); } } }