First of all lets get the 0-based index of the position, the board is a list which means it has 0-based indexing, so the position should as well.

``````
def player_turn(board, position, player):
position -= 1 # 0-based index
``````

The board contains a list of rows, and the position should tell us which row we need to edit, lets have a look at which position value corresponds to each row: Drawing out a table like this helps us see a pattern, in this case we can say that row = position // 3.

``````
def player_turn(board, position, player):
position -= 1 # 0-based index
row_pos = position // 3
``````

Next is the column, again lets draw up a table: Similar to the row table before, theres another pattern here, although it is less noticeable. Note how the numbers in column 0 are all exact multiples of 3, and column 1 positions are all 1 above that, this can be extrapolated to column = position % 3.

``````
def player_turn(board, position, player):
position -= 1 # 0-based index
row_pos = position // 3
column_pos = position % 3
``````

Now that we know the row and column to edit, lets check if there's already an 'X' or 'O' there, the question says a '#' is an 'empty' cell so:

``````
def player_turn(board, position, player):
position -= 1 # 0-based index
row_pos = position // 3
column_pos = position % 3
if board[row_pos][column_pos] != "#":
return print("Invalid move, try again.")
``````

Chaining a 'return' and a 'print' statement here is entirely optional, as doing print() and then return is just as valid.
Now that we've done that check, we can update the board, no 'else' statement necessary.

``````
def player_turn(board, position, player):
position -= 1 # 0-based index
row_pos = position // 3
column_pos = position % 3
if board[row_pos][column_pos] != "#":
return print("Invalid move, try again.")
board[row_pos] = board[row_pos][:column_pos] + player + board[row_pos][column_pos + 1:]
``````
Back to 101 Index