SHARE
TWEET

Bowling Scores

Double_X Mar 23rd, 2019 (edited) 82 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <!DOCTYPE html>
  2. <html>
  3.     <head>
  4.         <meta charset="utf-8">
  5.         <title>Bowling Scores</title>
  6.         <script>
  7.             // Edit these configurations
  8.             const DAY_COUNT = 240, ROUNDS_PER_DAY = 20;
  9.             const FRAME_COUNT = 10, PIN_COUNT = 10;
  10.             const ABOVE_OLD_SCORE = 79, BELOW_OLD_SCORE = 301;
  11.             const MAX_SPLITS = 11, MAX_STRIKES = 12, MAX_STRIKE_STRING_SUM = 10;
  12.             const MAX_STRIKE_STRING_LENGTH = 10, MAX_SPLIT_SPARES = 10;
  13.             const ASC_SCORE_MILESTONES = [
  14.                 80,
  15.                 90,
  16.                 100,
  17.                 110,
  18.                 120,
  19.                 130,
  20.                 140,
  21.                 150,
  22.                 160,
  23.                 170,
  24.                 180,
  25.                 190,
  26.                 200,
  27.                 210,
  28.                 220,
  29.                 230,
  30.                 240,
  31.                 250,
  32.                 260,
  33.                 270,
  34.                 280,
  35.                 290
  36.             ], SPARE_MARK = "./", EMPTY_MARK = " .", FRAME_SEPARATOR = "|";
  37.             const STRIKE_MARK = ".X", ZERO_MARK = ".-", SPLIT_MARK = "S";
  38.             const SCORE_9_MARK = ".9", SCORE_8_MARK = ".8";
  39.             const SCORE_2_MARK = ".2", SCORE_1_MARK = ".1";
  40.             const SPLIT_8_MARK = `${SPLIT_MARK}8`, MARK_SCORES = {
  41.                 [STRIKE_MARK]: 10,
  42.                 [SCORE_9_MARK]: 9,
  43.                 [SCORE_8_MARK]: 8,
  44.                 [SPLIT_8_MARK]: 8,
  45.                 [SCORE_2_MARK]: 2,
  46.                 [SCORE_1_MARK]: 1,
  47.                 [ZERO_MARK]: 0
  48.             };
  49.             const HEAD_SCORES = {
  50.                 [STRIKE_MARK]: 8, // 50% chance to strike
  51.                 [SCORE_9_MARK]: 4, // 25% chance for head ball to have 9 pins
  52.                 [SCORE_8_MARK]: 3, // 18.75% chance for head ball to have 8 pins
  53.                 [SPLIT_8_MARK]: 1 // 6.25% chance for head ball to have 8 pins with a split
  54.             };
  55.             const TAIL_SCORES = {
  56.                 // Used when the head ball has 9 pins
  57.                 [SCORE_9_MARK]: {
  58.                     [SCORE_1_MARK]: 1 // 100% chance to spare
  59.                 },
  60.                 //
  61.                 // Used when the head ball has 8 pins
  62.                 [SCORE_8_MARK]: {
  63.                     [SCORE_2_MARK]: 1, // 100% chance to spare
  64.                 },
  65.                 //
  66.                 // Used when the head ball has 8 pins with a split
  67.                 [SPLIT_8_MARK]: {
  68.                     [SCORE_2_MARK]: 2, // 50% chance to spare
  69.                     [SCORE_1_MARK]: 1, // 25% chance for the tail ball to have 1 pin
  70.                     [ZERO_MARK]: 1 // 25% chance for the tail ball to miss
  71.                 }
  72.                 //
  73.             };
  74.             //
  75.  
  76.             // Don't edit these implementations
  77.             const isStrike = headMark => {
  78.                 const headScore = MARK_SCORES[headMark];
  79.                 return headScore && headScore == PIN_COUNT;
  80.             };
  81.             const isSpare = (headMark, tailMark) => {
  82.                 if (isStrike(headMark)) return false;
  83.                 if (tailMark == SPARE_MARK) return true;
  84.                 const headScore = MARK_SCORES[headMark];
  85.                 if (!headScore) return false;
  86.                 const tailScore = MARK_SCORES[tailMark];
  87.                 return tailScore && headScore + tailScore == PIN_COUNT;
  88.             }, isLastFrame = count => count == FRAME_COUNT;
  89.             const sum = (s, no) => s + no, ballScoreMark = scores => {
  90.                 let rand = Math.floor(Math.random() * Object.values(scores).reduce(sum, 0)) + 1;
  91.                 for (const mark of Object.keys(scores)) {
  92.                     const ratio = scores[mark];
  93.                     if (rand <= ratio) return mark;
  94.                    rand -= ratio;
  95.                }
  96.                return ZERO_MARK;
  97.            };
  98.            const tailMark = headMark => {
  99.                 if (isStrike(headMark)) return EMPTY_MARK;
  100.                 const tailScore = TAIL_SCORES[headMark];
  101.                 const mark = tailScore ? ballScoreMark(tailScore) : ZERO_MARK;
  102.                 return isSpare(headMark, mark) ? SPARE_MARK : mark;
  103.             };
  104.             const oldFrameScores = round => {
  105.                 let isStrike1 = false, isStrike2 = false, isLastSpare = false;
  106.                 const scores = [];
  107.                 round.forEach(({ mark1, mark2, mark3 }, i) => {
  108.                     const score1 = MARK_SCORES[mark1];
  109.                     const score2 = MARK_SCORES[mark2];
  110.                     const score3 = MARK_SCORES[mark3];
  111.                     const isLast = isLastFrame(i + 1);
  112.                     const lastScore = () => {
  113.                         if (isStrike(mark2)) {
  114.                           return (scores[i - 1] || 0) + 20 + score3;
  115.                         } else if (isSpare(mark2, mark3)) {
  116.                           return (scores[i - 1] || 0) + 20;
  117.                         } else {
  118.                           return (scores[i - 1] || 0) + 10 + score2 + score3;
  119.                         }
  120.                     }
  121.                     if (isStrike1) {
  122.                         if (isStrike2) {
  123.                             scores[i - 2] = (scores[i - 3] || 0) + 20 + score1;
  124.                         }
  125.                         if (isStrike(mark1)) {
  126.                             if (isLast) {
  127.                                 scores[i - 1] = (scores[i - 2] || 0) + 20 + score2;
  128.                                 scores[i] = lastScore();
  129.                             } else {
  130.                                 isStrike2 = true;
  131.                             }
  132.                         } else {
  133.                             isStrike1 = isStrike2 = false;
  134.                             if (isSpare(mark1, mark2)) {
  135.                                 scores[i - 1] = (scores[i - 2] || 0) + 20;
  136.                                 isLastSpare = true;
  137.                                 if (!isLast) return;
  138.                                 scores[i] = (scores[i - 1] || 0) + 10 + score3;
  139.                             } else {
  140.                                 scores[i - 1] = (scores[i - 2] || 0) + 10 + score1 + score2;
  141.                                 scores[i] = (scores[i - 1] || 0) + score1 + score2;
  142.                             }
  143.                         }
  144.                     } else {
  145.                         if (isLastSpare) {
  146.                             isLastSpare = false;
  147.                             scores[i - 1] = (scores[i - 2] || 0) + 10 + score1;
  148.                         }
  149.                         if (isStrike(mark1)) {
  150.                             isStrike1 = true;
  151.                             if (isLast) scores[i] = lastScore();
  152.                         } else if (isSpare(mark1, mark2)) {
  153.                             isLastSpare = true;
  154.                             if (!isLast) return;
  155.                             scores[i] = (scores[i - 1] || 0) + 10 + score3;
  156.                         } else {
  157.                             scores[i] = (scores[i - 1] || 0) + score1 + score2;
  158.                         }
  159.                     }
  160.                 });
  161.                 return scores;
  162.             };
  163.             const newFrameScore = ({ mark1, mark2 }) => {
  164.                 if (isStrike(mark1)) return 30;
  165.                 const headScore = MARK_SCORES[mark1];
  166.                 return headScore + (isSpare(mark1, mark2) ? 10 : MARK_SCORES[mark2]);
  167.             };
  168.             const newFrameScores = round => {
  169.                 let total = 0;
  170.                 const scores = [];
  171.                 round.forEach(frame => {
  172.                     const score = newFrameScore(frame);
  173.                     scores.push(total + score);
  174.                     total += score;
  175.                 });
  176.                 return scores;
  177.             };
  178.             const lastFrame = headMark => {
  179.                 if (isStrike(headMark)) {
  180.                     const headMark2 = ballScoreMark(HEAD_SCORES);
  181.                     return {
  182.                         mark1: headMark,
  183.                         mark2: headMark2,
  184.                         mark3: isStrike(headMark2) ? ballScoreMark(HEAD_SCORES) : tailMark(headMark2)
  185.                     }
  186.                 }
  187.                 const tail = tailMark(headMark);
  188.                 return {
  189.                     mark1: headMark,
  190.                     mark2: tail,
  191.                     mark3: isSpare(headMark, tail) ? ballScoreMark(HEAD_SCORES) : EMPTY_MARK
  192.                 };
  193.             };
  194.             const frame = (_, i) => {
  195.                 const headMark = ballScoreMark(HEAD_SCORES);
  196.                 return isLastFrame(i + 1) ? lastFrame(headMark) : {
  197.                     mark1: headMark,
  198.                     mark2: tailMark(headMark)
  199.                 };
  200.             }, roundScore = scores => scores[9];
  201.             const roundMarkCount = (mark, round) => round.map(frame => {
  202.                 return Object.values(frame).filter(m => {
  203.                     return m.includes(mark);
  204.                 }).length;
  205.             }).reduce(sum, 0);
  206.             const roundStrikeStringSum = round => {
  207.                 let sum = 0, isLastStrike;
  208.                 round.forEach(frame => {
  209.                     Object.entries(frame).filter(([key, mark]) => {
  210.                         return key !== "mark3" && mark !== EMPTY_MARK;
  211.                     }).map(([key, mark]) => mark).forEach(mark => {
  212.                         if (!isStrike(mark)) return isLastStrike = false;
  213.                         if (isLastStrike) sum++;
  214.                         isLastStrike = true;
  215.                     });
  216.                 });
  217.                 return sum;
  218.             };
  219.             const roundStrikeStringLength = round => {
  220.                 let length = 0, tempLength = 0, isLastStrike;
  221.                 round.forEach(frame => {
  222.                     Object.entries(frame).filter(([key, mark]) => {
  223.                         return key !== "mark3" && mark !== EMPTY_MARK;
  224.                     }).map(([key, mark]) => mark).forEach(mark => {
  225.                         if (!isStrike(mark)) {
  226.                             length = Math.max(length, tempLength);
  227.                             tempLength = 0;
  228.                             return isLastStrike = false;
  229.                         }
  230.                         if (isLastStrike) tempLength++;
  231.                         isLastStrike = true;
  232.                     });
  233.                 });
  234.                 return Math.max(length, tempLength);
  235.             };
  236.             const roundSplitSpareCount = round => round.filter(frame => {
  237.                 const marks = Object.values(frame);
  238.                 if (!marks.includes(SPARE_MARK)) return false;
  239.                 if (frame.mark1.includes(SPLIT_MARK)) return true;
  240.                 return frame.mark2.includes(SPLIT_MARK);
  241.             }).length;
  242.             const isValid = round => {
  243.                 if (roundStrikeStringLength(round) > MAX_STRIKE_STRING_LENGTH) {
  244.                     return false;
  245.                 } else if (roundStrikeStringSum(round) > MAX_STRIKE_STRING_SUM) {
  246.                     return false;
  247.                 } else if (roundMarkCount(STRIKE_MARK, round) > MAX_STRIKES) {
  248.                     return false;
  249.                 } else if (roundSplitSpareCount(round) > MAX_SPLIT_SPARES) {
  250.                     return false;
  251.                 } else if (roundMarkCount(SPLIT_MARK, round) > MAX_SPLITS) {
  252.                     return false;
  253.                 }
  254.                 const score = roundScore(oldFrameScores(round));
  255.                 return score > ABOVE_OLD_SCORE && score < BELOW_OLD_SCORE;
  256.             }
  257.             const round = () => {
  258.                 const rawRound = Array(10).fill(0);
  259.                 let round;
  260.                 do { round = rawRound.map(frame); } while (!isValid(round));
  261.                 return round;
  262.             };
  263.             const roundScores = (system, rounds) => {
  264.                 return rounds.map(system).map(roundScore);
  265.             };
  266.             const roundScoreRanges = rounds => {
  267.                 const oldScores = roundScores(oldFrameScores, rounds);
  268.                 return roundScores(newFrameScores, rounds).map((score, i) => {
  269.                     return score - oldScores[i];
  270.                 });
  271.             };
  272.             const roundMarks = (mark, rounds) => {
  273.                 return rounds.map(roundMarkCount.bind(null, mark));
  274.             };
  275.             const roundStrikeStringSums = rounds => {
  276.                 return rounds.map(roundStrikeStringSum);
  277.             };
  278.             const roundStrikeStringLengths = rounds => {
  279.                 return rounds.map(roundStrikeStringLength);
  280.             }, roundSplitSpares = rounds => rounds.map(roundSplitSpareCount);
  281.             const isOpen = ({ mark2, mark3 }) => {
  282.                 if (mark2 === EMPTY_MARK || mark2 === SPARE_MARK) return false;
  283.                 return !isStrike(mark2) && mark3 !== SPARE_MARK;
  284.             }, roundOpenCount = round => round.filter(isOpen).length;
  285.             const roundOpens = rounds => rounds.map(roundOpenCount);
  286.             const isSplitOpen = frame => {
  287.                 const marks = Object.values(frame);
  288.                 if (marks.some(mark => mark === SPARE_MARK)) return false;
  289.                 if (frame.mark1.includes(SPLIT_MARK)) return true;
  290.                 return frame.mark2.includes(SPLIT_MARK);
  291.             };
  292.             const roundSplitOpenCount = round => {
  293.                 return round.filter(isSplitOpen).length;
  294.             }, roundSplitOpens = rounds => rounds.map(roundSplitOpenCount);
  295.             const roundNonSplitOpenCount = round => {
  296.                 return roundOpenCount(round) - roundSplitOpenCount(round);
  297.             };
  298.             const roundNonSplitOpens = rounds => {
  299.                 return rounds.map(roundNonSplitOpenCount);
  300.             }, avgNo = nos => nos.reduce(sum, 0) / nos.length;
  301.             const ascSort = (a, b) => a - b, summary = (data, name) => {
  302.                 const ascData = data.sort(ascSort);
  303.                 return [
  304.                     `Min ${name}: ${ascData[0]}`,
  305.                     `Avg ${name}: ${avgNo(ascData)}`,
  306.                     `Max ${name}: ${ascData[ascData.length - 1]}`
  307.                 ].join(" ");
  308.             }, isClearGame = round => !round.some(isOpen);
  309.             const clearGameCount = rounds => rounds.filter(isClearGame).length;
  310.             const clearGames = rounds => {
  311.                 return `Clear games: ${clearGameCount(rounds)}`;
  312.             };
  313.             const isSplitOnlyNonClearGame = round => {
  314.                 if (roundSplitOpenCount(round) === 0) return false;
  315.                 return roundNonSplitOpenCount(round) === 0;
  316.             };
  317.             const splitOnlyNonClearGameCount = rounds => {
  318.                 return rounds.filter(isSplitOnlyNonClearGame).length;
  319.             };
  320.             const splitOnlyNonClearGames = rounds => {
  321.                 return `Split only non-clear games: ${splitOnlyNonClearGameCount(rounds)}`;
  322.             };
  323.             const scoreMilestones = scores => {
  324.                 const milestone1 = ASC_SCORE_MILESTONES[0];
  325.                 return [
  326.                     `[0, ${milestone1}): ${scores.filter(score => {
  327.                         return score < milestone1;
  328.                    }).length}`
  329.                ].concat(ASC_SCORE_MILESTONES.map((milestone, i) => {
  330.                     const nextMilestone = ASC_SCORE_MILESTONES[i + 1];
  331.                     if (!isNaN(nextMilestone)) {
  332.                         return `[${milestone}, ${nextMilestone}): ${scores.filter(score => {
  333.                             return score >= milestone && score < nextMilestone;
  334.                         }).length}`;
  335.                     }
  336.                     return `[${milestone}, 300]: ${scores.filter(score => {
  337.                         return score >= milestone;
  338.                     }).length}`;
  339.                 })).join(" ");
  340.             };
  341.             const markResults = (rounds, resultMark) => {
  342.                 return Object.keys(MARK_SCORES).concat([
  343.                     SPARE_MARK
  344.                 ]).map(mark => {
  345.                     return `${mark}: ${rounds.reduce((sum, round) => {
  346.                         return sum + round.filter(frame => {
  347.                             return frame[resultMark] === mark;
  348.                         }).length;
  349.                     }, 0)}`;
  350.                 }).join(" | ");
  351.             };
  352.             const summaries = rounds => [
  353.                 summary(roundScores(oldFrameScores, rounds), "old score"),
  354.                 summary(roundScores(newFrameScores, rounds), "new score"),
  355.                 summary(roundScoreRanges(rounds), "new - old score difference"),
  356.                 summary(roundMarks(STRIKE_MARK, rounds), "strikes"),
  357.                 summary(roundStrikeStringSums(rounds), "strike string sum"),
  358.                 summary(roundStrikeStringLengths(rounds), "strike string length"),
  359.                 summary(roundMarks(SPARE_MARK, rounds), "spares"),
  360.                 summary(roundMarks(SPLIT_MARK, rounds), "splits"),
  361.                 summary(roundMarks(ZERO_MARK, rounds), "zeroes"),
  362.                 summary(roundSplitSpares(rounds), "split spares"),
  363.                 summary(roundOpens(rounds), "opens"),
  364.                 summary(roundSplitOpens(rounds), "split opens"),
  365.                 summary(roundNonSplitOpens(rounds), "non-split opens"),
  366.                 clearGames(rounds),
  367.                 splitOnlyNonClearGames(rounds),
  368.                 "Old Score Milestones:",
  369.                 scoreMilestones(roundScores(oldFrameScores, rounds)),
  370.                 "New Score Milestones:",
  371.                 scoreMilestones(roundScores(newFrameScores, rounds)),
  372.                 "1st Frame Mark",
  373.                 markResults(rounds, "mark1"),
  374.                 "2nd Frame Mark",
  375.                 markResults(rounds, "mark2"),
  376.                 "3rd Frame Mark",
  377.                 markResults(rounds, "mark3")
  378.             ].join("<br>");
  379.             const recordedFrames = round => round.map(({ mark1, mark2, mark3 }) => {
  380.                 return `${mark1}${mark2}${mark3 || ""}`
  381.             }).join(FRAME_SEPARATOR);
  382.             const recordedScore = score => {
  383.                 return `${Array(4 - `${score}`.length).fill(".").join("")}${score}`;
  384.             };
  385.             const recordedScores = scores => {
  386.                 return scores.map(recordedScore).join(FRAME_SEPARATOR);
  387.             };
  388.             const recordedRound = round => [
  389.                 recordedFrames(round),
  390.                 recordedScores(oldFrameScores(round)),
  391.                 recordedScores(newFrameScores(round))
  392.             ].join("<br>");
  393.             const recordedDay = rounds => [
  394.                 `<code>${rounds.map(recordedRound).join("<br>")}</code>`,
  395.                 `<div>${summaries(rounds)}</div>`
  396.             ].join("<br>");
  397.             const setHTML = (id, html) => {
  398.                 document.getElementById(id).innerHTML = html;
  399.             };
  400.             const ROUNDS = Array(DAY_COUNT * ROUNDS_PER_DAY).fill(0).map(round);
  401.             const DAYS = (() => {
  402.                 const days = [], rounds = ROUNDS.slice();
  403.                 while (rounds.length > 0) {
  404.                     days.push(rounds.splice(0, ROUNDS_PER_DAY));
  405.                 }
  406.                 return days;
  407.             })(), ASC_DAY_CLEAR_GAMES = DAYS.map(clearGameCount).sort(ascSort);
  408.             const ASC_DAY_SPLIT_ONLY_NON_CLEAR_GAMES = DAYS.map(splitOnlyNonClearGameCount).sort(ascSort);
  409.             const ASC_OLD_DAY_SCORES = DAYS.map(roundScores.bind(null, oldFrameScores)).map(avgNo).sort(ascSort);
  410.             const ASC_NEW_DAY_SCORES = DAYS.map(roundScores.bind(null, newFrameScores)).map(avgNo).sort(ascSort);
  411.             window.onload = () => {
  412.                 setHTML("summary", summaries(ROUNDS));
  413.                 setHTML("minDayClear", ASC_DAY_CLEAR_GAMES[0]);
  414.                 setHTML("maxDayClear", ASC_DAY_CLEAR_GAMES[ASC_DAY_CLEAR_GAMES.length - 1]);
  415.                 setHTML("minDaySplitNonClear", ASC_DAY_SPLIT_ONLY_NON_CLEAR_GAMES[0]);
  416.                 setHTML("maxDaySplitNonClear", ASC_DAY_SPLIT_ONLY_NON_CLEAR_GAMES[ASC_DAY_SPLIT_ONLY_NON_CLEAR_GAMES.length - 1]);
  417.                 setHTML("minOldDay", ASC_OLD_DAY_SCORES[0]);
  418.                 setHTML("maxOldDay", ASC_OLD_DAY_SCORES[ASC_OLD_DAY_SCORES.length - 1]);
  419.                 setHTML("minNewDay", ASC_NEW_DAY_SCORES[0]);
  420.                 setHTML("maxNewDay", ASC_NEW_DAY_SCORES[ASC_NEW_DAY_SCORES.length - 1]);
  421.                 setHTML("dayOldScoreMilestones", scoreMilestones(ASC_OLD_DAY_SCORES));
  422.                 setHTML("dayNewScoreMilestones", scoreMilestones(ASC_NEW_DAY_SCORES));
  423.                 setHTML("days", DAYS.map(recordedDay).join("<hr>"));
  424.             };
  425.             //
  426.         </script>
  427.     </head>
  428.     <body>
  429.         <div id="summary"></div>
  430.         <br>
  431.         Min day clear games: <span id="minDayClear"></span>
  432.         Max day clear games: <span id="maxDayClear"></span>
  433.         <br>
  434.         Min day split only non-clear games: <span id="minDaySplitNonClear"></span>
  435.         Max day split only non-clear games: <span id="maxDaySplitNonClear"></span>
  436.         <br>
  437.         Min old day avg score: <span id="minOldDay"></span>
  438.         Max old day avg score: <span id="maxOldDay"></span>
  439.         <br>
  440.         Min new day avg score: <span id="minNewDay"></span>
  441.         Max new day avg score: <span id="maxNewDay"></span>
  442.         <br>
  443.         Old score milestones:
  444.         <br>
  445.         <div id="dayOldScoreMilestones"></div>
  446.         New score milestones:
  447.         <br>
  448.         <div id="dayNewScoreMilestones"></div>
  449.         <br>
  450.         <hr>
  451.         <br>
  452.         <div id="days"></div>
  453.     </body>
  454. </html>
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top