removed chess.js to build my own engine now

This commit is contained in:
Didictateur 2025-11-09 18:33:02 +01:00
parent 09f50e2477
commit fa9c28df05
5 changed files with 62 additions and 44 deletions

7
package-lock.json generated
View file

@ -9,7 +9,6 @@
"version": "0.1.0", "version": "0.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chess.js": "^1.1.2",
"express": "^4.18.2", "express": "^4.18.2",
"socket.io": "^4.7.2", "socket.io": "^4.7.2",
"uuid": "^9.0.0" "uuid": "^9.0.0"
@ -129,12 +128,6 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",

View file

@ -11,7 +11,6 @@
"author": "Didictateur", "author": "Didictateur",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chess.js": "^1.1.2",
"express": "^4.18.2", "express": "^4.18.2",
"socket.io": "^4.7.2", "socket.io": "^4.7.2",
"uuid": "^9.0.0" "uuid": "^9.0.0"

View file

@ -111,7 +111,28 @@
connectAndJoin(); 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){ if(boardEl){
boardEl.addEventListener('click', (ev)=>{ boardEl.addEventListener('click', (ev)=>{
const sqEl = ev.target.closest && ev.target.closest('.square'); const sqEl = ev.target.closest && ev.target.closest('.square');
@ -120,8 +141,9 @@
if(!square) return; if(!square) return;
if(socket){ if(socket){
socket.emit('game:legalMoves', { roomId, square }, (resp)=>{ 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); log('legalMoves for', square, resp.moves || resp);
showLegalMoves(resp.moves || []);
}); });
} }
}); });

View file

@ -47,6 +47,15 @@ a:hover{text-decoration:underline}
.square{width:100%;height:100%;display:flex;align-items:center;justify-content:center} .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} .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 */ /* basic checkerboard coloring */
.square.light{background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));} .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));} .square.dark{background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.12));}

View file

@ -1,7 +1,6 @@
const express = require('express'); const express = require('express');
const http = require('http'); const http = require('http');
const { Server } = require('socket.io'); const { Server } = require('socket.io');
const { Chess } = require('chess.js');
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
const app = express(); const app = express();
@ -15,24 +14,36 @@ app.use(express.static('public'));
app.use('/assets', express.static(path.join(__dirname, 'assets'))); app.use('/assets', express.static(path.join(__dirname, 'assets')));
// In-memory rooms store. For production replace with persistent store. // 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(); 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) => { app.post('/rooms', (req, res) => {
const id = uuidv4().slice(0, 8); const id = uuidv4().slice(0, 8);
// allow optional board size in request body (default 8) // allow optional board size in request body (default 8)
const size = (req.body && parseInt(req.body.size, 10)) || 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 // For standard 8x8 we initialize a FEN string as the canonical board state.
const chess = size === 8 ? new Chess() : null; // 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 // 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 }); res.json({ roomId: id, size });
}); });
app.get('/rooms/:id', (req, res) => { app.get('/rooms/:id', (req, res) => {
const room = rooms.get(req.params.id); const room = rooms.get(req.params.id);
if (!room) return res.status(404).json({ error: 'room not found' }); 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 || {}) }); 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', { 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 })), players: room.players.map(p => ({ id: p.id, color: p.color })),
status: room.status, status: room.status,
hostId: room.hostId, hostId: room.hostId,
@ -85,26 +96,10 @@ io.on('connection', (socket) => {
const room = rooms.get(roomId); const room = rooms.get(roomId);
if (!room) return cb && cb({ error: 'room not found' }); if (!room) return cb && cb({ error: 'room not found' });
const chess = room.chess; // Move handling/validation is intentionally not implemented here because
// Implement a function to validate & apply moves on the room state and
// If we have a chess.js instance (standard 8x8) use it for validation // then emit 'move:moved' with move details and updated room.boardFEN.
if (!chess) return cb && cb({ error: 'custom board sizes not yet supported for moves' }); return cb && cb({ error: 'move handling not implemented on server; implement custom engine' });
// 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() });
}); });
// Host can start the game explicitly // 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' }); if (!room.hostId || room.hostId !== senderId) return cb && cb({ error: 'only host can start' });
room.status = 'playing'; 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', { 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 })), players: room.players.map(p => ({ id: p.id, color: p.color })),
status: room.status, status: room.status,
hostId: room.hostId, hostId: room.hostId,
@ -149,7 +144,7 @@ io.on('connection', (socket) => {
} }
if (room.players.length < 2 && room.status === 'playing') room.status = 'waiting'; if (room.players.length < 2 && room.status === 'playing') room.status = 'waiting';
io.to(roomId).emit('room:update', { 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 })), players: room.players.map(p => ({ id: p.id, color: p.color })),
status: room.status, status: room.status,
hostId: room.hostId, hostId: room.hostId,
@ -167,7 +162,7 @@ io.on('connection', (socket) => {
room.hostId = room.players[0] ? room.players[0].id : null; room.hostId = room.players[0] ? room.players[0].id : null;
} }
io.to(roomId).emit('room:update', { 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 })), players: room.players.map(p => ({ id: p.id, color: p.color })),
status: room.status, status: room.status,
hostId: room.hostId, 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) => { socket.on('game:legalMoves', ({ roomId, square }, cb) => {
const room = rooms.get(roomId); const room = rooms.get(roomId);
if (!room) return cb && cb({ error: 'room not found' }); 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{ try{
const moves = room.chess.moves({ square, verbose: true }) || []; const moves = computeLegalMoves(room, square) || [];
return cb && cb({ ok: true, moves }); return cb && cb({ ok: true, moves });
}catch(e){ }catch(e){
console.error('game:legalMoves error', e);
return cb && cb({ error: 'invalid square', moves: [] }); return cb && cb({ error: 'invalid square', moves: [] });
} }
}); });