GunGame.jsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { useEffect, useRef } from "react";
  2. import styles from "./GuessPane.module.css";
  3. const tileSize = 15;
  4. const tiles = 40;
  5. const rawSize = tileSize * tiles;
  6. const drawTile = (ctx, x, y, color) => {
  7. ctx.fillStyle = color;
  8. ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
  9. };
  10. const snakeGame = () => {
  11. const snakeHead = { x: null, y: null, vx: null, vy: null };
  12. let snakeTail = [];
  13. const apple = { x: null, y: null };
  14. const randomApple = () => {
  15. apple.x = Math.floor(Math.random() * tiles);
  16. apple.y = Math.floor(Math.random() * tiles);
  17. };
  18. const reset = () => {
  19. snakeHead.x = Math.floor(tiles / 2);
  20. snakeHead.y = snakeHead.x;
  21. snakeHead.vx = 0;
  22. snakeHead.vy = -1;
  23. snakeTail = [{ ...snakeHead }];
  24. randomApple();
  25. };
  26. reset();
  27. const onKey = ({ code }) => {
  28. switch (code) {
  29. case "ArrowUp":
  30. snakeHead.vx = 0;
  31. snakeHead.vy = -1;
  32. break;
  33. case "ArrowDown":
  34. snakeHead.vx = 0;
  35. snakeHead.vy = 1;
  36. break;
  37. case "ArrowLeft":
  38. snakeHead.vx = -1;
  39. snakeHead.vy = 0;
  40. break;
  41. case "ArrowRight":
  42. snakeHead.vx = 1;
  43. snakeHead.vy = 0;
  44. break;
  45. default:
  46. break;
  47. }
  48. };
  49. let interval;
  50. const start = (ctx, finishScore, onFinish) => {
  51. document.addEventListener("keydown", onKey);
  52. interval = setInterval(() => {
  53. snakeHead.x += snakeHead.vx;
  54. snakeHead.y += snakeHead.vy;
  55. if (
  56. snakeHead.x < 0 ||
  57. snakeHead.x >= tiles ||
  58. snakeHead.y < 0 ||
  59. snakeHead.y >= tiles ||
  60. snakeTail.find(({ x, y }) => x === snakeHead.x && y === snakeHead.y)
  61. ) {
  62. reset();
  63. }
  64. snakeTail.push({ ...snakeHead });
  65. if (apple.x === snakeHead.x && apple.y === snakeHead.y) {
  66. randomApple();
  67. } else {
  68. snakeTail.shift();
  69. }
  70. const finished = snakeTail.length >= finishScore;
  71. ctx.fillStyle = "black";
  72. ctx.fillRect(0, 0, rawSize, rawSize);
  73. snakeTail.forEach(({ x, y }) => {
  74. drawTile(ctx, x, y, "green");
  75. });
  76. if (!finished) {
  77. drawTile(ctx, apple.x, apple.y, "red");
  78. }
  79. ctx.font = "24px serif";
  80. ctx.fillStyle = "#fff";
  81. ctx.textAlign = "left";
  82. ctx.textBaseline = "top";
  83. ctx.fillText(`${snakeTail.length}/${finishScore}`, tileSize, tileSize);
  84. if (finished) {
  85. clearInterval(interval);
  86. interval = null;
  87. ctx.font = "48px serif";
  88. ctx.textAlign = "center";
  89. ctx.textBaseline = "middle";
  90. ctx.fillText("Done!", rawSize / 2, rawSize / 2);
  91. setTimeout(onFinish, 1000);
  92. }
  93. }, 50);
  94. };
  95. const stop = () => {
  96. document.removeEventListener("keydown", onKey);
  97. if (interval) {
  98. clearInterval(interval);
  99. }
  100. };
  101. return { start, stop, onKey };
  102. };
  103. const GunGame = ({ points, onFinish }) => {
  104. const ref = useRef();
  105. useEffect(() => {
  106. const { start, stop } = snakeGame();
  107. start(ref.current?.getContext("2d"), 10, onFinish);
  108. return () => {
  109. stop();
  110. };
  111. }, [onFinish]);
  112. return (
  113. <div className={styles.gungame}>
  114. <div className={styles.gungame_panel}>
  115. <div className={styles.gungame_label}>
  116. <div>Not good enough! That guess only got {points} points!</div>
  117. <div>Finish this snake game to try again!</div>
  118. </div>
  119. <canvas
  120. className={styles.gungame_canvas}
  121. ref={ref}
  122. width={rawSize}
  123. height={rawSize}
  124. />
  125. </div>
  126. </div>
  127. );
  128. };
  129. export default GunGame;