Stats Function being buggy

Godot Version

godot 4.3.win_64

Question

Hello everybody! I have a question, I have been working on my game where you can play Ultimate Tic Tac Toe or Simple Tic Tac Toe and I'm nearly done! So basically I have been working on a function where if you win or lose the game, it updates in my "stats.cfg" file and I have been working on it for a while, but the code has not been seeming to update at all when winning or losing, and it's getting frustrating cuz i've been troubleshooting it for about 2 days now... And the Output updates the code it seems like but doesn't update my stats file... Anyone have any ideas?

Heres the video to show whats happening

Here is my StatsPopUp.gd code that is what loads my .cfg file:

extends Popup
 
var stats: Dictionary = {}  # To store the stats
var stats_file_path = "user://Stats.cfg"  # Use consistent filename
 
func _ready():
    load_stats()  # Load stats when ready
    display_stats()  # Display stats on the label
 
    # Connect the reset button signal if not already connected
    if not $ResetButton.is_connected("pressed", Callable(self, "_on_ResetButton_pressed")):
        $ResetButton.connect("pressed", Callable(self, "_on_ResetButton_pressed"))
 
func load_stats():
    var file = FileAccess.open(stats_file_path, FileAccess.READ)  # Use consistent file name
    if file:
        stats.clear()  # Clear current stats before loading new ones
        while not file.eof_reached():
            var line = file.get_line()
            if line.begins_with("["):
                continue  # Skip section headers
            var key_value = line.split("=")
            if key_value.size() == 2:
                stats[key_value[0].strip_edges()] = int(key_value[1].strip_edges())
        file.close()
    else:
        print("Failed to open stats file for reading.")
 
func display_stats():
    var stats_text = "--WON--\n"
    stats_text += "Ultimate Tic Tac Toe AI Won: %d\n" % stats.get("Ultimate_Tic_Tac_Toe_AI_Won", 0)
    stats_text += "Ultimate Tic Tac Toe Yourself Won: %d\n" % stats.get("Ultimate_Tic_Tac_Toe_Yourself_Won", 0)
    stats_text += "Simple Tic Tac Toe AI Won: %d\n" % stats.get("Simple_Tic_Tac_Toe_AI_Won", 0)
    stats_text += "Simple Tic Tac Toe Won: %d\n\n" % stats.get("Simple_Tic_Tac_Toe_Won", 0)
    stats_text += "--LOST--\n"
    stats_text += "Ultimate Tic Tac Toe AI Lost: %d\n" % stats.get("Ultimate_Tic_Tac_Toe_AI_Lost", 0)
    stats_text += "Ultimate Tic Tac Toe Yourself Lost: %d\n" % stats.get("Ultimate_Tic_Tac_Toe_Yourself_Lost", 0)
    stats_text += "Simple Tic Tac Toe AI Lost: %d\n" % stats.get("Simple_Tic_Tac_Toe_AI_Lost", 0)
    stats_text += "Simple Tic Tac Toe Lost: %d\n" % stats.get("Simple_Tic_Tac_Toe_Lost", 0)
 
    $StatsLabel.text = stats_text  # Assuming you have a Label node named StatsLabel
 
func _on_CloseButton_pressed():
    queue_free()  # Close the popup when button is pressed
 
func _on_ResetButton_pressed():
    reset_stats()  # Call the reset_stats function
 
func reset_stats():
    # Reset all values in the dictionary to 0
    stats = {
        "Ultimate_Tic_Tac_Toe_AI_Won": 0,
        "Ultimate_Tic_Tac_Toe_Yourself_Won": 0,
        "Simple_Tic_Tac_Toe_AI_Won": 0,
        "Simple_Tic_Tac_Toe_Won": 0,
        "Ultimate_Tic_Tac_Toe_AI_Lost": 0,
        "Ultimate_Tic_Tac_Toe_Yourself_Lost": 0,
        "Simple_Tic_Tac_Toe_AI_Lost": 0,
        "Simple_Tic_Tac_Toe_Lost": 0
    }
    save_stats()  # Save the reset stats back to file
    display_stats()  # Refresh the display after reset
 
