My game is checking for winner "O" and not both for some reason... Any idea why?

Godot Version

godot 4.3

Question

Hello, I've been working on my Ultimate Tic Tac Toe game and I've been running into an issue where it is ok tracking wins for specific columns for "O" and not both or "X" and "O" anyone have any ideas?

Picture:

Look, see it showed that “O” wins but “X” didn’t win the specific column either, and this is what it shows in the Output:


It like barely ever checks for X when it’s supposed to check for both…
Here is the code for the game scene “AIminiboard.gd”

AIminiboard.gd:

extends Control

var current_player = "X"  # Player is "X", AI is "O"
var cells = []  # Store references to buttons
var is_ai_turn = false  # Track if it's AI's turn
var small_board_wins = []  # Track wins for small boards
var overall_winner = ""  # Track overall winner
var stats: Dictionary = load_stats()  # Load or initialize stats at the start
var active_column = -1  # Initialize with -1 to allow any board on the first move

@onready var winner_label = $WinnerLabel  # General winner label
@onready var ai_home_button = $AIHomeButton  # Reference to the AIHomeButton
@onready var ai_rematch_button = $AIRematchUltimateButton  # Reference to the rematch button
@onready var stating_labels = [
	$Stating1, $Stating2, $Stating3, $Stating4, $Stating5, $Stating6, $Stating7, $Stating8, $Stating9
]

# Declare labels for each column
var x_win_labels = []
var o_win_labels = []
var tie_labels = []  # Array to hold tie labels for each column

func _ready():
	for label in stating_labels:
		label.visible = false
	
	# Hide playing labels at the start of the game
	$XIsPlaying.visible = true
	$OIsPlaying.visible = false
	
	cells.clear()
	small_board_wins = [null, null, null, null, null, null, null, null, null]  # Initialize small board wins
	winner_label.visible = false
	ai_home_button.visible = false  # Hide the AIHomeButton initially
	ai_rematch_button.visible = false  # Hide rematch button initially

	# Initialize labels for each column
	for i in range(1, 10):
		x_win_labels.append(get_node("XWinLabelColumn" + str(i)))  # Reference to XWinLabelColumn1 to 9
		o_win_labels.append(get_node("OWinLabelColumn" + str(i)))  # Reference to OWinLabelColumn1 to 9
		tie_labels.append(get_node("ItsATieLabelColumn" + str(i)))  # Reference to ItsATieLabelColumn1 to 9
		x_win_labels[i - 1].visible = false  # Initialize visibility
		o_win_labels[i - 1].visible = false
		tie_labels[i - 1].visible = false  # Initialize visibility for tie labels

	# Initialize cells for columns and connect all buttons
	for column_index in range(1, 10):  # Columns 1 to 9
		var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index))
		for cell_index in range(9):  # Cells 1 to 9 in each column
			var cell: Button = grid_container.get_child(cell_index)
			cells.append(cell)
			cell.connect("pressed", Callable(self, "_on_AICell_pressed").bind(column_index - 1, cell_index))

	current_player = "X"  # Player starts first
	is_ai_turn = false
	var button10_click_player: AudioStreamPlayer = $Button10ClickPlayer  # Make sure this node exists in the scene
	button10_click_player.stream = load("res://sounds/click.ogg")  # Set the path to your sound file

	
# Function to save stats
func load_stats() -> Dictionary:
	var file_path = "user://Stats.cfg"
	if not FileAccess.file_exists(file_path):
		print("Stats file does not exist. Initializing with default values.")
		return {
			"Ultimate_Tic_Tac_Toe_Yourself_Won": 0,
			"Ultimate_Tic_Tac_Toe_Yourself_Lost": 0,
			"Ultimate_Tic_Tac_Toe_AI_Won": 0,
			"Ultimate_Tic_Tac_Toe_AI_Lost": 0,
			"Simple_Tic_Tac_Toe_AI_Won": 0,
			"Simple_Tic_Tac_Toe_Won": 0,
			"Simple_Tic_Tac_Toe_AI_Lost": 0,
			"Simple_Tic_Tac_Toe_Lost": 0
		}

	var file = FileAccess.open(file_path, FileAccess.READ)
	if file:
		var local_stats = {}  # Renamed the local variable
		while not file.eof_reached():
			var line = file.get_line().strip_edges()
			var parts = line.split("=")
			if parts.size() == 2:
				local_stats[parts[0]] = int(parts[1])  # Convert the value to an int
		file.close()
		return local_stats  # Return the renamed variable
	else:
		print("Failed to open file for reading.")
		return {}

