|
@@ -1,10 +1,35 @@
|
|
|
import { useEffect, useRef } from "react";
|
|
|
import styles from "./GuessPane.module.css";
|
|
|
|
|
|
-const tileSize = 15;
|
|
|
-const tiles = 40;
|
|
|
+const tileSize = 20;
|
|
|
+const tiles = 32;
|
|
|
const rawSize = tileSize * tiles;
|
|
|
|
|
|
+// A standard normal can be approximated by 12 summed uniform random values, shifted and rescaled
|
|
|
+// https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution#Approximating_a_Normal_distribution
|
|
|
+// This can then be reshifted to a better normal distribution for apple locations
|
|
|
+// https://en.wikipedia.org/wiki/Normal_distribution#General_normal_distribution
|
|
|
+// Specifically, desired mean is tiles / 2, with a std deviation of tiles / 4
|
|
|
+// (tiles / 2) + (tiles / 4) * (sum(U_(0, 1), 12) - 6)
|
|
|
+// = (tiles / 2) + ((tiles / 4) * sum(U_(0, 1), 12) - (tiles * 3 / 2))
|
|
|
+// = (tiles / 4) * sum(U_(0, 1), 12) - tiles
|
|
|
+// Then, take round and clamp that to [0, tiles - 1] (which does technically change the dist a bit)
|
|
|
+const tileFactor = tiles / 4;
|
|
|
+const gaussianRandomTile = () => {
|
|
|
+ const rands = Array.from({ length: 12 }, () => Math.random()).reduce(
|
|
|
+ (x, y) => x + y
|
|
|
+ );
|
|
|
+ const rescaled = Math.round(tileFactor * rands - tiles);
|
|
|
+ return Math.max(0, Math.min(tiles - 1, rescaled));
|
|
|
+};
|
|
|
+
|
|
|
+const OPPOSITE_KEY = {
|
|
|
+ ArrowUp: "ArrowDown",
|
|
|
+ ArrowDown: "ArrowUp",
|
|
|
+ ArrowLeft: "ArrowRight",
|
|
|
+ ArrowRight: "ArrowLeft",
|
|
|
+};
|
|
|
+
|
|
|
const drawTile = (ctx, x, y, color) => {
|
|
|
ctx.fillStyle = color;
|
|
|
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
|
|
@@ -16,8 +41,8 @@ const snakeGame = () => {
|
|
|
const apple = { x: null, y: null };
|
|
|
|
|
|
const randomApple = () => {
|
|
|
- apple.x = Math.floor(Math.random() * tiles);
|
|
|
- apple.y = Math.floor(Math.random() * tiles);
|
|
|
+ apple.x = gaussianRandomTile();
|
|
|
+ apple.y = gaussianRandomTile();
|
|
|
};
|
|
|
|
|
|
const reset = () => {
|
|
@@ -31,26 +56,11 @@ const snakeGame = () => {
|
|
|
|
|
|
reset();
|
|
|
|
|
|
+ let lastKey;
|
|
|
+
|
|
|
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;
|
|
|
+ if (snakeTail.length <= 1 || OPPOSITE_KEY[lastKey] !== code) {
|
|
|
+ lastKey = code;
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -59,6 +69,27 @@ const snakeGame = () => {
|
|
|
const start = (ctx, finishScore, onFinish) => {
|
|
|
document.addEventListener("keydown", onKey);
|
|
|
interval = setInterval(() => {
|
|
|
+ switch (lastKey) {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
snakeHead.x += snakeHead.vx;
|
|
|
snakeHead.y += snakeHead.vy;
|
|
|
|
|
@@ -107,9 +138,9 @@ const snakeGame = () => {
|
|
|
ctx.textBaseline = "middle";
|
|
|
ctx.fillText("Done!", rawSize / 2, rawSize / 2);
|
|
|
|
|
|
- setTimeout(onFinish, 1000);
|
|
|
+ setTimeout(onFinish, 500);
|
|
|
}
|
|
|
- }, 50);
|
|
|
+ }, 60);
|
|
|
};
|
|
|
|
|
|
const stop = () => {
|
|
@@ -123,10 +154,10 @@ const snakeGame = () => {
|
|
|
};
|
|
|
|
|
|
const GunGame = ({ onFinish }) => {
|
|
|
- const ref = useRef();
|
|
|
+ const canvasRef = useRef();
|
|
|
useEffect(() => {
|
|
|
const { start, stop } = snakeGame();
|
|
|
- start(ref.current?.getContext("2d"), 10, onFinish);
|
|
|
+ start(canvasRef.current?.getContext("2d"), 10, onFinish);
|
|
|
return () => {
|
|
|
stop();
|
|
|
};
|
|
@@ -140,7 +171,7 @@ const GunGame = ({ onFinish }) => {
|
|
|
</div>
|
|
|
<canvas
|
|
|
className={styles.gungame_canvas}
|
|
|
- ref={ref}
|
|
|
+ ref={canvasRef}
|
|
|
width={rawSize}
|
|
|
height={rawSize}
|
|
|
/>
|