diff --git a/package-lock.json b/package-lock.json index 9d042ac..f1be53e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "chess.js": "^1.1.2", "express": "^4.18.2", "socket.io": "^4.7.2", "uuid": "^9.0.0" @@ -129,12 +128,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chess.js": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/chess.js/-/chess.js-1.4.0.tgz", - "integrity": "sha512-BBJgrrtKQOzFLonR0l+k64A98NLemPwNsCskwb+29bRwobUa4iTm51E1kwGPbWXAcfdDa18nad6vpPPKPWarqw==", - "license": "BSD-2-Clause" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", diff --git a/package.json b/package.json index d20ea26..844fbaf 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "author": "Didictateur", "license": "MIT", "dependencies": { - "chess.js": "^1.1.2", "express": "^4.18.2", "socket.io": "^4.7.2", "uuid": "^9.0.0" diff --git a/public/game.html b/public/game.html index 34e7007..965edfc 100644 --- a/public/game.html +++ b/public/game.html @@ -111,7 +111,28 @@ connectAndJoin(); - // allow clicking on the board to request legal moves for a square + // show markers for legal moves when clicking any square (any piece color) + function clearLegalMarkers(){ + const prev = boardEl.querySelectorAll('.move-marker'); + prev.forEach(p=>p.remove()); + } + + function showLegalMoves(moves){ + clearLegalMarkers(); + (moves||[]).forEach(m => { + const to = m.to || (m.move && m.move.to); + if(!to) return; + const target = boardEl.querySelector(`.square[data-square="${to}"]`); + if(!target) return; + const marker = document.createElement('div'); + marker.className = 'move-marker'; + // store metadata if needed later + marker.dataset.from = m.from || ''; + marker.dataset.to = to; + target.appendChild(marker); + }); + } + if(boardEl){ boardEl.addEventListener('click', (ev)=>{ const sqEl = ev.target.closest && ev.target.closest('.square'); @@ -120,8 +141,9 @@ if(!square) return; if(socket){ socket.emit('game:legalMoves', { roomId, square }, (resp)=>{ - if(resp && resp.error){ log('legalMoves error', resp); return; } + if(resp && resp.error){ log('legalMoves error', resp); clearLegalMarkers(); return; } log('legalMoves for', square, resp.moves || resp); + showLegalMoves(resp.moves || []); }); } }); diff --git a/public/styles/style.css b/public/styles/style.css index 40f32ae..3ae6f1e 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -47,6 +47,15 @@ a:hover{text-decoration:underline} .square{width:100%;height:100%;display:flex;align-items:center;justify-content:center} .square img, .piece{max-width:78%;max-height:78%;user-select:none} +/* marker shown on target squares for legal moves */ +.square{position:relative} +.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 +} + /* basic checkerboard coloring */ .square.light{background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));} .square.dark{background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.12));} diff --git a/server.js b/server.js index f504a0c..f4e9a50 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,6 @@ const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); -const { Chess } = require('chess.js'); const { v4: uuidv4 } = require('uuid'); const app = express(); @@ -15,24 +14,36 @@ app.use(express.static('public')); app.use('/assets', express.static(path.join(__dirname, 'assets'))); // In-memory rooms store. For production replace with persistent store. -// room = { id, chess: Chess, players: [{id, socketId, color}], status } +// room = { id, boardFEN: string|null, turn: 'w'|'b'|null, players: [{id, socketId, color}], status } const rooms = new Map(); +// Placeholder: compute legal moves for a square in the given room. +// This is intentionally a stub — you will implement the full rules engine. +// (fields like { from, to, piece, color, flags, captured, san } are typical). +function computeLegalMoves(room, square){ + // TODO: implement rule engine. For now return empty array so client shows nothing. + return []; +} + app.post('/rooms', (req, res) => { const id = uuidv4().slice(0, 8); // allow optional board size in request body (default 8) const size = (req.body && parseInt(req.body.size, 10)) || 8; - // For standard 8x8 chess we use chess.js. For other sizes we keep a placeholder state - const chess = size === 8 ? new Chess() : null; + // For standard 8x8 we initialize a FEN string as the canonical board state. + // manually (or by a separate engine). This server stores the board FEN and + // the active turn so custom logic can operate on them. + const startingFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; + const boardFEN = size === 8 ? startingFEN : null; + const turn = size === 8 ? 'w' : null; // 'w' or 'b' // hostId will be set when the first player joins - rooms.set(id, { id, chess, players: [], status: 'waiting', hostId: null, size, cards: {}, playedCards: [], removalTimers: new Map() }); + rooms.set(id, { id, boardFEN, turn, players: [], status: 'waiting', hostId: null, size, cards: {}, playedCards: [], removalTimers: new Map() }); res.json({ roomId: id, size }); }); app.get('/rooms/:id', (req, res) => { const room = rooms.get(req.params.id); if (!room) return res.status(404).json({ error: 'room not found' }); - const fen = room.chess ? room.chess.fen() : null; + const fen = room.boardFEN || null; res.json({ id: room.id, fen, size: room.size, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, cards: Object.keys(room.cards || {}) }); }); @@ -70,7 +81,7 @@ io.on('connection', (socket) => { io.to(roomId).emit('room:update', { - fen: room.chess ? room.chess.fen() : null, + fen: room.boardFEN || null, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, @@ -85,26 +96,10 @@ io.on('connection', (socket) => { const room = rooms.get(roomId); if (!room) return cb && cb({ error: 'room not found' }); - const chess = room.chess; - - // If we have a chess.js instance (standard 8x8) use it for validation - if (!chess) return cb && cb({ error: 'custom board sizes not yet supported for moves' }); - - // Validate move - const move = chess.move({ from, to, promotion }); - if (!move) return cb && cb({ error: 'invalid move' }); - - io.to(roomId).emit('move:moved', { move, fen: chess.fen() }); - - if (chess.isGameOver()) { - room.status = 'finished'; - let result = 'draw'; - if (chess.isCheckmate()) result = 'checkmate'; - else if (chess.isStalemate()) result = 'stalemate'; - io.to(roomId).emit('game:over', { result, fen: chess.fen() }); - } - - cb && cb({ ok: true, move, fen: chess.fen() }); + // Move handling/validation is intentionally not implemented here because + // Implement a function to validate & apply moves on the room state and + // then emit 'move:moved' with move details and updated room.boardFEN. + return cb && cb({ error: 'move handling not implemented on server; implement custom engine' }); }); // Host can start the game explicitly @@ -120,9 +115,9 @@ io.on('connection', (socket) => { if (!room.hostId || room.hostId !== senderId) return cb && cb({ error: 'only host can start' }); room.status = 'playing'; - io.to(roomId).emit('game:started', { roomId, fen: room.chess.fen() }); + io.to(roomId).emit('game:started', { roomId, fen: room.boardFEN }); io.to(roomId).emit('room:update', { - fen: room.chess ? room.chess.fen() : null, + fen: room.boardFEN || null, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, @@ -149,7 +144,7 @@ io.on('connection', (socket) => { } if (room.players.length < 2 && room.status === 'playing') room.status = 'waiting'; io.to(roomId).emit('room:update', { - fen: room.chess ? room.chess.fen() : null, + fen: room.boardFEN || null, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, @@ -167,7 +162,7 @@ io.on('connection', (socket) => { room.hostId = room.players[0] ? room.players[0].id : null; } io.to(roomId).emit('room:update', { - fen: room.chess ? room.chess.fen() : null, + fen: room.boardFEN || null, players: room.players.map(p => ({ id: p.id, color: p.color })), status: room.status, hostId: room.hostId, @@ -177,15 +172,15 @@ io.on('connection', (socket) => { } }); - // Provide legal moves for a given square (for standard 8x8 via chess.js) + // delegates to `computeLegalMoves` which is a stub you should implement. socket.on('game:legalMoves', ({ roomId, square }, cb) => { const room = rooms.get(roomId); if (!room) return cb && cb({ error: 'room not found' }); - if (!room.chess) return cb && cb({ error: 'legal moves not supported for custom board sizes yet', moves: [] }); try{ - const moves = room.chess.moves({ square, verbose: true }) || []; + const moves = computeLegalMoves(room, square) || []; return cb && cb({ ok: true, moves }); }catch(e){ + console.error('game:legalMoves error', e); return cb && cb({ error: 'invalid square', moves: [] }); } });