Mercurial
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 />);