func save_stats():
    # Write current stats to the stats file
    var file = FileAccess.open(stats_file_path, FileAccess.WRITE)  # Use WRITE to overwrite
    if file:
        for key in stats.keys():
            file.store_line("%s=%d" % [key, stats[key]])
        file.close()
    else:
        print("Failed to open stats file for writing.")

Here is my miniboard.gd file (the file that is where i’m doing the ultimate tic tac toe thing, And this is the YOURSELF Function, the function that basically means you play the game on the same screen with family and friends like type thing, I’ll have a video showing everything to help!

extends Control  # Change this to the appropriate base class, if needed
 
 
var current_player = "X"  # Keep track of the current player
var cells = []  # Store references to buttons
var small_board_wins = []  # Track wins for small boards
var overall_winner = ""  # Track overall winner
var is_initialized = false  # Track if the board is initialized
var stats: Dictionary = load_stats()  # Load or initialize stats at the start
 
@onready var winner_label = $WinnerLabel  # General winner label
@onready var home_button = $HomeTownButton  # Reference to the HomeButton
@onready var rematch_button = $RematchYourselfButton  # Reference to the Rematch button
 
# Declare the variable for your AudioStreamPlayer
var button5_click_player: AudioStreamPlayer = null  # Reference to the AudioStreamPlayer for Button5
 
# Declare labels for each column
var x_win_labels = []
var o_win_labels = []
var tie_labels = []  # Array to hold tie labels for each column
 
# Function to save stats
func save_stats(new_stats: Dictionary):
    var file = FileAccess.open("user://Stats.cfg", FileAccess.WRITE)
    if file:
        file.store_string(JSON.stringify(new_stats))  # Correctly use JSON.stringify
        file.close()
    else:
        print("Failed to open file for writing.")
 
 
 
 
func _ready():
    if is_initialized:  # Prevent re-initialization
        return
    is_initialized = true  # Mark as initialized
    
    stats = load_stats()  # Load existing stats
 
# Initialize AudioStreamPlayer reference and set the sound stream
    button5_click_player = $Button5ClickPlayer  # Make sure this node exists in the scene
    button5_click_player.stream = load("res://sounds/click.ogg")  # Set the path to your sound file
 
    cells.clear()
    small_board_wins = [null, null, null, null, null, null, null, null, null]  # Initialize small board wins
    winner_label.visible = false
    home_button.visible = false  # Hide the HomeButton initially
    rematch_button.visible = false  # Hide the Rematch button initially
 
    # Initialize labels for each column
    for i in range(1, 10):
        # Use 'get_node_safe' or check if the node exists
        var x_label = get_node("XWinLabelColumn" + str(i))
        var o_label = get_node("OWinLabelColumn" + str(i))
        var tie_label = get_node("ItsATieLabelColumn" + str(i))
 
        if x_label and o_label and tie_label:
            x_win_labels.append(x_label)
            o_win_labels.append(o_label)
            tie_labels.append(tie_label)
            x_win_labels[i - 1].visible = false
            o_win_labels[i - 1].visible = false
            tie_labels[i - 1].visible = false
        else:
            print("One or more labels not found for column: ", i)
 
    # 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_Cell_pressed").bind(column_index - 1, cell_index))
            
            # Function to connect buttons safely
func _connect_button(button: Button, handler: String):
    if button and !button.is_connected("pressed", Callable(self, handler)):
        button.connect("pressed", Callable(self, handler))
    else:
        print(button.name + " not found or already connected.")
        
 
# Function to play button click sound
func _play_button_click_sound():
    print("Playing button click sound")  # Debugging statement
    button5_click_player.play()  # Play the button click sound
 
# Single handler for all Cells (1-81)
func _on_Cell_pressed(column_index: int, cell_index: int) -> void:
    if overall_winner != "":  # Ignore input if the game is won
        return 
 
    var cell: Button = get_node("GridContainerColumn" + str(column_index + 1)).get_child(cell_index)
    if cell.text == "":  # Ensure cell is empty before marking
        cell.text = current_player  # Set the cell text to the current player
        if check_winner(column_index):  # Check if the current player won the small board
            small_board_wins[column_index] = current_player  # Mark the small board as won
            update_column_win_label(column_index)  # Update the specific win label
            lock_column(column_index)  # Lock the column after winning
            if check_overall_winner():  # Check for overall winner
                display_winner(current_player)  # Display the overall winner
                lock_game()  # Lock the entire game
                return  # Ensure we exit after declaring the winner
 
        if is_column_full(column_index):  # Check if the column is full and no winner
            tie_labels[column_index].visible = true  # Show the tie label for the column
            lock_column(column_index)  # Lock the column as it's full
 
        current_player = "O" if current_player == "X" else "X"  # Switch player
        
func update_stats(is_win: bool, player: String):
    # Ensure the necessary keys are present in the stats dictionary
    if not stats.has("Ultimate_Tic_Tac_Toe_Yourself_Won"):
        stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] = 0  # Initialize wins for Player X
    if not stats.has("Ultimate_Tic_Tac_Toe_Yourself_Lost"):
        stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] = 0  # Initialize losses for Player X
 
    # Update stats logic
    if player == "X":  # Assuming "X" is the player
        if is_win:
            stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] += 1  # Increment wins for Player X
        else:
            stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1  # Increment losses for Player X
 
    save_stats(stats)  # Save the updated stats
 
 
 