func update_stats(is_win: bool, player: String):
	print("Updating stats - is_win:", is_win, "player:", player)  # Debugging output
	# Ensure the necessary keys are present in the stats dictionary
	if not stats.has("Ultimate_Tic_Tac_Toe_AI_Won"):
		stats["Ultimate_Tic_Tac_Toe_AI_Won"] = 0
	if not stats.has("Ultimate_Tic_Tac_Toe_AI_Lost"):
		stats["Ultimate_Tic_Tac_Toe_AI_Lost"] = 0

	# Update stats based on win/loss and player
	if player == "X":
		if is_win:
			stats["Ultimate_Tic_Tac_Toe_AI_Won"] += 1
	elif player == "O":
		if is_win:
			stats["Ultimate_Tic_Tac_Toe_AI_Lost"] += 1

	save_stats(stats)
	
func save_stats(new_stats: Dictionary) -> void:
	var file_path = "user://Stats.cfg"
	var file = FileAccess.open(file_path, FileAccess.WRITE)
	if file:
		for key in new_stats.keys():
			var line = key + "=" + str(new_stats[key])  # Create the line for each key-value pair
			file.store_line(line)  # Store the line in the file
		file.close()
	else:
		print("Failed to open file for writing.")

# Single handler for all AI cells (1-81)
func _on_AICell_pressed(column_index: int, cell_index: int) -> void:
	if is_ai_turn or overall_winner != "" or (active_column != -1 and column_index != active_column):
		return

	var cell: Button = get_node("GridContainerColumn" + str(column_index + 1)).get_child(cell_index)

	if cell.text == "":
		cell.text = current_player  # This should be "X" when it's the player's turn
		stating_labels[column_index].visible = false

		if check_winner(column_index, "O"):  # Check if the current player wins
			small_board_wins[column_index] = current_player
			update_column_win_label(column_index)
			lock_column(column_index)
			if check_overall_winner():
				display_winner(current_player)
				lock_game()
				return

		if is_column_full(column_index):
			tie_labels[column_index].visible = true
			lock_column(column_index)

		active_column = cell_index if small_board_wins[cell_index] == null else -1

		for label in stating_labels:
			label.visible = false
		if active_column != -1:
			stating_labels[active_column].visible = true
		else:
			active_column = find_next_unwon_column()
			if active_column != -1:
				stating_labels[active_column].visible = true

		current_player = "O"  # Set current_player to "O" for the AI's turn
		is_ai_turn = true
		switch_turn()  # Update to show it's AI's turn
		call_deferred("ai_turn")

func find_next_unwon_column() -> int:
	# Create a list of column indices and shuffle them
	var columns = [0, 1, 2, 3, 4, 5, 6, 7, 8]
	columns.shuffle()

	# Iterate through the randomized columns to find an available one
	for i in columns:
		if small_board_wins[i] == null:
			return i
	return -1  # Returns -1 if no unwon column is found


func is_column_full(column_index: int) -> bool:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		if cell.text == "":
			return false  # Return false if any cell is empty
	return true  # Return true if all cells are filled

func update_column_win_label(column_index: int) -> void:
	# Show the appropriate win label for the column
	if small_board_wins[column_index] == "X":
		x_win_labels[column_index].visible = true
	elif small_board_wins[column_index] == "O":
		o_win_labels[column_index].visible = true

func lock_column(column_index: int) -> void:
	# Ensure the column index is valid (0-8 for 1-9 in the UI)
	if column_index < 0 or column_index >= 9:
		return
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		cell.disabled = true  # Lock the buttons in this column

func lock_game() -> void:
	for column_index in range(9):  # Only loop through valid column indices (0-8)
		lock_column(column_index)  # Lock all columns

