You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

716 lines
22 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. #![allow(clippy::upper_case_acronyms)]
  2. #![allow(clippy::new_without_default)]
  3. use macroquad::prelude::*;
  4. use macroquad::rand::*;
  5. pub mod grammar;
  6. use grammar::*;
  7. const FONT: &str = &"./assets/fira_code.ttf";
  8. const FONT_WIDTH: u16 = 8;
  9. const FONT_HEIGHT_REAL: f32 = FONT_WIDTH as f32 / 8.0 * 13.0;
  10. const FONT_HEIGHT: u16 = (FONT_HEIGHT_REAL + 4.0) as u16;
  11. const FONT_DIFFERENCE: u16 = FONT_HEIGHT - FONT_HEIGHT_REAL as u16;
  12. const GRID_WIDTH: usize = 60;
  13. const GRID_HEIGHT: usize = 25;
  14. // 9 = 3 + height of fishing guy - 1 for some reason
  15. const WATER_LEVEL: i32 = 9;
  16. const HEIGHT: i32 = FONT_HEIGHT as i32 * 25;
  17. // 90 = 60 + 30
  18. const WIDTH: i32 = FONT_WIDTH as i32 * 90;
  19. const UPDATE: f64 = 150.0 / 1000.0; // in seconds
  20. const ANIM_CHANGE: f64 = 750.0 / 1000.0;
  21. fn window_conf() -> Conf {
  22. Conf {
  23. window_title: "fishing minigame".to_owned(),
  24. window_width: WIDTH,
  25. window_height: HEIGHT,
  26. fullscreen: false,
  27. ..Default::default()
  28. }
  29. }
  30. struct Fish {
  31. is_right: bool,
  32. // middle length
  33. length: u8,
  34. pos: IVec2,
  35. }
  36. impl Fish {
  37. fn new() -> Self {
  38. let is_right = gen_range(0, 2) == 0;
  39. let length = gen_range(0, 6);
  40. Self {
  41. is_right,
  42. length,
  43. pos: IVec2::new(
  44. if is_right {
  45. -(length as i32 + 3)
  46. } else {
  47. GRID_WIDTH as i32
  48. },
  49. gen_range(WATER_LEVEL + 1, GRID_HEIGHT as i32 - 2),
  50. ),
  51. }
  52. }
  53. fn into_text(self, grammar: &Grammar) -> Option<String> {
  54. grammar.flatten()
  55. }
  56. }
  57. enum Mode {
  58. Start,
  59. Play,
  60. End,
  61. }
  62. struct Game {
  63. mode: Mode,
  64. state: State,
  65. draw: Draw,
  66. grammar: Grammar,
  67. }
  68. impl Game {
  69. async fn new() -> Result<Self, FontError> {
  70. let mut draw = Draw::new().await.unwrap();
  71. let mut state = State::new();
  72. game_setup(&mut state, &mut draw);
  73. let grammar = grammar_setup();
  74. Ok(Self {
  75. mode: Mode::Start,
  76. state,
  77. draw,
  78. grammar,
  79. })
  80. }
  81. fn update(&mut self) {
  82. // fish update
  83. let mut remove = Vec::new();
  84. for (i, fish) in self.state.fish.iter_mut().enumerate() {
  85. // if something is hooked and it's not this
  86. let is_hooked = self.state.is_hooked.map(|idx| idx == i).unwrap_or(false);
  87. if !is_hooked {
  88. if fish.is_right {
  89. fish.pos.x += 1;
  90. if fish.pos.x >= GRID_WIDTH as i32 {
  91. remove.push(i);
  92. }
  93. } else {
  94. fish.pos.x -= 1;
  95. if fish.pos.x <= -(fish.length as i32 + 3) {
  96. remove.push(i);
  97. }
  98. }
  99. }
  100. }
  101. for i in remove.into_iter().rev() {
  102. self.state.fish.remove(i);
  103. if let Some(idx) = &mut self.state.is_hooked {
  104. if *idx > i {
  105. *idx -= 1;
  106. }
  107. }
  108. }
  109. if self.state.fish.len() < 3 && gen_range(0, 15) < 1 {
  110. self.state.fish.push(Fish::new());
  111. }
  112. // collision
  113. let hook = self.state.hook;
  114. if self.state.is_hooked.is_none() {
  115. for (i, fish) in self.state.fish.iter().enumerate() {
  116. if fish.pos.y == hook.y
  117. && fish.pos.x <= hook.x
  118. && fish.pos.x + fish.length as i32 + 3 > hook.x
  119. {
  120. self.state.is_hooked = Some(i);
  121. break;
  122. }
  123. }
  124. }
  125. }
  126. }
  127. struct Draw {
  128. // only two frames of animation
  129. first_frame: bool,
  130. // animated sprites
  131. animations: Vec<Animation>,
  132. // static sprites
  133. sprites: Vec<Sprite>,
  134. font: Font,
  135. }
  136. impl Draw {
  137. async fn new() -> Result<Self, FontError> {
  138. // font: fira code. dimensions: 13.0x8.0
  139. let font = load_ttf_font(FONT).await?;
  140. Ok(Self {
  141. first_frame: true,
  142. animations: Vec::new(),
  143. sprites: Vec::new(),
  144. font,
  145. })
  146. }
  147. fn add_sprite(&mut self, sprite: Sprite) -> usize {
  148. self.sprites.push(sprite);
  149. self.sprites.len() - 1
  150. }
  151. fn add_animation(&mut self, animation: Animation) -> usize {
  152. self.animations.push(animation);
  153. self.animations.len() - 1
  154. }
  155. }
  156. struct Sprite {
  157. width: usize,
  158. height: usize,
  159. color: Option<Color>,
  160. sprite: String,
  161. }
  162. impl Sprite {
  163. fn new(width: usize, height: usize, color: Option<Color>, sprite: String) -> Self {
  164. assert_eq!(width * height, sprite.len() - height + 1);
  165. Self {
  166. width,
  167. height,
  168. color,
  169. sprite,
  170. }
  171. }
  172. fn draw(&self, pos: IVec2, font: Font) {
  173. let pos = pos.as_f32() * Vec2::new(FONT_WIDTH as f32, FONT_HEIGHT as f32);
  174. if let Some(color) = self.color {
  175. draw_rectangle(
  176. pos.x,
  177. pos.y,
  178. self.width as f32 * FONT_WIDTH as f32,
  179. self.height as f32 * FONT_HEIGHT as f32,
  180. color,
  181. );
  182. }
  183. for (i, row) in self.sprite.split('\n').enumerate() {
  184. let i = (i + 1) as f32 * FONT_HEIGHT as f32 - FONT_DIFFERENCE as f32;
  185. draw_text_ex(
  186. row,
  187. pos.x,
  188. pos.y + i,
  189. TextParams {
  190. font,
  191. color: BLACK,
  192. font_size: FONT_HEIGHT - FONT_DIFFERENCE,
  193. ..Default::default()
  194. },
  195. );
  196. }
  197. }
  198. }
  199. struct Animation {
  200. width: usize,
  201. height: usize,
  202. color: Color,
  203. /// animations can have two frames and must be strings
  204. frames: [String; 2],
  205. }
  206. impl Animation {
  207. fn new(width: usize, height: usize, color: Color, frames: [String; 2]) -> Self {
  208. assert_eq!(width * height, frames[0].len() - height + 1);
  209. assert_eq!(width * height, frames[1].len() - height + 1);
  210. Self {
  211. width,
  212. height,
  213. color,
  214. frames,
  215. }
  216. }
  217. fn draw(&self, pos: IVec2, first_frame: bool, font: Font) {
  218. let pos = pos.as_f32() * Vec2::new(FONT_WIDTH as f32, FONT_HEIGHT as f32);
  219. draw_rectangle(
  220. pos.x,
  221. pos.y,
  222. self.width as f32 * FONT_WIDTH as f32,
  223. self.height as f32 * FONT_HEIGHT as f32,
  224. self.color,
  225. );
  226. for (i, row) in self.frames[first_frame as usize].split('\n').enumerate() {
  227. let i = (i + 1) as f32 * FONT_HEIGHT as f32 - FONT_DIFFERENCE as f32;
  228. draw_text_ex(
  229. row,
  230. pos.x,
  231. pos.y + i,
  232. TextParams {
  233. font,
  234. color: BLACK,
  235. font_size: FONT_HEIGHT - FONT_DIFFERENCE,
  236. ..Default::default()
  237. },
  238. );
  239. }
  240. }
  241. }
  242. struct State {
  243. // TODO: consider vecdeque instead of stack
  244. fish: Vec<Fish>,
  245. hook: IVec2,
  246. // which fish is hooked
  247. is_hooked: Option<usize>,
  248. // positions of other things (unmoving)
  249. positions: Vec<IVec2>,
  250. // parallel vec of sprites corresponding to ^
  251. sprites: Vec<DrawType>,
  252. caught_fish: Vec<String>,
  253. }
  254. enum DrawType {
  255. Anim(usize),
  256. Sprite(usize),
  257. }
  258. impl State {
  259. fn new() -> Self {
  260. State {
  261. fish: Vec::new(),
  262. hook: IVec2::new(GRID_WIDTH as i32 / 2, WATER_LEVEL + 5),
  263. is_hooked: None,
  264. positions: Vec::new(),
  265. sprites: Vec::new(),
  266. caught_fish: Vec::new(),
  267. }
  268. }
  269. }
  270. #[macroquad::main(window_conf)]
  271. async fn main() {
  272. srand(get_time().to_bits());
  273. let mut game = Game::new().await.unwrap();
  274. let mut updated = get_time();
  275. let mut anim_updated = get_time();
  276. loop {
  277. match game.mode {
  278. Mode::Start => {
  279. // draw
  280. clear_background(SKYBLUE);
  281. draw_text_centered(
  282. &"press ENTER to start",
  283. HEIGHT as f32 / 2.0 - FONT_HEIGHT as f32 * 2.0,
  284. game.draw.font,
  285. );
  286. draw_text_centered(
  287. &"use up and down to move the hook",
  288. HEIGHT as f32 / 2.0 - FONT_HEIGHT as f32,
  289. game.draw.font,
  290. );
  291. draw_text_centered(
  292. &"use space to catch a fish",
  293. HEIGHT as f32 / 2.0,
  294. game.draw.font,
  295. );
  296. draw_text_centered(
  297. &"use escape to end the game",
  298. HEIGHT as f32 / 2.0 + FONT_HEIGHT as f32,
  299. game.draw.font,
  300. );
  301. if is_key_pressed(KeyCode::Enter) {
  302. game.mode = Mode::Play;
  303. }
  304. }
  305. Mode::Play => {
  306. if is_key_pressed(KeyCode::Escape) {
  307. game.mode = Mode::End;
  308. continue;
  309. }
  310. if is_key_pressed(KeyCode::Up) {
  311. game.state.hook.y = (game.state.hook.y - 1).max(WATER_LEVEL);
  312. }
  313. if is_key_pressed(KeyCode::Down) {
  314. game.state.hook.y = (game.state.hook.y + 1).min(GRID_HEIGHT as i32 - 2);
  315. }
  316. if let Some(idx) = game.state.is_hooked {
  317. game.state.fish[idx].pos.y = game.state.hook.y;
  318. if is_key_pressed(KeyCode::Space) {
  319. let fish = game.state.fish.remove(idx);
  320. game.state
  321. .caught_fish
  322. .push(fish.into_text(&game.grammar).unwrap());
  323. game.state.is_hooked = None;
  324. }
  325. }
  326. if get_time() - updated >= UPDATE {
  327. game.update();
  328. updated = get_time();
  329. }
  330. game.draw();
  331. if get_time() - anim_updated >= ANIM_CHANGE {
  332. game.draw.first_frame = !game.draw.first_frame;
  333. anim_updated = get_time();
  334. }
  335. }
  336. Mode::End => {
  337. clear_background(SKYBLUE);
  338. draw_text_centered(
  339. &format!(
  340. "you caught {} fish, thanks for playing",
  341. game.state.caught_fish.len()
  342. ),
  343. HEIGHT as f32 / 2.0,
  344. game.draw.font,
  345. );
  346. draw_text_centered(
  347. &"press ENTER to restart",
  348. HEIGHT as f32 / 2.0 + FONT_HEIGHT as f32,
  349. game.draw.font,
  350. );
  351. if is_key_pressed(KeyCode::Enter) {
  352. game.state = State::new();
  353. game.draw.animations.clear();
  354. game.draw.sprites.clear();
  355. game_setup(&mut game.state, &mut game.draw);
  356. game.mode = Mode::Start;
  357. }
  358. }
  359. }
  360. next_frame().await;
  361. }
  362. }
  363. impl Game {
  364. fn draw(&self) {
  365. const FISH_WIDTH: f32 = FONT_WIDTH as f32 * GRID_WIDTH as f32;
  366. const H: f32 = HEIGHT as f32;
  367. const WL: f32 = WATER_LEVEL as f32 * FONT_HEIGHT as f32;
  368. // water
  369. clear_background(SKYBLUE);
  370. // sky
  371. draw_rectangle(0.0, 0.0, FISH_WIDTH, WL, WHITE);
  372. // ground
  373. draw_rectangle(
  374. 0.0,
  375. H - FONT_HEIGHT as f32,
  376. FISH_WIDTH,
  377. FONT_HEIGHT as f32,
  378. BEIGE,
  379. );
  380. // static sprites
  381. for (pos, sprite) in self.state.positions.iter().zip(self.state.sprites.iter()) {
  382. match sprite {
  383. DrawType::Anim(i) => {
  384. self.draw.animations[*i].draw(*pos, self.draw.first_frame, self.draw.font)
  385. }
  386. DrawType::Sprite(i) => self.draw.sprites[*i].draw(*pos, self.draw.font),
  387. }
  388. }
  389. // fish
  390. for fish in self.state.fish.iter() {
  391. let sprite = &self.draw.sprites[fish.length as usize * 2 + fish.is_right as usize];
  392. sprite.draw(fish.pos, self.draw.font);
  393. }
  394. // hook
  395. self.draw.sprites[12].draw(self.state.hook, self.draw.font);
  396. // line
  397. let sprite = (0..self.state.hook.y - 5)
  398. .map(|_| "|\n")
  399. .collect::<String>();
  400. let sprite = sprite.trim().to_string();
  401. let sprite = Sprite::new(1, self.state.hook.y as usize - 5, None, sprite);
  402. sprite.draw(IVec2::new(GRID_WIDTH as i32 / 2, 5), self.draw.font);
  403. // sidebar
  404. draw_rectangle(FISH_WIDTH, 0.0, WIDTH as f32 - FISH_WIDTH, H, LIGHTGRAY);
  405. // draw fish info
  406. // sidebar width: 480 - 8px? margins * 2 = 464 / 16 = 29 characters / line
  407. let mut y = 30.0;
  408. let x = GRID_WIDTH as f32 * FONT_WIDTH as f32 + 8.0;
  409. for text in self.state.caught_fish.iter().rev() {
  410. if y > HEIGHT as f32 + FONT_HEIGHT as f32 {
  411. break;
  412. }
  413. y += 20.0;
  414. let mut line = "".to_string();
  415. line.reserve_exact(28);
  416. for word in text.split_whitespace() {
  417. if line.len() + word.len() > 28 {
  418. draw_text_ex(
  419. &line,
  420. x,
  421. y,
  422. TextParams {
  423. font: self.draw.font,
  424. color: BLACK,
  425. font_size: FONT_HEIGHT - FONT_DIFFERENCE,
  426. ..Default::default()
  427. },
  428. );
  429. line.clear();
  430. y += FONT_HEIGHT as f32;
  431. }
  432. line += word;
  433. line += " ";
  434. }
  435. draw_text_ex(
  436. &line,
  437. x,
  438. y,
  439. TextParams {
  440. font: self.draw.font,
  441. color: BLACK,
  442. font_size: FONT_HEIGHT - FONT_DIFFERENCE,
  443. ..Default::default()
  444. },
  445. );
  446. y += FONT_HEIGHT as f32;
  447. }
  448. }
  449. }
  450. fn grammar_setup() -> Grammar {
  451. let rules = [
  452. ("origin".to_string(),
  453. vec!["#fish#. #fishFact#".to_string(),
  454. "#fish#. #fishFact#".to_string(),
  455. "#fish#. #fishFact#".to_string(),
  456. "#fish#. #fishFact#".to_string(),
  457. "#fish#. #fishFact#".to_string(),
  458. "#fish#. #fishFact#".to_string(),
  459. "#fish#. #fishFact#".to_string(),
  460. "#fish#. #fishFact#".to_string(),
  461. "#fish#. #fishFact#".to_string(),
  462. "#seaFish#. #seaFishFact#".to_string(),
  463. "#largeFish#. #largeFishFact#".to_string(),
  464. "#whale#. #whaleFact#".to_string(),
  465. "#unnervingFish#. #unnervingFishFact#".to_string(),
  466. "sea bass. no, wait--- it's at least a c+!".to_string()]),
  467. ("fish".to_string(),
  468. vec!["rainbow trout".to_string(),
  469. "salmon".to_string(),
  470. "black bass".to_string(),
  471. "catfish".to_string(),
  472. "brook trout".to_string(),
  473. "cod".to_string(),
  474. "minnow".to_string(),
  475. "turbofish".to_string()]),
  476. ("fishFact".to_string(),
  477. vec!["this fish loves to swim about.".to_string(),
  478. "this fish was sleeping and you woke it up, which is kind of rude.".to_string(),
  479. "this fish will grant you three wishes if you don't eat it but you weren't going to do that anyway.".to_string(),
  480. "fish are neat :)".to_string(),
  481. "this fish likes to hide under rocks.".to_string(),
  482. "this fish can breathe underwater.".to_string(),
  483. "this fish likes to blow bubbles.".to_string(),
  484. "this fish likes to flick its tail.".to_string(),
  485. "this fish can do a backflip.".to_string(),
  486. "this fish is very tired and wants to rest.".to_string(),
  487. "this fish wants to show you some sick card tricks.".to_string()]),
  488. ("unnervingFish".to_string(),
  489. vec!["swamp eel".to_string(),
  490. "pike".to_string(),
  491. "jellyfish".to_string(),
  492. "sunfish".to_string(),
  493. "angler fish".to_string(),
  494. "squid".to_string()]),
  495. ("unnervingFishFact".to_string(),
  496. vec!["#fishFact#".to_string(),
  497. "i'm spooked by this fish.".to_string(),
  498. "this fish has so many teeth.".to_string(),
  499. "you feel like this fish will haunt your nightmares.".to_string(),
  500. "please put it back.".to_string()]),
  501. ("largeFish".to_string(),
  502. vec!["lemon shark".to_string(),
  503. "mako shark".to_string(),
  504. "whale shark".to_string(),
  505. "great white shark".to_string()]),
  506. ("largeFishFact".to_string(),
  507. vec!["#seaFishFact#".to_string(),
  508. "#fishFact#".to_string(),
  509. "this fish is incredibly large.".to_string(),
  510. "OH GOD IT'S JUST SO BIG".to_string(),
  511. "this fish might eat you? you're not sure, it hasn't happened yet".to_string(),
  512. "this fish has so many teeth.".to_string()]),
  513. ("whale".to_string(),
  514. vec!["blue whale".to_string(),
  515. "humpback whale".to_string(),
  516. "sperm whale".to_string(),
  517. "right whale".to_string(),
  518. "razor back whale".to_string()]),
  519. ("whaleFact".to_string(),
  520. vec!["#seaFishFact#".to_string(),
  521. "#largeFishFact#".to_string(),
  522. "\"Oh, man! Admire and model thyself after the whale! Do thou, too, remain warm among ice. Do thou, too, live in this world without being of it. Be cool at the equator; keep thy blood fluid at the pole. Like the great dome of St. Peter's, and like the great whale, retain, O man! in all seasons a temperature of thine own.\" (melville 343)".to_string(),
  523. "\"I. A Fast-Fish belongs to the party fast to it. II. A Loose-Fish is fair game for anybody who can soonest catch it.\" (melville 441)".to_string(),
  524. "\"All men live enveloped in whale lines.\" (melville 315)".to_string(),
  525. "\"Let him go. I know little more of him,
  526. nor does anybody else.\" (melville 153)".to_string(),
  527. "\"a whale is A SPOUTING FISH WITH A HORIZONTAL TAIL. There you have him.\" (melville 148)".to_string()]),
  528. ("seaFish".to_string(),
  529. vec!["black sea bass".to_string(),
  530. "mackerel".to_string(),
  531. "tuna".to_string(),
  532. "sea bass".to_string(),
  533. "anchovy".to_string()]),
  534. ("seaFishFact".to_string(),
  535. vec!["#fishFact#".to_string(),
  536. "this fish lives in the sea and you're not sure how it got here.".to_string(),
  537. "you're not even sure if this fish can live in freshwater, but okay.".to_string()])
  538. ];
  539. Grammar::from(rules)
  540. }
  541. fn game_setup(state: &mut State, draw: &mut Draw) {
  542. // fish
  543. for i in 0..6 {
  544. let mut middle = "".to_string();
  545. middle.reserve_exact(i);
  546. for _ in 0..i {
  547. middle += ":";
  548. }
  549. draw.add_sprite(Sprite::new(
  550. i + 3,
  551. 1,
  552. Some(SKYBLUE),
  553. "<".to_owned() + &middle.clone() + "><",
  554. ));
  555. draw.add_sprite(Sprite::new(
  556. i + 3,
  557. 1,
  558. Some(SKYBLUE),
  559. "><".to_owned() + &middle + ">",
  560. ));
  561. }
  562. // hook
  563. draw.add_sprite(Sprite::new(1, 1, Some(SKYBLUE), "&".to_owned()));
  564. // seaweed
  565. let mut plant_already = [false; GRID_WIDTH];
  566. for _ in 0..5 {
  567. let mut x = gen_range(1, GRID_WIDTH - 1);
  568. while plant_already[x] {
  569. x = gen_range(1, GRID_WIDTH - 1);
  570. }
  571. plant_already[x] = true;
  572. let height = gen_range(2, (GRID_HEIGHT - WATER_LEVEL as usize) / 2);
  573. let mut plant = "".to_string();
  574. plant.reserve_exact(height + 2);
  575. for _ in 0..height / 2 + 1 {
  576. plant += "\\\n/\n";
  577. }
  578. let start = gen_range(0, 2) == 0;
  579. let anim = if start {
  580. [
  581. plant[..height * 2 - 1].to_string(),
  582. plant[2..height * 2 + 1].to_string(),
  583. ]
  584. } else {
  585. [
  586. plant[2..height * 2 + 1].to_string(),
  587. plant[..height * 2 - 1].to_string(),
  588. ]
  589. };
  590. let anim = Animation::new(1, height, GREEN, anim);
  591. let i = draw.add_animation(anim);
  592. state
  593. .positions
  594. .push(IVec2::new(x as i32, GRID_HEIGHT as i32 - height as i32 - 1));
  595. state.sprites.push(DrawType::Anim(i));
  596. }
  597. // water
  598. let mut water = "^".to_string();
  599. water.reserve_exact(GRID_WIDTH);
  600. for _ in 0..GRID_WIDTH / 2 {
  601. water += "~^";
  602. }
  603. let anim = Animation::new(
  604. GRID_WIDTH,
  605. 1,
  606. SKYBLUE,
  607. [water[..GRID_WIDTH].to_string(), water[1..].to_string()],
  608. );
  609. let i = draw.add_animation(anim);
  610. state.positions.push(IVec2::new(0, WATER_LEVEL));
  611. state.sprites.push(DrawType::Anim(i));
  612. // ground
  613. let groud_chars = [
  614. "-", "=", ".", "__", "--", "==", "..", "___", "---", "===", "...",
  615. ];
  616. let mut ground = "".to_string();
  617. let mut i = 0;
  618. while i < GRID_WIDTH {
  619. let add = groud_chars[gen_range(0, groud_chars.len())];
  620. i += add.len();
  621. ground += add;
  622. }
  623. ground = ground[..GRID_WIDTH].to_string();
  624. let i = draw.add_sprite(Sprite::new(GRID_WIDTH, 1, Some(BEIGE), ground));
  625. state.positions.push(IVec2::new(0, GRID_HEIGHT as i32 - 1));
  626. state.sprites.push(DrawType::Sprite(i));
  627. // water
  628. // dude
  629. #[rustfmt::skip]
  630. let dude = r#" O
  631. |--o_______________________.
  632. |__
  633. ===== |
  634. | | |
  635. | | "#;
  636. let i = draw.add_sprite(Sprite::new(31, 6, None, dude.to_string()));
  637. state.positions.push(IVec2::new(0, 3));
  638. state.sprites.push(DrawType::Sprite(i));
  639. }
  640. fn draw_text_centered(text: &str, y: f32, font: Font) {
  641. let dims = measure_text(text, Some(font), FONT_HEIGHT - FONT_DIFFERENCE, 1.0);
  642. draw_text_ex(
  643. text,
  644. WIDTH as f32 / 2.0 - dims.width / 2.0,
  645. y,
  646. TextParams {
  647. font,
  648. color: BLACK,
  649. font_size: FONT_HEIGHT - FONT_DIFFERENCE,
  650. ..Default::default()
  651. },
  652. );
  653. }