func display_winner(player: String) -> void:
    if player == "X":
        update_stats(true, player)  # Player wins
        update_stats(false, "O")  # AI loses
    else:
        update_stats(false, player)  # Player loses
        update_stats(true, "X")  # AI wins
 
    # Other display logic remains the same
    for label in x_win_labels:
        label.visible = false
    for label in o_win_labels:
        label.visible = false
    
    # Show the general winner label
    winner_label.text = player + " wins!"
    winner_label.visible = true
    home_button.visible = true
    rematch_button.visible = true
 
    # Call reset game state to prepare for the next game
    reset_game_state()
 
func end_game(is_player_win: bool):
    if is_player_win:  # If the player won
        update_stats(true, "X")  # Call with is_win true
    else:  # Player lost
        update_stats(false, "X")  # Call with is_win false 
 
 
# Update loss stats if the AI (or "O") wins
func update_stats_loss():
    if current_player == "O":  # Update loss if "O" wins
        stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1
    save_stats(stats)  # Pass the updated stats dictionary
 
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
 
 
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)
 
func check_winner(column_index: int) -> bool:
    var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
    for i in range(3):
        if grid_container.get_child(i * 3).text == current_player and \
           grid_container.get_child(i * 3 + 1).text == current_player and \
           grid_container.get_child(i * 3 + 2).text == current_player:
            return true
    
    for i in range(3):
        if grid_container.get_child(i).text == current_player and \
           grid_container.get_child(i + 3).text == current_player and \
           grid_container.get_child(i + 6).text == current_player:
            return true
    
    return false
 
