Generating Word Grids

8

2

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.

Dom Hastings

Posted 2017-09-12T15:45:34.540

Reputation: 16 415

3I actually contemplated adding a scrabble letter-set yield to Jelly a while back :p – Jonathan Allan – 2017-09-12T15:54:04.800

Should these not be called letter grids, if we're just being placing tiles randomly without actually having to form any words? – Shaggy – 2017-09-13T11:01:49.290

@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!

– Dom Hastings – 2017-09-13T11:32:13.850

Answers

5

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

Jonathan Allan

Posted 2017-09-12T15:45:34.540

Reputation: 67 804

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... – Dom Hastings – 2017-09-13T07:17:44.350

@DomHastings ⁾?!y means exactly that, but it seems to fail. – Erik the Outgolfer – 2017-09-13T11:11:24.577

@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... – Jonathan Allan – 2017-09-13T15:19:21.210

4

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!

Giuseppe

Posted 2017-09-12T15:45:34.540

Reputation: 21 077

2Could you move the ? to the part of the string corresponding to rep(2,9) and just increase that to rep(2,10)? – cole – 2017-09-12T16:53:15.953

@cole yes, that would be I think -1 byte? – Giuseppe – 2017-09-12T16:58:12.480

Looks like it, since you remove 2 without 2, and gain 1 for 10. – cole – 2017-09-12T16:59:55.330

1For 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...) – user2390246 – 2017-09-13T16:02:58.140

@user2390246 wow, thanks! – Giuseppe – 2017-09-13T16:13:00.437

You can also cut down the indexing for i by only taking the transform once: !(x=((L-K)%%3|L%in%c(1,n))|t(x)) – user2390246 – 2017-09-13T16:13:38.327

@user2390246 I had just started getting there but I used !((x=(L-K)%%3)|t(x))&(y=L-1&L-n)&t(y), so combining the two approaches gets us to i<-(x=!(L-K)%%3&L-1&L-n)&t(x) which is like 20 bytes shorter! – Giuseppe – 2017-09-13T16:31:24.137

I think that, with n < 9, this indexing is still valid (returns all FALSE), so you can get rid of the if statement. Also, there's the classic el(x) to replace x[[1]]. – user2390246 – 2017-09-13T16:49:43.667

@user2390246 oh yeah...thanks for all the help :) – Giuseppe – 2017-09-13T17:31:27.310

4

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>

Justin Mariner

Posted 2017-09-12T15:45:34.540

Reputation: 4 746

You should be able to save a couple of bytes by moving the assignment of c within one of the calls to g. – Shaggy – 2017-09-13T11:14:44.327

Really like the slider and layout functionality! – Dom Hastings – 2017-09-13T19:33:32.640

@Shaggy Thanks, I was able to save some more by improving the ternary expressions too. – Justin Mariner – 2017-09-13T19:47:38.303

3

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!

Xcali

Posted 2017-09-12T15:45:34.540

Reputation: 7 671

Fixed it now along with a few other things. – Xcali – 2017-09-13T04:17:05.877

3

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

nwellnhof

Posted 2017-09-12T15:45:34.540

Reputation: 10 037

3

[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

peterh - Reinstate Monica

Posted 2017-09-12T15:45:34.540

Reputation: 347

What's the best way for me to validate this and apply the input? :) – Dom Hastings – 2017-09-13T19:34:13.840

@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. – peterh - Reinstate Monica – 2017-09-13T19:38:20.433

@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. – peterh - Reinstate Monica – 2017-09-13T19:39:08.837

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!! – Dom Hastings – 2017-09-13T19:43:00.430

@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. – peterh - Reinstate Monica – 2017-09-13T20:19:14.457

Thank you for explaining and thank you for the screen shot! I'll play with this tomorrow as I'm quite intrigued, but thanks for indulging my questions! – Dom Hastings – 2017-09-13T20:24:37.967

2

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.

jferard

Posted 2017-09-12T15:45:34.540

Reputation: 1 764

@Giuseppe I will fix it – jferard – 2017-09-13T18:35:55.227

I like the extra clarification via the assert! – Dom Hastings – 2017-09-13T19:33:16.860

@Giuseppe I hope it's ok now. i<n was always true, even when i=n-1 (bottom). Same for j. – jferard – 2017-09-13T20:30:14.933

looks good to me! +1. – Giuseppe – 2017-09-13T20:34:31.817