g | x | w | all
Bytes Lang Time Link
057Charcoal250205T213811ZNeil
037Jelly250204T194000ZJonathan
04305AB1E250205T101533ZKevin Cr
202Maple250205T023141Zdharr
141JavaScript V8250204T131020Zl4m2
144JavaScript V8250204T080530ZArnauld
099Retina 0.8.2250204T100047ZNeil

Charcoal, 57 bytes

≔FOXθΦEX⁹¦⁸⭆¹⁶§θ÷ιX³λ›⬤θ‹№ιλ⁻⁷↔⊖μ⊙145№E¹⁶⭆³§ι⁺ν×⁻Iλ&⊗λνπθ

Don't Try it online! - far too slow. Instead, Attempt This Online! output the first few rows (and also tweaked for speed at a cost another byte so that it will complete in under a minute). Outputs a list of flattened grids. Explanation:

≔FOXθ

Save the string FOX as it gets used thrice.

ΦEX⁹¦⁸⭆¹⁶§θ÷ιX³λ

Filter on all 43046721 flattened grids of F, O and X that...

›⬤θ‹№ιλ⁻⁷↔⊖μ

... contain fewer than 6 Fs and Xs and 7 Os, but...

⊙145№E¹⁶⭆³§ι⁺ν×⁻Iλ&⊗λνπθ

... do not contain the word FOX.

Given any flattened board, there are always three three-letter words that can be formed starting at any letter on the board; from its (0-indexed) index l, you can step horizontally by 1-(l&2), vertically by 4-(l&8), and diagonally by 5-(l&10).

Jelly, 37 bytes

“OFX”ṁ⁴Œ!Qµs4ZU,ƊŒD;$Ẏṡ€3ẎUƬẎQṢ$ƑƇµÐḟ

A niladic Link that yields a list of lists of characters.

Don't Try it online! (way too inefficient.)
...a three by three using three of each letter takes ~30s.

How?

“OFX”ṁ⁴Œ!Qµ...µÐḟ - Link: no arguments
“OFX”             - literal "OFX"
     ṁ⁴           - mould like 16 -> "OFXOFXOFXOFXOFXO"
       Œ!         - all permutations
         Q        - deduplicate -> Potentials = distinct boards as lists of characters
          µ...µÐḟ - discard those of {Potentials} for which...

s4ZU,ƊŒD;$Ẏṡ€3ẎUƬẎQṢ$ƑƇ - (...) - f(Potential):
s4                      - split {Potential} into chunks of length four
  ZU,Ɗ                  - rotate {that} a quarter and pair with {that}
      ŒD;$              - concatenate {that} to its diagonals (i.e. the diagonals *and* anti-diagonals)
          Ẏ             - tighten {that}
           ṡ€3          - all sublists of length three of each of {those}
              Ẏ         - tighten {that}
               UƬ       - collect up reversing each of {those} while distinct
                 Ẏ      - tighten {that}
                    $ƑƇ - keep those which are invariant under:
                  Q     -   deduplicate {it}
                   Ṣ    -   sort {that}

05AB1E, 43 bytes

…OFX16∍œÙʒlS4ô2Fø€ü3}€`D€ø«D€í«D€Å\«J»'¨›å≠

Brute-force approach. Outputs as a list of flattened strings.

(Don't) try it online (way too slow).
Try it online with 3x3 instead. (16∍ and are changed to 9∍ and respectively.)

Explanation:

Unfortunately, 05AB1E lacks a builtin to get all diagonals/anti-diagonals of a matrix, except for the main diagonal, so some bytes are lost creating overlapping 3x3 blocks first.

…OFX          # Push string "OFX"
    16∍       # Extend it to length 16: "OFXOFXOFXOFXOFXO"
       œ      # Get all permutations of this string
        Ù     # Uniquify that list of permutations
ʒ             # Filter this list of permutations by:
 l            #  Convert it to lowercase
  S           #  Convert it to a list of characters
   4ô         #  Split it into 4 parts, to a 4x4 matrix
     2Fø€ü3}  #  Create overlapping 3x3 blocks:
     2F    }  #   Loop two times:
       ø      #    Zip/transpose; swapping rows/columns
        €     #    Map over each inner list:
         ü3   #     Create overlapping triplets
     €`       #  Flatten this nested list of 3x3 blocks one level down
     D        #  Duplicate it
      €ø      #  Zip/transpose each inner 3x3 block
        «     #  Merge the two lists of 3x3 blocks together
     D        #  Duplicate that again
      €       #  Map over each inner 3x3 block:
       í      #   Reverse each inner row
        «     #  Merge the two lists of 3x3 blocks together again
              #  (we now have all four rotations of each 3x3 block)
     D        #  Duplicate that yet again
      ہ\     #  Get each main diagonal of this copy
         «    #  Merge it to the list of 3x3 blocks as well
              #  (no need for additional anti-diagonals, since we have four rotations)
     J        #  Join each inner-most rows together to a string
      »       #  Join each inner list of three rows by spaces;
              #  and then all those block-strings by newlines
       '¨›   '#  Push dictionary string "fox"
          å   #  Check if the string contains "fox"
           ≠  #  Invert this check, to exclude those in the filter
              # (after which the list of valid permutations are output implicitly)

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why '¨› is "fox".

Maple, 202 bytes

a:=1,3,9,11;b:=0,1,4,5;s:=[[$0..2]$3];p:=s*~b[2..];q:=s*~(-1,3,4);for A in Iterator:-Permute([F$5,O$6,X$5])do`if`(`and`(seq(seq(seq(seq(A[j+~k])<>(F,O,X),k=[p,q,-q,-p][i]),j=a[i]+~b),i=1..4)),A,NULL)od;

Ungolfed:

