### Daily Challenge #8 - Scrabble Word Calculator

#### dev.to staff on July 05, 2019

Everyone loves a game of Scrabble! Your challenge today is to calculate the scrabble score of a given word. Scoring per tile: To make things e... [Read Full] Here's my solution in Perl, along with a few tests.

``````#!/usr/bin/env perl

use v5.24;
use strict;
use warnings;
use feature qw(signatures);
no warnings "experimental::signatures";
use List::Util qw(sum);
use Carp;

my %scores = (
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,
);

my %multiplier = ( D => 2, T => 3 );

sub scrabble_score (\$word) {
\$word = uc(\$word);
my \$score = 0;
my \$mult  = 1;
my \$count = 0;

if ( \$word =~ s/\((D|T)\)// ) {
\$mult = \$multiplier{\$1};
}

while ( \$word =~ s/([[:alpha:]])(\^|\*{0,2})// ) {
++\$count;
\$score += ( \$2 ne '^' ) * ( \$scores{\$1} + \$scores{\$1} * length(\$2) );
}

return ( \$score * \$mult ) + 50*(\$count == 7);
}

use Test::More tests => 7;

my \$word = "quintessential";
is( scrabble_score(\$word), 23, "Score for \$word is 23" );
\$word = "he*llo**";
is( scrabble_score(\$word), 11, "Score for \$word is 11" );
\$word = "quintessential(t)";
is( scrabble_score(\$word), 69, "Score for \$word is 69" );
\$word = "q^uintessential(t)";
is( scrabble_score(\$word), 39, "Score for \$word is 39" );
\$word = "he*llo**(d)";
is( scrabble_score(\$word), 22, "Score for \$word is 22" );
\$word = "he^llo**(d)";
is( scrabble_score(\$word), 18, "Score for \$word is 18" );
\$word = "wordier(d)";
is( scrabble_score(\$word), 72, "Score for \$word is 72" );
``````

Edit: fixed a bug, stray print, and added the 7 letter bonus

Erlang:

``````-module(devto8).
-export([scrabble_score/1]).

scrabble_score(Input) ->
LS = #{\$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},
LCInput = string:lowercase(Input),
score_word(LS, LCInput, []).

score_word(_, [], Scores) when length(Scores) == 7 ->
score_sum(Scores) + 50;
score_word(_, [\$(,\$t,\$)], Scores) when length(Scores) == 7 ->
score_sum(Scores) * 3 + 50;
score_word(_, [\$(,\$d,\$)], Scores) when length(Scores) == 7 ->
score_sum(Scores) * 2 + 50;
score_word(_, [\$(,\$t,\$)], Scores) ->
score_sum(Scores) * 3;
score_word(_, [\$(,\$d,\$)], Scores) ->
score_sum(Scores) * 2;
score_word(_, [], Scores) ->
score_sum(Scores);
score_word(LS, [\$^, _ | Rest], Scores) ->
score_word(LS, Rest, Scores ++ 0);
score_word(LS, [Letter, \$*, \$* | Rest], Scores) ->
score_word(LS, Rest, Scores ++ [maps:get(Letter,LS) * 3]);
score_word(LS, [Letter, \$* | Rest], Scores) ->
score_word(LS, Rest, Scores ++ [maps:get(Letter,LS) * 2]);
score_word(LS, [Letter | Rest], Scores) ->
score_word(LS, Rest, Scores ++ [ maps:get(Letter, LS)]).

score_sum(Scores) ->
lists:foldl(fun(X, Sum) -> X + Sum end, 0, Scores).
``````
``````devto8:scrabble_score("z**z**z**z**z**z**z**(t)").
680

devto8:scrabble_score("thiswasfun").
19
``````

JavaScript

``````const scrabbleWordValue = word => {

// scrabble letter values
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 };

// Pre-requesites - check that the string fulfills a good pattern:
//   - followed by have any combination of letters and asterisks
//   - optionally at the end it can have a (t) or (d) modifier
// if it doesn't follow that pattern (invalid or empty), return 0 points
if(!word.match(/^([a-z][\*]{0,2})+(\([t|d]\))?\$/gi)) {
return 0;
}

// word modifier - calculate the multiplier that will apply to the word:
//   - 1 (default): the word will not change value
//   - 2 (if it ends with '(d)'): the word will be multiplied by 2
//   - 3 (if it ends with '(t)'): the word will be multiplied by 3
// after calculating the word multiplier, update the word id applicable.
let wordModifier = 1;
if (word.indexOf("(") > 0) {
wordModifier = word.indexOf("(t)") > 0 ? 3 : 2;
word = word.slice(0,-3);
}

