lib.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. use ggez::graphics::spritebatch::{SpriteBatch, SpriteIdx};
  2. use ggez::graphics::{DrawParam, Drawable, Image};
  3. use ggez::{Context, GameError};
  4. use std::path::Path;
  5. #[derive(Eq, PartialEq, Debug, Copy, Clone)]
  6. pub enum Color {
  7. Red,
  8. Green,
  9. Blue,
  10. }
  11. impl From<Color> for ggez::graphics::Color {
  12. fn from(color: Color) -> ggez::graphics::Color {
  13. match color {
  14. Color::Red => [1.0, 0.0, 0.0, 1.0].into(),
  15. Color::Green => [0.0, 1.0, 0.0, 1.0].into(),
  16. Color::Blue => [0.0, 0.0, 1.0, 1.0].into(),
  17. }
  18. }
  19. }
  20. /// The `Tile` trait is used to identify which tile we care about on a
  21. /// tilesheet, and also to provide a sensible 'blank' tile. The tile
  22. /// identifiers we care about should at least be `Clone`, but often
  23. /// they'll be (wrappers over) small identifiers (like a `u8` or a
  24. /// `u16`) and could probably be `Copy`.
  25. pub trait Tile: Clone {
  26. fn to_location(self) -> [f32;4];
  27. fn blank() -> Self;
  28. }
  29. /// This represents a character from DOS codepage 437
  30. #[derive(Debug, Copy, Clone)]
  31. pub struct CP437(u8);
  32. impl CP437 {
  33. pub fn from_u8(ch: u8) -> CP437 {
  34. CP437(ch)
  35. }
  36. pub fn from_char(ch: char) -> CP437 {
  37. CP437(match ch as u32 {
  38. // happy faces
  39. 0x263A => 1,
  40. 0x263B => 2,
  41. // card suits
  42. 0x2665 => 3,
  43. 0x2666 => 4,
  44. 0x2663 => 5,
  45. 0x2660 => 6,
  46. 0x2022 => 7,
  47. // fill this in some time later
  48. // standard ASCII mappings
  49. 0x20 ..= 0x7e => ch as u8,
  50. 0x2302 => 0x7f,
  51. _ => panic!("Character {} does not have a CP437 equivalent", ch),
  52. })
  53. }
  54. }
  55. impl Tile for CP437 {
  56. fn to_location(self) -> [f32;4] {
  57. const TILE_SIZE: f32 = 1.0 / 16.0;
  58. let u = f32::from(self.0 % 16) * TILE_SIZE;
  59. let v = f32::from(self.0 / 16) * TILE_SIZE;
  60. [u, v, TILE_SIZE, TILE_SIZE]
  61. }
  62. fn blank() -> Self {
  63. CP437(0)
  64. }
  65. }
  66. pub struct Board<Idx: Tile> {
  67. size: Size,
  68. contents: Vec<(Idx, SpriteIdx)>,
  69. tileset: Tileset<Idx>,
  70. }
  71. pub struct Tileset<Idx: Tile> {
  72. tile_size: Size,
  73. batch: SpriteBatch,
  74. idx: std::marker::PhantomData<Idx>,
  75. }
  76. impl<Idx: Tile> Tileset<Idx> {
  77. pub fn from_file(
  78. ctx: &mut Context,
  79. tile_size: impl Into<Size>,
  80. file: impl AsRef<Path>,
  81. ) -> Result<Tileset<Idx>, GameError> {
  82. let tile_size = tile_size.into();
  83. let image = Image::new(ctx, file)?;
  84. let mut batch = SpriteBatch::new(image);
  85. batch.set_filter(ggez::graphics::FilterMode::Nearest);
  86. Ok(Tileset {
  87. tile_size,
  88. batch,
  89. idx: std::marker::PhantomData,
  90. })
  91. }
  92. fn to_screen(&self, coord: impl Into<Coord>) -> [f32; 2] {
  93. let Coord { x, y } = coord.into();
  94. [
  95. (x * self.tile_size.width) as f32,
  96. (y * self.tile_size.height) as f32,
  97. ]
  98. }
  99. }
  100. #[derive(Debug, PartialEq, Eq, Hash)]
  101. pub struct Size {
  102. width: usize,
  103. height: usize,
  104. }
  105. impl From<[usize; 2]> for Size {
  106. fn from([width, height]: [usize; 2]) -> Size {
  107. Size { width, height }
  108. }
  109. }
  110. #[derive(Debug, PartialEq, Eq, Hash)]
  111. pub struct Coord {
  112. x: usize,
  113. y: usize,
  114. }
  115. impl From<[usize; 2]> for Coord {
  116. fn from([x, y]: [usize; 2]) -> Coord {
  117. Coord { x, y }
  118. }
  119. }
  120. impl<Idx: Tile> Board<Idx> {
  121. pub fn new(size: impl Into<Size>, mut tileset: Tileset<Idx>) -> Board<Idx> {
  122. let size = size.into();
  123. let mut contents = Vec::new();
  124. for y in 0..size.height {
  125. for x in 0..size.width {
  126. let param = DrawParam::new()
  127. .src(Idx::blank().to_location().into())
  128. .dest(tileset.to_screen([x, y]));
  129. let idx = tileset.batch.add(param);
  130. contents.push((Idx::blank(), idx));
  131. }
  132. }
  133. Board {
  134. size,
  135. contents,
  136. tileset,
  137. }
  138. }
  139. // fn sprite_location(ch: u8) -> [f32; 4] {
  140. // let u = f32::from(ch % 16) * TILE_SIZE;
  141. // let v = f32::from(ch / 16) * TILE_SIZE;
  142. // [u, v, TILE_SIZE, TILE_SIZE]
  143. // }
  144. pub fn draw(&self, ctx: &mut Context) -> Result<(), ggez::GameError> {
  145. self.tileset.batch.draw(ctx, DrawParam::new())
  146. }
  147. pub fn set(&mut self, at: impl Into<Coord>, ch: Idx) {
  148. let at = at.into();
  149. let idx = at.x + at.y * self.size.width;
  150. let param = DrawParam::new()
  151. .src(ch.to_location().into())
  152. .dest(self.tileset.to_screen(at));
  153. self.tileset.batch.set(self.contents[idx].1, param).unwrap();
  154. }
  155. pub fn set_with_color(
  156. &mut self,
  157. at: impl Into<Coord>,
  158. ch: Idx,
  159. color: impl Into<ggez::graphics::Color>,
  160. ) {
  161. let at = at.into();
  162. let idx = at.x + at.y * self.size.width;
  163. let param = DrawParam::new()
  164. .src(ch.to_location().into())
  165. .dest(self.tileset.to_screen(at))
  166. .color(color.into());
  167. self.tileset.batch.set(self.contents[idx].1, param).unwrap();
  168. }
  169. pub fn get(&mut self, at: impl Into<Coord>) -> Idx {
  170. let at = at.into();
  171. let idx = at.x + at.y * self.size.width;
  172. self.contents[idx].0.clone()
  173. }
  174. pub fn clear(&mut self) {
  175. for (n, contents) in self.contents.iter_mut().enumerate() {
  176. let x = n % self.size.width;
  177. let y = n / self.size.width;
  178. let param = DrawParam::new()
  179. .src(Idx::blank().to_location().into())
  180. .dest(self.tileset.to_screen([x, y]));
  181. contents.0 = Idx::blank();
  182. self.tileset.batch.set(contents.1, param).unwrap();
  183. }
  184. }
  185. }
  186. impl Board<CP437> {
  187. pub fn print(&mut self, at: impl Into<Coord>, msg: &str) {
  188. let Coord { x, y } = at.into();
  189. for (idx, ch) in msg.chars().enumerate() {
  190. self.set([idx + x, y], CP437::from_char(ch));
  191. }
  192. }
  193. }