Skip to content

Commit 45907bb

Browse files
feat: improved learn from mistake behaviour + ui under game board
1 parent fee8f09 commit 45907bb

File tree

4 files changed

+188
-62
lines changed

4 files changed

+188
-62
lines changed

src/components/Analysis/ConfigurableScreens.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { motion } from 'framer-motion'
22
import React, { useState } from 'react'
33
import { ConfigureAnalysis } from 'src/components/Analysis/ConfigureAnalysis'
4+
import { LearnFromMistakes } from 'src/components/Analysis/LearnFromMistakes'
45
import { ExportGame } from 'src/components/Common/ExportGame'
5-
import { AnalyzedGame, GameNode } from 'src/types'
6+
import {
7+
AnalyzedGame,
8+
GameNode,
9+
LearnFromMistakesState,
10+
MistakePosition,
11+
} from 'src/types'
612

713
interface Props {
814
currentMaiaModel: string
@@ -21,6 +27,17 @@ interface Props {
2127
isSaving: boolean
2228
status: 'saving' | 'unsaved' | 'saved'
2329
}
30+
// Learn from mistakes props
31+
learnFromMistakesState?: LearnFromMistakesState
32+
learnFromMistakesCurrentInfo?: {
33+
mistake: MistakePosition
34+
progress: string
35+
isLastMistake: boolean
36+
} | null
37+
onShowSolution?: () => void
38+
onNextMistake?: () => void
39+
onStopLearnFromMistakes?: () => void
40+
lastMoveResult?: 'correct' | 'incorrect' | 'not-learning'
2441
}
2542

