lib.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. use ggez::graphics::spritebatch::{SpriteBatch, SpriteIdx};
  2. use ggez::graphics::{DrawParam, Drawable, Image};
  3. use ggez::{Context, GameError};
  4. use specs::WorldExt;
  5. use std::path::Path;
  6. pub use ggez::input::keyboard::{KeyCode, KeyMods};
  7. mod board;
  8. mod types;
  9. pub use board::{Board, BoardIter};
  10. pub use types::{Coord, Rect, Size};
  11. #[derive(Eq, PartialEq, Debug, Copy, Clone)]
  12. pub enum Color {
  13. Red,
  14. Green,
  15. Blue,
  16. }
  17. impl From<Color> for ggez::graphics::Color {
  18. fn from(color: Color) -> ggez::graphics::Color {
  19. match color {
  20. Color::Red => [1.0, 0.0, 0.0, 1.0].into(),
  21. Color::Green => [0.0, 1.0, 0.0, 1.0].into(),
  22. Color::Blue => [0.0, 0.0, 1.0, 1.0].into(),
  23. }
  24. }
  25. }
  26. /// The `Tile` trait is used to identify which tile we care about on a
  27. /// tilesheet, and also to provide a sensible 'blank' tile. The tile
  28. /// identifiers we care about should at least be `Clone`, but often
  29. /// they'll be (wrappers over) small identifiers (like a `u8` or a
  30. /// `u16`) and could probably be `Copy`.
  31. pub trait Tile: Clone + Send + Sync {
  32. fn to_location(self) -> [f32; 4];
  33. fn blank() -> Self;
  34. }
  35. /// This represents a character from DOS codepage 437
  36. #[derive(Debug, Copy, Clone)]
  37. pub struct CP437(u8);
  38. impl CP437 {
  39. pub fn from_u8(ch: u8) -> CP437 {
  40. CP437(ch)
  41. }
  42. pub fn from_char(ch: char) -> CP437 {
  43. CP437(match ch as u32 {
  44. // happy faces
  45. 0x263A => 1,
  46. 0x263B => 2,
  47. // card suits
  48. 0x2665 => 3,
  49. 0x2666 => 4,
  50. 0x2663 => 5,
  51. 0x2660 => 6,
  52. 0x2022 => 7,
  53. // fill this in some time later
  54. // standard ASCII mappings
  55. 0x20..=0x7e => ch as u8,
  56. 0x2302 => 0x7f,
  57. _ => panic!("Character {} does not have a CP437 equivalent", ch),
  58. })
  59. }
  60. }
  61. impl Tile for CP437 {
  62. fn to_location(self) -> [f32; 4] {
  63. const TILE_SIZE: f32 = 1.0 / 16.0;
  64. let u = f32::from(self.0 % 16) * TILE_SIZE;
  65. let v = f32::from(self.0 / 16) * TILE_SIZE;
  66. [u, v, TILE_SIZE, TILE_SIZE]
  67. }
  68. fn blank() -> Self {
  69. CP437(0)
  70. }
  71. }
  72. pub struct GameBoard<Idx: Tile> {
  73. size: Size,
  74. contents: Vec<(Idx, SpriteIdx)>,
  75. tileset: Tileset<Idx>,
  76. }
  77. pub struct Tileset<Idx: Tile> {
  78. tile_size: Size,
  79. batch: SpriteBatch,
  80. idx: std::marker::PhantomData<Idx>,
  81. }
  82. impl<Idx: Tile> Tileset<Idx> {
  83. pub fn from_file(
  84. ctx: &mut Context,
  85. tile_size: impl Into<Size>,
  86. file: impl AsRef<Path>,
  87. ) -> Result<Tileset<Idx>, GameError> {
  88. let tile_size = tile_size.into();
  89. let image = Image::new(ctx, file)?;
  90. let mut batch = SpriteBatch::new(image);
  91. batch.set_filter(ggez::graphics::FilterMode::Nearest);
  92. Ok(Tileset {
  93. tile_size,
  94. batch,
  95. idx: std::marker::PhantomData,
  96. })
  97. }
  98. fn to_screen(&self, coord: impl Into<Coord>) -> [f32; 2] {
  99. let Coord { x, y } = coord.into();
  100. [
  101. (x * self.tile_size.width) as f32,
  102. (y * self.tile_size.height) as f32,
  103. ]
  104. }
  105. }
  106. impl<Idx: Tile> GameBoard<Idx> {
  107. pub fn new(size: impl Into<Size>, mut tileset: Tileset<Idx>) -> GameBoard<Idx> {
  108. let size = size.into();
  109. let mut contents = Vec::new();
  110. for y in 0..size.height {
  111. for x in 0..size.width {
  112. let param = DrawParam::new()
  113. .src(Idx::blank().to_location().into())
  114. .dest(tileset.to_screen([x, y]));
  115. let idx = tileset.batch.add(param);
  116. contents.push((Idx::blank(), idx));
  117. }
  118. }
  119. GameBoard {
  120. size,
  121. contents,
  122. tileset,
  123. }
  124. }
  125. pub fn draw(&self, ctx: &mut Context) -> Result<(), ggez::GameError> {
  126. self.tileset.batch.draw(ctx, DrawParam::new())
  127. }
  128. pub fn set(&mut self, at: impl Into<Coord>, ch: Idx) {
  129. let at = at.into();
  130. let idx = at.x + at.y * self.size.width;
  131. let param = DrawParam::new()
  132. .src(ch.to_location().into())
  133. .dest(self.tileset.to_screen(at));
  134. self.tileset.batch.set(self.contents[idx].1, param).unwrap();
  135. }
  136. pub fn set_with_color(
  137. &mut self,
  138. at: impl Into<Coord>,
  139. ch: Idx,
  140. color: impl Into<ggez::graphics::Color>,
  141. ) {
  142. let at = at.into();
  143. let idx = at.x + at.y * self.size.width;
  144. let param = DrawParam::new()
  145. .src(ch.to_location().into())
  146. .dest(self.tileset.to_screen(at))
  147. .color(color.into());
  148. self.tileset.batch.set(self.contents[idx].1, param).unwrap();
  149. }
  150. pub fn get(&self, at: impl Into<Coord>) -> Idx {
  151. let at = at.into();
  152. let idx = at.x + at.y * self.size.width;
  153. self.contents[idx].0.clone()
  154. }
  155. pub fn clear(&mut self) {
  156. for (n, contents) in self.contents.iter_mut().enumerate() {
  157. let x = n % self.size.width;
  158. let y = n / self.size.width;
  159. let param = DrawParam::new()
  160. .src(Idx::blank().to_location().into())
  161. .dest(self.tileset.to_screen([x, y]));
  162. contents.0 = Idx::blank();
  163. self.tileset.batch.set(contents.1, param).unwrap();
  164. }
  165. }
  166. }
  167. impl GameBoard<CP437> {
  168. pub fn print(&mut self, at: impl Into<Coord>, msg: &str) {
  169. let Coord { x, y } = at.into();
  170. for (idx, ch) in msg.chars().enumerate() {
  171. self.set([idx + x, y], CP437::from_char(ch));
  172. }
  173. }
  174. }
  175. type Updater = Box<dyn FnMut(&mut specs::World)>;
  176. type Key = (ggez::input::keyboard::KeyCode, ggez::event::KeyMods);
  177. type EventMap = std::collections::HashMap<Key, Updater>;
  178. pub struct World<Idx> {
  179. pub world: specs::World,
  180. updater: Updater,
  181. events: EventMap,
  182. idx: std::marker::PhantomData<Idx>,
  183. }
  184. fn do_nothing() -> Updater {
  185. Box::new(|_| {})
  186. }
  187. impl<Idx: Tile + 'static> World<Idx> {
  188. pub fn new(board: GameBoard<Idx>) -> World<Idx> {
  189. let mut world = specs::World::new();
  190. world.insert(board);
  191. let updater = do_nothing();
  192. let events = std::collections::HashMap::new();
  193. World {
  194. world,
  195. updater,
  196. events,
  197. idx: std::marker::PhantomData,
  198. }
  199. }
  200. pub fn set(&mut self, at: impl Into<Coord>, ch: Idx) {
  201. self.world.fetch_mut::<GameBoard<Idx>>().set(at, ch)
  202. }
  203. pub fn set_with_color(
  204. &mut self,
  205. at: impl Into<Coord>,
  206. ch: Idx,
  207. color: impl Into<ggez::graphics::Color>,
  208. ) {
  209. self.world
  210. .fetch_mut::<GameBoard<Idx>>()
  211. .set_with_color(at, ch, color)
  212. }
  213. pub fn get(&self, at: impl Into<Coord>) -> Idx {
  214. self.world.fetch::<GameBoard<Idx>>().get(at)
  215. }
  216. pub fn on_update(&mut self, update: impl FnMut(&mut specs::World) + 'static) {
  217. self.updater = Box::new(update) as Updater;
  218. }
  219. pub fn on_keycode(&mut self, kc: Key, update: impl FnMut(&mut specs::World) + 'static) {
  220. self.events.insert(kc, Box::new(update) as Updater);
  221. }
  222. }
  223. impl World<CP437> {
  224. pub fn print(&mut self, at: impl Into<Coord>, msg: &str) {
  225. self.world.fetch_mut::<GameBoard<CP437>>().print(at, msg)
  226. }
  227. }
  228. impl<Idx: 'static + Tile> ggez::event::EventHandler for World<Idx> {
  229. fn draw(&mut self, ctx: &mut Context) -> Result<(), ggez::GameError> {
  230. ggez::graphics::clear(ctx, ggez::graphics::Color::BLACK);
  231. self.world.fetch::<GameBoard<Idx>>().draw(ctx)?;
  232. ggez::graphics::present(ctx)
  233. }
  234. fn update(&mut self, _ctx: &mut Context) -> Result<(), ggez::GameError> {
  235. let mut updater = do_nothing();
  236. std::mem::swap(&mut updater, &mut self.updater);
  237. updater(&mut self.world);
  238. std::mem::swap(&mut updater, &mut self.updater);
  239. Ok(())
  240. }
  241. fn key_down_event(
  242. &mut self,
  243. ctx: &mut Context,
  244. keycode: KeyCode,
  245. keymod: KeyMods,
  246. _repeat: bool,
  247. ) {
  248. if keycode == KeyCode::Escape {
  249. ggez::event::quit(ctx);
  250. }
  251. if let Some(cb) = self.events.get_mut(&(keycode, keymod)) {
  252. cb(&mut self.world);
  253. }
  254. }
  255. }
  256. //
  257. pub struct Game<Idx> {
  258. pub ctx: ggez::Context,
  259. pub evloop: ggez::event::EventLoop<()>,
  260. pub world: World<Idx>,
  261. }
  262. impl<Idx: Tile + 'static> Game<Idx> {
  263. pub fn create(
  264. board: GameBoard<Idx>,
  265. ctx: ggez::Context,
  266. evloop: ggez::event::EventLoop<()>,
  267. ) -> Result<Game<Idx>, ggez::GameError> {
  268. let world = World::new(board);
  269. Ok(Game { world, ctx, evloop })
  270. }
  271. pub fn register<C>(&mut self)
  272. where
  273. C: specs::Component,
  274. <C as specs::Component>::Storage: std::default::Default,
  275. {
  276. self.world.world.register::<C>();
  277. }
  278. pub fn insert(&mut self, r: impl specs::prelude::Resource) {
  279. self.world.world.insert(r);
  280. }
  281. pub fn create_entity(&mut self) -> specs::world::EntityBuilder {
  282. self.world.world.create_entity()
  283. }
  284. pub fn on_key(&mut self, kc: Key, update: impl FnMut(&mut specs::World) + 'static) {
  285. self.world.on_keycode(kc, update);
  286. }
  287. pub fn run(self) -> ggez::GameResult<()> {
  288. let Game { world, ctx, evloop } = self;
  289. ggez::event::run(ctx, evloop, world)
  290. }
  291. pub fn run_with_systems(
  292. self,
  293. update: impl FnMut(&mut specs::World) + 'static,
  294. ) -> ggez::GameResult<()> {
  295. let Game {
  296. mut world,
  297. ctx,
  298. evloop,
  299. } = self;
  300. world.on_update(update);
  301. ggez::event::run(ctx, evloop, world)
  302. }
  303. }
  304. // game builder
  305. use std::path::PathBuf;
  306. pub struct GameBuilder<Idx: Tile> {
  307. name: String,
  308. author: String,
  309. resource_path: Option<PathBuf>,
  310. tile_size: Option<[usize; 2]>,
  311. tile_path: Option<PathBuf>,
  312. map_size: Option<[usize; 2]>,
  313. idx: std::marker::PhantomData<Idx>,
  314. }
  315. impl<Idx: Tile + 'static> GameBuilder<Idx> {
  316. pub fn new() -> GameBuilder<Idx> {
  317. GameBuilder {
  318. name: "Carpet Game".to_owned(),
  319. author: "unknown author".to_owned(),
  320. resource_path: None,
  321. tile_size: None,
  322. tile_path: None,
  323. map_size: None,
  324. idx: std::marker::PhantomData,
  325. }
  326. }
  327. pub fn name(mut self, name: impl Into<String>) -> GameBuilder<Idx> {
  328. self.name = name.into();
  329. self
  330. }
  331. pub fn author(mut self, author: impl Into<String>) -> GameBuilder<Idx> {
  332. self.author = author.into();
  333. self
  334. }
  335. pub fn resource_path(mut self, path: impl AsRef<Path>) -> GameBuilder<Idx> {
  336. self.resource_path = Some(path.as_ref().to_path_buf());
  337. self
  338. }
  339. pub fn tileset(
  340. mut self,
  341. image: impl AsRef<Path>,
  342. size: impl Into<[usize; 2]>,
  343. ) -> GameBuilder<Idx> {
  344. self.tile_path = Some(image.as_ref().to_path_buf());
  345. self.tile_size = Some(size.into());
  346. self
  347. }
  348. pub fn map_size(mut self, width: usize, height: usize) -> GameBuilder<Idx> {
  349. self.map_size = Some([width, height]);
  350. self
  351. }
  352. pub fn build(self) -> ggez::GameResult<Game<Idx>> {
  353. use ggez::error::GameError::ResourceLoadError;
  354. let resource_path = self
  355. .resource_path
  356. .ok_or_else(|| ResourceLoadError("No resource path specified".to_owned()))?;
  357. let tile_path = self
  358. .tile_path
  359. .ok_or_else(|| ResourceLoadError("No tile path specified".to_owned()))?;
  360. let tile_size = self
  361. .tile_size
  362. .ok_or_else(|| ResourceLoadError("No tile size specified".to_owned()))?;
  363. let map_size = self
  364. .map_size
  365. .ok_or_else(|| ResourceLoadError("No map size specified".to_owned()))?;
  366. let (mut ctx, evloop) = ggez::ContextBuilder::new(&self.name, &self.author)
  367. .add_resource_path(resource_path)
  368. .window_mode(ggez::conf::WindowMode {
  369. width: (tile_size[0] * map_size[0]) as f32,
  370. height: (tile_size[1] * map_size[1]) as f32,
  371. ..ggez::conf::WindowMode::default()
  372. })
  373. .build()?;
  374. let tileset = Tileset::from_file(&mut ctx, tile_size, tile_path)?;
  375. let board = GameBoard::new(map_size, tileset);
  376. Game::create(board, ctx, evloop)
  377. }
  378. }