func check_overall_winner() -> bool:
    for i in range(3):
        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) or \
           (small_board_wins[i] == current_player and \
            small_board_wins[i + 3] == current_player and \
            small_board_wins[i + 6] == current_player):
            return true
    if (small_board_wins[0] == current_player and \
        small_board_wins[4] == current_player and \
        small_board_wins[8] == current_player) or \
       (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 _on_hometown_button_pressed() -> void:
    button5_click_player.play()  # Play the button click sound
    print("HomeButton pressed")
    await get_tree().create_timer(0.1).timeout  # Add a delay
    get_tree().change_scene_to_file("res://UI.tscn")  # Update with your actual main menu scene path
 
func _on_minihomepage_pressed() -> void:
    button5_click_player.play()  # Play the button click sound
    print("MiniHomepage pressed")
    await get_tree().create_timer(0.1).timeout  # Add a delay
    get_tree().change_scene_to_file("res://UI.tscn")  # Update with your actual main menu scene path
 
func _on_rematchyourselfbutton_pressed() -> void:
    button5_click_player.play()  # Play the button click sound
    print("Rematch button pressed")
    await get_tree().create_timer(0.1).timeout  # Add a delay
    # Call functions to reset the game
    clear_board()
    reset_labels()
    reset_game_state()
    rematch_button.visible = false  # Hide the rematch button after pressing it
    home_button.visible = false # hide after pressed
 
 
func clear_board() -> void:
    for column_index in range(9):
        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.text = ""
            cell.disabled = false  # Re-enable the cells
 
func reset_labels() -> void:
    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
    winner_label.visible = false  # Hide the winner label
 
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
        }
 
    var file = FileAccess.open(file_path, FileAccess.READ)
    if file:
        var json = JSON.new()  # Create an instance of JSON
        var file_content = file.get_as_text()
        print("File content: ", file_content)  # Print file content for debugging
        var result = json.parse(file_content)
        if result == OK:
            stats = json.get_data()  # Retrieve the parsed data
        else:
            print("Failed to parse JSON data.")
        file.close()
    else:
        print("Failed to open file for reading.")
    return stats

UI.gd (The main scene when u load up the game)

extends Control
 
var credits_popup: Popup = null
var settings_popup: Popup = null
var button_click_player: AudioStreamPlayer = null  # Reference to the AudioStreamPlayer
var music_player: AudioStreamPlayer = null  # Reference to the AudioStreamPlayer for music
 
# Function ready
func _ready():
    # Initialize the music player
    music_player = get_node("VibesPlayer")  # Adjust this path based on your scene hierarchy
 
    # Start playing the background music if not already playing
    if music_player:
        if not music_player.is_playing():  # Check if the music is already playing
            music_player.play()  # Start playing the music
 
    # Initialize AudioStreamPlayer reference and set the sound stream
    button_click_player = $ButtonClickPlayer
    button_click_player.stream = load("res://sounds/click.ogg")
 
    if $SubscribeLabel:
        $SubscribeLabel.connect("gui_input", Callable(self, "_on_SubscribeLabel_clicked"))
 
    if $DiscordLabel:
        $DiscordLabel.connect("gui_input", Callable(self, "_on_DiscordLabel_clicked"))
 
func _on_SettingsButton_pressed():
    _play_button_click_sound()  # Play the button click sound
    print("Settings Button Clicked!")  # Log button click to the console
    
    # Load and instance the SettingsPopup.tscn
    if settings_popup == null:  # Check if the popup is not created
        var packed_scene = preload("res://settingspopup.tscn")  # Preload the scene
        settings_popup = packed_scene.instantiate()  # Instantiate the scene
        get_tree().current_scene.add_child(settings_popup)  # Add to the current scene
        settings_popup.popup_centered()  # Show it centered on the screen
    else:
        if settings_popup.is_visible():
            settings_popup.hide()  # Hide the popup if it's visible
        else:
            settings_popup.popup_centered()  # Show it centered on the screen
 
# Function to play button click sound
func _play_button_click_sound():
    button_click_player.play()  # Play the button click sound
 
 
