170 MB seems like a lot. Aren't there less than 1,000 different states? My first naive idea would be to link to a static version of the gameboard further down the page like href="xo-------".
Each tile can take on 3 states, x, o, and the empty state. 3^9 = 19683. Of course, this is also counting many invalid states where a player would have won and symmetries of the board.
HN lunch challenge: Many here have noted one should be able to do this with far fewer total states by ignoring the order of the moves and focusing only on what the next board state needs to be. Who can generate the smallest working HTML file first? Preferably with how they generated the HTML too.
It seems that there is a missed optimization where the order of moves after. Assuming that X always goes first for every board state you know who goes next and the path that you took to get there doesn't matter. So you don't have to store the full sequence of moves that lead to the state, just the state itself.
> Popovertarget passes every choice made by the player, for example: x1-o2-x3 means X chooses the first position, O chooses the second and X chooses the third, and so on.
I think this is going to lead to a lot of redundant states, isn't it, because there are multiple paths to get to a particular board layout?
> It all started when Chrome implemented popovers in HTML [...] we found some pretty cool vectors that allow you to exploit hidden inputs and meta tags
So if these vectors/sploits were patched out, the game wouldn't work anymore?
I'm not sure I agree that there's any exploit here. The crux of the claim seems to be that JavaScript code in an onbeforetoggle attribute is "unlikely to be filtered by a WAF".
In any case, this game doesn't involve any of that, it just uses popovers as fancy internal links.
This Python scripts generates a playable HTML-only Tic Tac Toe game. The generated file is 560KB, that is more than 300 times smaller. Also it works in any browser not just Chrome, probably even TUI browsers like w3m or links for that matter ^^. It generates all and only the valid game states, that is 5478 boards. It may be optimized further.
from copy import deepcopy
opponent = {'X':'O','O':'X'}
generated_boards = []
def board_id (board):
return ''.join([''.join(row) for row in board])
def winning (board, player):
return player == board[0][0] == board[0][1] == board[0][2] \
or player == board[1][0] == board[1][1] == board[1][2] \
or player == board[2][0] == board[2][1] == board[2][2] \
or player == board[0][0] == board[1][0] == board[2][0] \
or player == board[0][1] == board[1][1] == board[2][1] \
or player == board[0][2] == board[1][2] == board[2][2] \
or player == board[0][0] == board[1][1] == board[2][2] \
or player == board[0][2] == board[1][1] == board[2][0]
def full (board):
return '_' not in [cell for row in board for cell in row]
def print_board (board, player):
print(f'<hr id="{board_id(board)}">', end="")
links = True
if winning(board, opponent[player]):
links = False
print("WIN:")
elif full(board):
links = False
print("DRAW:")
for row in range(0, 3):
for col in range(0, 3):
if links and board[row][col] == '_':
board[row][col] = player
print(f'<a href="#{board_id(board)}">_</a>', end="")
board[row][col] = '_'
else:
print(f'{board[row][col]}', end="")
if row != 2: print('')
return links
def next_boards (board, player):
boards = []
for row in range(0, 3):
for col in range(0, 3):
if board[row][col] == '_':
board[row][col] = player
bid = player + board_id(board)
if bid not in generated_boards:
boards.append(deepcopy(board))
generated_boards.append(bid)
board[row][col] = '_'
return boards
def print_boards (board, player):
if not print_board(board, player): return
for board in next_boards(board, player):
print_boards(board, opponent[player])
print("<pre>")
print_boards([['_' for _ in range(0, 3)] for _ in range(0, 3)], 'X')
Funny things: there are 4578 valid boards. Of those, 16 are draw positions, and 942 are winning positions. Among the winning positions, 626 are winning position for the player who starts, and 316 for the second player.