Ruby, 305 bytes
19683.times{|i|c=(i%3).to_s
s=(9**8+i/3*6562).to_s(3)
w=0
t=(0..3).map{|j|r=s[1+j*2,9]
w|=1<<r[0..2].sum%8|1<<(c+r[j/2]+r[4+j/2]).sum%8
[r,r.reverse].min}.min
v=t[0..2]+$/+t[7]+c+t[3]+$/+t[6]+t[5]+t[4]
w&=65
b=v.sum%51
w<65&&b>w/64&&b<3-w%2&&t==s[1,9]&&puts(v.tr("012","X.O"),w<1?v.include?(?1):w%31,"")}
This works similiarly to the other answers in that it generates all 3**9
boards then filters out the valid ones. Internally, we use ternary numbers where 0=X 1=. 2=O
in the output. Iterate c
through the 3 possible values for the centre, and s
through the 3**8 = 6561
values for the perimeter. Before converting i/3
to a string representation of a ternary number, we multiply by 6562
to duplicate all digits, and add 3**16
to start the number with a 1, in order to ensure there are leading zeros where applicable. w
is the win condition - set this to zero.
For each board Iterate through 4 rotations of the digits in s
to find the lexically lowest version of the current 8 digit ternary number representing the perimeter. At the same time, add the ascii values of the first 3 digits (top row of current rotation) and use this to check for a win. Also, add the ascii values of c
and a pair of diametrically opposite digits to check if there is a win through the centre.
Check if the output is valid - If both 1's bit and 64's bit of w
are both set, both sides win - this is not valid. Check the balance of X's and O's (if there is no winner yet it can be either equal X and O or one more X - but if the game is won, there is only one possible value, as the winner must have gone last.) To avoid showing different rotations of the same board, only output if the lexically lowest version of the perimeter corresponds to the current value of s[2,9]
.
Output the board, sustituting the symbols tr("012","X.O")
. The game status is shown below the board. If w=0, this is true
if there are still empty squares (game still in progress) and false
if the board is full. If w
is nonzero we output 1
if player 1 (X) has won or 64%31==2
if player 2 (O) has won.
Ungolfed
19683.times{|i| #check 3**9 possibilities
c=(i%3).to_s #centre square: 0=X, 1=. 2=O
s=(9**8+i/3*6562).to_s(3) #perimeter:multiply i/3 by 3**8+1 to duplicate digits, add 3**16 to give a 1 at left side to ensure leading zeros
w=0 #set w=0 to clear wins (1´s bit holds win for X, 64´s bit holds win for O)
t=(0..3).map{|j| #build an array of 4 different rotations of the perimeter
r=s[1+j*2,9] #by taking different 9-character slices of s
w|=1<<r[0..2].sum%8| #add ascii codes mod 8 for top row of current rotation, take sum modulo 8 to give sum of digits. set a bit in w (we only care about bits 0 and 6)
1<<(c+r[j/2]+r[4+j/2]).sum%8 #do the same for one of the 4 lines through the centre. if j/2=0 check diagonal, if j/2=1 check horizontal/vertical.
[r,r.reverse].min}.min #add to the array the lexically lowest version(forward or reverse) of the current rotation. When the loop ends, find the lexically lowest version overall and assign to t.
v=t[0..2]+$/+t[7]+c+t[3]+$/+t[6]+t[5]+t[4]#format the output into a square 012\n 7c3\n 654
w&=65 #clear bits 1 through 5 of w, leave only bits 0 and 6 which are the ones of interest.
b=v.sum%51 #valid values of sum of ascii codes in output are 461 (equal 0's and 2's) and 460 (one more 0 than 2). Take modulo 51 to reduce these values to 1 and 2
w<65&& #if a maximum of one player has a winning line (not both) and
b>w/64&& #b must be 1 or 2 (only 2 permitted if O's won)
b<3-w%2&& #b must be 1 or 2 (only 1 permitted if X's won)
t==s[1,9]&& #s[2,9] is the lexically lowest version of the current board (to avoid duplicates) then
puts(v.tr("012","X.O"), #output the board, subsituting internal "012" characters for external "X.O"
w<1?v.include?(?1):w%31,"") #if nobody won, output true if game in still in play (1's present on board) else false
} #if there is a winner, output (player) 1 for X, (player) w%31=2 for O. Also output a blank line.
Checking scheme
The diagrams below show the scheme for rotation (and win checking, in capitals). The diagrams are shown unrotated. The four different rotations are taken as substrings from the double copy of i/3
, with the 3 consecutive capital letters on the perimeter of each diagram (the "top" per current rotation) being the first 3 characters in the 9-character substring. For each rotation, 9-character reversal (diagonal flip about A-E or C-G axis) is also tried. The board is only output if the current value of i/3
is the lexically lowest of all rotations and mirrors.
ABC abC aBc Abc
hZd hZD hZd HZD
gfE GfE GFE Gfe
2Welcome to PPCG! Every challenge on PPCG has to have a winning criterion. What is the criterion for this challenge? Is it [tag:code-golf] where the program with the shortest bytecount wins? – user41805 – 2017-05-27T06:50:12.957
If you confirm the winning criterion, this should be opened again soon. I haven't counted the positions yet, but I was wondering, is the empty board a) required b) prohibited c) optional? – Level River St – 2017-05-27T11:01:38.737
Just realised, you already said shortest code wins. Editing in code-golf tag. We usually score by number of bytes here, because there are some languages specifically invented to use multibyte characters to shorten code, which isn't very interesting. – Level River St – 2017-05-27T11:05:45.943
@StephenS That's a different thing. A byte can have any value from 0-255. This website can only display single byte characters in the range 32-126 plus a few others. 256 byte codepages have been around for ages and I myself have posted C answers in codepage 437. UTF-8 was invented because these codepages didn't have enough characters for all natural languages, but UTF-8 is not designed to handle non-text data with random bytes. I was referring to languages like https://esolangs.org/wiki/Sclipting which extensively uses multibyte asian characters to exploit the loophole of scoring in characters
– Level River St – 2017-05-27T14:14:06.100@LevelRiverSt ah, gotcha. Thanks for the info :) – Stephen – 2017-05-27T14:16:01.587
There were many boring kolmorogorov complexity answers in Sclipting and similar which led PPCG to change to scoring by bytes to close this loophole. I disagree with your edit as the OP's stated intention was scoring in characters and your edit has made it more ambiguous.It is up to OP to decide (and OP should clarify), but for this particular challenge (as it is not kolmogorov) I suspect it will make little difference whether scoring by bytes or characters is used. – Level River St – 2017-05-27T14:20:11.287
OP, feel free to rollback my character->byte edit. – Stephen – 2017-05-27T14:54:48.263
@ThomasR : Whats a duplicate ? Mirrored, Rotated ? – LMD – 2017-05-27T15:41:08.543
@user7185318
Positions are considered equal if one can be obtained from the other by rotation or mirroring
– Level River St – 2017-05-28T02:38:32.917Invalid positions (https://codegolf.stackexchange.com/questions/102985/is-this-tic-tac-toe-board-valid) must not be printed, and all in-game states to reach valid positions must be printed, right? And must outcome be printed?
– user202729 – 2017-05-28T05:39:41.293Related? – Matthew Roh – 2017-05-28T09:48:57.243
Related but uses two different colours instead of three (as in this challenge) – Beta Decay – 2017-05-28T19:34:48.650