@@ -42,6 +42,7 @@ const state = {
4242 pendingPromotion : null , // { from, to } while waiting for user to pick
4343 capturedByWhite : [ ] , // pieces taken by white (black pieces lost)
4444 capturedByBlack : [ ] , // pieces taken by black (white pieces lost)
45+ sqlInputHasTemplate : false , // true while the SQL textarea shows an auto-filled template
4546} ;
4647
4748/* ─── Helpers ─────────────────────────────────────────────────── */
@@ -170,6 +171,8 @@ function updatePlayerBars() {
170171 document . getElementById ( 'blackBadge' ) . textContent = turn === 'b' ? 'Your turn' : '' ;
171172 document . getElementById ( 'whiteBar' ) . style . opacity = turn === 'w' ? '1' : '.65' ;
172173 document . getElementById ( 'blackBar' ) . style . opacity = turn === 'b' ? '1' : '.65' ;
174+ document . getElementById ( 'whiteBar' ) . classList . toggle ( 'active-turn' , turn === 'w' ) ;
175+ document . getElementById ( 'blackBar' ) . classList . toggle ( 'active-turn' , turn === 'b' ) ;
173176}
174177
175178function updateStatus ( ) {
@@ -222,6 +225,123 @@ function renderAll() {
222225 buildMoveHistory ( ) ;
223226}
224227
228+ /* ─── SQL Move Input ──────────────────────────────────────────── */
229+
230+ /**
231+ * Populate the SQL input textarea with a move template for the selected piece.
232+ */
233+ function fillSQLInputTemplate ( sqName , piece ) {
234+ const input = document . getElementById ( 'sqlMoveInput' ) ;
235+ if ( ! input ) return ;
236+ const color = piece . color === 'w' ? 'white' : 'black' ;
237+ input . value =
238+ `UPDATE chess_piece\n` +
239+ `SET position = '???'\n` +
240+ `WHERE position = '${ sqName } '\n` +
241+ ` AND color = '${ color } ';` ;
242+ state . sqlInputHasTemplate = true ;
243+ // Place cursor on the ??? so the user can immediately type the destination
244+ const idx = input . value . indexOf ( '???' ) ;
245+ input . focus ( ) ;
246+ input . setSelectionRange ( idx , idx + 3 ) ;
247+ clearSQLRunError ( ) ;
248+ }
249+
250+ /**
251+ * Parse a SQL string and extract { from, to } squares.
252+ * Accepted formats:
253+ * 1. UPDATE chess_piece SET position = 'e4' WHERE ... position = 'e2' ...
254+ * 2. Shorthand: e2 e4 / e2-e4 / e2 to e4
255+ */
256+ function parseSQLMove ( query ) {
257+ const q = query . trim ( ) ;
258+
259+ // Shorthand: "e2 e4", "e2-e4", "e2 to e4", "e2e4"
260+ const shorthand = q . match ( / ^ ( [ a - h ] [ 1 - 8 ] ) \s * (?: - | t o ) ? \s * ( [ a - h ] [ 1 - 8 ] ) $ / i) ;
261+ if ( shorthand ) {
262+ const from = shorthand [ 1 ] . toLowerCase ( ) ;
263+ const to = shorthand [ 2 ] . toLowerCase ( ) ;
264+ if ( from === to ) return null ;
265+ return { from, to } ;
266+ }
267+
268+ // Standard UPDATE … SET position = 'to' … WHERE … position = 'from'
269+ const setMatch = q . match ( / S E T \s + p o s i t i o n \s * = \s * ' ( [ a - h ] [ 1 - 8 ] ) ' / i) ;
270+ if ( ! setMatch ) return null ;
271+ const to = setMatch [ 1 ] . toLowerCase ( ) ;
272+
273+ const whereIdx = q . search ( / W H E R E / i) ;
274+ if ( whereIdx === - 1 ) return null ;
275+ const whereClause = q . slice ( whereIdx ) ;
276+ const fromMatch = whereClause . match ( / p o s i t i o n \s * = \s * ' ( [ a - h ] [ 1 - 8 ] ) ' / i) ;
277+ if ( ! fromMatch ) return null ;
278+ const from = fromMatch [ 1 ] . toLowerCase ( ) ;
279+
280+ if ( from === to ) return null ;
281+ return { from, to } ;
282+ }
283+
284+ function showSQLRunError ( msg ) {
285+ const el = document . getElementById ( 'sqlRunError' ) ;
286+ if ( ! el ) return ;
287+ el . textContent = msg ;
288+ el . classList . remove ( 'hidden' ) ;
289+ }
290+
291+ function clearSQLRunError ( ) {
292+ const el = document . getElementById ( 'sqlRunError' ) ;
293+ if ( el ) { el . textContent = '' ; el . classList . add ( 'hidden' ) ; }
294+ }
295+
296+ function runSQLMove ( ) {
297+ if ( ! state . chess ) { showSQLRunError ( 'No game in progress.' ) ; return ; }
298+ if ( state . chess . game_over ( ) ) { showSQLRunError ( 'Game is over.' ) ; return ; }
299+ if ( state . pendingPromotion ) { showSQLRunError ( 'Finish pawn promotion first.' ) ; return ; }
300+
301+ const input = document . getElementById ( 'sqlMoveInput' ) ;
302+ const query = input ? input . value : '' ;
303+ if ( ! query . trim ( ) ) { showSQLRunError ( 'Enter a SQL statement to move a piece.' ) ; return ; }
304+
305+ const parsed = parseSQLMove ( query ) ;
306+ if ( ! parsed ) {
307+ showSQLRunError ( 'Could not parse move. Use: UPDATE chess_piece SET position = \'e4\' WHERE position = \'e2\';' ) ;
308+ return ;
309+ }
310+
311+ const { from, to } = parsed ;
312+
313+ // Validate that there's a piece at `from` belonging to the current player
314+ const piece = state . chess . get ( from ) ;
315+ if ( ! piece ) { showSQLRunError ( `No piece found at ${ from } .` ) ; return ; }
316+ if ( piece . color !== state . chess . turn ( ) ) {
317+ showSQLRunError ( `It is ${ state . chess . turn ( ) === 'w' ? 'White' : 'Black' } 's turn.` ) ;
318+ return ;
319+ }
320+
321+ // Check if it's a promotion
322+ const validMoves = state . chess . moves ( { square : from , verbose : true } ) ;
323+ const moveObj = validMoves . find ( m => m . to === to ) ;
324+ if ( ! moveObj ) {
325+ showSQLRunError ( `Move ${ from } →${ to } is not legal.` ) ;
326+ return ;
327+ }
328+
329+ clearSQLRunError ( ) ;
330+ if ( input ) input . value = '' ;
331+
332+ // Reset board selection
333+ state . selectedSquare = null ;
334+ state . validMoves = [ ] ;
335+
336+ if ( moveObj . flags . includes ( 'p' ) ) {
337+ state . pendingPromotion = { from, to } ;
338+ openPromotionDialog ( state . chess . turn ( ) ) ;
339+ return ;
340+ }
341+
342+ executeMove ( from , to , null ) ;
343+ }
344+
225345/* ─── Square Click Logic ──────────────────────────────────────── */
226346function onSquareClick ( sqName ) {
227347 // Ignore clicks if game over or awaiting promotion
@@ -237,6 +357,8 @@ function onSquareClick(sqName) {
237357 state . selectedSquare = sqName ;
238358 state . validMoves = state . chess . moves ( { square : sqName , verbose : true } ) ;
239359 renderPieces ( ) ;
360+ // Auto-fill SQL input with template
361+ fillSQLInputTemplate ( sqName , piece ) ;
240362 return ;
241363 }
242364
@@ -298,6 +420,14 @@ function executeMove(from, to, promotion) {
298420 state . validMoves = [ ] ;
299421 state . moveCount ++ ;
300422
423+ // Clear SQL input template after a successful board-click move
424+ const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
425+ if ( sqlMoveInput && state . sqlInputHasTemplate ) {
426+ sqlMoveInput . value = '' ;
427+ state . sqlInputHasTemplate = false ;
428+ clearSQLRunError ( ) ;
429+ }
430+
301431 // Generate SQL
302432 if ( state . showSQL ) {
303433 const sql = SQLGen . move ( moveResult , state . moveCount , state . gameId ) ;
@@ -621,6 +751,15 @@ function appendSQL(code, label, moveNum) {
621751 }
622752 labelEl . append ( ' ' + label ) ;
623753
754+ // Per-block copy button
755+ const copyBtn = document . createElement ( 'button' ) ;
756+ copyBtn . type = 'button' ;
757+ copyBtn . className = 'sql-copy-btn' ;
758+ copyBtn . textContent = '⎘ Copy' ;
759+ copyBtn . title = 'Copy this SQL block' ;
760+ copyBtn . addEventListener ( 'click' , ( ) => copyToClipboard ( code , 'SQL copied!' ) ) ;
761+ labelEl . appendChild ( copyBtn ) ;
762+
624763 const codeEl = document . createElement ( 'div' ) ;
625764 codeEl . className = 'sql-code' ;
626765 codeEl . innerHTML = highlightSQL ( code ) ;
@@ -651,11 +790,17 @@ function startGame(whiteName, blackName, showSQL, existingPGN) {
651790 state . capturedByWhite = [ ] ;
652791 state . capturedByBlack = [ ] ;
653792 state . pendingPromotion = null ;
793+ state . sqlInputHasTemplate = false ;
654794
655795 // Names in UI
656796 document . getElementById ( 'whitePlayerName' ) . textContent = state . whitePlayer ;
657797 document . getElementById ( 'blackPlayerName' ) . textContent = state . blackPlayer ;
658798
799+ // Clear SQL input
800+ const sqlMoveInput = document . getElementById ( 'sqlMoveInput' ) ;
801+ if ( sqlMoveInput ) sqlMoveInput . value = '' ;
802+ clearSQLRunError ( ) ;
803+
659804 // SQL panel visibility
660805 const sqlPanel = document . getElementById ( 'sqlPanel' ) ;
661806 const lblEl = document . getElementById ( 'sqlToggleLabel' ) ;
@@ -873,6 +1018,21 @@ function init() {
8731018 } ) ;
8741019
8751020 // Close invite on overlay click already wired above
1021+
1022+ // SQL Move Input
1023+ document . getElementById ( 'btnRunSQL' ) . addEventListener ( 'click' , runSQLMove ) ;
1024+ document . getElementById ( 'btnClearInput' ) . addEventListener ( 'click' , ( ) => {
1025+ const input = document . getElementById ( 'sqlMoveInput' ) ;
1026+ if ( input ) input . value = '' ;
1027+ state . sqlInputHasTemplate = false ;
1028+ clearSQLRunError ( ) ;
1029+ } ) ;
1030+ document . getElementById ( 'sqlMoveInput' ) . addEventListener ( 'keydown' , ( e ) => {
1031+ if ( ( e . ctrlKey || e . metaKey ) && e . key === 'Enter' ) {
1032+ e . preventDefault ( ) ;
1033+ runSQLMove ( ) ;
1034+ }
1035+ } ) ;
8761036}
8771037
8781038function copyToClipboard ( text , toastMsg ) {
0 commit comments