Error: Can't add child 'Cell0' to '@GridContainer@22', already has a parent '@GridContainer@5'

Godot Version

godot 4.3

Question

So i've been working on my ultimate tic tac toe game and I've been having an issue. When I start the game, I get (81) errors in the debugger:



and so on, but is there like any way that this error can be fixed without messing up the game/cells?

Here is a picture of my game for referencing:

Here is my miniboard.gd:

#miniboard.gd 
extends Control

@export var tween_intensity: float
@export var tween_duration: float

const Cell = preload("res://Scenes/cell.tscn")
var current_player: String = "X"
var board: Array = []
var cells: Array = []
var is_game_end: bool = false
var focusable_cells: Array = []
var current_focus_index: int = 0

@onready var cells_container = $Cells

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
var active_board = -1  # Initialize with -1 to allow any board on the first move
var focusable_buttons: Array = []
var is_accept_button_processing: bool = false  # Flag to track button state
var boards = []
const BOARD_SIZE = 3
const NUM_BOARDS = 9

@onready var winner_label = $WinnerLabel  # General winner label
@onready var home_button = $CanvasLayer/Control/HomeTownButton  # Reference to the HomeButton
@onready var rematch_button = $CanvasLayer/Control/RematchYourselfButton  # Reference to the Rematch button
@onready var state_labels = [
	$State1, $State2, $State3, $State4, $State5, $State6, $State7, $State8, $State9
]
@onready var x_playing_label = $XIsPlaying
@onready var o_playing_label = $OIsPlaying



# 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 has_node("CanvasLayer/Control/FPS_COUNTER_MINIBOARD"):
		$CanvasLayer/Control/FPS_COUNTER_MINIBOARD.add_to_group("fps_counters")
		$CanvasLayer/Control/FPS_COUNTER_MINIBOARD.visible = FpsManager.fps_enabled
		
	for label in state_labels:
		label.visible = false

	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()
	create_game_board()
	setup_buttons()
	boards.clear()
	small_board_wins = [null, null, null, null, null, null, null, null, null]
	winner_label.visible = false
	home_button.visible = false
	rematch_button.visible = false

	_connect_button($CanvasLayer/Control/RematchYourselfButton, "_on_RematchYourself_pressed")
	_connect_button($CanvasLayer/Control/HomeTownButton, "_on_HomeTown_pressed")
	_connect_button($CanvasLayer/Control/ResetGameButton, "_on_ResetGame_pressed")
	$CanvasLayer/Control/RematchYourselfButton.disabled = false
	$CanvasLayer/Control/HomeTownButton.disabled = false
	$CanvasLayer/Control/ResetGameButton.disabled = false


	# Initialize board array
	for _i in range(NUM_BOARDS):
		var board_array = []
		for _j in range(BOARD_SIZE * BOARD_SIZE):
			board_array.append("")
		boards.append(board_array)

	assert(boards.size() == NUM_BOARDS, "Boards not properly initialized")
	assert(current_player in ["X", "O"], "Current player not properly set")
	assert(active_board in range(-1, NUM_BOARDS), "Active board not properly set")

	# Initialize labels for each column
	for i in range(1, 10):
		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)

	# Add specific buttons to focusable buttons
	focusable_buttons.append_array([rematch_button, home_button, $CanvasLayer/Control/RematchYourselfButton])

	for button in focusable_buttons:
		button.focus_mode = Control.FOCUS_ALL
	
	x_playing_label.visible = true
	o_playing_label.visible = false
		
	if not $CanvasLayer/Control/ResetGameButton.is_connected("pressed", Callable(self, "_on_ResetGame_pressed")):
		$CanvasLayer/Control/ResetGameButton.connect("pressed", Callable(self, "_on_ResetGame_pressed"))

	# Center the main container
	var main_container = Control.new()
	main_container.name = "MainContainer"
	main_container.set_anchors_preset(Control.PRESET_CENTER)
	add_child(main_container)
	# Create a MarginContainer to add some padding
	var margin_container = MarginContainer.new()
	margin_container.add_theme_constant_override("margin_top", 50)
	margin_container.add_theme_constant_override("margin_left", 50)
	margin_container.add_theme_constant_override("margin_right", 50)
	margin_container.add_theme_constant_override("margin_bottom", 50)
	main_container.add_child(margin_container)
	
	# Create the grid for the boards
	var board_grid = GridContainer.new()
	board_grid.columns = 3
	board_grid.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
	board_grid.size_flags_vertical = Control.SIZE_SHRINK_CENTER
	margin_container.add_child(board_grid)
	
	# Create the 9 small boards
	for i in range(NUM_BOARDS):
		var board_container = create_small_board(i)
		board_grid.add_child(board_container)
		
		# Add separators between boards
		if i % 3 != 2 and i < 6:  # Add vertical separators except after the last column
			var v_sep = VSeparator.new()
			v_sep.custom_minimum_size.x = 4
			board_grid.add_child(v_sep)
		
		if i < 6 and i % 3 == 2:  # Add horizontal separators after each row except the last
			var h_sep = HSeparator.new()
			h_sep.custom_minimum_size.y = 4
			board_grid.add_child(h_sep)
			
			# Add placeholder nodes to maintain grid alignment
			if i < 5:  # Don't add after the last row
				for _j in range(2):  # Add two placeholder separators
					var placeholder = Control.new()
					placeholder.custom_minimum_size.x = 4
					board_grid.add_child(placeholder)

