🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of DanOlson

DanOlson's solution

to Scrabble Score in the Ruby Track

Published at Apr 01 2020 · 0 comments
Instructions
Test suite
Solution

Given a word, compute the scrabble score for that word.

Letter Values

You'll need these:

Letter                           Value
A, E, I, O, U, L, N, R, S, T       1
D, G                               2
B, C, M, P                         3
F, H, V, W, Y                      4
K                                  5
J, X                               8
Q, Z                               10

Examples

"cabbage" should be scored as worth 14 points:

  • 3 points for C
  • 1 point for A, twice
  • 3 points for B, twice
  • 2 points for G
  • 1 point for E

And to total:

  • 3 + 2*1 + 2*3 + 2 + 1
  • = 3 + 2 + 6 + 3
  • = 5 + 9
  • = 14

Extensions

  • You can play a double or a triple letter.
  • You can play a double or a triple word.

For installation and learning resources, refer to the Ruby resources page.

For running the tests provided, you will need the Minitest gem. Open a terminal window and run the following command to install minitest:

gem install minitest

If you would like color output, you can require 'minitest/pride' in the test file, or note the alternative instruction, below, for running the test file.

Run the tests from the exercise directory using the following command:

ruby scrabble_score_test.rb

To include color from the command line:

ruby -r minitest/pride scrabble_score_test.rb

Source

Inspired by the Extreme Startup game https://github.com/rchatley/extreme_startup

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

scrabble_score_test.rb

require 'minitest/autorun'
require_relative 'scrabble_score'

class ScrabbleTest < Minitest::Test
  def test_empty_word_scores_zero
    assert_equal 0, Scrabble.new('').score
  end

  def test_whitespace_scores_zero
    skip
    assert_equal 0, Scrabble.new(" \t\n").score
  end

  def test_nil_scores_zero
    skip
    assert_equal 0, Scrabble.new(nil).score
  end

  def test_scores_very_short_word
    skip
    assert_equal 1, Scrabble.new('a').score
  end

  def test_scores_other_very_short_word
    skip
    assert_equal 4, Scrabble.new('f').score
  end

  def test_simple_word_scores_the_number_of_letters
    skip
    assert_equal 6, Scrabble.new('street').score
  end

  def test_complicated_word_scores_more
    skip
    assert_equal 22, Scrabble.new('quirky').score
  end

  def test_scores_are_case_insensitive
    skip
    assert_equal 41, Scrabble.new('OXYPHENBUTAZONE').score
  end

  def test_convenient_scoring
    skip
    assert_equal 13, Scrabble.score('alacrity')
  end
end
class Scrabble
  class Letter
    attr_reader :character, :value

    def initialize(character, value)
      @character = character
      @value = value
      @multiplier = 1
    end

    def double
      @multiplier = 2
    end

    def triple
      @multiplier = 3
    end

    def multiplied?
      @multiplier > 1
    end

    def score
      value * @multiplier
    end
  end

  class Score
    class Configuration
      def initialize
        @scores_by_letter = {}
      end

      def apply(value:, to:)
        Array(to).each do |letter|
          @scores_by_letter[letter.downcase] = value
        end
      end

      def value_of(letter)
        @scores_by_letter.fetch(letter.downcase, 0)
      end
    end

    class << self
      attr_reader :configuration

      def for(letters)
        new(letters)
      end

      def configure
        config = Configuration.new
        yield config
        @configuration = config
      end
    end

    def initialize(letters, multiplier: 1)
      @letters = init_letters(letters)
      @multiplier = multiplier
    end

    def to_s
      "#<Scrabble::Score:#{to_int} @letters=#{@letters}, @multiplier=#{@multiplier}>"
    end

    def inspect
      to_s
    end

    def ==(other)
      Integer(other) == Integer(self)
    end

    def to_int
      @letters.sum(&:score) * @multiplier
    end

    def double_letter(*doubled_letters)
      letters_matching(doubled_letters).each &:double
      self
    end

    def triple_letter(*tripled_letters)
      letters_matching(tripled_letters).each &:triple
      self
    end

    def double_word
      @multiplier = 2
      self
    end

    def triple_word
      @multiplier = 3
      self
    end

    private

    def init_letters(characters)
      characters.map do |char|
        value = self.class.configuration.value_of(char)
        Letter.new(char, value)
      end
    end

    def letters_matching(multiplier_candidates)
      Enumerator.new do |y|
        multiplier_candidates.flat_map { |l| l.split('') }.each do |l|
          letter = find_multiplier_candidate(l) and y << letter
        end
      end
    end

    def find_multiplier_candidate(character)
      @letters.find do |letter|
        letter.character == character && !letter.multiplied?
      end
    end
  end

  Score.configure do |config|
    config.apply value: 1, to: %w(A E I O U L N R S T)
    config.apply value: 2, to: %w(D G)
    config.apply value: 3, to: %w(B C M P)
    config.apply value: 4, to: %w(F H V W Y)
    config.apply value: 5, to: 'K'
    config.apply value: 8, to: %w(J X)
    config.apply value: 10, to: %w(Q Z)
  end

  class << self
    def score(word)
      new(word).score
    end
  end

  def initialize(word)
    @word = word
  end

  def score
    Score.for(letters)
  end

  private

  def letters
    String(@word).scan(/[a-zA-Z]/)
  end