// calculate the addition of all letters
//   - if the letter is an asterisk, use the previous letter
//   - update the previous letter to current letter
//   - add the value of the letter to the total
let previous = 0;
let valueWord = word.split('')
.reduce((acc, curr) => {
if (curr == '*') curr = previous;
previous = curr;
return letterValues[curr] + acc;
}, 0);

// if the words has seven letters, add 50 extra points!
const allLettersBonus = word.replace(/\*/g,'').length == 7 ? 50 : 0;

// return the value of the word considering all modifiers
return valueWord * wordModifier + allLettersBonus;
}
``````

Live demo on CodePen.

Good old C. Be advised though there are ways to bork this one with some inputs not consistent with the rubric.

``````int score(char *word) {
// Digits are one less than score values for each letter
// This maps 1-10 to 0-9, allowing one-character representation of each score
char *letter_scores = "02210313074020029000033739";
int score = 0, letter_count = 0, mult=1;
for(char *ptr = word; *ptr; ptr++) {
if (isalpha(*ptr)) {
letter_count++;
int letter_score = letter_scores[toupper(*ptr)-'A']-'0'+1;
score += letter_score;
if (*(ptr+1) == '*') {
score += letter_score;
if (*(ptr+2) == '*') {
score += letter_score;
}
}
}
if (*ptr == '(') {
mult = *(ptr+1) == 'd' ? 2 : 3;
break;
}
}
return score*mult+50*(letter_count == 7);
}
``````

Because these are fun in languages you don't actually know, here's Haskell:

``````import Data.Map (Map, (!))
import qualified Data.Map as Map

