Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4fcf686
Initial commit
snptrs Jun 16, 2023
9a43977
Adds Frame.addBallScore and .getBallScore
snptrs Jun 16, 2023
1dfabd3
Adds .frameScore, .totalFrameScore and .updateBonusScore
snptrs Jun 16, 2023
84cab3f
Adds Frame.setStrike and .getStrike
snptrs Jun 16, 2023
da2721a
Adds Frame.setSpare and .getSpare
snptrs Jun 16, 2023
f328e97
Adds .getBonusScore and .checkCompleteFrame
snptrs Jun 16, 2023
c182251
Adds Scorecard.updatePendingBonuses and .updatePendingStrikes
snptrs Jun 16, 2023
40d3348
Updates Frame.totalFrameScore
snptrs Jun 16, 2023
5574066
Updates Frame.setStrike to take a ball number as input
snptrs Jun 17, 2023
8044f89
Adds ScoreCard.updatePendingSpares, and tests for strikes
snptrs Jun 17, 2023
166640b
Adds Gameplay class and tests
snptrs Jun 17, 2023
e5eef43
Adds Express
snptrs Jun 17, 2023
ff9395b
Creates app.js
snptrs Jun 17, 2023
527c245
Sets up Handlebars for templating
snptrs Jun 17, 2023
5b61d6d
Updates style.css
snptrs Jun 17, 2023
9a4f4bf
Adds Scorecard.getBallScores
snptrs Jun 17, 2023
d752afd
Adds Frame.getRemainingPins
snptrs Jun 17, 2023
f4e4f9f
Adds routes
snptrs Jun 17, 2023
fe37009
Adds node modules for form parsing
snptrs Jun 17, 2023
39ad55d
Updates routes
snptrs Jun 17, 2023
b1f6c8f
Adds scoreboard.hbs template
snptrs Jun 17, 2023
1e5e980
Updates package.json
snptrs Jun 17, 2023
5761e0b
Updates node version in package.json
snptrs Jun 17, 2023
f7c8f08
Updates node version in package.json
snptrs Jun 17, 2023
3c3b0f9
Merge remote-tracking branch 'refs/remotes/origin/main'
snptrs Jun 17, 2023
5f0f31b
Style tweaks
snptrs Jun 17, 2023
8e32235
Updates input processing
snptrs Jun 17, 2023
f912b10
Adds reset link
snptrs Jun 17, 2023
f87dd9a
Adds total score
snptrs Jun 17, 2023
dd2c81e
Updates formatting for displaying scores
snptrs Jun 18, 2023
21d830b
Updates tests to include frame number when creating frames
snptrs Jun 18, 2023
1704547
Adds 'let' to for loops
snptrs Jun 18, 2023
b9721b9
Updates template to display frame 10, ball 3
snptrs Jun 18, 2023
b76f07e
Changes Frame.checkCompleteFrame to .checkTwoBallsPlayed
snptrs Jun 18, 2023
0a6cd76
Adjusts scorecard display
snptrs Jun 18, 2023
1157869
Updates .formatScoreForDisplay to correctly display zero
snptrs Jun 18, 2023
5894f29
Updates Scorecard to handle spares and strikes better
snptrs Jun 18, 2023
f94e7d5
Updates Frame.checkTwoBallsPlayed
snptrs Jun 18, 2023
3431415
Fixes frame 10 showing incorrect number of remaining pins
snptrs Jun 18, 2023
15fe6f4
Fixes strikes being incorrectly processed in frame 10
snptrs Jun 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const Frame = require("./frame");
const Gameplay = require("./gameplay");
const ScoreCard = require("./scorecard");
const handlebars = require("express-handlebars");
const bodyParser = require("body-parser");
const multer = require("multer");
const upload = multer();

const express = require("express");
const app = express();
const port = 3000;
app.use(express.static("public"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "hbs");
app.engine(
"hbs",
handlebars.engine({
layoutsDir: __dirname + "/views/layouts",
extname: "hbs",
})
);

const frames = {};
const game = new Gameplay();
const scoreCard = new ScoreCard();

const createFrames = () => {
for (let i = 1; i < 11; i++) {
const frame = new Frame(i);
frames[i] = frame;
}
};

createFrames();

app.get("/", (req, res) => {
const buttons = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "X"];
const remainingPins = frames[game.currentFrame].getRemainingPins() + 1;
const buttonsToShow = buttons.slice(0, remainingPins);

res.render("scorecard", {
layout: "index",
scores: scoreCard.getGameScores(frames),
checkContinue: game.checkContinue(frames),
buttonsToShow: buttonsToShow,
totalScore: game.getFinalScore(frames),
});
});