func _on_Button_pressed():
    _play_button_click_sound()  # Play the button click sound
    print("Credits Button Clicked!")  # Log button click to the console
    if credits_popup == null:  # Check if the popup is not created
        credits_popup = Popup.new()  # Create a new popup
 
        # Create a RichTextLabel for credits with clickable links
        var rich_text_label = RichTextLabel.new()  
        rich_text_label.bbcode_enabled = true  # Enable BBCode for clickable links
        rich_text_label.text = "Game by [url=YT[/url]/\n" + \
            "Song name at home page: [url=https://www.youtube.com/watch?v=x-ObUKegl6g]Vibes - David Renda[/url]\n" + \
            "Click sound effect: [url=https://freesound.org/people/Breviceps/sounds/448081]Tic Toc UI Click[/url]\n" + \
            "Simple Tic Tac Toe picture credits: [url=https://projectbook.code.brettchalupa.com/games/img/ttt.webp]https://projectbook.code.brettchalupa.com/games/img/ttt.webp[/url]\n" + \
            "Super Tic Tac Toe credits: [url=https://upload.wikimedia.org/wikipedia/commons/7/7d/Super_tic-tac-toe_rules_example.png]https://upload.wikimedia.org/wikipedia/commons/7/7d/Super_tic-tac-toe_rules_example.png[/url]\n"  # BBCode for clickable links
 
        # Set minimum size for the label
        rich_text_label.custom_minimum_size = Vector2(200, 100)  
        credits_popup.add_child(rich_text_label)  # Add the label to the popup
        
        # Connect the link click event to open the URL
        rich_text_label.connect("meta_clicked", Callable(self, "_on_link_clicked"))
 
        # Connect the close functionality
        credits_popup.connect("popup_hide", Callable(self, "_on_CreditsPopup_closed"))
 
        # Add the popup to the current scene
        get_tree().current_scene.add_child(credits_popup)  # Adding to the current scene
 
        # Position the popup in the center of the screen after it's been added to the tree
        credits_popup.popup_centered()  # Show it centered on the screen
    else:
        # If popup is already created, just show or hide it
        if credits_popup.is_visible():
            credits_popup.hide()  # Hide the popup if it's visible
        else:
            credits_popup.popup_centered()  # Show it centered on the screen
 
# Function to handle link clicks
func _on_link_clicked(meta: String):
    print("Clicked link: ", meta)  # Print the clicked link
    OS.shell_open(meta)  # Open the link in the default browser
 
func _on_PlayButton_pressed():
    print("Play Button sound trigger")
    _play_button_click_sound()  # Play the button click sound
 
    # Add a slight delay if necessary
    await get_tree().create_timer(0.15).timeout
 
    print("Play Button Pressed")
    get_tree().change_scene_to_file("res://ultimate_or_simple.tscn")
 
func _on_SaveButton_pressed():
    _play_button_click_sound()  # Play the button click sound for the save button
 
    # Save settings logic can be modified here if you still want to save other settings
    print("Settings Saved.")
 
func _on_DiscordLabel_clicked(event: InputEvent):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
        print("Discord Label Clicked!")  # Print message when Discord label is clicked
        OS.shell_open("my link")  # Open Discord in default browser
 
# Function to handle when the SubscribeLabel is clicked
func _on_SubscribeLabel_clicked(event: InputEvent):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
        print("Subscribe Label Clicked!")  # Print message when Subscribe label is clicked
        OS.shell_open("my link")  # Open YouTube channel in the default browser
 
 
func _on_statsbutton_pressed() -> void:
    _play_button_click_sound()  # Play the button click sound
    print("Stats Button Clicked!")  # Log button click to the console
 
    # Load and instance the StatsPopUp.tscn
    var packed_scene = preload("res://StatsPopUp.tscn")  # Preload the scene
    var stats_popup = packed_scene.instantiate()  # Instantiate the scene
    get_tree().current_scene.add_child(stats_popup)  # Add to the current scene
    stats_popup.popup_centered()  # Show it centered on the screen
 

StatsPopUp SceneTree
image
UI SceneTree (main scene)


And you can let me know if you need other SceneTree’s or GDScripts

Any help would be much appreciated, thank you very much and have a great day! :slight_smile:

What I would test first is to create a test cfg file in a text editor. Make sure it has some values in it that are not 0.
Test displaying that file without playing any games so that you are not writing to the file.
If that file displays correctly then you can at least eliminate the idea that your displaying code is corrupt.
If it doesn’t then you have a place to look.

There is something not right here though:

# Update stats logic
    if player == "X":  # Assuming "X" is the player
        if is_win:
            stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] += 1  # Increment wins for Player X
        else:
            stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1  # Increment losses for Player X
 
    save_stats(stats)  # Save the updated stats