scores :: Map Char Int
scores = Map.fromList pairs
where
pairs = [
('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)]

scoreWord :: String -> Int
scoreWord w =
let
sevenLetterBonus = if (length \$ stripMarkers w) == 7 then 50 else 0
wordMultiplier =
let
suffix = dropWhile (/= '(') w
in
if length suffix > 0 then
case suffix !! 1 of
't' -> 3
'd' -> 2
_ -> 1
else 1
--
preparedWord = expandMarkers \$ takeWhile (/= '(') w
rawScore = sum \$ scoreLetters \$ preparedWord
in
rawScore * wordMultiplier + sevenLetterBonus
where
scoreLetters cs = map (\c -> scores ! c) cs

-- transform doubles, triples, carats
-- if we hit an asterisk, replace it with the previous letter
-- if we hit a carat, drop the previus letter
expandMarkers :: String -> String
expandMarkers [] = []
expandMarkers (c:[]) = [c]
expandMarkers (c:rest) =
'*' ->
if (head \$ tail rest) == '*' then
[c] ++ [c] ++ [c] ++ (expandMarkers \$ drop 2 rest) else
[c] ++ [c] ++ (expandMarkers \$ tail rest)
'^' -> expandMarkers \$ tail rest
_ -> [c] ++ expandMarkers rest

-- remove suffix and all markers for deciding on the 7-letter bonus
stripMarkers :: String -> String
stripMarkers w = filter (\c -> c /= '*' && c /= '^') \$ takeWhile (/= '(') w
``````

I didn't provide tests, but I think it works. Maybe I'll write some later on.

Ruby solution

``````require "minitest/autorun"

class ScrabbleLetter
SCORES = {
'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
}.freeze

# Parses a string of scrabble letters and separates
# them with their multiplier still intact.
#
# @example
#
#   ScrabbleLetter.parse("h^i**")
#   # => [#<ScrabbleLetter @letter="h^">, #<ScrabbleLetter @letter="i**">]
#
def self.parse string
string.chars.each_with_object([]) do |char, letters|
SCORES[char] ? letters << char : letters[-1] << char
end.map { |letter| new letter }
end

def initialize letter
@letter = letter
end

def score
letter_score * multiplier
end

private

def letter_score
SCORES[@letter.chr]
end

def multiplier
return 0 if @letter.end_with?('^')
return 3 if @letter.end_with?('**')
return 2 if @letter.end_with?('*')
return 1
end
end

class ScrabbleWord
DOUBLE_WORD_TOKEN = '(d)'.freeze
TRIPLE_WORD_TOKEN = '(t)'.freeze

def initialize word
@word = word
end

def score
letters_score * multiplier + length_bonus
end

private

def letters_score
letters.map(&:score).reduce(:+)
end

def multiplier
return 2 if @word.end_with?(DOUBLE_WORD_TOKEN)
return 3 if @word.end_with?(TRIPLE_WORD_TOKEN)
return 1
end

def length_bonus
letters.count == 7 ? 50 : 0
end

def letters
ScrabbleLetter.parse word_without_multiplier
end

def word_without_multiplier
@word
.gsub(DOUBLE_WORD_TOKEN, "")
.gsub(TRIPLE_WORD_TOKEN, "")
end
end

class ScrabbleWordTest < MiniTest::Test
def test_simple_word
assert_equal 23, ScrabbleWord.new("quintessential").score
end

def test_double_and_triple_letters
assert_equal 11, ScrabbleWord.new("he*llo**").score
end

def test_triple_word
assert_equal 69, ScrabbleWord.new("quintessential(t)").score
end

def test_blank_tile_with_triple_word
assert_equal 39, ScrabbleWord.new("q^uintessential(t)").score
end

def test_double_and_triple_letters_with_double_word
assert_equal 22, ScrabbleWord.new("he*llo**(d)").score
end

def test_blank_tile_with_double_letter_and_double_word
assert_equal 18, ScrabbleWord.new("he^llo**(d)").score
end

def test_seven_letter_word_bonus
assert_equal 72, ScrabbleWord.new("wordier(d)").score
end
end
``````

I borrowed your tests, @yzhernand . Thank you for writing them, so I didn't have to.

My overly complex (nim) solution :)

``````from strutils import toLower
from sequtils import filter

type WordType = enum value, multiply, global

type Word = object
Value: int
Type: WordType

# a..z
# [97, 122]
const score_mapping = [1, 3, 3, 2, 1, 4,
2, 4, 1, 8, 5, 1, 3,
1, 1, 3, 10, 1, 1,
1, 1, 4, 4, 8 , 4, 10]

# Build a stack, tokenizing the characters
# This will let us apply operations in a reverse order
proc buildStack(word: string): seq[ref Word] =
result = newSeq[ref Word]()
let lowerWord = toLower(word)

for i in 0..(len(lowerWord) - 1):
let currentWord = new(Word)
let letter = lowerWord[i]
let letterAsInt = int(letter)

if letterAsInt < 97 or letterAsInt > 122:
if letter == '*':
currentWord.Value = 2
currentWord.Type = WordType.multiply
if letter == '^':
currentWord.Value = 0
currentWord.Type = WordType.multiply
if letter == '(':
currentWord.Value = if lowerWord[i+1] == 't': 3 else: 2
currentWord.Type = WordType.global
result.add(currentWord) # we reached the end of the string
break
else:
let scoreMappingPosition = letterAsInt - 97

currentWord.Value = score_mapping[scoreMappingPosition]
currentWord.Type = WordType.value

# Gets the value of the operations
proc parseOperations(value: int, operations: seq[ref Word]): int =
var multiplier = 1

if len(operations) > 0:
for operation in operations:
if operation.Type == Wordtype.multiply:
if operation.Value == 0:
return 0
else:
multiplier += 1

result += value * multiplier

proc getScore(stack: seq[ref Word]): int =
var s = stack # make mutable
var operations: seq[ref Word]
var globalMultiplier = 1

if stack.filter(proc(p: ref Word): bool = p.Type == Wordtype.value).len >= 7:
result += 50

while len(s) > 0:
let item = s.pop()

if item.Type == WordType.value:
result += parseOperations(item.Value, operations)
operations = @[]
elif item.Type == Wordtype.global:
globalMultiplier = item.Value
else:

result *= globalMultiplier

echo buildStack("d**e*v^(d)").getScore()
``````

A few days late, but here's my JavaScript solution, using `reduce`.

``````function scoreWord(string) {
let word = string.toLowerCase();

const scores = {
a: 1,
// ... etc, cut for conciseness
z: 10,
q: 10
}

let multiplier = 1;

if(word.substring(word.length - 1) === '2') {
multiplier = 2;
word = word.substring(0, word.length - 1);
} else if(word.substring(word.length - 1) === '3') {
multiplier = 3;
word = word.substring(0, word.length - 1);
}

let bonus = word.split('').filter(char => {
return !['*', '^'].includes(char);
}).length >= 7 ? 50 : 0;

return word.split('').reduce((score, letter, index, letters) => {
const next = index + 1 < letters.length ? letters[index + 1] : null;
if('abcdefghijklmnopqrstuvwxyz'.includes(letter)) {
if(next && ['*', '^'].includes(next)) {
if(next === '^') {
return score += 0;
}
if(next === '*') {
if(index + 2 < letters.length && letters[index + 2] === '*') {
score += (scores[letter] * 3);
} else {
score += (scores[letter] * 2);
}
return score;
}
}
return score += scores[letter];
} else {
return score;
}
}, 0) * multiplier + bonus;
}
``````

I changed the rules a bit, since it's hard to distinguish between double/triple words and words that naturally end in d or t, I decided to use 2 or 3 instead.

``````import { pipe, toUpper } from "ramda";

// CHARACTERS, POINTS, BONUSES //////////////////////////////////////
const A = { char: "A", points: 1 } as const;
const B = { char: "B", points: 3 } as const;
const C = { char: "C", points: 3 } as const;
const D = { char: "D", points: 2 } as const;
const E = { char: "E", points: 1 } as const;
const F = { char: "F", points: 4 } as const;
const G = { char: "G", points: 2 } as const;
const H = { char: "H", points: 4 } as const;
const I = { char: "I", points: 1 } as const;
const J = { char: "J", points: 8 } as const;
const K = { char: "K", points: 5 } as const;
const L = { char: "L", points: 1 } as const;
const M = { char: "M", points: 3 } as const;
const N = { char: "N", points: 1 } as const;
const O = { char: "O", points: 1 } as const;
const P = { char: "P", points: 3 } as const;
const Q = { char: "Q", points: 10 } as const;
const R = { char: "R", points: 1 } as const;
const S = { char: "S", points: 1 } as const;
const T = { char: "T", points: 1 } as const;
const U = { char: "U", points: 1 } as const;
const V = { char: "V", points: 4 } as const;
const W = { char: "W", points: 4 } as const;
const X = { char: "X", points: 8 } as const;
const Y = { char: "Y", points: 4 } as const;
const Z = { char: "Z", points: 10 } as const;
// prettier-ignore
const charMap = {Q,W,E,R,T,Y,U,I,O,P,A,S,D,F,G,H,J,K,L,Z,X,C,V,B,N,M} as const;
const tripleWordBonus = "(T)";
const doubleWordBonus = "(D)";
const tripleLetterBonus = "**";
const doubleLetterBonus = "*";

// TYPES ////////////////////////////////////////////////////////////
type ValueOf<T> = T[keyof T];
type Char = ValueOf<typeof charMap>;
type LetterTile = { type: "LETTER" };
type BlankTile = { type: "BLANK" };
type Square = { letterBonus: 1 | 2 | 3; wordBonus: 1 | 2 | 3 };

type Tile = ((LetterTile) | BlankTile) & Char & Square;

// CONVERT STRING TO TILES //////////////////////////////////////////
const REGEX = /[A-Z](\*|\^|\((T|D)\))*/g; // https://bit.ly/31LZ0mZ
const parse = (input: string): Tile[] => {
const strings: string[] = input.match(REGEX) || [];
return strings.map(toTile);
};

const type = (input: string) => (input.includes("^") ? "BLANK" : "LETTER");
const letterAndPoints = (char: Tile["char"]) => charMap[char];
const letterBonus = (input: string) => {
if (input.includes(tripleLetterBonus)) return 3;
if (input.includes(doubleLetterBonus)) return 2;
return 1;
};
const wordBonus = (input: string) =>
input.includes(tripleWordBonus) ? 3 : input.includes(doubleWordBonus) ? 2 : 1;
const toTile = (input: string): Tile => {
const chars = input.split("");
const [char, ...rest] = chars;
const modifiers = rest.join("");
return {
type: type(modifiers),
...letterAndPoints(char),
letterBonus: letterBonus(modifiers),
wordBonus: wordBonus(modifiers)
};
};

// CALCULATE SCORE //////////////////////////////////////////////////
const letterPoints = (tiles: Tile[]) =>
tiles.reduce(
(result, { type, char, letterBonus }) =>
type === "BLANK" ? result : result + charMap[char].points * letterBonus,
0
);
const wordBonusMultiplier = (tiles: Tile[]) =>
tiles.reduce((result, { wordBonus }) => result * wordBonus, 1);
const BINGO = { length: 7, points: 50 };
const bingoPoints = (tiles: Tile[]) =>
tiles.length === BINGO.length ? BINGO.points : 0;

const scoreTiles = (tiles: Tile[]) =>
letterPoints(tiles) * wordBonusMultiplier(tiles) + bingoPoints(tiles);

export const getScore = pipe(
toUpper,
parse,
scoreTiles
);
``````

Clojure:

``````(ns scrabble
(:require [clojure.string :as s]))

(def values
(zipmap
[\a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z]
[ 1  3  3  2  1  4  2  4  1  8  5  1  3  1  1  3 10  1  1  1  1  4  4  8  4 10]))

(defn letter-score [[a b c]]
(let [base (values a 0)
mult (cond (= b \^)                0
(and (= b \*) (= c \*)) 3
(= b \*)                2
:else                   1)]
(* base mult)))

(defn trim [word]
(s/replace word #"\(\w\)\$" ""))

(defn base-word-score [word]
(->> (trim word)
(partition-all 3 1)
(map letter-score)
(apply +)))

(defn bonus [word]
(if (= 7 (count (s/replace (trim word) #"\W" ""))) 50 0))

(defn score [word]
(let [base (base-word-score word)
mult (cond (s/ends-with? word "(t)") 3
(s/ends-with? word "(d)") 2
:else                     1)]
(+ (bonus word) (* base mult))))
``````

I’m learning Erlang.

I had forgotten that you can have several elements to the Head in a pattern match (then I saw @stevemoon ’s solution), so I used `lists:foldr` to process the word from the end, which means I accumulate the multipliers and apply them once I encounter a letter. I could have named variables better, but with short names it’s easier on the eye, with this state tuple I’m moving around.

For fun I decided to allow several word multipliers, and to allow the blank indicator before, after, or in the middle of the asterisks.

I made my own tests, and stole other peoples’ as well.

``````-module( scrabble ).
-export( [ word_score/1, letter_score/1 ] ).

-include_lib("eunit/include/eunit.hrl").

letter_score( Letter ) ->
Points = #{
\$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
},
maps:get( Letter, Points ).

word_score( Word ) ->
{ Score, Wm, _Lm, Lc, _State } = lists:foldr(
fun char/2,
{ 0, 1, 1, 0, normal },
string:lowercase( Word )
),
Bonus = case Lc of 7 -> 50; _ -> 0 end,
Score * Wm + Bonus.

% Score
% Wm = Word multiplier
% Lm = Letter multiplier
% Lc = Letter count
% Parser state
char( \$\), { Score, Wm, Lm, Lc, normal } ) ->
{ Score, Wm, Lm, Lc, word_mult };
char( \$d, { Score, Wm, Lm, Lc, word_mult } ) ->
{ Score, 2 * Wm, Lm, Lc, word_mult };
char( \$t, { Score, Wm, Lm, Lc, word_mult } ) ->
{ Score, 3 * Wm, Lm, Lc, word_mult };
char( \$\(, { Score, Wm, Lm, Lc, word_mult } ) ->
{ Score, Wm, Lm, Lc, normal };
char( \$*, { Score, Wm, 0, Lc, normal } ) ->
{ Score, Wm, 0, Lc, normal };
char( \$*, { Score, Wm, 1, Lc, normal } ) ->
{ Score, Wm, 2, Lc, normal };
char( \$*, { Score, Wm, 2, Lc, normal } ) ->
{ Score, Wm, 3, Lc, normal };
char( \$^, { Score, Wm, _Lm, Lc, normal } ) ->
{ Score, Wm, 0, Lc, normal };
char( L, { Score, Wm, Lm, Lc, normal } ) ->
{ Score + Lm * letter_score( L ), Wm, 1, Lc + 1, normal }.

char_test() -> [
?assert( char( \$\), { 0, 1, 1, 0, normal } ) =:= { 0, 1, 1, 0, word_mult } ),
?assert( char( \$d, { 0, 1, 1, 0, word_mult } ) =:= { 0, 2, 1, 0, word_mult } ),
?assert( char( \$d, { 0, 2, 1, 0, word_mult } ) =:= { 0, 4, 1, 0, word_mult } ),
?assert( char( \$t, { 0, 2, 1, 0, word_mult } ) =:= { 0, 6, 1, 0, word_mult } ),
?assert( char( \$\(, { 0, 3, 1, 0, word_mult } ) =:= { 0, 3, 1, 0, normal } ),
?assert( char( \$a, { 0, 1, 1, 0, normal } ) =:= { 1, 1, 1, 1, normal } ),
?assert( char( \$b, { 1, 1, 1, 1, normal } ) =:= { 4, 1, 1, 2, normal } ),
?assert( char( \$*, { 4, 1, 1, 2, normal } ) =:= { 4, 1, 2, 2, normal } ),
?assert( char( \$*, { 4, 1, 2, 2, normal } ) =:= { 4, 1, 3, 2, normal } ),
?assert( char( \$^, { 4, 1, 1, 2, normal } ) =:= { 4, 1, 0, 2, normal } ),
?assert( char( \$*, { 4, 1, 0, 2, normal } ) =:= { 4, 1, 0, 2, normal } )
].

score_test() -> [
1 = word_score("A"),
2 = word_score("A(d)"),
3 = word_score("A(t)"),
6 = word_score("A(dt)"),
6 = word_score("A(td)"),
12 = word_score("A(ddt)"),
12 = word_score("A(dtd)"),
18 = word_score("A(ttd)"),
4 = word_score("AB"),
5 = word_score("A*B"),
6 = word_score("A**B"),
14 = word_score("SCRABBLE"),
28 = word_score("SCRABBLE(d)"),
42 = word_score("SCRABBLE(t)"),
21 = word_score("F**OX"),
38 = word_score("F**O*X**"),
63 = word_score("PROBLEM"),
77 = word_score("PR*OB**LE*M**"),
12 = word_score("ZER^O"),
12 = word_score("ZER*^O"),
12 = word_score("ZER**^O"),
12 = word_score("ZER^*O"),
12 = word_score("ZER^**O"),
12 = word_score("ZER*^*O"),
68 = word_score("P**RO*B^LE*M"),

23 = word_score("QUINTESSENTIAL"),
11 = word_score("HE*LLO**"),
69 = word_score("QUINTESSENTIAL(t)"),
39 = word_score("Q^UINTESSENTIAL(t)"),
22 = word_score("HE*LLO**(d)"),
18 = word_score("HE^LLO**(d)"),
72 = word_score("WORDIER(d)"),

680 = word_score("Z**Z**Z**Z**Z**Z**Z**(t)"),
19 = word_score("THISWASFUN")
].
``````

To run:

``````% erl
1> c(scrabble).
{ok,scrabble}
2> scrabble:test().
2 tests passed.
ok
``````

Elixir. Admittedly my Exercism solution with a bunch of String.replace calls to handle the more sophisticated way the problem is stated here.

``````defmodule Scrabble do
@spec slw?(String.t()) :: boolean
def slw?(word) do
word
|> String.replace(~r/\(.*/, "")
|> String.replace("*", "")
|> String.length
|> Kernel.==(7)
end

@spec score(String.t()) :: non_neg_integer
def score(word) do
word
|> String.replace(~r/(.)\*\*/, "\\1\\1\\1")
|> String.replace(~r/(.)\*/, "\\1\\1")
|> String.replace(~r/^(.*)\(t\)\$/, "\\1\\1\\1")
|> String.replace(~r/^(.*)\(d\)\$/, "\\1\\1")
|> String.downcase
|> String.to_charlist
|> Enum.reduce(if(slw?(word), do: 50, else: 0), fn c, acc ->
acc + cond do
c == ?^ -> 0
c in 'urtoenails' -> 1
c in 'dg' -> 2
c in 'bcmp' -> 3
c in 'fhvwy' -> 4
c == ?k -> 5
c in 'jx' -> 8
c in 'qz' -> 10
true -> 0
end
end)
end
end
``````

Calculates only by letter points

``````
import "strings"

var LETTER_POINTS = map[byte]int{
'a': 1, 'b': 3, 'c': 3, 'd': 1, '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,
}

func CalculateScrabblePoints(word string) int {
points := 0

if len(word) == 7 {
return 50
}

lowerWord := strings.ToLower(word)

for _, letter := range lowerWord {
points += LETTER_POINTS[byte(letter)]
}

return points
}

``````

Ooh I love Scrabble! I'm excited for this one lol

Here is my over engineered solution in Rust!

I broke it down into multiple different Rust strucs and enums! I'm getting more and more familiar with the Rust type system, and breaking down a game like this is great practice!

One thing I slightly added, is the ability to parse multiple word modifiers together! I opted for a `(t)(d)` syntax do indicate both a triple and double word score. This is possible in Scrabble, so I wanted to include support for it

My solution and test cases is just over 250 lines so here is the link to the source file in Github!
github.com/coreyja/dev-to-challeng...

Oh no, I didn't get to it today! Tomorrow will have to be a double day!

code of conduct - report abuse  