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!