# Daily Challenge #8 - Scrabble Word Calculator

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 even more challenging, please consider additional scoring as follows:

Double letter (doubles the value of the letter)
-A double letter will be represented with an asterisk after the letter. he*llo would make a double letter on the e.

Triple letter (triples the value of the letter)
-A triple letter will be represented with two asterisks after the letter. he**llo would make a triple letter on the e.

Double word (double the value of the word after letter rules have been applied)
-A double word is represented by the word ending in (d)

Triple word (triple the value of the word after letter rules have been applied)
-A triple word is represented by the word ending in (t)

A blank (the letter given will score 0)
-A blank tile will be represented with a caret after the letter or asterisk is the letter has a double or triple letter value. he^llo would mean the e scores 0.

Bonus 50!
-If the word is a seven letter word an additional 50 points are awarded.

Good luck and happy coding!

This challenge comes from user grantw1991 on Codewars

Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

### Discussion  Yozen Hernandez

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 Steve Moon

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
`````` Lorraine Lee

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);
}
`````` Alvaro Montoro

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. Ben Lovy

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. Dylan Paulus

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()
`````` Jonathan Kuhl

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. Kasper Meyer

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. Lorraine Lee

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
`````` Olivier “Ölbaum” Scherler

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
`````` Kevin Sullivan
``````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
);
`````` jmc

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))))
`````` margo1993

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
}

`````` Corey Alexander

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...  