Here's my solution in Perl, along with a few tests.
#!/usr/bin/env perlusev5.24;usestrict;usewarnings;usefeatureqw(signatures);nowarnings"experimental::signatures";useList::Utilqw(sum);useCarp;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+=($2ne'^')*($scores{$1}+$scores{$1}*length($2));}return($score*$mult)+50*($count==7);}useTest::Moretests=>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
constscrabbleWordValue=word=>{// scrabble letter valuesconstletterValues={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:// - it must start with a letter// - 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 pointsif(!word.match(/^([a-z][\*]{0,2})+(\([t|d]\))?$/gi)){return0;}// 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.letwordModifier=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 totalletprevious=0;letvalueWord=word.split('').reduce((acc,curr)=>{if(curr=='*')curr=previous;previous=curr;returnletterValues[curr]+acc;},0);// if the words has seven letters, add 50 extra points!constallLettersBonus=word.replace(/\*/g,'').length==7?50:0;// return the value of the word considering all modifiersreturnvalueWord*wordModifier+allLettersBonus;}
I am a software development engineer in test for Infosys. My job is officially to write automated tests in Selenium Webdriver. I'm also a web developer as a hobbyest
A few days late, but here's my JavaScript solution, using reduce.
functionscoreWord(string){letword=string.toLowerCase();constscores={a:1,// ... etc, cut for concisenessz:10,q:10}letmultiplier=1;if(word.substring(word.length-1)==='2'){multiplier=2;word=word.substring(0,word.length-1);}elseif(word.substring(word.length-1)==='3'){multiplier=3;word=word.substring(0,word.length-1);}letbonus=word.split('').filter(char=>{return!['*','^'].includes(char);}).length>=7?50:0;returnword.split('').reduce((score,letter,index,letters)=>{constnext=index+1<letters.length?letters[index+1]:null;if('abcdefghijklmnopqrstuvwxyz'.includes(letter)){if(next&&['*','^'].includes(next)){if(next==='^'){returnscore+=0;}if(next==='*'){if(index+2<letters.length&&letters[index+2]==='*'){score+=(scores[letter]*3);}else{score+=(scores[letter]*2);}returnscore;}}returnscore+=scores[letter];}else{returnscore;}},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.
Because these are fun in languages you don't actually know, here's Haskell:
importData.Map(Map,(!))importqualifiedData.MapasMapscores::MapCharIntscores=Map.fromListpairswherepairs=[('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->IntscoreWordw=letsevenLetterBonus=if(length$stripMarkersw)==7then50else0wordMultiplier=letsuffix=dropWhile(/='(')winiflengthsuffix>0thencasesuffix!!1of't'->3'd'->2_->1else1-- preparedWord=expandMarkers$takeWhile(/='(')wrawScore=sum$scoreLetters$preparedWordinrawScore*wordMultiplier+sevenLetterBonuswherescoreLetterscs=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 letterexpandMarkers::String->StringexpandMarkers[]=[]expandMarkers(c:[])=[c]expandMarkers(c:rest)=caseheadrestof'*'->if(head$tailrest)=='*'then[c]++[c]++[c]++(expandMarkers$drop2rest)else[c]++[c]++(expandMarkers$tailrest)'^'->expandMarkers$tailrest_->[c]++expandMarkersrest-- remove suffix and all markers for deciding on the 7-letter bonusstripMarkers::String->StringstripMarkersw=filter(\c->c/='*'&&c/='^')$takeWhile(/='(')w
I didn't provide tests, but I think it works. Maybe I'll write some later on.
fromstrutilsimporttoLowerfromsequtilsimportfiltertypeWordType=enumvalue,multiply,globaltypeWord=objectValue:intType:WordType# a..z# [97, 122]constscore_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 orderproc buildStack(word:string):seq[refWord]=result=newSeq[refWord]()letlowerWord=toLower(word)foriin0..(len(lowerWord)-1):letcurrentWord=new(Word)letletter=lowerWord[i]letletterAsInt=int(letter)ifletterAsInt<97orletterAsInt>122:ifletter=='*':currentWord.Value=2currentWord.Type=WordType.multiplyifletter=='^':currentWord.Value=0currentWord.Type=WordType.multiplyifletter=='(':currentWord.Value=iflowerWord[i+1]=='t':3else:2currentWord.Type=WordType.globalresult.add(currentWord)# we reached the end of the stringbreakelse:letscoreMappingPosition=letterAsInt-97currentWord.Value=score_mapping[scoreMappingPosition]currentWord.Type=WordType.valueresult.add(currentWord)# Gets the value of the operationsproc parseOperations(value:int,operations:seq[refWord]):int=varmultiplier=1iflen(operations)>0:foroperationinoperations:ifoperation.Type==Wordtype.multiply:ifoperation.Value==0:return0else:multiplier+=1result+=value*multiplierproc getScore(stack:seq[refWord]):int=vars=stack# make mutablevaroperations:seq[refWord]varglobalMultiplier=1ifstack.filter(proc(p:refWord):bool=p.Type==Wordtype.value).len>=7:result+=50whilelen(s)>0:letitem=s.pop()ifitem.Type==WordType.value:result+=parseOperations(item.Value,operations)operations=@[]elifitem.Type==Wordtype.global:globalMultiplier=item.Valueelse:operations.add(item)result*=globalMultiplierechobuildStack("d**e*v^(d)").getScore()
require"minitest/autorun"classScrabbleLetterSCORES={'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**">]#defself.parsestringstring.chars.each_with_object([])do|char,letters|SCORES[char]?letters<<char:letters[-1]<<charend.map{|letter|newletter}enddefinitializeletter@letter=letterenddefscoreletter_score*multiplierendprivatedefletter_scoreSCORES[@letter.chr]enddefmultiplierreturn0if@letter.end_with?('^')return3if@letter.end_with?('**')return2if@letter.end_with?('*')return1endendclassScrabbleWordDOUBLE_WORD_TOKEN='(d)'.freezeTRIPLE_WORD_TOKEN='(t)'.freezedefinitializeword@word=wordenddefscoreletters_score*multiplier+length_bonusendprivatedefletters_scoreletters.map(&:score).reduce(:+)enddefmultiplierreturn2if@word.end_with?(DOUBLE_WORD_TOKEN)return3if@word.end_with?(TRIPLE_WORD_TOKEN)return1enddeflength_bonusletters.count==7?50:0enddeflettersScrabbleLetter.parseword_without_multiplierenddefword_without_multiplier@word.gsub(DOUBLE_WORD_TOKEN,"").gsub(TRIPLE_WORD_TOKEN,"")endendclassScrabbleWordTest<MiniTest::Testdeftest_simple_wordassert_equal23,ScrabbleWord.new("quintessential").scoreenddeftest_double_and_triple_lettersassert_equal11,ScrabbleWord.new("he*llo**").scoreenddeftest_triple_wordassert_equal69,ScrabbleWord.new("quintessential(t)").scoreenddeftest_blank_tile_with_triple_wordassert_equal39,ScrabbleWord.new("q^uintessential(t)").scoreenddeftest_double_and_triple_letters_with_double_wordassert_equal22,ScrabbleWord.new("he*llo**(d)").scoreenddeftest_blank_tile_with_double_letter_and_double_wordassert_equal18,ScrabbleWord.new("he^llo**(d)").scoreenddeftest_seven_letter_word_bonusassert_equal72,ScrabbleWord.new("wordier(d)").scoreendend
I borrowed your tests, @yzhernand
. Thank you for writing them, so I didn't have to.
Full stack web dev.
Studying FP web development approaches, while helping Mission Bit create paths to programming for underserved public school kids.
Previously @ Gradescope.
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
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(funchar/2,{0,1,1,0,normal},string:lowercase(Word)),Bonus=caseLcof7->50;_->0end,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")].
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
Here's my solution in Perl, along with a few tests.
Edit: fixed a bug, stray print, and added the 7 letter bonus
JavaScript
Live demo on CodePen.
Erlang:
Ah, neat. Pattern matching was definitely the way to go about it functionally, wish I'd done that instead!
Good old C. Be advised though there are ways to bork this one with some inputs not consistent with the rubric.
A few days late, but here's my JavaScript solution, using
reduce.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.
Because these are fun in languages you don't actually know, here's Haskell:
I didn't provide tests, but I think it works. Maybe I'll write some later on.
My overly complex (nim) solution :)
Ruby solution
I borrowed your tests, @yzhernand . Thank you for writing them, so I didn't have to.
Clojure:
Calculates only by letter points
Elixir. Admittedly my Exercism solution with a bunch of String.replace calls to handle the more sophisticated way the problem is stated here.
codesandbox.io/s/daily-challenges-...
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:foldrto 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.
To run:
Clojure:
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 itMy 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!