|
@@ -0,0 +1,153 @@
|
|
|
+import { useEffect, useRef } from "react";
|
|
|
+import styles from "./GuessPane.module.css";
|
|
|
+
|
|
|
+const tileSize = 15;
|
|
|
+const tiles = 40;
|
|
|
+const rawSize = tileSize * tiles;
|
|
|
+
|
|
|
+const drawTile = (ctx, x, y, color) => {
|
|
|
+ ctx.fillStyle = color;
|
|
|
+ ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
|
|
|
+};
|
|
|
+
|
|
|
+const snakeGame = () => {
|
|
|
+ const snakeHead = { x: null, y: null, vx: null, vy: null };
|
|
|
+ let snakeTail = [];
|
|
|
+ const apple = { x: null, y: null };
|
|
|
+
|
|
|
+ const randomApple = () => {
|
|
|
+ apple.x = Math.floor(Math.random() * tiles);
|
|
|
+ apple.y = Math.floor(Math.random() * tiles);
|
|
|
+ };
|
|
|
+
|
|
|
+ const reset = () => {
|
|
|
+ snakeHead.x = Math.floor(tiles / 2);
|
|
|
+ snakeHead.y = snakeHead.x;
|
|
|
+ snakeHead.vx = 0;
|
|
|
+ snakeHead.vy = -1;
|
|
|
+ snakeTail = [{ ...snakeHead }];
|
|
|
+ randomApple();
|
|
|
+ };
|
|
|
+
|
|
|
+ reset();
|
|
|
+
|
|
|
+ const onKey = ({ code }) => {
|
|
|
+ switch (code) {
|
|
|
+ case "ArrowUp":
|
|
|
+ snakeHead.vx = 0;
|
|
|
+ snakeHead.vy = -1;
|
|
|
+ break;
|
|
|
+ case "ArrowDown":
|
|
|
+ snakeHead.vx = 0;
|
|
|
+ snakeHead.vy = 1;
|
|
|
+ break;
|
|
|
+ case "ArrowLeft":
|
|
|
+ snakeHead.vx = -1;
|
|
|
+ snakeHead.vy = 0;
|
|
|
+ break;
|
|
|
+ case "ArrowRight":
|
|
|
+ snakeHead.vx = 1;
|
|
|
+ snakeHead.vy = 0;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let interval;
|
|
|
+
|
|
|
+ const start = (ctx, finishScore, onFinish) => {
|
|
|
+ document.addEventListener("keydown", onKey);
|
|
|
+ interval = setInterval(() => {
|
|
|
+ snakeHead.x += snakeHead.vx;
|
|
|
+ snakeHead.y += snakeHead.vy;
|
|
|
+
|
|
|
+ if (
|
|
|
+ snakeHead.x < 0 ||
|
|
|
+ snakeHead.x >= tiles ||
|
|
|
+ snakeHead.y < 0 ||
|
|
|
+ snakeHead.y >= tiles ||
|
|
|
+ snakeTail.find(({ x, y }) => x === snakeHead.x && y === snakeHead.y)
|
|
|
+ ) {
|
|
|
+ reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ snakeTail.push({ ...snakeHead });
|
|
|
+
|
|
|
+ if (apple.x === snakeHead.x && apple.y === snakeHead.y) {
|
|
|
+ randomApple();
|
|
|
+ } else {
|
|
|
+ snakeTail.shift();
|
|
|
+ }
|
|
|
+
|
|
|
+ const finished = snakeTail.length >= finishScore;
|
|
|
+
|
|
|
+ ctx.fillStyle = "black";
|
|
|
+ ctx.fillRect(0, 0, rawSize, rawSize);
|
|
|
+
|
|
|
+ snakeTail.forEach(({ x, y }) => {
|
|
|
+ drawTile(ctx, x, y, "green");
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!finished) {
|
|
|
+ drawTile(ctx, apple.x, apple.y, "red");
|
|
|
+ }
|
|
|
+ ctx.font = "24px serif";
|
|
|
+ ctx.fillStyle = "#fff";
|
|
|
+ ctx.textAlign = "left";
|
|
|
+ ctx.textBaseline = "top";
|
|
|
+ ctx.fillText(`${snakeTail.length}/${finishScore}`, tileSize, tileSize);
|
|
|
+
|
|
|
+ if (finished) {
|
|
|
+ clearInterval(interval);
|
|
|
+ interval = null;
|
|
|
+
|
|
|
+ ctx.font = "48px serif";
|
|
|
+ ctx.textAlign = "center";
|
|
|
+ ctx.textBaseline = "middle";
|
|
|
+ ctx.fillText("Done!", rawSize / 2, rawSize / 2);
|
|
|
+
|
|
|
+ setTimeout(onFinish, 1000);
|
|
|
+ }
|
|
|
+ }, 50);
|
|
|
+ };
|
|
|
+
|
|
|
+ const stop = () => {
|
|
|
+ document.removeEventListener("keydown", onKey);
|
|
|
+ if (interval) {
|
|
|
+ clearInterval(interval);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return { start, stop, onKey };
|
|
|
+};
|
|
|
+
|
|
|
+const GunGame = ({ points, onFinish }) => {
|
|
|
+ const ref = useRef();
|
|
|
+ useEffect(() => {
|
|
|
+ const { start, stop } = snakeGame();
|
|
|
+ start(ref.current?.getContext("2d"), 10, onFinish);
|
|
|
+ return () => {
|
|
|
+ stop();
|
|
|
+ };
|
|
|
+ }, [onFinish]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={styles.gungame}>
|
|
|
+ <div className={styles.gungame_panel}>
|
|
|
+ <div className={styles.gungame_label}>
|
|
|
+ <div>Not good enough! That guess only got {points} points!</div>
|
|
|
+ <div>Finish this snake game to try again!</div>
|
|
|
+ </div>
|
|
|
+ <canvas
|
|
|
+ className={styles.gungame_canvas}
|
|
|
+ ref={ref}
|
|
|
+ width={rawSize}
|
|
|
+ height={rawSize}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default GunGame;
|