No Description
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.

game.rs 40KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411
  1. use controller::{Controller, ControllerButton};
  2. use error;
  3. use geometry::Geometry;
  4. use log;
  5. use audio_play;
  6. use audio_volume;
  7. use colour::{Colour, Format};
  8. use sprite::Sprite;
  9. use std::collections::HashMap;
  10. use units::*;
  11. use Config;
  12. #[derive(PartialEq)]
  13. pub enum GameMode {
  14. Playing,
  15. Paused,
  16. }
  17. // hacky: make sure these values match up with code in web/js/Audio.js
  18. #[derive(Clone, Copy)]
  19. pub enum SoundEffect {
  20. Impact = 1,
  21. GameOver = 2,
  22. Line1 = 3,
  23. Line2 = 4,
  24. Line3 = 5,
  25. Line4 = 6,
  26. PauseIn = 7,
  27. PauseOut = 8,
  28. MenuMove = 9,
  29. }
  30. #[derive(Clone, Copy)]
  31. pub struct Block {
  32. occupied: bool,
  33. offset: Vec2D,
  34. colour: Col,
  35. }
  36. impl Block {
  37. pub fn new(col: Col) -> Block {
  38. Block {
  39. occupied: false,
  40. offset: Vec2D { x: 0.0, y: 0.0 },
  41. colour: col,
  42. }
  43. }
  44. pub fn reset(&mut self, colour: &Col) {
  45. self.occupied = false;
  46. self.offset = Vec2D { x: 0.0, y: 0.0 };
  47. self.colour = *colour;
  48. }
  49. }
  50. pub struct Board {
  51. board: Vec<Vec<Block>>,
  52. width: usize,
  53. height: usize,
  54. inactive_colour: Col,
  55. crt_update_speed: f32,
  56. }
  57. impl Board {
  58. pub fn new(config: &Config) -> Board {
  59. // width: usize, height: usize, inactive_colour: Col, crt_update_speed: f32
  60. let inactive_colour = Col::new(
  61. config.block_inactive_col_r,
  62. config.block_inactive_col_g,
  63. config.block_inactive_col_b,
  64. config.block_inactive_col_a,
  65. );
  66. let width = config.board_width;
  67. let height = config.board_height;
  68. let crt_update_speed = 1.0; // this will be updated by Game::new() calling set_crt_update_speed
  69. Board {
  70. board: vec![vec![Block::new(inactive_colour); width]; height],
  71. width,
  72. height,
  73. inactive_colour,
  74. crt_update_speed,
  75. }
  76. }
  77. pub fn reset(&mut self) {
  78. for line in 0..self.height {
  79. for x in 0..self.width {
  80. self.board[line][x].reset(&self.inactive_colour);
  81. }
  82. }
  83. }
  84. pub fn reconfigure(&mut self, config: &Config) {
  85. // not updating board width/height in order to retain the current state of the board
  86. self.inactive_colour = Col::new(
  87. config.block_inactive_col_r,
  88. config.block_inactive_col_g,
  89. config.block_inactive_col_b,
  90. config.block_inactive_col_a,
  91. );
  92. }
  93. pub fn set_crt_update_speed(&mut self, new_crt_update_speed: f32) {
  94. self.crt_update_speed = new_crt_update_speed;
  95. }
  96. pub fn is_line_filled(&self, line_number: usize) -> bool {
  97. if line_number >= self.height {
  98. // should error
  99. return false;
  100. }
  101. let line = &self.board[line_number];
  102. line.iter().all(|block| block.occupied)
  103. }
  104. pub fn remove_line(&mut self, line_number: usize) -> error::Result<()> {
  105. if line_number >= self.height {
  106. return Err(error::TetrisError::LineRemoval);
  107. }
  108. for line in line_number..(self.height - 1) {
  109. // copy top_line onto line
  110. //
  111. let top_line = line + 1;
  112. for x in 0..self.width {
  113. self.board[line][x] = self.board[top_line][x];
  114. }
  115. }
  116. let highest_line = self.height - 1;
  117. for x in 0..self.width {
  118. self.board[highest_line][x].reset(&self.inactive_colour);
  119. }
  120. Ok(())
  121. }
  122. }
  123. #[derive(Debug, Clone, Copy)]
  124. pub struct BoardPos {
  125. pub x: i32,
  126. pub y: i32,
  127. }
  128. #[derive(Clone, Copy, Hash, Eq, PartialEq)]
  129. enum ShapeKind {
  130. I,
  131. J,
  132. L,
  133. O,
  134. S,
  135. T,
  136. Z,
  137. }
  138. pub struct Shape {
  139. aabb_width: i32,
  140. aabb_height: i32,
  141. real_height: i32,
  142. fully_rotate: bool,
  143. form: Vec<Vec<bool>>,
  144. }
  145. enum PieceAngle {
  146. R0,
  147. R90,
  148. R180,
  149. R270,
  150. }
  151. pub struct Piece {
  152. shape_kind: ShapeKind,
  153. pos: BoardPos,
  154. angle: PieceAngle,
  155. colour: Col,
  156. }
  157. pub struct Game {
  158. mode: GameMode,
  159. resume_from_paused: bool,
  160. board: Board,
  161. board_offset: Block2D,
  162. shapes: HashMap<ShapeKind, Shape>,
  163. piece: Piece, // the current piece under player control
  164. next_piece_shape: ShapeKind,
  165. next_piece_colour: Col,
  166. colour_saturation: f64,
  167. colour_lightness: f64,
  168. menu_num_items: i32,
  169. menu_active_item: i32,
  170. menu_volume: i32,
  171. menu_max_volume: i32,
  172. menu_crt_update_speed: i32,
  173. menu_max_crt_update_speed: i32,
  174. menu_curvature: i32,
  175. menu_max_curvature: i32,
  176. is_game_over: bool,
  177. score: i32,
  178. level: i32,
  179. lines: i32,
  180. tetris: bool,
  181. time_to_next_lowering: f32, // ms
  182. piece_is_dropping: bool,
  183. impact_sfx_played: bool,
  184. default_cooldown: f32, // ms
  185. left_cooldown: f32,
  186. right_cooldown: f32,
  187. up_cooldown: f32,
  188. down_cooldown: f32,
  189. a_cooldown: f32,
  190. b_cooldown: f32,
  191. i_count: i32,
  192. j_count: i32,
  193. l_count: i32,
  194. o_count: i32,
  195. s_count: i32,
  196. t_count: i32,
  197. z_count: i32,
  198. debug_mode: bool,
  199. debug_flash: bool,
  200. debug_delta: f32,
  201. }
  202. impl Game {
  203. pub fn new(config: &Config) -> Game {
  204. let mut game = Game {
  205. mode: GameMode::Playing,
  206. resume_from_paused: false,
  207. board: Board::new(config),
  208. board_offset: Block2D {
  209. x: config.board_offset_x,
  210. y: config.board_offset_y,
  211. },
  212. shapes: define_shapes(),
  213. piece: Piece {
  214. shape_kind: ShapeKind::J,
  215. pos: BoardPos { x: 5, y: 19 },
  216. angle: PieceAngle::R0,
  217. colour: Col::new(0.4, 0.8, 0.2, 0.9),
  218. },
  219. next_piece_shape: ShapeKind::L,
  220. next_piece_colour: Col::new(1.0, 1.0, 1.0, 1.0),
  221. colour_saturation: config.colour_saturation,
  222. colour_lightness: config.colour_lightness,
  223. menu_num_items: 5,
  224. menu_active_item: 0,
  225. menu_volume: config.menu_volume,
  226. menu_max_volume: config.menu_max_volume,
  227. menu_crt_update_speed: config.menu_crt_update_speed,
  228. menu_max_crt_update_speed: config.menu_max_crt_update_speed,
  229. menu_curvature: config.menu_curvature,
  230. menu_max_curvature: config.menu_max_curvature,
  231. is_game_over: false,
  232. score: 0,
  233. level: 0,
  234. lines: 0,
  235. tetris: false, // was the last scoring move a tetris?
  236. time_to_next_lowering: get_time_to_next_lowering(0),
  237. piece_is_dropping: false,
  238. impact_sfx_played: false,
  239. default_cooldown: config.default_cooldown,
  240. left_cooldown: 0.0,
  241. right_cooldown: 0.0,
  242. up_cooldown: 0.0,
  243. down_cooldown: 0.0,
  244. a_cooldown: 0.0,
  245. b_cooldown: 0.0,
  246. i_count: 0,
  247. j_count: 0,
  248. l_count: 0,
  249. o_count: 0,
  250. s_count: 0,
  251. t_count: 0,
  252. z_count: 0,
  253. debug_mode: false,
  254. debug_flash: true,
  255. debug_delta: 0.0,
  256. };
  257. // set state according to initial config values
  258. update_audio_volume(&game);
  259. update_crt_update_speed(&mut game);
  260. game
  261. }
  262. pub fn restart(&mut self, random: f32) -> error::Result<()> {
  263. self.is_game_over = false;
  264. self.score = 0;
  265. self.level = 0;
  266. self.lines = 0;
  267. self.tetris = false;
  268. self.time_to_next_lowering = get_time_to_next_lowering(0);
  269. self.left_cooldown = 0.0;
  270. self.right_cooldown = 0.0;
  271. self.up_cooldown = 0.0;
  272. self.down_cooldown = 0.0;
  273. self.a_cooldown = 0.0;
  274. self.b_cooldown = 0.0;
  275. self.i_count = 0;
  276. self.j_count = 0;
  277. self.l_count = 0;
  278. self.o_count = 0;
  279. self.s_count = 0;
  280. self.t_count = 0;
  281. self.z_count = 0;
  282. self.board.reset();
  283. self.next_piece_shape = get_random_shape(1.0 - random)?;
  284. self.next_piece_colour = get_colour_from_hsl(
  285. ((1.0 - random) * 360.0) as f64,
  286. self.colour_saturation,
  287. self.colour_lightness,
  288. 1.0
  289. )?;
  290. use_next_shape(self, random)?;
  291. Ok(())
  292. }
  293. pub fn get_curvature(&self) -> f32 {
  294. let f = self.menu_curvature as f32 / self.menu_max_curvature as f32;
  295. f * (1.0 / 8.0)
  296. }
  297. pub fn reconfigure(&mut self, config: &Config) {
  298. self.board.reconfigure(config);
  299. self.board_offset = Block2D {
  300. x: config.board_offset_x,
  301. y: config.board_offset_y,
  302. };
  303. self.colour_saturation = config.colour_saturation;
  304. self.colour_lightness = config.colour_lightness;
  305. self.default_cooldown = config.default_cooldown;
  306. }
  307. pub fn init(&mut self, random: f32) -> error::Result<()> {
  308. self.next_piece_shape = get_random_shape(1.0 - random)?;
  309. self.next_piece_colour = get_colour_from_hsl(
  310. ((1.0 - random) * 360.0) as f64,
  311. self.colour_saturation,
  312. self.colour_lightness,
  313. 1.0
  314. )?;
  315. use_next_shape(self, random)?;
  316. Ok(())
  317. }
  318. pub fn update_geometry(&self, geometry: &mut Geometry) {
  319. geometry.clear();
  320. // zero vert
  321. geometry.push(0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0);
  322. render_board(geometry, &self.board, &self.board_offset);
  323. let x = self.board_offset.x + 11;
  324. let y = self.board_offset.y + 3;
  325. if let Ok(c) = get_colour_from_hsl(90.0,
  326. self.colour_saturation,
  327. self.colour_lightness,
  328. 1.0) {
  329. render_score(geometry, self.score, Block2D { x, y: y + 14 }, c);
  330. render_level(geometry, self.level, Block2D { x, y: y + 10 }, c);
  331. render_lines(geometry, self.lines, Block2D { x, y: y + 6 }, c);
  332. }
  333. if let Some(shape) = self.shapes.get(&self.next_piece_shape) {
  334. render_next_shape(geometry, &shape, Block2D { x, y }, self.next_piece_colour);
  335. }
  336. // render last as this contains a full-screen alpha
  337. if self.mode == GameMode::Paused {
  338. if let Ok(c) = get_colour_from_hsl(90.0,
  339. self.colour_saturation,
  340. self.colour_lightness,
  341. 1.0) {
  342. let board_centre = Block2D {
  343. x: (self.board.width / 2) as i32 + self.board_offset.x,
  344. y: (self.board.height / 2) as i32 + self.board_offset.y,
  345. };
  346. geometry.push_filled_fullscreen(Col::new(0.0, 0.0, 0.0, 0.4));
  347. if let Ok(background_colour) = get_colour_from_hsl(90.0,
  348. self.colour_saturation / 2.0,
  349. self.colour_lightness / 2.0,
  350. 0.7) {
  351. render_pause_menu(self, geometry, board_centre, c, background_colour);
  352. }
  353. }
  354. }
  355. if self.debug_mode {
  356. if self.debug_flash {
  357. geometry.push_text("X", Block2D { x: 11, y: 1 }, Col::new(1.0, 1.0, 1.0, 1.0));
  358. } else {
  359. geometry.push_text("-", Block2D { x: 11, y: 1 }, Col::new(1.0, 1.0, 1.0, 1.0));
  360. }
  361. geometry.push_text(&format!("{}", self.debug_delta), Block2D { x: 12, y: 1 }, Col::new(1.0, 1.0, 1.0, 1.0));
  362. }
  363. }
  364. pub fn tick(&mut self, controller: &Controller, delta: f32, random: f32) -> bool {
  365. if controller.just_released(ControllerButton::Start) {
  366. if self.mode == GameMode::Playing {
  367. play_sound(SoundEffect::PauseIn);
  368. self.mode = GameMode::Paused;
  369. } else {
  370. play_sound(SoundEffect::PauseOut);
  371. self.mode = GameMode::Playing;
  372. self.resume_from_paused = true;
  373. }
  374. }
  375. if controller.just_released(ControllerButton::Select) {
  376. self.debug_mode = !self.debug_mode;
  377. }
  378. // always followed by a render
  379. // returns true if tick should be called again
  380. //
  381. let corrected_delta;
  382. // prevent a crazy delta value if we're resuming from a paused state
  383. if self.resume_from_paused {
  384. self.resume_from_paused = false;
  385. corrected_delta = 0.0;
  386. } else {
  387. corrected_delta = delta;
  388. }
  389. if self.debug_mode {
  390. self.debug_flash = !self.debug_flash;
  391. self.debug_delta = corrected_delta;
  392. if self.debug_delta > 0.0 && self.debug_delta < 15.0 {
  393. log(&format!("{}", self.debug_delta));
  394. }
  395. }
  396. match self.mode {
  397. GameMode::Playing => match self.tick_playing(controller, corrected_delta, random) {
  398. Ok(res) => res,
  399. Err(_) => false,
  400. },
  401. GameMode::Paused => match self.tick_paused(controller, random) {
  402. Ok(res) => res,
  403. Err(_) => false,
  404. },
  405. }
  406. }
  407. fn tick_paused(&mut self, controller: &Controller, random: f32) -> error::Result<bool> {
  408. let updated = update_pause_menu(self, controller, random);
  409. Ok(updated)
  410. }
  411. // counting on the fact that delta will always be a 'sensible' value.
  412. // so if the player has paused the game for a while, on the first resumed call to tick_playing
  413. // delta should be 0 rather than a large multi-second value
  414. fn tick_playing(
  415. &mut self,
  416. controller: &Controller,
  417. delta: f32,
  418. random: f32,
  419. ) -> error::Result<bool> {
  420. if self.is_game_over {
  421. log("game over");
  422. return Ok(false);
  423. }
  424. if let Some(shape) = self.shapes.get(&self.piece.shape_kind) {
  425. remove_shape_from_board(&mut self.board, shape, &self.piece.pos, &self.piece.angle);
  426. }
  427. apply_user_input(self, controller, delta);
  428. if self.piece_is_dropping {
  429. if let Some(shape) = self.shapes.get(&self.piece.shape_kind) {
  430. if !lower_piece(&self.board, shape, &mut self.piece) {
  431. // NOTE: don't lock in the shape when it stops dropping.
  432. //
  433. // the game 'feels' better if the user has time to modify the
  434. // piece's position at the bottom of the drop
  435. self.piece_is_dropping = false;
  436. if !self.impact_sfx_played {
  437. play_sound(SoundEffect::Impact);
  438. self.impact_sfx_played = true;
  439. }
  440. }
  441. }
  442. } else if is_time_to_lower_piece(self, delta) {
  443. let mut piece_cant_go_lower = false;
  444. if let Some(shape) = self.shapes.get(&self.piece.shape_kind) {
  445. piece_cant_go_lower = !lower_piece(&self.board, shape, &mut self.piece);
  446. }
  447. if piece_cant_go_lower {
  448. let mut num_lines = 0;
  449. if let Some(shape) = self.shapes.get(&self.piece.shape_kind) {
  450. add_shape_to_board(
  451. &mut self.board,
  452. shape,
  453. &self.piece.pos,
  454. &self.piece.angle,
  455. &self.piece.colour,
  456. );
  457. num_lines = remove_complete_lines(&mut self.board)?;
  458. }
  459. if num_lines > 0 {
  460. match num_lines {
  461. 1 => play_sound(SoundEffect::Line1),
  462. 2 => play_sound(SoundEffect::Line2),
  463. 3 => play_sound(SoundEffect::Line3),
  464. 4 => play_sound(SoundEffect::Line4),
  465. _ => ()
  466. };
  467. update_score(self, num_lines);
  468. } else if !self.impact_sfx_played {
  469. play_sound(SoundEffect::Impact);
  470. self.impact_sfx_played = true;
  471. }
  472. use_next_shape(self, random)?;
  473. if check_for_game_over(self) {
  474. play_sound(SoundEffect::GameOver);
  475. log(&format!("i_count: {}", self.i_count));
  476. log(&format!("j_count: {}", self.j_count));
  477. log(&format!("l_count: {}", self.l_count));
  478. log(&format!("o_count: {}", self.o_count));
  479. log(&format!("s_count: {}", self.s_count));
  480. log(&format!("t_count: {}", self.t_count));
  481. log(&format!("z_count: {}", self.z_count));
  482. self.is_game_over = true;
  483. }
  484. }
  485. }
  486. if let Some(shape) = self.shapes.get(&self.piece.shape_kind) {
  487. add_shape_to_board(
  488. &mut self.board,
  489. shape,
  490. &self.piece.pos,
  491. &self.piece.angle,
  492. &self.piece.colour,
  493. );
  494. }
  495. // update the colour of all the blocks that aren't occupied
  496. update_block_colours(&mut self.board);
  497. Ok(true)
  498. }
  499. }
  500. fn play_sound(sound_effect: SoundEffect) {
  501. audio_play(sound_effect as i32);
  502. }
  503. fn update_score(game: &mut Game, num_lines: i32) {
  504. game.lines += num_lines;
  505. game.score += score_for_current_move(game, num_lines);
  506. game.tetris = num_lines == 4;
  507. if (game.lines % 10) == 0 {
  508. game.level += 1;
  509. }
  510. }
  511. fn score_for_current_move(game: &Game, num_lines: i32) -> i32 {
  512. match num_lines {
  513. 1 => 100,
  514. 2 => 300,
  515. 3 => 500,
  516. 4 => if game.tetris {
  517. 1200
  518. } else {
  519. 800
  520. },
  521. _ => 0,
  522. }
  523. }
  524. fn check_for_game_over(game: &mut Game) -> bool {
  525. // new pieces are spawned at the highest valid point,
  526. // if any of those blocks are already occupied then it's game over
  527. //
  528. if let Some(shape) = game.shapes.get(&game.piece.shape_kind) {
  529. return is_colliding(&game.board, shape, &game.piece.pos, &game.piece.angle);
  530. }
  531. false
  532. }
  533. fn use_next_shape(game: &mut Game, random: f32) -> error::Result<()> {
  534. game.piece.shape_kind = game.next_piece_shape.clone();
  535. if let Some(shape) = game.shapes.get(&game.piece.shape_kind) {
  536. game.piece.pos.x = ((game.board.width as i32) - shape.aabb_width) / 2;
  537. game.piece.pos.y = (game.board.height as i32) - shape.real_height;
  538. game.piece.angle = PieceAngle::R0;
  539. game.piece.colour = game.next_piece_colour;
  540. }
  541. match game.piece.shape_kind {
  542. ShapeKind::I => game.i_count += 1,
  543. ShapeKind::J => game.j_count += 1,
  544. ShapeKind::L => game.l_count += 1,
  545. ShapeKind::O => game.o_count += 1,
  546. ShapeKind::S => game.s_count += 1,
  547. ShapeKind::T => game.t_count += 1,
  548. ShapeKind::Z => game.z_count += 1,
  549. }
  550. game.impact_sfx_played = false;
  551. game.next_piece_shape = get_random_shape(random)?;
  552. game.next_piece_colour = get_colour_from_hsl(
  553. (random * 360.0) as f64,
  554. game.colour_saturation,
  555. game.colour_lightness,
  556. 1.0
  557. )?;
  558. Ok(())
  559. }
  560. fn get_colour_from_hsl(hue: f64, saturation: f64, lightness: f64, alpha: f64) -> error::Result<Col> {
  561. let hsluv = Colour::HSLuv(hue, saturation, lightness, alpha);
  562. col_from_colour(&hsluv)
  563. }
  564. fn col_from_colour(colour: &Colour) -> error::Result<Col> {
  565. if let Ok(rgb) = colour.clone_as(Format::RGB) {
  566. match rgb {
  567. Colour::RGB(r, g, b, a) => Ok(Col::new(r as f32, g as f32, b as f32, a as f32)),
  568. _ => Err(error::TetrisError::InvalidColourFormat),
  569. }
  570. } else {
  571. Err(error::TetrisError::InvalidColourFormat)
  572. }
  573. }
  574. fn get_random_shape(random: f32) -> error::Result<ShapeKind> {
  575. let irandom = (random * 7000.0) as i32;
  576. match irandom % 7 {
  577. 0 => Ok(ShapeKind::I),
  578. 1 => Ok(ShapeKind::J),
  579. 2 => Ok(ShapeKind::L),
  580. 3 => Ok(ShapeKind::O),
  581. 4 => Ok(ShapeKind::S),
  582. 5 => Ok(ShapeKind::T),
  583. 6 => Ok(ShapeKind::Z),
  584. _ => Err(error::TetrisError::InvalidRandomShapeIndex),
  585. }
  586. }
  587. fn is_time_to_lower_piece(game: &mut Game, delta: f32) -> bool {
  588. game.time_to_next_lowering -= delta;
  589. if game.time_to_next_lowering < 0.0 {
  590. game.time_to_next_lowering = get_time_to_next_lowering(game.level);
  591. true
  592. } else {
  593. false
  594. }
  595. }
  596. // if possible vertically drop the piece by one row, otherwise return false
  597. //
  598. fn lower_piece(board: &Board, shape: &Shape, piece: &mut Piece) -> bool {
  599. let new_position = BoardPos {
  600. x: piece.pos.x,
  601. y: piece.pos.y - 1,
  602. };
  603. if is_allowed(board, shape, &new_position, &piece.angle) {
  604. piece.pos = new_position;
  605. true
  606. } else {
  607. false
  608. }
  609. }
  610. fn apply_user_input(game: &mut Game, controller: &Controller, delta: f32) {
  611. if let Some(shape) = game.shapes.get(&game.piece.shape_kind) {
  612. if can_apply_input(
  613. &mut game.left_cooldown,
  614. controller.left,
  615. game.default_cooldown,
  616. delta,
  617. ) {
  618. let new_position = BoardPos {
  619. x: game.piece.pos.x - 1,
  620. y: game.piece.pos.y,
  621. };
  622. if is_allowed(&game.board, shape, &new_position, &game.piece.angle) {
  623. // log(&format!("old position: {:?}, new position {:?}", game.piece.pos, new_position));
  624. game.piece.pos = new_position;
  625. }
  626. }
  627. if can_apply_input(
  628. &mut game.right_cooldown,
  629. controller.right,
  630. game.default_cooldown,
  631. delta,
  632. ) {
  633. let new_position = BoardPos {
  634. x: game.piece.pos.x + 1,
  635. y: game.piece.pos.y,
  636. };
  637. if is_allowed(&game.board, shape, &new_position, &game.piece.angle) {
  638. game.piece.pos = new_position;
  639. }
  640. }
  641. if controller.just_pressed(ControllerButton::Up) {
  642. let new_angle;
  643. if shape.fully_rotate {
  644. new_angle = match game.piece.angle {
  645. PieceAngle::R0 => PieceAngle::R270,
  646. PieceAngle::R270 => PieceAngle::R180,
  647. PieceAngle::R180 => PieceAngle::R90,
  648. PieceAngle::R90 => PieceAngle::R0,
  649. };
  650. } else {
  651. new_angle = match game.piece.angle {
  652. PieceAngle::R0 => PieceAngle::R90,
  653. _ => PieceAngle::R0,
  654. };
  655. }
  656. if is_allowed(&game.board, shape, &game.piece.pos, &new_angle) {
  657. game.piece.angle = new_angle;
  658. }
  659. }
  660. if controller.just_pressed(ControllerButton::Down) {
  661. game.piece_is_dropping = true;
  662. }
  663. if can_apply_input(
  664. &mut game.a_cooldown,
  665. controller.a,
  666. game.default_cooldown,
  667. delta,
  668. ) {
  669. let new_angle;
  670. if shape.fully_rotate {
  671. new_angle = match game.piece.angle {
  672. PieceAngle::R0 => PieceAngle::R90,
  673. PieceAngle::R90 => PieceAngle::R180,
  674. PieceAngle::R180 => PieceAngle::R270,
  675. PieceAngle::R270 => PieceAngle::R0,
  676. };
  677. } else {
  678. new_angle = match game.piece.angle {
  679. PieceAngle::R0 => PieceAngle::R90,
  680. _ => PieceAngle::R0,
  681. };
  682. }
  683. if is_allowed(&game.board, shape, &game.piece.pos, &new_angle) {
  684. game.piece.angle = new_angle;
  685. }
  686. }
  687. if can_apply_input(
  688. &mut game.b_cooldown,
  689. controller.b,
  690. game.default_cooldown,
  691. delta,
  692. ) {
  693. let new_angle;
  694. if shape.fully_rotate {
  695. new_angle = match game.piece.angle {
  696. PieceAngle::R0 => PieceAngle::R270,
  697. PieceAngle::R270 => PieceAngle::R180,
  698. PieceAngle::R180 => PieceAngle::R90,
  699. PieceAngle::R90 => PieceAngle::R0,
  700. };
  701. } else {
  702. new_angle = match game.piece.angle {
  703. PieceAngle::R0 => PieceAngle::R90,
  704. _ => PieceAngle::R0,
  705. };
  706. }
  707. if is_allowed(&game.board, shape, &game.piece.pos, &new_angle) {
  708. game.piece.angle = new_angle;
  709. }
  710. }
  711. }
  712. }
  713. // apply a cooldown effect on input that remains pressed, this way it is only
  714. // acted upon every 'default_cooldown' ms
  715. fn can_apply_input(cooldown: &mut f32, pressed: bool, default_cooldown: f32, delta: f32) -> bool {
  716. if pressed {
  717. if cooldown <= &mut 0.0 {
  718. *cooldown = default_cooldown;
  719. return true;
  720. } else {
  721. *cooldown -= delta;
  722. }
  723. } else {
  724. *cooldown = 0.0;
  725. }
  726. false
  727. }
  728. fn get_time_to_next_lowering(level: i32) -> f32 {
  729. let delay = 1000 - (level * 100);
  730. let smallest_delay = 100;
  731. if delay <= smallest_delay {
  732. smallest_delay as f32
  733. } else {
  734. delay as f32
  735. }
  736. }
  737. fn is_allowed(board: &Board, shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> bool {
  738. !is_outside(board, shape, pos, angle) && !is_colliding(board, shape, pos, angle)
  739. }
  740. fn is_outside(board: &Board, shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> bool {
  741. let w = board.width as i32;
  742. get_board_positions(shape, pos, angle)
  743. .iter()
  744. .any(|p| p.x < 0 || p.x >= w || p.y < 0)
  745. }
  746. fn is_colliding(board: &Board, shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> bool {
  747. get_board_positions(shape, pos, angle)
  748. .iter()
  749. .filter(|p| is_valid_board_position(board, p))
  750. .any(|p| board.board[p.y as usize][p.x as usize].occupied)
  751. }
  752. fn remove_complete_lines(board: &mut Board) -> error::Result<i32> {
  753. let mut offset = 0;
  754. for y in 0..board.height {
  755. if board.is_line_filled(y - offset) {
  756. board.remove_line(y - offset)?;
  757. offset += 1;
  758. }
  759. }
  760. Ok(offset as i32)
  761. }
  762. fn add_shape_to_board(
  763. board: &mut Board,
  764. shape: &Shape,
  765. pos: &BoardPos,
  766. angle: &PieceAngle,
  767. colour: &Col,
  768. ) {
  769. for pos in get_board_positions(shape, pos, angle) {
  770. if is_valid_board_position(board, &pos) {
  771. set_block(board, pos.x, pos.y, true, colour);
  772. }
  773. }
  774. }
  775. fn remove_shape_from_board(board: &mut Board, shape: &Shape, pos: &BoardPos, angle: &PieceAngle) {
  776. let inactive_colour = board.inactive_colour;
  777. for pos in get_board_positions(shape, &pos, &angle) {
  778. if is_valid_board_position(board, &pos) {
  779. set_block(board, pos.x, pos.y, false, &inactive_colour);
  780. }
  781. }
  782. }
  783. fn is_valid_board_position(board: &Board, pos: &BoardPos) -> bool {
  784. let w = board.width as i32;
  785. let h = board.height as i32;
  786. pos.x >= 0 && pos.x < w && pos.y >= 0 && pos.y < h
  787. }
  788. fn get_board_positions(shape: &Shape, pos: &BoardPos, angle: &PieceAngle) -> Vec<BoardPos> {
  789. let mut res: Vec<BoardPos> = Vec::with_capacity(4);
  790. let mut x: i32;
  791. let mut y: i32;
  792. for y_offset in 0..shape.aabb_height {
  793. for x_offset in 0..shape.aabb_width {
  794. if shape.form[y_offset as usize][x_offset as usize] {
  795. match angle {
  796. PieceAngle::R0 => {
  797. x = x_offset;
  798. y = y_offset;
  799. }
  800. PieceAngle::R90 => {
  801. x = y_offset;
  802. y = shape.aabb_width - x_offset - 1;
  803. }
  804. PieceAngle::R180 => {
  805. x = shape.aabb_width - x_offset - 1;
  806. y = shape.aabb_height - y_offset - 1;
  807. }
  808. PieceAngle::R270 => {
  809. x = shape.aabb_height - y_offset - 1;
  810. y = x_offset;
  811. }
  812. }
  813. res.push(BoardPos {
  814. x: pos.x + x,
  815. y: pos.y + y,
  816. });
  817. }
  818. }
  819. }
  820. res
  821. }
  822. fn set_block(board: &mut Board, x: i32, y: i32, occupied: bool, colour: &Col) {
  823. board.board[y as usize][x as usize].occupied = occupied;
  824. if occupied {
  825. // set the colour straight away if it's occupied
  826. // (unoccupied blocks will fade out, see update_block_colours)
  827. board.board[y as usize][x as usize].colour = *colour;
  828. }
  829. }
  830. fn update_block_colours(board: &mut Board) {
  831. for mut row in &mut board.board {
  832. for mut block in row {
  833. if !block.occupied {
  834. block.colour = move_col_closer(
  835. &block.colour,
  836. &board.inactive_colour,
  837. board.crt_update_speed,
  838. );
  839. }
  840. }
  841. }
  842. }
  843. fn move_col_closer(src: &Col, dest: &Col, delta: f32) -> Col {
  844. Col::new(
  845. move_f32_closer(src.r, dest.r, delta),
  846. move_f32_closer(src.g, dest.g, delta),
  847. move_f32_closer(src.b, dest.b, delta),
  848. move_f32_closer(src.a, dest.a, delta),
  849. )
  850. }
  851. fn move_f32_closer(src: f32, dest: f32, delta: f32) -> f32 {
  852. let mut res = src;
  853. if src < dest {
  854. res = src + delta;
  855. if res > dest {
  856. res = dest;
  857. }
  858. } else if src > dest {
  859. res = src - delta;
  860. if res < dest {
  861. res = dest;
  862. }
  863. }
  864. res
  865. }
  866. fn render_board(geometry: &mut Geometry, board: &Board, board_offset: &Block2D) {
  867. let mut x = 0;
  868. let mut y = 0;
  869. for row in &board.board {
  870. for block in row {
  871. geometry.push_sprite(
  872. Sprite::Block,
  873. block.colour,
  874. Block2D {
  875. x: x + board_offset.x,
  876. y: y + board_offset.y,
  877. },
  878. block.offset,
  879. );
  880. x += 1;
  881. }
  882. x = 0;
  883. y += 1;
  884. }
  885. }
  886. fn render_score(geometry: &mut Geometry, score: i32, offset: Block2D, colour: Col) {
  887. geometry.push_text("SCORE", offset, colour);
  888. geometry.push_text(
  889. &score.to_string(),
  890. Block2D {
  891. x: offset.x,
  892. y: offset.y - 1,
  893. },
  894. colour,
  895. );
  896. }
  897. fn render_level(geometry: &mut Geometry, level: i32, offset: Block2D, colour: Col) {
  898. geometry.push_text("LEVEL", offset, colour);
  899. geometry.push_text(
  900. &level.to_string(),
  901. Block2D {
  902. x: offset.x,
  903. y: offset.y - 1,
  904. },
  905. colour,
  906. );
  907. }
  908. fn render_lines(geometry: &mut Geometry, lines: i32, offset: Block2D, colour: Col) {
  909. geometry.push_text("LINES", offset, colour);
  910. geometry.push_text(
  911. &lines.to_string(),
  912. Block2D {
  913. x: offset.x,
  914. y: offset.y - 1,
  915. },
  916. colour,
  917. );
  918. }
  919. fn render_next_shape(geometry: &mut Geometry, shape: &Shape, offset: Block2D, colour: Col) {
  920. let board_positions = get_board_positions(shape, &BoardPos { x: 0, y: 0 }, &PieceAngle::R0);
  921. for pos in board_positions {
  922. geometry.push_sprite(
  923. Sprite::Block,
  924. colour,
  925. Block2D {
  926. x: pos.x + offset.x,
  927. y: pos.y + offset.y,
  928. },
  929. Vec2D { x: 0.0, y: 0.0 },
  930. );
  931. }
  932. }
  933. fn update_pause_menu(game: &mut Game, controller: &Controller, random: f32) -> bool {
  934. let mut update = false;
  935. if controller.just_pressed(ControllerButton::Down) {
  936. game.menu_active_item += 1;
  937. update = true;
  938. }
  939. if controller.just_pressed(ControllerButton::Up) {
  940. game.menu_active_item -= 1;
  941. update = true;
  942. }
  943. game.menu_active_item = wrap(game.menu_active_item, 0, game.menu_num_items - 1);
  944. // volume
  945. if game.menu_active_item == 1 {
  946. let mut volume_changed = false;
  947. if controller.just_pressed(ControllerButton::Left) {
  948. volume_changed = true;
  949. game.menu_volume -= 1;
  950. }
  951. if controller.just_pressed(ControllerButton::Right) {
  952. volume_changed = true;
  953. game.menu_volume += 1;
  954. }
  955. if volume_changed {
  956. game.menu_volume = clamp(game.menu_volume, 0, game.menu_max_volume);
  957. update = true;
  958. update_audio_volume(game);
  959. }
  960. }
  961. // crt update speed
  962. if game.menu_active_item == 2 {
  963. let mut crt_update_speed_changed = false;
  964. if controller.just_pressed(ControllerButton::Left) {
  965. crt_update_speed_changed = true;
  966. game.menu_crt_update_speed -= 1;
  967. }
  968. if controller.just_pressed(ControllerButton::Right) {
  969. crt_update_speed_changed = true;
  970. game.menu_crt_update_speed += 1;
  971. }
  972. if crt_update_speed_changed {
  973. game.menu_crt_update_speed = clamp(game.menu_crt_update_speed, 0, game.menu_max_crt_update_speed);
  974. update = true;
  975. update_crt_update_speed(game);
  976. }
  977. }
  978. // curvature
  979. if game.menu_active_item == 3 {
  980. if controller.just_pressed(ControllerButton::Left) {
  981. game.menu_curvature -= 1;
  982. update = true;
  983. }
  984. if controller.just_pressed(ControllerButton::Right) {
  985. game.menu_curvature += 1;
  986. update = true;
  987. }
  988. game.menu_curvature = clamp(game.menu_curvature, 0, game.menu_max_curvature);
  989. }
  990. if game.menu_active_item == 4 {
  991. if controller.just_pressed(ControllerButton::Start) {
  992. let _ = game.restart(random);
  993. update = true;
  994. }
  995. }
  996. if update {
  997. play_sound(SoundEffect::MenuMove);
  998. }
  999. update
  1000. }
  1001. fn update_audio_volume(game: &Game) {
  1002. let volume = game.menu_volume as f32 / game.menu_max_volume as f32;
  1003. audio_volume(volume);
  1004. }
  1005. fn update_crt_update_speed(game: &mut Game) {
  1006. if game.menu_crt_update_speed == game.menu_max_crt_update_speed {
  1007. // switch off any ghosting
  1008. game.board.set_crt_update_speed(1.0);
  1009. } else {
  1010. // 0.00 -> ~1.0
  1011. // 0.01 -> 0.2
  1012. let percent = game.menu_crt_update_speed as f32 / game.menu_max_crt_update_speed as f32;
  1013. let new_crt_update_speed = (percent * 0.2) + 0.01;
  1014. game.board.set_crt_update_speed(new_crt_update_speed);
  1015. }
  1016. }
  1017. fn wrap(val: i32, min: i32, max: i32) -> i32 {
  1018. if val < min {
  1019. return max
  1020. } else if val > max {
  1021. return min
  1022. }
  1023. val
  1024. }
  1025. fn clamp(val: i32, min: i32, max: i32) -> i32 {
  1026. if val < min {
  1027. return min
  1028. } else if val > max {
  1029. return max
  1030. }
  1031. val
  1032. }
  1033. fn render_pause_menu(game: &Game, geometry: &mut Geometry, centre: Block2D, text_colour: Col, background_colour: Col) {
  1034. let resume = build_menu_item(game, 0, "RESUME");
  1035. let vol = build_menu_item_slider(game, 1, "Volume ", game.menu_max_volume, game.menu_volume);
  1036. let crt_update_speed = build_menu_item_slider(game, 2, "CRT Refresh ", game.menu_max_crt_update_speed, game.menu_crt_update_speed);
  1037. let curvature = build_menu_item_slider(game, 3, "Curvature ", game.menu_max_curvature, game.menu_curvature);
  1038. let restart = build_menu_item(game, 4, "Restart");
  1039. // remember to update Game::menu_num_items when adding another menu option
  1040. let lines: Vec<&str> = vec![&resume[..],
  1041. &vol[..],
  1042. &crt_update_speed[..],
  1043. &curvature[..],
  1044. &restart[..]];
  1045. geometry.push_centred_boxed_multiline(&lines, centre, text_colour, background_colour);
  1046. }
  1047. fn build_menu_item(game: &Game, item_order: i32, label: &str) -> String {
  1048. let mut out = menu_item_base(game, item_order);
  1049. out.push_str(label);
  1050. out
  1051. }
  1052. fn build_menu_item_slider(game: &Game, item_order: i32, label: &str, max: i32, val: i32) -> String {
  1053. let mut out = menu_item_base(game, item_order);
  1054. let slider = build_menu_item_slider_bar(max, val);
  1055. out.push_str(label);
  1056. out.push_str(&slider[..]);
  1057. out
  1058. }
  1059. fn menu_item_base(game: &Game, item_order: i32) -> String {
  1060. let mut out = "".to_owned();
  1061. if game.menu_active_item == item_order {
  1062. out.push_str("> ");
  1063. } else {
  1064. out.push_str(" ");
  1065. }
  1066. out
  1067. }
  1068. fn build_menu_item_slider_bar(max: i32, value: i32) -> String {
  1069. let mut out = "".to_owned();
  1070. out.push_str("[");
  1071. for _ in 0..value {
  1072. out.push_str("-");
  1073. }
  1074. out.push_str("*");
  1075. for _ in value..max {
  1076. out.push_str("-");
  1077. }
  1078. out.push_str("]");
  1079. out
  1080. }
  1081. fn define_shapes() -> HashMap<ShapeKind, Shape> {
  1082. let mut res = HashMap::new();
  1083. // origin is in the bottom left
  1084. // indices are in [y][x] order
  1085. //
  1086. fn i_shape() -> Shape {
  1087. //
  1088. // -----
  1089. // __X__
  1090. // __X__
  1091. // __X__
  1092. // __X__
  1093. //
  1094. let mut v = vec![vec![false; 5]; 5];
  1095. v[0][2] = true;
  1096. v[1][2] = true;
  1097. v[2][2] = true;
  1098. v[3][2] = true;
  1099. Shape {
  1100. aabb_width: 5,
  1101. aabb_height: 5,
  1102. real_height: 4,
  1103. fully_rotate: false,
  1104. form: v,
  1105. }
  1106. }
  1107. fn o_shape() -> Shape {
  1108. //
  1109. // XX
  1110. // XX
  1111. //
  1112. let v = vec![vec![true; 2]; 2];
  1113. Shape {
  1114. aabb_width: 2,
  1115. aabb_height: 2,
  1116. real_height: 2,
  1117. fully_rotate: true,
  1118. form: v,
  1119. }
  1120. }
  1121. fn l_shape() -> Shape {
  1122. //
  1123. // _X_
  1124. // _X_
  1125. // _XX
  1126. //
  1127. let mut v = vec![vec![true; 3]; 3];
  1128. v[0][0] = false;
  1129. v[1][0] = false;
  1130. v[2][0] = false;
  1131. v[1][2] = false;
  1132. v[2][2] = false;
  1133. Shape {
  1134. aabb_width: 3,
  1135. aabb_height: 3,
  1136. real_height: 3,
  1137. fully_rotate: true,
  1138. form: v,
  1139. }
  1140. }
  1141. fn j_shape() -> Shape {
  1142. //
  1143. // _X_
  1144. // _X_
  1145. // XX_
  1146. //
  1147. let mut v = vec![vec![true; 3]; 3];
  1148. v[0][2] = false;
  1149. v[1][2] = false;
  1150. v[2][2] = false;
  1151. v[1][0] = false;
  1152. v[2][0] = false;
  1153. Shape {
  1154. aabb_width: 3,
  1155. aabb_height: 3,
  1156. real_height: 3,
  1157. fully_rotate: true,
  1158. form: v,
  1159. }
  1160. }
  1161. fn s_shape() -> Shape {
  1162. //
  1163. // ___
  1164. // _XX
  1165. // XX_
  1166. //
  1167. let mut v = vec![vec![true; 3]; 3];
  1168. v[0][2] = false;
  1169. v[1][0] = false;
  1170. v[2][0] = false;
  1171. v[2][1] = false;
  1172. v[2][2] = false;
  1173. Shape {
  1174. aabb_width: 3,
  1175. aabb_height: 3,
  1176. real_height: 2,
  1177. fully_rotate: false,
  1178. form: v,
  1179. }
  1180. }
  1181. fn t_shape() -> Shape {
  1182. //
  1183. // ___
  1184. // XXX
  1185. // _X_
  1186. //
  1187. let mut v = vec![vec![true; 3]; 3];
  1188. v[0][0] = false;
  1189. v[0][2] = false;
  1190. v[2][0] = false;
  1191. v[2][1] = false;
  1192. v[2][2] = false;
  1193. Shape {
  1194. aabb_width: 3,
  1195. aabb_height: 3,
  1196. real_height: 2,
  1197. fully_rotate: true,
  1198. form: v,
  1199. }
  1200. }
  1201. fn z_shape() -> Shape {
  1202. //
  1203. // ___
  1204. // XX_
  1205. // _XX
  1206. //
  1207. let mut v = vec![vec![true; 3]; 3];
  1208. v[0][0] = false;
  1209. v[1][2] = false;
  1210. v[2][0] = false;
  1211. v[2][1] = false;
  1212. v[2][2] = false;
  1213. Shape {
  1214. aabb_width: 3,
  1215. aabb_height: 3,
  1216. real_height: 2,
  1217. fully_rotate: false,
  1218. form: v,
  1219. }
  1220. }
  1221. res.insert(ShapeKind::I, i_shape());
  1222. res.insert(ShapeKind::J, j_shape());
  1223. res.insert(ShapeKind::L, l_shape());
  1224. res.insert(ShapeKind::O, o_shape());
  1225. res.insert(ShapeKind::S, s_shape());
  1226. res.insert(ShapeKind::T, t_shape());
  1227. res.insert(ShapeKind::Z, z_shape());
  1228. res
  1229. }