func ai_turn() -> void:
	await get_tree().create_timer(1.0).timeout  # Delay for AI's move

	var column_to_play = find_valid_column_for_ai()

	if column_to_play == -1:
		return  # No valid columns available
	
	print("AI is deciding for column: ", column_to_play)
	
	if can_win(column_to_play, "O"):  # Check if AI can win
		take_winning_move(column_to_play)
	elif block_player(column_to_play):
		pass  # Player's move is blocked
	else:
		play_random_move(column_to_play)  # Random move if no winning or blocking move

	# Check if AI wins the small board
	if check_winner(column_to_play, "O"):  # Ensure this reflects "O"
		small_board_wins[column_to_play] = "O"  # Mark the small board as won
		update_column_win_label(column_to_play)  # Update the specific win label
		lock_column(column_to_play)  # Lock the column after winning
		if check_overall_winner():  # Check for overall winner
			display_winner("O")  # Display AI as winner
			lock_game()  # Lock the entire game

	current_player = "X"  # Switch back to player's turn
	is_ai_turn = false
	switch_turn()  # Show X is playing


# Function to switch turns
func switch_turn():
	# Toggle the current player
	if current_player == "O":
		current_player = "X"
		$XIsPlaying.visible = false   # Hide X label
		$OIsPlaying.visible = true    # Show O label
	else:
		current_player = "X"
		$OIsPlaying.visible = false   # Hide O label
		$XIsPlaying.visible = true    # Show X label

# Call this function each time a turn is completed
func _on_turn_completed():
	switch_turn()


func find_valid_column_for_ai() -> int:
	var valid_columns = []
	for i in range(9):
		if small_board_wins[i] == null:  # Only consider columns that haven't been won
			valid_columns.append(i)
	return valid_columns[randi() % valid_columns.size()] if valid_columns.size() > 0 else -1

func check_winner(column_index: int, player: String) -> bool:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	print("Checking winner for player: " , player)
	for i in range(3):
		# Check rows in the small board
		if grid_container.get_child(i * 3).text == player and \
		   grid_container.get_child(i * 3 + 1).text == player and \
		   grid_container.get_child(i * 3 + 2).text == player:
			return true
	
	# Check columns
	for i in range(3):
		if grid_container.get_child(i).text == player and \
		   grid_container.get_child(i + 3).text == player and \
		   grid_container.get_child(i + 6).text == player:
			return true
	
	# Check diagonals
	if grid_container.get_child(0).text == player and \
	   grid_container.get_child(4).text == player and \
	   grid_container.get_child(8).text == player:
		return true
	
	if grid_container.get_child(2).text == player and \
	   grid_container.get_child(4).text == player and \
	   grid_container.get_child(6).text == player:
		return true
	
	return false



func check_overall_winner() -> bool:
	# Check for overall winner in the 3x3 grid of small boards
	for i in range(3):
		# Check rows for overall winner
		if small_board_wins[i * 3] == current_player and small_board_wins[i * 3 + 1] == current_player and small_board_wins[i * 3 + 2] == current_player:
			return true
		# Check columns for overall winner
		if small_board_wins[i] == current_player and small_board_wins[i + 3] == current_player and small_board_wins[i + 6] == current_player:
			return true
	# Check diagonals for overall winner
	if small_board_wins[0] == current_player and small_board_wins[4] == current_player and small_board_wins[8] == current_player:
		return true
	if small_board_wins[2] == current_player and small_board_wins[4] == current_player and small_board_wins[6] == current_player:
		return true
	return false

func display_winner(player: String) -> void:
	# Update stats for the winner and loser
	if player == "X":
		update_stats(true, "X")  # Player wins
		update_stats(false, "O")  # AI loses
	else:
		update_stats(false, "X")  # Player loses
		update_stats(true, "O")  # AI wins
	
	# Hide all column win labels first
	for label in x_win_labels:
		label.visible = false
	for label in o_win_labels:
		label.visible = false
	
	# Show the appropriate win label for the overall winner
	for i in range(9):
		if small_board_wins[i] == player:
			if player == "X":
				x_win_labels[i].visible = true
			else:
				o_win_labels[i].visible = true
	
	# Print the winner to the console
	print(player + " wins! In AI Ultimate Tic Tac Toe")  # Outputs "X wins!" or "O wins!" to the console
	
	# Show the general winner label
	winner_label.text = player + " wins!"
	winner_label.visible = true
	ai_home_button.visible = true  # Show the AIHomeButton when a player wins
	
	# Show the rematch button
	ai_rematch_button.visible = true  # Make the rematch button visible
	
	# Call reset game state to prepare for the next game
	reset_game_state()

