lib.rs 12 KB

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