diff react_games/src/current.tsx @ 44:0cfd7d9277b0

[ReactGame] 2048
author MrJuneJune <me@mrjunejune.com>
date Wed, 03 Dec 2025 18:34:22 -0800
parents fb9bcd3145cb
children 829623189a57
line wrap: on
line diff
--- a/react_games/src/current.tsx	Mon Dec 01 22:43:40 2025 -0800
+++ b/react_games/src/current.tsx	Wed Dec 03 18:34:22 2025 -0800
@@ -1,60 +1,265 @@
+import { CSSProperties, useEffect, useReducer, useState } from "react";
 import ReactDOM from "react-dom/client";
 
-const shaders = `
-struct VertexOut {
-  @builtin(position) position : vec4f,
-  @location(0) color : vec4f
+/**
+ * 2048
+ * 
+ * 4 X 4
+ */
+
+const MAX_WIDTH = 4;
+
+type Color = 'white' | 'orange' | 'yellow' | 'red';
+
+type Cell = {
+  value: number;
+  color: Color;
+}
+
+type Board = Cell[][];
+
+type GameState = "in_progress" | "lost" | "won";
+
+type Game = {
+  board: Board;
+  state: GameState;
+  steps: number;
+}
+
+type Command = "u" | "d" | "l" | "r";
+
+type GameAction = 
+  { type: "move", command: Command } | { type: "calculate" };
+
+
+interface GameStyle {
+  container: CSSProperties;
+  board: CSSProperties;
+  cell: (color: Color) => CSSProperties;
 }
 
-@vertex
-fn vertex_main(@location(0) position: vec4f,
-               @location(1) color: vec4f) -> VertexOut
-{
-  var output : VertexOut;
-  output.position = position;
-  output.color = color;
-  return output;
+const gameStyle: GameStyle = {
+  container: {
+    display: "flex",
+    flexDirection: "column",
+    justifyContent: "center",
+    alignItems: "center",
+    height: "100vh"
+  },
+  board: {
+    display: "grid",
+    gridTemplateColumns: "repeat(4, 50px)",
+    background: "#EEFFEE",
+  },
+  cell: (color: Color) => ({
+    display: "flex",
+    justifyContent: "center",
+    alignItems: "center",
+    aspectRatio: "1 / 1 ",
+    margin: "10px",
+    background: color,
+  })
+}
+
+function initializeBoard(): Board {
+  const board = Array.from({ length: MAX_WIDTH }, () =>
+    Array.from({ length: MAX_WIDTH }, (): Cell => ({ value: 0, color: 'orange' }))
+  );
+  let rowIndex: number;
+  let colIndex: number;
+  rowIndex = Math.floor(Math.random() * 4);
+  colIndex = Math.floor(Math.random() * 4);
+  board[rowIndex][colIndex].value = 2;
+  board[rowIndex-1][colIndex].value = 2;
+  return board;
 }
 
-@fragment
-fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
-{
-  return fragData.color;
+function initializeGame(): Game {
+  return {
+    board: initializeBoard(),
+    state: "in_progress",
+    steps: 0,
+  }
 }
-`;
+
+
+function handleMove(board: Board, command: Command): Board {
+  // Deep copy the board and initialize the merged status for the new board
+  const copiedBoard = board.map(row => 
+    row.map(cell => ({ ...cell, merged: false }))
+  );
+
+  let diff: { row: number, col: number };
+  let startRow: number, endRow: number, stepRow: number;
+  let startCol: number, endCol: number, stepCol: number;
+
+  const size = copiedBoard.length;
 
-async function init() {
-  if (!navigator.gpu) {
-    throw Error("WebGPU not supported.");
+  switch (command) {
+    case "u": 
+      diff = { row: -1, col: 0 };
+      startRow = 0; endRow = size; stepRow = 1;
+      startCol = 0; endCol = size; stepCol = 1;
+      break;
+    case "d": 
+      diff = { row: 1, col: 0 };
+      startRow = size - 1; endRow = -1; stepRow = -1;
+      startCol = 0; endCol = size; stepCol = 1;
+      break;
+    case "l":
+      diff = { row: 0, col: -1 };
+      startRow = 0; endRow = size; stepRow = 1;
+      startCol = 0; endCol = size; stepCol = 1;
+      break;
+    case "r":
+      diff = { row: 0, col: 1 };
+      startRow = 0; endRow = size; stepRow = 1;
+      startCol = size - 1; endCol = -1; stepCol = -1;
+      break;
   }
 
-  const adapter = await navigator.gpu.requestAdapter();
-  if (!adapter) {
-    throw Error("Couldn't request WebGPU adapter.");
+  for (let rowIndex = startRow; rowIndex !== endRow; rowIndex += stepRow) {
+    for (let colIndex = startCol; colIndex !== endCol; colIndex += stepCol) {
+      const currentCell = copiedBoard[rowIndex][colIndex];
+      
+      if (currentCell.value === 0) continue;
+
+      let r = rowIndex;
+      let c = colIndex;
+      let emptySlot: { r: number, c: number } = { r: rowIndex, c: colIndex };
+      let finalSlot: { r: number, c: number } = { r: rowIndex, c: colIndex };
+
+      while (true) {
+        r += diff.row;
+        c += diff.col;
+
+        if (r < 0 || r >= size || c < 0 || c >= size) {
+          finalSlot = emptySlot;
+          break;
+        }
+
+        const nextCell = copiedBoard[r][c];
+        
+        if (nextCell.value === 0) {
+          emptySlot = { r, c };
+          finalSlot = emptySlot;
+        } else if (nextCell.value === currentCell.value && !nextCell.merged) {
+          finalSlot = { r, c };
+          break;
+        } else {
+          finalSlot = emptySlot; 
+          break;
+        }
+      }
+
+      const targetCell = copiedBoard[finalSlot.r][finalSlot.c];
+
+      if (finalSlot.r === rowIndex && finalSlot.c === colIndex) {
+        continue;
+      }
+      
+      if (targetCell.value === currentCell.value && !targetCell.merged) {
+        targetCell.value *= 2;
+        targetCell.merged = true;
+        
+        copiedBoard[rowIndex][colIndex].value = 0;
+        
+      } else if (targetCell.value === 0) {
+        targetCell.value = currentCell.value;
+        copiedBoard[rowIndex][colIndex].value = 0;
+      }
+    }
+  }
+
+  return copiedBoard;
+}
+
+function addNewItemsToTheBoard(board: Board) { 
+  let randomRowIndex: number;
+  let randomColIndex: number;
+
+
+  let zeroPos = 0;
+  board.forEach((row) => {
+    row.forEach((cell) => {
+      if (cell.value === 0) {
+        zeroPos += 1;
+      }
+    })
+  })
+  if (zeroPos === 0) {
+    return;
   }
 
-  const device = await adapter.requestDevice();
-
-  const shaderModule = device.createShaderModule({
-    code: shaders,
-  });
+  let curr = 0;
+  const maxAddedValues = zeroPos < 2 ? 1 : (zeroPos / 2) | 0;
+  while (curr < maxAddedValues) {
+    randomRowIndex = Math.floor(Math.random() * board.length)
+    randomColIndex = Math.floor(Math.random() * board.length)
+    if (board[randomRowIndex][randomColIndex].value === 0)
+    {
+      board[randomRowIndex][randomColIndex].value = 2;
+      curr++;
+    }
+  }
+}
 
-  const canvas = document.querySelector("#gpuCanvas");
-  const context = canvas.getContext("webgpu");
-  
-  context.configure({
-    device,
-    format: navigator.gpu.getPreferredCanvasFormat(),
-    alphaMode: "premultiplied",
-  });
-
-  console.log(device)
+function gameDispatch(game: Game, gameAction: GameAction): Game {
+  switch(gameAction.type) {
+    case "move": {
+      const newBoard = handleMove(game.board, gameAction.command);
+      addNewItemsToTheBoard(newBoard);
+      return  {
+        ...game,
+        board: newBoard,
+      }
+    }
+    case "calculate": {
+      return  {
+        ...game,
+      }
+    }
+  }
 }
 
-void init();
+function Current() {
+  const [game, dispatch] = useReducer(gameDispatch, null, initializeGame);
 
-const Current = () => {
-  return (<>hello </>);
-};
+  useEffect(() => {
+    window.addEventListener("keyup", (e) => {
+      switch(e.key) {
+        case "ArrowDown": {
+          dispatch({ type: "move", command: "d" });
+          return;
+        }
+        case "ArrowUp": {
+          dispatch({ type: "move", command: "u" });
+          return;
+        }
+        case "ArrowRight": {
+          dispatch({ type: "move", command: "r" });
+          return;
+        }
+        case "ArrowLeft": {
+          dispatch({ type: "move", command: "l" });
+          return;
+        }
+        default:
+          return;
+      }
+    })
+
+  }, [])
+  return (
+    <div style={gameStyle.container}>
+      <h1> 2048 </h1>
+      <div style={gameStyle.board}>
+        {game.board.map((row: Cell[]) => {
+          return row.map((cell: Cell) => (<div style={gameStyle.cell(cell.color)}> {cell.value} </div>))
+        })}
+      </div>
+    </div>
+  );
+}
 
 ReactDOM.createRoot(document.getElementById("root")!).render(<Current />);