{
  "schemaVersion": 1,
  "slug": "darts-score-calculator",
  "category": "sports",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "027e1b236c8f4fd95bd89e7242d0c900b5d5f9f537a10449a2bea96c67dcc60c",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/darts-score-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "A = 3S ÷ D; P = 100H ÷ T; R = D ÷ H",
    "vars": [
      [
        "S",
        "Points scored"
      ],
      [
        "D",
        "Darts thrown"
      ],
      [
        "H",
        "Checkouts hit"
      ],
      [
        "T",
        "Checkout attempts"
      ],
      [
        "A",
        "Three-dart average"
      ],
      [
        "P",
        "Checkout percentage"
      ],
      [
        "R",
        "Darts per checkout"
      ]
    ],
    "note": "Calculate a three-dart average, checkout percentage and one suggested route for the remaining score. Averages and checkout rates depend on the matches and attempts you enter."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-18",
    "domain": "math",
    "sources": [
      {
        "name": "Darts Regulation Authority — Rules",
        "url": "https://www.pdc.tv/sites/default/files/2020-08/DRA-Rules.pdf"
      }
    ],
    "implementationHash": "027e1b236c8f4fd95bd89e7242d0c900b5d5f9f537a10449a2bea96c67dcc60c",
    "basis": "Checkout mode searches standard double-out finishes using at most the available darts. S is single, D double and T triple; 25 is outer bull, while Bull is 50 and may be the final double. A suggested route is one valid option, not a guaranteed or individually optimal strategy. Some valid scores have no finish: 1 and 169 cannot be checked out in three darts, and the maximum three-dart checkout is 170. In statistics mode, three-dart average is points scored divided by all darts thrown, times three. Count each dart aimed at a finishing double as one checkout attempt, not each visit. Checkout percentage is successful checkouts divided by those attempts, times 100; it is unavailable with zero attempts. All darts per successful checkout uses all darts thrown divided by successes, not just finishing darts, and is unavailable with zero successes. Use totals from the same set of legs.",
    "limitations": "Find a double-out route with 1–3 darts available, see the remaining score after each dart and compare alternative routes. Switch to Match statistics for three-dart average and checkout percentage."
  },
  "contract": {
    "compute": "const empty={average:null,checkoutPct:null,dartsPerCheckout:null,finish:null,note:null,__checkoutState:[mode,Number(dartsLeft)]};\nconst statsValid=[totalScored,dartsThrown,checkoutsHit,checkoutAttempts].every(Number.isInteger)&&totalScored>=0&&totalScored<=100000&&dartsThrown>=1&&dartsThrown<=10000&&totalScored<=dartsThrown*60&&checkoutsHit>=0&&checkoutAttempts>=0&&checkoutsHit<=checkoutAttempts&&checkoutAttempts<=2000&&checkoutAttempts<=dartsThrown;\nconst left=Number(dartsLeft),scoreValid=Number.isInteger(score)&&score>=1&&score<=501&&Number.isInteger(left)&&left>=1&&left<=3;\nif(!['checkout','stats'].includes(mode)||(mode==='checkout'&&!scoreValid)||(mode==='stats'&&!statsValid))return {...empty,__error:'inputError'};\nconst average=statsValid?totalScored/dartsThrown*3:null,checkoutPct=statsValid&&checkoutAttempts>0?checkoutsHit/checkoutAttempts*100:null,dartsPerCheckout=statsValid&&checkoutsHit>0?dartsThrown/checkoutsHit:null;\nconst curated={170:'T20 T20 Bull',167:'T20 T19 Bull',164:'T20 T18 Bull',161:'T20 T17 Bull',160:'T20 T20 D20',122:'T18 T20 D4',121:'T20 T11 D14',110:'T20 Bull',100:'T20 D20',81:'T19 D12',60:'S20 D20',50:'Bull',40:'D20',32:'D16',20:'D10',16:'D8',8:'D4',4:'D2',2:'D1'};\nconst marks=[];for(let n=1;n<=20;n++)marks.push({v:3*n,t:'T'+n},{v:2*n,t:'D'+n},{v:n,t:'S'+n});marks.push({v:25,t:'25'},{v:50,t:'Bull'});marks.sort((a,b)=>b.v-a.v||a.t.localeCompare(b.t));const doubles=[];for(let n=1;n<=20;n++)doubles.push({v:2*n,t:'D'+n});doubles.push({v:50,t:'Bull'});\nconst routes=[];const add=r=>{const text=r.join(' ');if(r.length<=left&&!routes.includes(text))routes.push(text);};\nif(mode==='checkout'&&score<=170){if(curated[score])add(curated[score].split(' '));const one=doubles.find(x=>x.v===score);if(one)add([one.t]);if(left>=2)for(const a of marks){const last=doubles.find(x=>x.v===score-a.v);if(last)add([a.t,last.t]);if(routes.length>=5)break;}if(left>=3&&routes.length<5){outer:for(const a of marks)for(const b of marks){const last=doubles.find(x=>x.v===score-a.v-b.v);if(last){add([a.t,b.t,last.t]);if(routes.length>=5)break outer;}}}}\nconst route=routes[0],finish=mode==='checkout'?(route||(left===3?'No checkout in three darts':'—')):null;let remain=score;\nconst steps=route?route.split(' ').map((mark,i)=>{const v=marks.find(x=>x.t===mark).v;remain-=v;return {dart:i+1,mark,points:v,remaining:remain};}):[];\nreturn {average,checkoutPct,dartsPerCheckout,finish,note:mode==='checkout'?'In standard double-out, the final dart must land in a double; the bull (50) counts as double bull.':null,__checkoutState:[mode,left],__checkoutSteps:steps,__routes:routes};",
    "inputs": [
      {
        "name": "mode",
        "type": "select",
        "default": "checkout",
        "options": [
          {
            "value": "checkout"
          },
          {
            "value": "stats"
          }
        ]
      },
      {
        "name": "score",
        "type": "number",
        "default": 121,
        "min": 1,
        "max": 501,
        "step": 1,
        "requiredValue": true,
        "strictRange": true,
        "showWhen": {
          "mode": [
            "checkout"
          ]
        }
      },
      {
        "name": "dartsLeft",
        "type": "select",
        "default": 3,
        "options": [
          {
            "value": 3
          },
          {
            "value": 2
          },
          {
            "value": 1
          }
        ],
        "showWhen": {
          "mode": [
            "checkout"
          ]
        }
      },
      {
        "name": "totalScored",
        "type": "number",
        "default": 1500,
        "min": 0,
        "max": 100000,
        "step": 1,
        "requiredValue": true,
        "strictRange": true,
        "slider": false,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      },
      {
        "name": "dartsThrown",
        "type": "number",
        "default": 60,
        "min": 1,
        "max": 10000,
        "step": 1,
        "requiredValue": true,
        "strictRange": true,
        "slider": false,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      },
      {
        "name": "checkoutsHit",
        "type": "number",
        "default": 12,
        "min": 0,
        "max": 2000,
        "step": 1,
        "slider": false,
        "requiredValue": true,
        "strictRange": true,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      },
      {
        "name": "checkoutAttempts",
        "type": "number",
        "default": 22,
        "min": 0,
        "max": 2000,
        "step": 1,
        "slider": false,
        "requiredValue": true,
        "strictRange": true,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      }
    ],
    "outputs": [
      {
        "name": "finish",
        "precision": 0,
        "unit": "",
        "type": "text",
        "display": "tokens",
        "showWhen": {
          "mode": [
            "checkout"
          ]
        },
        "optional": true
      },
      {
        "name": "average",
        "precision": 2,
        "unit": "",
        "showWhen": {
          "mode": [
            "stats"
          ]
        },
        "optional": true
      },
      {
        "name": "checkoutPct",
        "precision": 1,
        "unit": "%",
        "optional": true,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      },
      {
        "name": "dartsPerCheckout",
        "precision": 1,
        "unit": "",
        "optional": true,
        "showWhen": {
          "mode": [
            "stats"
          ]
        }
      },
      {
        "name": "note",
        "precision": 0,
        "unit": "",
        "type": "text",
        "optional": true,
        "showWhen": {
          "mode": [
            "checkout"
          ]
        }
      }
    ],
    "interpret": null
  }
}
