Anti Tic-Tac-Toe

The rule of Anti Tic-Tac-Toe is like Tic-Tac-Toe.

The rules are:

• There is a 3*3 grid, the squares in the grid are labeled 1 to 9:
123
456
789

• X goes first.
• The most important rule: If there's a line, a column or a diagonal with all squares non-blank and the squares are not all the same, the player with more squares in the line, column or diagonal wins and the game stops. For example, if the grid is like this (. stands for empty cells):
XOX
...
...


Then X wins.

If there are more than one lines like this and their winners are different, the game will stop and the state of the game will be bad.

For example:

OXO
X..
X.O


Your task is to input a list of integers from 1 to 9, each integer represents the cell the current player plays. The integers are guaranteed to be different from each other. You have to output the state of the game: X if X wins, O if O wins, . if the game hasn't stopped yet, ! if the game is bad.

Obviously, ties can't occur because if all the squares are taken, there will always be at least one row, column or diagonal that satisfies the winning condition.

You can use any forms of I/O, such as standard I/O, file I/O or function.

Examples

1 -> .
124578 -> .
123 -> X
1539 -> O
214379 -> O
294371 -> !


This is , so code in the fewest bytes.

• X has a trivial win. What would be more interesting is if XXO was a win for O instead...
– Neil
Commented May 7 at 23:47
• Suggest case 124578 => . fulfilled but same is nothing
– l4m2
Commented May 8 at 0:48
• @Neil I didn't realize that before I posted the challenge, let's leave that for another challenge Commented May 8 at 4:59
• Do we need to check whether the game should have ended beforehand? For example if the sequence is 123497 it looks like a bad game but actually the game should have ended at 123 with a win for X. Commented May 8 at 12:26
• As a note: name the cells as 816/357/492 (aka. 3x3 magic square) instead of 123/456/789 could be useful when implement tic-tac-toe game.
– tsh
Commented May 9 at 5:09

JavaScript (ES6), 124 bytes

Expects a list of integers.

a=>".XO!"[a.some(b=v=>[7,56,73,84,146,273,292,448].map(m=q=>m|=q==(p=b&q)+(q&=b>>9)&&p*q&&-~!(q&~-q),b|=1<<v-1+(a^=9))|m)*m]


Try it online!

Commented

a =>                     // a[] = input array
".XO!"[                  // lookup string for output
a.some(b =             // b = board bitmask, initially zero'ish
v =>                   // for each value v in a[]:
[                    //   lookup array of matching patterns:
7,                 //     000 000 111 - 1st row (1-2-3)
56,                //     000 111 000 - 2nd row (4-5-6)
73,                //     001 001 001 - 1st column (1-4-7)
84,                //     001 010 100 - anti-diagonal (3-5-7)
146,               //     010 010 010 - 2nd column (2-5-8)
273,               //     100 010 001 - diagonal (1-5-9)
292,               //     100 100 100 - 3rd column (3-6-9)
448                //     111 000 000 - 3rd row (7-8-9)
].map(m =            //   m = score bitmask, initially zero'ish
q =>                 //   for each value q in the above array:
m |=               //     update m
q ==             //     if q is equal to the sum of ...
(p = b & q) +    //     ... the pattern p of O's masked with q
(q &= b >> 9) && //     and the pattern q of X's masked with q
p * q &&         //     and neither p nor q is zero, then do:
-~!(q & ~-q),    //       m |= 2 if there's a single bit set in q
//       (we figure this out by clearing the LSB)
//       m |= 1 otherwise
b |= 1 <<          //     start by updating the board b:
v - 1 +          //       do either b |= 1 << v - 1 for a new 'O'
(a ^= 9)         //       or b |= 1 << v + 8 for a new 'X' (*)
) | m                //   end of map(); yield m
) * m                  // end of some(); yield m
]                        // get the m-th character from the lookup string


(*) Because we re-use a to store the shift offset, this fails if the input array is a singleton. But in that case, only one bit will be set in b and no 3-bit pattern will be found anyway.

Jelly,  45  42 bytes

9s3ZU,ƊjJị"$€$⁸iⱮⱮẠƇḂĠ€Ẉ€ṖƇµƤȯ/Q’ḄSị“XO!.”


A monadic Link that accepts the list of moves from $$\[1,9]\$$ and yields one of the characters .XO! for ongoing, X-wins, O-wins, and bad-game, respectively.

Try it online!

How?

Analyses the state of the board for each prefix of the moves producing data that can identify the game's win-state at that point, then outputs the appropriate character.

The rows, columns, and diagonals of the board at each point are formed from 0 (empty), odd-positives (X), and even-positives (O) by using their index in the input. If any of these triples contain 0 they are removed and any remaining ones are converted to their parities (X:1, O:0), then their indices are grouped by their values and the lengths of these groups are taken (e.g. for XOX: [1,0,1] => [[2],[1,3]] => [1,2]).

The results for all these fully-filled lines are deduplicated giving the following possible lists of lists which are decremented, converted from binary, summed, and then used to index into XO!.:

