|
|
|
|
@@ -3,12 +3,7 @@ extends Node2D
|
|
|
|
|
signal score_changed(points: int)
|
|
|
|
|
signal grid_state_loaded(grid_size: Vector2i, tile_types: int)
|
|
|
|
|
|
|
|
|
|
enum GameState {
|
|
|
|
|
WAITING, # Waiting for player input
|
|
|
|
|
SELECTING, # First tile selected
|
|
|
|
|
SWAPPING, # Animating tile swap
|
|
|
|
|
PROCESSING # Processing matches and cascades
|
|
|
|
|
}
|
|
|
|
|
enum GameState { WAITING, SELECTING, SWAPPING, PROCESSING } # Waiting for player input # First tile selected # Animating tile swap # Processing matches and cascades
|
|
|
|
|
|
|
|
|
|
var GRID_SIZE := Vector2i(8, 8)
|
|
|
|
|
var TILE_TYPES := 5
|
|
|
|
|
@@ -32,22 +27,26 @@ const CASCADE_WAIT_TIME := 0.1
|
|
|
|
|
const SWAP_ANIMATION_TIME := 0.3
|
|
|
|
|
const TILE_DROP_WAIT_TIME := 0.2
|
|
|
|
|
|
|
|
|
|
var grid := []
|
|
|
|
|
var grid: Array[Array] = []
|
|
|
|
|
var tile_size: float = 48.0
|
|
|
|
|
var grid_offset: Vector2
|
|
|
|
|
var grid_offset: Vector2 = Vector2.ZERO
|
|
|
|
|
var current_state: GameState = GameState.WAITING
|
|
|
|
|
var selected_tile: Node2D = null
|
|
|
|
|
var cursor_position: Vector2i = Vector2i(0, 0)
|
|
|
|
|
var keyboard_navigation_enabled: bool = false
|
|
|
|
|
var grid_initialized: bool = false
|
|
|
|
|
var instance_id: String
|
|
|
|
|
var instance_id: String = ""
|
|
|
|
|
|
|
|
|
|
func _ready():
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
|
# Generate instance ID
|
|
|
|
|
instance_id = "Match3_%d" % get_instance_id()
|
|
|
|
|
|
|
|
|
|
if grid_initialized:
|
|
|
|
|
DebugManager.log_warn("[%s] Match3 _ready() called multiple times, skipping initialization" % instance_id, "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
"[%s] Match3 _ready() called multiple times, skipping initialization" % instance_id,
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("[%s] Match3 _ready() started" % instance_id, "Match3")
|
|
|
|
|
@@ -72,6 +71,7 @@ func _ready():
|
|
|
|
|
# Debug: Check scene tree structure
|
|
|
|
|
call_deferred("_debug_scene_structure")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _calculate_grid_layout():
|
|
|
|
|
var viewport_size = get_viewport().get_visible_rect().size
|
|
|
|
|
var available_width = viewport_size.x * SCREEN_WIDTH_USAGE
|
|
|
|
|
@@ -85,13 +85,13 @@ func _calculate_grid_layout():
|
|
|
|
|
# Align grid to left side with margins
|
|
|
|
|
var total_grid_height = tile_size * GRID_SIZE.y
|
|
|
|
|
grid_offset = Vector2(
|
|
|
|
|
GRID_LEFT_MARGIN,
|
|
|
|
|
(viewport_size.y - total_grid_height) / 2 + GRID_TOP_MARGIN
|
|
|
|
|
GRID_LEFT_MARGIN, (viewport_size.y - total_grid_height) / 2 + GRID_TOP_MARGIN
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize_grid():
|
|
|
|
|
# Create gem pool for current tile types
|
|
|
|
|
var gem_indices = []
|
|
|
|
|
var gem_indices: Array[int] = []
|
|
|
|
|
for i in range(TILE_TYPES):
|
|
|
|
|
gem_indices.append(i)
|
|
|
|
|
|
|
|
|
|
@@ -113,9 +113,16 @@ func _initialize_grid():
|
|
|
|
|
|
|
|
|
|
# Connect tile signals
|
|
|
|
|
tile.tile_selected.connect(_on_tile_selected)
|
|
|
|
|
DebugManager.log_debug("Created tile at grid(%d,%d) world_pos(%s) with type %d" % [x, y, tile_position, new_type], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Created tile at grid(%d,%d) world_pos(%s) with type %d"
|
|
|
|
|
% [x, y, tile_position, new_type]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
grid[y].append(tile)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _has_match_at(pos: Vector2i) -> bool:
|
|
|
|
|
# Bounds and null checks
|
|
|
|
|
if not _is_valid_grid_position(pos):
|
|
|
|
|
@@ -131,7 +138,9 @@ func _has_match_at(pos: Vector2i) -> bool:
|
|
|
|
|
|
|
|
|
|
# Check if tile has required properties
|
|
|
|
|
if not "tile_type" in tile:
|
|
|
|
|
DebugManager.log_warn("Tile at (%d,%d) missing tile_type property" % [pos.x, pos.y], "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
"Tile at (%d,%d) missing tile_type property" % [pos.x, pos.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
var matches_horizontal = _get_match_line(pos, Vector2i(1, 0))
|
|
|
|
|
@@ -141,6 +150,7 @@ func _has_match_at(pos: Vector2i) -> bool:
|
|
|
|
|
var matches_vertical = _get_match_line(pos, Vector2i(0, 1))
|
|
|
|
|
return matches_vertical.size() >= 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check for any matches on the board
|
|
|
|
|
func _check_for_matches() -> bool:
|
|
|
|
|
for y in range(GRID_SIZE.y):
|
|
|
|
|
@@ -149,14 +159,19 @@ func _check_for_matches() -> bool:
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
|
|
|
|
# Validate input parameters
|
|
|
|
|
if not _is_valid_grid_position(start):
|
|
|
|
|
DebugManager.log_error("Invalid start position for match line: (%d,%d)" % [start.x, start.y], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Invalid start position for match line: (%d,%d)" % [start.x, start.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
if abs(dir.x) + abs(dir.y) != 1 or (dir.x != 0 and dir.y != 0):
|
|
|
|
|
DebugManager.log_error("Invalid direction vector for match line: (%d,%d)" % [dir.x, dir.y], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Invalid direction vector for match line: (%d,%d)" % [dir.x, dir.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# Check grid bounds and tile validity
|
|
|
|
|
@@ -194,6 +209,7 @@ func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
|
|
|
|
|
|
|
|
|
return line
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_matches():
|
|
|
|
|
# Check grid integrity
|
|
|
|
|
if not _validate_grid_integrity():
|
|
|
|
|
@@ -278,10 +294,17 @@ func _clear_matches():
|
|
|
|
|
|
|
|
|
|
var tile_pos = tile.grid_position
|
|
|
|
|
# Validate grid position before clearing reference
|
|
|
|
|
if _is_valid_grid_position(tile_pos) and tile_pos.y < grid.size() and tile_pos.x < grid[tile_pos.y].size():
|
|
|
|
|
if (
|
|
|
|
|
_is_valid_grid_position(tile_pos)
|
|
|
|
|
and tile_pos.y < grid.size()
|
|
|
|
|
and tile_pos.x < grid[tile_pos.y].size()
|
|
|
|
|
):
|
|
|
|
|
grid[tile_pos.y][tile_pos.x] = null
|
|
|
|
|
else:
|
|
|
|
|
DebugManager.log_warn("Invalid grid position during tile removal: (%d,%d)" % [tile_pos.x, tile_pos.y], "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
"Invalid grid position during tile removal: (%d,%d)" % [tile_pos.x, tile_pos.y],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
tile.queue_free()
|
|
|
|
|
|
|
|
|
|
@@ -291,6 +314,7 @@ func _clear_matches():
|
|
|
|
|
await get_tree().create_timer(TILE_DROP_WAIT_TIME).timeout
|
|
|
|
|
_fill_empty_cells()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _drop_tiles():
|
|
|
|
|
var moved = true
|
|
|
|
|
while moved:
|
|
|
|
|
@@ -309,6 +333,7 @@ func _drop_tiles():
|
|
|
|
|
tile.position = grid_offset + Vector2(x, y + 1) * tile_size
|
|
|
|
|
moved = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _fill_empty_cells():
|
|
|
|
|
# Safety check for grid integrity
|
|
|
|
|
if not _validate_grid_integrity():
|
|
|
|
|
@@ -316,7 +341,7 @@ func _fill_empty_cells():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Create gem pool for current tile types
|
|
|
|
|
var gem_indices = []
|
|
|
|
|
var gem_indices: Array[int] = []
|
|
|
|
|
for i in range(TILE_TYPES):
|
|
|
|
|
gem_indices.append(i)
|
|
|
|
|
|
|
|
|
|
@@ -334,7 +359,9 @@ func _fill_empty_cells():
|
|
|
|
|
if not grid[y][x]:
|
|
|
|
|
var tile = TILE_SCENE.instantiate()
|
|
|
|
|
if not tile:
|
|
|
|
|
DebugManager.log_error("Failed to instantiate tile at (%d,%d)" % [x, y], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Failed to instantiate tile at (%d,%d)" % [x, y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
tile.grid_position = Vector2i(x, y)
|
|
|
|
|
@@ -375,19 +402,35 @@ func _fill_empty_cells():
|
|
|
|
|
iteration += 1
|
|
|
|
|
|
|
|
|
|
if iteration >= MAX_CASCADE_ITERATIONS:
|
|
|
|
|
DebugManager.log_warn("Maximum cascade iterations reached (%d), stopping to prevent infinite loop" % MAX_CASCADE_ITERATIONS, "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
(
|
|
|
|
|
"Maximum cascade iterations reached (%d), stopping to prevent infinite loop"
|
|
|
|
|
% MAX_CASCADE_ITERATIONS
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Save grid state after cascades complete
|
|
|
|
|
save_current_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func regenerate_grid():
|
|
|
|
|
# Validate grid size before regeneration
|
|
|
|
|
if GRID_SIZE.x < MIN_GRID_SIZE or GRID_SIZE.y < MIN_GRID_SIZE or GRID_SIZE.x > MAX_GRID_SIZE or GRID_SIZE.y > MAX_GRID_SIZE:
|
|
|
|
|
DebugManager.log_error("Invalid grid size for regeneration: %dx%d" % [GRID_SIZE.x, GRID_SIZE.y], "Match3")
|
|
|
|
|
if (
|
|
|
|
|
GRID_SIZE.x < MIN_GRID_SIZE
|
|
|
|
|
or GRID_SIZE.y < MIN_GRID_SIZE
|
|
|
|
|
or GRID_SIZE.x > MAX_GRID_SIZE
|
|
|
|
|
or GRID_SIZE.y > MAX_GRID_SIZE
|
|
|
|
|
):
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Invalid grid size for regeneration: %dx%d" % [GRID_SIZE.x, GRID_SIZE.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if TILE_TYPES < 3 or TILE_TYPES > MAX_TILE_TYPES:
|
|
|
|
|
DebugManager.log_error("Invalid tile types count for regeneration: %d" % TILE_TYPES, "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Invalid tile types count for regeneration: %d" % TILE_TYPES, "Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Use time-based seed to ensure different patterns each time
|
|
|
|
|
@@ -440,6 +483,7 @@ func regenerate_grid():
|
|
|
|
|
# Regenerate the grid with safety checks
|
|
|
|
|
_initialize_grid()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_tile_types(new_count: int):
|
|
|
|
|
# Input validation
|
|
|
|
|
if new_count < 3:
|
|
|
|
|
@@ -447,7 +491,9 @@ func set_tile_types(new_count: int):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if new_count > MAX_TILE_TYPES:
|
|
|
|
|
DebugManager.log_error("Tile types count too high: %d (maximum %d)" % [new_count, MAX_TILE_TYPES], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Tile types count too high: %d (maximum %d)" % [new_count, MAX_TILE_TYPES], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if new_count == TILE_TYPES:
|
|
|
|
|
@@ -460,14 +506,27 @@ func set_tile_types(new_count: int):
|
|
|
|
|
# Regenerate grid with new tile types (gem pool is updated in regenerate_grid)
|
|
|
|
|
await regenerate_grid()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func set_grid_size(new_size: Vector2i):
|
|
|
|
|
# Comprehensive input validation
|
|
|
|
|
if new_size.x < MIN_GRID_SIZE or new_size.y < MIN_GRID_SIZE:
|
|
|
|
|
DebugManager.log_error("Grid size too small: %dx%d (minimum %dx%d)" % [new_size.x, new_size.y, MIN_GRID_SIZE, MIN_GRID_SIZE], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
(
|
|
|
|
|
"Grid size too small: %dx%d (minimum %dx%d)"
|
|
|
|
|
% [new_size.x, new_size.y, MIN_GRID_SIZE, MIN_GRID_SIZE]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if new_size.x > MAX_GRID_SIZE or new_size.y > MAX_GRID_SIZE:
|
|
|
|
|
DebugManager.log_error("Grid size too large: %dx%d (maximum %dx%d)" % [new_size.x, new_size.y, MAX_GRID_SIZE, MAX_GRID_SIZE], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
(
|
|
|
|
|
"Grid size too large: %dx%d (maximum %dx%d)"
|
|
|
|
|
% [new_size.x, new_size.y, MAX_GRID_SIZE, MAX_GRID_SIZE]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if new_size == GRID_SIZE:
|
|
|
|
|
@@ -480,6 +539,7 @@ func set_grid_size(new_size: Vector2i):
|
|
|
|
|
# Regenerate grid with new size
|
|
|
|
|
await regenerate_grid()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func reset_all_visual_states() -> void:
|
|
|
|
|
# Debug function to reset all tile visual states
|
|
|
|
|
DebugManager.log_debug("Resetting all tile visual states", "Match3")
|
|
|
|
|
@@ -493,6 +553,7 @@ func reset_all_visual_states() -> void:
|
|
|
|
|
current_state = GameState.WAITING
|
|
|
|
|
keyboard_navigation_enabled = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _debug_scene_structure() -> void:
|
|
|
|
|
DebugManager.log_debug("=== Scene Structure Debug ===", "Match3")
|
|
|
|
|
DebugManager.log_debug("Match3 node children count: %d" % get_child_count(), "Match3")
|
|
|
|
|
@@ -510,22 +571,30 @@ func _debug_scene_structure() -> void:
|
|
|
|
|
for x in range(GRID_SIZE.x):
|
|
|
|
|
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
|
|
|
|
tile_count += 1
|
|
|
|
|
DebugManager.log_debug("Created %d tiles out of %d expected" % [tile_count, GRID_SIZE.x * GRID_SIZE.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Created %d tiles out of %d expected" % [tile_count, GRID_SIZE.x * GRID_SIZE.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Check first tile in detail
|
|
|
|
|
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
|
|
|
|
var first_tile = grid[0][0]
|
|
|
|
|
DebugManager.log_debug("First tile global position: %s" % first_tile.global_position, "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"First tile global position: %s" % first_tile.global_position, "Match3"
|
|
|
|
|
)
|
|
|
|
|
DebugManager.log_debug("First tile local position: %s" % first_tile.position, "Match3")
|
|
|
|
|
|
|
|
|
|
# Check parent chain
|
|
|
|
|
var current_node = self
|
|
|
|
|
var depth = 0
|
|
|
|
|
while current_node and depth < 10:
|
|
|
|
|
DebugManager.log_debug("Parent level %d: %s (type: %s)" % [depth, current_node.name, current_node.get_class()], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Parent level %d: %s (type: %s)" % [depth, current_node.name, current_node.get_class()],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
current_node = current_node.get_parent()
|
|
|
|
|
depth += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
|
|
|
# Debug key to reset all visual states
|
|
|
|
|
if event.is_action_pressed("action_east") and DebugManager.is_debug_enabled():
|
|
|
|
|
@@ -560,7 +629,9 @@ func _move_cursor(direction: Vector2i) -> void:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if direction.x != 0 and direction.y != 0:
|
|
|
|
|
DebugManager.log_error("Diagonal cursor movement not supported: " + str(direction), "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Diagonal cursor movement not supported: " + str(direction), "Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Validate grid integrity before cursor operations
|
|
|
|
|
@@ -582,7 +653,10 @@ func _move_cursor(direction: Vector2i) -> void:
|
|
|
|
|
if not old_tile.is_selected:
|
|
|
|
|
old_tile.is_highlighted = false
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("Cursor moved from (%d,%d) to (%d,%d)" % [old_pos.x, old_pos.y, new_pos.x, new_pos.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Cursor moved from (%d,%d) to (%d,%d)" % [old_pos.x, old_pos.y, new_pos.x, new_pos.y],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
cursor_position = new_pos
|
|
|
|
|
|
|
|
|
|
# Safe access to new tile
|
|
|
|
|
@@ -591,25 +665,48 @@ func _move_cursor(direction: Vector2i) -> void:
|
|
|
|
|
if not new_tile.is_selected:
|
|
|
|
|
new_tile.is_highlighted = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _select_tile_at_cursor() -> void:
|
|
|
|
|
# Validate cursor position and grid integrity
|
|
|
|
|
if not _is_valid_grid_position(cursor_position):
|
|
|
|
|
DebugManager.log_warn("Invalid cursor position for selection: (%d,%d)" % [cursor_position.x, cursor_position.y], "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
(
|
|
|
|
|
"Invalid cursor position for selection: (%d,%d)"
|
|
|
|
|
% [cursor_position.x, cursor_position.y]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var tile = _safe_grid_access(cursor_position)
|
|
|
|
|
if tile:
|
|
|
|
|
DebugManager.log_debug("Keyboard selection at cursor (%d,%d)" % [cursor_position.x, cursor_position.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Keyboard selection at cursor (%d,%d)" % [cursor_position.x, cursor_position.y],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
_on_tile_selected(tile)
|
|
|
|
|
else:
|
|
|
|
|
DebugManager.log_warn("No valid tile at cursor position (%d,%d)" % [cursor_position.x, cursor_position.y], "Match3")
|
|
|
|
|
DebugManager.log_warn(
|
|
|
|
|
"No valid tile at cursor position (%d,%d)" % [cursor_position.x, cursor_position.y],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_tile_selected(tile: Node2D) -> void:
|
|
|
|
|
if current_state == GameState.SWAPPING or current_state == GameState.PROCESSING:
|
|
|
|
|
DebugManager.log_debug("Tile selection ignored - game busy (state: %s)" % [GameState.keys()[current_state]], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Tile selection ignored - game busy (state: %s)" % [GameState.keys()[current_state]],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("Tile selected at (%d,%d), gem type: %d" % [tile.grid_position.x, tile.grid_position.y, tile.tile_type], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Tile selected at (%d,%d), gem type: %d"
|
|
|
|
|
% [tile.grid_position.x, tile.grid_position.y, tile.tile_type]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if current_state == GameState.WAITING:
|
|
|
|
|
# First tile selection
|
|
|
|
|
@@ -621,7 +718,18 @@ func _on_tile_selected(tile: Node2D) -> void:
|
|
|
|
|
_deselect_tile()
|
|
|
|
|
else:
|
|
|
|
|
# Attempt to swap with selected tile
|
|
|
|
|
DebugManager.log_debug("Attempting swap between (%d,%d) and (%d,%d)" % [selected_tile.grid_position.x, selected_tile.grid_position.y, tile.grid_position.x, tile.grid_position.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Attempting swap between (%d,%d) and (%d,%d)"
|
|
|
|
|
% [
|
|
|
|
|
selected_tile.grid_position.x,
|
|
|
|
|
selected_tile.grid_position.y,
|
|
|
|
|
tile.grid_position.x,
|
|
|
|
|
tile.grid_position.y
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
_attempt_swap(selected_tile, tile)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -629,13 +737,22 @@ func _select_tile(tile: Node2D) -> void:
|
|
|
|
|
selected_tile = tile
|
|
|
|
|
tile.is_selected = true
|
|
|
|
|
current_state = GameState.SELECTING
|
|
|
|
|
DebugManager.log_debug("Selected tile at (%d, %d)" % [tile.grid_position.x, tile.grid_position.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Selected tile at (%d, %d)" % [tile.grid_position.x, tile.grid_position.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _deselect_tile() -> void:
|
|
|
|
|
if selected_tile and is_instance_valid(selected_tile):
|
|
|
|
|
# Safe access to tile properties
|
|
|
|
|
if "grid_position" in selected_tile:
|
|
|
|
|
DebugManager.log_debug("Deselecting tile at (%d,%d)" % [selected_tile.grid_position.x, selected_tile.grid_position.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Deselecting tile at (%d,%d)"
|
|
|
|
|
% [selected_tile.grid_position.x, selected_tile.grid_position.y]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
DebugManager.log_debug("Deselecting tile (no grid position available)", "Match3")
|
|
|
|
|
|
|
|
|
|
@@ -653,7 +770,13 @@ func _deselect_tile() -> void:
|
|
|
|
|
var cursor_tile = _safe_grid_access(cursor_position)
|
|
|
|
|
if cursor_tile and "is_highlighted" in cursor_tile:
|
|
|
|
|
cursor_tile.is_highlighted = true
|
|
|
|
|
DebugManager.log_debug("Restored cursor highlighting at (%d,%d)" % [cursor_position.x, cursor_position.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Restored cursor highlighting at (%d,%d)"
|
|
|
|
|
% [cursor_position.x, cursor_position.y]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# For mouse navigation, just clear highlighting
|
|
|
|
|
if "is_highlighted" in selected_tile:
|
|
|
|
|
@@ -662,6 +785,7 @@ func _deselect_tile() -> void:
|
|
|
|
|
selected_tile = null
|
|
|
|
|
current_state = GameState.WAITING
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _are_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
|
|
|
|
if not tile1 or not tile2:
|
|
|
|
|
return false
|
|
|
|
|
@@ -671,19 +795,44 @@ func _are_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
|
|
|
|
var diff = abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y)
|
|
|
|
|
return diff == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void:
|
|
|
|
|
if not _are_adjacent(tile1, tile2):
|
|
|
|
|
DebugManager.log_debug("Tiles are not adjacent, cannot swap", "Match3")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("Starting swap animation: (%d,%d)[type:%d] <-> (%d,%d)[type:%d]" % [tile1.grid_position.x, tile1.grid_position.y, tile1.tile_type, tile2.grid_position.x, tile2.grid_position.y, tile2.tile_type], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Starting swap animation: (%d,%d)[type:%d] <-> (%d,%d)[type:%d]"
|
|
|
|
|
% [
|
|
|
|
|
tile1.grid_position.x,
|
|
|
|
|
tile1.grid_position.y,
|
|
|
|
|
tile1.tile_type,
|
|
|
|
|
tile2.grid_position.x,
|
|
|
|
|
tile2.grid_position.y,
|
|
|
|
|
tile2.tile_type
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
current_state = GameState.SWAPPING
|
|
|
|
|
await _swap_tiles(tile1, tile2)
|
|
|
|
|
|
|
|
|
|
# Check if swap creates matches
|
|
|
|
|
if _has_match_at(tile1.grid_position) or _has_match_at(tile2.grid_position):
|
|
|
|
|
DebugManager.log_info("Valid swap created matches at (%d,%d) or (%d,%d)" % [tile1.grid_position.x, tile1.grid_position.y, tile2.grid_position.x, tile2.grid_position.y], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"Valid swap created matches at (%d,%d) or (%d,%d)"
|
|
|
|
|
% [
|
|
|
|
|
tile1.grid_position.x,
|
|
|
|
|
tile1.grid_position.y,
|
|
|
|
|
tile2.grid_position.x,
|
|
|
|
|
tile2.grid_position.y
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
_deselect_tile()
|
|
|
|
|
current_state = GameState.PROCESSING
|
|
|
|
|
_clear_matches()
|
|
|
|
|
@@ -697,6 +846,7 @@ func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void:
|
|
|
|
|
_deselect_tile()
|
|
|
|
|
current_state = GameState.WAITING
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _swap_tiles(tile1: Node2D, tile2: Node2D) -> void:
|
|
|
|
|
if not tile1 or not tile2:
|
|
|
|
|
DebugManager.log_error("Cannot swap tiles - one or both tiles are null", "Match3")
|
|
|
|
|
@@ -706,7 +856,13 @@ func _swap_tiles(tile1: Node2D, tile2: Node2D) -> void:
|
|
|
|
|
var pos1 = tile1.grid_position
|
|
|
|
|
var pos2 = tile2.grid_position
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("Swapping tile positions: (%d,%d) -> (%d,%d), (%d,%d) -> (%d,%d)" % [pos1.x, pos1.y, pos2.x, pos2.y, pos2.x, pos2.y, pos1.x, pos1.y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Swapping tile positions: (%d,%d) -> (%d,%d), (%d,%d) -> (%d,%d)"
|
|
|
|
|
% [pos1.x, pos1.y, pos2.x, pos2.y, pos2.x, pos2.y, pos1.x, pos1.y]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
tile1.grid_position = pos2
|
|
|
|
|
tile2.grid_position = pos1
|
|
|
|
|
@@ -729,9 +885,16 @@ func _swap_tiles(tile1: Node2D, tile2: Node2D) -> void:
|
|
|
|
|
await tween.finished
|
|
|
|
|
DebugManager.log_trace("Tile swap animation completed", "Match3")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func serialize_grid_state() -> Array:
|
|
|
|
|
# Convert the current grid to a serializable 2D array
|
|
|
|
|
DebugManager.log_info("Starting serialization: grid.size()=%d, GRID_SIZE=(%d,%d)" % [grid.size(), GRID_SIZE.x, GRID_SIZE.y], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"Starting serialization: grid.size()=%d, GRID_SIZE=(%d,%d)"
|
|
|
|
|
% [grid.size(), GRID_SIZE.x, GRID_SIZE.y]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if grid.size() == 0:
|
|
|
|
|
DebugManager.log_error("Grid array is empty during serialization!", "Match3")
|
|
|
|
|
@@ -749,18 +912,29 @@ func serialize_grid_state() -> Array:
|
|
|
|
|
valid_tiles += 1
|
|
|
|
|
# Only log first few for brevity
|
|
|
|
|
if valid_tiles <= 5:
|
|
|
|
|
DebugManager.log_debug("Serializing tile (%d,%d): type %d" % [x, y, grid[y][x].tile_type], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Serializing tile (%d,%d): type %d" % [x, y, grid[y][x].tile_type], "Match3"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
row.append(-1) # Invalid/empty tile
|
|
|
|
|
null_tiles += 1
|
|
|
|
|
# Only log first few nulls for brevity
|
|
|
|
|
if null_tiles <= 5:
|
|
|
|
|
DebugManager.log_debug("Serializing tile (%d,%d): NULL/empty (-1)" % [x, y], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Serializing tile (%d,%d): NULL/empty (-1)" % [x, y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
serialized_grid.append(row)
|
|
|
|
|
|
|
|
|
|
DebugManager.log_info("Serialized grid state: %dx%d grid, %d valid tiles, %d null tiles" % [GRID_SIZE.x, GRID_SIZE.y, valid_tiles, null_tiles], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"Serialized grid state: %dx%d grid, %d valid tiles, %d null tiles"
|
|
|
|
|
% [GRID_SIZE.x, GRID_SIZE.y, valid_tiles, null_tiles]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return serialized_grid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func get_active_gem_types() -> Array:
|
|
|
|
|
# Get active gem types from the first available tile
|
|
|
|
|
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
|
|
|
|
@@ -772,15 +946,23 @@ func get_active_gem_types() -> Array:
|
|
|
|
|
default_types.append(i)
|
|
|
|
|
return default_types
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func save_current_state():
|
|
|
|
|
# Save complete game state
|
|
|
|
|
var grid_layout = serialize_grid_state()
|
|
|
|
|
var active_gems = get_active_gem_types()
|
|
|
|
|
|
|
|
|
|
DebugManager.log_info("Saving match3 state: size(%d,%d), %d tile types, %d active gems" % [GRID_SIZE.x, GRID_SIZE.y, TILE_TYPES, active_gems.size()], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"Saving match3 state: size(%d,%d), %d tile types, %d active gems"
|
|
|
|
|
% [GRID_SIZE.x, GRID_SIZE.y, TILE_TYPES, active_gems.size()]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
SaveManager.save_grid_state(GRID_SIZE, TILE_TYPES, active_gems, grid_layout)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func load_saved_state() -> bool:
|
|
|
|
|
# Check if there's a saved grid state
|
|
|
|
|
if not SaveManager.has_saved_grid():
|
|
|
|
|
@@ -792,10 +974,18 @@ func load_saved_state() -> bool:
|
|
|
|
|
# Restore grid settings
|
|
|
|
|
var saved_size = Vector2i(saved_state.grid_size.x, saved_state.grid_size.y)
|
|
|
|
|
TILE_TYPES = saved_state.tile_types_count
|
|
|
|
|
var saved_gems = saved_state.active_gem_types
|
|
|
|
|
var saved_gems: Array[int] = []
|
|
|
|
|
for gem in saved_state.active_gem_types:
|
|
|
|
|
saved_gems.append(int(gem))
|
|
|
|
|
var saved_layout = saved_state.grid_layout
|
|
|
|
|
|
|
|
|
|
DebugManager.log_info("[%s] Loading saved grid state: size(%d,%d), %d tile types, layout_size=%d" % [instance_id, saved_size.x, saved_size.y, TILE_TYPES, saved_layout.size()], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"[%s] Loading saved grid state: size(%d,%d), %d tile types, layout_size=%d"
|
|
|
|
|
% [instance_id, saved_size.x, saved_size.y, TILE_TYPES, saved_layout.size()]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Debug: Print first few rows of loaded layout
|
|
|
|
|
for y in range(min(3, saved_layout.size())):
|
|
|
|
|
@@ -806,11 +996,23 @@ func load_saved_state() -> bool:
|
|
|
|
|
|
|
|
|
|
# Validate saved data
|
|
|
|
|
if saved_layout.size() != saved_size.y:
|
|
|
|
|
DebugManager.log_error("Saved grid layout height mismatch: expected %d, got %d" % [saved_size.y, saved_layout.size()], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
(
|
|
|
|
|
"Saved grid layout height mismatch: expected %d, got %d"
|
|
|
|
|
% [saved_size.y, saved_layout.size()]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if saved_layout.size() > 0 and saved_layout[0].size() != saved_size.x:
|
|
|
|
|
DebugManager.log_error("Saved grid layout width mismatch: expected %d, got %d" % [saved_size.x, saved_layout[0].size()], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
(
|
|
|
|
|
"Saved grid layout width mismatch: expected %d, got %d"
|
|
|
|
|
% [saved_size.x, saved_layout[0].size()]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
# Apply the saved settings
|
|
|
|
|
@@ -819,7 +1021,10 @@ func load_saved_state() -> bool:
|
|
|
|
|
|
|
|
|
|
# Recalculate layout if size changed
|
|
|
|
|
if old_size != saved_size:
|
|
|
|
|
DebugManager.log_info("Grid size changed from %s to %s, recalculating layout" % [old_size, saved_size], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
"Grid size changed from %s to %s, recalculating layout" % [old_size, saved_size],
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
_calculate_grid_layout()
|
|
|
|
|
|
|
|
|
|
await _restore_grid_from_layout(saved_layout, saved_gems)
|
|
|
|
|
@@ -827,8 +1032,15 @@ func load_saved_state() -> bool:
|
|
|
|
|
DebugManager.log_info("Successfully loaded saved grid state", "Match3")
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
func _restore_grid_from_layout(grid_layout: Array, active_gems: Array):
|
|
|
|
|
DebugManager.log_info("[%s] Starting grid restoration: layout_size=%d, active_gems=%s" % [instance_id, grid_layout.size(), active_gems], "Match3")
|
|
|
|
|
|
|
|
|
|
func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> void:
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
(
|
|
|
|
|
"[%s] Starting grid restoration: layout_size=%d, active_gems=%s"
|
|
|
|
|
% [instance_id, grid_layout.size(), active_gems]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Clear ALL existing tile children, not just ones in grid array
|
|
|
|
|
# This ensures no duplicate layers are created
|
|
|
|
|
@@ -839,7 +1051,9 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array):
|
|
|
|
|
if script_path == "res://scenes/game/gameplays/tile.gd":
|
|
|
|
|
all_tile_children.append(child)
|
|
|
|
|
|
|
|
|
|
DebugManager.log_debug("Found %d existing tile children to remove" % all_tile_children.size(), "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"Found %d existing tile children to remove" % all_tile_children.size(), "Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Remove all found tile children
|
|
|
|
|
for child in all_tile_children:
|
|
|
|
|
@@ -872,26 +1086,44 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array):
|
|
|
|
|
|
|
|
|
|
# Set the saved tile type
|
|
|
|
|
var saved_tile_type = grid_layout[y][x]
|
|
|
|
|
DebugManager.log_debug("Setting tile (%d,%d): saved_type=%d, TILE_TYPES=%d" % [x, y, saved_tile_type, TILE_TYPES], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
(
|
|
|
|
|
"Setting tile (%d,%d): saved_type=%d, TILE_TYPES=%d"
|
|
|
|
|
% [x, y, saved_tile_type, TILE_TYPES]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if saved_tile_type >= 0 and saved_tile_type < TILE_TYPES:
|
|
|
|
|
tile.tile_type = saved_tile_type
|
|
|
|
|
DebugManager.log_debug("✓ Restored tile (%d,%d) with saved type %d" % [x, y, saved_tile_type], "Match3")
|
|
|
|
|
DebugManager.log_debug(
|
|
|
|
|
"✓ Restored tile (%d,%d) with saved type %d" % [x, y, saved_tile_type], "Match3"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# Fallback for invalid tile types
|
|
|
|
|
tile.tile_type = randi() % TILE_TYPES
|
|
|
|
|
DebugManager.log_error("✗ Invalid saved tile type %d at (%d,%d), using random %d" % [saved_tile_type, x, y, tile.tile_type], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
(
|
|
|
|
|
"✗ Invalid saved tile type %d at (%d,%d), using random %d"
|
|
|
|
|
% [saved_tile_type, x, y, tile.tile_type]
|
|
|
|
|
),
|
|
|
|
|
"Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Connect tile signals
|
|
|
|
|
tile.tile_selected.connect(_on_tile_selected)
|
|
|
|
|
grid[y].append(tile)
|
|
|
|
|
|
|
|
|
|
DebugManager.log_info("Completed grid restoration: %d tiles restored" % [GRID_SIZE.x * GRID_SIZE.y], "Match3")
|
|
|
|
|
DebugManager.log_info(
|
|
|
|
|
"Completed grid restoration: %d tiles restored" % [GRID_SIZE.x * GRID_SIZE.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Safety and validation helper functions
|
|
|
|
|
func _is_valid_grid_position(pos: Vector2i) -> bool:
|
|
|
|
|
return pos.x >= 0 and pos.y >= 0 and pos.x < GRID_SIZE.x and pos.y < GRID_SIZE.y
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _validate_grid_integrity() -> bool:
|
|
|
|
|
# Check if grid array structure is valid
|
|
|
|
|
if not grid is Array:
|
|
|
|
|
@@ -899,7 +1131,9 @@ func _validate_grid_integrity() -> bool:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if grid.size() != GRID_SIZE.y:
|
|
|
|
|
DebugManager.log_error("Grid height mismatch: %d vs %d" % [grid.size(), GRID_SIZE.y], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Grid height mismatch: %d vs %d" % [grid.size(), GRID_SIZE.y], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
for y in range(grid.size()):
|
|
|
|
|
@@ -908,11 +1142,14 @@ func _validate_grid_integrity() -> bool:
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if grid[y].size() != GRID_SIZE.x:
|
|
|
|
|
DebugManager.log_error("Grid row %d width mismatch: %d vs %d" % [y, grid[y].size(), GRID_SIZE.x], "Match3")
|
|
|
|
|
DebugManager.log_error(
|
|
|
|
|
"Grid row %d width mismatch: %d vs %d" % [y, grid[y].size(), GRID_SIZE.x], "Match3"
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _safe_grid_access(pos: Vector2i) -> Node2D:
|
|
|
|
|
# Safe grid access with comprehensive bounds checking
|
|
|
|
|
if not _is_valid_grid_position(pos):
|
|
|
|
|
@@ -928,6 +1165,7 @@ func _safe_grid_access(pos: Vector2i) -> Node2D:
|
|
|
|
|
|
|
|
|
|
return tile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _safe_tile_access(tile: Node2D, property: String):
|
|
|
|
|
# Safe property access on tiles
|
|
|
|
|
if not tile or not is_instance_valid(tile):
|
|
|
|
|
|