app.post("/score", (req, res) => {
let input = req.body.score;
let currentFrame = game.currentFrame;
game.processCurrentBall(frames[currentFrame], input);
scoreCard.updatePendingBonuses(frames, currentFrame);
if (game.checkContinue(frames)) {
game.setNextBall(frames);
}

res.redirect("/");
});

app.get("/reset", (req, res) => {
createFrames();
game.reset();
res.redirect("/");
});

console.log(`Server listening on localhost:${port}`);
app.listen(port);
85 changes: 85 additions & 0 deletions frame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
class Frame {
constructor(frameNumber) {
this.frameNumber = frameNumber;
this.lastBallPlayed = 0;
this.ballScores = [0, 0, 0];
this.bonusScore = 0;
this.spare = false;
this.strike = false;
}

checkTwoBallsPlayed() {
if (this.lastBallPlayed === 2) {
return true;
} else {
return false;
}
}

frameScore() {
return this.ballScores.reduce(
(accumulator, currentValue) => Number(accumulator) + Number(currentValue)
);
}

getBallScore(ball) {
return this.ballScores[ball - 1];
}

getBonusScore() {
return this.bonusScore;
}

getRemainingPins() {
if (this.frameNumber < 10) {
return 10 - this.frameScore();
} else {
if (this.lastBallPlayed === 1 && this.getBallScore(1) === 10) {
return 10;
} else if (
this.lastBallPlayed === 2 &&
(this.frameScore() === 10 || this.getBallScore(2) === 10)
) {
return 10;
} else {
return 10 - this.frameScore();
}
}
}

getSpare() {
return this.spare;
}

getStrike() {
return this.strike;
}

setBallScore(ball, score) {
this.ballScores[ball - 1] = score;
this.lastBallPlayed = ball;
}

setBonusScore(bonus) {
this.bonusScore = bonus;
}

setSpare() {
this.spare = true;
const score = 10 - this.getBallScore(1);
this.setBallScore(2, score);
}

setStrike(ball) {
this.strike = true;
this.setBallScore(ball, 10);
}

totalFrameScore() {
const frameScore = this.frameScore();
const bonusScore = this.getBonusScore();
return Number(frameScore + bonusScore);
}
}

module.exports = Frame;
76 changes: 76 additions & 0 deletions frame.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const Frame = require("./frame");

describe("Frame", () => {
test(".setBallScore adds a score to the frame", () => {
frame = new Frame(1);
frame.setBallScore(1, 5);
expect(frame.getBallScore(1)).toBe(5);
});

test(".frameScore returns the score for the frame, excluding bonuses", () => {
frame = new Frame(1);
frame.setBallScore(1, 5);
frame.setBallScore(2, 3);
expect(frame.frameScore()).toBe(8);
});

test(".totalFrameScore returns the score for the frame, including bonuses", () => {
frame = new Frame(1);
frame.setBallScore(1, 5);
frame.setBallScore(2, 3);
frame.setBonusScore(10);
expect(frame.totalFrameScore()).toBe(18);
});

test(".setStrike sets ball 1 score to 10", () => {
frame = new Frame(1);
frame.setStrike(1);
expect(frame.getBallScore(1)).toBe(10);
});

test(".getStrike returns true if the frame has a strike", () => {
frame = new Frame(1);
frame.setStrike(1);
expect(frame.getStrike()).toBe(true);
});

test(".setBonusScore and .getBonusScore set and return a frame bonus", () => {
frame = new Frame(1);
frame.setBonusScore(15);
expect(frame.getBonusScore()).toBe(15);
});

test(".setSpare sets ball 2 score", () => {
frame = new Frame(1);
frame.setBallScore(1, 4);
frame.setSpare();
expect(frame.getBallScore(2)).toBe(6);
});

test(".getSpare returns true if the frame has a spare", () => {
frame = new Frame(1);
frame.setSpare();
expect(frame.getSpare()).toBe(true);
});

test(".checkTwoBallsPlayed returns true if two balls bowled", () => {
frame = new Frame(1);
frame.setBallScore(1, 5);
expect(frame.checkTwoBallsPlayed()).toBe(false);
frame.setBallScore(2, 3);
expect(frame.checkTwoBallsPlayed()).toBe(true);
});

test(".getRemaining pins returns the number of pins left in play", () => {
frame = new Frame(1);
frame.setBallScore(1, 4);
expect(frame.getRemainingPins()).toBe(6);
});

test(".lastBallPlayed increments properly", () => {
frame = new Frame(1);
expect(frame.lastBallPlayed).toBe(0);
frame.setBallScore(1, 4);
expect(frame.lastBallPlayed).toBe(1);
});
});
66 changes: 66 additions & 0 deletions gameplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const ScoreCard = require("./scorecard");

