@@ -4,27 +4,174 @@ interface PlayerInfoProps {
44 rating ?: number
55 termination ?: string
66 showArrowLegend ?: boolean
7+ currentFen ?: string
8+ orientation ?: 'white' | 'black'
79 clock ?: {
810 timeInSeconds : number
911 isActive : boolean
1012 lastUpdateTime : number
1113 }
1214}
1315
14- import { useState , useEffect } from 'react'
16+ import { useState , useEffect , useMemo } from 'react'
17+ import { Chess } from 'chess.ts'
18+
19+ type PieceType = 'p' | 'n' | 'b' | 'r' | 'q' | 'k'
20+ type MaterialCount = Record < PieceType , number >
21+
22+ const PIECE_VALUES : Record < string , number > = {
23+ p : 1 , // pawn
24+ n : 3 , // knight
25+ b : 3 , // bishop
26+ r : 5 , // rook
27+ q : 9 , // queen
28+ k : 0 , // king (not counted)
29+ }
30+
31+ const STARTING_MATERIAL : { white : MaterialCount ; black : MaterialCount } = {
32+ white : { p : 8 , n : 2 , b : 2 , r : 2 , q : 1 , k : 1 } ,
33+ black : { p : 8 , n : 2 , b : 2 , r : 2 , q : 1 , k : 1 } ,
34+ }
35+
36+ const calculateCapturedPieces = ( fen ?: string ) => {
37+ if ( ! fen ) return { white : { } , black : { } }
38+
39+ const chess = new Chess ( fen )
40+ const board = chess . board ( )
41+
42+ // Count current pieces on board
43+ const currentMaterial : { white : MaterialCount ; black : MaterialCount } = {
44+ white : { p : 0 , n : 0 , b : 0 , r : 0 , q : 0 , k : 0 } ,
45+ black : { p : 0 , n : 0 , b : 0 , r : 0 , q : 0 , k : 0 } ,
46+ }
47+
48+ for ( const row of board ) {
49+ for ( const square of row ) {
50+ if ( square ) {
51+ const piece = square . type . toLowerCase ( ) as PieceType
52+ const color = square . color === 'w' ? 'white' : 'black'
53+ currentMaterial [ color ] [ piece ] ++
54+ }
55+ }
56+ }
57+
58+ // Calculate captured pieces (starting - current)
59+ const captured = {
60+ white : { } as Record < string , number > ,
61+ black : { } as Record < string , number > ,
62+ }
63+
64+ for ( const piece of Object . keys ( STARTING_MATERIAL . white ) as PieceType [ ] ) {
65+ const whiteCaptured =
66+ STARTING_MATERIAL . white [ piece ] - currentMaterial . white [ piece ]
67+ const blackCaptured =
68+ STARTING_MATERIAL . black [ piece ] - currentMaterial . black [ piece ]
69+
70+ if ( whiteCaptured > 0 ) captured . white [ piece ] = whiteCaptured
71+ if ( blackCaptured > 0 ) captured . black [ piece ] = blackCaptured
72+ }
73+
74+ return captured
75+ }
76+
77+ const calculateMaterialAdvantage = (
78+ fen ?: string ,
79+ ) : { white : number ; black : number } => {
80+ if ( ! fen ) return { white : 0 , black : 0 }
81+
82+ const chess = new Chess ( fen )
83+ const board = chess . board ( )
84+
85+ let whiteTotal = 0
86+ let blackTotal = 0
87+
88+ for ( const row of board ) {
89+ for ( const square of row ) {
90+ if ( square ) {
91+ const piece = square . type . toLowerCase ( )
92+ const value = PIECE_VALUES [ piece ] || 0
93+ if ( square . color === 'w' ) {
94+ whiteTotal += value
95+ } else {
96+ blackTotal += value
97+ }
98+ }
99+ }
100+ }
101+
102+ return { white : whiteTotal , black : blackTotal }
103+ }
15104
16105export const PlayerInfo : React . FC < PlayerInfoProps > = ( {
17106 name,
18107 rating,
19108 color,
20109 termination,
21110 showArrowLegend = false ,
111+ currentFen,
112+ orientation = 'white' ,
22113 clock,
23114} ) => {
24115 const [ currentTime , setCurrentTime ] = useState < number > (
25116 clock ?. timeInSeconds || 0 ,
26117 )
27118
119+ // Calculate captured pieces and material advantage
120+ const capturedPieces = useMemo (
121+ ( ) => calculateCapturedPieces ( currentFen ) ,
122+ [ currentFen ] ,
123+ )
124+ const materialAdvantage = useMemo (
125+ ( ) => calculateMaterialAdvantage ( currentFen ) ,
126+ [ currentFen ] ,
127+ )
128+
129+ // Get pieces captured by this player (pieces of opposite color that were captured)
130+ const myCapturedPieces =
131+ color === 'white' ? capturedPieces . black : capturedPieces . white
132+
133+ // Calculate score advantage for this player
134+ const myAdvantage =
135+ materialAdvantage [ color as 'white' | 'black' ] -
136+ materialAdvantage [ color === 'white' ? 'black' : 'white' ]
137+
138+ // Map chess pieces to Material UI icons
139+ const getPieceIcon = ( piece : string ) : string => {
140+ const iconMap : Record < string , string > = {
141+ p : 'chess_pawn' ,
142+ n : 'chess_knight' ,
143+ b : 'chess_bishop' ,
144+ r : 'chess_rook' ,
145+ q : 'chess' , // queen uses 'chess' icon
146+ }
147+ return iconMap [ piece ] || 'chess'
148+ }
149+
150+ // Render captured pieces
151+ const renderCapturedPieces = ( ) => {
152+ const pieces : React . JSX . Element [ ] = [ ]
153+
154+ // Order pieces by value (lowest to highest)
155+ const orderedPieces = [ 'p' , 'n' , 'b' , 'r' , 'q' ]
156+
157+ orderedPieces . forEach ( ( piece ) => {
158+ const count = myCapturedPieces [ piece ] || 0
159+ for ( let i = 0 ; i < count ; i ++ ) {
160+ pieces . push (
161+ < span
162+ key = { `${ piece } -${ i } ` }
163+ className = "material-symbols-outlined text-sm text-secondary"
164+ title = { `${ piece === 'p' ? 'pawn' : piece === 'n' ? 'knight' : piece === 'b' ? 'bishop' : piece === 'r' ? 'rook' : 'queen' } ` }
165+ >
166+ { getPieceIcon ( piece ) }
167+ </ span > ,
168+ )
169+ }
170+ } )
171+
172+ return pieces
173+ }
174+
28175 // Update clock countdown every second if this clock is active
29176 useEffect ( ( ) => {
30177 if ( ! clock || ! clock . isActive ) return
@@ -83,6 +230,24 @@ export const PlayerInfo: React.FC<PlayerInfoProps> = ({
83230 </ div >
84231 </ div >
85232 ) }
233+
234+ { /* Captured pieces and material advantage */ }
235+ { currentFen && (
236+ < div className = "flex items-center gap-2" >
237+ { /* Captured pieces icons */ }
238+ < div className = "flex items-center gap-0.5" >
239+ { renderCapturedPieces ( ) }
240+ </ div >
241+
242+ { /* Material advantage score */ }
243+ { myAdvantage !== 0 && (
244+ < span className = "text-xs font-medium text-primary" >
245+ +{ Math . abs ( myAdvantage ) }
246+ </ span >
247+ ) }
248+ </ div >
249+ ) }
250+
86251 { clock && (
87252 < div
88253 className = { `flex items-center px-2 py-1 ${
0 commit comments