Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

Your task is to determine if a given string is of proper length and can be represented with Scrabble tiles and, if so, output the sum of each letter's score.

If you don't know how to play Scrabble:, you have 100 tiles with various letters A–Z printed on them, as well as two wildcards that can represent any letter. Each letter has a certain number of points, and each tile (but not necessarily word) can only be used once. When a word is played, the point value of each tile used is added up, which becomes the score. As there are a limited number of letters available, a word can only have a certain letter as many times as that letter has tiles + any unused wildcards. The Scrabble board is 15×15 cells, so the word must be between 2 and 15 characters long.

For a list of the quantity and score of each letter in the English version, see below or http://boardgames.about.com/od/scrabble/a/tile_distribute.htm (archive).

Letter  Qty  Points        Letter  Qty  Points
-------------------        -------------------
A       9    1             O       8    1
B       2    3             P       2    3
C       2    3             Q       1    10
D       4    2             R       6    1
E       12   1             S       4    1
F       2    4             T       6    1
G       3    2             U       4    1
H       2    4             V       2    4
I       9    1             W       2    4
J       1    8             X       1    8
K       1    5             Y       2    4
L       4    1             Z       1    10
M       2    3             [wild]  2    0
N       6    1

Further rules

  • The program shall take a single string of input from STDIN or the like.
  • The input will always contain only uppercase letters.
  • If the string contains more copies of a letter than there are unused wildcards or tiles for that letter OR the string's length is not between 2 and 15 inclusive, the program should output Invalid.
  • Otherwise, the score should be added up with using data from the chart above and output.
  • Do not use wildcards unless necessary.
  • Do not worry about bonuses such as double word scores or whether the string is a real word.
  • The program shall output the result through STDOUT or the like.
  • Standard "loopholes" which are no longer funny are not allowed.
  • Using an external source such as a website, as well as any libraries, APIs, functions, or the like that calculate Scrabble scores or proper quantities are not alllowed.
  • This is , so fewest bytes wins.


C -> 3, O -> 1, D -> 2, E -> 1, G -> 2, O -> 1, L -> 1, F -> 4
3 + 1 + 2 + 1 + 2 + 1 + 1 + 4 = 15
Output: 15


Input             Output
SCRABBLE          14
JAZZ              19
PIZZAZZ           Invalid
KIXOKEJAJAX       Invalid
share|improve this question
Might want to add a test case for a valid word which uses wild cards (eg. JAZZ = 19, not 29) –  Alconja Aug 5 at 0:41
You know, this challenge would be so much more evil if it involved a language whose Scrabble tiles cannot be represented with a single character, like Spanish, Basque, Hungarian, Tuvan or Welsh. –  guifa Aug 10 at 21:26

13 Answers 13

Perl 5 228 205 186 184 178 177 153 150 149 142 137 (136+1 for -E)


$_=<>;@a=@b=map-ord,'``     H 0@0 0             '=~/./g;say s!.!($a[$q=-65+ord$&]+=8)<8?$-+=1-29/$b[$q]:++$j!ge~~[2..15]&&$j<3?$-:Invalid

This solution uses some non-printable characters, so a hexdump is provided below:

0000000 5f24 3c3d 3b3e 6140 403d 3d62 616d 2d70
0000010 726f 2c64 6027 0e0e 601d 1809 4809 0704
0000020 0e20 4030 030e 2030 2030 0909 0904 2703
0000030 7e3d 2e2f 672f 733b 7961 7320 2e21 2821
0000040 6124 245b 3d71 362d 2b35 726f 2464 5d26
0000050 3d2b 2938 383c 243f 2b2d 313d 322d 2f39
0000060 6224 245b 5d71 2b3a 242b 216a 6567 7e7e
0000070 325b 2e2e 3531 265d 2426 3c6a 3f33 2d24
0000080 493a 766e 6c61 6469


# Read in input
# @a and @b: represents approximately 8x the number of tiles (when rounded up). The 
#   non-multiple-of-8 values distinguish tiles that are given equally, but are worth
#  different values
# above is equivalent to
# @a=@b (-96,-14,-14,-29,-96,-09,-24,-09,-72,-04,-07,-32,-14,-48,-64,-14,-03,-48,-32,-48,-32,-09,-09,-04,-09,-03);
    # for each character
        # $q: A->0, B->1, etc.
        # decrement number of $q tiles, add points if needed, otherwise
        #    increment j, which counts number of wilds used
        # truncate(1-29/b[q]): decimal values were chosen specifically
        #    for this to return the point value. b[q] is the number of tiles
        #    of the qth letter after a originally given.
        #  $- contains the score, is initially zero (if in a one line program, 
        #   as the golfed version is), and is always an integer
        ($a[$q=-65+ord$&]+=8)<8 ? $- += 1 - 29/$b[$q] : ++$j
    # s returns length, check if between 2 and 15
    !ge ~~ [2..15]
    # make sure less than 3 negative tiles (aka wilds) 
    && $j < 3 ?
        # print score
    # or invalid
    : Invalid
