Skip to content

Commit 70c1d7a

Browse files
feat: enhance OpeningSelectionModal with improved styling and integrate chess sound effects in opening drill controller
1 parent 6672199 commit 70c1d7a

4 files changed

Lines changed: 117 additions & 47 deletions

File tree

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useMemo } from 'react'
2+
import Image from 'next/image'
23
import { AnimatePresence, motion } from 'framer-motion'
34
import Chessground from '@react-chess/chessground'
45
import { Opening, OpeningVariation, OpeningSelection } from 'src/types'
@@ -139,7 +140,7 @@ export const OpeningSelectionModal: React.FC<Props> = ({
139140
</div>
140141

141142
{/* Search Bar */}
142-
<div className="border-b border-white/10 px-4 pb-4">
143+
<div className="border-b border-white/10 p-4">
143144
<div className="relative">
144145
<span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-sm text-secondary">
145146
search
@@ -333,50 +334,50 @@ export const OpeningSelectionModal: React.FC<Props> = ({
333334
{selections.map((selection) => (
334335
<div
335336
key={selection.id}
336-
className="rounded bg-background-2 p-3"
337+
className="flex cursor-pointer items-center justify-between border-b border-white/5 p-3 transition-colors hover:bg-human-2/10"
337338
>
338-
<div className="flex items-start justify-between">
339-
<div>
340-
<h4 className="text-sm font-medium">
341-
{selection.opening.name}
342-
</h4>
343-
{selection.variation && (
344-
<p className="text-xs text-secondary">
345-
{selection.variation.name}
346-
</p>
347-
)}
348-
<div className="mt-1 flex items-center gap-2">
349-
<span
350-
className={`inline-flex items-center gap-1 text-xs ${
339+
<div className="min-w-0 flex-1">
340+
<div className="flex items-center gap-3">
341+
<div className="relative h-5 w-5 flex-shrink-0">
342+
<Image
343+
src={
351344
selection.playerColor === 'white'
352-
? 'text-white'
353-
: 'text-gray-400'
354-
}`}
355-
>
356-
<span className="material-symbols-outlined text-xs">
357-
chess
358-
</span>
359-
{selection.playerColor}
360-
</span>
361-
<span className="text-xs text-human-3">
362-
vs{' '}
363-
{
364-
MAIA_VERSIONS.find(
365-
(v) => v.id === selection.maiaVersion,
366-
)?.name
345+
? '/assets/pieces/white king.svg'
346+
: '/assets/pieces/black king.svg'
367347
}
348+
fill={true}
349+
alt={`${selection.playerColor} king`}
350+
/>
351+
</div>
352+
<div className="flex min-w-0 flex-1 flex-col">
353+
<span className="truncate text-sm font-medium text-primary">
354+
{selection.opening.name}
368355
</span>
356+
<div className="flex items-center gap-1 text-xs text-secondary">
357+
{selection.variation && (
358+
<>
359+
<span className="truncate">
360+
{selection.variation.name}
361+
</span>
362+
<span></span>
363+
</>
364+
)}
365+
<span>
366+
v. Maia{' '}
367+
{selection.maiaVersion.replace('maia_kdd_', '')}
368+
</span>
369+
</div>
369370
</div>
370371
</div>
371-
<button
372-
onClick={() => removeSelection(selection.id)}
373-
className="text-secondary transition-colors hover:text-human-4"
374-
>
375-
<span className="material-symbols-outlined text-sm">
376-
close
377-
</span>
378-
</button>
379372
</div>
373+
<button
374+
onClick={() => removeSelection(selection.id)}
375+
className="ml-2 text-secondary transition-colors hover:text-human-4"
376+
>
377+
<span className="material-symbols-outlined text-sm">
378+
close
379+
</span>
380+
</button>
380381
</div>
381382
))}
382383
</div>

src/hooks/useOpeningDrillController/useOpeningDrillController.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
22
import { Chess, PieceSymbol } from 'chess.ts'
33
import { getGameMove } from 'src/api/play/play'
44
import { useTreeController } from '../useTreeController'
5+
import { useChessSound } from '../useChessSound'
56
import { GameTree, GameNode } from 'src/types'
67
import { OpeningSelection, OpeningDrillGame } from 'src/types/openings'
78

@@ -62,6 +63,9 @@ export const useOpeningDrillController = (selections: OpeningSelection[]) => {
6263

6364
const currentSelection = selections[currentSelectionIndex]
6465

66+
// Add chess sound hook
67+
const { playSound } = useChessSound()
68+
6569
// Initialize drill games from selections
6670
useEffect(() => {
6771
const games: { [key: string]: OpeningDrillGame } = {}
@@ -309,6 +313,14 @@ export const useOpeningDrillController = (selections: OpeningSelection[]) => {
309313
}
310314

311315
if (newNode) {
316+
// Check if this was a capture move for sound effect
317+
const chess = new Chess(fromNode.fen)
318+
const pieceAtDestination = chess.get(maiaMove.slice(2, 4))
319+
const isCapture = !!pieceAtDestination
320+
321+
// Play sound effect for Maia's move
322+
playSound(isCapture)
323+
312324
// Update the controller to the new node immediately
313325
controller.setCurrentNode(newNode)
314326

@@ -330,7 +342,7 @@ export const useOpeningDrillController = (selections: OpeningSelection[]) => {
330342
console.error('Error making Maia move:', error)
331343
}
332344
},
333-
[currentDrillGame, controller, currentSelection],
345+
[currentDrillGame, controller, currentSelection, playSound],
334346
)
335347

336348
// Store makeMaiaMove in a ref to avoid circular dependencies

src/pages/openings/index.tsx

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import Head from 'next/head'
22
import { NextPage } from 'next'
3+
import { useRouter } from 'next/router'
34
import { useState, useEffect, useContext, useCallback, useMemo } from 'react'
45
import { Chess, PieceSymbol } from 'chess.ts'
56
import { AnimatePresence } from 'framer-motion'
67
import type { Key } from 'chessground/types'
8+
import type { DrawShape } from 'chessground/draw'
79

810
import { WindowSizeContext } from 'src/contexts'
911
import { OpeningSelection, AnalyzedGame } from 'src/types'
@@ -31,11 +33,14 @@ import {
3133
import { TreeControllerContext } from 'src/contexts/TreeControllerContext/TreeControllerContext'
3234

3335
const OpeningsPage: NextPage = () => {
36+
const router = useRouter()
3437
const [showSelectionModal, setShowSelectionModal] = useState(true)
3538
const [selections, setSelections] = useState<OpeningSelection[]>([])
3639
const [promotionFromTo, setPromotionFromTo] = useState<
3740
[string, string] | null
3841
>(null)
42+
const [arrows, setArrows] = useState<DrawShape[]>([])
43+
const [hoverArrow, setHoverArrow] = useState<DrawShape | null>(null)
3944

4045
// Pre-load engines when page loads - keep them at top level to prevent reloading
4146
const { status: maiaStatus } = useMaiaEngine()
@@ -133,6 +138,55 @@ const OpeningsPage: NextPage = () => {
133138
}
134139
}, [controller.currentNode, analysisController])
135140

141+
// Set arrows for Maia and Stockfish recommendations when analysis is enabled
142+
useEffect(() => {
143+
if (!controller.analysisEnabled) {
144+
setArrows([])
145+
return
146+
}
147+
148+
const arr = []
149+
150+
if (analysisController.moveEvaluation?.maia) {
151+
const maia = Object.entries(
152+
analysisController.moveEvaluation?.maia?.policy,
153+
)[0]
154+
if (maia) {
155+
arr.push({
156+
brush: 'red',
157+
orig: maia[0].slice(0, 2) as Key,
158+
dest: maia[0].slice(2, 4) as Key,
159+
} as DrawShape)
160+
}
161+
}
162+
163+
if (analysisController.moveEvaluation?.stockfish) {
164+
const stockfish = Object.entries(
165+
analysisController.moveEvaluation?.stockfish.cp_vec,
166+
)[0]
167+
if (stockfish) {
168+
arr.push({
169+
brush: 'blue',
170+
orig: stockfish[0].slice(0, 2) as Key,
171+
dest: stockfish[0].slice(2, 4) as Key,
172+
modifiers: { lineWidth: 8 },
173+
})
174+
}
175+
}
176+
177+
setArrows(arr)
178+
}, [
179+
controller.analysisEnabled,
180+
analysisController.moveEvaluation,
181+
analysisController.currentNode,
182+
analysisController.orientation,
183+
])
184+
185+
// Clear hover arrow when node changes
186+
useEffect(() => {
187+
setHoverArrow(null)
188+
}, [controller.currentNode])
189+
136190
// Show selection modal when no selections are made
137191
useEffect(() => {
138192
if (selections.length === 0) {
@@ -151,8 +205,11 @@ const OpeningsPage: NextPage = () => {
151205
const handleCloseModal = useCallback(() => {
152206
if (selections.length > 0) {
153207
setShowSelectionModal(false)
208+
} else {
209+
// If no selections, redirect to home page
210+
router.push('/')
154211
}
155-
}, [selections.length])
212+
}, [selections.length, router])
156213

157214
const currentPlayer = useMemo(() => {
158215
if (!controller.currentNode) return 'white'
@@ -316,7 +373,7 @@ const OpeningsPage: NextPage = () => {
316373
orientation={controller.orientation}
317374
onPlayerMakeMove={onPlayerMakeMove}
318375
availableMoves={controller.moves}
319-
shapes={[]}
376+
shapes={hoverArrow ? [...arrows, hoverArrow] : [...arrows]}
320377
onSelectSquare={onSelectSquare}
321378
/>
322379
{promotionFromTo && (
@@ -337,12 +394,12 @@ const OpeningsPage: NextPage = () => {
337394
<div className="flex flex-col gap-2">
338395
<button
339396
onClick={() => setShowSelectionModal(true)}
340-
className="w-full rounded bg-background-2 py-2 text-sm text-secondary transition-colors hover:bg-background-3"
397+
className="flex w-full items-center justify-center rounded bg-background-2 py-2 text-sm text-secondary transition-colors hover:bg-background-3"
341398
>
342399
<span className="material-symbols-outlined mr-1 text-sm">
343400
settings
344401
</span>
345-
Change Selections
402+
Change Selected Openings
346403
</button>
347404
</div>
348405
</div>
@@ -381,7 +438,7 @@ const OpeningsPage: NextPage = () => {
381438
orientation={controller.orientation}
382439
onPlayerMakeMove={onPlayerMakeMove}
383440
availableMoves={controller.moves}
384-
shapes={[]}
441+
shapes={hoverArrow ? [...arrows, hoverArrow] : [...arrows]}
385442
onSelectSquare={onSelectSquare}
386443
/>
387444
{promotionFromTo && (

src/types/base/tree.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export class GameNode {
378378

379379
if (
380380
this._analysis.stockfish &&
381-
this._analysis.stockfish.depth >= 15 &&
381+
this._analysis.stockfish.depth >= 13 &&
382382
move
383383
) {
384384
this.classifyMoveByWinrate(
@@ -399,7 +399,7 @@ export class GameNode {
399399
this._analysis.maia = maiaEval
400400

401401
// Check if any existing children could be unlikely good moves
402-
if (this._analysis.stockfish && this._analysis.stockfish.depth >= 15) {
402+
if (this._analysis.stockfish && this._analysis.stockfish.depth >= 13) {
403403
for (const child of this._children) {
404404
if (child.move) {
405405
const winrate_loss =
@@ -424,7 +424,7 @@ export class GameNode {
424424
): void {
425425
this._analysis.stockfish = stockfishEval
426426

427-
if (stockfishEval.depth >= 15) {
427+
if (stockfishEval.depth >= 13) {
428428
for (const child of this._children) {
429429
if (child.move) {
430430
this.classifyMoveByWinrate(

0 commit comments

Comments
 (0)