Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bowling challenge #1606

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
33 changes: 33 additions & 0 deletions frame.js
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Tom, I think your code is good, especially your tests. I believe you predicted a lot of things that could go wrong in the program in your tests, so you already threw exceptions for those cases.
I like that you've already created your frames at the beginning with 10, so you're sure you won't have a null number. That was pretty smart!

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Frame {
constructor() {
this.rolls = [];
this.regularPoints = 0;
this.bonusPoints = 0;
};

roll(points) {
if (this.rolls.length == 2 || this.rolls[0] == 10) {
throw new Error('Tried to add points to a frame that is already over');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this part where you are catching the error.

} else if ((this.regularPoints + points) > 10) {
throw new Error(`Tried to add roll that would exceed max score in a frame (${this.regularPoints} + ${points})`);
} else {
this.rolls.push(points);
this.regularPoints += points;
};
};
// is this function necessary? This logic could be in Game
playFrame(points_array) {
points_array.forEach((points) => {
this.roll(points);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to change this for the interface?


isStrike() {
return this.rolls[0] == 10;
}

isSpare() {
return this.regularPoints == 10 && this.rolls.length == 2;
}
}
module.exports = Frame;
91 changes: 91 additions & 0 deletions frame.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const Frame = require('./frame');

describe('Frame class unit test', () => {
describe('constructor', () => {
it('initliases with empty array this.rolls', () => {
frame = new Frame();
expect(frame.rolls).toEqual([]);
})

it('initliases with bonus points & regular points values of 0', () => {
frame = new Frame();
expect(frame.bonusPoints).toEqual(0);
expect(frame.regularPoints).toEqual(0);

})
});

describe('.roll', () => {
it('adds roll score to this.rolls', () => {
frame = new Frame();
frame.roll(5);
expect(frame.rolls[0]).toEqual(5);
expect(frame.rolls.length).toEqual(1);
frame.roll(5);
expect(frame.regularPoints).toEqual(10);
expect(frame.bonusPoints).toEqual(0);
});

it('refuses to add a roll if two have already been scored', () => {
expect(() => {
frame = new Frame();
frame.roll(5);
frame.roll(3);
frame.roll(6)
}).toThrow('Tried to add points to a frame that is already over');
});

it('refuses to add a roll if a strike was made on first roll=', () => {
frame = new Frame();
frame.roll(10);
expect(() => frame.roll(6)).toThrow('Tried to add points to a frame that is already over');
expect(frame.rolls).toEqual([10]);
});

it('returns error message if roll would exceed max score in a frame', () => {
frame = new Frame();
frame.roll(5);
expect(() => frame.roll(7)).toThrow('Tried to add roll that would exceed max score in a frame (5 + 7');
});
});

it('takes an array of points for .playFrame() and rolls for each one', () => {
frame = new Frame();
frame.playFrame([5, 3]);
expect(frame.regularPoints).toEqual(8)
});

describe('.isSpare()', () => {
it('returns true when frame is a spare', () => {
frame = new Frame();
frame.playFrame([5, 5]);
expect(frame.isSpare()).toEqual(true);
});

it('returns true when frame is a spare with a 10 on 2nd roll', () => {
frame = new Frame();
frame.playFrame([0, 10]);
expect(frame.isSpare()).toEqual(true);
});

it('returns false when frame is a strike', () => {
frame = new Frame();
frame.playFrame([10]);
expect(frame.isSpare()).toEqual(false);
});
});

describe('.isStrike()', () => {
it('returns true when frame is a strike', () => {
frame = new Frame();
frame.playFrame([10]);
expect(frame.isStrike()).toEqual(true);
});

it('returns false when frame is a spare', () => {
frame = new Frame();
frame.playFrame([2, 8]);
expect(frame.isStrike()).toEqual(false);
});
});
});
62 changes: 62 additions & 0 deletions game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
class Game {
constructor(frameClass) {
this.grandTotal = 0;
this.frames = [];
for(let i = 0; i < 10; i++) {
let frame = new frameClass();
this.frames.push(frame);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a method, maybe?

};

play(scoresArray) { // take a 2D array, 1 array for each frame. If strike, array length will be 1
this.frames[0].playFrame(scoresArray[0]);
// no bonus points to allocate after first frame
for (let i = 1; i < 10; i++) {
this.frames[i].playFrame(scoresArray[i]);
this.allocateBonusPoints(i);
}
if (this.frames[9].isStrike() || this.frames[9].isSpare()) {
// Bonus rolls after the final frame provided from UI as addl array element
this.playFinalFrameBonusRolls(scoresArray[10]);
}
this.calculateGrandTotal();
};

allocateBonusPoints(index) {
if (this.frames[index-1].isSpare()) {
this.frames[index-1].bonusPoints += this.frames[index].rolls[0];
} else if (this.frames[index-1].isStrike()) {
this.frames[index-1].bonusPoints += this.frames[index].regularPoints;
// if this is at least the 3rd frame & have been 2 consecutive strikes before this frame
if (index >= 2 && this.frames[index-2].isStrike()) {
this.frames[index-2].bonusPoints += this.frames[index].rolls[0]
};
};
};

playFinalFrameBonusRolls(bonusRollsArray) {
bonusRollsArray.forEach((points) => {
this.frames[9].bonusPoints += points;
});
// If player had a strike in frame 9 and 10, allocate the first bonus roll as bonus point to frame 9:
if (this.frames[9].isStrike() && this.frames[8].isStrike()) {
this.frames[8].bonusPoints += bonusRollsArray[0];
};
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice part


calculateGrandTotal() {
this.frames.forEach((frame) => {
this.grandTotal += (frame.regularPoints + frame.bonusPoints);
});
};

isGutterGame() {
return this.grandTotal == 0;
};

isPerfectGame() {
return this.grandTotal == 300;
};
};

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

jest.mock('./frame.js');

describe('Game class unit test', () => {
beforeEach(() => {
Frame.mockClear();

mockFrame = {
playFrame: jest.fn(),
isSpare: jest.fn(),
isStrike: jest.fn(),
rolls: jest.fn(),
regularPoints: jest.fn(),
bonusPoints: 0,
};

Frame.mockImplementation(() => mockFrame); // mock the constructor of Frame to return our mockFrame
});

describe('constructor', () => {
it('initializes with an array of 10 frames', () => {
let game = new Game(Frame);
expect(Frame).toHaveBeenCalledTimes(10);
expect(game.frames.length).toEqual(10);
});

it('initializes with a grand total score of 0', () => {
let game = new Game(Frame);
expect(game.grandTotal).toEqual(0);
});
});

describe('.play()', () => {
it('calls .playFrame on each of the 10 frames without spare or strike in last frame', () => {
let game = new Game(Frame);
const scores = [[1, 4], [4, 5], [6, 4], [5, 5], [10], [0, 1], [7, 3], [6, 4], [10], [2, 7]];
game.frames[3].rolls.mockImplementation(() => [5, 5]);
game.frames[4].rolls.mockImplementation(() => [10]);
game.frames[7].rolls.mockImplementation(() => [6, 4]);
game.frames[8].rolls.mockImplementation(() => [10]);
game.frames[9].rolls.mockImplementation(() => [2, 7]);
game.play(scores);
expect(mockFrame.playFrame).toHaveBeenCalledTimes(10);
expect(mockFrame.playFrame).toHaveBeenCalledWith(scores[0]);
expect(mockFrame.playFrame).toHaveBeenCalledWith(scores[1]);
expect(mockFrame.playFrame).toHaveBeenCalledWith(scores[9]);
});

it('Score a complex game correctly', () => {
let game = new Game(Frame);
const scores = [[1, 4], [4, 5], [6, 4], [5, 5], [10], [0, 1], [7, 3], [6, 4], [10], [2, 8], [6]];
game.frames[0].rolls.mockImplementation(() => [1, 4]);
game.frames[0].isSpare.mockImplementation(() => false);
game.frames[0].isStrike.mockImplementation(() => false);
game.frames[0].regularPoints.mockImplementation(() => 5);

game.frames[1].rolls.mockImplementation(() => [4, 5]);
game.frames[1].isSpare.mockImplementation(() => false);
game.frames[1].isStrike.mockImplementation(() => false);
game.frames[1].regularPoints.mockImplementation(() => 9);

game.frames[2].rolls.mockImplementation(() => [6, 4]);
game.frames[2].isSpare.mockImplementation(() => true);
game.frames[2].isStrike.mockImplementation(() => false);
game.frames[2].regularPoints.mockImplementation(() => 10);


game.frames[3].rolls.mockImplementation(() => [5, 5]);
game.frames[3].isSpare.mockImplementation(() => true);
game.frames[3].isStrike.mockImplementation(() => false);
game.frames[3].regularPoints.mockImplementation(() => 10);


game.frames[4].rolls.mockImplementation(() => [10]);
game.frames[4].isStrike.mockImplementation(() => true);
game.frames[4].isSpare.mockImplementation(() => false);
game.frames[4].regularPoints.mockImplementation(() => 10);


game.frames[5].rolls.mockImplementation(() => [0, 1]);
game.frames[5].isSpare.mockImplementation(() => false);
game.frames[5].isStrike.mockImplementation(() => false);
game.frames[5].regularPoints.mockImplementation(() => 10);

game.frames[6].rolls.mockImplementation(() => [7, 3]);
game.frames[6].isSpare.mockImplementation(() => true);
game.frames[6].isStrike.mockImplementation(() => false);
game.frames[6].regularPoints.mockImplementation(() => 10);


game.frames[7].rolls.mockImplementation(() => [6, 4]);
game.frames[7].isSpare.mockImplementation(() => true);
game.frames[7].isStrike.mockImplementation(() => false);
game.frames[7].regularPoints.mockImplementation(() => 10);


game.frames[8].rolls.mockImplementation(() => [10]);
game.frames[8].isStrike.mockImplementation(() => true);
game.frames[8].isSpare.mockImplementation(() => false);
game.frames[8].regularPoints.mockImplementation(() => 10);


game.frames[9].rolls.mockImplementation(() => [2, 8]);
game.frames[9].isSpare.mockImplementation(() => true);
game.frames[9].isStrike.mockImplementation(() => false);
game.frames[9].regularPoints.mockImplementation(() => 10);

game.play(scores);

expect(game.grandTotal).toEqual(133);
});
});
});

92 changes: 92 additions & 0 deletions integration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const Frame = require('./frame');
const Game = require('./game');

describe('integration test', () => {
describe('game.play()', () => {
it("takes an array of points and each to the frame's rolls array", () => {
let game = new Game(Frame);
const scores = [[1, 4], [4, 5], [6, 4], [5, 5], [10], [0, 1], [7, 3], [6, 4], [10], [2, 7]];
game.play(scores);
expect(game.frames[9].rolls).toEqual([2, 7]);
});

it('Allocates bonus points for a spare in the final frame', () => {
let game = new Game(Frame);
const scores = [ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [5, 5], [6]];
game.play(scores);
expect(game.frames[9].regularPoints).toEqual(10);
expect(game.frames[9].bonusPoints).toEqual(6);
expect(game.grandTotal).toEqual(16);
});

it('Allocates bonus points for a strike in the final frame', () => {
let game = new Game(Frame);
const scores = [ [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [10], [6, 10]];
game.play(scores);
expect(game.frames[9].regularPoints).toEqual(10);
expect(game.frames[9].bonusPoints).toEqual(16);
expect(game.grandTotal).toEqual(26);
});

it('Alloates bonus points for spares', () => {
let game = new Game(Frame);
const scores = [[5, 5], [6, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]];
game.play(scores);
expect(game.frames[0].bonusPoints).toEqual(6);
expect(game.grandTotal).toEqual(22);
});

it('Allocates bonus points for a single strike', () => {
let game = new Game(Frame);
const scores = [[10], [6, 3], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]];
game.play(scores);
expect(game.frames[0].bonusPoints).toEqual(9);
expect(game.grandTotal).toEqual(28);
});

it('Scores a complex game correctly', () => {
let game = new Game(Frame);
const scores = [[1, 4], [4, 5], [6, 4], [5, 5], [10], [0, 1], [7, 3], [6, 4], [10], [2, 8], [6]];
game.play(scores);
expect(game.grandTotal).toEqual(133);
});

it('Scores succesive strikes', () => {
let game = new Game(Frame);
const scores = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [0, 0]];
game.play(scores);
expect(game.grandTotal).toEqual(240);
});

it('Scores succesive strikes + spare in last frame', () => {
let game = new Game(Frame);
const scores = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [5, 5], [5]];
game.play(scores);
expect(game.grandTotal).toEqual(270);
});

it('Score succesive strikes + strike in last frame', () => {
let game = new Game(Frame);
const scores = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10], [5, 5]];
game.play(scores);
expect(game.grandTotal).toEqual(285);
});

it('Score a perfect game', () => {
let game = new Game(Frame);
const scores = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10], [10, 10]];
game.play(scores);
expect(game.grandTotal).toEqual(300);
expect(game.isPerfectGame()).toEqual(true);

});

it('Allocates a gutter game correctly', () => {
let game = new Game(Frame);
const scores = [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]];
game.play(scores);
expect(game.grandTotal).toEqual(0);
expect(game.isGutterGame()).toEqual(true);
});
})
})
Loading