end

if defined?(Minitest)
  class AdditionalScrabbleTests < Minitest::Test
    def test_double_letter_score
      assert_equal 13, Scrabble.new('barf').score.double_letter('f')
    end

    def test_double_letter_score_banana_with_b_and_one_a
      assert_equal 12, Scrabble.new('banana').score.double_letter('b', 'a')
    end

    def test_double_letter_score_banana_with_b_and_two_as
      assert_equal 13, Scrabble.new('banana').score.double_letter('b', 'a', 'a')
    end

    def test_multiple_double_letter_scores
      assert_equal 16, Scrabble.new('barf').score.double_letter('bf')
    end

    def test_multiple_double_letter_scores_varargs
      assert_equal 16, Scrabble.new('barf').score.double_letter('f', 'b')
    end

    def test_repeated_calls_to_double_letter_score
      assert_equal 16, Scrabble.new('barf').score.double_letter('f').double_letter('b')
    end

    def test_double_letter_score_ignores_letter_not_in_word
      assert_equal 9, Scrabble.new('barf').score.double_letter('z')
    end

    def test_double_letter_score_ignores_invalid_input
      assert_equal 9, Scrabble.new('barf').score.double_letter('pico')
    end

    def test_double_letter_score_ignores_invalid_portion_of_input
      assert_equal 13, Scrabble.new('barf').score.double_letter('foo')
    end

    def test_double_letter_score_ignores_invalid_portion_of_input_varargs
      assert_equal 13, Scrabble.new('barf').score.double_letter('f', 'o', 'o')
    end

    def test_triple_letter_score
      assert_equal 15, Scrabble.new('barf').score.triple_letter('b')
    end

    def test_multiple_triple_letter_scores
      assert_equal 17, Scrabble.new('barf').score.triple_letter('b', 'a')
    end

    def test_repeated_calls_to_triple_letter_score
      assert_equal 17, Scrabble.new('barf').score.triple_letter('b').triple_letter('a')
    end

    def test_triple_letter_score_ignores_letter_not_in_word
      assert_equal 9, Scrabble.new('barf').score.triple_letter('z')
    end

    def test_triple_letter_score_ignores_invalid_input
      assert_equal 9, Scrabble.new('barf').score.triple_letter('pico')
    end

    def test_triple_letter_score_ignores_invalid_portion_of_input
      assert_equal 17, Scrabble.new('barf').score.triple_letter('foo')
    end

    def test_triple_letter_score_ignores_invalid_portion_of_input_varargs
      assert_equal 17, Scrabble.new('barf').score.triple_letter('f', 'o', 'o')
    end

    def test_double_and_triple_letter_scores
      assert_equal 20, Scrabble.new('barf').score.double_letter('b').triple_letter('f')
    end

    def test_double_word_score_with_simple_word
      assert_equal 12, Scrabble.new('street').score.double_word
    end

    def test_double_word_score_with_double_letter_score
      assert_equal 64, Scrabble.new('quiz').score.double_word.double_letter('z')
    end

    def test_double_word_score_with_triple_letter_score
      assert_equal 48, Scrabble.new('quiz').score.double_word.triple_letter('u')
    end

    def test_double_word_score_with_double_and_triple_letter_scores
      assert_equal 68, Scrabble.new('quiz').score.double_word.double_letter('z').triple_letter('u')
    end

    def test_double_and_triple_letter_scores_with_double_word_score
      assert_equal 68, Scrabble.new('quiz').score.double_letter('z').triple_letter('u').double_word
    end

    def test_triple_word_score_with_simple_word
      assert_equal 18, Scrabble.new('street').score.triple_word
    end

    def test_triple_word_score_with_double_letter_score
      assert_equal 105, Scrabble.new('pizza').score.triple_word.double_letter('z')
    end

    def test_triple_word_score_with_triple_letter_score
      assert_equal 135, Scrabble.new('pizza').score.triple_word.triple_letter('z')
    end

    def test_triple_word_score_with_double_and_triple_letter_scores
      assert_equal 144, Scrabble.new('pizza').score.triple_word.double_letter('p').triple_letter('z')
    end

    def test_double_and_triple_letter_scores_with_triple_word_score
      assert_equal 144, Scrabble.new('pizza').score.double_letter('p').triple_letter('z').triple_word
    end
  end
end

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?