lint fixes
Some checks failed
Continuous Integration / Code Formatting (pull_request) Successful in 27s
Continuous Integration / Code Quality Check (pull_request) Successful in 29s
Continuous Integration / Test Execution (pull_request) Failing after 33s
Continuous Integration / CI Summary (pull_request) Failing after 5s
Some checks failed
Continuous Integration / Code Formatting (pull_request) Successful in 27s
Continuous Integration / Code Quality Check (pull_request) Successful in 29s
Continuous Integration / Test Execution (pull_request) Failing after 33s
Continuous Integration / CI Summary (pull_request) Failing after 5s
This commit is contained in:
@@ -7,7 +7,7 @@ func _ready():
|
||||
target_script_path = "res://scenes/game/gameplays/match3_gameplay.gd"
|
||||
|
||||
# Call parent's _ready
|
||||
super._ready()
|
||||
super()
|
||||
|
||||
DebugManager.log_debug("Match3DebugMenu _ready() completed", log_category)
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ signal grid_state_loaded(grid_size: Vector2i, tile_types: int)
|
||||
## PROCESSING: Detecting matches, clearing tiles, dropping new ones, checking cascades
|
||||
enum GameState { WAITING, SELECTING, SWAPPING, PROCESSING }
|
||||
|
||||
var GRID_SIZE := Vector2i(8, 8)
|
||||
var TILE_TYPES := 5
|
||||
const TILE_SCENE := preload("res://scenes/game/gameplays/tile.tscn")
|
||||
|
||||
# Safety constants
|
||||
@@ -32,6 +30,9 @@ const CASCADE_WAIT_TIME := 0.1
|
||||
const SWAP_ANIMATION_TIME := 0.3
|
||||
const TILE_DROP_WAIT_TIME := 0.2
|
||||
|
||||
var grid_size := Vector2i(8, 8)
|
||||
var tile_types := 5
|
||||
|
||||
var grid: Array[Array] = []
|
||||
var tile_size: float = 48.0
|
||||
var grid_offset: Vector2 = Vector2.ZERO
|
||||
@@ -71,7 +72,7 @@ func _ready() -> void:
|
||||
DebugManager.log_debug("Match3 _ready() completed, calling debug structure check", "Match3")
|
||||
|
||||
# Notify UI that grid state is loaded
|
||||
grid_state_loaded.emit(GRID_SIZE, TILE_TYPES)
|
||||
grid_state_loaded.emit(grid_size, tile_types)
|
||||
|
||||
# Debug: Check scene tree structure
|
||||
call_deferred("_debug_scene_structure")
|
||||
@@ -83,12 +84,12 @@ func _calculate_grid_layout():
|
||||
var available_height = viewport_size.y * SCREEN_HEIGHT_USAGE
|
||||
|
||||
# Calculate tile size based on available space
|
||||
var max_tile_width = available_width / GRID_SIZE.x
|
||||
var max_tile_height = available_height / GRID_SIZE.y
|
||||
var max_tile_width = available_width / grid_size.x
|
||||
var max_tile_height = available_height / grid_size.y
|
||||
tile_size = min(max_tile_width, max_tile_height)
|
||||
|
||||
# Align grid to left side with margins
|
||||
var total_grid_height = tile_size * GRID_SIZE.y
|
||||
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
|
||||
)
|
||||
@@ -97,12 +98,12 @@ func _calculate_grid_layout():
|
||||
func _initialize_grid():
|
||||
# Create gem pool for current tile types
|
||||
var gem_indices: Array[int] = []
|
||||
for i in range(TILE_TYPES):
|
||||
for i in range(tile_types):
|
||||
gem_indices.append(i)
|
||||
|
||||
for y in range(GRID_SIZE.y):
|
||||
for y in range(grid_size.y):
|
||||
grid.append([])
|
||||
for x in range(GRID_SIZE.x):
|
||||
for x in range(grid_size.x):
|
||||
var tile = TILE_SCENE.instantiate()
|
||||
var tile_position = grid_offset + Vector2(x, y) * tile_size
|
||||
tile.position = tile_position
|
||||
@@ -113,7 +114,7 @@ func _initialize_grid():
|
||||
tile.set_active_gem_types(gem_indices)
|
||||
|
||||
# Set tile type after adding to scene tree
|
||||
var new_type = randi() % TILE_TYPES
|
||||
var new_type = randi() % tile_types
|
||||
tile.tile_type = new_type
|
||||
|
||||
# Connect tile signals
|
||||
@@ -159,8 +160,8 @@ func _has_match_at(pos: Vector2i) -> bool:
|
||||
|
||||
func _check_for_matches() -> bool:
|
||||
"""Scan entire grid to detect if any matches exist (used for cascade detection)"""
|
||||
for y in range(GRID_SIZE.y):
|
||||
for x in range(GRID_SIZE.x):
|
||||
for y in range(grid_size.y):
|
||||
for x in range(grid_size.x):
|
||||
if _has_match_at(Vector2i(x, y)):
|
||||
return true
|
||||
return false
|
||||
@@ -205,7 +206,7 @@ func _get_match_line(start: Vector2i, dir: Vector2i) -> Array:
|
||||
var current = start + dir * offset
|
||||
var steps = 0
|
||||
# Safety limit prevents infinite loops in case of logic errors
|
||||
while steps < GRID_SIZE.x + GRID_SIZE.y and _is_valid_grid_position(current):
|
||||
while steps < grid_size.x + grid_size.y and _is_valid_grid_position(current):
|
||||
if current.y >= grid.size() or current.x >= grid[current.y].size():
|
||||
break
|
||||
|
||||
@@ -238,11 +239,11 @@ func _clear_matches() -> void:
|
||||
var match_groups := []
|
||||
var processed_tiles := {}
|
||||
|
||||
for y in range(GRID_SIZE.y):
|
||||
for y in range(grid_size.y):
|
||||
if y >= grid.size():
|
||||
continue
|
||||
|
||||
for x in range(GRID_SIZE.x):
|
||||
for x in range(grid_size.x):
|
||||
if x >= grid[y].size():
|
||||
continue
|
||||
|
||||
@@ -338,17 +339,18 @@ func _drop_tiles():
|
||||
var moved = true
|
||||
while moved:
|
||||
moved = false
|
||||
for x in range(GRID_SIZE.x):
|
||||
# Fixed: Start from GRID_SIZE.y - 1 to avoid out of bounds
|
||||
for y in range(GRID_SIZE.y - 1, -1, -1):
|
||||
for x in range(grid_size.x):
|
||||
# Fixed: Start from grid_size.y - 1 to avoid out of bounds
|
||||
for y in range(grid_size.y - 1, -1, -1):
|
||||
var tile = grid[y][x]
|
||||
# Fixed: Check bounds before accessing y + 1
|
||||
if tile and y + 1 < GRID_SIZE.y and not grid[y + 1][x]:
|
||||
if tile and y + 1 < grid_size.y and not grid[y + 1][x]:
|
||||
grid[y + 1][x] = tile
|
||||
grid[y][x] = null
|
||||
tile.grid_position = Vector2i(x, y + 1)
|
||||
# You can animate position here using Tween for smooth drop:
|
||||
# tween.interpolate_property(tile, "position", tile.position, grid_offset + Vector2(x, y + 1) * tile_size, 0.2)
|
||||
# tween.interpolate_property(tile, "position", tile.position,
|
||||
# grid_offset + Vector2(x, y + 1) * tile_size, 0.2)
|
||||
tile.position = grid_offset + Vector2(x, y + 1) * tile_size
|
||||
moved = true
|
||||
|
||||
@@ -361,16 +363,16 @@ func _fill_empty_cells():
|
||||
|
||||
# Create gem pool for current tile types
|
||||
var gem_indices: Array[int] = []
|
||||
for i in range(TILE_TYPES):
|
||||
for i in range(tile_types):
|
||||
gem_indices.append(i)
|
||||
|
||||
var tiles_created = 0
|
||||
for y in range(GRID_SIZE.y):
|
||||
for y in range(grid_size.y):
|
||||
if y >= grid.size():
|
||||
DebugManager.log_error("Grid row %d does not exist" % y, "Match3")
|
||||
continue
|
||||
|
||||
for x in range(GRID_SIZE.x):
|
||||
for x in range(grid_size.x):
|
||||
if x >= grid[y].size():
|
||||
DebugManager.log_error("Grid column %d does not exist in row %d" % [x, y], "Match3")
|
||||
continue
|
||||
@@ -394,10 +396,10 @@ func _fill_empty_cells():
|
||||
DebugManager.log_warn("Tile missing set_active_gem_types method", "Match3")
|
||||
|
||||
# Set random tile type with bounds checking
|
||||
if TILE_TYPES > 0:
|
||||
tile.tile_type = randi() % TILE_TYPES
|
||||
if tile_types > 0:
|
||||
tile.tile_type = randi() % tile_types
|
||||
else:
|
||||
DebugManager.log_error("TILE_TYPES is 0, cannot set tile type", "Match3")
|
||||
DebugManager.log_error("tile_types is 0, cannot set tile type", "Match3")
|
||||
tile.queue_free()
|
||||
continue
|
||||
|
||||
@@ -436,19 +438,19 @@ func _fill_empty_cells():
|
||||
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
|
||||
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"
|
||||
"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:
|
||||
if tile_types < 3 or tile_types > MAX_TILE_TYPES:
|
||||
DebugManager.log_error(
|
||||
"Invalid tile types count for regeneration: %d" % TILE_TYPES, "Match3"
|
||||
"Invalid tile types count for regeneration: %d" % tile_types, "Match3"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -515,12 +517,12 @@ func set_tile_types(new_count: int):
|
||||
)
|
||||
return
|
||||
|
||||
if new_count == TILE_TYPES:
|
||||
if new_count == tile_types:
|
||||
DebugManager.log_debug("Tile types count unchanged, skipping regeneration", "Match3")
|
||||
return
|
||||
|
||||
DebugManager.log_debug("Changing tile types from %d to %d" % [TILE_TYPES, new_count], "Match3")
|
||||
TILE_TYPES = new_count
|
||||
DebugManager.log_debug("Changing tile types from %d to %d" % [tile_types, new_count], "Match3")
|
||||
tile_types = new_count
|
||||
|
||||
# Regenerate grid with new tile types (gem pool is updated in regenerate_grid)
|
||||
await regenerate_grid()
|
||||
@@ -548,12 +550,12 @@ func set_grid_size(new_size: Vector2i):
|
||||
)
|
||||
return
|
||||
|
||||
if new_size == GRID_SIZE:
|
||||
if new_size == grid_size:
|
||||
DebugManager.log_debug("Grid size unchanged, skipping regeneration", "Match3")
|
||||
return
|
||||
|
||||
DebugManager.log_debug("Changing grid size from %s to %s" % [GRID_SIZE, new_size], "Match3")
|
||||
GRID_SIZE = new_size
|
||||
DebugManager.log_debug("Changing grid size from %s to %s" % [grid_size, new_size], "Match3")
|
||||
grid_size = new_size
|
||||
|
||||
# Regenerate grid with new size
|
||||
await regenerate_grid()
|
||||
@@ -562,8 +564,8 @@ func set_grid_size(new_size: Vector2i):
|
||||
func reset_all_visual_states() -> void:
|
||||
# Debug function to reset all tile visual states
|
||||
DebugManager.log_debug("Resetting all tile visual states", "Match3")
|
||||
for y in range(GRID_SIZE.y):
|
||||
for x in range(GRID_SIZE.x):
|
||||
for y in range(grid_size.y):
|
||||
for x in range(grid_size.x):
|
||||
if grid[y][x] and grid[y][x].has_method("force_reset_visual_state"):
|
||||
grid[y][x].force_reset_visual_state()
|
||||
|
||||
@@ -586,12 +588,12 @@ func _debug_scene_structure() -> void:
|
||||
|
||||
# Check tiles
|
||||
var tile_count = 0
|
||||
for y in range(GRID_SIZE.y):
|
||||
for x in range(GRID_SIZE.x):
|
||||
for y in range(grid_size.y):
|
||||
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"
|
||||
"Created %d tiles out of %d expected" % [tile_count, grid_size.x * grid_size.y], "Match3"
|
||||
)
|
||||
|
||||
# Check first tile in detail
|
||||
@@ -668,8 +670,8 @@ func _move_cursor(direction: Vector2i) -> void:
|
||||
var new_pos = cursor_position + direction
|
||||
|
||||
# Bounds checking
|
||||
new_pos.x = clamp(new_pos.x, 0, GRID_SIZE.x - 1)
|
||||
new_pos.y = clamp(new_pos.y, 0, GRID_SIZE.y - 1)
|
||||
new_pos.x = clamp(new_pos.x, 0, grid_size.x - 1)
|
||||
new_pos.y = clamp(new_pos.y, 0, grid_size.y - 1)
|
||||
|
||||
if new_pos != cursor_position:
|
||||
# Safe access to old tile
|
||||
@@ -925,8 +927,8 @@ 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]
|
||||
"Starting serialization: grid.size()=%d, grid_size=(%d,%d)"
|
||||
% [grid.size(), grid_size.x, grid_size.y]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
@@ -939,9 +941,9 @@ func serialize_grid_state() -> Array:
|
||||
var valid_tiles = 0
|
||||
var null_tiles = 0
|
||||
|
||||
for y in range(GRID_SIZE.y):
|
||||
for y in range(grid_size.y):
|
||||
var row = []
|
||||
for x in range(GRID_SIZE.x):
|
||||
for x in range(grid_size.x):
|
||||
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
||||
row.append(grid[y][x].tile_type)
|
||||
valid_tiles += 1
|
||||
@@ -963,7 +965,7 @@ func serialize_grid_state() -> Array:
|
||||
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]
|
||||
% [grid_size.x, grid_size.y, valid_tiles, null_tiles]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
@@ -974,12 +976,11 @@ 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]:
|
||||
return grid[0][0].active_gem_types.duplicate()
|
||||
else:
|
||||
# Fallback to default
|
||||
var default_types = []
|
||||
for i in range(TILE_TYPES):
|
||||
default_types.append(i)
|
||||
return default_types
|
||||
# Fallback to default
|
||||
var default_types = []
|
||||
for i in range(tile_types):
|
||||
default_types.append(i)
|
||||
return default_types
|
||||
|
||||
|
||||
func save_current_state():
|
||||
@@ -990,12 +991,12 @@ func save_current_state():
|
||||
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()]
|
||||
% [grid_size.x, grid_size.y, tile_types, active_gems.size()]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
|
||||
SaveManager.save_grid_state(GRID_SIZE, TILE_TYPES, active_gems, grid_layout)
|
||||
SaveManager.save_grid_state(grid_size, tile_types, active_gems, grid_layout)
|
||||
|
||||
|
||||
func load_saved_state() -> bool:
|
||||
@@ -1008,7 +1009,7 @@ 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
|
||||
tile_types = saved_state.tile_types_count
|
||||
var saved_gems: Array[int] = []
|
||||
for gem in saved_state.active_gem_types:
|
||||
saved_gems.append(int(gem))
|
||||
@@ -1017,7 +1018,7 @@ func load_saved_state() -> bool:
|
||||
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()]
|
||||
% [instance_id, saved_size.x, saved_size.y, tile_types, saved_layout.size()]
|
||||
),
|
||||
"Match3"
|
||||
)
|
||||
@@ -1051,8 +1052,8 @@ func load_saved_state() -> bool:
|
||||
return false
|
||||
|
||||
# Apply the saved settings
|
||||
var old_size = GRID_SIZE
|
||||
GRID_SIZE = saved_size
|
||||
var old_size = grid_size
|
||||
grid_size = saved_size
|
||||
|
||||
# Recalculate layout if size changed
|
||||
if old_size != saved_size:
|
||||
@@ -1107,9 +1108,9 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
||||
await get_tree().process_frame
|
||||
|
||||
# Restore grid from saved layout
|
||||
for y in range(GRID_SIZE.y):
|
||||
for y in range(grid_size.y):
|
||||
grid.append([])
|
||||
for x in range(GRID_SIZE.x):
|
||||
for x in range(grid_size.x):
|
||||
var tile = TILE_SCENE.instantiate()
|
||||
var tile_position = grid_offset + Vector2(x, y) * tile_size
|
||||
tile.position = tile_position
|
||||
@@ -1123,20 +1124,20 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
||||
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]
|
||||
"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:
|
||||
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"
|
||||
)
|
||||
else:
|
||||
# Fallback for invalid tile types
|
||||
tile.tile_type = randi() % TILE_TYPES
|
||||
tile.tile_type = randi() % tile_types
|
||||
DebugManager.log_error(
|
||||
(
|
||||
"✗ Invalid saved tile type %d at (%d,%d), using random %d"
|
||||
@@ -1150,13 +1151,13 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v
|
||||
grid[y].append(tile)
|
||||
|
||||
DebugManager.log_info(
|
||||
"Completed grid restoration: %d tiles restored" % [GRID_SIZE.x * GRID_SIZE.y], "Match3"
|
||||
"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
|
||||
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:
|
||||
@@ -1165,9 +1166,9 @@ func _validate_grid_integrity() -> bool:
|
||||
DebugManager.log_error("Grid is not an array", "Match3")
|
||||
return false
|
||||
|
||||
if grid.size() != GRID_SIZE.y:
|
||||
if grid.size() != grid_size.y:
|
||||
DebugManager.log_error(
|
||||
"Grid height mismatch: %d vs %d" % [grid.size(), GRID_SIZE.y], "Match3"
|
||||
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y], "Match3"
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -1176,9 +1177,9 @@ func _validate_grid_integrity() -> bool:
|
||||
DebugManager.log_error("Grid row %d is not an array" % y, "Match3")
|
||||
return false
|
||||
|
||||
if grid[y].size() != GRID_SIZE.x:
|
||||
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"
|
||||
"Grid row %d width mismatch: %d vs %d" % [y, grid[y].size(), grid_size.x], "Match3"
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b4kv7g7kllwgb"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/game/gameplays/match3_gameplay.gd" id="1_mvfdp"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/game/gameplays/Match3DebugMenu.tscn" id="2_debug_menu"]
|
||||
[ext_resource type="Script" uid="uid://o8crf6688lan" path="res://scenes/game/gameplays/match3_gameplay.gd" id="1_mvfdp"]
|
||||
[ext_resource type="PackedScene" uid="uid://b76oiwlifikl3" path="res://scenes/game/gameplays/Match3DebugMenu.tscn" id="2_debug_menu"]
|
||||
|
||||
[node name="Match3" type="Node2D"]
|
||||
script = ExtResource("1_mvfdp")
|
||||
|
||||
83
scenes/game/gameplays/match3_input_handler.gd
Normal file
83
scenes/game/gameplays/match3_input_handler.gd
Normal file
@@ -0,0 +1,83 @@
|
||||
class_name Match3InputHandler
|
||||
extends RefCounted
|
||||
|
||||
## Mouse input handler for Match3 gameplay
|
||||
##
|
||||
## Static methods for handling mouse interactions in Match3 games.
|
||||
## Converts between world coordinates and grid positions, performs hit detection on tiles.
|
||||
##
|
||||
## Usage:
|
||||
## var tile = Match3InputHandler.find_tile_at_position(grid, grid_size, mouse_pos)
|
||||
## var grid_pos = Match3InputHandler.get_grid_position_from_world(node, world_pos, offset, size)
|
||||
|
||||
|
||||
static func find_tile_at_position(grid: Array, grid_size: Vector2i, world_pos: Vector2) -> Node2D:
|
||||
## Find the tile that contains the world position.
|
||||
##
|
||||
## Iterates through all tiles and checks if the world position falls within
|
||||
## any tile's sprite boundaries.
|
||||
##
|
||||
## Args:
|
||||
## grid: 2D array of tile nodes arranged in [y][x] format
|
||||
## grid_size: Dimensions of the grid (width x height)
|
||||
## world_pos: World coordinates to test
|
||||
##
|
||||
## Returns:
|
||||
## The first tile node that contains the position, or null if no tile found
|
||||
for y in range(grid_size.y):
|
||||
for x in range(grid_size.x):
|
||||
if y < grid.size() and x < grid[y].size():
|
||||
var tile = grid[y][x]
|
||||
if tile and tile.has_node("Sprite2D"):
|
||||
var sprite = tile.get_node("Sprite2D")
|
||||
if sprite and sprite.texture:
|
||||
var sprite_bounds = get_sprite_world_bounds(tile, sprite)
|
||||
if is_point_inside_rect(world_pos, sprite_bounds):
|
||||
return tile
|
||||
return null
|
||||
|
||||
|
||||
static func get_sprite_world_bounds(tile: Node2D, sprite: Sprite2D) -> Rect2:
|
||||
## Calculate the world space bounding rectangle of a sprite.
|
||||
##
|
||||
## Args:
|
||||
## tile: The tile node containing the sprite
|
||||
## sprite: The Sprite2D node to calculate bounds for
|
||||
##
|
||||
## Returns:
|
||||
## Rect2 representing the sprite's bounds in world coordinates
|
||||
var texture_size = sprite.texture.get_size()
|
||||
var actual_size = texture_size * sprite.scale
|
||||
var half_size = actual_size * 0.5
|
||||
var top_left = tile.position - half_size
|
||||
return Rect2(top_left, actual_size)
|
||||
|
||||
|
||||
static func is_point_inside_rect(point: Vector2, rect: Rect2) -> bool:
|
||||
# Check if a point is inside a rectangle
|
||||
return (
|
||||
point.x >= rect.position.x
|
||||
and point.x <= rect.position.x + rect.size.x
|
||||
and point.y >= rect.position.y
|
||||
and point.y <= rect.position.y + rect.size.y
|
||||
)
|
||||
|
||||
|
||||
static func get_grid_position_from_world(
|
||||
node: Node2D, world_pos: Vector2, grid_offset: Vector2, tile_size: float
|
||||
) -> Vector2i:
|
||||
## Convert world coordinates to grid array indices.
|
||||
##
|
||||
## Args:
|
||||
## node: Reference node for coordinate space conversion
|
||||
## world_pos: Position in world coordinates to convert
|
||||
## grid_offset: Offset of the grid's origin from the node's position
|
||||
## tile_size: Size of each tile in world units
|
||||
##
|
||||
## Returns:
|
||||
## Vector2i containing the grid coordinates (x, y) for array indexing
|
||||
var local_pos = node.to_local(world_pos)
|
||||
var relative_pos = local_pos - grid_offset
|
||||
var grid_x = int(relative_pos.x / tile_size)
|
||||
var grid_y = int(relative_pos.y / tile_size)
|
||||
return Vector2i(grid_x, grid_y)
|
||||
1
scenes/game/gameplays/match3_input_handler.gd.uid
Normal file
1
scenes/game/gameplays/match3_input_handler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bgygx6iofwqwc
|
||||
143
scenes/game/gameplays/match3_save_manager.gd
Normal file
143
scenes/game/gameplays/match3_save_manager.gd
Normal file
@@ -0,0 +1,143 @@
|
||||
class_name Match3SaveManager
|
||||
extends RefCounted
|
||||
|
||||
## Save/Load manager for Match3 gameplay state
|
||||
##
|
||||
## Handles serialization and deserialization of Match3 game state.
|
||||
## Converts game objects to data structures for storage and restoration.
|
||||
##
|
||||
## Usage:
|
||||
## # Save current state
|
||||
## var grid_data = Match3SaveManager.serialize_grid_state(game_grid, grid_size)
|
||||
##
|
||||
## # Restore previous state
|
||||
## var success = Match3SaveManager.deserialize_grid_state(grid_data, game_grid, grid_size)
|
||||
|
||||
|
||||
static func serialize_grid_state(grid: Array, grid_size: Vector2i) -> Array:
|
||||
## Convert the current game grid to a serializable 2D array of tile types.
|
||||
##
|
||||
## Extracts the tile_type property from each tile node and creates a 2D array
|
||||
## that can be saved to disk. Invalid or missing tiles are represented as -1.
|
||||
##
|
||||
## Args:
|
||||
## grid: The current game grid (2D array of tile nodes)
|
||||
## grid_size: Dimensions of the grid to serialize
|
||||
##
|
||||
## Returns:
|
||||
## Array: 2D array where each element is either a tile type (int) or -1 for empty
|
||||
var serialized_grid = []
|
||||
var valid_tiles = 0
|
||||
var null_tiles = 0
|
||||
|
||||
for y in range(grid_size.y):
|
||||
var row = []
|
||||
for x in range(grid_size.x):
|
||||
if y < grid.size() and x < grid[y].size() and grid[y][x]:
|
||||
row.append(grid[y][x].tile_type)
|
||||
valid_tiles += 1
|
||||
else:
|
||||
row.append(-1) # Invalid/empty tile
|
||||
null_tiles += 1
|
||||
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"
|
||||
)
|
||||
return serialized_grid
|
||||
|
||||
|
||||
static func get_active_gem_types_from_grid(grid: Array, tile_types: int) -> Array:
|
||||
# Get active gem types from the first available tile
|
||||
if grid.size() > 0 and grid[0].size() > 0 and grid[0][0]:
|
||||
return grid[0][0].active_gem_types.duplicate()
|
||||
|
||||
# Fallback to default
|
||||
var default_types = []
|
||||
for i in range(tile_types):
|
||||
default_types.append(i)
|
||||
return default_types
|
||||
|
||||
|
||||
static func save_game_state(grid: Array, grid_size: Vector2i, tile_types: int):
|
||||
# Save complete game state
|
||||
var grid_layout = serialize_grid_state(grid, grid_size)
|
||||
var active_gems = get_active_gem_types_from_grid(grid, tile_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"
|
||||
)
|
||||
|
||||
SaveManager.save_grid_state(grid_size, tile_types, active_gems, grid_layout)
|
||||
|
||||
|
||||
static func restore_grid_from_layout(
|
||||
match3_node: Node2D,
|
||||
grid_layout: Array,
|
||||
active_gems: Array[int],
|
||||
grid_size: Vector2i,
|
||||
tile_scene: PackedScene,
|
||||
grid_offset: Vector2,
|
||||
tile_size: float,
|
||||
tile_types: int
|
||||
) -> Array[Array]:
|
||||
# Clear ALL existing tile children
|
||||
var all_tile_children = []
|
||||
for child in match3_node.get_children():
|
||||
if child.has_method("get_script") and child.get_script():
|
||||
var script_path = child.get_script().resource_path
|
||||
if script_path == "res://scenes/game/gameplays/tile.gd":
|
||||
all_tile_children.append(child)
|
||||
|
||||
# Remove all found tile children
|
||||
for child in all_tile_children:
|
||||
child.queue_free()
|
||||
|
||||
# Wait for nodes to be freed
|
||||
await match3_node.get_tree().process_frame
|
||||
|
||||
# Create new grid
|
||||
var new_grid: Array[Array] = []
|
||||
for y in range(grid_size.y):
|
||||
new_grid.append(Array([]))
|
||||
for x in range(grid_size.x):
|
||||
var tile = tile_scene.instantiate()
|
||||
var tile_position = grid_offset + Vector2(x, y) * tile_size
|
||||
tile.position = tile_position
|
||||
tile.grid_position = Vector2i(x, y)
|
||||
|
||||
match3_node.add_child(tile)
|
||||
|
||||
# Configure Area2D
|
||||
tile.monitoring = true
|
||||
tile.monitorable = true
|
||||
tile.input_pickable = true
|
||||
|
||||
tile.set_tile_size(tile_size)
|
||||
tile.set_active_gem_types(active_gems)
|
||||
|
||||
# Set the saved tile type
|
||||
var saved_tile_type = grid_layout[y][x]
|
||||
if saved_tile_type >= 0 and saved_tile_type < tile_types:
|
||||
tile.tile_type = saved_tile_type
|
||||
else:
|
||||
tile.tile_type = randi() % tile_types
|
||||
|
||||
# Connect tile signals
|
||||
if tile.has_signal("tile_selected") and match3_node.has_method("_on_tile_selected"):
|
||||
tile.tile_selected.connect(match3_node._on_tile_selected)
|
||||
if tile.has_signal("tile_hovered") and match3_node.has_method("_on_tile_hovered"):
|
||||
tile.tile_hovered.connect(match3_node._on_tile_hovered)
|
||||
tile.tile_unhovered.connect(match3_node._on_tile_unhovered)
|
||||
|
||||
new_grid[y].append(tile)
|
||||
|
||||
return new_grid
|
||||
1
scenes/game/gameplays/match3_save_manager.gd.uid
Normal file
1
scenes/game/gameplays/match3_save_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://balbki1cnwdn1
|
||||
102
scenes/game/gameplays/match3_validator.gd
Normal file
102
scenes/game/gameplays/match3_validator.gd
Normal file
@@ -0,0 +1,102 @@
|
||||
class_name Match3Validator
|
||||
extends RefCounted
|
||||
|
||||
## Validation utilities for Match3 gameplay
|
||||
##
|
||||
## Static methods for validating Match3 game state and data integrity.
|
||||
## Prevents crashes by checking bounds, data structures, and game logic constraints.
|
||||
##
|
||||
## Usage:
|
||||
## if Match3Validator.is_valid_grid_position(pos, grid_size):
|
||||
## # Safe to access grid[pos.y][pos.x]
|
||||
##
|
||||
## if Match3Validator.validate_grid_integrity(grid, grid_size):
|
||||
## # Grid structure is valid for game operations
|
||||
|
||||
|
||||
static func is_valid_grid_position(pos: Vector2i, grid_size: Vector2i) -> bool:
|
||||
## Check if the position is within the grid boundaries.
|
||||
##
|
||||
## Performs bounds checking to prevent index out of bounds errors.
|
||||
##
|
||||
## Args:
|
||||
## pos: Grid position to validate (x, y coordinates)
|
||||
## grid_size: Dimensions of the grid (width, height)
|
||||
##
|
||||
## Returns:
|
||||
## bool: True if position is valid, False if out of bounds
|
||||
return pos.x >= 0 and pos.y >= 0 and pos.x < grid_size.x and pos.y < grid_size.y
|
||||
|
||||
|
||||
static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool:
|
||||
## Verify that the grid array structure matches expected dimensions.
|
||||
##
|
||||
## Validates the grid's 2D array structure for safe game operations.
|
||||
## Checks array types, dimensions, and structural consistency.
|
||||
##
|
||||
## Args:
|
||||
## grid: The 2D array representing the game grid
|
||||
## grid_size: Expected dimensions (width x height)
|
||||
##
|
||||
## Returns:
|
||||
## bool: True if grid structure is valid, False if corrupted or malformed
|
||||
if not grid is Array:
|
||||
DebugManager.log_error("Grid is not an array", "Match3")
|
||||
return false
|
||||
|
||||
if grid.size() != grid_size.y:
|
||||
DebugManager.log_error(
|
||||
"Grid height mismatch: %d vs %d" % [grid.size(), grid_size.y], "Match3"
|
||||
)
|
||||
return false
|
||||
|
||||
for y in range(grid.size()):
|
||||
if not grid[y] is Array:
|
||||
DebugManager.log_error("Grid row %d is not an array" % y, "Match3")
|
||||
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"
|
||||
)
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
static func safe_grid_access(grid: Array, pos: Vector2i, grid_size: Vector2i) -> Node2D:
|
||||
# Safe grid access with comprehensive bounds checking
|
||||
if not is_valid_grid_position(pos, grid_size):
|
||||
return null
|
||||
|
||||
if pos.y >= grid.size() or pos.x >= grid[pos.y].size():
|
||||
DebugManager.log_warn("Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3")
|
||||
return null
|
||||
|
||||
var tile = grid[pos.y][pos.x]
|
||||
if not tile or not is_instance_valid(tile):
|
||||
return null
|
||||
|
||||
return tile
|
||||
|
||||
|
||||
static func safe_tile_access(tile: Node2D, property: String):
|
||||
# Safe property access on tiles
|
||||
if not tile or not is_instance_valid(tile):
|
||||
return null
|
||||
|
||||
if not property in tile:
|
||||
DebugManager.log_warn("Tile missing property: %s" % property, "Match3")
|
||||
return null
|
||||
|
||||
return tile.get(property)
|
||||
|
||||
|
||||
static func are_tiles_adjacent(tile1: Node2D, tile2: Node2D) -> bool:
|
||||
if not tile1 or not tile2:
|
||||
return false
|
||||
|
||||
var pos1 = tile1.grid_position
|
||||
var pos2 = tile2.grid_position
|
||||
var diff = abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y)
|
||||
return diff == 1
|
||||
1
scenes/game/gameplays/match3_validator.gd.uid
Normal file
1
scenes/game/gameplays/match3_validator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cjav8g5js6umr
|
||||
@@ -2,8 +2,12 @@ extends Node2D
|
||||
|
||||
signal tile_selected(tile: Node2D)
|
||||
|
||||
# Target size for each tile to fit in the 54x54 grid cells
|
||||
const TILE_SIZE = 48 # Slightly smaller than 54 to leave some padding
|
||||
|
||||
@export var tile_type: int = 0:
|
||||
set = _set_tile_type
|
||||
|
||||
var grid_position: Vector2i
|
||||
var is_selected: bool = false:
|
||||
set = _set_selected
|
||||
@@ -11,26 +15,24 @@ var is_highlighted: bool = false:
|
||||
set = _set_highlighted
|
||||
var original_scale: Vector2 = Vector2.ONE # Store the original scale for the board
|
||||
|
||||
@onready var sprite: Sprite2D = $Sprite2D
|
||||
|
||||
# Target size for each tile to fit in the 54x54 grid cells
|
||||
const TILE_SIZE = 48 # Slightly smaller than 54 to leave some padding
|
||||
|
||||
# All available gem textures
|
||||
var all_gem_textures: Array[Texture2D] = [
|
||||
preload("res://assets/sprites/gems/bg_19.png"), # 0 - Blue gem
|
||||
preload("res://assets/sprites/gems/dg_19.png"), # 1 - Dark gem
|
||||
preload("res://assets/sprites/gems/gg_19.png"), # 2 - Green gem
|
||||
preload("res://assets/sprites/gems/mg_19.png"), # 3 - Magenta gem
|
||||
preload("res://assets/sprites/gems/rg_19.png"), # 4 - Red gem
|
||||
preload("res://assets/sprites/gems/yg_19.png"), # 5 - Yellow gem
|
||||
preload("res://assets/sprites/gems/pg_19.png"), # 6 - Purple gem
|
||||
preload("res://assets/sprites/gems/sg_19.png"), # 7 - Silver gem
|
||||
preload("res://assets/sprites/skulls/red.png"),
|
||||
preload("res://assets/sprites/skulls/blue.png"),
|
||||
preload("res://assets/sprites/skulls/green.png"),
|
||||
preload("res://assets/sprites/skulls/pink.png"),
|
||||
preload("res://assets/sprites/skulls/purple.png"),
|
||||
preload("res://assets/sprites/skulls/dark-blue.png"),
|
||||
preload("res://assets/sprites/skulls/grey.png"),
|
||||
preload("res://assets/sprites/skulls/orange.png"),
|
||||
preload("res://assets/sprites/skulls/yellow.png"),
|
||||
]
|
||||
|
||||
# Currently active gem types (indices into all_gem_textures)
|
||||
var active_gem_types: Array[int] = [] # Will be set from TileManager
|
||||
|
||||
@onready var sprite: Sprite2D = $Sprite2D
|
||||
|
||||
|
||||
func _set_tile_type(value: int) -> void:
|
||||
tile_type = value
|
||||
|
||||
Reference in New Issue
Block a user