Scacchi online multiplayer gratis
Indice ▾
- Il modo più semplice per iniziare
- Il P2P non è magia: è un processo in tre fasi
- Cosa può impedire la connessione?
- Layer 1 — Tracker irraggiungibili
- Layer 2 — Peer non si trovano
- Layer 3 — Canale lento o bloccato
- Il problema del NAT: una mappa
- Il test diagnostico: come capire cosa non va
- In sintesi
- Il modulo p2p-diagnostics.js
Piccola applicazione di scacchi multiplayer che gira interamente nel browser.
L’interfaccia usa cm-chessboard per la scacchiera e chess.js per la logica delle mosse; la comunicazione tra giocatori avviene in P2P (WebRTC) tramite Trystero (torrent strategy).
Versione online: Gioca subito →
Il modo più semplice per iniziare
La comunicazione P2P tra dispositivi funziona nella maggior parte dei casi, ma dipende da molti fattori di rete che non sempre sono sotto il tuo controllo: il tipo di router, le impostazioni del firewall, l'operatore telefonico, la presenza di una VPN.
Per evitare qualsiasi problema e avere la migliore esperienza possibile — latenza minima, connessione immediata, nessuna configurazione — il modo più semplice e affidabile è collegare tutti i dispositivi alla stessa rete Wi-Fi.
In questo scenario i dispositivi si trovano e si parlano direttamente sulla rete locale, senza dover attraversare NAT, firewall o relay esterni. È la condizione ideale.
Se invece stai giocando con qualcuno che si trova in un'altra abitazione o su una rete mobile, la connessione funzionerà comunque nella grande maggioranza dei casi — ma se qualcosa non va, continua a leggere per capire come diagnosticarlo.
Il P2P non è magia: è un processo in tre fasi
Quando due browser si connettono con Trystero (strategia torrent), devono superare tre “gradini” in sequenza. Se uno di questi si inceppa, la connessione non parte.
Signaling — “ci sei?”
Trystero usa tracker BitTorrent pubblici (via WebSocket) per mettere in contatto i due peer. È come un centralinista: si limita a presentare le due parti, non passa i dati del gioco.
Discovery — “ti trovo”
Dopo il signaling, i peer si scambiano le coordinate di rete (candidati ICE). Ognuno dice: “mi puoi raggiungere a questi indirizzi”. Se le reti sono incompatibili, ci si perde qui.
Data channel — “parliamo”
Il canale WebRTC è attivo. Da qui i dati viaggiano direttamente tra i due browser. La qualità dipende dalla latenza: sotto 80ms è ottima, sopra 300ms diventa problematica.