func create_game_board():
	cells.clear()
	
	var main_container = Control.new()
	main_container.name = "MainGameBoard"
	main_container.set_anchors_preset(Control.PRESET_CENTER)
	main_container.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
	main_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER
	add_child(main_container)

	var margin_container = MarginContainer.new()
	margin_container.add_theme_constant_override("margin_top", 365)
	margin_container.add_theme_constant_override("margin_bottom", 20)
	margin_container.add_theme_constant_override("margin_left", 300)
	margin_container.add_theme_constant_override("margin_right", 20)
	main_container.add_child(margin_container)

	var board_grid = GridContainer.new()
	board_grid.columns = 3
	board_grid.add_theme_constant_override("h_separation", 100)
	board_grid.add_theme_constant_override("v_separation", 50)
	margin_container.add_child(board_grid)

	for i in range(NUM_BOARDS):
		var board_container = create_small_board(i)
		board_grid.add_child(board_container)

	# Add separators between boards
	for i in range(NUM_BOARDS - 1):
		if i % 3 == 2:  # After every third board (except the last row)
			var h_separator = HSeparator.new()
			h_separator.custom_minimum_size.y = 5
			board_grid.add_child(h_separator)
		elif i < 6:  # Vertical separators (except after the last column)
			var v_separator = VSeparator.new()
			v_separator.custom_minimum_size.x = 5
			board_grid.add_child(v_separator)

func create_small_board(board_index: int) -> Control:
	var board_container = PanelContainer.new()
	board_container.name = "Board" + str(board_index)

	var grid = GridContainer.new()
	grid.columns = 3
	grid.add_theme_constant_override("h_separation", 5)
	grid.add_theme_constant_override("v_separation", 5)
	board_container.add_child(grid)

	for i in range(BOARD_SIZE * BOARD_SIZE):
		var cell_index = board_index * BOARD_SIZE * BOARD_SIZE + i
		var cell: Button
		if cell_index < cells.size():
			cell = cells[cell_index]
		else:
			cell = Cell.instantiate()
			cell.name = "Cell" + str(i)
			cell.board_index = board_index
			cell.cell_index = i
			cell.main = self
			cells.append(cell)
			cell.connect("cell_updated", Callable(self, "_on_cell_updated"))
		grid.add_child(cell)
		print("Added/Reused cell. Total cells: ", cells.size())
		
	return board_container

func setup_buttons():
	# Clear and reinitialize focusable buttons
	focusable_buttons.clear()
	
	# Add all cells to focusable buttons
	for cell in cells:
		focusable_buttons.append(cell)
	
	# Add UI buttons
	if has_node("RematchYourselfButton"):
		focusable_buttons.append($CanvasLayer/Control/RematchYourselfButton)
	if has_node("HomeTownButton"):
		focusable_buttons.append($CanvasLayer/Control/HomeTownButton)
	if has_node("MiniHomePage"):
		focusable_buttons.append($MiniHomePage)
	
	# Set up button properties
	for button in focusable_buttons:
		button.focus_mode = Control.FOCUS_ALL

func set_initial_focus():
	if not focusable_buttons.is_empty():
		focusable_buttons[0].grab_focus()
		current_focus_index = 0

