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 6 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
37 changes: 37 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,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');
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.

// 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);
});
}
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.includes(10) && this.rolls.length == 1 ? true : false);
}

isSpare() {
return (this.regularPoints == 10 && this.rolls.length == 2 ? true : false);
}
}
module.exports = Frame;
97 changes: 97 additions & 0 deletions frame.test.js
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);
});
});
});
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 ? true : false);
};

isPerfectGame() {
return (this.grandTotal == 300 ? true : false);
};
};

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);
});
});
});

Loading