Nicholas Brittain Nicholas Brittain - 1 month ago 8
Python Question

Basic Tic Tac Toe Game: Somehow, when I call a function in a different file, that function changes a list in the original file

main.py:

import ai
import utility


def main(): # {
# Main Loop Variables
win_condition = '*'
player_turn = True
turn = 0
board_state = [['*', '*', '*'],
['*', '*', '*'],
['*', '*', '*']]
# Start of Main Turn Loop
while (win_condition == '*') and (turn <= 9):
turn += 1
print('Turn: ', turn)
player_input = 0

for y in range(3): # Prints Board
print(board_state[y][0] + board_state[y][1] + board_state[y][2])

if player_turn: # Player Moves
player_turn = not player_turn

player_input = int(input("Enter 1, 2, 3...9")) - 1

board_state[int(player_input) // 3][int(player_input) % 3] = 'X'
else: # Computer moves
print('Thinking...')
player_turn = not player_turn

ai_move = ai.random(board_state)

print(utility.list_possible_states(board_state))
print(board_state)

board_state[int(ai_move) // 3][int(ai_move) % 3] = 'O'

win_condition = utility.test_for_win(board_state)

# }
main()


ai.py:

import utility
from random import randrange


def random(board_state):
possible_moves = utility.list_possible_moves(board_state)

return possible_moves[randrange(len(possible_moves))]


utility.py:

def test_for_win(test_board): # {
return_value = '*'
for y in range(3): # Tests for horizontal win.
test_set = set(test_board[y])
if len(test_set) == 1 and not ('*' in test_set):
print("The winner is " + test_board[y][1])
return_value = test_board[y][1]
break

for x in range(3): # Tests for vertical win.
test_list = []
for y in range(3):
test_list.append(test_board[y][x])
test_set = set(test_list)
if (len(test_set) == 1) and not ('*' in test_set):
print("The winner is " + test_board[1][x])
return_value = test_board[1][x]
break

test_list = []
for xy in range(3): # Tests for diagnoal win (-)
test_list.append(test_board[xy][xy])
# print(test_list, len(test_list)) #Debugging
if (len(set(test_list)) == 1) and not ('*' in test_list):
print("The winner is " + test_board[1][1])
return_value = test_board[1][1]

test_list = []
for xy in range(3): # Tests for diagnoal win (+)
test_list.append(test_board[xy][2 - xy])
# print(test_list, len(test_list)) #Debugging
if (len(set(test_list)) == 1) and not ('*' in test_list):
print("The winner is " + test_board[1][1])
return_value = test_board[1][1]

return return_value
# }


def list_possible_moves(board): # {
possible_moves = []
for move_counter in range(9): # Prints Possible Moves to List
if board[move_counter // 3][move_counter % 3] == '*':
possible_moves.append(move_counter)

return possible_moves
# }


def list_possible_states(board): # {
possible_moves = list_possible_moves(board)

xoro = '*'
if (len(possible_moves)) % 2 == 0:
xoro = 'O'
else:
xoro = 'X'

possible_states = []
for move in possible_moves:
board_s = board
board_s[move // 3][move % 3] = xoro
possible_states.append(board_s)
del board_s

return possible_states
# }


If I comment out this print statement in main.py:

print(utility.list_possible_states(board_state))


This prints out:

[['X', '*', '*'], ['*', '*', '*'], ['*', '*', '*']]


But if I don't comment out that line it prints out:

[['X', 'O', 'O'], ['O', 'O', 'O'], ['O', 'O', 'O']]


How is the variable board_state being changed in the function main() from the function list_possible_states()? And if you have any suggestions / see any major errors don't hesitate to bring them up as well.

*edit to utility.py{

import copy

def test_for_win(test_board): # {
return_value = '*'
for y in range(3): # Tests for horizontal win.
test_set = set(test_board[y])
if len(test_set) == 1 and not ('*' in test_set):
print("The winner is " + test_board[y][1])
return_value = test_board[y][1]
break

for x in range(3): # Tests for vertical win.
test_list = []
for y in range(3):
test_list.append(test_board[y][x])
test_set = set(test_list)
if (len(test_set) == 1) and not ('*' in test_set):
print("The winner is " + test_board[1][x])
return_value = test_board[1][x]
break

test_list = []
for xy in range(3): # Tests for diagnoal win (-)
test_list.append(test_board[xy][xy])
# print(test_list, len(test_list)) #Debugging
if (len(set(test_list)) == 1) and not ('*' in test_list):
print("The winner is " + test_board[1][1])
return_value = test_board[1][1]

test_list = []
for xy in range(3): # Tests for diagnoal win (+)
test_list.append(test_board[xy][2 - xy])
# print(test_list, len(test_list)) #Debugging
if (len(set(test_list)) == 1) and not ('*' in test_list):
print("The winner is " + test_board[1][1])
return_value = test_board[1][1]

return return_value
# }


def list_possible_moves(board): # {
testtest = copy.copy(board)
possible_moves = []
for move_counter in range(9): # Prints Possible Moves to List
if testtest[move_counter // 3][move_counter % 3] == '*':
possible_moves.append(move_counter)

return possible_moves
# }


def list_possible_states(board): # {
test = copy.copy(board)
possible_moves = list_possible_moves(test)

xoro = '*'
if (len(possible_moves)) % 2 == 0:
xoro = 'O'
else:
xoro = 'X'

possible_states = []
for move in possible_moves:
board_s = test
board_s[move // 3][move % 3] = xoro
possible_states.append(board_s)
del board_s

return possible_states
# }


}

Answer

The problem with your list_possible_states function is that the line board_s = board doesn't do what you seem to expect it to do. It doesn't copy the board list. It just creates a new reference to the existing list object. When you change the list's contents, you'll see the change through both references.

The fact that list_possible_states is defined in a different module than where you call it from doesn't matter, since it's not modifying a global variable. You're passing it a reference to the board, and it's modifying it by mistake.

If you want to make a copy, use the deepcopy function from the copy module in the standard library:

board_s = copy.deep_copy(board)

Just using copy.copy (as you're doing in your update) is not enough, since board is a nested list and you need new lists at all levels.