8
\$\begingroup\$

Given input n, produce a word grid of size n, in as few bytes as possible.

Details

The number provided will always be odd. The centre square of the grid must always be empty (a space character). The letters that fill the grid must be chosen at random from the English Scrabble letter distribution of letters. That is, each tile must be chosen at random, with uniform probability, from the following 100 characters:

??EEEEEEEEEEEEAAAAAAAAAIIIIIIIIIOOOOOOOONNNNNNRRRRRRTTTTTTLLLLSSSSUUUUDDDDGGGBBCCMMPPFFHHVVWWYYKJXQZ

where ? denotes blank tiles.

As with a Scrabble board, some tiles in this board will also be bonus tiles for a higher score. These tiles are only visible on a board with a size of 9 or more. and should be visible every 3 rows from the centre square in a pattern emanating outwards, but should never appear on the outermost letters. Bonus squares are denoted using lowercase letters, with blank tiles identified as ! instead of ?. Please see this visual demonstration or the reference implementation for more examples on how to correctly distribute bonus tiles.

Examples

Input: 5

Output:

VNZNT
IFOSN
UD VD
ZIOO?
KTLED

Input: 9

Output:

UWDESTKPW
ItDBaDEdI
TERMDYSTR
ROANJLEFT
EkCI OOsT
IPAJPGM?Y
MZLORETVI
G!EGgPUeI
MNROYOEER

Reference implementation.

Rules

This is so the shortest code in bytes wins.

  • Any reasonable format can be used for I/O, assuming it is consistent.
  • You should be able to handle grids at least up to 999.
  • All standard loopholes are forbidden.
\$\endgroup\$
  • 3
    \$\begingroup\$ I actually contemplated adding a scrabble letter-set yield to Jelly a while back :p \$\endgroup\$ – Jonathan Allan Sep 12 '17 at 15:54
  • \$\begingroup\$ Should these not be called letter grids, if we're just being placing tiles randomly without actually having to form any words? \$\endgroup\$ – Shaggy Sep 13 '17 at 11:01
  • \$\begingroup\$ @Shaggy So, I originally wanted to make the challenge about solving these as per this sandbox post, but when making scripts to generate these and validate the co-ordinates, I thought they'd make fun challenges on their own, I didn't update the name of the challenge as I worked it back though... Perhaps you're right! \$\endgroup\$ – Dom Hastings Sep 13 '17 at 11:32
5
\$\begingroup\$

Jelly,  67 65 64 66 64  63 bytes