2643
export const ConfigurableScreens: React.FC<Props> = ({
@@ -36,6 +53,12 @@ export const ConfigurableScreens: React.FC<Props> = ({
3653
isAnalysisInProgress,
3754
isLearnFromMistakesActive,
3855
autoSave,
56+
learnFromMistakesState,
57+
learnFromMistakesCurrentInfo,
58+
onShowSolution,
59+
onNextMistake,
60+
onStopLearnFromMistakes,
61+
lastMoveResult,
3962
}) => {
4063
const screens = [
4164
{
@@ -50,6 +73,44 @@ export const ConfigurableScreens: React.FC<Props> = ({
5073

5174
const [screen, setScreen] = useState(screens[0])
5275

76+
// If learn from mistakes is active, show only the learning interface
77+
if (
78+
isLearnFromMistakesActive &&
79+
learnFromMistakesState &&
80+
learnFromMistakesCurrentInfo
81+
) {
82+
return (
83+
<div className="flex w-full flex-1 flex-col overflow-hidden bg-background-1/60 md:w-auto md:rounded">
84+
<div className="red-scrollbar flex flex-1 flex-col items-start justify-start overflow-y-scroll bg-backdrop/30 p-3">
85+
<LearnFromMistakes
86+
state={learnFromMistakesState}
87+
currentInfo={learnFromMistakesCurrentInfo}
88+
onShowSolution={
89+
onShowSolution ||
90+
(() => {
91+
/* no-op */
92+
})
93+
}
94+
onNext={
95+
onNextMistake ||
96+
(() => {
97+
/* no-op */
98+
})
99+
}
100+
onStop={
101+
onStopLearnFromMistakes ||
102+
(() => {
103+
/* no-op */
104+
})
105+
}
106+
lastMoveResult={lastMoveResult || 'not-learning'}
107+
/>
108+
</div>
109+
</div>
110+
)
111+
}
112+
113+
// Normal state with configure/export tabs
53114
return (
54115
<div className="flex w-full flex-1 flex-col overflow-hidden bg-background-1/60 md:w-auto md:rounded">
55116
<div className="flex flex-row border-b border-white/10">

src/components/Analysis/LearnFromMistakes.tsx

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export const LearnFromMistakes: React.FC<Props> = ({
2929
const { mistake, progress, isLastMistake } = currentInfo
3030

3131
const getMoveDisplay = () => {
32-
const moveNumber = Math.ceil((mistake.moveIndex + 1) / 2)
33-
const isWhiteMove = mistake.moveIndex % 2 === 0
32+
const moveNumber = Math.ceil(mistake.moveIndex / 2)
33+
const isWhiteMove = mistake.playerColor === 'white'
3434

3535
if (isWhiteMove) {
3636
return `${moveNumber}. ${mistake.san}`
@@ -49,7 +49,11 @@ export const LearnFromMistakes: React.FC<Props> = ({
4949

5050
const getFeedbackText = () => {
5151
if (state.showSolution) {
52-
return `Correct! ${mistake.bestMoveSan} was the best move.`
52+
if (lastMoveResult === 'correct') {
53+
return `Correct! ${mistake.bestMoveSan} was the best move.`
54+
} else {
55+
return `The best move was ${mistake.bestMoveSan}.`
56+
}
5357
}
5458

5559
if (lastMoveResult === 'incorrect') {
@@ -62,7 +66,7 @@ export const LearnFromMistakes: React.FC<Props> = ({
6266
}
6367

6468
return (
65-
<div className="fixed bottom-4 left-1/2 z-50 w-full max-w-2xl -translate-x-1/2 transform rounded-lg border border-white/10 bg-background-1 p-4 shadow-lg">
69+
<div className="flex w-full flex-col gap-3">
6670
<div className="flex flex-col gap-3">
6771
{/* Header */}
6872
<div className="flex items-center justify-between">
@@ -83,7 +87,7 @@ export const LearnFromMistakes: React.FC<Props> = ({
8387
</div>
8488

8589
{/* Main prompt */}
86-
<div className="rounded bg-background-2/50 p-3">
90+
<div>
8791
<p className="text-sm text-primary">{getPromptText()}</p>
8892
{getFeedbackText() && (
8993
<p
@@ -102,16 +106,29 @@ export const LearnFromMistakes: React.FC<Props> = ({
102106

103107
{/* Action buttons */}
104108
<div className="flex gap-2">
105-
{!state.showSolution ? (
106-
<button
107-
onClick={onShowSolution}
108-
className="flex items-center gap-1.5 rounded bg-human-4/60 px-3 py-1.5 text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary"
109-
>
110-
<span className="material-symbols-outlined !text-sm">
111-
lightbulb
112-
</span>
113-
Show me the solution
114-
</button>
109+
{!state.showSolution && lastMoveResult !== 'correct' ? (
110+
<>
111+
<button
112+
onClick={onShowSolution}
113+
className="flex items-center gap-1.5 rounded bg-human-4/60 px-3 py-1.5 text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary"
114+
>
115+
<span className="material-symbols-outlined !text-sm">
116+
lightbulb
117+
</span>
118+
Show me the solution
119+
</button>
120+
{!isLastMistake && (
121+
<button
122+
onClick={onNext}
123+
className="flex items-center gap-1.5 rounded bg-background-2 px-3 py-1.5 text-sm text-secondary transition duration-200 hover:bg-background-3 hover:text-primary"
124+
>
125+
<span className="material-symbols-outlined !text-sm">
126+
skip_next
127+
</span>
128+
Skip to next mistake
129+
</button>
130+
)}
131+
</>
115132
) : (
116133
<button
117134
onClick={onNext}
@@ -123,18 +140,6 @@ export const LearnFromMistakes: React.FC<Props> = ({
123140
{isLastMistake ? 'Finish' : 'Next mistake'}
124141
</button>
125142
)}
126-
127-
{state.showSolution && !isLastMistake && (
128-
<button
129-
onClick={onNext}
130-
className="flex items-center gap-1.5 rounded bg-background-2 px-3 py-1.5 text-sm text-secondary transition duration-200 hover:bg-background-3 hover:text-primary"
131-
>
132-
<span className="material-symbols-outlined !text-sm">
133-
skip_next
134-
</span>
135-
Skip to next
136-
</button>
137-
)}
138143
</div>
139144
</div>
140145
</div>

src/components/Board/MovesContainer.tsx

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface AnalysisProps {
1515
showAnnotations?: boolean
1616
showVariations?: boolean
1717
disableKeyboardNavigation?: boolean
18+
disableMoveClicking?: boolean
1819
}
1920

2021
interface TuringProps {
@@ -25,6 +26,7 @@ interface TuringProps {
2526
showAnnotations?: boolean
2627
showVariations?: boolean
2728
disableKeyboardNavigation?: boolean
29+
disableMoveClicking?: boolean
2830
}
2931

3032
interface PlayProps {
@@ -35,6 +37,7 @@ interface PlayProps {
3537
showAnnotations?: boolean
3638
showVariations?: boolean
3739
disableKeyboardNavigation?: boolean
40+
disableMoveClicking?: boolean
3841
}
3942

4043
type Props = AnalysisProps | TuringProps | PlayProps
@@ -70,6 +73,7 @@ export const MovesContainer: React.FC<Props> = (props) => {
7073
showAnnotations = true,
7174
showVariations = true,
7275
disableKeyboardNavigation = false,
76+
disableMoveClicking = false,
7377
} = props
7478
const { isMobile } = useContext(WindowSizeContext)
7579
const containerRef = useRef<HTMLDivElement>(null)
@@ -224,9 +228,11 @@ export const MovesContainer: React.FC<Props> = (props) => {
224228
? currentMoveRef
225229
: null
226230
}
227-
onClick={() =>
228-
baseController.goToNode(pair.whiteMove as GameNode)
229-
}
231+
onClick={() => {
232+
if (!disableMoveClicking) {
233+
baseController.goToNode(pair.whiteMove as GameNode)
234+
}
235+
}}
230236
className={`flex min-w-fit cursor-pointer flex-row items-center rounded px-2 py-1 text-sm ${
231237
baseController.currentNode === pair.whiteMove
232238
? 'bg-human-4/20'
@@ -262,9 +268,11 @@ export const MovesContainer: React.FC<Props> = (props) => {
262268
? currentMoveRef
263269
: null
264270
}
265-
onClick={() =>
266-
baseController.goToNode(pair.blackMove as GameNode)
267-
}
271+
onClick={() => {
272+
if (!disableMoveClicking) {
273+
baseController.goToNode(pair.blackMove as GameNode)
274+
}
275+
}}
268276
className={`flex min-w-fit cursor-pointer flex-row items-center rounded px-2 py-1 text-sm ${
269277
baseController.currentNode === pair.blackMove
270278
? 'bg-human-4/20'
@@ -298,9 +306,13 @@ export const MovesContainer: React.FC<Props> = (props) => {
298306
{termination && (
299307
<div
300308
className="min-w-fit cursor-pointer border border-primary/10 bg-background-1/90 px-4 py-1 text-sm opacity-90"
301-
onClick={() =>
302-
baseController.goToNode(mainLineNodes[mainLineNodes.length - 1])
303-
}
309+
onClick={() => {
310+
if (!disableMoveClicking) {
311+
baseController.goToNode(
312+
mainLineNodes[mainLineNodes.length - 1],
313+
)
314+
}
315+
}}
304316
>
305317
{termination.result}
306318
{', '}
@@ -330,7 +342,9 @@ export const MovesContainer: React.FC<Props> = (props) => {
330342
baseController.currentNode === whiteNode ? currentMoveRef : null
331343
}
332344
onClick={() => {
333-
if (whiteNode) baseController.goToNode(whiteNode)
345+
if (whiteNode && !disableMoveClicking) {
346+
baseController.goToNode(whiteNode)
347+
}
334348
}}
335349
data-index={index * 2 + 1}
336350
className={`col-span-2 flex h-7 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-background-2 ${baseController.currentNode === whiteNode && 'bg-human-4/10'} ${highlightSet.has(index * 2 + 1) && 'bg-human-3/80'}`}
@@ -363,14 +377,17 @@ export const MovesContainer: React.FC<Props> = (props) => {
363377
currentNode={baseController.currentNode}
364378
goToNode={baseController.goToNode}
365379
showAnnotations={showAnnotations}
380+
disableMoveClicking={disableMoveClicking}
366381
/>
367382
) : null}
368383
<div
369384
ref={
370385
baseController.currentNode === blackNode ? currentMoveRef : null
371386
}
372387
onClick={() => {
373-
if (blackNode) baseController.goToNode(blackNode)
388+
if (blackNode && !disableMoveClicking) {
389+
baseController.goToNode(blackNode)
390+
}
374391
}}
375392
data-index={index * 2 + 2}
376393
className={`col-span-2 flex h-7 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-background-2 ${baseController.currentNode === blackNode && 'bg-human-4/10'} ${highlightSet.has(index * 2 + 2) && 'bg-human-3/80'}`}
@@ -403,6 +420,7 @@ export const MovesContainer: React.FC<Props> = (props) => {
403420
currentNode={baseController.currentNode}
404421
goToNode={baseController.goToNode}
405422
showAnnotations={showAnnotations}
423+
disableMoveClicking={disableMoveClicking}
406424
/>
407425
) : null}
408426
</>
@@ -411,9 +429,11 @@ export const MovesContainer: React.FC<Props> = (props) => {
411429
{termination && !isMobile && (
412430
<div
413431
className="col-span-5 cursor-pointer rounded-sm border border-primary/10 bg-background-1/90 p-5 text-center opacity-90"
414-
onClick={() =>
415-
baseController.goToNode(mainLineNodes[mainLineNodes.length - 1])
416-
}
432+
onClick={() => {
433+
if (!disableMoveClicking) {
434+
baseController.goToNode(mainLineNodes[mainLineNodes.length - 1])
435+
}
436+
}}
417437
>
418438
{termination.result}
419439
{', '}
@@ -432,12 +452,14 @@ function FirstVariation({
432452
currentNode,
433453
goToNode,
434454
showAnnotations,
455+
disableMoveClicking = false,
435456
}: {
436457
node: GameNode
437458
color: 'white' | 'black'
438459
currentNode: GameNode | undefined
439460
goToNode: (node: GameNode) => void
440461
showAnnotations: boolean
462+
disableMoveClicking?: boolean
441463
}) {
442464
return (
443465
<>
@@ -452,6 +474,7 @@ function FirstVariation({
452474
goToNode={goToNode}
453475
level={0}
454476
showAnnotations={showAnnotations}
477+
disableMoveClicking={disableMoveClicking}
455478
/>
456479
))}
457480
</ul>
@@ -487,7 +510,11 @@ function VariationTree({
487510
return (
488511
<li className={`tree-li ${level === 0 ? 'no-tree-connector' : ''}`}>
489512
<span
490-
onClick={() => goToNode(node)}
513+
onClick={() => {
514+
if (!disableMoveClicking) {
515+
goToNode(node)
516+
}
517+
}}
491518
className={`cursor-pointer px-0.5 text-xs ${
492519
currentNode?.fen === node?.fen
493520
? 'rounded bg-human-4/20 text-primary'
@@ -520,6 +547,7 @@ function VariationTree({
520547
goToNode={goToNode}
521548
level={level}
522549
showAnnotations={showAnnotations}
550+
disableMoveClicking={disableMoveClicking}
523551
/>
524552
</span>
525553
) : variations.length > 1 ? (
@@ -532,6 +560,7 @@ function VariationTree({
532560
goToNode={goToNode}
533561
level={level + 1}
534562
showAnnotations={showAnnotations}
563+
disableMoveClicking={disableMoveClicking}
535564
/>
536565
))}
537566
</ul>
@@ -577,7 +606,11 @@ function InlineChain({
577606
{chain.map((child) => (
578607
<Fragment key={child.move}>
579608
<span
580-
onClick={() => goToNode(child)}
609+
onClick={() => {
610+
if (!disableMoveClicking) {
611+
goToNode(child)
612+
}
613+
}}
581614
className={`cursor-pointer px-0.5 text-xs ${
582615
currentNode?.fen === child?.fen
583616
? 'rounded bg-human-4/50 text-primary'
@@ -617,6 +650,7 @@ function InlineChain({
617650
goToNode={goToNode}
618651
level={level + 1}
619652
showAnnotations={showAnnotations}
653+
disableMoveClicking={disableMoveClicking}
620654
/>
621655
))}
622656
</ul>

0 commit comments

Comments
 (0)