Browse Source

Improve snake game apple dist and control logic

Kirk Trombley 3 years ago
parent
commit
cbf45b0194

+ 3 - 2
client/src/components/screens/GamePanel/GuessPane/GuessPane.jsx

@@ -42,7 +42,7 @@ const GuessPane = () => {
     }
   };
 
-  const handleGateKeeping = async () => {
+  const handleGateKeeping = async ({ target }) => {
     const { score } = await checkScore(
       selectedPoint,
       targetPoint,
@@ -50,6 +50,7 @@ const GuessPane = () => {
       roundNum
     );
     if (score < 4000) {
+      target.blur();
       setGunGameBlock(true);
     } else {
       await handleSubmitGuess();
@@ -66,7 +67,7 @@ const GuessPane = () => {
           onClick={
             gameMode === GUN_GAME ? handleGateKeeping : handleSubmitGuess
           }
-          disabled={submitted || selectedPoint === null}
+          disabled={gunGameBlock || submitted || selectedPoint === null}
         >
           Submit Guess
         </button>

+ 59 - 28
client/src/components/screens/GamePanel/GuessPane/GunGame.jsx

@@ -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}
         />