func _input(_event):
	if Input.is_action_just_pressed("ui_right"):
		change_focus(1, 0)
		get_viewport().set_input_as_handled()
	elif Input.is_action_just_pressed("ui_left"):
		change_focus(-1, 0)
		get_viewport().set_input_as_handled()
	elif Input.is_action_just_pressed("ui_down"):
		change_focus(0, 1)
		get_viewport().set_input_as_handled()
	elif Input.is_action_just_pressed("ui_up"):
		change_focus(0, -1)
		get_viewport().set_input_as_handled()
	elif Input.is_action_just_pressed("ui_accept"):
		if focusable_buttons[current_focus_index].has_focus():
			focusable_buttons[current_focus_index].emit_signal("pressed")
			get_viewport().set_input_as_handled()  # Prevent double input

func change_focus(dx: int, dy: int):
	var grid_size = 3  # 3x3 grid for each small board
	@warning_ignore("integer_division")
	var current_grid = int(current_focus_index / (grid_size * grid_size))
	var position_in_grid = current_focus_index % (grid_size * grid_size)
	@warning_ignore("integer_division")
	var row = int(position_in_grid / grid_size)
	var col = position_in_grid % grid_size
		
	row += dy
	col += dx
		
	if row < 0:
		if current_grid >= 3:
			current_grid -= 3
			row = 2
		else:
			row = 0
	elif row > 2:
		if current_grid < 6:
			current_grid += 3
			row = 0
		else:
			row = 2
		
	if col < 0:
		if current_grid % 3 > 0:
			current_grid -= 1
			col = 2
		else:
			col = 0
	elif col > 2:
		if current_grid % 3 < 2:
			current_grid += 1
			col = 0
		else:
			col = 2
		
	var new_index = (current_grid * grid_size * grid_size) + (row * grid_size) + col
	new_index = clamp(new_index, 0, focusable_buttons.size() - 1)
		
	if new_index != current_focus_index:
		current_focus_index = new_index
		focusable_buttons[current_focus_index].grab_focus()

func update_state_labels() -> void:
	for i in range(state_labels.size()):
		state_labels[i].visible = (i == active_board and small_board_wins[i] == null)
	
	for i in range(NUM_BOARDS):
		var board_node = get_node("Board" + str(i + 1))
		for cell in board_node.get_children():
			if cell is Button:
				cell.disabled = not is_valid_move(i) or small_board_wins[i] != null

	if active_board != -1 and not is_board_full(active_board):
		var board_node = get_node("Board" + str(active_board + 1))
		for j in range(9):
			var cell: Button = board_node.get_child(j)
			if cell.text == "":
				cell.grab_focus()
				current_focus_index = focusable_buttons.find(cell)
				break
	else:
		active_board = find_next_unwon_board()
		if active_board != -1:
			update_state_labels()

func is_column_locked(board_index: int) -> bool:
	var column = board_index % 3
	for i in range(3):
		var column_board_index = column + (i * 3)
		if small_board_wins[column_board_index] == null:
			return false
	return true

func _process(_delta: float) -> void:
	for button in focusable_buttons:
		if is_instance_valid(button):
			button_hovered(button)
	
func start_tween(object: Object, property: String, final_val: Variant, duration: float):
	var tween = create_tween()
	tween.tween_property(object, property, final_val, duration)

func button_hovered(button: Control) -> void:
	if not is_instance_valid(button):
		return
		
	if not button is Button:
		return
		
	button.pivot_offset = button.size / 2
	if button.is_hovered() or button.has_focus():
		start_tween(button, "scale", Vector2.ONE * tween_intensity, tween_duration)
	else:
		start_tween(button, "scale", Vector2.ONE, tween_duration)

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

func update_player_labels():
	x_playing_label.visible = (current_player == "X")
	o_playing_label.visible = (current_player == "O")

func update_cell_color(cell: Button) -> void:
	if cell.text != "":
		var color = ColorManager.get_player_color(cell.text)
		cell.add_theme_color_override("font_color", color)

func _on_cell_updated(cell: Button) -> void:
	var board_index = cell.board_index
	var cell_index = cell.cell_index
	
	if overall_winner != "" or not is_valid_move(board_index) or small_board_wins[board_index] != null:
		return

	cell.text = current_player
	cell.cell_value = current_player
	boards[board_index][cell_index] = current_player
	update_cell_color(cell)
	cell.disabled = true  # Disable the cell after it's been played

	if check_winner(board_index):
		small_board_wins[board_index] = current_player
		update_board_win_label(board_index)
		lock_column(board_index)

		if check_overall_winner():
			display_winner(current_player)
			lock_game()
			return

	active_board = cell_index if small_board_wins[cell_index] == null else find_next_unwon_board()
	update_state_labels()

	current_player = "O" if current_player == "X" else "X"
	update_player_labels()