What if the player == “O”? No stats will be saved in that case.

1 Like

Okay I experimented with this for a while, I got it to work slightly, but heres the problem now, it updates that I won perfectly fine, but when I lose, it doesnt update in the stats and adds a point to the Win stats

Also to answer the thing for player “O” I attempted that and for some reason it will duplicate itself, like when I win, it shows I lost and won at the same time…

Here is miniboard.gd (that adds my points when I lose or win)

extends Control  # Change this to the appropriate base class, if needed


var current_player = "X"  # Keep track of the current player
var cells = []  # Store references to buttons
var small_board_wins = []  # Track wins for small boards
var overall_winner = ""  # Track overall winner
var is_initialized = false  # Track if the board is initialized
var stats: Dictionary = load_stats()  # Load or initialize stats at the start

@onready var winner_label = $WinnerLabel  # General winner label
@onready var home_button = $HomeTownButton  # Reference to the HomeButton
@onready var rematch_button = $RematchYourselfButton  # Reference to the Rematch button

# Declare the variable for your AudioStreamPlayer
var button5_click_player: AudioStreamPlayer = null  # Reference to the AudioStreamPlayer for Button5

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

# 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 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.")

func _ready():
	if is_initialized:  # Prevent re-initialization
		return
	is_initialized = true  # Mark as initialized
	
	stats = load_stats()  # Load existing stats

# Initialize AudioStreamPlayer reference and set the sound stream
	button5_click_player = $Button5ClickPlayer  # Make sure this node exists in the scene
	button5_click_player.stream = load("res://sounds/click.ogg")  # Set the path to your sound file

	cells.clear()
	small_board_wins = [null, null, null, null, null, null, null, null, null]  # Initialize small board wins
	winner_label.visible = false
	home_button.visible = false  # Hide the HomeButton initially
	rematch_button.visible = false  # Hide the Rematch button initially

	# Initialize labels for each column
	for i in range(1, 10):
		# Use 'get_node_safe' or check if the node exists
		var x_label = get_node("XWinLabelColumn" + str(i))
		var o_label = get_node("OWinLabelColumn" + str(i))
		var tie_label = get_node("ItsATieLabelColumn" + str(i))

		if x_label and o_label and tie_label:
			x_win_labels.append(x_label)
			o_win_labels.append(o_label)
			tie_labels.append(tie_label)
			x_win_labels[i - 1].visible = false
			o_win_labels[i - 1].visible = false
			tie_labels[i - 1].visible = false
		else:
			print("One or more labels not found for column: ", i)

	# 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_Cell_pressed").bind(column_index - 1, cell_index))
			
			# Function to connect buttons safely
func _connect_button(button: Button, handler: String):
	if button and !button.is_connected("pressed", Callable(self, handler)):
		button.connect("pressed", Callable(self, handler))
	else:
		print(button.name + " not found or already connected.")
		

# Function to play button click sound
func _play_button_click_sound():
	print("Playing button click sound")  # Debugging statement
	button5_click_player.play()  # Play the button click sound

# Single handler for all Cells (1-81)
func _on_Cell_pressed(column_index: int, cell_index: int) -> void:
	if overall_winner != "":  # Ignore input if the game is won
		return 

	var cell: Button = get_node("GridContainerColumn" + str(column_index + 1)).get_child(cell_index)
	if cell.text == "":  # Ensure cell is empty before marking
		cell.text = current_player  # Set the cell text to the current player
		if check_winner(column_index):  # Check if the current player won the small board
			small_board_wins[column_index] = current_player  # Mark the small board as won
			update_column_win_label(column_index)  # Update the specific win label
			lock_column(column_index)  # Lock the column after winning
			if check_overall_winner():  # Check for overall winner
				display_winner(current_player)  # Display the overall winner
				lock_game()  # Lock the entire game
				return  # Ensure we exit after declaring the winner

		if is_column_full(column_index):  # Check if the column is full and no winner
			tie_labels[column_index].visible = true  # Show the tie label for the column
			lock_column(column_index)  # Lock the column as it's full

		current_player = "O" if current_player == "X" else "X"  # Switch player
		
