Skip to content

Commit 85fceab

Browse files
feat: add first iteration of analysis in puzzle page
1 parent 67c25e1 commit 85fceab

4 files changed

Lines changed: 893 additions & 156 deletions

File tree

src/components/Training/Feedback.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Chess } from 'chess.ts'
2-
import { useMemo, Dispatch, SetStateAction, useCallback } from 'react'
2+
import { useMemo, Dispatch, SetStateAction } from 'react'
33

44
import { Markdown } from 'src/components'
55
import { useTrainingController } from 'src/hooks'
@@ -65,11 +65,11 @@ export const Feedback: React.FC<Props> = ({
6565
}, [defaultContent, incorrectContent, correctContent, status, targetIndex])
6666

6767
return (
68-
<div className="flex w-screen flex-1 flex-col justify-between gap-2 rounded-sm bg-background-1 p-3 md:w-auto md:gap-0 md:p-5">
68+
<div className="flex w-screen flex-1 flex-col rounded-sm bg-background-1 p-3 md:w-auto md:p-5 lg:justify-between">
6969
<div>
7070
<Markdown>{content.trim()}</Markdown>
7171
</div>
72-
<div className="flex flex-col gap-1.5">
72+
<div className="mt-2 flex min-w-32 flex-row gap-1.5 lg:mt-0 lg:flex-col">
7373
{status !== 'archived' && (
7474
<>
7575
{status === 'incorrect' && (
@@ -85,8 +85,13 @@ export const Feedback: React.FC<Props> = ({
8585
)}
8686
{status !== 'correct' && status !== 'incorrect' && (
8787
<button
88-
onClick={() => controller.reset()}
89-
disabled={status == 'loading'}
88+
onClick={() => {
89+
controller.reset()
90+
if (status !== 'success' && status !== 'forfeit') {
91+
setStatus('default')
92+
}
93+
}}
94+
disabled={status == 'loading' || status == 'default'}
9095
className="flex w-full justify-center rounded-sm bg-engine-3 py-1.5 text-sm font-medium text-primary transition duration-300 hover:bg-engine-4 disabled:bg-backdrop disabled:text-secondary"
9196
>
9297
Reset

src/hooks/useTrainingController/useTrainingController.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,23 @@ const buildTrainingGameTree = (game: TrainingGame): GameTree => {
3030

3131
export const useTrainingController = (game: TrainingGame) => {
3232
const gameTree = useMemo(() => buildTrainingGameTree(game), [game])
33-
const initialOrientation = useMemo(
34-
() => (game.targetIndex % 2 === 0 ? 'white' : 'black'),
35-
[game.targetIndex],
36-
)
33+
const initialOrientation = useMemo(() => {
34+
const puzzleFen = game.moves[game.targetIndex].board
35+
const chess = new Chess(puzzleFen)
36+
return chess.turn() === 'w' ? 'white' : 'black'
37+
}, [game.targetIndex, game.moves])
3738
const controller = useTreeController(gameTree, initialOrientation)
3839

3940
const puzzleStartingNode = useMemo(() => {
40-
return gameTree.getRoot()
41+
let node = gameTree.getRoot()
42+
for (let i = 1; i <= game.targetIndex; i++) {
43+
if (node.mainChild) {
44+
node = node.mainChild
45+
} else {
46+
break
47+
}
48+
}
49+
return node
4150
}, [gameTree, game.targetIndex])
4251

4352
useEffect(() => {

src/pages/analysis/[...id].tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
AuthenticatedWrapper,
3838
MovesContainer,
3939
BoardController,
40+
PromotionOverlay,
4041
} from 'src/components'
4142
import Head from 'next/head'
4243
import toast from 'react-hot-toast'
@@ -241,6 +242,9 @@ const Analysis: React.FC<Props> = ({
241242
const [screen, setScreen] = useState(screens[0])
242243
const toastId = useRef<string>(null)
243244
const [currentSquare, setCurrentSquare] = useState<Key | null>(null)
245+
const [promotionFromTo, setPromotionFromTo] = useState<
246+
[string, string] | null
247+
>(null)
244248

245249
const controller = useAnalysisController(analyzedGame)
246250

@@ -363,6 +367,51 @@ const Analysis: React.FC<Props> = ({
363367
}
364368
}
365369

370+
const onPlayerMakeMove = useCallback(
371+
(playedMove: [string, string] | null) => {
372+
if (!playedMove) return
373+
374+
// Check for promotions in available moves
375+
const availableMoves = Array.from(controller.moves.entries()).flatMap(
376+
([from, tos]) => tos.map((to) => ({ from, to })),
377+
)
378+
379+
const matching = availableMoves.filter((m) => {
380+
return m.from === playedMove[0] && m.to === playedMove[1]
381+
})
382+
383+
if (matching.length > 1) {
384+
// Multiple matching moves (i.e. promotion)
385+
setPromotionFromTo(playedMove)
386+
return
387+
}
388+
389+
// Single move
390+
const moveUci = playedMove[0] + playedMove[1]
391+
makeMove(moveUci)
392+
},
393+
[controller.moves, makeMove],
394+
)
395+
396+
const onPlayerSelectPromotion = useCallback(
397+
(piece: string) => {
398+
if (!promotionFromTo) {
399+
return
400+
}
401+
setPromotionFromTo(null)
402+
const moveUci = promotionFromTo[0] + promotionFromTo[1] + piece
403+
makeMove(moveUci)
404+
},
405+
[promotionFromTo, setPromotionFromTo, makeMove],
406+
)
407+
408+
// Determine current player for promotion overlay
409+
const currentPlayer = useMemo(() => {
410+
if (!controller.currentNode) return 'white'
411+
const chess = new Chess(controller.currentNode.fen)
412+
return chess.turn() === 'w' ? 'white' : 'black'
413+
}, [controller.currentNode])
414+
366415
const Player = ({
367416
name,
368417
rating,
@@ -533,9 +582,17 @@ const Analysis: React.FC<Props> = ({
533582
shapes={hoverArrow ? [...arrows, hoverArrow] : [...arrows]}
534583
currentNode={controller.currentNode as GameNode}
535584
orientation={controller.orientation}
585+
onPlayerMakeMove={onPlayerMakeMove}
536586
goToNode={controller.goToNode}
537587
gameTree={analyzedGame.tree}
538588
/>
589+
{promotionFromTo ? (
590+
<PromotionOverlay
591+
player={currentPlayer}
592+
file={promotionFromTo[1].slice(0, 1)}
593+
onPlayerSelectPromotion={onPlayerSelectPromotion}
594+
/>
595+
) : null}
539596
</div>
540597
<Player
541598
name={
@@ -674,9 +731,17 @@ const Analysis: React.FC<Props> = ({
674731
shapes={hoverArrow ? [...arrows, hoverArrow] : [...arrows]}
675732
currentNode={controller.currentNode as GameNode}
676733
orientation={controller.orientation}
734+
onPlayerMakeMove={onPlayerMakeMove}
677735
goToNode={controller.goToNode}
678736
gameTree={analyzedGame.tree}
679737
/>
738+
{promotionFromTo ? (
739+
<PromotionOverlay
740+
player={currentPlayer}
741+
file={promotionFromTo[1].slice(0, 1)}
742+
onPlayerSelectPromotion={onPlayerSelectPromotion}
743+
/>
744+
) : null}
680745
</div>
681746
<div className="flex w-full flex-col gap-0">
682747
<div className="w-full !flex-grow-0">

0 commit comments

Comments
 (0)