The Chance Bowling Game was originally conceptualized and developed by Kyle Linette in January of 2019.
The following documentation is his recount of the development process with code explanation.
I was over 3 months into learning JavaScript and was feeling completely hopeless. If there was a test on paper about writing out and describing JavaScript concepts, I would ace it. Yet, stick me in front of a computer and ask me to write a function that would do “so-and-so”, and my mind would go completely blank.
I watched countless tutorials on making various beginner projects. I could read and understand the finished code, but again, when tasked to writing my own code, my mind became a barren wasteland of tumbleweeds. Knowing a bunch of theory was worthless if I couldn’t do anything with that knowledge.
Until one day,,,
There was a point where I picked up a JavaScript only online course to focus on this shortcoming. I had this creative itch to make up my own original game and code it on my own from scratch. It was my goal at some point that I would be able to achieve this feat before I finished that course.
Early on in the course at the end of the ‘functions’ section, there was a practice quiz to write a function that kept track of a bowling score. I was determined to get this.
I spend 2 whole weeks on this problem. I was able to figure out normal scoring, and what to do if there was a strike, but for the life of me I could not figure out scoring a spare.
After 2 weeks of serious thought, I gave in and watched the solution video.
This was the moment that changed my JavaScript future forever. The approach in thinking the instructor used to solve the problem was completely different from the way I approached it. I’m not sure why exactly, but it was like a wall in my brain was knocked down.
“Ooohhh, so I think it through that way.”
You see, I’m someone who comes from an art background. My brain knows imagination, creativity, and visualization. Finding the most complex and convoluted path is how I naturally solve problems. The type of thinking the instructor just walked me through wasn’t anything unheard of to me per se. Watching a 6 minute tutorial on how to think that way though was just the thing I needed to get out of being stuck in the spot I was at with JavaScript.
It was time to make my game and for obvious reasons, I was inspired by the game of bowling.
Yes, the game visually is bare-bones basic with elementary flashing and scaling effects. Nothing to look at , but getting the game to work was my one and only priority. Especially considering I thought the chances of actually ever getting the game to fully function were slim to none.
Writing the pseudocode was an extremely important step. So many ways a basic game of bowling could go, I mean who knew? I’m not going to walk you through the countless back and forth frustrations with making this game. As I went along, there were many scenarios that I didn’t account for from the get go. I was constantly reconfiguring the code.
I know the code needs refactoring and I’m sure anyone could rewrite this game in a third of the lines and make it 50 times more awesome too, but I’m proud of what I accomplished. For me at the 4 month into learning JavaScript marker, this was a challenge that I found to be extremely rewarding.
Time to start with declaring my variables. I had 13 elements I needed to define from the HTML for DOM manipulation.
Then we have the pins array. I specifically chose to define my own array instead of a Math.random scenario so there would be a higher chance of getting spares and strikes (i.e. overall higher scores). Constantly rolling 2s and 3s wouldn’t be any fun now would it?
As far as tracking variables were concerned, I needed to keep track of what frame we were in and the overall total score. I needed to remember the previous frames 1st and 2nd throw scores and lastly, a variable I dubbed “strikeState”. This variable’s sole purpose was to track if 3 strikes in a row happened so the score could be tripled. This way, I only had to keep track of one previous frame's scores.
const startButton = document.getElementById("startButton");
const start = document.getElementById("start");
const resetButton = document.getElementById("resetButton");
const frameNumber = document.getElementById("frameNumber");
const totalScore = document.getElementById("totalScore");
const frame1 = document.getElementById("frame1");
const frame2 = document.getElementById("frame2");
const pins1st = document.getElementById("pins1st");
const pins2nd = document.getElementById("pins2nd");
const nextThrow = document.getElementById("nextThrow");
const nextFrame = document.getElementById("nextFrame");
const choice1st = document.getElementsByClassName("choice1st");
const choice2nd = document.getElementsByClassName("choice2nd");
const pinsArray = [10, 7, 5, 9, 6, 4, 10, 0, 8, 7, 3, 9, 6, 2, 10, 8, 4, 8, 1, 7, 9, 5, 3, 10, 6, 9];
let frameCount = 1;
let firstThrow = 0;
let secondThrow = 0;
let score = 0;
let strikeState = 0;
Here lies that classic function for random number generating fun. Not much more to say here, besides the obvious that this function is the backbone of the whole entire game.
function newPins() {
  let randomPinsNumber = Math.floor(Math.random() * (pinsArray.length));
  return pinsArray[randomPinsNumber];
}
Up next are all of my recurring functions because, well, they’re recurring.
Recurring recurring!
Unfortunately, I did have to double up on my 'pointer' and 'grayscale' functions due to there being areas of the game where I only needed to toggle one and not the other. I also found that I had to write two 'flash' functions because I couldn’t get the bowling balls to repeatedly flash after the first frame. The two 'next…' buttons worked just fine with the original flash function, but I had to make some developer magic happen to get those balls to flash over the course of the entire game.
Recurring recurring!
function stylesToggle(element) {
  element.classList.toggle("pointer");
  element.classList.toggle("grayscale");
}
function pointerToggle(element) {
  element.classList.toggle("pointer");
}
function grayscaleToggle(element) {
  element.classList.toggle("grayscale");
}
function flash(element) {
  element.classList.add("playing");
  setTimeout (function () {
    element.classList.remove("playing");
  }, 750);
}
function flashChoice(element) {
  element.classList.add("flash");
  element.classList.add("playing");
  setTimeout (function () {
    element.classList.remove("playing");
    element.classList.remove("flash");
  }, 750);
}
function textChange(element, text) {
  element.textContent = text;
}
Now comes the point where, from a thinking perspective, I found it best to make a function (that included DOM manipulation) for each 'area' of the game. By 'area' I mean:
- Start Button
- Reset Button
- 1st Throw Ball Selection Area
- Next Throw Button
- 2nd Throw Ball Selection Area
- Next Frame Button
Hint-hint kids, no matter which ball you pick, you will get the same score!
My line of thinking also reasoned it was best to give the 1st and 2nd extra throws at the end of the game their own functions. For those who don’t know score keeping in bowling - these “extra throws” are awarded if you get a strike or spare in the tenth (and final) frame of the game.
So a grand total of 8 functions it is to get this game working properly.
Okay, I know I told myself I wouldn’t mention the struggles associated with writing the code for this game, but I couldn’t help myself.
Back when I was writing this code, there was a whole lotta fiddling going on. How I managed to write the code that came after at that time is a miracle for sure, but…
Okay, times two. It's just best to be prepared and brace yourselves for the looping action that is coming my friends.
startButton.addEventListener("click", function pageLoad() {
  grayscaleToggle(frame1);
  textChange(frameNumber, "1");
  stylesToggle(startButton);
  stylesToggle(resetButton);
  for (let i = 0; i < 3; i++) {
    flashChoice(choice1st[i]);
    pointerToggle(choice1st[i]);
    choice1st[i].addEventListener("click", rollOne);
  }
  startButton.removeEventListener("click", pageLoad);
});
resetButton.addEventListener("click", function() {
  window.location.reload();
});
Wow, what a lengthy function we have here.
I’m accounting for three strikes in a row scoring, two strikes in a row scoring, just normal scoring, making a big complicated mess out of that whole 'strikeState' variable (I swear, it was the only way I could get it to work without breaking a bunch of other things!), and who could forget the partridge in a pear tree?
function rollOne() {
  nextThrow.removeEventListener("click", nextThrowButton);
  let currentThrow = newPins();
  textChange(pins1st, currentThrow);
  if (firstThrow === 10 && currentThrow === 10 && strikeState >= 1) {
    strikeState++;
  }
  if (firstThrow === 10 && strikeState >= 3 && frameCount <= 10) {
    let currentTriple = currentThrow;
    currentTriple *= 3;
    score += currentTriple;
  } else if ((firstThrow === 10 && frameCount <= 10) || ((firstThrow + secondThrow) === 10 && frameCount <= 10)) {
    let currentDouble = currentThrow;
    currentDouble *= 2;
    score += currentDouble;
  } else if (frameCount <= 10) {
    score += currentThrow;
  }
  if (currentThrow === 10) {
    strikeState++;
  } else if (currentThrow !== 10) {
    strikeState = 0;
  }
  textChange(totalScore, score);
  for (let i = 0; i < 3; i++) {
    stylesToggle(choice1st[i]);
    choice1st[i].removeEventListener("click", rollOne);
  }
  flash(nextThrow);
  pointerToggle(nextThrow);
  nextThrow.addEventListener("click", nextThrowButton);
  firstThrow = currentThrow;
  return {score, firstThrow, strikeState};
}
If you thought the last function was long, you’re probably going to need an extra pair of underwear before you read any further. Just a friendly warning here.
So many paths. So-so many.
I’m pretty sure this is a case of imagination gone wild, but hey, it functions doesn’t it? Quick summary of the functionality going on here, not counting style toggles/text changes, but they’re in order:
- I’m taking you to the “extra 2nd throw” function because you just rolled your 1st extra throw (say whhhaaaattt????).
- The game has ended after your first extra throw (meaning you threw a spare in the 10th frame).
- You threw a strike and it was the 9th frame or less logic (to the 10th frame and beyond!).
- Or it was the 10th frame and you threw a strike (and off to the 1st extra throw you go!).
- And finally you rolled a 9 or less (wow, you stink at bowling…just kidding).
I know, that was a lot to take in, especially if you’re in the process of learning JavaScript. I’m not sure of the advice I could give? Just really break it down to the smallest piece you can manage and after you get those tiny pieces working, put them together to form a hilariously big giant function such as this??? I mean what a hot mess we have here.
function nextThrowButton() {
  pointerToggle(nextThrow);
  if (frameCount === 11 || frameCount === 12) {
    grayscaleToggle(frame1);
    for (let i = 0; i < 3; i++) {
      grayscaleToggle(choice1st[i]);
    }
    if (frameCount === 11) {
      grayscaleToggle(frame2);
      for (let i = 0; i < 3; i++) {
        flashChoice(choice2nd[i]);
        pointerToggle(choice2nd[i]);
        choice2nd[i].addEventListener("click", xtra2ndThrow);
      }
    } else if (frameCount === 12) {
      alert ("You bowled a " + score + "! Click 'RESET GAME' button to play again.");
      return;
    }
  }
  if (firstThrow === 10 && frameCount <= 10) {
    for (let i = 0; i < 3; i++) {
      flashChoice(choice1st[i]);
      stylesToggle(choice1st[i]);
    }
    textChange(pins1st, "0");
    if (frameCount <= 9) {
      frameCount++;
      textChange(frameNumber, frameCount);
      for (let i = 0; i < 3; i++) {
        choice1st[i].addEventListener("click", rollOne);
      }
      nextThrow.removeEventListener("click", nextThrowButton);
      secondThrow = 10;
      return secondThrow;
    } else {
      frameCount++;
      textChange(frameNumber, "x2");
      for (let i = 0; i < 3; i++) {
        choice1st[i].addEventListener("click", xtra1stThrow);
      }
      nextThrow.removeEventListener("click", nextThrowButton);
    }
  }
  if (firstThrow <= 9 && frameCount <= 10) {
    grayscaleToggle(frame1);
    grayscaleToggle(frame2);
    for (let i = 0; i < 3; i++) {
      grayscaleToggle(choice1st[i]);
    }
    for (let i = 0; i < 3; i++) {
      flashChoice(choice2nd[i]);
      pointerToggle(choice2nd[i]);
      choice2nd[i].addEventListener("click", rollTwo);
    }
    nextThrow.removeEventListener("click", nextThrowButton);
    return;
  }
}
Finally, a little decency. Now that we have exercised the demons, we proceed with a function that is way more digestible.
To start, I am compensating for the random number between 0 and 10 that's coming from the 'newPins' function. It’s not possible in a real game of bowling to knock down more than ten pins over 2 throws per frame, so I’m taking that into account and checking it twice and if I’m still getting 11 or more, sorry, but it’s a zero for you.
Hey, I’m just crossing my t’s and dotting my … gulp … lowercase j’s …
Then we’re checking if a strike was thrown the last frame to double the score or if we’re cool, it’s just normal scoring.
function rollTwo() {
  currentThrow = newPins();
  if (firstThrow + currentThrow >= 11) {
    currentThrow = firstThrow % currentThrow;
    if (firstThrow + currentThrow >= 11) {
      currentThrow = 0;
    }
  }
  if (secondThrow === 10) {
    let currentDouble = currentThrow;
    currentDouble *= 2;
    score += currentDouble;
    textChange(totalScore, score);
  } else {
    score += currentThrow;
    textChange(totalScore, score);
  }
  for (let i = 0; i < 3; i++) {
    stylesToggle(choice2nd[i]);
    choice2nd[i].removeEventListener("click", rollTwo);
  }
  textChange(pins2nd, currentThrow);
  flash(nextFrame);
  pointerToggle(nextFrame);
  nextFrame.addEventListener("click", nextFrameButton);
  secondThrow = currentThrow;
  return {score, secondThrow};
}
We’re almost there.
The end I mean. So, the rundown is as follows:
- We start with throwing a spare in the 10th frame and getting send to my 1st extra throw function.
– The game ends.
– Or it was the 9th frame or less and game on!
Game on!!!
function nextFrameButton() {
  for (let i = 0; i < 3; i++) {
    grayscaleToggle(choice2nd[i]);
  }
  textChange(pins1st, "0");
  textChange(pins2nd, "0");
  grayscaleToggle(frame2);
  pointerToggle(nextFrame);
  frameCount++;
  if (frameCount === 11 && (firstThrow + secondThrow) === 10) {
    textChange(frameNumber, "x1");
    grayscaleToggle(frame1);
    frameCount++;
    for (let i = 0; i < 3; i++) {
      flashChoice(choice1st[i]);
      pointerToggle(choice1st[i]);
      choice1st[i].addEventListener("click", xtra1stThrow);
    }
    nextFrame.removeEventListener("click", nextFrameButton);
  } else if (frameCount === 11 || frameCount === 12) {
    alert ("You bowled a " + score + "! Click 'RESET GAME' button to play again.");
    nextFrame.removeEventListener("click", nextFrameButton);
  } else {
    grayscaleToggle(frame1);
    textChange(frameNumber, frameCount);
    for (let i = 0; i < 3; i++) {
      flashChoice(choice1st[i]);
      pointerToggle(choice1st[i]);
      choice1st[i].addEventListener("click", rollOne);
    }
    nextFrame.removeEventListener("click", nextFrameButton);
  }
}
Not much left to say here. I’ve been talking about these mystical functions up for how long. Here they both are in all of their glory.
function xtra1stThrow() {
  nextThrow.removeEventListener("click", nextThrowButton);
  let currentThrow = newPins();
  score += currentThrow;
  if (firstThrow === 10 && currentThrow === 10) {
    score += 10;
  }
  firstThrow = currentThrow;
  textChange(pins1st, currentThrow);
  textChange(totalScore, score);
  for (let i = 0; i < 3; i++) {
    stylesToggle(choice1st[i]);
    choice1st[i].removeEventListener("click", xtra1stThrow);
  }
  flash(nextThrow);
  nextThrow.addEventListener("click", nextThrowButton);
  pointerToggle(nextThrow);
  return {score, firstThrow};
}
function xtra2ndThrow() {
  nextThrow.removeEventListener("click", nextThrowButton);
  let currentThrow = newPins();
  if (firstThrow <= 9) {
    if (firstThrow + currentThrow <= 11) {
      currentThrow = firstThrow % currentThrow;
      if (firstThrow + currentThrow >= 11) {
        currentThrow = 0;
      }
    }
  }
  textChange(pins2nd, currentThrow);
  score += currentThrow;
  textChange(totalScore, score);
  for (let i = 0; i < 3; i++) {
    stylesToggle(choice2nd[i]);
    choice2nd[i].removeEventListener("click", xtra2ndThrow);
  }
  flash(nextFrame);
  pointerToggle(nextFrame);
  nextFrame.addEventListener("click", nextFrameButton);
  return score;
}
That’s right, I just had to.
Honestly, I added this in a couple weeks before writing this. 4 months prior when I coded this game, it would have been way too much for me to fathom.
I made a small addition to the first few lines of the 'rollOne' function. It’s up to you to figure out what “ball1 ball2 ball3” means exactly, right?
let secretArray = [];
function cheatCode() {
  let secretChoice;
  for (let i = 0; i < 3; i++) {
    choice1st[i].addEventListener("click", function() {
      secretChoice = this.getAttribute("id");
      secretArray.push(secretChoice);
    });
  }
}
cheatCode();
function rollOne() {
  nextThrow.removeEventListener("click", nextThrowButton);
  let currentThrow = newPins();
  let last3clicked = secretArray.slice(-3)
  let clickCheck = last3clicked.join(" ");
  if (clickCheck === "ball1 ball2 ball3") {
    currentThrow = 10;
  }
--- rest of function ---