From fb8671b31e1d5280d858fb28c1149c1245453992 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 9 Jul 2017 19:53:24 +0100 Subject: [PATCH] Significant refactor and added new unit tests; Need to complete unit testing and fix feature test; --- README.md | 12 +++-- SpecRunner.html | 6 ++- package.json | 4 +- spec/features/playFrameSpec.js | 14 +++++ spec/units/FrameSpec.js | 91 ++++++++++++++++++------------- spec/units/GameSpec.js | 82 ++++++++++++++++++---------- spec/units/RollSpec.js | 27 ++++------ spec/units/ScoreCardSpec.js | 52 ++++++++++++++++++ src/Frame.js | 98 ++++++++++++++++++++++++---------- src/Game.js | 63 +++++++++++----------- src/Roll.js | 8 ++- src/ScoreCard.js | 23 ++++---- 12 files changed, 313 insertions(+), 167 deletions(-) create mode 100644 spec/features/playFrameSpec.js create mode 100644 spec/units/ScoreCardSpec.js diff --git a/README.md b/README.md index 2fb8ab0cfa..bc75701493 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,22 @@ cd bowling-challenge ### To run tests ```bash -jasmine +npm i +open SpecRunner.html ``` +If open command does not work on your machine, just copy the full path of the SpecRunner file into your browser's address bar after. ## User stories --[] I want to start a game of bowling +-[x] I want to start a game of bowling --[] I want to roll +-[x] I want to roll --[] I want to have two rolls per frame +-[x] I want to have two rolls per frame -[] I want to end a frame when I score 10 in first roll --[] I want to keep track of my score for each roll including skipped ones +-[x] I want to keep track of my score for each roll including skipped ones -[] I want to keep track of my score for each frame including bonuses diff --git a/SpecRunner.html b/SpecRunner.html index 7ccb32046e..182a15b3bc 100644 --- a/SpecRunner.html +++ b/SpecRunner.html @@ -13,16 +13,18 @@ - + + + - + diff --git a/package.json b/package.json index 9a98c13d2e..b039120154 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "doc": "docs" }, "dependencies": { - "eslint": "^3.19.0" }, "devDependencies": { "eslint": "^3.19.0", @@ -19,8 +18,7 @@ "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.1.0", "eslint-plugin-promise": "^3.5.0", - "eslint-plugin-standard": "^3.0.1", - "jasmine": "^2.6.0" + "eslint-plugin-standard": "^3.0.1" }, "repository": { "type": "git", diff --git a/spec/features/playFrameSpec.js b/spec/features/playFrameSpec.js new file mode 100644 index 0000000000..be8ad76295 --- /dev/null +++ b/spec/features/playFrameSpec.js @@ -0,0 +1,14 @@ +'use strict'; + +describe('Play a frame', function () { + it('updates the scorecard with correct frame scores', function () { + var game, scorecard; + game = new Game(); + spyOn(Math, 'random').and.returnValues(0.51, 0.9); + game.play(); + game.play(); + scorecard = game.getScoreCard(); + console.dir(scorecard.getCard()); + expect(scorecard.getCard()).toEqual([[5, 5]]); + }); +}); diff --git a/spec/units/FrameSpec.js b/spec/units/FrameSpec.js index 30044e5fe5..16ca6ebeca 100644 --- a/spec/units/FrameSpec.js +++ b/spec/units/FrameSpec.js @@ -1,52 +1,67 @@ 'use strict'; -describe('Frame', function() { +describe('Frame', function () { var frame; - beforeEach(function() { + + beforeEach(function () { frame = new Frame(); }); - describe('roll', function() { - it('returns a number between 0 and 10', function() { - frame.roll(); - expect(frame.getScore()[0]).toBeLessThan(11); + + describe('processRoll', function () { + it('calls checkFullFrame', function () { + spyOn(frame, 'checkFullFrame'); + frame.processRoll(); + expect(frame.checkFullFrame).toHaveBeenCalled(); }); - it('does not roll more than 2 times', function() { - for (var i = 0; i < 3; i++) { - frame.roll(); - } - expect(frame.getScore().length).toBeLessThan(3); + it('calls updateFrameScore and passes roll() if rollcount is less than 2 and remainder greater than 0', function () { + spyOn(frame, 'getRollCount'); + frame.getRollCount.and.returnValue(1); + spyOn(frame, 'remainder'); + frame.remainder.and.returnValue(10); + spyOn(Math, 'random').and.returnValue(0.51); + spyOn(frame, 'updateFrameScore'); + frame.processRoll(); + expect(frame.updateFrameScore).toHaveBeenCalledWith(5); }); - }); - describe('isSpare', function() { - it('sets this.spare to true when first and second score = 10', function() { - spyOn(Math, "random").and.returnValues(0.51, 0.9); - frame.roll(); - frame.roll(); - expect(frame.getScore()).toEqual([5, 5]); - expect(frame.getSpare()).toBeTrue; + it('calls updateFrameScore and passes 0 if rollcount is less than 2 but remainder is 0', function () { + spyOn(frame, 'getRollCount'); + frame.getRollCount.and.returnValue(1); + spyOn(frame, 'remainder'); + frame.remainder.and.returnValue(0); + spyOn(frame, 'updateFrameScore'); + frame.processRoll(); + expect(frame.updateFrameScore).toHaveBeenCalledWith(0); }); - it('does not set spare to true when strike', function() { - spyOn(Math, "random").and.returnValue(0.97); - frame.roll(); - frame.roll(); - expect(frame.getScore()).toEqual([10, 0]); - expect(frame.getSpare()).toBeFalse; + it('does not call updateFrameScore if rollcount is >= 2', function () { + spyOn(frame, 'getRollCount'); + frame.getRollCount.and.returnValue(2); + spyOn(frame, 'remainder'); + frame.remainder.and.returnValue(5); + spyOn(frame, 'updateFrameScore'); + frame.processRoll(); + expect(frame.updateFrameScore).not.toHaveBeenCalled(); }); }); - describe('isStrike', function() { - it('sets this.strike to true when first score = 10', function() { - spyOn(Math, "random").and.returnValues(0.95, 0.5); - frame.roll(); - frame.roll(); - expect(frame.getScore()).toEqual([10, 0]); - expect(frame.getStrike()).toBeTrue; + + describe('getRollCount', function () { + it('returns rollCount', function () { + frame.rollCount = 4; + expect(frame.getRollCount()).toEqual(4); + }); + }); + + describe('remainder', function () { + it('defaults to return 10 if no roll has been taken', function () { + spyOn(frame, 'getRollCount'); + frame.getRollCount.and.returnValue(0); + expect(frame.remainder()).toEqual(10); }); - it('does not set strike to true when second roll is 10', function() { - spyOn(Math, "random").and.returnValues(0, 0.97); - frame.roll(); - frame.roll(); - expect(frame.getScore()).toEqual([0, 10]); - expect(frame.getStrike()).toBeFalse; + it('returns points left after a roll has been taken', function () { + spyOn(frame, 'getRollCount'); + frame.getRollCount.and.returnValue(1); + spyOn(frame, 'getFrameScore'); + frame.getFrameScore.and.returnValue([4]); + expect(frame.remainder()).toEqual(6); }); }); }); diff --git a/spec/units/GameSpec.js b/spec/units/GameSpec.js index 1c2ff49e85..7fb0d8b2f1 100644 --- a/spec/units/GameSpec.js +++ b/spec/units/GameSpec.js @@ -1,45 +1,71 @@ 'use strict'; describe('Game', function () { - var game; + var game, frame; beforeEach(function () { game = new Game(); + frame = game.currentFrame; }); - describe('getScoreCard', function () { - it('returns each frame that has been started', function () { - for (var i = 0; i < 6; i++) { - game.play(); - } - expect(game.getScoreCard().length).toEqual(6); - }); - xit('returns both rolls for each frame', function () { - for (var i = 0; i < 6; i++) { - game.play(); - } - expect(game.getScoreCard()[1].length).toEqual(2); + describe('play', function () { + it('calls processroll on currentFrame', function () { + spyOn(frame, 'processRoll'); + game.play(); + expect(frame.processRoll).toHaveBeenCalled(); + }); + it('calls nextFrame', function () { + spyOn(game, 'nextFrame'); + game.play(); + expect(game.nextFrame).toHaveBeenCalled(); }); }); - describe('play', function () { - it('rolls a ball and updates the frame score', function () { - spyOn(Math, 'random').and.returnValue(0.5); - game.play(); - expect(game.getScoreCard()).toEqual([ - [5] - ]); + describe('getCurrentFrame', function () { + it('returns a frame object', function () { + expect(frame instanceof Frame).toBeTruthy(); + }); + }); + + describe('startNewFrame', function () { + it('updates currentFrame with new Frame instance', function () { + game.startNewFrame(); + var newFrame = game.getCurrentFrame(); + expect(frame).not.toBe(newFrame); }); }); - describe('getTotalScore', function () { - it('returns total score', function () { - spyOn(Math, 'random').and.returnValues(0.5, 0.9, 0.1, 0.5); - for (var i = 0; i < 5; i++) { - game.play(); - console.log(game.getScoreCard()[i]); - } - expect(game.getTotalScore()).toEqual(); + describe('nextFrame', function () { + beforeEach(function () { + spyOn(game, 'isFrameFinished'); + spyOn(game, 'startNewFrame'); + }); + it('calls startNewFrame if isFrameFinished returns true', function () { + game.isFrameFinished.and.returnValue(true); + game.nextFrame(); + expect(game.isFrameFinished).toHaveBeenCalled(); + expect(game.startNewFrame).toHaveBeenCalled(); + }); + it('does not call startNewFrame if isFrameFinished returns false', function () { + game.isFrameFinished.and.returnValue(false); + game.nextFrame(); + expect(game.isFrameFinished).toHaveBeenCalled(); + expect(game.startNewFrame).not.toHaveBeenCalled(); + }); + }); + + describe('isFrameFinished', function () { + it('calls getIsFinished on currentFrame', function () { + spyOn(frame, 'getIsFinished'); + game.isFrameFinished(); + expect(frame.getIsFinished).toHaveBeenCalled(); + }); + }); + + describe('getScoreCard', function () { + it('returns a frame object', function () { + var card = game.getScoreCard(); + expect(card instanceof ScoreCard).toBeTruthy(); }); }); }); diff --git a/spec/units/RollSpec.js b/spec/units/RollSpec.js index f00a6f2e4a..b2d9f402b6 100644 --- a/spec/units/RollSpec.js +++ b/spec/units/RollSpec.js @@ -1,25 +1,18 @@ 'use strict'; -describe('Roll', function () { - var roll; - beforeEach(function () { - roll = new Roll(); - }); - - describe('randomInt', function () { - it('returns random number between 0 and the remaining pins for a frame', +describe('randomInt', function () { + it('returns random number between 0 and the remaining pins for a frame', function () { - expect(roll.randomInt(10)).toBeLessThan(11); - expect(roll.randomInt(7)).toBeLessThan(8); - expect(roll.randomInt(5)).toBeLessThan(6); - expect(roll.randomInt(2)).toBeLessThan(3); + expect(randomInt(10)).toBeLessThan(11); + expect(randomInt(7)).toBeLessThan(8); + expect(randomInt(5)).toBeLessThan(6); + expect(randomInt(2)).toBeLessThan(3); }); - }); +}); - describe('score', function () { - it('returns random number between 0 and the remaining pins for a frame', +describe('roll', function () { + it('returns random number between 0 and the remaining pins for a frame', function () { - expect(roll.score(10)).toBeLessThan(11); + expect(roll(10)).toBeLessThan(11); }); - }); }); diff --git a/spec/units/ScoreCardSpec.js b/spec/units/ScoreCardSpec.js new file mode 100644 index 0000000000..140e2746ed --- /dev/null +++ b/spec/units/ScoreCardSpec.js @@ -0,0 +1,52 @@ +'use strict'; + +describe('ScoreCard', function () { + var card; + + beforeEach(function () { + card = new ScoreCard(); + }); + + describe('getCard', function () { + it('returns an array', function () { + expect(card.getCard()).toEqual(jasmine.any(Array)); + }); + it('returns array of arrays stored in this.card', function () { + card.card = [[1, 1], [5, 0]]; + expect(card.getCard()).toEqual([[1, 1], [5, 0]]); + }); + }); + + describe('updateCard', function () { + beforeEach(function () { + spyOn(card, 'isFullFrame'); + }); + it('checks if frame is full', function () { + card.updateCard(1); + expect(card.isFullFrame).toHaveBeenCalled(); + }); + it('updates complete frame with new rollScore', function () { + spyOn(card, 'getLastFrame'); + card.getLastFrame.and.returnValue([1]); + card.isFullFrame.and.returnValue(false); + card.updateCard(2); + expect(card.getLastFrame).toHaveBeenCalled(); + expect(card.getLastFrame()).toEqual([1, 2]); + }); + it('pushes new frame if last frame has 2 objects', function () { + spyOn(card, 'getLastFrame').and.callThrough(); + card.isFullFrame.and.returnValue(true); + card.updateCard(2); + expect(card.getLastFrame).not.toHaveBeenCalled(); + expect(card.getLastFrame()).toEqual([2]); + expect(card.getCard()).toEqual([[], [2]]); + }); + }); + + describe('getLastFrame', function () { + it('returns last item in this.card as an array', function () { + card.card = [[1, 1], [5, 0]]; + expect(card.getLastFrame()).toEqual([5, 0]); + }); + }); +}); diff --git a/src/Frame.js b/src/Frame.js index eeae2ef6ac..19f948e7ad 100644 --- a/src/Frame.js +++ b/src/Frame.js @@ -1,40 +1,80 @@ 'use strict'; -function Frame() { - this.score = []; +function Frame (card) { + this.frameScore = []; + this.scoreCard = card; + this.isFinished = false; + this.rollCount = 0; } -Frame.prototype.roll = function() { - if (this.rollCount() <= 1 && this.remainder() > 0) { - var roll = new Roll().score(this.remainder()); - this.updateScore(roll); - } else if (this.rollCount() <= 1) { - this.updateScore(0); - } - this.checkEnd(); -}; +Frame.prototype = { + processRoll: function () { + if (this.getRollCount() < 2 && this.remainder() > 0) { + var roll = this.roll(); + this.updateFrameScore(roll); + } else if (this.getRollCount() < 2) { + this.updateFrameScore(0); + } + this.checkFullFrame(); + }, -Frame.prototype.rollCount = function() { - return this.getScore().length; -}; + getRollCount: function () { + return this.rollCount; + }, -Frame.prototype.checkEnd = function() { - if (this.rollCount() === 2) { - scoreCard.updateFrames(this.getScore()); - } -}; + remainder: function () { + if (this.getRollCount() > 0) { + return 10 - this.getFrameScore()[0]; + } + return 10; + }, -Frame.prototype.getScore = function() { - return this.score; -}; + updateFrameScore: function (rollScore) { + if (this.getFrameScore().length > 1) { + throw Error('Game has tried to play a full frame'); + } + // else if (!(rollScore instanceof 'Integer'){ + // throw TypeError('not passed a number'); + // } + this.frameScore.push(rollScore); + this.updateRollCount(); + }, -Frame.prototype.updateScore = function(rollScore) { - this.score.push(rollScore); -}; + roll: function () { + return roll(this.remainder()); + }, + + updateRollCount: function () { + this.rollCount ++; + }, + + checkFullFrame: function () { + if (this.getRollCount() === 2) { + this.updateIsFinished(true); + } + }, + + getIsFinished: function () { + return this.isFinished; + }, + + updateIsFinished: function (boolean) { + this.isFinished = boolean; + }, + + updateScoreCard: function () { + this.getScoreCard().updateCard(this.getFrameScore()); + }, + + getFrameScore: function () { + return this.frameScore; + }, -Frame.prototype.remainder = function() { - if (this.rollCount() > 0) { - return 10 - this.score[0]; + getLastFrameScore: function () { + var lastScore = this.getFrameScore()[this.getFameScore().length - 1]; + if (lastScore === 'undefined') { + throw Error('Tried to update scorecard with no score in frame'); + } + return lastScore; } - return 10; }; diff --git a/src/Game.js b/src/Game.js index 3b2b5ceb29..a25f2a64ce 100644 --- a/src/Game.js +++ b/src/Game.js @@ -1,41 +1,44 @@ 'use strict'; -var scoreCard; +// const MAX_TURNS = 13; function Game () { - this.scoreCard = []; - this.totalScore = 0; - this.MAX_FRAMES = 10; - this.currentFrame = null; - scoreCard = new ScoreCard(); + this.scoreCard = new ScoreCard(); + this.currentFrame = new Frame(this.scoreCard); } -Game.prototype.play = function () { - this.roll(); - this.checkFrame(); -}; +Game.prototype = { + play: function () { + this.getCurrentFrame().processRoll(); + this.nextFrame(); + }, -Game.prototype.checkGameEnd = function () { - if (this.turn < this.MAX_TURNS || - (scorecard[scorecard.length - 1][0] === 10 || scorecard.length < 14)) { - return; - } - this.endGame(); -}; + getCurrentFrame: function () { + return this.currentFrame; + }, -Game.prototype.countFrames = function () { - return scorecard.length; -}; + startNewFrame: function () { + this.currentFrame = new Frame(this.scoreCard); + }, -Game.prototype.checkFrame = function () { - if (this.currentFrame === null) { - this.currentFrame = new Frame(); - } -}; + nextFrame: function () { + if (this.isFrameFinished()) { + this.startNewFrame(); + } + }, -Game.prototype.roll = function () { - this.currentFrame.roll(); -}; + isFrameFinished: function () { + return this.getCurrentFrame().getIsFinished(); + }, -Game.prototype.endGame = function () { - return 'Game over!'; + getScoreCard: function () { + return this.scoreCard; + } }; + +// Game.prototype.checkGameEnd = function () { +// if (this.turn < MAX_TURNS || +// (scorecard[scorecard.length - 1][0] === 10 || scorecard.length < 14)) { +// return; +// } +// this.endGame(); +// }; diff --git a/src/Roll.js b/src/Roll.js index 60d27591cf..34c64a2b05 100644 --- a/src/Roll.js +++ b/src/Roll.js @@ -1,12 +1,10 @@ 'use strict'; -function Roll () {} - -Roll.prototype.randomInt = function (remainder) { +function randomInt (remainder) { var max = Math.round(remainder); return Math.round(Math.random() * (max)); }; -Roll.prototype.score = function (remainder) { - return this.randomInt(remainder); +function roll (remainder) { + return randomInt(remainder); }; diff --git a/src/ScoreCard.js b/src/ScoreCard.js index 442b26b83f..961b5d6628 100644 --- a/src/ScoreCard.js +++ b/src/ScoreCard.js @@ -1,22 +1,25 @@ 'use strict'; function ScoreCard () { - this.frames = []; - this.total = 0; + this.card = [[]]; } -ScoreCard.prototype.getTotal = function () { - return this.total; +ScoreCard.prototype.getCard = function () { + return this.card; }; -ScoreCard.prototype.getFrames = function () { - return this.frames; +ScoreCard.prototype.updateCard = function (rollScore) { + if (this.isFullFrame()) { + this.card.push([rollScore]); + } else { + this.getLastFrame().push(rollScore); + } }; -ScoreCard.prototype.updateTotal = function (subtotal) { - this.total += subtotal; +ScoreCard.prototype.isFullFrame = function () { + return this.getLastFrame().length > 1; }; -ScoreCard.prototype.updateFrames = function (frame) { - this.frames.push(frame); +ScoreCard.prototype.getLastFrame = function () { + return this.getCard()[this.getCard().length - 1]; };