comparison 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
comparison
equal deleted inserted replaced
43:5e6a5d3c6868 44:0cfd7d9277b0
1 import { CSSProperties, useEffect, useReducer, useState } from "react";
1 import ReactDOM from "react-dom/client"; 2 import ReactDOM from "react-dom/client";
2 3
3 const shaders = ` 4 /**
4 struct VertexOut { 5 * 2048
5 @builtin(position) position : vec4f, 6 *
6 @location(0) color : vec4f 7 * 4 X 4
7 } 8 */
8 9
9 @vertex 10 const MAX_WIDTH = 4;
10 fn vertex_main(@location(0) position: vec4f, 11
11 @location(1) color: vec4f) -> VertexOut 12 type Color = 'white' | 'orange' | 'yellow' | 'red';
12 { 13
13 var output : VertexOut; 14 type Cell = {
14 output.position = position; 15 value: number;
15 output.color = color; 16 color: Color;
16 return output; 17 }
17 } 18
18 19 type Board = Cell[][];
19 @fragment 20
20 fn fragment_main(fragData: VertexOut) -> @location(0) vec4f 21 type GameState = "in_progress" | "lost" | "won";
21 { 22
22 return fragData.color; 23 type Game = {
23 } 24 board: Board;
24 `; 25 state: GameState;
25 26 steps: number;
26 async function init() { 27 }
27 if (!navigator.gpu) { 28
28 throw Error("WebGPU not supported."); 29 type Command = "u" | "d" | "l" | "r";
29 } 30
30 31 type GameAction =
31 const adapter = await navigator.gpu.requestAdapter(); 32 { type: "move", command: Command } | { type: "calculate" };
32 if (!adapter) { 33
33 throw Error("Couldn't request WebGPU adapter."); 34
34 } 35 interface GameStyle {
35 36 container: CSSProperties;
36 const device = await adapter.requestDevice(); 37 board: CSSProperties;
37 38 cell: (color: Color) => CSSProperties;
38 const shaderModule = device.createShaderModule({ 39 }
39 code: shaders, 40
40 }); 41 const gameStyle: GameStyle = {
41 42 container: {
42 const canvas = document.querySelector("#gpuCanvas"); 43 display: "flex",
43 const context = canvas.getContext("webgpu"); 44 flexDirection: "column",
44 45 justifyContent: "center",
45 context.configure({ 46 alignItems: "center",
46 device, 47 height: "100vh"
47 format: navigator.gpu.getPreferredCanvasFormat(), 48 },
48 alphaMode: "premultiplied", 49 board: {
49 }); 50 display: "grid",
50 51 gridTemplateColumns: "repeat(4, 50px)",
51 console.log(device) 52 background: "#EEFFEE",
52 } 53 },
53 54 cell: (color: Color) => ({
54 void init(); 55 display: "flex",
55 56 justifyContent: "center",
56 const Current = () => { 57 alignItems: "center",
57 return (<>hello </>); 58 aspectRatio: "1 / 1 ",
58 }; 59 margin: "10px",
60 background: color,
61 })
62 }
63
64 function initializeBoard(): Board {
65 const board = Array.from({ length: MAX_WIDTH }, () =>
66 Array.from({ length: MAX_WIDTH }, (): Cell => ({ value: 0, color: 'orange' }))
67 );
68 let rowIndex: number;
69 let colIndex: number;
70 rowIndex = Math.floor(Math.random() * 4);
71 colIndex = Math.floor(Math.random() * 4);
72 board[rowIndex][colIndex].value = 2;
73 board[rowIndex-1][colIndex].value = 2;
74 return board;
75 }
76
77 function initializeGame(): Game {
78 return {
79 board: initializeBoard(),
80 state: "in_progress",
81 steps: 0,
82 }
83 }
84
85
86 function handleMove(board: Board, command: Command): Board {
87 // Deep copy the board and initialize the merged status for the new board
88 const copiedBoard = board.map(row =>
89 row.map(cell => ({ ...cell, merged: false }))
90 );
91
92 let diff: { row: number, col: number };
93 let startRow: number, endRow: number, stepRow: number;
94 let startCol: number, endCol: number, stepCol: number;
95
96 const size = copiedBoard.length;
97
98 switch (command) {
99 case "u":
100 diff = { row: -1, col: 0 };
101 startRow = 0; endRow = size; stepRow = 1;
102 startCol = 0; endCol = size; stepCol = 1;
103 break;
104 case "d":
105 diff = { row: 1, col: 0 };
106 startRow = size - 1; endRow = -1; stepRow = -1;
107 startCol = 0; endCol = size; stepCol = 1;
108 break;
109 case "l":
110 diff = { row: 0, col: -1 };
111 startRow = 0; endRow = size; stepRow = 1;
112 startCol = 0; endCol = size; stepCol = 1;
113 break;
114 case "r":
115 diff = { row: 0, col: 1 };
116 startRow = 0; endRow = size; stepRow = 1;
117 startCol = size - 1; endCol = -1; stepCol = -1;
118 break;
119 }
120
121 for (let rowIndex = startRow; rowIndex !== endRow; rowIndex += stepRow) {
122 for (let colIndex = startCol; colIndex !== endCol; colIndex += stepCol) {
123 const currentCell = copiedBoard[rowIndex][colIndex];
124
125 if (currentCell.value === 0) continue;
126
127 let r = rowIndex;
128 let c = colIndex;
129 let emptySlot: { r: number, c: number } = { r: rowIndex, c: colIndex };
130 let finalSlot: { r: number, c: number } = { r: rowIndex, c: colIndex };
131
132 while (true) {
133 r += diff.row;
134 c += diff.col;
135
136 if (r < 0 || r >= size || c < 0 || c >= size) {
137 finalSlot = emptySlot;
138 break;
139 }
140
141 const nextCell = copiedBoard[r][c];
142
143 if (nextCell.value === 0) {
144 emptySlot = { r, c };
145 finalSlot = emptySlot;
146 } else if (nextCell.value === currentCell.value && !nextCell.merged) {
147 finalSlot = { r, c };
148 break;
149 } else {
150 finalSlot = emptySlot;
151 break;
152 }
153 }
154
155 const targetCell = copiedBoard[finalSlot.r][finalSlot.c];
156
157 if (finalSlot.r === rowIndex && finalSlot.c === colIndex) {
158 continue;
159 }
160
161 if (targetCell.value === currentCell.value && !targetCell.merged) {
162 targetCell.value *= 2;
163 targetCell.merged = true;
164
165 copiedBoard[rowIndex][colIndex].value = 0;
166
167 } else if (targetCell.value === 0) {
168 targetCell.value = currentCell.value;
169 copiedBoard[rowIndex][colIndex].value = 0;
170 }
171 }
172 }
173
174 return copiedBoard;
175 }
176
177 function addNewItemsToTheBoard(board: Board) {
178 let randomRowIndex: number;
179 let randomColIndex: number;
180
181
182 let zeroPos = 0;
183 board.forEach((row) => {
184 row.forEach((cell) => {
185 if (cell.value === 0) {
186 zeroPos += 1;
187 }
188 })
189 })
190 if (zeroPos === 0) {
191 return;
192 }
193
194 let curr = 0;
195 const maxAddedValues = zeroPos < 2 ? 1 : (zeroPos / 2) | 0;
196 while (curr < maxAddedValues) {
197 randomRowIndex = Math.floor(Math.random() * board.length)
198 randomColIndex = Math.floor(Math.random() * board.length)
199 if (board[randomRowIndex][randomColIndex].value === 0)
200 {
201 board[randomRowIndex][randomColIndex].value = 2;
202 curr++;
203 }
204 }
205 }
206
207 function gameDispatch(game: Game, gameAction: GameAction): Game {
208 switch(gameAction.type) {
209 case "move": {
210 const newBoard = handleMove(game.board, gameAction.command);
211 addNewItemsToTheBoard(newBoard);
212 return {
213 ...game,
214 board: newBoard,
215 }
216 }
217 case "calculate": {
218 return {
219 ...game,
220 }
221 }
222 }
223 }
224
225 function Current() {
226 const [game, dispatch] = useReducer(gameDispatch, null, initializeGame);
227
228 useEffect(() => {
229 window.addEventListener("keyup", (e) => {
230 switch(e.key) {
231 case "ArrowDown": {
232 dispatch({ type: "move", command: "d" });
233 return;
234 }
235 case "ArrowUp": {
236 dispatch({ type: "move", command: "u" });
237 return;
238 }
239 case "ArrowRight": {
240 dispatch({ type: "move", command: "r" });
241 return;
242 }
243 case "ArrowLeft": {
244 dispatch({ type: "move", command: "l" });
245 return;
246 }
247 default:
248 return;
249 }
250 })
251
252 }, [])
253 return (
254 <div style={gameStyle.container}>
255 <h1> 2048 </h1>
256 <div style={gameStyle.board}>
257 {game.board.map((row: Cell[]) => {
258 return row.map((cell: Cell) => (<div style={gameStyle.cell(cell.color)}> {cell.value} </div>))
259 })}
260 </div>
261 </div>
262 );
263 }
59 264
60 ReactDOM.createRoot(document.getElementById("root")!).render(<Current />); 265 ReactDOM.createRoot(document.getElementById("root")!).render(<Current />);