123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- use ggez::graphics::spritebatch::{SpriteBatch, SpriteIdx};
- use ggez::graphics::{DrawParam, Drawable, Image};
- use ggez::{Context, GameError};
- use specs::WorldExt;
- use std::path::Path;
- pub use winit::VirtualKeyCode;
- pub use ggez::input::keyboard::KeyMods;
- #[derive(Eq, PartialEq, Debug, Copy, Clone)]
- pub enum Color {
- Red,
- Green,
- Blue,
- }
- impl From<Color> 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 GameBoard<Idx: Tile> {
- size: Size,
- contents: Vec<(Idx, SpriteIdx)>,
- tileset: Tileset<Idx>,
- }
- pub struct Tileset<Idx: Tile> {
- tile_size: Size,
- batch: SpriteBatch,
- idx: std::marker::PhantomData<Idx>,
- }
- impl<Idx: Tile> Tileset<Idx> {
- pub fn from_file(
- ctx: &mut Context,
- tile_size: impl Into<Size>,
- file: impl AsRef<Path>,
- ) -> Result<Tileset<Idx>, 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<Coord>) -> [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<Idx: Tile> GameBoard<Idx> {
- pub fn new(size: impl Into<Size>, mut tileset: Tileset<Idx>) -> GameBoard<Idx> {
- 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));
- }
- }
- GameBoard {
- size,
- contents,
- tileset,
- }
- }
- 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<Coord>, 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<Coord>,
- ch: Idx,
- color: impl Into<ggez::graphics::Color>,
- ) {
- 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<Coord>) -> 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 GameBoard<CP437> {
- pub fn print(&mut self, at: impl Into<Coord>, msg: &str) {
- let Coord { x, y } = at.into();
- for (idx, ch) in msg.chars().enumerate() {
- self.set([idx + x, y], CP437::from_char(ch));
- }
- }
- }
- type Updater = Box<dyn FnMut(&mut specs::World)>;
- type Keycode = (winit::VirtualKeyCode, ggez::event::KeyMods);
- type EventMap = std::collections::HashMap<Keycode, Updater>;
- pub struct World<Idx> {
- pub world: specs::World,
- updater: Updater,
- events: EventMap,
- idx: std::marker::PhantomData<Idx>,
- }
- fn do_nothing() -> Updater {
- Box::new(|_| {})
- }
- impl<Idx: Tile + 'static> World<Idx> {
- pub fn new(board: GameBoard<Idx>) -> World<Idx> {
- let mut world = specs::World::new();
- world.insert(board);
- let updater = do_nothing();
- let events = std::collections::HashMap::new();
- World {
- world,
- updater,
- events,
- idx: std::marker::PhantomData,
- }
- }
- pub fn set(&mut self, at: impl Into<Coord>, ch: Idx) {
- self.world.fetch_mut::<GameBoard<Idx>>().set(at, ch)
- }
- pub fn set_with_color(
- &mut self,
- at: impl Into<Coord>,
- ch: Idx,
- color: impl Into<ggez::graphics::Color>,
- ) {
- self.world
- .fetch_mut::<GameBoard<Idx>>()
- .set_with_color(at, ch, color)
- }
- pub fn get(&self, at: impl Into<Coord>) -> Idx {
- self.world.fetch::<GameBoard<Idx>>().get(at)
- }
- pub fn on_update(&mut self, update: impl FnMut(&mut specs::World) + 'static) {
- self.updater = Box::new(update) as Updater;
- }
- pub fn on_keycode(&mut self, kc: Keycode, update: impl FnMut(&mut specs::World) + 'static) {
- self.events.insert(kc, Box::new(update) as Updater);
- }
- }
- impl World<CP437> {
- pub fn print(&mut self, at: impl Into<Coord>, msg: &str) {
- self.world.fetch_mut::<GameBoard<CP437>>().print(at, msg)
- }
- }
- impl<Idx: 'static + Tile> ggez::event::EventHandler for World<Idx> {
- fn draw(&mut self, ctx: &mut Context) -> Result<(), ggez::GameError> {
- ggez::graphics::clear(ctx, ggez::graphics::BLACK);
- self.world.fetch::<GameBoard<Idx>>().draw(ctx)?;
- ggez::graphics::present(ctx)
- }
- fn update(&mut self, _ctx: &mut Context) -> Result<(), ggez::GameError> {
- let mut updater = do_nothing();
- std::mem::swap(&mut updater, &mut self.updater);
- updater(&mut self.world);
- std::mem::swap(&mut updater, &mut self.updater);
- Ok(())
- }
- fn key_down_event(
- &mut self,
- ctx: &mut Context,
- keycode: winit::VirtualKeyCode,
- keymod: ggez::event::KeyMods,
- _repeat: bool,
- ) {
- if keycode == VirtualKeyCode::Escape {
- ggez::event::quit(ctx);
- }
- if let Some(cb) = self.events.get_mut(&(keycode, keymod)) {
- cb(&mut self.world);
- }
- }
- }
- //
- pub struct Game<Idx> {
- pub ctx: ggez::Context,
- pub evloop: ggez::event::EventsLoop,
- pub world: World<Idx>,
- }
- impl<Idx: Tile + 'static> Game<Idx> {
- pub fn create(
- board: GameBoard<Idx>,
- ctx: ggez::Context,
- evloop: ggez::event::EventsLoop,
- ) -> Result<Game<Idx>, ggez::GameError> {
- let world = World::new(board);
- Ok(Game { world, ctx, evloop })
- }
- pub fn register<C>(&mut self)
- where
- C: specs::Component,
- <C as specs::Component>::Storage: std::default::Default,
- {
- self.world.world.register::<C>();
- }
- pub fn insert(&mut self, r: impl specs::prelude::Resource) {
- self.world.world.insert(r);
- }
- pub fn create_entity(&mut self) -> specs::world::EntityBuilder {
- self.world.world.create_entity()
- }
- pub fn on_key(&mut self, kc: Keycode, update: impl FnMut(&mut specs::World) + 'static) {
- self.world.on_keycode(kc, update);
- }
- pub fn run(self) -> ggez::GameResult<()> {
- let Game {
- mut world,
- mut ctx,
- mut evloop,
- } = self;
- ggez::event::run(&mut ctx, &mut evloop, &mut world)
- }
- pub fn run_with_systems(
- self,
- update: impl FnMut(&mut specs::World) + 'static,
- ) -> ggez::GameResult<()> {
- let Game {
- mut world,
- mut ctx,
- mut evloop,
- } = self;
- world.on_update(update);
- ggez::event::run(&mut ctx, &mut evloop, &mut world)
- }
- }
- // game builder
- use std::path::PathBuf;
- pub struct GameBuilder<Idx: Tile> {
- name: String,
- author: String,
- resource_path: Option<PathBuf>,
- tile_size: Option<[usize; 2]>,
- tile_path: Option<PathBuf>,
- map_size: Option<[usize; 2]>,
- idx: std::marker::PhantomData<Idx>,
- }
- impl<Idx: Tile + 'static> GameBuilder<Idx> {
- pub fn new() -> GameBuilder<Idx> {
- GameBuilder {
- name: "Carpet Game".to_owned(),
- author: "unknown author".to_owned(),
- resource_path: None,
- tile_size: None,
- tile_path: None,
- map_size: None,
- idx: std::marker::PhantomData,
- }
- }
- pub fn name(mut self, name: impl Into<String>) -> GameBuilder<Idx> {
- self.name = name.into();
- self
- }
- pub fn author(mut self, author: impl Into<String>) -> GameBuilder<Idx> {
- self.author = author.into();
- self
- }
- pub fn resource_path(mut self, path: impl AsRef<Path>) -> GameBuilder<Idx> {
- self.resource_path = Some(path.as_ref().to_path_buf());
- self
- }
- pub fn tileset(
- mut self,
- image: impl AsRef<Path>,
- size: impl Into<[usize; 2]>,
- ) -> GameBuilder<Idx> {
- self.tile_path = Some(image.as_ref().to_path_buf());
- self.tile_size = Some(size.into());
- self
- }
- pub fn map_size(mut self, width: usize, height: usize) -> GameBuilder<Idx> {
- self.map_size = Some([width, height]);
- self
- }
- pub fn build(self) -> ggez::GameResult<Game<Idx>> {
- use ggez::error::GameError::ResourceLoadError;
- let resource_path = self
- .resource_path
- .ok_or_else(|| ResourceLoadError("No resource path specified".to_owned()))?;
- let tile_path = self
- .tile_path
- .ok_or_else(|| ResourceLoadError("No tile path specified".to_owned()))?;
- let tile_size = self
- .tile_size
- .ok_or_else(|| ResourceLoadError("No tile size specified".to_owned()))?;
- let map_size = self
- .map_size
- .ok_or_else(|| ResourceLoadError("No map size specified".to_owned()))?;
- let (mut ctx, evloop) = ggez::ContextBuilder::new(&self.name, &self.author)
- .add_resource_path(resource_path)
- .window_mode(ggez::conf::WindowMode {
- width: (tile_size[0] * map_size[0]) as f32,
- height: (tile_size[1] * map_size[1]) as f32,
- ..ggez::conf::WindowMode::default()
- })
- .build()?;
- let tileset = Tileset::from_file(&mut ctx, tile_size, tile_path)?;
- let board = GameBoard::new(map_size, tileset);
- Game::create(board, ctx, evloop)
- }
- }
- pub struct Board<T> {
- width: usize,
- height: usize,
- storage: Vec<T>,
- }
- impl<T> Board<T> {
- pub fn new_from(width: usize, height: usize, func: impl Fn(usize, usize) -> T) -> Board<T> {
- let mut storage = Vec::with_capacity(width * height);
- for y in 0..height {
- for x in 0..width {
- storage.push(func(x, y))
- }
- }
- Board { width, height, storage }
- }
- pub fn get(&self, x: usize, y: usize) -> Option<&T> {
- let idx = x + self.width * y;
- self.storage.get(idx)
- }
- pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut T> {
- let idx = x + self.width * y;
- self.storage.get_mut(idx)
- }
- pub fn set(&mut self, x: usize, y: usize, val: T) {
- let idx = x + self.width * y;
- self.storage[idx] = val;
- }
- pub fn width(&self) -> usize {
- self.width
- }
- pub fn height(&self) -> usize {
- self.height
- }
- pub fn contains(&self, pos: impl Into<Coord>) -> bool {
- let pos = pos.into();
- pos.x < self.width && pos.y < self.height
- }
- }
- impl<T> std::ops::Index<Coord> for Board<T> {
- type Output = T;
- fn index(&self, pos: Coord) -> &Self::Output {
- self.get(pos.x, pos.y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", pos))
- }
- }
- impl<T> std::ops::Index<(usize, usize)> for Board<T> {
- type Output = T;
- fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
- self.get(x, y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", (x, y)))
- }
- }
- impl<T> std::ops::Index<[usize;2]> for Board<T> {
- type Output = T;
- fn index(&self, [x, y]: [usize;2]) -> &Self::Output {
- self.get(x, y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", (x, y)))
- }
- }
- impl<T> std::ops::IndexMut<Coord> for Board<T> {
- fn index_mut(&mut self, pos: Coord) -> &mut Self::Output {
- self.get_mut(pos.x, pos.y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", pos))
- }
- }
- impl<T> std::ops::IndexMut<(usize, usize)> for Board<T> {
- fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
- self.get_mut(x, y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", (x, y)))
- }
- }
- impl<T> std::ops::IndexMut<[usize;2]> for Board<T> {
- fn index_mut(&mut self, [x, y]: [usize;2]) -> &mut Self::Output {
- self.get_mut(x, y).unwrap_or_else(|| panic!("Coordinate {:?} out of range", (x, y)))
- }
- }
|