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
/** @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 = 5
// 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 [showInfo, setShowInfo] = useState(false)
const boardRef = useRef<HTMLDivElement>(null)
useEffect(() => {
initializeBoard()
}, [])
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
}
const handleTouchStart = (row: number, col: number) => {
setIsDragging(true)
setSelectedCells([[row, col]])
}
const handleTouchMove = (e: React.TouchEvent) => {
if (!isDragging || !boardRef.current) return
const touch = e.touches[0]
const board = boardRef.current
const rect = board.getBoundingClientRect()
const x = touch.clientX - rect.left
const y = touch.clientY - rect.top
const cellSize = rect.width / BOARD_SIZE
const row = Math.floor(y / cellSize)
const col = Math.floor(x / cellSize)
if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
handleCellEnter(row, col)
}
}
const handleTouchEnd = () => {
setIsDragging(false)
if (selectedCells.length > 1) {
processChain()
} else {
setSelectedCells([])
}
}
const handleMouseDown = (row: number, col: number) => {
setIsDragging(true)
setSelectedCells([[row, col]])
}
const handleMouseEnter = (row: number, col: number) => {
if (!isDragging) return
handleCellEnter(row, col)
}
const handleMouseUp = () => {
handleTouchEnd()
}
const handleCellEnter = (row: number, col: number) => {
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