From 465d7bec266f7e4bc150a623e6c10b8d50fab033 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Tue, 14 Nov 2017 16:20:12 -0800 Subject: [PATCH 1/4] Wrote initial word scoring without exceptions --- package-lock.json | 123 ++++++++++++++++++++++++++++++++++++++++++ scrabble.js | 56 +++++++++++++++++-- spec/scrabble_spec.js | 4 +- 3 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..80043d7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,123 @@ +{ + "name": "js-scrabble", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "add-matchers": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.5.0.tgz", + "integrity": "sha1-UCGQ5HUM1XIWGDkyaLYaFXNm52U=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "eslint-plugin-jasmine": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.9.1.tgz", + "integrity": "sha1-IuGaWfFvOl9kOgSroEQ40OMEcDA=" + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "requires": { + "exit": "0.1.2", + "glob": "7.1.2", + "jasmine-core": "2.8.0" + } + }, + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=" + }, + "jasmine-expect": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-3.8.1.tgz", + "integrity": "sha512-klARdR5AVX9nZhHhYDlbDYgxgi6kl9DGS0vguhaioKoSwr8HL1uOQ7FFUBASq/sqBL/s2nkh/1efqZ2ugcknFA==", + "requires": { + "add-matchers": "0.5.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/scrabble.js b/scrabble.js index 7a1f161..1d5077a 100644 --- a/scrabble.js +++ b/scrabble.js @@ -1,7 +1,57 @@ +function UserException(message) { + this.message = message; + this.name = 'UserException'; +} + +const LetterValues = { + A: 1, + B: 3, + C: 3, + D: 2, + E: 1, + F: 4, + G: 2, + H: 4, + I: 1, + J: 8, + K: 5, + L: 1, + M: 3, + N: 1, + O: 1, + P: 3, + Q: 10, + R: 1, + S: 1, + T: 1, + U: 1, + V: 4, + W: 4, + X: 8, + Y: 4, + Z: 10, +}; + + const Scrabble = { - score: function(word) { - // TODO: implement score - } + score(word) { + // switch (word) { + // case word.length > 7: + // throw new UserException('Cannot play words longer than 7 letters.'); + // break; + // case + // default: + // + // } + let total = 0; + for (let i = 0; i < word.length; i += 1) { + total += LetterValues[`${word[i].toUpperCase()}`]; + } + if (word.length === 7) { + total += 50; + } + return total; + }, // TODO: add the highestScoreFrom method diff --git a/spec/scrabble_spec.js b/spec/scrabble_spec.js index c195a03..f776c37 100644 --- a/spec/scrabble_spec.js +++ b/spec/scrabble_spec.js @@ -36,7 +36,7 @@ describe('score', function() { }); }); -describe('highestScoreFrom', function() { +xdescribe('highestScoreFrom', function() { it ('is defined', function() { expect(Scrabble.highestScoreFrom).toBeDefined(); }); @@ -97,7 +97,7 @@ describe('highestScoreFrom', function() { }); }); -describe('Player', function() { +xdescribe('Player', function() { it ('is defined', function() { expect(Scrabble.Player).toBeDefined(); }); From 980af62acd328ffdcfc4e3a75c26824a18ff2292 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Tue, 14 Nov 2017 16:35:02 -0800 Subject: [PATCH 2/4] wrote score function with exceptions --- scrabble.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scrabble.js b/scrabble.js index 1d5077a..37ad406 100644 --- a/scrabble.js +++ b/scrabble.js @@ -35,14 +35,14 @@ const LetterValues = { const Scrabble = { score(word) { - // switch (word) { - // case word.length > 7: - // throw new UserException('Cannot play words longer than 7 letters.'); - // break; - // case - // default: - // - // } + if (word.length > 7) { + throw new UserException('Cannot play words longer than 7 letters.'); + } else if (word.length === 0) { + throw new UserException('Please play a word.'); + } else if ((/[^a-zA-Z]+/).test(word)) { + throw new UserException('Cannot play non-alphabetical characters.'); + } + let total = 0; for (let i = 0; i < word.length; i += 1) { total += LetterValues[`${word[i].toUpperCase()}`]; From cc92c493aa351452c0249e3cfbaad8989edc204c Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Tue, 14 Nov 2017 17:10:28 -0800 Subject: [PATCH 3/4] Wrote highestScoreFrom function and passed tests --- scrabble.js | 30 ++++++++++++++++++++++-------- spec/scrabble_spec.js | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/scrabble.js b/scrabble.js index 37ad406..69d878b 100644 --- a/scrabble.js +++ b/scrabble.js @@ -1,6 +1,6 @@ -function UserException(message) { - this.message = message; - this.name = 'UserException'; +function GameException(message) { + this.message = message; + this.name = 'GameException'; } const LetterValues = { @@ -36,11 +36,11 @@ const LetterValues = { const Scrabble = { score(word) { if (word.length > 7) { - throw new UserException('Cannot play words longer than 7 letters.'); + throw new GameException('Cannot play words longer than 7 letters.'); } else if (word.length === 0) { - throw new UserException('Please play a word.'); + throw new GameException('Please play a word.'); } else if ((/[^a-zA-Z]+/).test(word)) { - throw new UserException('Cannot play non-alphabetical characters.'); + throw new GameException('Cannot play non-alphabetical characters.'); } let total = 0; @@ -52,8 +52,22 @@ const Scrabble = { } return total; }, - - // TODO: add the highestScoreFrom method + highestScoreFrom(arrayOfWords) { + if (arrayOfWords.length === 0) { + throw new GameException('There are no words to compare.'); + } + let highest = arrayOfWords[0]; + for (let i = 1; i < arrayOfWords.length; i += 1) { + if (this.score(highest) < this.score(arrayOfWords[i])) { + highest = arrayOfWords[i]; + } else if (this.score(highest) === this.score(arrayOfWords[i]) && highest.length !== 7) { + if (arrayOfWords[i].length < highest.length || (arrayOfWords[i].length === 7)) { + highest = arrayOfWords[i]; + } + } + } + return highest; + }, }; diff --git a/spec/scrabble_spec.js b/spec/scrabble_spec.js index f776c37..bce0504 100644 --- a/spec/scrabble_spec.js +++ b/spec/scrabble_spec.js @@ -36,7 +36,7 @@ describe('score', function() { }); }); -xdescribe('highestScoreFrom', function() { +describe('highestScoreFrom', function() { it ('is defined', function() { expect(Scrabble.highestScoreFrom).toBeDefined(); }); From d4055e64cc02ac507d204469f62b948840e545e5 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Wed, 15 Nov 2017 15:18:13 -0800 Subject: [PATCH 4/4] OOOPS! Well, finished Scrabble.Player and wrote tests and implemented Scrabble.TileBag --- scrabble.js | 142 ++++++++++++++++++++++++++++++++---------- spec/scrabble_spec.js | 45 ++++++++++++- 2 files changed, 153 insertions(+), 34 deletions(-) diff --git a/scrabble.js b/scrabble.js index 69d878b..6c32c87 100644 --- a/scrabble.js +++ b/scrabble.js @@ -3,37 +3,63 @@ function GameException(message) { this.name = 'GameException'; } -const LetterValues = { - A: 1, - B: 3, - C: 3, - D: 2, - E: 1, - F: 4, - G: 2, - H: 4, - I: 1, - J: 8, - K: 5, - L: 1, - M: 3, - N: 1, - O: 1, - P: 3, - Q: 10, - R: 1, - S: 1, - T: 1, - U: 1, - V: 4, - W: 4, - X: 8, - Y: 4, - Z: 10, -}; - - const Scrabble = { + LetterValues: { + A: 1, + B: 3, + C: 3, + D: 2, + E: 1, + F: 4, + G: 2, + H: 4, + I: 1, + J: 8, + K: 5, + L: 1, + M: 3, + N: 1, + O: 1, + P: 3, + Q: 10, + R: 1, + S: 1, + T: 1, + U: 1, + V: 4, + W: 4, + X: 8, + Y: 4, + Z: 10, + }, + letterFrequency: { + A: 9, + B: 2, + C: 2, + D: 4, + E: 12, + F: 2, + G: 3, + H: 2, + I: 9, + J: 1, + K: 1, + L: 4, + M: 2, + N: 6, + O: 8, + P: 2, + Q: 1, + R: 6, + S: 4, + T: 6, + U: 4, + V: 2, + W: 2, + X: 1, + Y: 2, + Z: 1, + }, score(word) { if (word.length > 7) { throw new GameException('Cannot play words longer than 7 letters.'); @@ -45,7 +71,7 @@ const Scrabble = { let total = 0; for (let i = 0; i < word.length; i += 1) { - total += LetterValues[`${word[i].toUpperCase()}`]; + total += this.LetterValues[`${word[i].toUpperCase()}`]; } if (word.length === 7) { total += 50; @@ -72,7 +98,59 @@ const Scrabble = { }; Scrabble.Player = class { - // TODO: implement the Player class + constructor(name) { + if (name === undefined) { + throw new GameException('A name is required.'); + } + this.name = name; + this.plays = []; + } + play(word) { + if (this.hasWon()) { + return false; + } else if (/[^a-zA-Z]+/.test(word) || word === undefined) { + throw new GameException('Letters must be played.'); + } else { + this.plays.push(word); + } + return true; + } + hasWon() { + return (this.totalScore() >= 100); + } + totalScore() { + const total = this.plays.reduce((acc, curr) => acc + Scrabble.score(curr), 0); + return total; + } + highestScoringWord() { + return Scrabble.highestScoreFrom(this.plays); + } + highestWordScore() { + return Scrabble.score(this.highestScoringWord()); + } +}; + +Scrabble.TileBag = class { + constructor() { + this.tiles = []; + Object.entries(Scrabble.letterFrequency).forEach(([key, value]) => { + for (let i = 0; i < value; i += 1) { + this.tiles.push(key); + } + }); + } + draw(num) { + let n = num; + if (num > this.tiles.length) { + n = this.tiles.length; + } + const hand = []; + for (let i = 0; i < n; i += 1) { + const index = Math.floor(Math.random() * this.tiles.length); + hand.push(this.tiles.splice(index, 1)[0]); + } + return hand; + } }; module.exports = Scrabble; diff --git a/spec/scrabble_spec.js b/spec/scrabble_spec.js index bce0504..7a59c4d 100644 --- a/spec/scrabble_spec.js +++ b/spec/scrabble_spec.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + const Scrabble = require('../scrabble'); describe('score', function() { @@ -97,7 +99,7 @@ describe('highestScoreFrom', function() { }); }); -xdescribe('Player', function() { +describe('Player', function() { it ('is defined', function() { expect(Scrabble.Player).toBeDefined(); }); @@ -198,7 +200,7 @@ xdescribe('Player', function() { describe('highestScoringWord', function() { // Tie-breaking logic is already described in the tests - // for highestWordFrom, so we will not repeat it here. + // for highestScoreFrom, so we will not repeat it here. it('returns the highest scoring word played', function() { let player = new Scrabble.Player('test player'); player.play('cat'); @@ -226,3 +228,42 @@ xdescribe('Player', function() { }); }); }); + +describe('TileBag', function() { + it ('is defined', function() { + expect(Scrabble.TileBag).toBeDefined(); + }) + + describe('Constructor', function() { + it('Creates a new tilebag', function() { + let tilebag = new Scrabble.TileBag(); + expect(tilebag.tiles.length).toBe(98); + }); + }); + + describe('draw', function() { + it ('draws number of tiles given', function() { + let tilebag = new Scrabble.TileBag(); + expect(tilebag.draw(10).length).toBe(10); + expect(Array.isArray(tilebag.draw(5))).toBe(true); + }); + + it ('decrements the tilebag by number of tiles drawn', function() { + let tilebag = new Scrabble.TileBag(); + let demobag = new Scrabble.TileBag(); + expect(tilebag.draw(98).sort().toString()).toBe(demobag.tiles.toString()); + expect(tilebag.tiles.length).toBe(0); + demobag.draw(30); + expect(demobag.tiles.length).toBe(68); + }); + + it ('when tile bag is empty, no further tiles are drawn', function() { + let tilebag = new Scrabble.TileBag(); + tilebag.draw(95); + expect(tilebag.draw(7).length).toBe(3); + expect(tilebag.tiles.length).toBe(0); + + }); + }); + +})