func reset_game_state() -> void:
	current_player = "X"  # Reset current player
	small_board_wins = [null, null, null, null, null, null, null, null, null]  # Reset small board wins
	overall_winner = ""  # Reset overall winner

# AI Strategies
func can_win(column_index: int, player: String) -> bool:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		if cell.text == "":
			cell.text = player  # Try to make a move for the current player
			if check_winner(column_index, player):  # Pass the player as an argument
				cell.text = ""  # Reset move
				return true  # Winning move found
			cell.text = ""  # Reset move
	return false  # No winning move found

func take_winning_move(column_index: int) -> void:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		if cell.text == "":
			cell.text = "O"  # AI makes the winning move
			break  # Exit loop after move

func block_player(column_index: int) -> bool:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		if cell.text == "X":
			# Check if blocking is necessary
			if can_win(column_index, "X"):  # Check if player can win
				cell.text = "O"  # Block the player
				return true  # Block successful
	return false  # No blocking move necessary

func play_random_move(column_index: int) -> void:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	var empty_cells = []
	for i in range(9):
		var cell: Button = grid_container.get_child(i)
		if cell.text == "":
			empty_cells.append(cell)  # Add empty cells to the list
	if empty_cells.size() > 0:
		var random_cell: Button = empty_cells[randi() % empty_cells.size()]
		random_cell.text = "O"  # AI plays randomly

func reset_game() -> void:
	# Reset the small board wins
	small_board_wins = [null, null, null, null, null, null, null, null, null]
	overall_winner = ""
	current_player = "X"  # Reset to player starting first
	is_ai_turn = false

	# Clear all cell texts
	for column_index in range(1, 10):  # Columns 1 to 9
		var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index))
		for cell_index in range(9):  # Cells 1 to 9 in each column
			var cell: Button = grid_container.get_child(cell_index)
			cell.text = ""  # Clear the cell text
			cell.disabled = false  # Enable the button again

	# Hide all win labels
	for label in x_win_labels:
		label.visible = false
	for label in o_win_labels:
		label.visible = false
	for label in tie_labels:
		label.visible = false
	
	# Hide the general winner label and AI home button
	winner_label.visible = false
	ai_home_button.visible = false
	
func _on_aihomebutton_pressed() -> void:
	$Button10ClickPlayer.play()  # Play the button click sound
	await get_tree().create_timer(0.1).timeout  # Wait for a short duration
	print("AI Home button clicked")  # Log button click to the console
	get_tree().change_scene_to_file("res://UI.tscn")  # Home

func _on_miniboardhomepage_pressed() -> void:
	$Button10ClickPlayer.play()  # Play the button click sound
	await get_tree().create_timer(0.1).timeout  # Wait for a short duration
	print("Mini Board Home button clicked")  # Log button click to the console
	get_tree().change_scene_to_file("res://UI.tscn")  # Go back to home

func _on_airematchultimatebutton_pressed() -> void:
	$Button10ClickPlayer.play()  # Play the button click sound
	await get_tree().create_timer(0.1).timeout  # Wait for a short duration
	print("AI Rematch Ultimate button clicked")  # Log button click to the console
	reset_game()  # Call your reset function
	ai_rematch_button.visible = false  # Hide the rematch button after it's clicked

If you want videos, Pictures, Scripts, scenes or anything let me know, any help would be much appreciated, thank you very much! :slight_smile:

I’ve been debugging it a little bit and It keeps checking for “O” and not both… Any ideas or reasons possible in my code or something? I can’t tell if I mismatched something lol

Thanks :slight_smile: