@@ -37,6 +37,7 @@ import {
3737 AuthenticatedWrapper ,
3838 MovesContainer ,
3939 BoardController ,
40+ PromotionOverlay ,
4041} from 'src/components'
4142import Head from 'next/head'
4243import 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