with recursive rnd_move(move) as (
select *, random() rnd from generate_series(1, 9) move
), winning_positions(a, b, c) as (
values (1, 2, 3), (4, 5, 6), (7, 8, 9),
(1, 4, 7), (2, 5, 8), (3, 6, 9),
(1, 5, 9), (3, 5, 7)
), game as (
select 'O' as who_next, ARRAY['.', '.', '.', '.', '.', '.', '.', '.', '.'] as board
union
(
select case when who_next = 'X' then 'O' else 'X' end as who_next,
board[:move-1] || who_next || board[move+1:]
from game, rnd_move where board[move] = '.' order by rnd limit 1
)
), game_with_winner as (
select *, lag(a is not null) over () as finished, lag(who_next) over () as who
from game left join winning_positions on
board[a] != '.' and board[a] = board[b] and board[a] = board[c]
)
select array_to_string(board[1:3] || chr(10) || board[4:6] || chr(10) || board[7:9] || chr(10), '') board,
case when a is not null then who || ' wins' end as winner
from game_with_winner where not finished;