diff --git a/public/game.html b/public/game.html index 5457d0b..a1032a3 100644 --- a/public/game.html +++ b/public/game.html @@ -50,6 +50,8 @@ const remoteSelections = {}; // track remote moves: { playerId: [moves] } const remoteMoves = {}; + // current boardState cached locally + let currentBoardState = null; // safe logger: prefer on-page log if present, otherwise console function log(...args){ @@ -134,6 +136,16 @@ }catch(e){ console.warn('game:select handler error', e); } }); + socket.on('move:moved', (data) => { + try{ + log('recv move:moved', data && { from: data.from, to: data.to }); + // clear any local selection (move completed) before re-render + clearSelection(false); + if(data && data.boardState) renderBoardFromState(data.boardState); + if(data && data.boardState) updateTurnBanner(data.boardState); + }catch(e){ console.warn('move:moved handler error', e); } + }); + socket.emit('room:join', { roomId, playerId }, (resp)=>{ if(resp && resp.error){ log('join error', resp); alert(resp.error); return; } log('Rejoint room', resp); @@ -296,6 +308,27 @@ const square = sqEl.getAttribute('data-square'); if(!square) return; + // If this square contains a move-marker (either clicked marker or square area), attempt the move. + // Prefer the actual clicked marker if present, otherwise look for a marker element inside the square. + const clickedMarker = ev.target.closest && ev.target.closest('.move-marker'); + const marker = clickedMarker || sqEl.querySelector('.move-marker'); + if(marker){ + // ignore remote player's markers + const remoteBy = marker.dataset && marker.dataset.remoteBy; + if(remoteBy && remoteBy !== myPlayerId){ log('marker belongs to remote player, ignoring'); return; } + const to = marker.dataset && marker.dataset.to; + const from = marker.dataset && marker.dataset.from ? marker.dataset.from : selectedSquare; + if(!to || !from){ log('invalid marker metadata', { from, to }); return; } + // only allow if it's our turn + const myShort = (myColor && myColor[0]) || ''; + if(!currentBoardState || currentBoardState.turn !== myShort){ log('not your turn'); return; } + socket.emit('game:move', { roomId, from, to }, (resp)=>{ + if(resp && resp.error){ log('move error', resp); alert(resp.error); } + else { log('move sent', resp); } + }); + return; + } + // clicked a piece -> select it (no legal moves requested) const pieceImg = ev.target.closest && ev.target.closest('img.piece'); if(pieceImg){ @@ -316,6 +349,8 @@ function renderBoardFromState(boardState){ if(!boardState) return; + // cache boardState for client-side checks (turn etc.) + currentBoardState = boardState; // clear existing pieces but keep squares if present const squares = boardEl.querySelectorAll('.square'); squares.forEach(s=> s.innerHTML = ''); diff --git a/public/styles/style.css b/public/styles/style.css index 6e05f0f..56d0fa6 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -56,9 +56,10 @@ a:hover{text-decoration:underline} .square .move-marker{ position:absolute; left:50%;top:50%;transform:translate(-50%,-50%); - width:18%;height:18%;border-radius:50%;pointer-events:none;background:rgba(96,165,250,0.95); - box-shadow:0 2px 6px rgba(2,6,23,0.6);z-index:3; + width:18%;height:18%;border-radius:50%;pointer-events:auto;cursor:pointer;background:rgba(96,165,250,0.95); + box-shadow:0 2px 6px rgba(2,6,23,0.6);z-index:3;transition:transform 120ms ease, opacity 120ms ease; } +.square .move-marker:hover{ transform:translate(-50%,-50%) scale(1.12); opacity:0.95 } /* highlighted/selected square when a player selects a piece */ .square.selected{ diff --git a/server.js b/server.js index 9d5f794..3755779 100644 --- a/server.js +++ b/server.js @@ -237,11 +237,64 @@ io.on('connection', (socket) => { socket.on('game:move', ({ roomId, from, to, promotion }, cb) => { const room = rooms.get(roomId); if (!room) return cb && cb({ error: 'room not found' }); + // Basic move handling: validate turn, validate piece ownership, validate target is one of computeLegalMoves, + // apply the move to room.boardState, handle captures, flip turn and broadcast the updated board. + try{ + const senderId = socket.data.playerId; + if(!senderId) return cb && cb({ error: 'not joined' }); + const roomPlayer = room.players.find(p => p.id === senderId); + if(!roomPlayer) return cb && cb({ error: 'player not in room' }); + if(!room.boardState) return cb && cb({ error: 'no board state' }); - // Move handling/validation is intentionally not implemented here. - // Implement a function to validate & apply moves on the room.boardState and - // then emit 'move:moved' with move details and updated room.boardState. - return cb && cb({ error: 'move handling not implemented on server; implement custom engine' }); + const board = room.boardState; + // determine player's color short form ('w'|'b') + const playerColorShort = (roomPlayer.color && roomPlayer.color[0]) || null; + if(!playerColorShort) return cb && cb({ error: 'invalid player color' }); + + // must be player's turn + if(board.turn !== playerColorShort) return cb && cb({ error: 'not your turn' }); + + // find piece at 'from' + const pieces = board.pieces || []; + const moving = pieces.find(p => p.square === from); + if(!moving) return cb && cb({ error: 'no piece at source' }); + if(moving.color !== playerColorShort) return cb && cb({ error: 'not your piece' }); + + // validate that 'to' is among legal moves + const legal = computeLegalMoves(room, from) || []; + const ok = legal.some(m => m.to === to); + if(!ok) return cb && cb({ error: 'illegal move' }); + + // apply move: remove any piece on target (capture) + const targetIndex = pieces.findIndex(p => p.square === to); + if(targetIndex >= 0){ + // remove captured piece + pieces.splice(targetIndex, 1); + } + // move the piece + moving.square = to; + + // advance version and flip turn + board.version = (board.version || 0) + 1; + board.turn = (board.turn === 'w') ? 'b' : 'w'; + + // broadcast move and new board state + const moved = { playerId: senderId, from, to, boardState: board }; + io.to(roomId).emit('move:moved', moved); + io.to(roomId).emit('room:update', { + boardState: board, + players: room.players.map(p => ({ id: p.id, color: p.color })), + status: room.status, + hostId: room.hostId, + size: room.size, + cards: Object.keys(room.cards || {}) + }); + + return cb && cb({ ok: true, moved }); + }catch(err){ + console.error('game:move error', err); + return cb && cb({ error: 'server error' }); + } }); // Host can start the game explicitly