”?;ØAx“³Ċu~Ṿr¥rṇ⁽ȦƑ’ḃ12¤µŒl⁾?!yW,WKF€
H3ṬṚ¤ṁ‘1¦ṚŒḄ0,0j«þ`ị¢X€€Y

A monadic link taking a number and returning a list of characters, or a full program printing the result.

Try it online! (I prefer it using G rather than Y since that's more square)

How?

”?;ØAx“³Ċu~Ṿr¥rṇ⁽ȦƑ’ḃ12¤µŒl⁾?!yW,WKF€ - Link 1, getLetterSets: no arguments
”?                                    - literal '?'
   ØA                                 - yield uppercase alphabet
  ;                                   - concatenate
                       ¤              - nilad followed by link(s) as a nilad:
      “³Ċu~Ṿr¥rṇ⁽ȦƑ’                  -   base 250 number
                    ḃ12               -   converted to bijective base 12 (frequencies)
     x                                -   times (repeat each)
                        µ             - start a new monadic chain, call that uppers
                         Œl           - to lower-case
                           ⁾?!        - literal ['?','!']
                              y       - translate (change '?'s to '!'s)
                               W      - wrap (that) in a list
                                 W    - wrap (uppers) in a list
                                ,     - pair
                                  K   - join with a space, ' '
                                   F€ - flatten €ach (both flattens the wrapped lists
                                      -               AND makes the lone ' ' into [' '])

H3ṬṚ¤ṁ‘1¦ṚŒḄ0,0j«þ`ị¢X€€Y - Main link: number, n                 e.g. 13
H                         - halve                                     6.5
    ¤                     - nilad followed by link(s) as a nilad:
 3                        -   literal three                           3
  Ṭ                       -   untruth                                 [0,0,1]
   Ṛ                      -   reverse                                 [1,0,0]
     ṁ                    - mould like (implicit range(int(right)))   [1,0,0,1,0,0]
        ¦                 - sparse application:
       1                  - ...to indices: 1
      ‘                   - ...action: increment                      [2,0,0,1,0,0]
         Ṛ                - reverse                                   [0,0,1,0,0,2]
          ŒḄ              - bounce                          [0,0,1,0,0,2,0,0,1,0,0]
            0,0           - literal [0,0]                             [0,0]
               j          - join                          [0,0,0,1,0,0,2,0,0,1,0,0,0]
                  `       - repeat left argument as right argument with:
                 þ        -   outer product using:       [[0,0,0,0,0,0,0,0,0,0,0,0,0],
                «         -     minimum                   [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,1,0,0,1,0,0,1,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,1,0,0,2,0,0,1,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,1,0,0,1,0,0,1,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0],
                          -                               [0,0,0,0,0,0,0,0,0,0,0,0,0]]
                    ¢     - call the last link (1) as a nilad (the letter sets)
                   ị      - index into - gets the correct letter sets for each cell
                     X€€  - random-choice for €ach cell in €ach row
                        Y - join with newlines
                          - if running as a full program: implicit print
\$\endgroup\$
  • \$\begingroup\$ I don't think this replaces ? with ! when they are in the bonus tile position, I ran a few sets (easier to spot in the 9s!) Sorry... \$\endgroup\$ – Dom Hastings Sep 13 '17 at 7:17
  • \$\begingroup\$ @DomHastings ⁾?!y means exactly that, but it seems to fail. \$\endgroup\$ – Erik the Outgolfer Sep 13 '17 at 11:11
  • \$\begingroup\$ @DomHastings you are correct there is a bug (since the list of uppercase-letters is wrapped in a list at the point I attempt to perform the translation it never sees the ?s) I will fix it up right now... \$\endgroup\$ – Jonathan Allan Sep 13 '17 at 15:19
4
\$\begingroup\$

R, 288 281 267 257 225 214 bytes

thanks to @cole for -1 byte, reordering the ? to collapse the 2 into rep(2,10)

-10 bytes realizing that row(m) == t(col(m))

-41 bytes thanks to user2390246 for reconfiguring the weights, golfing down the indexing, and some more usual R tips

function(n){m=matrix(sample(el(strsplit("EOAINRTLSUDGBCMPFHVW?YKJXQZ","")),n^2,T,rep(c(12,8,9,6,4:1),c(1,1:4,1,10,5))),,n)
K=n/2+.5
L=col(m)
m[i]=chartr("A-Z?","a-z!",m[i<-(x=!(L-K)%%3&L-1&L-n)&t(x)])
m[K,K]=" "
m}

Try it online!

Returns a matrix. Fairly simple implementation; samples n^2 values with the proper distribution, stores as an nxn matrix.

K is the index of the center.

L=col(m) is a matrix indicating the column number of each element in the matrix. Hence we compute !(L-K)%%3 to get the possible columns (including the edges), i.e., those a multiple of 3 away from the center column. To remove the edges, we consider L-1 and L-n. L-1 is 0 (false) for the first column and L-n is 0 for the last column. Applying & (element-wise boolean AND) to these three yields a matrix with TRUE in those columns a multiple of three away from the center, excluding the edges. We store this result as x.

If we take the transpose of x, t(x), we get the same matrix, but for the rows, hence x&t(x) is a matrix we save as i containing: TRUE indices for the required cells, and FALSE everywhere else.

Then we use chartr to perform the required transformation on m[i] and save the result as m[i], change the center cell to a space, and return the matrix.

Importantly as user2390246 pointed out, we don't need to test n>=9 because for n<7, there aren't any cells a multiple of 3 away from the center (apart from the center which is changed to a space anyway), and for n==7, the only cells a multiple of 3 from the center are on the edge so they are excluded. Neat!

\$\endgroup\$
  • 2
    \$\begingroup\$ Could you move the ? to the part of the string corresponding to rep(2,9) and just increase that to rep(2,10)? \$\endgroup\$ – cole Sep 12 '17 at 16:53
  • \$\begingroup\$ @cole yes, that would be I think -1 byte? \$\endgroup\$ – Giuseppe Sep 12 '17 at 16:58
  • \$\begingroup\$ Looks like it, since you remove 2 without 2, and gain 1 for 10. \$\endgroup\$ – cole Sep 12 '17 at 16:59
  • 1
    \$\begingroup\$ For the letter distribution: rep(c(12,8,9,6,4:1),c(1,1:4,1,10,5)) saves 10 bytes. (You'll need to switch the order of the letters to EOAI...) \$\endgroup\$ – user2390246 Sep 13 '17 at 16:02
  • \$\begingroup\$ @user2390246 wow, thanks! \$\endgroup\$ – Giuseppe Sep 13 '17 at 16:13
4
\$\begingroup\$

JavaScript (ES6), 247 242 bytes

-5 bytes with help from @Shaggy

n=>[...Array(p=n*n)].map((_,i)=>i==p>>1?" ":g(i/n|0)&g(i%n,c="AI9O8NRTEE6LSUD4G3BCMPFHVWY?2KJXQZ".replace(/(\D+)(\d)/g,(_,c,i)=>c.repeat(i))[Math.random()*100|0])?c<"A"?"!":c.toLowerCase():++i%n?c:c+`
`,g=x=>x&&x<n-1&((n>>1)-x)%n%3==0).join``

Test Snippet

let f=
n=>[...Array(p=n*n)].map((_,i)=>i==p>>1?" ":g(i/n|0)&g(i%n,c="AI9O8NRTEE6LSUD4G3BCMPFHVWY?2KJXQZ".replace(/(\D+)(\d)/g,(_,c,i)=>c.repeat(i))[Math.random()*100|0])?c<"A"?"!":c.toLowerCase():++i%n?c:c+`
`,g=x=>x&&x<n-1&((n>>1)-x)%n%3==0).join``

;(R.onclick=L.onchange=I.oninput=function(){let n=+I.value,res=f(n);O.innerText=n+"\n"+(L.checked?res.replace(/[A-Z\?]/g,".").replace(/[a-z!]/g,"#"):res);})()
<label><input id=L type=checkbox> Layout only</label> <button id=R>Rerun</button><br><input id=I type=range min=9 max=53 step=2 value=9 style="width:100%">
<pre id=O></pre>

\$\endgroup\$
  • \$\begingroup\$ You should be able to save a couple of bytes by moving the assignment of c within one of the calls to g. \$\endgroup\$ – Shaggy Sep 13 '17 at 11:14
  • \$\begingroup\$ Really like the slider and layout functionality! \$\endgroup\$ – Dom Hastings Sep 13 '17 at 19:33
  • \$\begingroup\$ @Shaggy Thanks, I was able to save some more by improving the ternary expressions too. \$\endgroup\$ – Justin Mariner Sep 13 '17 at 19:47
3
\$\begingroup\$

Perl 6, 162 161 154 153 bytes

{(my@b=0,|(-$^m..$m).map({$_%%3+!$_}),0).map:{((my$e='AIJKQXZG'~'NRTBCFHMPVWYG'x 2~'EEEAAIIOONRTUSLD'x 4)~'??',$e.lc~'!!',' ')[@b Xmin$_]».comb».roll}}

Try it online!

Takes (n-3)/2 as input, returns a list of lists of letters.

Explanation:

-> $m {
    # Idea stolen from @Xcali's answer.
    my $e = 'AIJKQXZG' ~ 'NRTBCFHMPVWYG' x 2 ~ 'EEEAAIIOONRTUSLD' x 4;
    # Array containing 1 for bonus tiles, 2 for middle element, like
    #     (0,1,0,0,1,0,0,2,0,0,1,0,0,1,0)
    my @b = 0, |(-$m..$m).map({ $_ %% 3 + !$_ }), 0;
    # Map column vector.
    @b.map: {
        # Compute element-wise minimum of row vector and value from
        # column vector. Select character pools accordingly and get
        # random items.
        ($e~'??', $e.lc~'!!', ' ')[@b Xmin $_]».comb».roll
    }
}
\$\endgroup\$
3
\$\begingroup\$

Perl 5, 246 244 + 1 (-n) = 247 245 bytes

$m=($n=$_-$_%2)/2;for(@a=(U x++$n)x$n){$_=(LUU,ULU,UUL)[$m%3]x($n/3).U x($n%3)if$z%@a&&$z%3==$m%3;substr$_,$m,1,$"if$m==$z++;s/L/-U/g;s/U/substr"EEAIONRT"x6 .AIJKQXZG."AIO?BCFHMPVWYGUUSSLLDD"x2,rand 100,1/ge;s/^-//;s/-\?/!/g;s/-(.)/lc$1/ge;say}

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Fixed it now along with a few other things. \$\endgroup\$ – Xcali Sep 13 '17 at 4:17
3
\$\begingroup\$

[C64 basic v2, 210 209 bytes]

1t$="2?9E3E9A9I8O6N6R6T4L4S4U4D3G2B2C2M2P2F2H2V2W2Y1K1J1X1Q1Z":rEn
3fOx=1ton:fOy=1ton:a=rN(0)*100:b=1
4c=aS(mI(t$,b,1))-48:d=aS(mI(t$,b+1,1)):b=b+2:ifc>atH6
5a=a-c:gO4
6pO983+40*y+x,c:nE:nE:pO1003.5+n*20.5,32

An input n 5 can be specified like

0dA5

That "5" to the end should be changed to any odd number. Don't give more than 25, then the program will overwrite itself.


How to try it: google for "vice c64 emulator", install it, and copy-paste this basic code into it. To start the program, type: RUN. To clear the screen, press shift/home.

And the result:

enter image description here

\$\endgroup\$
  • \$\begingroup\$ What's the best way for me to validate this and apply the input? :) \$\endgroup\$ – Dom Hastings Sep 13 '17 at 19:34
  • \$\begingroup\$ @DomHastings C64 emulator with a monitor (monitors are byte-level memory modifiers in the c64 world), or the help plus plus basic extender. I am not 100% sure that it will work, but 90 yes. \$\endgroup\$ – peterh Sep 13 '17 at 19:38
  • \$\begingroup\$ @DomHastings I can make a 3 byte longer, debuggable version. The important thing is that the common basic commands are given here in tokenized form. The token is always very simple: first char of the command + the second char in uppercase. For example, FOR is tokenzied as fO. Or GOTO as gO. I could have made it also interactive, but unfortunately the INPUT command is an exception, it doesn't have a token. This is why the input should be given with DATA command, whose token is dA. \$\endgroup\$ – peterh Sep 13 '17 at 19:39
  • \$\begingroup\$ I'd certainly love to see it in action, any way you can help me get to see it would be awesome! Ah, thanks for explaining, that make it read better to me. I'll come back to this in the morning!! \$\endgroup\$ – Dom Hastings Sep 13 '17 at 19:43
  • \$\begingroup\$ @DomHastings Don't need, it is ready. :-) There were some minor bugs, I fixed them and the result become 1 byte smaller! In asm, it could have been only roughly 40 byte. Although I had to use some tricky way to the random generation. \$\endgroup\$ – peterh Sep 13 '17 at 20:19
2
\$\begingroup\$

Python 3, 214 236 240 bytes

lambda n:[[[choice([s,[[c.lower(),'!'][c<'A']for c in s]][((i-n//2)%3+(j-n//2)%3<1)*(i*j>0)*(i<n-1)*(j<n-1)])," "][i==j==n//2]for j in range(n)]for i in range(n)]
from random import*
s=(("OIAE"*2+"SDLUNTRE")*2+"HVBMCYPWF?GNTR")*2+"ZXJQKGIA"

Try it online!

The multiplicity of each character is expressed as sum of powers of two, e.g 12 = 8 + 4 => "E"*12 = "E"*2*2*2 + "E"*2*2.

((i-n//2)%3+(j-n//2)%3<1)*(i*j>0)*(i<n-1)*(j<n-1) may probably be golfed.

\$\endgroup\$
  • \$\begingroup\$ @Giuseppe I will fix it \$\endgroup\$ – jferard Sep 13 '17 at 18:35
  • \$\begingroup\$ I like the extra clarification via the assert! \$\endgroup\$ – Dom Hastings Sep 13 '17 at 19:33
  • \$\begingroup\$ @Giuseppe I hope it's ok now. i<n was always true, even when i=n-1 (bottom). Same for j. \$\endgroup\$ – jferard Sep 13 '17 at 20:30
  • \$\begingroup\$ looks good to me! +1. \$\endgroup\$ – Giuseppe Sep 13 '17 at 20:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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