R, 288 281 267 257 225225 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))
-3141 bytes thanks to user2390246 for reconfiguring the weights, and golfing down the indexing, and some more usual R tips
function(n){m=matrix(sample(el(strsplit("EOAINRTLSUDGBCMPFHVW?YKJXQZ","")[[1]]),n^2,T,rep(c(12,8,9,6,4:1),c(1,1:4,1,10,5))),,n)
K=(n+1)K=n/22+.5
if(n>8){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}
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!