Stats Function Not Working

Godot Version

Godot 4.3 win_64

Question

So basically i've been working on my game where you can play Ultimate Tic Tac Toe or Simple Tic Tac Toe and for the past day i've been trying to implemente a stats function, so it creates a .cfg file whenever you win or lose a game, well it's been very frustrating because I added a ResetButton so you can reset your stats but I think I broke the .cfg file, any help would be much appreciated πŸ™

Heres a video to show what I’m talking about

(stats are always at 0 for some reason it seems)

Here is some code to help:

StatsPopUp.gd (what controls the .cfg file)

extends Popup

var stats: Dictionary = {}  # To store the stats

func _ready():
	load_stats()  # Load stats when ready
	display_stats()  # Display stats on the label

	# Check if the signal is already connected to avoid duplicate connections
	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("user://Stats.cfg", FileAccess.READ)  # Use user:// for saved files
	if file:
		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()

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
	}
	
	# Write the reset values back to Stats.cfg
	var file = FileAccess.open("user://Stats.cfg", FileAccess.WRITE)  # Use WRITE to overwrite
	if file:
		for key in stats.keys():
			file.store_line("%s=%d" % [key, stats[key]])
		file.close()

UI.gd (the main scene u see when u load in 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=https://youtube.com/@monkagaming420]MonkaGaming420YT[/url]/Anthony\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("https://discord.gg/hX9JeZD7Rx")  # 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("https://www.youtube.com/@monkagaming420")  # 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

miniboard.gd (The Ultimate Tic Tac Toe yourself game that has it where it will update the .cfg whenever you win or lose)

extends Control

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 = {
	"Ultimate_Tic_Tac_Toe_Yourself_Won": 0,
	"Ultimate_Tic_Tac_Toe_Yourself_Lost": 0,
	# Other stats...
}

@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

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

# 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
				update_stats()  # Update stats for win when overall winner is declared
				return
		# No need to call update_stats_loss here; it's only updated if the game is over.
		
		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

# Update the stats only when a player wins
func update_stats():
	if current_player == "X":  # Assuming "X" is the player you want to track
		stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] += 1
	else:
		stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1  # Track losses if needed
	save_stats()  # Save the updated stats to Stats.cfg

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):
		# Check rows in the small board
		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:
			update_stats()  # Update stats when player wins
			return true
	
	# Check columns
	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:
			update_stats()  # Update stats when player wins
			return true
	
	return false

func save_stats():
	var file = FileAccess.open("res://Stats.cfg", FileAccess.WRITE)
	if file:
		for key in stats.keys():
			file.store_line("%s=%d" % [key, stats[key]])
		file.close()


func check_overall_winner() -> bool:
	for i in range(3):
		# Check rows
		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:
			update_stats()  # Moved to here
			return true
		# Check columns
		if small_board_wins[i] == current_player and small_board_wins[i + 3] == current_player and small_board_wins[i + 6] == current_player:
			update_stats()  # Moved to here
			return true
	# Check diagonals
	if small_board_wins[0] == current_player and small_board_wins[4] == current_player and small_board_wins[8] == current_player:
		update_stats()  # Moved to here
		return true
	if small_board_wins[2] == current_player and small_board_wins[4] == current_player and small_board_wins[6] == current_player:
		update_stats()  # Moved to here
		return true
	return false


func display_winner(player: String) -> void:
	if player != "X":  # If 'O' won, update loss for 'X'
		update_stats_loss()

	# 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

	# Show the general winner label
	winner_label.text = player + " wins!"
	winner_label.visible = true
	home_button.visible = true  # Show the HomeButton when a player wins
	rematch_button.visible = true  # Show the Rematch button when a player wins

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 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 update_stats_loss():
	if current_player == "X":
		stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] += 1
	save_stats()  # Save the updated stats to Stats.cfg

StatsPopUp.tscn SceneTree
image

UI.tscn SceneTree

ALL SCRIPTS (Let me know if you want a specific script and I will send it in replies)

image
image
image

All Scenes: (let me know and I will send SceneTree if needed too)


Thank you and any help would be very much appreciated! :slight_smile:

After looking through ways to fix it, or attempting, i Cannot seem to find any ways to get it to work.

Any reason why you’re not using a ConfigFile? See my example:

My guess is that no data is loaded into your stats dictionary. Put a breakpoint on this line:

And see if that line is reached and what value gets assigned to stats. To give any more help, more information like that is needed.

1 Like

image
when I put a breakpoint I get an error: Expected an expression after β€œ=”.

And I will experiment with what you said and see if it works, Thank you so much! :slight_smile:

1 Like

You put a return character in front of int, now they are on separate lines. make sure that the int is on the same line as the stats[key_value[0].strip_edges()]

Edit: To be clear, when I say breakpoint, I mean this:

1 Like

Okay so with what you said and a little bit of experimenting the stats file works now, But i’m going to make a followup topic because now whenever I win the win points work perfectly smooth but whenever you lose it adds a point to win and not lose, but once again, Thank you!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.