lib.rs 12 KB

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