lib.rs 15 KB

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