group lengths deduplicated decrement; convert from binary sum index into XO!. meaning
[[1,2]] [1] 1 X X won
[[2,1]] [2] 2 O O won
[[1,2],[2,1]] [1,2] 3 ! bad game
[[2,1],[1,2]] [2,1] 3 ! bad game
[] 0 0 . ongoing game
9s3ZU,ƊjJị"$€$⁸iⱮⱮẠƇḂĠ€Ẉ€ṖƇµƤȯ/QḄSị“XO!.” - Link: list of integers, Moves
µƤ             - for each prefix of {Moves}:
9s3                                       -   [1..9] split into threes
ZU,Ɗ                                   -   rotate a quarter and pair
jJị"$€$                            -   join with their diagonals
ẠƇ                      -   keep if all non-zero (all found)
Ḃ                     -   mod-2
Ġ€                   -   group indices of each by values
Ẉ€                 -   length of each of each
ṖƇ               -   keep if pop (discards [3] (XXX or OOO))
ȯ/            - reduce by logical OR (first non-empty result, or [] if none found)
Q           - deduplicate
’          - decrement
Ḅ         - convert from binary
S        - sum
ị“XO!.” - modular-index into "XO!."


JavaScript (Node.js), 145 bytes

e=>'.OX!'[e.some(o=x=>v=/001/.test(s='123456789147258369159753'.replace(/.../g,'$&$& ',o[x]=++i%2).replace(/./g,i=>o[i]))+2*/011/.test(s),i=0)*v]


Try it online!

05AB1E, 55 52 bytes

9Å0Iv˜NÈ>y<ǝ3ôÐø«DÂ‚€Å\«ʒĀP}D<‚ΘO2QO2β".XO!"ès}\)Ù1è


Explanation:

9Å0             # Start with a list of 9 0s
I            # Push the input-integer
v           # Pop and loop over its digits y:
˜          #  Flatten the current list/matrix
N         #  Push the 0-based loop-index
È>       #  %2+1 so even indices become 1 and odd become 2
y<     #  Push the current y, and decrement it to a 0-based index
ǝ    #  Insert the N%2+1 at index y-1 into the list
3ô  #  Then convert the list to a 3x3 matrix
Ðø«DÂ‚€Å\« #  Get a list of all rows, columns, and (anti-)diagonals:
Ð          #   Triplicate the current matrix
ø         #   Pop one copy, and zip/transpose it; swapping rows/columns
«        #   Merge the lists of rows and columns together
D       #   Duplicate it
Â      #   Bifurcate it; short for Duplicate & Reverse copy
‚     #   Pair the two together
€Å\  #   For each inner 3x6 matrix: map it to its main diagonal
« #   Merge the diagonals to the list of rows and column
ʒ  }       #  Filter this list of triplets by:
Ā         #   Check for each inner value whether it's NOT 0 (thus 1 or 2)
P        #   Check if all three are truthy
D      #  Duplicate this list of valid lines
<     #  Decrease each inner value in the copy by 1
‚    #  Pair the two lists of triplets together
Θ   #  Check for each inner-most values whether they're equal to 1
O  #  Sum each inner-most triplet
2β         #  Convert it from a base-2 list to a base-10 integer
".XO!"è  #  0-based index that into string ".XO!"
s #  Swap so the matrix is at the top again for the next iteration
}\          # After the loop: discard the matrix
)         # Wrap all intermediate results into a list
Ù1è      # Get the first non-"." result if present, or "." otherwise:
Ù        #  Uniquify it
1è      #  Pop and leave the modular 0-based 1st item
# (which will be output implicitly as result)


Charcoal, 77 bytes

Ｐ.≔Ｅχ⁰θＦＳ¿¬ⅈ«§≔θＩι∨±Σθ¹≔ΦＥΦ⁺⪪Φθλ³⁺Ｅ³✂θ⊕κχ³⟦✂θ¹χ⁴✂θ³⊖χ²⟧‹↔ΣκΣ↔κΣκκη¿η§!OX⁺⌊η⌈η


Try it online! Link is to verbose version of code. Explanation:

Ｐ.


Output a . without moving the cursor. This will remain if the game is unfinished.

≔Ｅχ⁰θ


Start with an empty board. (The extra element is ostensibly due to 1-indexing, but it's also golfier, since it doesn't require a separator.)

ＦＳ¿¬ⅈ«


Loop though the input string while the game is still ongoing.

§≔θＩι∨±Σθ¹


Place a 1 or -1 as appropriate in the given board position.

≔ΦＥΦ⁺⪪Φθλ³⁺Ｅ³✂θ⊕κχ³⟦✂θ¹χ⁴✂θ³⊖χ²⟧‹↔ΣκΣ↔κΣκκη


Score only those horizontal, vertical and diagonal slices of the board that contain both 1s and -1s (determined by comparing the absolute value of the sum with the sum of the absolute values), keeping only those slices with a non-zero score.

¿η§!OX⁺⌊η⌈η


If there were any non-zero scores then output the appropriate symbol, which will also cause the rest of the input to be skipped. Since there could be more than one 1 or -1, the sum of the minimum and maximum score is taken, which will be 0 for a bad game, 2 if X wins, or -2 (which cyclically wraps to 1) if O wins.

• input: 124578, output: X, but . is expected Commented May 8 at 23:23
• @None1 Oops, I hadn't thought to check the case of a line containing three moves by the same player, even though it was one of the test cases...
– Neil
Commented May 8 at 23:36
• I think you didn't check that a winning row, column or diagonal does not contain a blank square. Commented May 9 at 4:43
• @None1 An input of 123 gives an output of X as expected, ignoring the X.. and XO. and only scoring the XOX. Or am I missing something?
– Neil
Commented May 9 at 6:49
• I say your previous program Commented May 9 at 10:55