diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..82e5a661 --- /dev/null +++ b/Rakefile @@ -0,0 +1,7 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.test_files = FileList['specs/*_spec.rb'] + end + +task default: :test diff --git a/player.rb b/player.rb new file mode 100644 index 00000000..19a769d9 --- /dev/null +++ b/player.rb @@ -0,0 +1,72 @@ +require_relative 'scoring' + +module Scrabble + class Player + MAX_TILES = 7 + + attr_reader :name, :plays, :tiles + + def initialize(name) + @name = name + @plays = [] + @tiles = [] + +# if @tiles.length > MAX_TILES +# raise ArgumentError.new("Too many tiles") +# end + end + + def play(word) + if won? + return false + else + @plays << word + word_score = Scrabble::Scoring.score(word) + return word_score + end + end + + def total_score + score_sum = 0 + @plays.each do |word| + score = Scrabble::Scoring.score(word) + score_sum += score + end + return score_sum + end + + def won? + total_score > 100 + end + + def highest_scoring_word + if @plays.empty? + return "NO WORDS PLAYED" + else + highest = Scrabble::Scoring.highest_score_from(@plays) + return highest + end + end + + def highest_word_score + if @plays.empty? + return 0 + else + highest_score = Scrabble::Scoring.score(highest_scoring_word) + return highest_score + end + end + + def draw_tiles(tile_bag) + difference = MAX_TILES - @tiles.length + holding = [] + if @tiles.length < MAX_TILES + holding = tile_bag.draw_tiles(difference) + end + + holding.each do |letter| + @tiles << letter + end + end + end +end diff --git a/scoring.rb b/scoring.rb new file mode 100644 index 00000000..56874be8 --- /dev/null +++ b/scoring.rb @@ -0,0 +1,73 @@ +module Scrabble + class Scoring + LETTERS = { + 1 => %w(A E I O U L N R S T), + 2 => %w(D G), + 3 => %w(B C M P), + 4 => %w(F H V W Y), + 5 => %w(K), + 8 => %w(J X), + 10 => %w(Q Z) + } + + def self.score(word) + score = 0 + user_word = word.upcase.split("") + valid_letters = [] + + LETTERS.values.each do |array| + array.each do |letter| + valid_letters << letter + end + end + + user_word.each do |letter| + if !valid_letters.include?(letter) + raise ArgumentError.new("Invalid characters") + end + end + + if word.length < 1 || word.length > 7 + raise ArgumentError.new("Invalid word length") + end + + user_word.each do |letter| + LETTERS.each do |key, value| + if value.include?(letter) + score += key + end + end + end + if user_word.length == 7 + score += 50 + end + return score + end + + def self.highest_score_from(array_of_words) + scoring_hash = {} + max_words = [] + + raise ArgumentError.new("Invalid input type") if !array_of_words.is_a?(Array) + + array_of_words.each do |word| + scoring_hash[word] = Scrabble::Scoring.score(word) + end + + max_val = scoring_hash.values.max + scoring_hash.each do |key, value| + if value == max_val + max_words << key + end + end + + max_words.sort_by! {|word| word.length} + + if max_words.last.length == 7 + return max_words.last + else + return max_words.first + end + end + end +end diff --git a/specs/player_spec.rb b/specs/player_spec.rb new file mode 100644 index 00000000..697503ca --- /dev/null +++ b/specs/player_spec.rb @@ -0,0 +1,154 @@ +require_relative 'spec_helper' +require_relative '../player' +require_relative '../tilebag' + +describe Scrabble::Player do + + describe "#initialize" do + jane = Scrabble::Player.new("Jane") + + it "should create an instance of Player" do + jane.must_be_instance_of(Scrabble::Player) + end + + it "should be able to return player's name" do + jane.must_respond_to(:name) + end + + it "must be able to return player's plays" do + jane.must_respond_to(:plays) + end + + it "must be able to return player's tiles" do + jane.must_respond_to(:tiles) + end + +# it "must not have more than 7 tiles" do +# kelly = Scrabble::Player.new("Kelly") +# proc { kelly.tiles = ["a", "b", "c", "d", "e", "f", "g", "h"] }.must_raise(ArgumentError) +# end + + end + + describe "#play(word)" do + + it "should add player's words to @plays array" do + john = Scrabble::Player.new("John") + played_words = ["hex", "bats", "kittys"] + + played_words.each do |word| + john.play(word) + end + john.plays.length.must_equal(3) + end + + it "should return score of word" do + john = Scrabble::Player.new("John") + john.play("fox").must_equal(13) + end + + it "should return false if player has won" do + john = Scrabble::Player.new("John") + winning_array = ["staring", "zzzzzx"] + + winning_array.each do |word| + john.play(word) + end + + john.play("fox").must_equal(false) + end + end + + describe "#total_score" do + + it "should return sum of all words' scores" do + john = Scrabble::Player.new("John") + animals = ["kittys", "fox", "cat", "dog"] + + animals.each do |word| + john.play(word) + end + john.total_score.must_equal(36) + end + + it "should return 0 if no words have been played" do + john = Scrabble::Player.new("John") + john.total_score.must_equal(0) + + end + end + + describe "#won?" do + + it "should return true if score is > 100" do + john = Scrabble::Player.new("John") + winning_array = ["staring", "zzzzzx"] + + winning_array.each do |word| + john.play(word) + end + + john.won?.must_equal(true) + end + + it "should return false if empty" do + john = Scrabble::Player.new("John") + john.won?.must_equal(false) + end + + it "should return false if not winning" do + john = Scrabble::Player.new("John") + losing_array = ["cat", "dog"] + + losing_array.each do |word| + john.play(word) + end + john.won?.must_equal(false) + end + end + + describe "#highest_scoring_word" do + + it "should return the highest scoring word played" do + john = Scrabble::Player.new("John") + animals = ["kittys", "fox", "cat", "dog"] # => "fox" + + animals.each do |word| + john.play(word) + end + + john.highest_scoring_word.must_equal("fox") + end + + it "should return 'NO WORDS PLAYED' if no words have been played" do + john = Scrabble::Player.new("John") + john.highest_scoring_word.must_equal("NO WORDS PLAYED") + end + + end + + describe "#highest_word_score" do + it "must return the numerical score of highest_scoring_word" do + john = Scrabble::Player.new("John") + animals = ["kittys", "fox", "cat", "dog"] + animals.each do |word| + john.play(word) + end + john.highest_word_score.must_equal(13) + end + + it "should return 0 if no words in array" do + john = Scrabble::Player.new("John") + john.highest_word_score.must_equal(0) + end + end + + describe "draw_tiles(tile_bag)" do + it "must fill tile array until tiles is seven letters" do + noelle = Scrabble::Player.new("Noelle") + tile_bag = Scrabble::TileBag.new + noelle.draw_tiles(tile_bag) + noelle.tiles.length.must_equal(7) + end + end +end diff --git a/specs/scoring_spec.rb b/specs/scoring_spec.rb new file mode 100644 index 00000000..2372cb65 --- /dev/null +++ b/specs/scoring_spec.rb @@ -0,0 +1,66 @@ +require_relative 'spec_helper' +require_relative '../scoring' + +describe Scrabble::Scoring do + it "should have a constant equal to a hash" do + Scrabble::Scoring::LETTERS.must_be_instance_of(Hash) + end + + describe "self.score" do + it "must correctly score known words" do + known_words = { + "farm" => 9, # 4,1,1,3 + "cat" => 5, # 3,1,1 + "ferrets" => 60, # 50 + 4,1,1,1,1,1,1 + } + + known_words.each do |word, expected_score| + Scrabble::Scoring.score(word).must_equal(expected_score) + end + end + + it "should raise an error if there are any non-letter characters in a word" do + invalid_chars = ["1de", "cat! 2", "com,ma"] + invalid_chars.each do |word| + proc { Scrabble::Scoring.score(word) }.must_raise(ArgumentError) + end + end + + it "must not have an empty string argument" do + proc { Scrabble::Scoring.score("") }.must_raise(ArgumentError) + end + + it "must not take an argument greater than 7 letters" do + proc {Scrabble::Scoring.score("caterpillar")}.must_raise(ArgumentError) + end + end + + describe "self.highest_score_from" do + + it "should raise an error when non-array is passed in" do + proc{ Scrabble::Scoring.highest_score_from({}) }.must_raise(ArgumentError) + end + + it "must return the word with the highest score" do + highest_score_array = ["farm", "cat", "ferrets"] + Scrabble::Scoring.highest_score_from(highest_score_array).must_equal("ferrets") + end + + it "should return the word with the fewest letters in case of tie" do + tied_scores = ["zoos", "hex", "kittys"] + Scrabble::Scoring.highest_score_from(tied_scores).must_equal("hex") + end + + it "should return a seven-letter word in case of a tie" do + tied_scores_seven = ["staring", "zzzzzx", "cat"] + Scrabble::Scoring.highest_score_from(tied_scores_seven).must_equal("staring") + end + + it "should return the first word if all winning words are the same length" do + same_len_tied = ["hex", "fox", "kittys", "zoos", "wix"] + Scrabble::Scoring.highest_score_from(same_len_tied).must_equal("hex") + end + + end + +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 00000000..ef5c926d --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,10 @@ +require 'simplecov' +SimpleCov.start + +require 'minitest' +require 'minitest/spec' +require "minitest/autorun" +require "minitest/reporters" +require 'minitest/pride' + +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..f4611ed8 --- /dev/null +++ b/specs/tilebag_spec.rb @@ -0,0 +1,80 @@ +require_relative 'spec_helper' +require_relative '../tilebag' + +describe Scrabble::TileBag do + describe "#initialize" do + + it "must create an instance of TileBag" do + new_game = Scrabble::TileBag.new + new_game.must_be_instance_of(Scrabble::TileBag) + end + + it "must be initialized with the proper distribution of letters" do + new_game = Scrabble::TileBag.new + tiles = { + "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 + } + + tiles.each do |key, value| + new_game.tiles[key].must_equal(value) + end + end + end + + describe "#draw_tiles(num)" do + it "should raise an error when a non-Fixnum is passed in" do + new_game = Scrabble::TileBag.new + bad_args = ["a", 1.2, "2a", "2", "!"] + + bad_args.each do |item| + proc { new_game.draw_tiles(item) }.must_raise(ArgumentError) + end + end + + it "should return an array containing num tiles" do + new_game = Scrabble::TileBag.new + new_game.draw_tiles(3).length.must_equal(3) + end + + it "should not pick a tile[key] that has tile[value] = 0" do + new_game = Scrabble::TileBag.new + new_game.draw_tiles(98) #98 is total num of tiles + all_tiles = new_game.tiles.values.reduce(:+) + all_tiles.must_equal(0) + end + + it "should decrease the num of available tiles inside tiles hash" do + new_game = Scrabble::TileBag.new + original_values = new_game.tiles.values.clone + new_game.draw_tiles(1) + new_game.tiles.values.wont_equal(original_values) + end + + it "should return remaining tiles if num is greater than remaining tiles" do + new_game = Scrabble::TileBag.new + new_game.draw_tiles(96) + new_game.draw_tiles(3).length.must_equal(2) # Only two tiles left + end + end + + describe "#tiles_remaining" do + + it "must return the numbers of tiles left in tilebag" do + another_game = Scrabble::TileBag.new + another_game.draw_tiles(2) + another_game.tiles_remaining.must_equal(96) + end + end +end diff --git a/tilebag.rb b/tilebag.rb new file mode 100644 index 00000000..6757722e --- /dev/null +++ b/tilebag.rb @@ -0,0 +1,49 @@ +module Scrabble + class TileBag + attr_reader :tiles + attr_accessor :available_tiles + + def initialize + @tiles = { + "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 + } + + end + + def draw_tiles(num) + raise ArgumentError.new("Invalid argument type") if !num.is_a?(Fixnum) + + pick = [] + if num > tiles_remaining + num = tiles_remaining + end + until pick.length == num + tile = @tiles.keys.sample + until @tiles[tile] != 0 + tile = @tiles.keys.sample + end + pick << tile + @tiles[tile] -= 1 + end + + return pick + end + + def tiles_remaining + remaining_val = @tiles.values.reduce(:+) + return remaining_val + end + end +end