Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* This game implements a number chain puzzle where players drag between adjacent squares (including diagonals) with the same number to add up.
* When a valid chain is selected, the last square in the chain holds the total value rounded up to the nearest power of two,
* and the other squares get new random values. The player scores points based on the chain length and values.
* We'll use React for the UI and manage game state on the client side.
* The server will just serve the initial HTML and JavaScript.
*
* We've updated the color scheme to have a dark background with colored tiles.
* Each number has its own color on a hue scale, cycling through hues for arbitrary powers of two.
* Selected chain tiles now have a white border, and numbers are white for better visibility.
* Non-bordered tiles now have a transparent border to maintain consistent size and prevent layout jumps.
* New tiles fading in no longer show a white border.
* New tiles now have a random power of two between the min and max numbers on the board.
* Initial possible numbers are now between 2 and 32.
*
* Added error handling to prevent "TypeError: cell1 is not iterable" error.
*/
/** @jsxImportSource https://esm.sh/react */
import React, { useEffect, useRef, useState } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
// Game board size
const BOARD_SIZE = 6;
// Hue difference between each power of two
const HUE_DIFF = 25;
function getColor(value: number): string {
const power = Math.log2(value / 2);
const hue = (power * HUE_DIFF) % 360;
return `hsl(${hue}, 50%, 50%)`;
}
function App() {
const [board, setBoard] = useState<number[][]>([]);
const [selectedCells, setSelectedCells] = useState<[number, number][]>([]);
const [score, setScore] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const [animatingCells, setAnimatingCells] = useState<[number, number][]>([]);
const [newCells, setNewCells] = useState<[number, number][]>([]);
const [noMovesAvailable, setNoMovesAvailable] = useState(false);
const boardRef = useRef<HTMLDivElement>(null);
useEffect(() => {
initializeBoard();
}, []);
useEffect(() => {
checkForAvailableMoves();
}, [board]);
const initializeBoard = () => {
const newBoard = Array(BOARD_SIZE)
.fill(null)
.map(() =>
Array(BOARD_SIZE)
.fill(null)
.map(() => Math.pow(2, Math.floor(Math.random() * 5) + 1))
);
setBoard(newBoard);
setSelectedCells([]);
setScore(0);
setNewCells(
newBoard.flatMap((row, i) => row.map((_, j) => [i, j] as [number, number])),
);
setTimeout(() => setNewCells([]), 500); // Clear new cells after animation
setNoMovesAvailable(false);
};
const handleMouseDown = (row: number, col: number) => {
setIsDragging(true);
setSelectedCells([[row, col]]);
};
const handleMouseEnter = (row: number, col: number) => {
if (!isDragging) return;
setSelectedCells((prev) => {
if (prev.length === 0) return [[row, col]];
const lastSelected = prev[prev.length - 1];
if (
isValidCell(lastSelected)
&& isValidCell([row, col])
&& isAdjacent(lastSelected, [row, col])
&& board[row][col] === board[lastSelected[0]][lastSelected[1]]
&& !prev.some(
(cell) => isValidCell(cell) && cell[0] === row && cell[1] === col,
)
) {
return [...prev, [row, col]];
}
return prev;
});
};
const handleMouseUp = () => {
setIsDragging(false);
if (selectedCells.length > 1) {
processChain();
tnorthcutt-numbergame.web.val.run
September 3, 2024