Cosa può impedire la connessione?
Ogni fase ha i suoi nemici specifici. Ecco le cause più comuni di fallimento, raggruppate per layer.
Layer 1 — Tracker irraggiungibili
Firewall aziendale
Blocca WebSocket outbound verso domini BitTorrent
Estensioni browser
uBlock Origin, Privacy Badger bloccano i tracker torrent
HTTPS + ws://
Pagina HTTPS non può connettersi a tracker non sicuri (ws://)
Layer 2 — Peer non si trovano
NAT simmetrico
Comune su reti aziendali e connessioni 4G/5G. STUN non basta.
AP isolation
Reti Wi-Fi pubbliche (hotel, aeroporti) isolano i client tra loro
Doppio NAT
Router dietro router: i candidati ICE non si incrociano
Layer 3 — Canale lento o bloccato
Firewall UDP
WebRTC usa UDP di default. Senza UDP la connessione non parte
VPN con blocco WebRTC
Molte VPN bloccano WebRTC per prevenire IP leak
Firefox hardened
media.peerconnection.enabled = false disabilita WebRTC
Il problema del NAT: una mappa
Non tutti i router sono uguali. Il tipo di NAT determina se la connessione WebRTC riesce in autonomia o ha bisogno di aiuto.
| Tipo di NAT | WebRTC diretto? | Soluzione da implementare |
|---|---|---|
| Full cone | Sì | Nessuna |
| Restricted cone | Sì | STUN sufficiente |
| Simmetrico | No | Serve server TURN |
| Carrier-grade NAT (4G/5G) | Spesso no | TURN raccomandato |
| IPv6 disponibile | Quasi sempre | Nessuna |
Nota importante: Trystero con strategia torrent non include un server TURN. Se entrambi i peer sono dietro NAT simmetrico la connessione fallisce silenziosamente, senza alcun errore visibile.
Il test diagnostico: come capire cosa non va
Il modulo p2p-diagnostics.js, che viene eseguito dall'applicazione di scacchi multiplayer quando si seleziona la stanza "Scacchi - diagnostica", esegue tre test in sequenza, uno per ogni layer. Ecco cosa controlla e cosa significa ogni risultato.
Raggiungibilità dei tracker BitTorrent
Il modulo tenta di aprire una connessione WebSocket verso tre tracker pubblici e misura il tempo di risposta.
Se tutti i tracker falliscono: la rete blocca il signaling. Controlla firewall, VPN, estensioni browser. Senza signaling, nessun peer si trova mai.
Trovare un altro peer nella room
Il modulo entra in una room Trystero temporanea e aspetta fino a 15 secondi che un secondo peer si unisca. Questo test richiede due dispositivi o due tab aperti contemporaneamente.
Se il signaling funziona ma nessun peer si trova: probabile AP isolation (reti Wi-Fi pubbliche) o mismatch di appId tra i client.
Latenza del canale dati (ping/pong)
Una volta connessi, vengono inviati 5 ping e misurato il tempo di risposta (RTT). La media determina la qualità della connessione.
< 80ms
Ottimo. Connessione diretta, nessun relay
80–300ms
Accettabile. Possibile relay TURN attivo
> 300ms
Critico. Rete congestionata o relay lontano
Interpretare il report finale
Il modulo restituisce un oggetto JSON con il risultato di ogni layer. Ecco come leggere i casi più comuni.
Soluzione universale: aggiungere un server TURN alla configurazione WebRTC risolve quasi tutti i casi di Layer 2 e Layer 3. Senza TURN, circa il 15–20% delle reti reali non riesce a stabilire la connessione diretta.
In sintesi
Trystero P2P richiede che tre cose funzionino tutte insieme. Se una sola si inceppa, i peer non si parlano:
Tracker raggiungibili
WebSocket verso tracker pubblici non bloccato
NAT attraversabile
STUN sufficiente, o TURN configurato come fallback
UDP aperto
WebRTC usa UDP; firewall troppo restrittivi lo bloccano
Il modulo p2p-diagnostics.js testa questi tre layer in sequenza e restituisce un report strutturato con causa e suggerimento per ogni problema trovato.
Il modulo p2p-diagnostics.js
/**
* p2p-diagnostics.js
* Diagnostica P2P per applicazioni Trystero (torrent strategy)
*
*
* Uso:
* import { runDiagnostics } from './p2p-diagnostics.js';
* const result = await runDiagnostics({ onProgress: console.log });
*/
import { joinRoom } from 'https://cdn.jsdelivr.net/npm/trystero@0.21.0/torrent.js';
// ─── Configurazione ────────────────────────────────────────────────────────────
const CONFIG = {
DIAGNOSTIC_ROOM: '__p2p_diag_' + Math.random().toString(36).slice(2, 8),
TIMEOUT_TRACKER: 10_000,
TIMEOUT_DISCOVERY: 15_000,
TIMEOUT_PING: 10_000,
TRACKERS: [
'wss://tracker.openwebtorrent.com',
'wss://tracker.webtorrent.dev',
'wss://tracker.btorrent.xyz',
],
PING_COUNT: 5,
APP_ID: 'p2p-diag-v1',
};
export const DiagResult = { OK: 'ok', WARNING: 'warning', ERROR: 'error' };
function now() {
return typeof performance !== 'undefined' ? performance.now() : Date.now();
}
// ─── LAYER 1: Raggiungibilità tracker ─────────────────────────────────────────
function testSingleTracker(url) {
return new Promise(resolve => {
const t0 = now();
let ws;
const done = (reachable, error) => {
try { ws?.close(); } catch (_) {}
resolve({ url, reachable, latencyMs: Math.round(now() - t0), error });
};
try {
ws = new WebSocket(url);
const tid = setTimeout(() => done(false, 'timeout'), CONFIG.TIMEOUT_TRACKER);
ws.onopen = () => { clearTimeout(tid); done(true); };
ws.onerror = e => { clearTimeout(tid); done(false, 'connection error'); };
} catch (e) {
done(false, e.message);
}
});
}
async function testTrackers(onProgress) {
onProgress?.({ phase: 'tracker', status: 'running',
message: 'Test connessione tracker BitTorrent...' });
const results = await Promise.all(CONFIG.TRACKERS.map(testSingleTracker));
const reachable = results.filter(r => r.reachable);
const allFailed = reachable.length === 0;
onProgress?.({ phase: 'tracker', status: allFailed ? 'error' : 'done',
message: allFailed
? '❌ Nessun tracker raggiungibile (firewall? rete offline?)'
: `✅ ${reachable.length}/${results.length} tracker raggiungibili`,
details: results });
return {
layer: 1, name: 'Signaling (tracker)',
result: allFailed
? DiagResult.ERROR
: reachable.length < results.length ? DiagResult.WARNING : DiagResult.OK,
trackers: results,
reachableCount: reachable.length,
};
}
// ─── LAYER 2 + 3: Discovery e Data Channel ────────────────────────────────────
async function testPeerConnection(onProgress) {
onProgress?.({ phase: 'discovery', status: 'running',
message: 'Apertura room Trystero diagnostica...' });
return new Promise(resolve => {
let room, discoveryResolved = false;
let layer2Result = null, layer3Result = null;
const peers = {};
let pingInterval = null, pingSeq = 0;
const pendingPings = {};
const finish = () => {
clearInterval(pingInterval);
try { room?.leave?.(); } catch (_) {}
resolve({ layer2: layer2Result, layer3: layer3Result });
};
// Timeout discovery
const discoveryTimeout = setTimeout(() => {
if (discoveryResolved) return;
discoveryResolved = true;
layer2Result = {
layer: 2, name: 'Peer discovery', result: DiagResult.ERROR,
message: '❌ Nessun peer trovato — stesso room ID? Signaling ok?',
peersFound: 0,
};
onProgress?.({ phase: 'discovery', status: 'error', message: layer2Result.message });
layer3Result = { layer: 3, name: 'Data channel', result: DiagResult.ERROR,
message: '⏭️ Saltato (nessun peer disponibile)' };
finish();
}, CONFIG.TIMEOUT_DISCOVERY);
try {
room = joinRoom({ appId: CONFIG.APP_ID }, CONFIG.DIAGNOSTIC_ROOM);
} catch (e) {
clearTimeout(discoveryTimeout);
layer2Result = { layer: 2, name: 'Peer discovery', result: DiagResult.ERROR,
message: `❌ Impossibile creare la room: ${e.message}` };
layer3Result = { layer: 3, name: 'Data channel', result: DiagResult.ERROR,
message: '⏭️ Saltato' };
return resolve({ layer2: layer2Result, layer3: layer3Result });
}
const [sendPing, getPing] = room.makeAction('ping');
const [sendPong, getPong] = room.makeAction('pong');
room.onPeerJoin(peerId => {
peers[peerId] = { connectedAt: now(), latencies: [] };
if (discoveryResolved) return;
discoveryResolved = true;
clearTimeout(discoveryTimeout);
layer2Result = {
layer: 2, name: 'Peer discovery', result: DiagResult.OK,
message: `✅ Peer trovato (id: ${peerId.slice(0, 8)}…)`, peersFound: 1,
};
onProgress?.({ phase: 'discovery', status: 'done', message: layer2Result.message });
onProgress?.({ phase: 'ping', status: 'running',
message: `Misuro latenza RTT (${CONFIG.PING_COUNT} ping)...` });
// Invia ping periodici
let sent = 0;
pingInterval = setInterval(() => {
if (sent >= CONFIG.PING_COUNT) { clearInterval(pingInterval); return; }
const seq = ++pingSeq;
pendingPings[seq] = { peerId, sentAt: now() };
sendPing({ seq }, peerId);
sent++;
}, 500);
// Timeout conclusione ping
setTimeout(() => {
const allLat = Object.values(peers).flatMap(p => p.latencies);
if (allLat.length === 0) {
layer3Result = {
layer: 3, name: 'Data channel (latenza)', result: DiagResult.ERROR,
message: '❌ Nessun pong ricevuto — data channel bloccato (NAT simmetrico? firewall UDP?)',
};
} else {
const avg = Math.round(allLat.reduce((a, b) => a + b, 0) / allLat.length);
const max = Math.round(Math.max(...allLat));
const q = avg < 80 ? DiagResult.OK : avg < 300 ? DiagResult.WARNING : DiagResult.ERROR;
layer3Result = {
layer: 3, name: 'Data channel (latenza)', result: q,
message: q === DiagResult.OK
? `✅ Latenza ottima: media ${avg}ms, max ${max}ms`
: q === DiagResult.WARNING
? `⚠️ Latenza elevata: media ${avg}ms (possibile relay TURN)`
: `❌ Latenza critica: media ${avg}ms — connessione degradata`,
avgLatencyMs: avg, maxLatencyMs: max, samplesCount: allLat.length,
};
}
onProgress?.({ phase: 'ping', status: layer3Result.result, message: layer3Result.message });
finish();
}, CONFIG.TIMEOUT_PING);
});
// Rispondo ai ping in arrivo (ruolo server)
getPing(({ seq }, peerId) => sendPong({ seq }, peerId));
// Ricevo pong e calcolo RTT
getPong(({ seq }) => {
const entry = pendingPings[seq];
if (!entry) return;
const rtt = now() - entry.sentAt;
peers[entry.peerId]?.latencies.push(rtt);
delete pendingPings[seq];
onProgress?.({ phase: 'ping', status: 'sample',
message: ` ping #${seq}: ${Math.round(rtt)}ms RTT`, seq, rttMs: Math.round(rtt) });
});
room.onPeerLeave(peerId => { delete peers[peerId]; });
});
}
// ─── API pubblica ──────────────────────────────────────────────────────────────
export async function runDiagnostics({ onProgress } = {}) {
const startedAt = new Date().toISOString();
onProgress?.({ phase: 'start', message: '🔍 Avvio diagnostica P2P...' });
const layer1 = await testTrackers(onProgress);
if (layer1.result === DiagResult.ERROR) {
const report = buildReport(startedAt, layer1, null, null);
onProgress?.({ phase: 'done', report });
return report;
}
const { layer2, layer3 } = await testPeerConnection(onProgress);
const report = buildReport(startedAt, layer1, layer2, layer3);
onProgress?.({ phase: 'done', report });
return report;
}
function buildReport(startedAt, layer1, layer2, layer3) {
const layers = [layer1, layer2, layer3].filter(Boolean);
const worst = layers.some(l => l.result === DiagResult.ERROR) ? DiagResult.ERROR
: layers.some(l => l.result === DiagResult.WARNING) ? DiagResult.WARNING
: DiagResult.OK;
return {
startedAt,
summary: worst === DiagResult.OK ? '✅ Connessione P2P funzionante'
: worst === DiagResult.WARNING ? '⚠️ P2P parzialmente degradato'
: '❌ Connessione P2P non funzionante',
overallResult: worst,
layers,
};
}
if (typeof window !== 'undefined') window.p2pDiag = { runDiagnostics };

