lib.rs 12 KB

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