-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
base: main
Are you sure you want to change the base?
Bowling challenge #1606
Changes from 6 commits
09e1b77
542cc53
d5b2ab3
5fa09db
43483ba
7a4229a
405f86a
30d3ce4
febd1f1
b9c6a6c
f8eba9e
14af738
7eaaf9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
class Frame { | ||
constructor() { | ||
this.rolls = []; | ||
this.regularPoints = 0; | ||
this.bonusPoints = 0; | ||
}; | ||
|
||
roll(points) { | ||
// check if frame has already ended | ||
if (this.rolls.length == 2 || this.rolls.includes(10)) { | ||
throw new Error('Tried to add points to a frame that is already over'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this part where you are catching the error. |
||
// check for invalid scores being inputted | ||
} else if(points < 0 || points > 10 ) { | ||
throw new Error(`Tried to add an invalid roll score (${points})`); | ||
} 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); | ||
}); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe to change this for the interface? |
||
|
||
isStrike() { | ||
return (this.rolls.includes(10) && this.rolls.length == 1 ? true : false); | ||
} | ||
|
||
isSpare() { | ||
return (this.regularPoints == 10 && this.rolls.length == 2 ? true : false); | ||
} | ||
} | ||
module.exports = Frame; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
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 given an invalid roll score', () => { | ||
frame = new Frame(); | ||
expect(() => frame.roll(13)).toThrow('Tried to add an invalid roll score (13)'); | ||
expect(() => frame.roll(-5)).toThrow('Tried to add an invalid roll score (-5)'); | ||
}); | ||
|
||
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); | ||
}); | ||
}); | ||
}); |
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); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]; | ||
}; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? true : false); | ||
}; | ||
|
||
isPerfectGame() { | ||
return (this.grandTotal == 300 ? true : false); | ||
}; | ||
}; | ||
|
||
module.exports = Game; |
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); | ||
}); | ||
}); | ||
}); | ||
|
There was a problem hiding this comment.
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!