func is_valid_move(board_index: int) -> bool:
	return active_board == -1 or active_board == board_index


func find_next_unwon_board() -> int:
	var available_boards = []
	for i in range(9):
		if small_board_wins[i] == null and not is_board_full(i) and not is_column_locked(i):
			available_boards.append(i)
	if available_boards.size() > 0:
		return available_boards[randi() % available_boards.size()]
	return -1  # No unwon boards available

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_Yourself_Won"):
		stats["Ultimate_Tic_Tac_Toe_Yourself_Won"] = 0
	if not stats.has("Ultimate_Tic_Tac_Toe_Yourself_Lost"):
		stats["Ultimate_Tic_Tac_Toe_Yourself_Lost"] = 0

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

	save_stats(stats)

func display_winner(player: String) -> void:
	if player == "X":
		update_stats(true, "X")  # Player wins
		update_stats(false, "O")  # AI loses
		winner_label.text = "X wins!"
	elif player == "O":
		update_stats(false, "X")  # Player loses
		update_stats(true, "O")  # AI wins
		winner_label.text = "O wins!"
	else:
		# It's a tie
		update_stats(false, "X")
		update_stats(false, "O")
		winner_label.text = "It's a Tie!"

	# 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

	winner_label.visible = true
	home_button.visible = true
	rematch_button.visible = true

	lock_game()
	overall_winner = player

	# Update cell colors if needed
	for cell in cells:
		update_cell_color(cell)
		cell.queue_redraw()  # Force redraw of the cell

	# Set focus to the appropriate button based on the winner
	if overall_winner == "X":
		rematch_button.grab_focus()  # Focus on rematch button
	else:
		home_button.grab_focus()  # Focus on home button

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

func reset_game_state() -> void:
	current_player = "X"
	small_board_wins = [null, null, null, null, null, null, null, null, null]
	overall_winner = ""
	active_board = -1
	for cell in cells:
		cell.disabled = false
		cell.text = ""
		cell.cell_value = ""
	update_state_labels()

func is_board_full(board_index: int) -> bool:
	var board_node = get_node("Board" + str(board_index + 1))
	for cell in board_node.get_children():
		if cell is Button and cell.text == "":
			return false  # Return false if any cell is empty
	return true  # Return true if all cells are filled

func lock_game() -> void:
	for board_index in range(9):
		lock_column(board_index)
	active_board = -1

func check_winner(board_index: int) -> bool:
	var current_board = boards[board_index]
	
	# Check rows
	for i in range(3):
		if current_board[i*3] != "" and current_board[i*3] == current_board[i*3+1] and current_board[i*3] == current_board[i*3+2]:
			return true
	
	# Check columns
	for i in range(3):
		if current_board[i] != "" and current_board[i] == current_board[i+3] and current_board[i] == current_board[i+6]:
			return true
	
	# Check diagonals
	if current_board[0] != "" and current_board[0] == current_board[4] and current_board[0] == current_board[8]:
		return true
	if current_board[2] != "" and current_board[2] == current_board[4] and current_board[2] == current_board[6]:
		return true
	
	return false

func check_overall_winner() -> bool:
	for i in range(3):
		if small_board_wins[i*3] != null and small_board_wins[i*3] == small_board_wins[i*3+1] and small_board_wins[i*3] == small_board_wins[i*3+2]:
			return true
		if small_board_wins[i] != null and small_board_wins[i] == small_board_wins[i+3] and small_board_wins[i] == small_board_wins[i+6]:
			return true
	if small_board_wins[0] != null and small_board_wins[0] == small_board_wins[4] and small_board_wins[0] == small_board_wins[8]:
		return true
	if small_board_wins[2] != null and small_board_wins[2] == small_board_wins[4] and small_board_wins[2] == small_board_wins[6]:
		return true
	return false

func update_board_win_label(board_index: int) -> void:
	# Update labels based on column index
	if small_board_wins[board_index] == "X":
		if has_node("XWinLabelColumn" + str(board_index + 1)):
			get_node("XWinLabelColumn" + str(board_index + 1)).visible = true
	elif small_board_wins[board_index] == "O":
		if has_node("OWinLabelColumn" + str(board_index + 1)):
			get_node("OWinLabelColumn" + str(board_index + 1)).visible = true
	else:  # It's a tie
		if has_node("ItsATieLabelColumn" + str(board_index + 1)):
			get_node("ItsATieLabelColumn" + str(board_index + 1)).visible = true