class Gameplay {
constructor() {
this.currentFrame = 1;
this.currentBall = 1;
this.scorecard = new ScoreCard();
}

checkContinue(frames) {
const frame = frames[this.currentFrame];
if (this.currentFrame === 10 && this.currentBall == 4) {
return false;
} else if (
this.currentFrame === 10 &&
frame.checkTwoBallsPlayed() &&
frame.getSpare() === false &&
frame.getStrike() === false
) {
return false;
} else {
return true;
}
}

getFinalScore(frames) {
let score = 0;
Object.values(frames).forEach((frame) => {
score = score + frame.totalFrameScore();
});
return score;
}

processCurrentBall(frame, input) {
if (frame.getBallScore(1) + Number(input) === 10) {
frame.setSpare();
} else if (input === "X") {
frame.setStrike(this.currentBall);
} else {
frame.setBallScore(this.currentBall, Number(input));
}
}

setNextBall(frames) {
if (this.currentFrame < 10) {
if (
this.currentBall === 1 &&
frames[this.currentFrame].getStrike() == false
) {
this.currentBall = 2;
} else {
this.currentFrame++;
this.currentBall = 1;
}
} else {
this.currentBall++;
}
}

reset() {
this.currentFrame = 1;
this.currentBall = 1;
}
}

module.exports = Gameplay;
80 changes: 80 additions & 0 deletions gameplay.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const Gameplay = require("./gameplay");
const Frame = require("./frame");

describe("Gameplay", () => {
beforeEach(() => {
game = new Gameplay();
});

describe(".continue", () => {
it("returns true if the game should continue", () => {
const frame1 = new Frame(1);
const frames = { 1: frame1 };

expect(game.checkContinue(frames)).toBe(true);
});
});

describe(".incrementBall", () => {
it("increments the current ball and frame", () => {
const frame1 = new Frame(1);
frame1.setBallScore(1, 3);
frame1.setBallScore(2, 4);

const frame2 = new Frame(2);
frame2.setStrike(1);

const frame3 = new Frame(3);
frame3.setBallScore(1, 2);

const frames = { 1: frame1, 2: frame2, 3: frame3 };

expect(game.currentFrame).toBe(1);
expect(game.currentBall).toBe(1);

game.setNextBall(frames);
expect(game.currentFrame).toBe(1);
expect(game.currentBall).toBe(2);

game.setNextBall(frames);
expect(game.currentFrame).toBe(2);
expect(game.currentBall).toBe(1);

game.setNextBall(frames);
expect(game.currentFrame).toBe(3);
expect(game.currentBall).toBe(1);
});
});

describe(".processCurrentBall", () => {
it("processes input and modifies the current ball", () => {
const frame1 = new Frame(1);
game.processCurrentBall(frame1, "X");
expect(frame1.getStrike()).toBe(true);
expect(frame1.frameScore()).toBe(10);

const frame2 = new Frame(2);
game.processCurrentBall(frame2, 4);
expect(frame2.getBallScore(1)).toBe(4);
game.processCurrentBall(frame2, 6);
expect(frame2.frameScore()).toBe(10);
});
});

describe(".getFinalScore", () => {
it("returns the final score for the game", () => {
const frame1 = new Frame(1);
frame1.setStrike(1);

const frame2 = new Frame(2);
frame2.setStrike(1);

const frame3 = new Frame(3);
frame3.setBallScore(1, 5);

const frames = { 1: frame1, 2: frame2, 3: frame3 };

expect(game.getFinalScore(frames)).toBe(25);
});
});
});
Loading