share|improve this answer
you can squeeze at least 20 bytes out of those arrays with some creative manipulation –  Sparr Aug 5 at 2:23
Gah, always one step ahead of me. :) Have an upvote. –  Alconja Aug 5 at 22:44
This has been interesting, our scores have been so close all the way. +1. –  steveverrill Aug 13 at 9:59

C, Rev 2, 151 145 138

Inspired by the 159-byte code in @bebe's comment, I squeezed another 8 14 21 characters out:

4 bytes saved by rearranging the length counter i. This is initialised to 1 (assuming the program takes no arguments) then multiplied by 4 every time a letter is read. It overflows to zero when the word length is greater than 15, so to check if the word length is bad, we simply check if i<5 (I put i<9 so it will still give invalid for one-letter words if the user accidentally intialises i to 2 by putting a single argument on the command line.)

4 bytes saved by changing the loop condition test to a simple &31. This requires that the word is terminated with a space (ASCII 32) or a null character (ASCII 0.) Normally keyboard input is terminated by a newline (ASCII 10) so the program's a bit inconvenient to use, because you have to type the space then press return as well to get the computer to read the buffer. For newline-terminated strings, I could match but not beat the way bebe does it.

6 13 bytes saved by changing the encoding to -(number of tiles of each letter)-(score for that letter-1)*13. This now requires a range from -4 for L,S,U to -118 for Q,Z. The reason for using negative numbers is to avoid the unprintable ASCII range 0 to 31. Instead the range used is the two's complement of the negative numbers 256-4=252 to 256-118=138. These are printable, extended ASCII characters. There are issues with copying and pasting these in Unicode (the way it simplifies back to ASCII depends on the code page installed which can lead to unpredictable results) so I have included the correct ASCII codes in the program comment.

