{"id":4048,"date":"2026-09-07T14:39:02","date_gmt":"2026-09-07T14:39:02","guid":{"rendered":"https:\/\/www.chess.lumn.eu\/wordpress\/?p=4048"},"modified":"2026-09-07T14:40:00","modified_gmt":"2026-09-07T14:40:00","slug":"calculateur-de-tournoi","status":"publish","type":"post","link":"https:\/\/www.chess.lumn.eu\/wordpress\/2026\/09\/07\/calculateur-de-tournoi\/","title":{"rendered":"Calculateur de tournoi"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"4048\" class=\"elementor elementor-4048\">\n\t\t\t\t\t\t<div class=\"elementor-inner\">\n\t\t\t\t<div class=\"elementor-section-wrap\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-6804c99 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"6804c99\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t\t\t<div class=\"elementor-row\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-fae1cb1\" data-id=\"fae1cb1\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-column-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-472af1b elementor-widget elementor-widget-heading\" data-id=\"472af1b\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Calculateur de tournoi<\/h2>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-b513bcf elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"b513bcf\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t\t\t<div class=\"elementor-row\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-4be2a4d\" data-id=\"4be2a4d\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-column-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-367f0f5 elementor-widget elementor-widget-html\" data-id=\"367f0f5\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Analyseur de tournoi \u2014 classement par points ou performance Elo<\/title>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,400&family=JetBrains+Mono:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n<style>\n  .naum-body{\n    --creme:#f7f4ee; --encre:#2b2b2b; --rouge:#c0392b;\n    --or:#b8863b; --trait:#e3ddd0; --gris:#6b665c;\n    max-width:900px; margin:0 auto; padding:64px 28px 120px;\n    background:var(--creme); color:var(--encre);\n    font-family:'DM Sans',system-ui,sans-serif; font-weight:400;\n    font-size:18px; line-height:1.72; -webkit-font-smoothing:antialiased;\n  }\n  .naum-body, .naum-body *{box-sizing:border-box;}\n\n  .naum-body h1{font-family:'DM Serif Display',serif; font-weight:400; font-size:2.9rem;\n     line-height:1.08; letter-spacing:-.5px; margin:0 0 .35em; color:var(--encre);}\n  .naum-body h2{font-family:'DM Serif Display',serif; font-weight:400; font-size:1.85rem;\n     line-height:1.15; letter-spacing:-.3px; margin:2.6em 0 .5em; padding-bottom:.25em;\n     border-bottom:2px solid var(--rouge); color:var(--encre);}\n  .naum-body h3{font-family:'DM Sans',sans-serif; font-weight:700; font-size:1.1rem;\n     letter-spacing:.2px; text-transform:uppercase; margin:1.8em 0 .4em; color:var(--rouge);}\n  .naum-body .eyebrow{font-family:'JetBrains Mono',monospace; font-size:.78rem; letter-spacing:3px;\n     text-transform:uppercase; color:var(--or); margin:0 0 1.2em;}\n  .naum-body .chapeau{font-size:1.22rem; line-height:1.6; color:var(--gris); font-style:italic;\n     margin:0 0 2.4em; padding-left:1.1em; border-left:3px solid var(--or);}\n  .naum-body p{margin:0 0 1.15em;}\n  .naum-body a{color:var(--rouge); text-decoration:none; border-bottom:1px solid rgba(192,57,43,.35);}\n  .naum-body a:hover{border-bottom-color:var(--rouge);}\n  .naum-body strong{font-weight:700;}\n  .naum-body code{font-family:'JetBrains Mono',monospace; font-size:.85em; background:#efe9dd;\n     padding:.08em .35em; border-radius:3px; color:#7a3b2e;}\n  .naum-body ul,.naum-body ol{margin:0 0 1.2em; padding-left:1.35em;}\n  .naum-body li{margin:.4em 0;}\n\n  .naum-body ol.etapes{list-style:none; padding-left:0; counter-reset:et;}\n  .naum-body ol.etapes>li{counter-increment:et; position:relative; padding-left:52px; margin:1.1em 0;}\n  .naum-body ol.etapes>li::before{content:counter(et); position:absolute; left:0; top:-2px;\n     width:34px; height:34px; border-radius:50%; background:var(--rouge); color:#fff;\n     font-family:'JetBrains Mono',monospace; font-weight:700; font-size:1rem;\n     display:flex; align-items:center; justify-content:center;}\n\n  .naum-body .box{border-radius:8px; padding:18px 22px; margin:1.6em 0; font-size:.97rem; line-height:1.62;}\n  .naum-body .box .box-titre{font-family:'JetBrains Mono',monospace; font-size:.72rem; letter-spacing:2px;\n     text-transform:uppercase; display:block; margin-bottom:.5em; font-weight:700;}\n  .naum-body .naum-note-box{background:#f3ecda; border:1px solid #ddd0ab;}\n  .naum-body .naum-note-box .box-titre{color:#9a7b2e;}\n  .naum-body .naum-alert-box{background:#fbeae7; border:1px solid #e8b6ac;}\n  .naum-body .naum-alert-box .box-titre{color:var(--rouge);}\n  .naum-body .naum-coherence-box{background:#e9f2e6; border:1px solid #b6d3ab;}\n  .naum-body .naum-coherence-box .box-titre{color:#4e7a3b;}\n\n  .naum-body .tbl-wrap{overflow-x:auto; margin:1.6em 0;}\n  .naum-body table{border-collapse:collapse; width:100%; font-size:.9rem; background:transparent;}\n  .naum-body th,.naum-body td{border:1px solid var(--trait); padding:10px 13px; text-align:left; vertical-align:top;}\n  .naum-body th{background:#efe9dd; font-weight:700; letter-spacing:.3px;}\n  .naum-body tbody tr:nth-child(even){background:#faf7f0;}\n  .naum-body .ok{color:#4e7a3b; font-weight:700;}\n  .naum-body .taille{font-family:'JetBrains Mono',monospace; font-size:.86em; white-space:nowrap;}\n\n  .naum-body .champ{font-family:'JetBrains Mono',monospace; font-size:.82rem; background:#20232a;\n     color:#e6e1d6; padding:14px 18px; border-radius:6px; margin:.6em 0; overflow-x:auto;\n     border-left:3px solid var(--or);}\n  .naum-body .champ b{color:#e8b17a; font-weight:700;}\n\n  .naum-body .dl-zone{background:#20232a; border-radius:12px; padding:34px 30px; margin:2.2em 0;\n     text-align:center; color:#e6e1d6;}\n  .naum-body .dl-zone h3{color:#e8b17a; margin:0 0 .3em;}\n  .naum-body .dl-zone p{color:#b7b1a4; font-size:.95rem; margin:0 0 1.4em;}\n  .naum-body .dl-button{font-family:'DM Sans',sans-serif; font-weight:700; font-size:1.05rem;\n     background:var(--rouge); color:#fff; border:none; padding:15px 34px; border-radius:8px;\n     cursor:pointer; letter-spacing:.3px; transition:transform .12s ease, background .2s ease;}\n  .naum-body .dl-button:hover{background:#a5301f; transform:translateY(-2px);}\n  .naum-body .dl-button:active{transform:translateY(0);}\n  .naum-body .dl-sub{font-family:'JetBrains Mono',monospace; font-size:.74rem; color:#8a857a; margin-top:1em;}\n\n  .naum-body hr{border:none; border-top:1px solid var(--trait); margin:3em 0;}\n  .naum-body .sig{font-family:'JetBrains Mono',monospace; font-size:.78rem; color:var(--gris);\n     text-align:right; margin-top:3.5em;}\n\n  @media (max-width:600px){\n    .naum-body{font-size:16px; padding:44px 18px 90px;}\n    .naum-body h1{font-size:2.1rem;}\n    .naum-body h2{font-size:1.45rem;}\n  }\n<\/style>\n<style>\n  \/* --- complements propres a la page tournoi, memes variables que l'article --- *\/\n  .naum-body .zone-depot{border:2px dashed var(--trait); border-radius:10px; padding:34px 24px;\n     text-align:center; background:#fbf9f4; transition:border-color .15s, background .15s; cursor:pointer;}\n  .naum-body .zone-depot.survol{border-color:var(--rouge); background:#f6efe6;}\n  .naum-body .zone-depot p{margin:.3em 0; color:var(--gris);}\n  .naum-body .zone-depot .gros{font-family:'DM Serif Display',serif; font-size:1.4rem; color:var(--encre);}\n  .naum-body .barre{display:flex; flex-wrap:wrap; gap:14px 22px; align-items:center;\n     margin:1.4em 0; padding:14px 18px; background:#efe9dd; border-radius:8px; font-size:.92rem;}\n  .naum-body .barre label{display:flex; align-items:center; gap:7px; cursor:pointer;}\n  .naum-body .barre input[type=text]{font-family:'JetBrains Mono',monospace; font-size:.85rem;\n     border:1px solid var(--trait); border-radius:4px; padding:4px 8px; width:110px;}\n  .naum-body .resume{font-family:'JetBrains Mono',monospace; font-size:.82rem; color:var(--gris); margin:.8em 0 1.4em;}\n  .naum-body table.cls th{cursor:pointer; user-select:none; white-space:nowrap;}\n  .naum-body table.cls th:hover{background:#e5dcc9;}\n  .naum-body table.cls th .fleche{color:var(--rouge); font-size:.8em;}\n  .naum-body table.cls td.num{font-family:'JetBrains Mono',monospace; text-align:right; white-space:nowrap;}\n  .naum-body table.cls tr.py td:nth-child(2){color:#1f5fa8; font-weight:700;}\n  .naum-body table.cls tr.detail-ouvert{background:#f3ecda !important;}\n  .naum-body .detail{background:#faf7f0; font-size:.86rem;}\n  .naum-body .detail table{margin:.4em 0; width:auto; min-width:60%;}\n  .naum-body .pos{color:#4e7a3b;} .naum-body .neg{color:var(--rouge);}\n  .naum-body .mini{font-size:.8rem; color:var(--gris);}\n  .naum-body textarea.cotes{width:100%; min-height:150px; font-family:'JetBrains Mono',monospace;\n     font-size:.78rem; border:1px solid var(--trait); border-radius:6px; padding:10px;\n     background:#fbf9f4; color:var(--encre);}\n  .naum-body details.reglages summary{cursor:pointer; font-family:'JetBrains Mono',monospace;\n     font-size:.78rem; letter-spacing:1.5px; text-transform:uppercase; color:var(--or); margin:1.6em 0 .6em;}\n  .naum-body .vide{text-align:center; color:var(--gris); font-style:italic; padding:40px 0;}\n<\/style>\n<\/head>\n<body>\n<article class=\"naum-body\">\n\n  <p class=\"eyebrow\">Laboratoire &middot; outil<\/p>\n  <h1>Analyseur de tournoi<\/h1>\n  <p class=\"chapeau\">\n    D\u00e9posez un fichier PGN : la page en tire le classement du tournoi, par points ou par\n    performance Elo, en s'appuyant sur les cotes du classement du laboratoire. Tout se passe\n    dans votre navigateur, aucun fichier n'est envoy\u00e9 nulle part.\n  <\/p>\n\n  <div class=\"zone-depot\" id=\"depot\">\n    <p class=\"gros\">D\u00e9posez un PGN ici<\/p>\n    <p>ou cliquez pour choisir un fichier &middot; plusieurs fichiers accept\u00e9s<\/p>\n    <input type=\"file\" id=\"fichier\" accept=\".pgn,.txt\" multiple hidden>\n  <\/div>\n\n  <div class=\"barre\">\n    <label><input type=\"checkbox\" id=\"opt-dedup\" checked> \u00e9carter les doublons<\/label>\n    <label><input type=\"checkbox\" id=\"opt-incid\" checked> \u00e9carter les parties incident\u00e9es<\/label>\n    <label><input type=\"checkbox\" id=\"opt-excl\"> exclure les moteurs contenant\n      <input type=\"text\" id=\"motif\" value=\"UCI py\"><\/label>\n    <label>parties min. <input type=\"text\" id=\"minparties\" value=\"1\" style=\"width:48px\"><\/label>\n  <\/div>\n\n  <p class=\"resume\" id=\"resume\"><\/p>\n  <div id=\"sortie\"><p class=\"vide\">Aucun fichier charg\u00e9.<\/p><\/div>\n\n  <details class=\"reglages\">\n    <summary>Cotes de r\u00e9f\u00e9rence &mdash; modifier ou remplacer<\/summary>\n    <p class=\"mini\">\n      Un moteur par ligne, au format <code>Nom = cote<\/code>. Ces cotes servent d'ancrage au\n      calcul des performances : seules les parties contre un moteur list\u00e9 ici y contribuent.\n      Le nom doit correspondre exactement \u00e0 celui du PGN.\n    <\/p>\n    <textarea class=\"cotes\" id=\"cotes\" spellcheck=\"false\"><\/textarea>\n    <p style=\"margin-top:.8em;\">\n      <button class=\"dl-button\" id=\"appliquer\" style=\"font-size:.9rem; padding:9px 20px;\">Appliquer<\/button>\n      <button class=\"dl-button\" id=\"reinit\" style=\"font-size:.9rem; padding:9px 20px; background:#7a746a;\">R\u00e9tablir la liste d'origine<\/button>\n    <\/p>\n  <\/details>\n\n  <div class=\"box naum-note-box\">\n    <span class=\"box-titre\">Comment lire les colonnes<\/span>\n    <strong>Pts<\/strong> et <strong>%<\/strong> : le score brut dans ce tournoi, sans pond\u00e9ration.\n    <strong>Perf<\/strong> : la performance Elo, calcul\u00e9e uniquement sur les parties contre des\n    adversaires cot\u00e9s, par la formule <code>cote adverse moyenne + 400\u00b7log\u2081\u2080(p\/(1\u2212p))<\/code>.\n    <strong>\u00b1<\/strong> : l'incertitude \u00e0 un \u00e9cart-type. <strong>Anc.<\/strong> : le nombre de\n    parties contre un adversaire cot\u00e9, sur lequel repose la performance &mdash; en dessous de\n    vingt, la valeur est indicative. <strong>\u0394<\/strong> : l'\u00e9cart \u00e0 la cote du classement,\n    quand le moteur y figure. Cliquez une ligne pour voir le d\u00e9tail adversaire par adversaire.\n  <\/div>\n\n  <p class=\"sig\">chess.lumn.eu &middot; laboratoire de calibration<\/p>\n\n<\/article>\n<script>\n(function(){\n\"use strict\";\n\n\/\/ ---------------------------------------------------------------- cotes de reference\n\/\/ Classement du laboratoire au 07\/09\/2026. Modifiable dans le panneau \"Cotes de reference\".\nconst COTES_DEFAUT = `Stockfish 18 \u2014 8 Threads = 3707\nCaissa 1.24 \u2014 8 Threads = 3692\nLc0 v0.31.0 RTX 4060 Ti = 3687\nFritz 20 \u2014 8 Threads = 3676\nStockfish 12 \u2014 8 Threads = 3665\nRebel-16.3 \u2014 8 Threads = 3655\nStockfish 11 64 \u2014 8 Threads = 3635\nDragon Komodo \u2014 8 Threads = 3634\nFire 9.3 \u2014 8 Threads = 3596\nWasp 7.00 \u2014 8 Threads = 3556\nKomodo 14.1 64-bit \u2014 8 Threads = 3551\nFire 8 x64 \u2014 8 Threads = 3527\nEthereal 12.75 \u2014 8 Threads = 3512\nKomodo 12.1.1 64-bit \u2014 8 Threads = 3510\nCaissa 1.5 \u2014 8 Threads = 3493\nHoudini 5.01 \u2014 8 Threads = 3483\nStockfish 8 64 \u2014 8 Threads = 3480\nEthereal 11.75 \u2014 8 Threads = 3454\nXiphos 0.6 \u2014 8 Threads = 3452\nWasp 5.50 \u2014 8 Threads = 3451\nBooot 6.5_x64 \u2014 8 Threads = 3446\nRebel-Extreme-1.1 \u2014 8 Threads = 3443\nrofChade 2.3 \u2014 8 Threads = 3412\nDeep Shredder 13 x64 \u2014 8 Threads = 3394\nLaser 1.7 \u2014 8 Threads = 3376\nHoudini 4 Pro x64 \u2014 8 Threads = 3337\nDefenchess 2.2 x64 \u2014 8 Threads = 3335\nGull 3 x64 \u2014 8 Threads = 3314\nCritter 1.6a 64-bit \u2014 8 Threads = 3260\nWasp 3.75 \u2014 8 Threads = 3253\nDark Toga 0.3.12 JA \u2014 8 Threads = 3208\nSenpai 2.0 \u2014 8 Threads = 3181\nWasp 2.00 \u2014 8 Threads = 3174\nNaum 4.2 \u2014 8 Threads = 3168\nKomodo64 3 = 3164\nRybka 2.4 mp 32-bit \u2014 8 Threads = 3048\nFritz 13 SE = 3040\nGull 1.2 x64 = 3005\nGlaurung 2.2 JA \u2014 8 Threads = 2990\nToga II 1.4 beta5c \u2014 8 Threads = 2961\nLoop M1-T \u2014 4 Threads = 2936\nFruit 2.3.1 = 2890\nThe King 3.50 8CPU = 2870\nFritz 10 = 2866\nShredder 10 UCI = 2848\nFritz 9 UCI py = 2824\nChess Tiger 2007 = 2792\nShredder 9.1 UCI = 2792\nShredder 6 = 2790\nFritz 8 = 2778\nDeep Junior 7 = 2776\nDeep Gandalf 7.0-64 = 2772\nTHE KING 3.23 = 2765\nProDeo 2.0 = 2762\nFritz 7 = 2762\nHiarcs 8 = 2745\nDeep Fritz = 2726\nDeep Sjeng 1.6 \u2014 8 Threads = 2725\nJunior 8 = 2723\nTHE KING 3.33 = 2721\nGambit Tiger 2.0 = 2714\nJunior 8 UCI py = 2711\nThe King 3.50 = 2703\nRuffian 2.1.0 = 2703\nJunior 7 = 2695\nGandalf 6.01 = 2678\nSOS 5 for Arena = 2675\nList 512 = 2651\nFritz 6 = 2651\nZarkov 6.50 w32 = 2640\nRuffian 1.0.5 = 2629\nGandalf 6.0 = 2608\nGandalf 5.1 = 2608\nNimzo 8 = 2605\nFritz 5.32 = 2594\nHiarcs 7.32 = 2593\nYace Paderborn = 2562\nCray Blitz 4.9h = 2531\nRebel 6.0 = 2505`;\n\n\/\/ ---------------------------------------------------------------- lecture du PGN\nfunction parsePGN(text){\n  text = text.replace(\/\\r\\n\/g,'\\n').replace(\/\\r\/g,'\\n');\n  const blocs = text.split(\/(?=\\[Event\\s*\")\/).filter(b => \/\\[Event\\s*\"\/.test(b));\n  const tag = (b,n) => { const m = b.match(new RegExp('\\\\[' + n + '\\\\s+\"([^\"]*)\"\\\\]')); return m ? m[1] : ''; };\n  return blocs.map(b => ({\n    white: tag(b,'White'), black: tag(b,'Black'), result: tag(b,'Result'),\n    term:  tag(b,'Termination').toLowerCase(),\n    ply:   parseInt(tag(b,'PlyCount')||'0',10),\n    moves: (b.slice(b.lastIndexOf(']')+1)\n             .match(\/[a-h][1-8][a-h][1-8][qrbn]?|[KQRBNO][-a-h1-8x+#=]*|[a-h]x?[a-h]?[1-8](=[QRBN])?\/g)||[]).join(' ')\n  }));\n}\n\n\/\/ Terminaisons qui signalent une partie dont le resultat ne veut rien dire.\nconst TERM_ECARTER = ['crash','stalled connection','disconnected','terminated',\n                      'illegal move','unterminated','no result','abandoned'];\n\nfunction agreger(parties, opt){\n  const vus = new Set(), E = new Map();\n  let lus=0, dup=0, exclus=0, incid=0, retenues=0;\n  const get = e => { if(!E.has(e)) E.set(e,{pts:0,n:0,opp:new Map()}); return E.get(e); };\n  for(const g of parties){\n    lus++;\n    if(!['1-0','0-1','1\/2-1\/2'].includes(g.result)){ incid++; continue; }\n    if(opt.incid && TERM_ECARTER.includes(g.term)){ incid++; continue; }\n    if(opt.motif){\n      const m = opt.motif.toLowerCase();\n      if(g.white.toLowerCase().includes(m) || g.black.toLowerCase().includes(m)){ exclus++; continue; }\n    }\n    if(opt.dedup){\n      \/\/ texte COMPLET des coups : deux parties de meme ouverture ne sont pas des doublons\n      const cle = g.white+'|'+g.black+'|'+g.ply+'|'+g.moves;\n      if(vus.has(cle)){ dup++; continue; }\n      vus.add(cle);\n    }\n    retenues++;\n    const pw = g.result==='1-0' ? 1 : g.result==='0-1' ? 0 : 0.5;\n    [[g.white,g.black,pw],[g.black,g.white,1-pw]].forEach(([moi,adv,p])=>{\n      const e = get(moi); e.pts += p; e.n++;\n      const o = e.opp.get(adv) || {pts:0,n:0}; o.pts += p; o.n++; e.opp.set(adv,o);\n    });\n  }\n  return {E, stats:{lus,dup,exclus,incid,retenues}};\n}\n\nfunction classer(E, cotes){\n  const out = [];\n  for(const [nom,e] of E){\n    let ap=0, an=0, ws=0;\n    for(const [adv,o] of e.opp){\n      const c = cotes[adv];\n      if(c!==undefined && adv!==nom){ ap+=o.pts; an+=o.n; ws+=c*o.n; }\n    }\n    let perf=null, err=null, champ=null;\n    if(an>0){\n      champ = ws\/an; const x = ap\/an;\n      if(x>0 && x<1){ perf = champ + 400*Math.log10(x\/(1-x)); err = 400*Math.sqrt(x*(1-x)\/an); }\n    }\n    const cote = cotes[nom]!==undefined ? cotes[nom] : null;\n    out.push({nom, pts:e.pts, n:e.n, pct:100*e.pts\/e.n, perf, err, anc:an, champ, cote,\n              ecart:(perf!==null && cote!==null)?perf-cote:null, opp:e.opp});\n  }\n  return out;\n}\n\n\/\/ ---------------------------------------------------------------- interface\nconst $ = s => document.querySelector(s);\nlet PARTIES = [], COTES = {}, TRI = {col:'perf', desc:true}, OUVERT = null;\n\nfunction lireCotes(txt){\n  const o = {};\n  txt.split('\\n').forEach(l=>{\n    const i = l.lastIndexOf('=');\n    if(i<0) return;\n    const nom = l.slice(0,i).trim(), v = parseInt(l.slice(i+1).trim(),10);\n    if(nom && !isNaN(v)) o[nom]=v;\n  });\n  return o;\n}\n\nfunction fmt(v,d){ return v===null||v===undefined ? '\u2014' : (d? v.toFixed(d) : Math.round(v).toString()); }\n\nfunction rendre(){\n  if(!PARTIES.length){ $('#sortie').innerHTML='<p class=\"vide\">Aucun fichier charg\u00e9.<\/p>'; $('#resume').textContent=''; return; }\n  const opt = {dedup:$('#opt-dedup').checked, incid:$('#opt-incid').checked,\n               motif:$('#opt-excl').checked ? $('#motif').value.trim() : ''};\n  const {E,stats} = agreger(PARTIES, opt);\n  const min = Math.max(1, parseInt($('#minparties').value,10)||1);\n  let r = classer(E, COTES).filter(x=>x.n>=min);\n\n  const cle = TRI.col, sens = TRI.desc?-1:1;\n  r.sort((a,b)=>{\n    if(cle==='nom') return sens*a.nom.localeCompare(b.nom);\n    const va = a[cle], vb = b[cle];\n    const na = (va===null||va===undefined), nb = (vb===null||vb===undefined);\n    if(na&&nb) return 0; if(na) return 1; if(nb) return -1;   \/\/ valeurs absentes toujours en bas\n    return sens*(va-vb);\n  });\n\n  $('#resume').textContent =\n    `${stats.lus} partie(s) lue(s) \u00b7 ${stats.retenues} retenue(s) \u00b7 ${stats.dup} doublon(s)` +\n    ` \u00b7 ${stats.incid} incident(s) \u00b7 ${stats.exclus} exclue(s) par motif \u00b7 ${r.length} moteur(s) class\u00e9(s)`;\n\n  const th = (c,lib,titre) =>\n    `<th data-col=\"${c}\" title=\"${titre||''}\">${lib}${TRI.col===c?' <span class=\"fleche\">'+(TRI.desc?'\u25bc':'\u25b2')+'<\/span>':''}<\/th>`;\n  let h = '<div class=\"tbl-wrap\"><table class=\"cls\"><thead><tr>' +\n    '<th>#<\/th>' + th('nom','Moteur') + th('pts','Pts','Points marqu\u00e9s') + th('n','J','Parties jou\u00e9es') +\n    th('pct','%','Pourcentage') + th('perf','Perf','Performance Elo') + '<th>\u00b1<\/th>' +\n    th('anc','Anc.','Parties contre un adversaire cot\u00e9') + th('cote','Cote','Cote du classement') +\n    th('ecart','\u0394','\u00c9cart \u00e0 la cote') + '<\/tr><\/thead><tbody>';\n  r.forEach((x,i)=>{\n    const py = \/uci py\/i.test(x.nom) ? ' py' : '';\n    const dcl = x.ecart===null ? '' : (x.ecart>=0?' class=\"num pos\"':' class=\"num neg\"');\n    h += `<tr class=\"ligne${py}\" data-nom=\"${encodeURIComponent(x.nom)}\">` +\n         `<td class=\"num\">${i+1}<\/td><td>${x.nom}<\/td>` +\n         `<td class=\"num\">${x.pts.toFixed(1)}<\/td><td class=\"num\">${x.n}<\/td>` +\n         `<td class=\"num\">${x.pct.toFixed(1)} %<\/td>` +\n         `<td class=\"num\">${fmt(x.perf)}<\/td><td class=\"num\">${x.err===null?'\u2014':'\u00b1'+fmt(x.err)}<\/td>` +\n         `<td class=\"num\">${x.anc}<\/td><td class=\"num\">${x.cote===null?'\u2014':x.cote}<\/td>` +\n         `<td${dcl||' class=\"num\"'}>${x.ecart===null?'\u2014':(x.ecart>0?'+':'')+fmt(x.ecart)}<\/td><\/tr>`;\n    if(OUVERT === x.nom){\n      const lignes = [...x.opp.entries()]\n        .map(([adv,o])=>({adv,o,c:COTES[adv]!==undefined?COTES[adv]:null}))\n        .sort((a,b)=>(b.c??-9e9)-(a.c??-9e9));\n      let d = '<tr class=\"detail\"><td colspan=\"10\"><table><thead><tr><th>Adversaire<\/th>' +\n              '<th>Cote<\/th><th>Score<\/th><th>%<\/th><th>Perf locale<\/th><\/tr><\/thead><tbody>';\n      lignes.forEach(({adv,o,c})=>{\n        const p=o.pts\/o.n;\n        const pl = (c!==null && p>0 && p<1) ? Math.round(c+400*Math.log10(p\/(1-p))) : '\u2014';\n        d += `<tr><td>${adv}<\/td><td class=\"num\">${c===null?'\u2014':c}<\/td>` +\n             `<td class=\"num\">${o.pts.toFixed(1)} \/ ${o.n}<\/td>` +\n             `<td class=\"num\">${(100*p).toFixed(1)} %<\/td><td class=\"num\">${pl}<\/td><\/tr>`;\n      });\n      d += '<\/tbody><\/table><\/td><\/tr>';\n      h += d;\n    }\n  });\n  h += '<\/tbody><\/table><\/div>';\n  $('#sortie').innerHTML = h;\n\n  $('#sortie').querySelectorAll('th[data-col]').forEach(e=>{\n    e.onclick = () => {\n      const c = e.dataset.col;\n      if(TRI.col===c) TRI.desc = !TRI.desc;\n      else { TRI.col=c; TRI.desc = (c!=='nom'); }\n      rendre();\n    };\n  });\n  $('#sortie').querySelectorAll('tr.ligne').forEach(e=>{\n    e.onclick = () => { const n = decodeURIComponent(e.dataset.nom);\n                        OUVERT = (OUVERT===n) ? null : n; rendre(); };\n  });\n}\n\nfunction charger(fichiers){\n  const restants = [...fichiers];\n  PARTIES = [];\n  let lus = 0;\n  restants.forEach(f=>{\n    const fr = new FileReader();\n    fr.onload = ev => {\n      PARTIES = PARTIES.concat(parsePGN(ev.target.result));\n      if(++lus === restants.length){ OUVERT=null; rendre(); }\n    };\n    fr.readAsText(f, 'utf-8');\n  });\n}\n\n\/\/ evenements\nconst depot = $('#depot');\ndepot.onclick = () => $('#fichier').click();\n$('#fichier').onchange = e => charger(e.target.files);\n['dragenter','dragover'].forEach(t=>depot.addEventListener(t,e=>{e.preventDefault();depot.classList.add('survol');}));\n['dragleave','drop'].forEach(t=>depot.addEventListener(t,e=>{e.preventDefault();depot.classList.remove('survol');}));\ndepot.addEventListener('drop', e=>{ if(e.dataTransfer.files.length) charger(e.dataTransfer.files); });\n['#opt-dedup','#opt-incid','#opt-excl','#motif','#minparties'].forEach(s=>{\n  $(s).addEventListener('change', rendre);\n  $(s).addEventListener('input',  rendre);\n});\n$('#cotes').value = COTES_DEFAUT;\nCOTES = lireCotes(COTES_DEFAUT);\n$('#appliquer').onclick = () => { COTES = lireCotes($('#cotes').value); rendre(); };\n$('#reinit').onclick = () => { $('#cotes').value = COTES_DEFAUT; COTES = lireCotes(COTES_DEFAUT); rendre(); };\n})();\n<\/script>\n<\/body>\n<\/html>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Calculateur de tournoi Analyseur de tournoi \u2014 classement par points ou performance Elo Laboratoire \u00b7 outil Analyseur de tournoi D\u00e9posez un fichier PGN : la page en tire le classement du tournoi, par points ou par performance Elo, en s&#8217;appuyant sur les cotes du classement du laboratoire. Tout se passe dans votre navigateur, aucun fichier&hellip;&nbsp;<a href=\"https:\/\/www.chess.lumn.eu\/wordpress\/2026\/09\/07\/calculateur-de-tournoi\/\" class=\"\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">Calculateur de tournoi<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":""},"categories":[5],"tags":[],"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"valterd","author_link":"https:\/\/www.chess.lumn.eu\/wordpress\/author\/valterd\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/www.chess.lumn.eu\/wordpress\/category\/tournaments\/\" rel=\"category tag\">Tournaments<\/a>","rttpg_excerpt":"Calculateur de tournoi Analyseur de tournoi \u2014 classement par points ou performance Elo Laboratoire \u00b7 outil Analyseur de tournoi D\u00e9posez un fichier PGN : la page en tire le classement du tournoi, par points ou par performance Elo, en s&#8217;appuyant sur les cotes du classement du laboratoire. Tout se passe dans votre navigateur, aucun fichier&hellip;&nbsp;Read&hellip;","_links":{"self":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4048"}],"collection":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/comments?post=4048"}],"version-history":[{"count":5,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4048\/revisions"}],"predecessor-version":[{"id":4053,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4048\/revisions\/4053"}],"wp:attachment":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/media?parent=4048"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/categories?post=4048"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/tags?post=4048"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}