1+ function renderer ( options ) {
2+ const { environment, step, parent, interactive, isInteractive } = options ;
3+
4+ // Chess-specific constants
5+ const DEFAULT_NUM_ROWS = 8 ;
6+ const DEFAULT_NUM_COLS = 8 ;
7+ const PIECE_SVG_URLS = {
8+ 'p' : 'https://upload.wikimedia.org/wikipedia/commons/c/c7/Chess_pdt45.svg' , // Black Pawn
9+ 'r' : 'https://upload.wikimedia.org/wikipedia/commons/f/ff/Chess_rdt45.svg' , // Black Rook
10+ 'n' : 'https://upload.wikimedia.org/wikipedia/commons/e/ef/Chess_ndt45.svg' , // Black Knight
11+ 'b' : 'https://upload.wikimedia.org/wikipedia/commons/9/98/Chess_bdt45.svg' , // Black Bishop
12+ 'q' : 'https://upload.wikimedia.org/wikipedia/commons/4/47/Chess_qdt45.svg' , // Black Queen
13+ 'k' : 'https://upload.wikimedia.org/wikipedia/commons/f/f0/Chess_kdt45.svg' , // Black King
14+ 'P' : 'https://upload.wikimedia.org/wikipedia/commons/4/45/Chess_plt45.svg' , // White Pawn
15+ 'R' : 'https://upload.wikimedia.org/wikipedia/commons/7/72/Chess_rlt45.svg' , // White Rook
16+ 'N' : 'https://upload.wikimedia.org/wikipedia/commons/7/70/Chess_nlt45.svg' , // White Knight
17+ 'B' : 'https://upload.wikimedia.org/wikipedia/commons/b/b1/Chess_blt45.svg' , // White Bishop
18+ 'Q' : 'https://upload.wikimedia.org/wikipedia/commons/1/15/Chess_qlt45.svg' , // White Queen
19+ 'K' : 'https://upload.wikimedia.org/wikipedia/commons/4/42/Chess_klt45.svg' // White King
20+ } ;
21+ const LIGHT_SQUARE_COLOR = '#f0d9b5' ;
22+ const DARK_SQUARE_COLOR = '#b58863' ;
23+
24+ // Renderer state variables
25+ let currentBoardElement = null ;
26+ let currentStatusTextElement = null ;
27+ let currentWinnerTextElement = null ;
28+ let currentMessageBoxElement = typeof document !== 'undefined' ? document . getElementById ( 'messageBox' ) : null ;
29+ let currentRendererContainer = null ;
30+ let currentTitleElement = null ;
31+
32+ function _showMessage ( message , type = 'info' , duration = 3000 ) {
33+ if ( typeof document === 'undefined' || ! document . body ) return ;
34+ if ( ! currentMessageBoxElement ) {
35+ currentMessageBoxElement = document . createElement ( 'div' ) ;
36+ currentMessageBoxElement . id = 'messageBox' ;
37+ // Identical styling to the Connect Four renderer's message box
38+ Object . assign ( currentMessageBoxElement . style , {
39+ position : 'fixed' ,
40+ top : '10px' ,
41+ left : '50%' ,
42+ transform : 'translateX(-50%)' ,
43+ padding : '0.75rem 1rem' ,
44+ borderRadius : '0.375rem' ,
45+ boxShadow : '0 2px 4px rgba(0,0,0,0.1)' ,
46+ zIndex : '1000' ,
47+ opacity : '0' ,
48+ transition : 'opacity 0.3s ease-in-out, background-color 0.3s' ,
49+ fontSize : '0.875rem' ,
50+ fontFamily : "'Inter', sans-serif" ,
51+ color : 'white'
52+ } ) ;
53+ document . body . appendChild ( currentMessageBoxElement ) ;
54+ }
55+ currentMessageBoxElement . textContent = message ;
56+ currentMessageBoxElement . style . backgroundColor = type === 'error' ? '#ef4444' : '#10b981' ;
57+ currentMessageBoxElement . style . opacity = '1' ;
58+ setTimeout ( ( ) => { if ( currentMessageBoxElement ) currentMessageBoxElement . style . opacity = '0' ; } , duration ) ;
59+ }
60+
61+ function _ensureRendererElements ( parentElementToClear , rows , cols ) {
62+ if ( ! parentElementToClear ) return false ;
63+ parentElementToClear . innerHTML = '' ;
64+
65+ currentRendererContainer = document . createElement ( 'div' ) ;
66+ Object . assign ( currentRendererContainer . style , {
67+ display : 'flex' ,
68+ flexDirection : 'column' ,
69+ alignItems : 'center' ,
70+ padding : '20px' ,
71+ boxSizing : 'border-box' ,
72+ width : '100%' ,
73+ height : '100%' ,
74+ fontFamily : "'Inter', sans-serif"
75+ } ) ;
76+
77+ currentTitleElement = document . createElement ( 'h1' ) ;
78+ currentTitleElement . textContent = 'Chess' ;
79+ // Identical styling to the Connect Four renderer's title
80+ Object . assign ( currentTitleElement . style , {
81+ fontSize : '1.875rem' ,
82+ fontWeight : 'bold' ,
83+ marginBottom : '1rem' ,
84+ textAlign : 'center' ,
85+ color : '#2563eb'
86+ } ) ;
87+ currentRendererContainer . appendChild ( currentTitleElement ) ;
88+
89+ currentBoardElement = document . createElement ( 'div' ) ;
90+ Object . assign ( currentBoardElement . style , {
91+ display : 'grid' ,
92+ gridTemplateColumns : `repeat(${ cols } , 50px)` ,
93+ gridTemplateRows : `repeat(${ rows } , 50px)` ,
94+ width : `${ cols * 50 } px` ,
95+ height : `${ rows * 50 } px` ,
96+ border : '2px solid #333'
97+ } ) ;
98+
99+ for ( let r = 0 ; r < rows ; r ++ ) {
100+ for ( let c = 0 ; c < cols ; c ++ ) {
101+ const square = document . createElement ( 'div' ) ;
102+ square . id = `cell-${ r } -${ c } ` ;
103+ Object . assign ( square . style , {
104+ width : '50px' ,
105+ height : '50px' ,
106+ backgroundColor : ( r + c ) % 2 === 0 ? LIGHT_SQUARE_COLOR : DARK_SQUARE_COLOR ,
107+ display : 'flex' ,
108+ alignItems : 'center' ,
109+ justifyContent : 'center' ,
110+ } ) ;
111+ currentBoardElement . appendChild ( square ) ;
112+ }
113+ }
114+ currentRendererContainer . appendChild ( currentBoardElement ) ;
115+
116+ const statusContainer = document . createElement ( 'div' ) ;
117+ // Identical styling to the Connect Four renderer's status container
118+ Object . assign ( statusContainer . style , {
119+ padding : '10px 15px' ,
120+ backgroundColor : 'white' ,
121+ borderRadius : '8px' ,
122+ boxShadow : '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06)' ,
123+ textAlign : 'center' ,
124+ width : 'auto' ,
125+ minWidth : '200px' ,
126+ maxWidth : '90vw' ,
127+ marginTop : '20px'
128+ } ) ;
129+ currentRendererContainer . appendChild ( statusContainer ) ;
130+
131+ currentStatusTextElement = document . createElement ( 'p' ) ;
132+ Object . assign ( currentStatusTextElement . style , {
133+ fontSize : '1.1rem' ,
134+ fontWeight : '600' ,
135+ margin : '0 0 5px 0'
136+ } ) ;
137+ statusContainer . appendChild ( currentStatusTextElement ) ;
138+
139+ currentWinnerTextElement = document . createElement ( 'p' ) ;
140+ Object . assign ( currentWinnerTextElement . style , {
141+ fontSize : '1.25rem' ,
142+ fontWeight : '700' ,
143+ margin : '5px 0 0 0'
144+ } ) ;
145+ statusContainer . appendChild ( currentWinnerTextElement ) ;
146+
147+ parentElementToClear . appendChild ( currentRendererContainer ) ;
148+
149+ if ( typeof document !== 'undefined' && ! document . body . hasAttribute ( 'data-renderer-initialized' ) ) {
150+ document . body . setAttribute ( 'data-renderer-initialized' , 'true' ) ;
151+ }
152+ return true ;
153+ }
154+
155+ function _parseFen ( fen ) {
156+ if ( ! fen || typeof fen !== 'string' ) return null ;
157+
158+ const [ piecePlacement , activeColor , castling , enPassant , halfmoveClock , fullmoveNumber ] = fen . split ( ' ' ) ;
159+ const board = [ ] ;
160+ const rows = piecePlacement . split ( '/' ) ;
161+
162+ for ( const row of rows ) {
163+ const boardRow = [ ] ;
164+ for ( const char of row ) {
165+ if ( isNaN ( parseInt ( char ) ) ) {
166+ boardRow . push ( char ) ;
167+ } else {
168+ for ( let i = 0 ; i < parseInt ( char ) ; i ++ ) {
169+ boardRow . push ( null ) ;
170+ }
171+ }
172+ }
173+ board . push ( boardRow ) ;
174+ }
175+
176+ return {
177+ board,
178+ activeColor,
179+ castling,
180+ enPassant,
181+ halfmoveClock,
182+ fullmoveNumber
183+ } ;
184+ }
185+
186+
187+ function _renderBoardDisplay ( gameStateToDisplay , displayRows , displayCols ) {
188+ if ( ! currentBoardElement || ! currentStatusTextElement || ! currentWinnerTextElement ) return ;
189+
190+ // Clear board
191+ for ( let r = 0 ; r < displayRows ; r ++ ) {
192+ for ( let c = 0 ; c < displayCols ; c ++ ) {
193+ const squareElement = currentBoardElement . querySelector ( `#cell-${ r } -${ c } ` ) ;
194+ if ( squareElement ) {
195+ squareElement . innerHTML = '' ;
196+ }
197+ }
198+ }
199+
200+ if ( ! gameStateToDisplay || ! gameStateToDisplay . board ) {
201+ currentStatusTextElement . textContent = "Waiting for game data..." ;
202+ currentWinnerTextElement . textContent = "" ;
203+ return ;
204+ }
205+
206+
207+ const { board, activeColor, is_terminal, winner } = gameStateToDisplay ;
208+
209+ for ( let r_data = 0 ; r_data < displayRows ; r_data ++ ) {
210+ for ( let c_data = 0 ; c_data < displayCols ; c_data ++ ) {
211+ const piece = board [ r_data ] [ c_data ] ;
212+ const squareElement = currentBoardElement . querySelector ( `#cell-${ r_data } -${ c_data } ` ) ;
213+ if ( squareElement && piece ) {
214+ const pieceImg = document . createElement ( 'img' ) ;
215+ pieceImg . src = PIECE_SVG_URLS [ piece ] ;
216+ pieceImg . style . width = '45px' ;
217+ pieceImg . style . height = '45px' ;
218+ squareElement . appendChild ( pieceImg ) ;
219+ }
220+ }
221+ }
222+
223+ currentStatusTextElement . innerHTML = '' ;
224+ currentWinnerTextElement . innerHTML = '' ;
225+ if ( is_terminal ) {
226+ currentStatusTextElement . textContent = "Game Over!" ;
227+ if ( winner ) {
228+ if ( String ( winner ) . toLowerCase ( ) === 'draw' ) {
229+ currentWinnerTextElement . textContent = "It's a Draw!" ;
230+ } else {
231+ const winnerColor = String ( winner ) . toLowerCase ( ) === 'white' ? 'White' : 'Black' ;
232+ currentWinnerTextElement . innerHTML = `Winner: <span style="font-weight: bold;">${ winnerColor } </span>` ;
233+ }
234+ } else {
235+ currentWinnerTextElement . textContent = "Game ended." ;
236+ }
237+ } else {
238+ const playerColor = String ( activeColor ) . toLowerCase ( ) === 'w' ? 'White' : 'Black' ;
239+ currentStatusTextElement . innerHTML = `Current Player: <span style="font-weight: bold;">${ playerColor } </span>` ;
240+ }
241+ }
242+
243+ // --- Main execution logic ---
244+ if ( ! _ensureRendererElements ( parent , DEFAULT_NUM_ROWS , DEFAULT_NUM_COLS ) ) {
245+ if ( parent && typeof parent . innerHTML !== 'undefined' ) {
246+ parent . innerHTML = "<p style='color:red; font-family: sans-serif;'>Critical Error: Renderer element setup failed.</p>" ;
247+ }
248+ return ;
249+ }
250+
251+ if ( ! environment || ! environment . steps || ! environment . steps [ step ] ) {
252+ _renderBoardDisplay ( null , DEFAULT_NUM_ROWS , DEFAULT_NUM_COLS ) ;
253+ if ( currentStatusTextElement ) currentStatusTextElement . textContent = "Initializing environment..." ;
254+ return ;
255+ }
256+
257+ const currentStepAgents = environment . steps [ step ] ;
258+ if ( ! currentStepAgents || ! Array . isArray ( currentStepAgents ) || currentStepAgents . length === 0 ) {
259+ _renderBoardDisplay ( null , DEFAULT_NUM_ROWS , DEFAULT_NUM_COLS ) ;
260+ if ( currentStatusTextElement ) currentStatusTextElement . textContent = "Waiting for agent data..." ;
261+ return ;
262+ }
263+
264+ // In chess, observation is the same for both agents. We can take it from the first.
265+ const agent = currentStepAgents [ 0 ] ;
266+
267+ if ( ! agent || typeof agent . observation === 'undefined' ) {
268+ _renderBoardDisplay ( null , DEFAULT_NUM_ROWS , DEFAULT_NUM_COLS ) ;
269+ if ( currentStatusTextElement ) currentStatusTextElement . textContent = "Waiting for observation data..." ;
270+ return ;
271+ }
272+ const observationForRenderer = agent . observation ;
273+
274+ let gameSpecificState = null ;
275+
276+ if ( observationForRenderer && typeof observationForRenderer . observation_string === 'string' && observationForRenderer . observation_string . trim ( ) !== '' ) {
277+ try {
278+ const fen = observationForRenderer . observation_string ;
279+ const parsedFen = _parseFen ( fen ) ;
280+ if ( parsedFen ) {
281+ // Assuming `is_terminal` and `winner` are provided in the top-level observation
282+ gameSpecificState = {
283+ ...parsedFen ,
284+ is_terminal : observationForRenderer . is_terminal ,
285+ winner : observationForRenderer . winner
286+ } ;
287+ }
288+ } catch ( e ) {
289+ _showMessage ( "Error: Corrupted game state (obs_string)." , 'error' ) ;
290+ }
291+ }
292+
293+ _renderBoardDisplay ( gameSpecificState , DEFAULT_NUM_ROWS , DEFAULT_NUM_COLS ) ;
294+ }
0 commit comments