init
This commit is contained in:
222
index.js
Normal file
222
index.js
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* http://www.fryes4fun.com/Bowling/scoring.htm
|
||||
*
|
||||
* high level overview:
|
||||
* one player per traditional round
|
||||
* each frame is mostly the same
|
||||
* 10 frame total
|
||||
* final frame may have 2-3 throws
|
||||
*
|
||||
* score for turn can be dependent on the next two throws
|
||||
* (i.e. strike or spare)
|
||||
*/
|
||||
|
||||
class Frame {
|
||||
// an open frame is any frame in which all ten pins are not knocked down
|
||||
open
|
||||
strike
|
||||
spare
|
||||
pinsKnockedDown
|
||||
throws
|
||||
|
||||
constructor(open, strike, spare, pinsKnockedDown, throws) {
|
||||
this.open = open || false;
|
||||
this.strike = strike || false;
|
||||
this.spare = spare || false;
|
||||
this.pinsKnockedDown = pinsKnockedDown || 0;
|
||||
this.throws = throws || [0, 0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
const STRIKE = 10;
|
||||
|
||||
class Bowling {
|
||||
/** for keeping track of score state, what kinds of throws happened throughout game, etc. */
|
||||
scorecard
|
||||
currentThrow
|
||||
currentFrame
|
||||
/** for overall score representation, i.e. the cumulative results from data tracked on the scorecard */
|
||||
score
|
||||
|
||||
constructor(score = 0, currentFrame = 0, currentThrow = 1) {
|
||||
this.score = score;
|
||||
this.currentFrame = currentFrame;
|
||||
this.currentThrow = currentThrow;
|
||||
|
||||
this.scorecard = []
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.scorecard.push(new Frame());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** semantic helpers for applying conditional logic based on proximity to endgame */
|
||||
isFinalFrame() {
|
||||
return this.currentFrame === 9;
|
||||
}
|
||||
|
||||
isNotFinalFrame() {
|
||||
return this.currentFrame !== 9;
|
||||
}
|
||||
|
||||
/** getters and setters for managing Frame class */
|
||||
setThrowResult(result) {
|
||||
this.scorecard[this.currentFrame].throws[this.currentThrow - 1] = result;
|
||||
}
|
||||
|
||||
setPinsKnockedDown(pinsKnockedDown) {
|
||||
this.scorecard[this.currentFrame].pinsKnockedDown = pinsKnockedDown;
|
||||
}
|
||||
|
||||
getPinsKnockedDown() {
|
||||
return this.scorecard[this.currentFrame].pinsKnockedDown;
|
||||
}
|
||||
|
||||
/**
|
||||
* attempt to knock down pins until the rules dictate that we can't any more
|
||||
* at this point, we break from the function
|
||||
*
|
||||
* @returns true when the frame is over, false when the frame is not over
|
||||
*/
|
||||
throwTheBall() {
|
||||
const remainingPins = 10 - this.scorecard[this.currentFrame].pinsKnockedDown;
|
||||
|
||||
// we still base our result on 10 total possible, and use the maximum possible value in the case of
|
||||
// "knocking down pins that aren't there"
|
||||
// otherwise our RNG is too strict and it's highly improbable that we ever throw strikes or spares
|
||||
const throwResult = Math.min(Math.floor(Math.random() * 11), remainingPins);
|
||||
|
||||
// record the throw result in the corresponding throw record in the scorecard
|
||||
this.setThrowResult(throwResult);
|
||||
|
||||
if (throwResult === STRIKE) {
|
||||
this.scorecard[this.currentFrame].strike = true;
|
||||
this.setPinsKnockedDown(STRIKE);
|
||||
|
||||
// unless it is the final turn of the game, rolling a strike will end this turn
|
||||
if (this.isNotFinalFrame()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// in the final turn of the game, rolling a strike on the first turn awards you two additional throws
|
||||
if (this.currentThrow < 3) {
|
||||
this.currentThrow += 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
/** spare case (not the first throw AND all the pins are knocked down) */
|
||||
} else if (throwResult === remainingPins) {
|
||||
this.scorecard[this.currentFrame].spare = true;
|
||||
this.setPinsKnockedDown(STRIKE);
|
||||
|
||||
if (this.isNotFinalFrame()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// in the final turn of the game, rolling a strike on the first turn awards you two additional throws
|
||||
if (this.currentThrow < 3) {
|
||||
this.currentThrow += 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
/** add the number of pins knocked down to your total pins knocked down for this roll */
|
||||
this.setPinsKnockedDown(this.getPinsKnockedDown() + throwResult);
|
||||
let frameIsOver = false;
|
||||
|
||||
if (this.isNotFinalFrame()) {
|
||||
frameIsOver = this.currentThrow >= 2;
|
||||
|
||||
if (frameIsOver) return true;
|
||||
|
||||
this.currentThrow += 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
frameIsOver = this.currentThrow >= 3;
|
||||
|
||||
if (frameIsOver) return true;
|
||||
|
||||
this.currentThrow += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** throw the ball until the criteria for completing a frame have been met */
|
||||
completeFrame() {
|
||||
let frameComplete = this.throwTheBall();
|
||||
|
||||
while (!frameComplete) {
|
||||
frameComplete = this.throwTheBall();
|
||||
}
|
||||
|
||||
if (this.getPinsKnockedDown() !== STRIKE) {
|
||||
this.scorecard[this.currentFrame].open = true;
|
||||
}
|
||||
|
||||
this.currentFrame += 1;
|
||||
this.currentThrow = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* strikes and spares have a scoring function that is relative to the throws that happen after the
|
||||
* strike or spare. from the above link:
|
||||
*
|
||||
* - A strike earns 10 points plus the sum of your next two shots
|
||||
* - A spare earns 10 points plus the sum of your next one shot
|
||||
*/
|
||||
calculateScore() {
|
||||
let i = 0;
|
||||
console.log("SCORING:")
|
||||
|
||||
while (i < 9) {
|
||||
let scoringExplanation = `Frame ${i + 1}: ${this.scorecard[i].pinsKnockedDown}`;
|
||||
if (i === 0) {
|
||||
this.score += this.scorecard[i].pinsKnockedDown;
|
||||
} else if (this.scorecard[i - 1].strike) {
|
||||
// add the strike value with the sum of the points for the next two throws
|
||||
scoringExplanation += ` PLUS strike on frame ${i} results in additional ${this.scorecard[i].throws[0]} + ${this.scorecard[i].throws[1]} from next two throws`
|
||||
this.score += this.scorecard[i].pinsKnockedDown + this.scorecard[i].throws[0] + this.scorecard[i].throws[1];
|
||||
} else if (this.scorecard[i - 1].spare) {
|
||||
// add the value of the first throw to your score for this frame
|
||||
scoringExplanation += ` PLUS spare on frame ${i} results in additional ${this.scorecard[i].throws[0]} from next throw`
|
||||
this.score += this.scorecard[i].pinsKnockedDown + this.scorecard[i].throws[0];
|
||||
} else {
|
||||
this.score += this.scorecard[i].pinsKnockedDown;
|
||||
}
|
||||
|
||||
console.log(scoringExplanation);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** "main" method that executes the component methods in the correct order */
|
||||
play() {
|
||||
console.log("\n")
|
||||
console.log("COMPLETELY ACCURATE BOWLING SIMULATOR")
|
||||
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
||||
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
||||
console.log("\n")
|
||||
|
||||
while (true) {
|
||||
if (this.currentFrame === 10) break;
|
||||
this.completeFrame();
|
||||
}
|
||||
|
||||
this.calculateScore();
|
||||
|
||||
// logging for scoring details
|
||||
console.log(`\nYour score was: ${this.score}`);
|
||||
console.log("Scorecard:\n");
|
||||
|
||||
this.scorecard.forEach(frame => {
|
||||
console.log(`${frame.throws} ${frame.strike ? "(strike)" : frame.spare ? "(spare)" : ""}`);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
new Bowling().play();
|
||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "bowling-in-javascript",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
Reference in New Issue
Block a user