Skip to content

Commit 65ab959

Browse files
committed
örnek oyun tetris eklendi
1 parent e3fa455 commit 65ab959

6 files changed

Lines changed: 298 additions & 16 deletions

File tree

‎src/App.jsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const EXAMPLE_POSTS = [
3434
]
3535

3636
const EXAMPLE_GAMES = [
37+
{ id: 'tetris', name: 'Tetris', summary: 'Efsanevi blok dizme oyunu', type: 'tetris' },
3738
{ id: 1, name: 'Hızlı Tuşlar', summary: 'Doğru tuşa hızlıca basıyorsunuz', type: 'timing' },
3839
{ id: 2, name: 'Bellek Oyunu', summary: 'Desenleri hatırlamaya çalışın', type: 'memory' },
3940
{ id: 3, name: 'Renk Eşleştirme', summary: 'Aynı renkli çiftleri bulun', type: 'matching' },
@@ -607,8 +608,8 @@ function App() {
607608
}
608609

609610
if (action.startsWith('OPEN_GAME_')) {
610-
const gameId = Number(action.replace('OPEN_GAME_', ''))
611-
const selectedGame = EXAMPLE_GAMES.find((game) => game.id === gameId)
611+
const rawId = action.replace('OPEN_GAME_', '')
612+
const selectedGame = EXAMPLE_GAMES.find((game) => String(game.id) === rawId)
612613
if (selectedGame) {
613614
setActiveGame(selectedGame)
614615
beep(700, 70)
@@ -835,6 +836,7 @@ function App() {
835836
<GameDetailPage
836837
game={activeGame}
837838
labels={pageUi}
839+
settings={calibrationSettings}
838840
footerActions={[
839841
{ id: 'game-detail-home', action: 'SURFACE_HOME', label: 'Ana Sayfa', onClick: () => { setActiveGame(null); openSurface('home') }, isFocused: focusedId === 'game-detail-home' },
840842
]}

‎src/components/TetrisGame.css‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.tetris-container {
2+
width: 100%;
3+
height: 100%;
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
background-color: #111;
8+
border-radius: 8px;
9+
position: relative;
10+
overflow: hidden;
11+
}
12+
13+
.tetris-board {
14+
display: grid;
15+
grid-template-columns: repeat(10, 1fr);
16+
grid-template-rows: repeat(20, 1fr);
17+
width: auto;
18+
aspect-ratio: 10 / 20;
19+
height: 90%;
20+
background-color: #222;
21+
border: 2px solid #444;
22+
position: relative;
23+
}
24+
25+
.tetris-cell {
26+
border: 1px solid #333;
27+
}
28+
29+
.tetris-game-over {
30+
position: absolute;
31+
top: 50%;
32+
left: 50%;
33+
transform: translate(-50%, -50%);
34+
background-color: rgba(0, 0, 0, 0.9);
35+
color: red;
36+
font-size: 32px;
37+
font-weight: bold;
38+
padding: 20px;
39+
border-radius: 8px;
40+
text-align: center;
41+
border: 2px solid red;
42+
}

‎src/components/TetrisGame.jsx‎

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import React, { useState, useEffect, useCallback, useRef } from 'react'
2+
import './TetrisGame.css'
3+
4+
const ROWS = 20
5+
const COLS = 10
6+
7+
const SHAPES = [
8+
// I
9+
{ shape: [[1, 1, 1, 1]], color: '#00ffff' },
10+
// O
11+
{ shape: [[1, 1], [1, 1]], color: '#ffff00' },
12+
// T
13+
{ shape: [[0, 1, 0], [1, 1, 1]], color: '#aa00ff' },
14+
// S
15+
{ shape: [[0, 1, 1], [1, 1, 0]], color: '#00ff00' },
16+
// Z
17+
{ shape: [[1, 1, 0], [0, 1, 1]], color: '#ff0000' },
18+
// J
19+
{ shape: [[1, 0, 0], [1, 1, 1]], color: '#0000ff' },
20+
// L
21+
{ shape: [[0, 0, 1], [1, 1, 1]], color: '#ffaa00' }
22+
]
23+
24+
function createEmptyGrid() {
25+
return Array.from({ length: ROWS }, () => Array(COLS).fill(null))
26+
}
27+
28+
function getRandomPiece() {
29+
const piece = SHAPES[Math.floor(Math.random() * SHAPES.length)]
30+
return {
31+
shape: piece.shape,
32+
color: piece.color,
33+
x: Math.floor(COLS / 2) - Math.floor(piece.shape[0].length / 2),
34+
y: 0
35+
}
36+
}
37+
38+
export default function TetrisGame({ onScoreUpdate, isFocused }) {
39+
const [grid, setGrid] = useState(createEmptyGrid())
40+
const [piece, setPiece] = useState(null)
41+
const [gameOver, setGameOver] = useState(false)
42+
const [score, setScore] = useState(0)
43+
44+
// Use refs to avoid closures issues in event listeners
45+
const stateRef = useRef({ grid, piece, gameOver, score })
46+
stateRef.current = { grid, piece, gameOver, score }
47+
48+
const spawnPiece = useCallback(() => {
49+
const nextPiece = getRandomPiece()
50+
setPiece(nextPiece)
51+
if (checkCollision(nextPiece, stateRef.current.grid)) {
52+
setGameOver(true)
53+
}
54+
}, [])
55+
56+
useEffect(() => {
57+
if (!piece && !gameOver) {
58+
spawnPiece()
59+
}
60+
}, [piece, gameOver, spawnPiece])
61+
62+
function checkCollision(p, currentGrid) {
63+
for (let r = 0; r < p.shape.length; r++) {
64+
for (let c = 0; c < p.shape[r].length; c++) {
65+
if (p.shape[r][c]) {
66+
const newY = p.y + r
67+
const newX = p.x + c
68+
if (newY >= ROWS || newX < 0 || newX >= COLS || (newY >= 0 && currentGrid[newY][newX])) {
69+
return true
70+
}
71+
}
72+
}
73+
}
74+
return false
75+
}
76+
77+
function mergePiece(p, currentGrid) {
78+
const newGrid = currentGrid.map(row => [...row])
79+
for (let r = 0; r < p.shape.length; r++) {
80+
for (let c = 0; c < p.shape[r].length; c++) {
81+
if (p.shape[r][c] && p.y + r >= 0) {
82+
newGrid[p.y + r][p.x + c] = p.color
83+
}
84+
}
85+
}
86+
return newGrid
87+
}
88+
89+
function clearLines(currentGrid) {
90+
let linesCleared = 0
91+
const newGrid = currentGrid.filter(row => {
92+
if (row.every(cell => cell !== null)) {
93+
linesCleared++
94+
return false
95+
}
96+
return true
97+
})
98+
99+
while (newGrid.length < ROWS) {
100+
newGrid.unshift(Array(COLS).fill(null))
101+
}
102+
103+
if (linesCleared > 0) {
104+
const addedScore = [0, 100, 300, 500, 800][linesCleared]
105+
const newScore = stateRef.current.score + addedScore
106+
setScore(newScore)
107+
if (onScoreUpdate) onScoreUpdate(newScore)
108+
}
109+
110+
return newGrid
111+
}
112+
113+
const moveDown = useCallback(() => {
114+
const { piece: p, grid: g, gameOver: over } = stateRef.current
115+
if (over || !p) return
116+
117+
const nextPiece = { ...p, y: p.y + 1 }
118+
if (checkCollision(nextPiece, g)) {
119+
const mergedGrid = mergePiece(p, g)
120+
const finalGrid = clearLines(mergedGrid)
121+
setGrid(finalGrid)
122+
spawnPiece()
123+
} else {
124+
setPiece(nextPiece)
125+
}
126+
}, [spawnPiece])
127+
128+
useEffect(() => {
129+
const interval = setInterval(moveDown, 800)
130+
return () => clearInterval(interval)
131+
}, [moveDown])
132+
133+
useEffect(() => {
134+
const handleAction = (e) => {
135+
const { action } = e.detail
136+
const { piece: p, grid: g, gameOver: over } = stateRef.current
137+
if (over || !p) return
138+
139+
let nextPiece = null
140+
141+
switch(action) {
142+
case 'LEFT':
143+
nextPiece = { ...p, x: p.x - 1 }
144+
if (!checkCollision(nextPiece, g)) setPiece(nextPiece)
145+
break
146+
case 'RIGHT':
147+
nextPiece = { ...p, x: p.x + 1 }
148+
if (!checkCollision(nextPiece, g)) setPiece(nextPiece)
149+
break
150+
case 'CLICK': {
151+
// Rotate
152+
const rotatedShape = p.shape[0].map((_, i) => p.shape.map(row => row[i]).reverse())
153+
nextPiece = { ...p, shape: rotatedShape }
154+
if (!checkCollision(nextPiece, g)) setPiece(nextPiece)
155+
break
156+
}
157+
158+
}
159+
}
160+
161+
const handler = (e) => handleAction(e)
162+
window.addEventListener('eyebridge:activate', handler)
163+
return () => window.removeEventListener('eyebridge:activate', handler)
164+
}, [spawnPiece])
165+
166+
// Render game playfield
167+
const renderGrid = () => {
168+
const displayGrid = grid.map(row => [...row])
169+
if (piece) {
170+
for (let r = 0; r < piece.shape.length; r++) {
171+
for (let c = 0; c < piece.shape[r].length; c++) {
172+
if (piece.shape[r][c] && piece.y + r >= 0 && piece.y + r < ROWS) {
173+
displayGrid[piece.y + r][piece.x + c] = piece.color
174+
}
175+
}
176+
}
177+
}
178+
179+
return (
180+
<div className="tetris-board">
181+
{displayGrid.map((row, r) => (
182+
row.map((cell, c) => (
183+
<div
184+
key={`${r}-${c}`}
185+
className={`tetris-cell ${cell ? 'filled' : ''}`}
186+
style={{ backgroundColor: cell || 'transparent' }}
187+
/>
188+
))
189+
))}
190+
{gameOver && <div className="tetris-game-over">Oyun Bitti</div>}
191+
</div>
192+
)
193+
}
194+
195+
return (
196+
<div className={`tetris-container ${isFocused ? 'game-focused' : ''}`}>
197+
{renderGrid()}
198+
</div>
199+
)
200+
}

‎src/hooks/useGazeInteraction.js‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,12 @@ export function useGazeInteraction({
183183
setDwellProgress(progress)
184184

185185
const isBlinkAction = nextElement.getAttribute('data-blink-action') === 'true'
186-
const cooldownElapsed = now - lastActivationRef.current > 500
186+
const isInstantAction = nextElement.getAttribute('data-instant-action') === 'true'
187+
188+
const cooldownTime = isInstantAction ? 300 : 500
189+
const cooldownElapsed = now - lastActivationRef.current > cooldownTime
187190

188-
if (isBlinkAction && progress >= 1 && cooldownElapsed) {
191+
if ((isInstantAction || (isBlinkAction && progress >= 1)) && cooldownElapsed) {
189192
triggerAction(nextElement, 'dwell')
190193
}
191194

‎src/hooks/useWebGazerBridge.js‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ export function useWebGazerBridge() {
332332
const manualHoldPointRef = useRef(null)
333333
const manualHoldUntilRef = useRef(0)
334334
const lastManualTargetRef = useRef(null)
335+
const lastBlinkEventRef = useRef(0)
335336

336337
const dispatchGaze = useCallback((x, y) => {
337338
const now = Date.now()
@@ -436,7 +437,7 @@ export function useWebGazerBridge() {
436437
minFaceDetectionConfidence: 0.5,
437438
minFacePresenceConfidence: 0.5,
438439
minTrackingConfidence: 0.5,
439-
outputFaceBlendshapes: false,
440+
outputFaceBlendshapes: true,
440441
outputFacialTransformationMatrixes: false,
441442
})
442443

@@ -636,6 +637,20 @@ export function useWebGazerBridge() {
636637
if (faceLandmarks) {
637638
emitPrediction(faceLandmarks)
638639
}
640+
641+
const faceBlendshapes = result?.faceBlendshapes?.[0]?.categories
642+
if (faceBlendshapes) {
643+
const leftBlink = faceBlendshapes.find(c => c.categoryName === 'eyeBlinkLeft')?.score || 0
644+
const rightBlink = faceBlendshapes.find(c => c.categoryName === 'eyeBlinkRight')?.score || 0
645+
646+
if (leftBlink > 0.45 && rightBlink > 0.45) {
647+
const now = Date.now()
648+
if (now - lastBlinkEventRef.current > 700) {
649+
lastBlinkEventRef.current = now
650+
window.dispatchEvent(new CustomEvent('eyebridge:blink'))
651+
}
652+
}
653+
}
639654
} catch (error) {
640655
setLastError(error?.message || 'unknown')
641656
setStatusMeta({ key: 'detectionError' })

0 commit comments

Comments
 (0)