diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 00000000..b0582e1f --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +Scrabble diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..276cbf9e --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.3.0 diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..f10c9770 --- /dev/null +++ b/Rakefile @@ -0,0 +1,8 @@ +require 'rake/testtask' +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['specs/*_spec.rb'] +end + +task default: :test \ No newline at end of file diff --git a/lib/player.rb b/lib/player.rb new file mode 100644 index 00000000..1eecf940 --- /dev/null +++ b/lib/player.rb @@ -0,0 +1,57 @@ +class Scrabble::Player + attr_reader :name, :played_words, :bag, :tiles + def initialize(hash) + @name = hash[:name] + @played_words = hash[:words] + @bag = Scrabble::TileBag.new + # initialize bag of tiles to play with - always 7 + @tiles = @bag.draw_tiles(7) + end + + # method to take in word, remove length from tiles, see if player won and generate score for word + def play(word) + # removes number of played tiles and re-draws from tile bag + length = word.length + @tiles.pop(length) + # pass new word to tiles_method to remove those letters from @tiles + draw(length) + # returns false if already won + if won? + return false + else + @played_words << word + Scrabble::Scoring.score(word) + end + end + + # take all played words and generate score (sum of all) + def total_score + points = 0 + played_words.each do |word| + points += Scrabble::Scoring.score(word) + end + return points + end + + # method to track if player has won + def won? + total_score >= 100 ? true : false + end + + # Returns the highest scoring played word + def highest_scoring_word + played_words.max_by {|word| Scrabble::Scoring.score(word)} + end + + # Returns the highest_scoring_word score + def highest_word_score + highest_word = played_words.max_by {|word| Scrabble::Scoring.score(word)} + Scrabble::Scoring.score(highest_word) + end + + # removes letters from played word and repopulates tiles to 7 + def draw(length) + @tiles += @bag.draw_tiles(length) + return @tiles + end +end diff --git a/lib/scoring.rb b/lib/scoring.rb new file mode 100644 index 00000000..0d1252e8 --- /dev/null +++ b/lib/scoring.rb @@ -0,0 +1,87 @@ +class Scrabble::Scoring + attr_reader :LETTERS + # letters and corresponding values for scoring the words + LETTERS = { + 1 => ["A", "E", "I", "O", "U", "L", "N", "R", "S", "T"], + 2 => ["D", "G"], + 3 => ["B", "C", "M", "P"], + 4 => ["F", "H", "V", "W", "Y"], + 5 => ["K"], + 8 => ["J", "X"], + 10 => ["Q", "Z"] + } + + # returns score (single word) + def self.score(word) + word.upcase! + score = 0 + word.each_char do |letter| + LETTERS.each do |k, v| + if v.include?(letter) + score += k + end + end + end + # special bonus if you use 7 tiles + if word.length == 7 + score += 50 + end + # return score as int + return score + end + + # method that sorts array or arrays by array[0], finds ties and returns array with only ties + def self.sort_drop(array_of_arrays, index) + sorted_array = array_of_arrays.sort_by { |array| array[0] } + min_max = sorted_array[index][0] + sorted_array.delete_if{|array| array[0] != min_max } + return sorted_array + end + # returns highest scored word as ([array]) + def self.highest_score_from(array_of_words) + score_array = [] + word_array = [] + + # creates separate score and word arrays based on array input + array_of_words.each do |word| + temp_score = Scrabble::Scoring.score(word) + score_array << temp_score + word_array << word + end + + # combines the score and word array AND sorts + total_array = score_array.zip(word_array) # => [[score,word]] + # call method to sort and return only score ties + sorted_array = self.sort_drop(total_array, -1) + + # if there's one word left + if sorted_array.length == 1 + return sorted_array[0][1] # this is the answer + # if there's a tie, reassign score to equal length of word => [[length,word]] + else + sorted_array.each do |array| + len = array[1].length + array[0] = len + end + end + + # call method to sort and return only length ties + sorted_tie_array = self.sort_drop(sorted_array, 0) + + # if there's one word left, it's obvs the answer + if sorted_tie_array.length == 1 + return sorted_tie_array[0][1] + + # else there are multiple words with the same length + # select the one that came first (based on user input) + else + array_of_words.each do |word| + sorted_tie_array.each do |array| + if word == array[1] + return array[1] + end + end + end + end + end +end diff --git a/lib/tilebag.rb b/lib/tilebag.rb new file mode 100644 index 00000000..1ff648b3 --- /dev/null +++ b/lib/tilebag.rb @@ -0,0 +1,42 @@ +class Scrabble::TileBag + attr_reader :tilebag + # initialize an array of array holding letter/amount info + def initialize + array_letters = ("A".."Z").to_a + array_numbers = [9, 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, 2, 6, 8, 2, 1, 6, 4, + 6, 4, 2, 2, 1, 2, 1] + @tilebag = array_letters.zip(array_numbers) + end + + # method to randomly select tiles + def draw_tiles(num) + letter = [] + num.times do |tile| + # make sure there are tiles left in bag + if tilebag.length == 0 + puts "No more tiles left." + break + end + index = rand(0..tilebag.length-1) + # shovels letters into array + letter << tilebag[index][0] + # removes 1 from letter count if tile is drawn + @tilebag[index][1] = tilebag[index][1] - 1 + # if letter amount = 0 remove entire subarray + if tilebag[index][1] == 0 + @tilebag.delete(tilebag[index]) + end + end + return letter + end + + # method to return number of tiles remaining + def tiles_remaining + tiles = 0 + # counter numbers from each letter subarray + tilebag.each do |letter, number| + tiles = tiles + number + end + return tiles + end +end diff --git a/sandbox.rb b/sandbox.rb new file mode 100644 index 00000000..a0caa48d --- /dev/null +++ b/sandbox.rb @@ -0,0 +1,9 @@ +def self.sort_drop(array_of_arrays, index) + sorted_array = array_of_arrays.sort_by { |array| array[0] } + min_max = sorted_array[index][0] + sorted_array.delete_if{|array| array[0] != min_max } + return sorted_array +end + +self.sort_drop(total_array, -1) +self.sort_drop(sorted_tie_array, 0) \ No newline at end of file diff --git a/scrabble.rb b/scrabble.rb new file mode 100644 index 00000000..45effd1d --- /dev/null +++ b/scrabble.rb @@ -0,0 +1,6 @@ + +module Scrabble +end +require_relative './lib/tilebag' +require_relative './lib/player' +require_relative './lib/scoring' diff --git a/specs/player_spec.rb b/specs/player_spec.rb new file mode 100644 index 00000000..037518de --- /dev/null +++ b/specs/player_spec.rb @@ -0,0 +1,100 @@ +require_relative './spec_helper' +require_relative '../scrabble' + +describe Scrabble::Player do + # make sure there is a Player class + it "Is there a class? Anything?" do + Scrabble::Player.wont_be_nil + end + + # make a bob, do certain plays and assert what's in bob + it "returns a name" do + bob = Scrabble::Player.new(name: "Bob", words: ["house", "cat", "zebra"]) + bob.name.must_equal("Bob") + end + + # make sure played_words returns array of words + it "returns the played words" do + bob = Scrabble::Player.new(name: "Bob", words: ["house", "cat", "zebra"]) + bob.played_words.must_equal(["house", "cat", "zebra"]) + end + + # test play(word) returns score + it "returns score of newly played word" do + bob = Scrabble::Player.new(name: "Bob", words: ["house", "cat"]) + bob.play("zebra").must_equal(16) + end + + # test total score based on array of words + it "returns the total score of all the words in the array" do + bob = Scrabble::Player.new(name: "Bob", words: ["zebre","zebra"]) + bob.total_score.must_equal(32) + end + + # test to see if player won based on total score = true + it "returns true/false based on player winning" do + bob = Scrabble::Player.new(name: "Bob", words: ["zzzzzzz","zzzzzzz"]) + bob.won?.must_equal true + end + + # test to see if player has NOT won => false + it "returns true/false based on player winning" do + bob = Scrabble::Player.new(name: "Bob", words: ["cat"]) + bob.won?.must_equal false + end + + # test to see if player has NOT won => false + it "returns true/false based on player winning" do + bob = Scrabble::Player.new(name: "Bob", words: ["cat"]) + bob.play("zebra") + bob.played_words.must_equal(["CAT", "ZEBRA"]) + bob.total_score.must_equal(21) + bob.play("zzzzzzz") + bob.total_score.must_equal(141) + bob.play("bees").must_equal false + end + + # test to see highest_scoring_word returns correctly + it "returns highest scoring word" do + bob = Scrabble::Player.new(name: "Bob", words: ["cat", "house", "zebra", "ZZZZZZZ"]) + bob.highest_scoring_word.must_equal "ZZZZZZZ" + end + + # test to see highest_word_score returns correctly + it "returns highest score" do + bob = Scrabble::Player.new(name: "Bob", words: ["cat", "house", "zebra", "ZZZZZZZ"]) + bob.highest_word_score.must_equal 120 + end + + # make sure player instances are created with 7 tiles to play with + it "should start with 7 tiles " do + bob = Scrabble::Player.new(name: "Bob", words: ["dog"]) + bob.tiles.length.must_equal(7) + end + + # make sure play and draw method work together to keep 7 tiles to play with + it "should always have 7 tiles to play with after playing words " do + bob = Scrabble::Player.new(name: "Bob", words: ["dog"]) + bob.tiles + bob.play("abcd") + bob.tiles.length.must_equal(7) + end + + # holistic test with two player instances + it "check that Darren's score is 20 " do + bob = Scrabble::Player.new(name: "Bob", words: ["dog"]) + darren = Scrabble::Player.new(name: "Darren", words: ["house"]) + bob.play("mouse") + darren.play("box") + darren.total_score.must_equal(20) + end + + # holistic test with two player instances PART 2! + it "check that Bob's score is 12 " do + bob = Scrabble::Player.new(name: "Bob", words: ["dog"]) + darren = Scrabble::Player.new(name: "Darren", words: ["house"]) + bob.play("mouse") + darren.play("box") + bob.total_score.must_equal(12) + end +end diff --git a/specs/scoring_spec.rb b/specs/scoring_spec.rb new file mode 100644 index 00000000..c3266068 --- /dev/null +++ b/specs/scoring_spec.rb @@ -0,0 +1,48 @@ +require_relative './spec_helper' +require_relative '../scrabble' + +describe Scrabble::Scoring do + # make sure there is Scoring class + it "Is there a class? Anything?" do + Scrabble::Scoring.wont_be_nil + end + + # make sure constant array is initialized (array of arrays) holding letter/amount info + it "Is the constant array of letter/amount correct?" + Scrabble::Scoring.LETTERS.length.must_equal(26) + end + + describe "Scrabble::Scoring#score" do + # check score method, should return SCORE of played word + it "WORD should generate the score of word being 8" do + Scrabble::Scoring.score("WORD").must_equal(8) + end + + # check that scoring on huge words words + it "QRSTLNE should generate the score of 66" do + Scrabble::Scoring.score("QRSTLNE").must_equal(66) + end + end + + describe "Scrabble::Scoring#highest_score_from" do + # returns word in played_words that has the highest score + it "returns the word in array with the highest score => ZEBRA " do + Scrabble::Scoring.highest_score_from(["WORD", "CAT", "MEOW", "ZEBRA"]).must_equal("ZEBRA") + end + + # checks that if a tie, word that was passed first in initial array wins + it "returns ZEBRE because it occurs before ZEBRA and both have 16 points" do + Scrabble::Scoring.highest_score_from(["WORD", "CAT", "ZEBRE", "ZEBRA"]).must_equal("ZEBRE") + end + + # if tie return word using 7 tiles + it "returns QRSTLNE because it has 7 letters and is tied for 16 pts" do + Scrabble::Scoring.highest_score_from(["WORD", "QRSTLNE", "ZEBRE", "ZEBRA"]).must_equal("QRSTLNE") + end + + # if tied return word with shortest length + it "returns ZEBRA because it has the shortest length -- both 16 points" do + Scrabble::Scoring.highest_score_from(["ZEDREE", "ZEBRA"]).must_equal("ZEBRA") + end + end +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 00000000..6d18d736 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,10 @@ +require 'simplecov' +SimpleCov.start + +require 'minitest' +require 'minitest/spec' +require 'minitest/autorun' # run all tests +require 'minitest/reporters' # bring in extra reports from minireporters gem + +# give us some really pretty output +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new diff --git a/specs/tilebag_spec.rb b/specs/tilebag_spec.rb new file mode 100644 index 00000000..34b218e4 --- /dev/null +++ b/specs/tilebag_spec.rb @@ -0,0 +1,35 @@ +require_relative './spec_helper' +require_relative '../scrabble' + +describe Scrabble::TileBag do + # make sure there is a TileBag class + it "Is there a class? Anything?" do + Scrabble::TileBag.wont_be_nil + end + + # make sure a new instance creates a tilebag correctly + it "should return 26 == number of letters in the alphabet" do + game = Scrabble::TileBag.new + game.tilebag.length.must_equal(26) + end + + # make sure draw_tiles method words correctly with passed parameter + it "should return length of selected tiles" do + game = Scrabble::TileBag.new + game.draw_tiles(5).length.must_equal(5) + end + + # check tiles_remaining corresponds to draw_tiles + it "should return number of tiles left in bag" do + game = Scrabble::TileBag.new + game.draw_tiles(40) + game.tiles_remaining.must_equal(58) + end + + # make sure tiles_remaining is zero when all tiles are removed + it "should return 0" do + game = Scrabble::TileBag.new + game.draw_tiles(99) + game.tiles_remaining.must_equal(0) + end +end