a:=1,3,9,11;                            # top left cell of each quadrant
b:=0,1,4,5;                             # offsets to stay, or go right, down, downright
t[1]:=[[0,1,2],[0,4,8],[0,5,10]];       # right, down, downright (from first quadrant)
t[2]:=[[0,-1,-2],[0,3,6],[0,4,8]];      # left, down, downleft (from 2nd quadrant)
t[3]:=[[0,1,2],[0,-3,-6],[0,-4,-8]];    # up, right, upright (from 3rd quadrant)
t[4]:=[[0,-1,-2],[0,-4,-8],[0,-5,-10]]; # up, left, upleft (from 4th quadrant)
for A in Iterator:-Permute([F$5,O$6,X$5])do # all valid boards as 1D Arrays
`if`(`and`(                             # look for all 48 places FOX can be
   seq(
       seq(
           seq(
               seq(A[j+~k])<>(F,O,X),   # test not F,O,X starting at j going in kth direction
               k=t[i]                   # in t[i]                  
              ),
           j=a[i]+~b                    # place to start in quadrant i
          ),
       i=1..4)                          # for each quadrant i
      ),
  A,NULL);                              # if none match output the 1D Array
od;

The board is in a 1D Array (1-relative indexing). For each quadrant of 4 cells in the (2D) board, e.g. top left, we look for F,O,X from each of the 4 cells going in each of 3 directions, e.g., right, down, downright from the top left. The t[i] are the offsets from the current cell to look in for the ith quadrant for each direction.

The 4 lines generating t[1]..t[4] can shortened to

s:=[[$0..2]$3];  # start with [[0,1,2],[0,1,2],[0,1,2]]
p:=s*~b[2..];    # multiply triples by 1, 4, 5 resp. to get t[1]
q:=s*~(-1,3,4);  # multiply triples by -1, 3, 4 resp. to get t[2]
t:=[p,q,-q,-p];  # t[3] = -t[2], t[4] = -t[1]

JavaScript (V8), 141 bytes

f=(s=!0+'FX'+1e5,q)=>s[43]?/(.)(.*\1){12}|^(....)*.?(F(O|....O....)|..F..O..)X|F...O...X/.test(s)||print(q):[..."FXO"].map(c=>f(c+s+c,[q]+c))

Try it online!


Original version but seems to fail, not sure why

JavaScript (V8), 142 bytes

f=(s='')=>s[15]?[...s].some((c,i,A)=>!--t[c]|A.some((d,j)=>c<(m=i/4^j/4||s[(i+j)/2])&m<d),t={F:6,X:6,O:7})||print(s):[..."FXO"].map(O=>f(s+O))

Try it online!

Decide if pos i, mid(i,j), j build fox

JavaScript (V8), 144 bytes

Similar to the other version, but much slower.

f=(s=1e3+"FXFX"+1e3,q)=>s[43]?/(.)(.*\1){12}|^(....)*.?(F(O|....O....)|..F..O..)X|F...O...X/.test(s)||print(q):[..."FXO"].map(c=>f(c+s+c,[q]+c))

Try it online!


JavaScript (V8), 146 bytes

Prints the 255,304 flattened winning grids.

f=(s={}+0,q)=>s[47]?/(F|X)(\w*\1){5}|(O\w*){7}|^(....)*.?(F(O|....O....)|..F..O..)X|F...O...X/.test(s)||print(q):[..."FXO"].map(c=>f(c+s+c,[q]+c))

Try it online!

Method

We generate all 43,046,721 4x4 grids filled with F, O, X as flattened strings.

For each grid, we build the string consisting of the flattened grid, followed by the separator string "[object Object]0", followed by the reversed flattened grid.

For instance, the grid FFFFFXXXXXOOOOOO is turned into:

FFFFFXXXXXOOOOOO[object Object]0OOOOOOXXXXXFFFFF

For a grid to be valid, none of the following regular expression parts should match this string:

  1. (F|X)(\w*\1){5} → more than 5 F or 5 X on an an alphanumeric substring
  2. (O\w*){7} → more than 6 O on an an alphanumeric substring
  3. ^(....)*.?F(O|....O....)X → horizontal or diagonal FOX, starting at 1st or 2nd character of a row
  4. ^(....)*.?..F..O..X → anti-diagonal FOX, starting at 3rd or 4th character of a row
  5. F...O...X → vertical FOX (anywhere)

Note that parts 3 and 4 are actually factorized as:

^(....)*.?(F(O|....O....)|..F..O..)X

Output

This is the beginning and the end of the output of a slightly modified version that prints the number of results:

output

Retina 0.8.2, 99 bytes


4$*
1
;;;4$*
+%1`1
F$'¶$`O$'¶$`X
A`(F|X)(.*\1){5}|(O.*){7}|(F|X)(.)*O(?<-5>.)*(?!\4)[FX](?(5)^)
;

Don't Try it online! - far too slow. Instead, Try it online! a 3×3 variant. Outputs a list of flattened boards. Explanation:


4$*
1
;;;4$*

Generate the string ;;;1111;;;1111;;;1111;;;1111.

+%1`1
F$'¶$`O$'¶$`X

Generate all the possibilities of filling 1s with F, O or X.

A`(F|X)(.*\1){5}|(O.*){7}|(F|X)(.)*O(?<-5>.)*(?!\4)[FX](?(5)^)

Exclude those with 6 Fs or Xs (shamelessly stealing @Arnauld's approach as both the ones I tried saved no bytes), 7 Os, or FOX or XOF spaced equidistantly using a .NET balancing group (compare my answers to Tic-Tac-Toe - X or O? or All Possible Ties in Tic-Tac-Toe). (Three semicolons are needed to stop the regex wrapping inappropriately.)

;

Delete the semicolons.