The advantage of this encoding is the elimination of the variable r as the number of tiles is always reduced by 1 (as it is stored as a negative number, we do t[x]++. Additionally, the postfix operator means we can perform this increment at the same time as adding the score to s.

//char t[]={32,247,228,228,239,244,215,240,215,247,164,203,252,228,250,248,228,138,250,252,250,252,215,215,164,215,138,0};
  for(char t[]=" ÷ääïô×ð×÷¤ËüäúøäŠúüúü×פ׊";x=getchar()&31;i*=4)

C,184 Rev 1 173 (or 172 with compiler option)

I'm using GCC, and with the compiler option -std=c99 it will allow me to move char t[]="...." into the initialization of the for loop for a saving of one additional semicolon. For readability, I have shown the program without this change, and with whitespace left in.

#define T t[x[1][i]-65]
main(int r,char**x){
  char t[]="Z>>QxS=SZW6(><P>m<(<(SSWSm";

The trick is in the datatable. For each letter an ASCII code of the form (total score of tiles for that letter)*10+(score of one tile-1) is stored in the table t[]. At runtime, these total scores are reduced as tiles are used up.

The total score of all the tiles for each letter ranges from 12 for E down to 4 for L,S,U. This form of encoding enables only printable ASCII characters to be used (ASCII 120,x for E down to ASCII 40,( for L,S,U.) Using the number of tiles would need a range from 120 down to 10, which is why I avoided it.

Thanks to a #define macro, a single symbol T is used in the main program to retrieve the letter index ifrom the first commandline argument, subtract ASCII A=65 from it to give an index ,and look it up in the table T: t[x[1][i]-65].

The for loop is used more like a while loop: the loop ends when a zero byte (string terminator) is encountered in the input string.

If the tiles of that letter are not exhausted (T/10 is nonzero) s is incremented by the tile score T%10+1 to keep a total score. At the same time the tile score is stored in r, so that the value in the able represented by T can be decremented by r*10 to indicate that one tile has been used. If the tiles are exhausted, the wildcard/blank counter b is incremented.

The printf statement is fairly self-explanatory. if the wordlength is out of bounds or the blank count is too high, print Invalid otherwise print the score s.

share|improve this answer
Since it's now another day, you can save one character by replacing r+=(r==7)*3 with r+=r-7?0:3. Also you don't need the brackets round T-=r*9,s+=r. –  Alchymist Aug 7 at 7:46
@Alchymist Thanks for the tip about the brackets, I always forget there's no issues with operator precedence between ? and :. Your other point is superseded, as I have completely changed the encoding so there's no need for any special handling of Q and Z. Now down to 173/172 with your help. –  steveverrill Aug 7 at 20:09
with getchar() it's 159: l,w,f;main(t,i){for(char b[]="Z>>QxS=SZW6(><P>m<(<(SSWSm";(i=getchar()-65)>=0;l++)b[i]/10?f+=t=b[i]%10+1,‌​b[i]-=t*10:w++;printf(l<2|l>15|w>2?"Invalid":"%d",f);} although I still don't get why char*foo=<string> crashes. it could save 2 chars. –  bebe Aug 8 at 16:04
@bebe char*foo="string" is a string literal, and its contents are not allowed to be modified. On the other hand, char foo[]="string" creates an array of chars initialized to string\0, which may then be modified. –  es1024 Aug 9 at 12:10
@bebe cool, I missed the idea of using getchar(). I have used your improvements to the code (with my variable names for consistency with the rest of my answer), plus an improvement to the wordlength validity check, and a cheeky improvement to the loop condition test (I tried shortening yours but could not do so with the same functionality.) I also tried getche() and getch() but my compiler (gcc on cygwin) would not link them automatically. –  steveverrill Aug 10 at 1:18

JavaScript (ES6) - 241 230 199 182


Edit - changed the way I encoded the quantities/scores to reduce size and remove non-ascii variables

Edit 2 - changed the quantity/score encodings to integers instead of strings

Edit 3 - switched to %13 (thanks @edc65), inverted the encoding, modified the values directly, and a few other minor improvements

Tested in Firefox console.

share|improve this answer
+1 very clever. Suggestions: 1.f[c]=1+f[c]||1 -> f[c]=-~f[c], 2.why not use % 13 –  edc65 Aug 5 at 11:55
192 f=s=>{for(E=12,A=I=9,B=C=M=P=28,D=17,F=H=V=W=Y=41,G=16,J=X=92,K=53,L=S=U=4,N=R=T‌​=6,O=8,Q=Z=118,$=2,t=i=0;c=s[i++];)(f[c]=-~f[c])>(l=this[c])%13?--$:t+=l/13+1|0;a‌​lert(i<3|i>16|$<0?"Invalid":t)} –  edc65 Aug 5 at 12:14
@edc65 - Thanks heaps. Hadn't seen that first trick, but didn't end up using it as I'm directly modifying the values now (mentally filing it away for future golf). %13 is a stroke of genius though. I got stuck thinking I had to store things in digits, but maths doesn't care about the difference between base10 and base13. –  Alconja Aug 5 at 22:42
Nice! (Doesn't work in Chrome's console, BTW: SyntaxError: Unexpected token >.) –  DLosc Aug 6 at 0:57
@DLosc - Yeah, I think Firefox is the only browser at the moment that supports all the ECMAScript 6 stuff (Chrome doesn't like the f=s=>{...} notation). –  Alconja Aug 6 at 2:00

BEFUNGE 93 - 210 bytes.

But it doesn't check the 15 letter limit.

v1332142418513113:11114484: >01g:"0"-!#v_1-01p1+\v
 9224<232911426821646422121v  "Invalid"<      vp0<
>>~:55+-!#v_"@"-::1g:"0"-! #^_1-\1p0g+"0"-02g+>02p
_v#:-1<    #p90+g90-"0"g1:<
     @.g20<        @,,,,,,,<
share|improve this answer

C, 197

Assumes the string is provided as a command line argument, e.g. ./scrabble STACKEXCHANGE

s;n;m=31;main(int c,char**v){char d[]="BIBBDLBCBIAADBFHBAFDFDBBABA@ACCBADBDAHEACAACJAAAADDHDJ";for(;c=*v[1]++&m;d[c]--,s+=d[c+27]&m)n+=1+m*(!(d[c]&m||d[c=0]&m));printf(n>1&&n<16?"%d":"Invalid",s);}
share|improve this answer

JavaScript - 232 201


z stores word. Outputs as alert.

Edit: improved as per recommendations below.

share|improve this answer
s is only used once, so you don't need to make it a variable at all; you can remove that declaration and replace r+=s[x] with r+=-~"02210313074020029000033739"[x]. Also, you don't need parentheses around (w>2|y<2|y>15) in the alert. –  hsl Aug 5 at 16:52

Python 3, 217 201

while i<26:n=w.count(chr(i+65));q=int('9224c232911426821646422121'[i],16);b-=max(0,n-q);s+=min(n,q)*int('1332142418513113a11114484a'[i],16);i+=1


b=2    # number of blanks available
i=s=0  # letter index 0..25, running score tally

# Loop through each letter of the alphabet
while i<26:
    # Get number of occurrences in the word
    # Get quantity of the letter from hex encoded string
    # Remove blanks for each occurrence over that letter's quantity
    # Score the non-blank tiles, getting scores from hex-encoded string
    # Increment

# If b > -1 and 1 < len(w) < 16, print the score; otherwise, print "Invalid"

Edit: Thanks to @BeetDemGuise for a tip that ultimately led me to much more than a 1-character reduction! Original code below:

q=[77-ord(x)for x in'DKKIAKJKDLLIKGEKLGIGIKKLKL'];b=2;s=0;w=input()
for c in set(w):n=w.count(c);o=ord(c)-65;b-=max(0,n-q[o]);s+=min(n,q[o])*(1+int('02210313074020029000033739'[o]))
share|improve this answer
Its quite minimal but you can save 1byte by encoding your scores string in hex: int('1332142418513113a11114484a'[o],16) :) –  BeetDemGuise Aug 6 at 20:07

Python 2.7 - 263

I couldn't come anywhere close to DLosc's answer, but this treats each letter as a 'bag' that you pull from, until its empty, then you pull blanks, and when that is empty it errors.

X={chr(97+i):[int(y)+1]*(77-ord(x))for i,(x,y)in enumerate(zip('DKKIAKJKDLLIKGEKLGIGIKKLKL','02210313074020029000033739'))}
 if len(S)>15:1/0
 print sum(map(lambda x:X[x].pop()if len(X[x])>0 else B.pop(),S))
 print "invalid"
share|improve this answer
This is a neat approach! You need raw_input if it's Python2 (one thing I like about Python3). The input is guaranteed uppercase, so remove .lower() and change 97+i to 65+i. Input less than 2 characters also needs to be invalid. You can raise the zero-division error without an if statement: divide your total score by (1<len(S)<16). A couple other tweaks like putting the prints on the same line as the block headers and deleting the space before "Invalid" brings it down to 250 by my count. :) –  DLosc Aug 6 at 20:29

Haskell - 538

Save it as scrabble.hs and then compile it using

ghc --make scrabble && ./scrabble

Then enter your word as input and press enter

sc=zip l [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]
vfs a y =snd $ filter (\x -> fst x == y) a !! 0
q = zip l [9,2,2,4,12,2,3,2,9,1,1,4,2,6,8,2,1,6,4,6,4,2,2,1,2,1]
i s =filter (\x -> (fst x) >=0) [(length [x | x <- s, x == a] - vfs q a,a) | a <- l]
main = do
 s <- getLine
 if length s <= 15 && length s > 2 && sum (map fst (i s)) <= 2 then
  putStrLn $ show (sum [vfs sc x| x <- s] - sum [(vfs sc (snd x)) * (fst x) | x <- (filter (\x -> fst x > 0) (i s))])
 else do
  putStrLn "Invalid"
share|improve this answer
You can remove a lot of spaces and in Haskell `['A', 'B', 'C'] == "ABC". Also you can use only one space for each level of indent. And you can use shorter names. There is a lot for golfing. –  Ray Aug 6 at 17:08
@Ray Have done that, I am new to Haskell, is there a way to represent lists of Ints more concisely than [1,2,3]? –  Tuomas Laakkonen Aug 6 at 17:25
"ABCDEFG" can be written as ['A'..'G'], [1,2,3] can be written as [1..3] –  Ray Aug 6 at 17:56
How do you obtain your byte count? wc gives me over 500 characters for your code. –  TheSpanishInquisition Aug 7 at 18:37
@TheSpanishInquisition Just got a update for my st3 word count extension, the author had swapped the two counts accidentally, edited to 538 –  Tuomas Laakkonen Aug 8 at 2:18

Haskell, 290 283

As far down as I could make it for now:

import Data.List
s w@(_:_:_)=let d=concat(zipWith(replicate.(`div`11).f 33)t("AEIO"++['A'..]))\\w;y=drop 15w in if length(d++w++y++y++y)>100 then s""else show$187-(sum$map((`mod`11).f 0.(t!!).f 61)d)
s _="Invalid"
f n=(-n+).fromEnum
main=interact s

This code abides by the rules very strictly, so make sure you don't pass it any extra characters (such as end-of-line). Use like this: echo -n "JAZZ" | runghc scrabble.hs.


The pattern (_:_:_) makes sure that only strings of at least two characters are considered, everything else results in "Invalid" (fallback pattern _). The table of tiles is encoded as 11*nTiles+value converted to ASCII with an offset that allows the lookup modulo 11 to work, where the letters AEIO are duplicated because they occur more than 6 times each. The pool of tiles is then created using replicate, from which the characters in the word are removed as they occur (list difference, \\). The pool contains 98 tiles, so if the total length of the word and the remaining part of the pool is larger then 100, then we have used too many wildcards. Also, the word minus the first 15 letters is added three times to the length computation so any word longer than 15 letters automatically appears to use up three wildcards and therefore is invalid. Scoring is done on the remaining pool, which initially had 187 points, which we simply subtract from. Note the f 61 rather than f 65, 65 being the ASCII number of 'A', because of the duplicate "AEIO" at the beginning of the pool. The rest is just boilerplate.

share|improve this answer

Python3 - 197

s,i,c,r=input(),0x1a24182424416141611a2381612341151891243224c142232391,[],[]; p=len(s)
for w in s:e=8*ord(w)-520;c+=[s.count(w)<=i>>e+4&15];r+=[i>>e&15]

Let's put the bignums to use :D (It doesn't handle wildcards currently, I've skipped reading that rule entirely, damn)

share|improve this answer

Ruby - 195


I'm assuming the output of "Invalid" is fine, if not I would need to do $><<(-b<1&&w.size<16?s:'Invalid') which would bump it up to 198

Clojure - 325

I haven't done clojure in a while so I'm sure there are several ways to improve my solution .ie the qty and pts lists

(let[w(first *command-line-args*)o(map #(count(filter #{%}(seq w)))(map char(range 65 91)))i(apply +(filter neg?(map #(- % %2)'(9 2 2 4 12 2 3 2 9 1 1 4 2 6 8 2 1 6 4 6 4 2 2 1 2 1) o)))](println(if(or(> -2 i)(not(<= 2(count w)15)))"Invalid"(apply +(map #(* % %2)o'(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))))))

Some what Un-golfed

(let [word    (first *command-line-args*)
      letters (map char(range 65 91))
      occ     (map #(count (filter #{%} (seq word))) letters)
      invalid (apply + (filter neg? (map #(- % %2)
                '(9 2 2 4 12 2 3 2 9 1 1 4 2 6 8 2 1 6 4 6 4 2 2 1 2 1)
      score   (apply + (map #(* % %2) occ '(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)))]
      (if (or (> -2 invalid)
              (not (<= 2 (count word) 15)))
share|improve this answer

ES6: 184 (non-strict)

w is assumed to already contain the word. r is the output string.


Here's it explained and a little less golfed:

// The sum.
i = 0,

// The data for the letters. It's encoded similar to the Ruby version, with
// the first being the wildcard holder. The rest hold in hex form the
// following: first = quantity left, second = value.
// The .map(c => parseInt(c, 16) simply parses all the hex characters.
o = [..."291232342c124322491181541236181231a61416141242418241a"]
  .map(c => parseInt(c, 16)),

// The result, `r`.
r = !w[16] || // If there is a 16th character in the word or no 2nd character,
    w[2] &&   // then the next section isn't evaluated. It immediately equates
              // to true, thus returning "Invalid".
   [...w] // Convert the string into an array of characters (ES6 equivalent to
          // `.split('')`
    .every(c => // This loop terminates when the callback returns a falsy
                // value.
      // Gets the ASCII value, subtracts 65, doubles it (the lookup table is
      // in pairs within one array), and decrements the counter at that entry.
      // The lookup table also doubles as a data holder.
      o[c = c.charCodeAt() * 2 - 129]--
        > 0 ?  // Test if there is something to take away. This must return
               // false at 0 and -1 so wildcards can be taken.
        i += o[c+1] : // If there was something to take away, then add the
                      // letter value to the sum.
        o[0]--) // Otherwise, take a wildcard. If this is already at 0, then
                // it returns falsy.
      ? "Invalid" : i + "" // This is where the text is returned.
share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.