func lock_column(board_index: int) -> void:
	var column = board_index % 3
	for i in range(3):
		var column_board_index = column + (i * 3)
		var board_node = get_node("Board" + str(column_board_index + 1))
		for cell in board_node.get_children():
			if cell is Button:
				cell.disabled = true
	update_state_labels()

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
	Transition.load_scene("res://Scenes/UI.tscn")
	# 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
	Transition.load_scene("res://Scenes/UI.tscn")
	# 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 i in range(boards.size()):
		for j in range(boards[i].size()):
			boards[i][j] = ""
	
	for cell in cells:
		cell.cell_value = ""
		cell.text = ""
		cell.self_modulate = Color.WHITE
		cell.disabled = false
	
	small_board_wins = [null, null, null, null, null, null, null, null, null]

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 _on_ResetGame_pressed() -> void:
	button5_click_player.play()  # Play the button click sound
	print("Reset Game button pressed")
	await get_tree().create_timer(0.1).timeout  # Add a delay
	reset_game()

func reset_game():
	# Clear win labels
	for i in range(1, 10):
		if has_node("XWinLabelColumn" + str(i)):
			get_node("XWinLabelColumn" + str(i)).visible = false
		if has_node("OWinLabelColumn" + str(i)):
			get_node("OWinLabelColumn" + str(i)).visible = false
		if has_node("ItsATieLabelColumn" + str(i)):
			get_node("ItsATieLabelColumn" + str(i)).visible = false
	
	is_game_end = false
	board = ["", "", "", "", "", "", "", "", ""]
	randomize_starting_player()

	for cell in cells:
		cell.cell_value = ""
		cell.text = ""
		cell.self_modulate = Color.WHITE
		cell.disabled = false
	
	$WinnerLabel.hide()
	$CanvasLayer/Control/HomeTownButton.hide()
	$CanvasLayer/Control/RematchYourselfButton.hide()
	update_player_labels()

func randomize_starting_player():
	current_player = "X" if randf() < 0.5 else "O"
	update_playing_labels()

func update_playing_labels():
	x_playing_label.visible = (current_player == "X")
	o_playing_label.visible = (current_player == "O")

And here is my Cell.gd (the script that references my cells to use in miniboard.gd and my other scripts:

extends Button

signal cell_updated(cell: Button)  # Updated signal definition
var main: Control
var cell_value: String = ""
var board_index: int
var cell_index: int

@onready var background = $Background
@onready var border = $Border

func _ready():
	custom_minimum_size = Vector2(50, 50)
	size_flags_horizontal = SIZE_EXPAND_FILL
	size_flags_vertical = SIZE_EXPAND_FILL
	
	var stylebox = StyleBoxFlat.new()
	stylebox.set_border_width_all(1)
	stylebox.border_color = Color.WHITE
	add_theme_stylebox_override("normal", stylebox)
	
	self_modulate.a = 0
	pressed.connect(_on_pressed)

func _on_pressed():
	draw_cell()

func draw_x():
	var tween = create_tween()
	self_modulate = Color("#00ffff")
	self_modulate.a = 0
	text = "X"
	cell_value = "X"
	tween.tween_property(self, "self_modulate:a", 1, 0.5)

func draw_o():
	var tween = create_tween()
	self_modulate = Color("#ff4200")
	self_modulate.a = 0
	text = "O"
	cell_value = "O"
	tween.tween_property(self, "self_modulate:a", 1, 0.5)

func draw_cell():
	if main == null or main.is_game_end or cell_value != "":
		return
	
	if main.current_player == "X":
		draw_x()
	else:
		draw_o()
	
	cell_updated.emit(self)  # Updated signal emit

func glow(color: Color):
	var tween = create_tween()
	background.modulate = color
	background.modulate.a = 0
	tween.tween_property(background, "modulate:a", 1, 0.5)

Here is the SceneTree’s if needed:
Cell.gd:


miniboard.gd:



Any help would be very much appreciated, and let me know if I should remove or add anything, or if you need anything, Thank you so much!! :slight_smile:

The error tells you that you try to add a node as a child of another node, eventhough it already has a parent. If you want to reparent a child use the my_node.reparent(new_parent)-method instead of the new_parent.add_child(my_node)

1 Like

picture:


So when I tried your thing, it just made the board disappear, maybe cuz i’m not using the right parent or something? I am slightly confused (I dont get the errors though) Thanks for helping! Any help would be very much appreciated

grid.reparent(cell)

i think you have to turn around the arguments:

cell.reparent(grid)