diff --git a/scrabble.js b/scrabble.js index e95f352..f593719 100644 --- a/scrabble.js +++ b/scrabble.js @@ -1,16 +1,160 @@ -const Scrabble = { +const Scrabble = { + score(word) { + 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 }; + + if (/^[a-zA-Z]+$/.test(word) && word.length <= 7) { + + word = word.toUpperCase(); + + let wordScore = (word.length == 7)? 50 : 0; + + for (let i = 0; i < word.length; i+=1) { + wordScore += letterValues[word.charAt(i)]; + } + + return wordScore; + + } else { + throw "Word must only contain letters from A-Z and have less than or equal to 7 characters"; + } }, + highestScoreFrom(arrayOfWords) { + if (arrayOfWords.length === 0) { + + throw new Error('Array of words can not be empty'); + + } else if (arrayOfWords.length === 1) { + + return arrayOfWords[0]; + + } else { + + const winningWord = arrayOfWords.reduce((word1, word2) => { + const scoreWord1 = Scrabble.score(word1); + const scoreWord2 = Scrabble.score(word2); + + if (scoreWord1 > scoreWord2 || word1.length === 7) { + return word1; + + } else if (scoreWord2 > scoreWord1 || word2.length === 7) { + return word2; + } + + if (word1.length === word2.length || word1.length < word2.length) { + return word1; + } + + return word2; + + }); + + return winningWord; + } }, }; Scrabble.Player = class { + constructor(name) { + if (name.length > 0 || typeof name === 'string') { + this.name = name; + } + else { + throw new Error('Please enter name of the player'); + } + + this.plays =[]; + + } + + play(word) { + + if (word === " "|| typeof word !== 'string') { + throw new Error('Please enter a valid word'); + } + if (this.hasWon()) { + return false; + } + this.plays.push(word); + return true; + } + + + totalScore() { + + let total = 0; + for (let word of this.plays) { + total += Scrabble.score(word); + } + return total; + } + + hasWon() { + + let won = (this.totalScore() >= 100) ? true:false; + return won; + } + + highestScoringWord() { + + return Scrabble.highestScoreFrom(this.plays); + } + + highestWordScore() { + + return Scrabble.score(this.highestScoringWord()); + } }; +// Optional Enhancements + +// TODO: I need to improve the TilesBag. The way I made the tiles in the constructor +// I know is not the best way. + +Scrabble.TilesBag = class { + constructor() { + this.tiles = ['A','A','A','A','A','A','A','A','A','B','B','C','C','D','D','D','D', + 'E','E','E','E','E','E','E','E','E','E','E','E','F','F','G','G','G','H','H','I','I','I','I','I','I', + 'I','I','I','J','K','L','L','L','L','M','M','N','N','N','N','N','N,','O','O','O','O','O','O','O','O', + 'P','P','Q','R','R','R','R','R','R','S','S','S','S','T','T','T','T','T','T','U','U','U','U','V','V', + 'W','W','X','Y','Y','Z']; + } + + drawTiles(number) { + if (number > this.tilesRemaining()) { + throw new Error('No enough tiles'); + } + let hand = []; + for (let i = 0; i < number; i+=1) { + let rand = Math.floor(Math.random() * this.tiles.length); + hand.push(this.tiles[rand]); + this.tiles.splice(rand, 1); + // splice removes items from an array. changes the original array + } + return hand; + } + + tilesRemaining() { + if (this.tiles.length === 0) { + return 'No more tiles in the bag' + } + return this.tiles.length; + } + +}; module.exports = Scrabble; + + +// console.log(Scrabble.score("ZZzzZZ")); +// console.log(Scrabble.score("baby")); +// console.log(Scrabble.highestScoreFrom(["ZZzzZZ", "zzzzzzz", "pedro"])); +// console.log(Scrabble.highestScoreFrom(["QQQQQQQ", "zzzzzzz", "pedro"])); +// console.log(Scrabble.score("QQQQQQQ")); diff --git a/specs/scrabble.spec.js b/specs/scrabble.spec.js index b562037..893f64c 100644 --- a/specs/scrabble.spec.js +++ b/specs/scrabble.spec.js @@ -11,46 +11,46 @@ describe('score', () => { expect(Scrabble.score('pig')).toBe(6); }); - test.skip('adds 50 points for a 7-letter word', () => { + test('adds 50 points for a 7-letter word', () => { expect(Scrabble.score('academy')).toBe(65); }); - test.skip('throws on bad characters', () => { + test('throws on bad characters', () => { expect(() => { Scrabble.score('char^'); }).toThrow(); }); - test.skip('handles all upper- and lower-case letters', () => { + test('handles all upper- and lower-case letters', () => { expect(Scrabble.score('dog')).toBe(5); expect(Scrabble.score('DOG')).toBe(5); expect(Scrabble.score('DoG')).toBe(5); }); - test.skip('does not allow words > 7 letters', () => { + test('does not allow words > 7 letters', () => { expect(() => { Scrabble.score('abcdefgh'); }).toThrow(); }); - test.skip('does not allow empty words', () => { + test('does not allow empty words', () => { expect(() => { Scrabble.score(''); }).toThrow(); }); }); describe('highestScoreFrom', () => { - test.skip('is defined', () => { + test('is defined', () => { expect(Scrabble.highestScoreFrom).toBeDefined(); }); - test.skip('throws if no words were passed', () => { + test('throws if no words were passed', () => { expect(() => { Scrabble.highestScoreFrom([]); }).toThrow(); expect(() => { Scrabble.highestScoreFrom('not array'); }).toThrow(); }); - test.skip('returns the only word in a length-1 array', () => { + test('returns the only word in a length-1 array', () => { expect(Scrabble.highestScoreFrom(['dog'])).toBe('dog'); }); - test.skip('returns the highest word if there are two words', () => { + test('returns the highest word if there are two words', () => { // Check score assumptions expect(Scrabble.score('dog')).toBe(5); expect(Scrabble.score('pig')).toBe(6); @@ -60,7 +60,7 @@ describe('highestScoreFrom', () => { expect(Scrabble.highestScoreFrom(['pig', 'dog'])).toBe('pig'); }); - test.skip('if tied, prefer a word with 7 letters', () => { + test('if tied, prefer a word with 7 letters', () => { const loser = 'zzzzzz'; const winner = 'iiiiddd'; @@ -73,7 +73,7 @@ describe('highestScoreFrom', () => { expect(Scrabble.highestScoreFrom([winner, loser])).toBe(winner); }); - test.skip('if tied and no word has 7 letters, prefers the word with fewer letters', () => { + test('if tied and no word has 7 letters, prefers the word with fewer letters', () => { // Check score assumptions expect(Scrabble.score('dog')).toBe(5); expect(Scrabble.score('goat')).toBe(5); @@ -83,7 +83,7 @@ describe('highestScoreFrom', () => { expect(Scrabble.highestScoreFrom(['goat', 'dog'])).toBe('dog'); }); - test.skip('returns the first word of a tie with same letter count', () => { + test('returns the first word of a tie with same letter count', () => { // Check score assumptions expect(Scrabble.score('i')).toBe(1); expect(Scrabble.score('dog')).toBe(5); @@ -98,19 +98,19 @@ describe('highestScoreFrom', () => { }); describe('Player', () => { - test.skip('is defined', () => { + test('is defined', () => { expect(Scrabble.Player).toBeDefined(); }); describe('Constructor', () => { - test.skip('Creates a new player', () => { + test('Creates a new player', () => { const name = 'test name'; const player = new Scrabble.Player(name); expect(player.name).toBe(name); }); - test.skip('Requires a name', () => { + test('Requires a name', () => { expect(() => { new Scrabble.Player(); }).toThrow(); @@ -118,7 +118,7 @@ describe('Player', () => { }); describe('play', () => { - test.skip('Records the played word', () => { + test('Records the played word', () => { const word = 'dog'; const player = new Scrabble.Player('test player'); @@ -130,7 +130,7 @@ describe('Player', () => { expect(player.plays[0]).toBe(word); }); - test.skip('Requires a real word', () => { + test('Requires a real word', () => { const player = new Scrabble.Player('test player'); expect(player.plays.length).toBe(0); @@ -142,7 +142,7 @@ describe('Player', () => { expect(player.plays.length).toBe(0); }); - test.skip('Returns false and does not update plays if the player has already won', () => { + test('Returns false and does not update plays if the player has already won', () => { const player = new Scrabble.Player('test player'); expect(player.play('zzzzzzz')).toBeTruthy(); // +120 pts @@ -155,13 +155,13 @@ describe('Player', () => { }); describe('totalScore', () => { - test.skip('Is zero if the player has not played anything', () => { + test('Is zero if the player has not played anything', () => { const player = new Scrabble.Player('test player'); expect(player.totalScore()).toBe(0); }); - test.skip('Is updated by play', () => { + test('Is updated by play', () => { // Arrange const player = new Scrabble.Player('test player'); const words = [{word: 'dog', score: 5}, {word: 'cat', score: 5}, {word: 'goat', score: 5}]; @@ -181,18 +181,47 @@ describe('Player', () => { }); describe('hasWon', () => { - test.skip('returns false when score < 100', () => { + test('returns false when score < 100', () => { + const player = new Scrabble.Player('test player'); + + player.play('alcohol'); + + + expect(player.totalScore()).toBe(62); + expect(player.hasWon()).toBe(false); + + + player.play('cookie'); + + + expect(player.totalScore()).toBe(74); + expect(player.hasWon()).toBe(false); }); - test.skip('returns true when score == 100', () => { + test('returns true when score == 100', () => { + const player = new Scrabble.Player('test player'); + + player.play('QQQQQ'); + player.play('ZZZZZ'); + + + expect(player.totalScore()).toBe(100); + expect(player.hasWon()).toBe(true); }); - test.skip('returns true when score > 100', () => { + test('returns true when score > 100', () => { + const player = new Scrabble.Player('test player'); + player.play('squeeze'); + player.play('jukebox'); + + + expect(player.totalScore()).toBe(152); + expect(player.hasWon()).toBe(true); }); }); @@ -200,26 +229,111 @@ describe('Player', () => { describe('highestScoringWord', () => { // Tie-breaking logic is already described in the tests // for highestWordFrom, so we will not repeat it here. - test.skip('returns the highest scoring word played', () => { + test('returns the highest scoring word played', () => { + const player = new Scrabble.Player('test player'); + + player.play('QQQQQ'); + player.play('KKKKK'); + + expect(Scrabble.score('QQQQQ')).toBe(50); + expect(Scrabble.score('KKKKK')).toBe(25); + expect(player.highestScoringWord()).toBe('QQQQQ'); + + }); + + test('throws an error if no words have been played', () => { + const player = new Scrabble.Player('test player'); + expect(() => { player.highestScoringWord() }).toThrow(); + }); + }); + + describe('highestWordScore', () => { + test('returns the score of the highest scoring word played', () => { + const player = new Scrabble.Player('test player'); + player.play('QQQQQ'); + player.play('KKKKK'); + expect(player.highestWordScore()).toBe(50); }); - test.skip('throws an error if no words have been played', () => { + test('throws an error if no words have been played', () => { + const player = new Scrabble.Player('test player'); + expect(() => { player.highestScoringWord() }).toThrow(); + + }); + }); +}); + +// optional enchancements tests +describe('TileBag', () => { + test('is defined', () => { + expect(Scrabble.TilesBag).toBeDefined(); + }); + describe('constructor', () => { + test('creates a new tile bag', () => { + const tileBag = new Scrabble.TilesBag(); + expect(tileBag.tiles.length).toEqual(98); }); + }); - describe('highestWordScore', () => { - test.skip('returns the score of the highest scoring word played', () => { + describe('drawTiles', () => { + test('returns a collection of tiles', () => { + const tileBag = new Scrabble.TilesBag(); + let tiles = tileBag.drawTiles(5); + expect(tiles.length).toEqual(5); }); - test.skip('throws an error if no words have been played', () => { + test('throws error if not enough tiles', () => { + const tileBag = new Scrabble.TilesBag(); + // a loop to use tiles + for (let i = 0; i < 10; i+=1) { + tileBag.drawTiles(9); + } + // try to get more tiles than the amount available + expect(() => { tileBag.drawTiles(10); }).toThrow(); }); + + test('removes the tile from the array of tiles', () => { + const tileBag = new Scrabble.TilesBag(); + + tileBag.drawTiles(2); + + expect(tileBag.tiles.length).toEqual(96); + }); + + + }); + + describe('tilesRemaining', () => { + test('returns the amount of tiles remaining in the tiles bag', () => { + const tileBag = new Scrabble.TilesBag(); + + tileBag.drawTiles(4); + + expect(tileBag.tilesRemaining()).toEqual(94); + }); + + test('returns "No more tiles in the bag" if no tiles left in the bag', () => { + const tileBag = new Scrabble.TilesBag(); + + // a loop to use all the tiles + for (let i = 0; i < 14; i++) { + tileBag.drawTiles(7); + } + + expect(tileBag.tilesRemaining()).toEqual('No more tiles in the bag'); + }); + + }); + + });