Determine a string's Scrabble score and validity

Perl 5 228 205 186 184 178 177 153 150 149 142 137 135

Run with perl -E.

Golfed:

$_=<>;@a=@b=map-ord,'            0 0@0 H        ``'=~/./g;say s!.!($a[$q=64-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:

00000000: 245f 3d3c 3e3b 4061 3d40 623d 6d61 702d  $_=<>;@a=@b=map-
00000010: 6f72 642c 2703 0904 0909 2030 2030 030e  ord,'..... 0 0..
00000020: 4030 0e20 0704 4809 1809 601d 0e0e 6027  @0. ..H...`...`'
00000030: 3d7e 2f2e 2f67 3b73 6179 2073 212e 2128  =~/./g;say s!.!(
00000040: 2461 5b24 713d 3634 2d6f 7264 2426 5d2b  $a[$q=64-ord$&]+
00000050: 3d38 293c 383f 242d 2b3d 312d 3239 2f24  =8)<8?$-+=1-29/$
00000060: 625b 2471 5d3a 2b2b 246a 2167 657e 7e5b  b[$q]:++$j!ge~~[
00000070: 322e 2e31 355d 2626 246a 3c33 3f24 2d3a  2..15]&&$j<3?$-:
00000080: 496e 7661 6c69 64                        Invalid

Alternatively, using Ctrl+Key:

$_=<>;@a=@b=map-ord,'^C^I^D^I^I 0 0^C^N@0^N ^G^DH^I^X^I`^]^N^N`'=~/./g;print s!.!($a[$q=64-ord$&]+=8)<8?$-+=1-29/$b[$q]:++$j!ge~~[2..15]&&$j<3?$-:Invalid

Ungolfed+commented:

# 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
@b=@a=map-ord,"...."~=/./g;
# above is equivalent to
# @a=@b=(-03,-09,-04,-09,-09,-32,-48,-32,-48,-03,-14,-64,-48,-14,-32,-07,-04,-72,-09,-24,-09,-96,-29,-14,-14,-96);
say
    # for each character
    s!.!
        # $q: A->-1, B->-2, 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=64-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

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};
b,s;
main(i,x){
  for(char t[]=" ÷ääïô×ð×÷¤ËüäúøäŠúüúü×פ׊";x=getchar()&31;i*=4)
    t[x]%13?
      s-=t[x]++/13-1:
      b++;
  printf(i<9|b>2?"Invalid":"%d",s);
} 

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]
i,b,s;
main(int r,char**x){
  char t[]="Z>>QxS=SZW6(><P>m<(<(SSWSm";
  for(;x[1][i];i++)
    T/10?
      s+=r=T%10+1,T-=r*10:
      b++;
  printf(i<2|i>15|b>2?"Invalid":"%d",s);
}

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.


JavaScript (ES6) - 241 230 199 182

f=s=>{for(i=t=_=0,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;c=s[i++];)this[c]%13<1?_++:t+=1+this[c]--/13|0;alert(i<3|i>16|_>2?"Invalid":t)}

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.