Skip to content

Commit 959f29d

Browse files
feat: allow repeated openings for increased number of drills
1 parent da0e8f4 commit 959f29d

5 files changed

Lines changed: 161 additions & 50 deletions

File tree

src/components/Openings/OpeningDrillSidebar.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ interface Props {
66
currentDrill: OpeningSelection | null
77
completedDrills: CompletedDrill[]
88
remainingDrills: OpeningSelection[]
9+
currentDrillIndex: number
10+
totalDrills: number
911
onResetCurrentDrill: () => void
1012
onChangeSelections?: () => void
1113
}
@@ -14,11 +16,13 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
1416
currentDrill,
1517
completedDrills,
1618
remainingDrills,
19+
currentDrillIndex,
20+
totalDrills,
1721
onResetCurrentDrill,
1822
onChangeSelections,
1923
}) => {
2024
return (
21-
<div className="flex h-full w-72 flex-col border-r border-white/10 bg-background-1">
25+
<div className="flex h-full w-full flex-col border-r border-white/10 bg-background-1 2xl:min-w-72">
2226
{/* Current Drill Info */}
2327
<div className="border-b border-white/10 p-4">
2428
<h2 className="mb-2 text-lg font-bold text-primary">Current Drill</h2>
@@ -76,8 +80,16 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
7680

7781
{/* Drill Progress */}
7882
<div className="border-b border-white/10 p-4">
79-
<h3 className="mb-2 text-sm font-medium text-primary">Progress</h3>
83+
<h3 className="mb-2 text-sm font-medium text-primary">
84+
Drill Progress
85+
</h3>
8086
<div className="space-y-2">
87+
<div className="flex justify-between text-xs">
88+
<span className="text-secondary">Current Drill</span>
89+
<span className="font-medium text-human-4">
90+
{currentDrillIndex + 1} of {totalDrills}
91+
</span>
92+
</div>
8193
<div className="flex justify-between text-xs">
8294
<span className="text-secondary">Completed</span>
8395
<span className="font-medium text-green-400">
@@ -86,19 +98,17 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
8698
</div>
8799
<div className="flex justify-between text-xs">
88100
<span className="text-secondary">Remaining</span>
89-
<span className="font-medium text-human-4">
90-
{remainingDrills.length}
101+
<span className="font-medium text-yellow-400">
102+
{totalDrills - completedDrills.length}
91103
</span>
92104
</div>
93105
<div className="h-2 w-full rounded bg-background-2">
94106
<div
95107
className="h-full rounded bg-human-4 transition-all duration-300"
96108
style={{
97109
width: `${
98-
completedDrills.length > 0
99-
? (completedDrills.length /
100-
(completedDrills.length + remainingDrills.length)) *
101-
100
110+
totalDrills > 0
111+
? (completedDrills.length / totalDrills) * 100
102112
: 0
103113
}%`,
104114
}}
@@ -109,20 +119,18 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
109119

110120
{/* Completed Drills List */}
111121
<div className="flex h-96 flex-col overflow-hidden">
112-
<div className="border-b border-white/10 p-3">
113-
<h3 className="text-sm font-medium text-primary">
114-
Completed Drills ({completedDrills.length})
115-
</h3>
116-
</div>
117-
<div className="flex h-full flex-col">
122+
<h3 className="p-3 text-sm font-medium text-primary">
123+
Completed Drills ({completedDrills.length})
124+
</h3>
125+
<div className="red-scrollbar flex h-full flex-col overflow-y-auto">
118126
{completedDrills.length === 0 ? (
119127
<div className="flex h-full items-center justify-center">
120128
<p className="max-w-[12rem] text-center text-xs text-secondary">
121129
Complete your first opening drill to see your progress here
122130
</p>
123131
</div>
124132
) : (
125-
<div className="flex flex-col gap-1 p-2">
133+
<div className="flex flex-col">
126134
{completedDrills.map((completedDrill, index) => {
127135
const accuracy =
128136
completedDrill.totalMoves > 0
@@ -136,7 +144,7 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
136144
return (
137145
<div
138146
key={completedDrill.selection.id}
139-
className="rounded bg-background-2 p-2 transition-colors hover:bg-background-3"
147+
className="bg-background-2 p-2 transition-colors hover:bg-background-3"
140148
>
141149
<div className="flex items-start justify-between">
142150
<div className="min-w-0 flex-1">

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import React, { useState, useMemo, useEffect } from 'react'
22
import Image from 'next/image'
33
import { AnimatePresence, motion } from 'framer-motion'
44
import Chessground from '@react-chess/chessground'
5-
import { Opening, OpeningVariation, OpeningSelection } from 'src/types'
5+
import {
6+
Opening,
7+
OpeningVariation,
8+
OpeningSelection,
9+
DrillConfiguration,
10+
} from 'src/types'
611
import { ModalContainer } from '../Misc/ModalContainer'
712

813
const MAIA_VERSIONS = [
@@ -20,7 +25,7 @@ const MAIA_VERSIONS = [
2025
interface Props {
2126
openings: Opening[]
2227
initialSelections?: OpeningSelection[]
23-
onComplete: (selections: OpeningSelection[]) => void
28+
onComplete: (configuration: DrillConfiguration) => void
2429
onClose: () => void
2530
}
2631

@@ -40,6 +45,7 @@ export const OpeningSelectionModal: React.FC<Props> = ({
4045
) // Default to 1500
4146
const [selectedColor, setSelectedColor] = useState<'white' | 'black'>('white')
4247
const [targetMoveNumber, setTargetMoveNumber] = useState(10)
48+
const [drillCount, setDrillCount] = useState(5)
4349
const [searchTerm, setSearchTerm] = useState('')
4450

4551
// Prevent background scrolling when modal is open
@@ -119,9 +125,42 @@ export const OpeningSelectionModal: React.FC<Props> = ({
119125
setPreviewVariation(variation)
120126
}
121127

128+
// Helper function to generate drill sequence
129+
const generateDrillSequence = (
130+
selections: OpeningSelection[],
131+
count: number,
132+
): OpeningSelection[] => {
133+
if (selections.length === 0) return []
134+
if (count <= selections.length) {
135+
// If drill count is less than or equal to selections, just shuffle and take the required amount
136+
const shuffled = [...selections].sort(() => Math.random() - 0.5)
137+
return shuffled.slice(0, count)
138+
}
139+
140+
// If drill count is more than selections, ensure each opening is played at least once
141+
const sequence: OpeningSelection[] = [...selections]
142+
const remaining = count - selections.length
143+
144+
// Fill remaining slots by randomly picking from selections
145+
for (let i = 0; i < remaining; i++) {
146+
const randomSelection =
147+
selections[Math.floor(Math.random() * selections.length)]
148+
sequence.push(randomSelection)
149+
}
150+
151+
// Shuffle the final sequence
152+
return sequence.sort(() => Math.random() - 0.5)
153+
}
154+
122155
const handleStartDrilling = () => {
123156
if (selections.length > 0) {
124-
onComplete(selections)
157+
const drillSequence = generateDrillSequence(selections, drillCount)
158+
const configuration: DrillConfiguration = {
159+
selections,
160+
drillCount,
161+
drillSequence,
162+
}
163+
onComplete(configuration)
125164
}
126165
}
127166

@@ -455,14 +494,39 @@ export const OpeningSelectionModal: React.FC<Props> = ({
455494
)}
456495
</div>
457496

458-
<div className="p-4">
497+
<div className="border-t border-white/10 p-4">
498+
{/* Drill Count Configuration */}
499+
<div className="mb-4">
500+
<p className="mb-2 text-sm font-medium">
501+
Number of Drills: {drillCount}
502+
</p>
503+
<input
504+
type="range"
505+
min="1"
506+
max="50"
507+
value={drillCount}
508+
onChange={(e) => setDrillCount(parseInt(e.target.value) || 5)}
509+
className="w-full accent-human-4"
510+
/>
511+
<div className="mt-1 flex justify-between text-xs text-secondary">
512+
<span>1</span>
513+
<span>50</span>
514+
</div>
515+
<p className="mt-1 text-xs text-secondary">
516+
{drillCount <= selections.length
517+
? `You'll play ${drillCount} of your selected openings`
518+
: selections.length > 0
519+
? `Each opening played at least once, with ${drillCount - selections.length} repeats`
520+
: 'Total number of opening drills to complete'}
521+
</p>
522+
</div>
523+
459524
<button
460525
onClick={handleStartDrilling}
461526
disabled={selections.length === 0}
462527
className="w-full rounded bg-human-4 py-2 text-sm font-medium transition-colors hover:bg-human-4/80 disabled:cursor-not-allowed disabled:opacity-50"
463528
>
464-
Start Drilling ({selections.length} opening
465-
{selections.length !== 1 ? 's' : ''})
529+
Start Drilling ({drillCount} drill{drillCount !== 1 ? 's' : ''})
466530
</button>
467531
</div>
468532
</div>

src/hooks/useOpeningDrillController/useOpeningDrillController.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
CompletedDrill,
1111
DrillPerformanceData,
1212
OverallPerformanceData,
13+
DrillConfiguration,
1314
} from 'src/types/openings'
1415

1516
// Helper function to parse PGN and create moves in the tree
@@ -71,7 +72,7 @@ const shuffleArray = <T>(array: T[]): T[] => {
7172
}
7273

7374
export const useOpeningDrillController = (
74-
allSelections: OpeningSelection[],
75+
configuration: DrillConfiguration,
7576
) => {
7677
// Drilling state
7778
const [remainingDrills, setRemainingDrills] = useState<OpeningSelection[]>([])
@@ -82,6 +83,7 @@ export const useOpeningDrillController = (
8283
const [currentDrillGame, setCurrentDrillGame] =
8384
useState<OpeningDrillGame | null>(null)
8485
const [analysisEnabled, setAnalysisEnabled] = useState(false)
86+
const [currentDrillIndex, setCurrentDrillIndex] = useState(0)
8587

8688
// Performance tracking state
8789
const [showPerformanceModal, setShowPerformanceModal] = useState(false)
@@ -98,14 +100,17 @@ export const useOpeningDrillController = (
98100
// Add chess sound hook
99101
const { playSound } = useChessSound()
100102

101-
// Initialize drilling session - shuffle and set first drill
103+
// Initialize drilling session from configuration
102104
useEffect(() => {
103-
if (allSelections.length > 0 && remainingDrills.length === 0) {
104-
const shuffled = shuffleArray(allSelections)
105-
setRemainingDrills(shuffled)
106-
setCurrentDrill(shuffled[0])
105+
if (
106+
configuration.drillSequence.length > 0 &&
107+
remainingDrills.length === 0
108+
) {
109+
setRemainingDrills(configuration.drillSequence)
110+
setCurrentDrill(configuration.drillSequence[0])
111+
setCurrentDrillIndex(0)
107112
}
108-
}, [allSelections, remainingDrills.length])
113+
}, [configuration.drillSequence, remainingDrills.length])
109114

110115
// Initialize current drill game when drill changes
111116
useEffect(() => {
@@ -284,6 +289,10 @@ export const useOpeningDrillController = (
284289
const performanceData = evaluateDrillPerformance(currentDrillGame)
285290
setCurrentPerformanceData(performanceData)
286291
setCompletedDrills((prev) => [...prev, performanceData.drill])
292+
293+
// Move drill from remaining to completed
294+
setRemainingDrills((prev) => prev.slice(1))
295+
287296
setShowPerformanceModal(true)
288297
}, [currentDrillGame, evaluateDrillPerformance])
289298

@@ -293,16 +302,17 @@ export const useOpeningDrillController = (
293302
setCurrentPerformanceData(null)
294303
setContinueAnalyzingMode(false) // Reset continue analyzing mode for next drill
295304

296-
const newRemaining = remainingDrills.slice(1)
297-
setRemainingDrills(newRemaining)
305+
const nextIndex = currentDrillIndex + 1
298306

299-
if (newRemaining.length > 0) {
300-
setCurrentDrill(newRemaining[0])
307+
// The drill has already been moved from remaining to completed in completeDrill()
308+
if (nextIndex < configuration.drillSequence.length) {
309+
setCurrentDrill(configuration.drillSequence[nextIndex])
310+
setCurrentDrillIndex(nextIndex)
301311
} else {
302312
// All drills completed - show final modal
303313
setShowFinalModal(true)
304314
}
305-
}, [remainingDrills])
315+
}, [currentDrillIndex, configuration.drillSequence])
306316

307317
// Continue analyzing current drill
308318
const continueAnalyzing = useCallback(() => {
@@ -315,7 +325,7 @@ export const useOpeningDrillController = (
315325
const overallPerformanceData = useMemo((): OverallPerformanceData => {
316326
if (completedDrills.length === 0) {
317327
return {
318-
totalDrills: allSelections.length,
328+
totalDrills: configuration.drillCount,
319329
completedDrills: [],
320330
overallAccuracy: 0,
321331
totalBlunders: 0,
@@ -369,7 +379,7 @@ export const useOpeningDrillController = (
369379
}, completedDrills[0])
370380

371381
return {
372-
totalDrills: allSelections.length,
382+
totalDrills: configuration.drillCount,
373383
completedDrills,
374384
overallAccuracy,
375385
totalBlunders,
@@ -378,7 +388,7 @@ export const useOpeningDrillController = (
378388
worstPerformance,
379389
averageEvaluation,
380390
}
381-
}, [completedDrills, allSelections.length])
391+
}, [completedDrills, configuration.drillCount])
382392

383393
// Make a move for the player - enhanced to support variations and completion checking
384394
const makePlayerMove = useCallback(
@@ -685,6 +695,8 @@ export const useOpeningDrillController = (
685695
remainingDrills,
686696
completedDrills,
687697
currentDrillGame,
698+
currentDrillIndex,
699+
totalDrills: configuration.drillCount,
688700
isPlayerTurn,
689701
isDrillComplete,
690702
isAtOpeningEnd,

0 commit comments

Comments
 (0)