func update_stats(is_win: bool, player: String):
	# Ensure the necessary keys are present in the stats dictionary
	if not stats.has("Ultimate_Tic_Tac_Toe_Yourself_Won"):
		stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] = 0  # Initialize wins for Player X
	if not stats.has("Ultimate_Tic_Tac_Toe_Yourself_Lost"):
		stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] = 0  # Initialize losses for Player X

	# Update stats logic
	if player == "X":  # Assuming "X" is the player
		if is_win:
			stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] += 1  # Increment wins for Player X
		else:
			stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1  # Increment losses for Player X

	save_stats(stats)  # Save the updated stats

func display_winner(player: String) -> void:
	if player == "X":
		update_stats(true, player)  # Player wins
		update_stats(false, "O")  # AI loses
	else:
		update_stats(false, player)  # Player loses
		update_stats(true, "X")  # AI wins

	# Other display logic remains the same
	for label in x_win_labels:
		label.visible = false
	for label in o_win_labels:
		label.visible = false
	
	# Show the general winner label
	winner_label.text = player + " wins!"
	winner_label.visible = true
	home_button.visible = true
	rematch_button.visible = true

	# Call reset game state to prepare for the next game
	reset_game_state()

func end_game(is_player_win: bool):
	if is_player_win:  # If the player won
		update_stats(true, "X")  # Call with is_win true
	else:  # Player lost
		update_stats(false, "X")  # Call with is_win false 


# Update loss stats if the AI (or "O") wins
func update_stats_loss():
	if current_player == "O":  # Update loss if "O" wins
		stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1
	save_stats(stats)  # Pass the updated stats dictionary

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


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)

func check_winner(column_index: int) -> bool:
	var grid_container: GridContainer = get_node("GridContainerColumn" + str(column_index + 1))
	for i in range(3):
		if grid_container.get_child(i * 3).text == current_player and \
		   grid_container.get_child(i * 3 + 1).text == current_player and \
		   grid_container.get_child(i * 3 + 2).text == current_player:
			return true
	
	for i in range(3):
		if grid_container.get_child(i).text == current_player and \
		   grid_container.get_child(i + 3).text == current_player and \
		   grid_container.get_child(i + 6).text == current_player:
			return true
	
	return false

func check_overall_winner() -> bool:
	for i in range(3):
		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) or \
		   (small_board_wins[i] == current_player and \
			small_board_wins[i + 3] == current_player and \
			small_board_wins[i + 6] == current_player):
			return true
	if (small_board_wins[0] == current_player and \
		small_board_wins[4] == current_player and \
		small_board_wins[8] == current_player) or \
	   (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 _on_hometown_button_pressed() -> void:
	button5_click_player.play()  # Play the button click sound
	print("HomeButton pressed")
	await get_tree().create_timer(0.1).timeout  # Add a delay
	get_tree().change_scene_to_file("res://UI.tscn")  # Update with your actual main menu scene path

func _on_minihomepage_pressed() -> void:
	button5_click_player.play()  # Play the button click sound
	print("MiniHomepage pressed")
	await get_tree().create_timer(0.1).timeout  # Add a delay
	get_tree().change_scene_to_file("res://UI.tscn")  # Update with your actual main menu scene path

func _on_rematchyourselfbutton_pressed() -> void:
	button5_click_player.play()  # Play the button click sound
	print("Rematch button pressed")
	await get_tree().create_timer(0.1).timeout  # Add a delay
	# Call functions to reset the game
	clear_board()
	reset_labels()
	reset_game_state()
	rematch_button.visible = false  # Hide the rematch button after pressing it
	home_button.visible = false # hide after pressed


func clear_board() -> void:
	for column_index in range(9):
		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.text = ""
			cell.disabled = false  # Re-enable the cells

func reset_labels() -> void:
	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
	winner_label.visible = false  # Hide the winner label

thank you so much! :slight_smile: