diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bd2026a..ac9639c 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -112,31 +112,31 @@ jobs: git diff --stat fi - - name: Commit and push formatting changes - if: steps.check-changes.outputs.has_changes == 'true' - run: | - echo "💾 Committing formatting changes..." + # - name: Commit and push formatting changes + # if: steps.check-changes.outputs.has_changes == 'true' + # run: | + # echo "💾 Committing formatting changes..." - git config user.name "Gitea Actions" - git config user.email "actions@gitea.local" + # git config user.name "Gitea Actions" + # git config user.email "actions@gitea.local" - git add -A + # git add -A - commit_message="🎨 Auto-format GDScript code + # commit_message="🎨 Auto-format GDScript code - Automated formatting applied by tools/run_development.py + # Automated formatting applied by tools/run_development.py - 🤖 Generated by Gitea Actions - Workflow: ${{ github.workflow }} - Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + # 🤖 Generated by Gitea Actions + # Workflow: ${{ github.workflow }} + # Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - git commit -m "$commit_message" + # git commit -m "$commit_message" - target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}" - echo "📤 Pushing changes to branch: $target_branch" - git push origin HEAD:"$target_branch" + # target_branch="${{ github.event.pull_request.head.ref || github.ref_name }}" + # echo "📤 Pushing changes to branch: $target_branch" + # git push origin HEAD:"$target_branch" - echo "✅ Formatting changes pushed successfully!" + # echo "✅ Formatting changes pushed successfully!" lint: name: Code Quality Check diff --git a/examples/ValueStepperExample.gd b/examples/ValueStepperExample.gd index 083532e..f55bb65 100644 --- a/examples/ValueStepperExample.gd +++ b/examples/ValueStepperExample.gd @@ -11,14 +11,17 @@ var language_stepper: ValueStepper = $VBoxContainer/Examples/LanguageContainer/L var difficulty_stepper: ValueStepper = $VBoxContainer/Examples/DifficultyContainer/DifficultyStepper @onready var resolution_stepper: ValueStepper = $VBoxContainer/Examples/ResolutionContainer/ResolutionStepper -@onready var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper +@onready +var custom_stepper: ValueStepper = $VBoxContainer/Examples/CustomContainer/CustomStepper func _ready(): DebugManager.log_info("ValueStepper example ready", "Example") # Setup navigation array - navigable_steppers = [language_stepper, difficulty_stepper, resolution_stepper, custom_stepper] + navigable_steppers = [ + language_stepper, difficulty_stepper, resolution_stepper, custom_stepper + ] # Connect to value change events for stepper in navigable_steppers: @@ -52,15 +55,22 @@ func _input(event: InputEvent): func _navigate_steppers(direction: int): - current_stepper_index = ((current_stepper_index + direction) % navigable_steppers.size()) + current_stepper_index = ( + (current_stepper_index + direction) % navigable_steppers.size() + ) if current_stepper_index < 0: current_stepper_index = navigable_steppers.size() - 1 _update_stepper_highlighting() - DebugManager.log_info("Stepper navigation: index " + str(current_stepper_index), "Example") + DebugManager.log_info( + "Stepper navigation: index " + str(current_stepper_index), "Example" + ) func _handle_stepper_input(action: String): - if current_stepper_index >= 0 and current_stepper_index < navigable_steppers.size(): + if ( + current_stepper_index >= 0 + and current_stepper_index < navigable_steppers.size() + ): var stepper = navigable_steppers[current_stepper_index] if stepper.handle_input_action(action): AudioManager.play_ui_click() @@ -73,7 +83,14 @@ func _update_stepper_highlighting(): func _on_stepper_value_changed(new_value: String, new_index: int): DebugManager.log_info( - "Stepper value changed to: " + new_value + " (index: " + str(new_index) + ")", "Example" + ( + "Stepper value changed to: " + + new_value + + " (index: " + + str(new_index) + + ")" + ), + "Example" ) # Handle value change in your scene # For example: apply settings, save preferences, update UI, etc. diff --git a/scenes/game/Game.gd b/scenes/game/Game.gd index d1e443b..c53569f 100644 --- a/scenes/game/Game.gd +++ b/scenes/game/Game.gd @@ -20,15 +20,20 @@ func _ready() -> void: # GameManager will set the gameplay mode, don't set default here DebugManager.log_debug( - "Game _ready() completed, waiting for GameManager to set gameplay mode", "Game" + "Game _ready() completed, waiting for GameManager to set gameplay mode", + "Game" ) func set_gameplay_mode(mode: String) -> void: - DebugManager.log_info("set_gameplay_mode called with mode: %s" % mode, "Game") + DebugManager.log_info( + "set_gameplay_mode called with mode: %s" % mode, "Game" + ) current_gameplay_mode = mode await load_gameplay(mode) - DebugManager.log_info("set_gameplay_mode completed for mode: %s" % mode, "Game") + DebugManager.log_info( + "set_gameplay_mode completed for mode: %s" % mode, "Game" + ) func load_gameplay(mode: String) -> void: @@ -37,24 +42,35 @@ func load_gameplay(mode: String) -> void: # Clear existing gameplay and wait for removal var existing_children = gameplay_container.get_children() if existing_children.size() > 0: - DebugManager.log_debug("Removing %d existing children" % existing_children.size(), "Game") + DebugManager.log_debug( + "Removing %d existing children" % existing_children.size(), "Game" + ) for child in existing_children: - DebugManager.log_debug("Removing existing child: %s" % child.name, "Game") + DebugManager.log_debug( + "Removing existing child: %s" % child.name, "Game" + ) child.queue_free() # Wait for children to be properly removed from scene tree await get_tree().process_frame DebugManager.log_debug( - "Children removal complete, container count: %d" % gameplay_container.get_child_count(), + ( + "Children removal complete, container count: %d" + % gameplay_container.get_child_count() + ), "Game" ) # Load new gameplay if GAMEPLAY_SCENES.has(mode): - DebugManager.log_debug("Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game") + DebugManager.log_debug( + "Found scene path: %s" % GAMEPLAY_SCENES[mode], "Game" + ) var gameplay_scene = load(GAMEPLAY_SCENES[mode]) var gameplay_instance = gameplay_scene.instantiate() - DebugManager.log_debug("Instantiated gameplay: %s" % gameplay_instance.name, "Game") + DebugManager.log_debug( + "Instantiated gameplay: %s" % gameplay_instance.name, "Game" + ) gameplay_container.add_child(gameplay_instance) DebugManager.log_debug( ( @@ -69,7 +85,9 @@ func load_gameplay(mode: String) -> void: gameplay_instance.score_changed.connect(_on_score_changed) DebugManager.log_debug("Connected score_changed signal", "Game") else: - DebugManager.log_error("Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game") + DebugManager.log_error( + "Gameplay mode '%s' not found in GAMEPLAY_SCENES" % mode, "Game" + ) func set_global_score(value: int) -> void: @@ -102,10 +120,15 @@ func _on_back_button_pressed() -> void: if gameplay_instance and gameplay_instance.has_method("save_current_state"): DebugManager.log_info("Saving grid state before exit", "Game") # Make sure the gameplay instance is still valid and not being destroyed - if is_instance_valid(gameplay_instance) and gameplay_instance.is_inside_tree(): + if ( + is_instance_valid(gameplay_instance) + and gameplay_instance.is_inside_tree() + ): gameplay_instance.save_current_state() else: - DebugManager.log_warn("Gameplay instance invalid, skipping grid save on exit", "Game") + DebugManager.log_warn( + "Gameplay instance invalid, skipping grid save on exit", "Game" + ) # Save the current score immediately before exiting SaveManager.finish_game(global_score) @@ -116,7 +139,10 @@ func _input(event: InputEvent) -> void: if event.is_action_pressed("ui_back"): # Handle gamepad/keyboard back action - same as back button _on_back_button_pressed() - elif event.is_action_pressed("action_south") and Input.is_action_pressed("action_north"): + elif ( + event.is_action_pressed("action_south") + and Input.is_action_pressed("action_north") + ): # Debug: Switch to clickomania when primary+secondary actions pressed together if current_gameplay_mode == "match3": set_gameplay_mode("clickomania") diff --git a/scenes/game/gameplays/Match3Gameplay.gd b/scenes/game/gameplays/Match3Gameplay.gd index 15c607a..fd144e9 100644 --- a/scenes/game/gameplays/Match3Gameplay.gd +++ b/scenes/game/gameplays/Match3Gameplay.gd @@ -49,13 +49,21 @@ func _ready() -> void: 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") + DebugManager.log_debug( + "[%s] Match3 _ready() started" % instance_id, "Match3" + ) grid_initialized = true # Calculate grid layout @@ -64,12 +72,16 @@ func _ready() -> void: # Try to load saved state, otherwise use default var loaded_saved_state = await load_saved_state() if not loaded_saved_state: - DebugManager.log_info("No saved state found, using default grid initialization", "Match3") + DebugManager.log_info( + "No saved state found, using default grid initialization", "Match3" + ) _initialize_grid() else: DebugManager.log_info("Successfully loaded saved grid state", "Match3") - DebugManager.log_debug("Match3 _ready() completed, calling debug structure check", "Match3") + 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) @@ -91,7 +103,8 @@ 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 ) @@ -136,7 +149,9 @@ func _has_match_at(pos: Vector2i) -> bool: return false if pos.y >= grid.size() or pos.x >= grid[pos.y].size(): - DebugManager.log_error("Grid array bounds exceeded at (%d,%d)" % [pos.x, pos.y], "Match3") + DebugManager.log_error( + "Grid array bounds exceeded at (%d,%d)" % [pos.x, pos.y], "Match3" + ) return false var tile = grid[pos.y][pos.x] @@ -146,7 +161,8 @@ 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" + "Tile at (%d,%d) missing tile_type property" % [pos.x, pos.y], + "Match3" ) return false @@ -177,13 +193,18 @@ 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" + ( + "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" + "Invalid direction vector for match line: (%d,%d)" % [dir.x, dir.y], + "Match3" ) return [] @@ -206,7 +227,10 @@ 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 @@ -233,7 +257,9 @@ func _clear_matches() -> void: """ # Check grid integrity if not _validate_grid_integrity(): - DebugManager.log_error("Grid integrity check failed in _clear_matches", "Match3") + DebugManager.log_error( + "Grid integrity check failed in _clear_matches", "Match3" + ) return var match_groups := [] @@ -282,7 +308,9 @@ func _clear_matches() -> void: else: match_score = match_size + max(0, match_size - 2) # n + (n-2) for n >= 4 total_score += match_score - print("Debug: Match of ", match_size, " gems = ", match_score, " points") + print( + "Debug: Match of ", match_size, " gems = ", match_score, " points" + ) # Remove duplicates from all matches combined var to_clear := [] @@ -308,7 +336,9 @@ func _clear_matches() -> void: # Validate tile has grid_position property if not "grid_position" in tile: - DebugManager.log_warn("Tile missing grid_position during removal", "Match3") + DebugManager.log_warn( + "Tile missing grid_position during removal", "Match3" + ) tile.queue_free() continue @@ -322,7 +352,10 @@ func _clear_matches() -> void: 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], + ( + "Invalid grid position during tile removal: (%d,%d)" + % [tile_pos.x, tile_pos.y] + ), "Match3" ) @@ -358,7 +391,9 @@ func _drop_tiles(): func _fill_empty_cells(): # Safety check for grid integrity if not _validate_grid_integrity(): - DebugManager.log_error("Grid integrity check failed in _fill_empty_cells", "Match3") + DebugManager.log_error( + "Grid integrity check failed in _fill_empty_cells", "Match3" + ) return # Create gem pool for current tile types @@ -374,14 +409,17 @@ func _fill_empty_cells(): 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") + DebugManager.log_error( + "Grid column %d does not exist in row %d" % [x, y], "Match3" + ) continue 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" + "Failed to instantiate tile at (%d,%d)" % [x, y], + "Match3" ) continue @@ -393,13 +431,17 @@ func _fill_empty_cells(): if tile.has_method("set_active_gem_types"): tile.set_active_gem_types(gem_indices) else: - DebugManager.log_warn("Tile missing set_active_gem_types method", "Match3") + 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 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 @@ -408,7 +450,9 @@ func _fill_empty_cells(): if tile.has_signal("tile_selected"): tile.tile_selected.connect(_on_tile_selected) else: - DebugManager.log_warn("Tile missing tile_selected signal", "Match3") + DebugManager.log_warn( + "Tile missing tile_selected signal", "Match3" + ) tiles_created += 1 @@ -423,12 +467,15 @@ 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 @@ -444,20 +491,27 @@ func regenerate_grid(): 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: DebugManager.log_error( - "Invalid tile types count for regeneration: %d" % tile_types, "Match3" + "Invalid tile types count for regeneration: %d" % tile_types, + "Match3" ) return # Use time-based seed to ensure different patterns each time var new_seed = Time.get_ticks_msec() seed(new_seed) - DebugManager.log_debug("Regenerating grid with seed: " + str(new_seed), "Match3") + DebugManager.log_debug( + "Regenerating grid with seed: " + str(new_seed), "Match3" + ) # Safe tile cleanup with improved error handling var children_to_remove = [] @@ -477,7 +531,9 @@ func regenerate_grid(): children_to_remove.append(child) removed_count += 1 - DebugManager.log_debug("Found %d tile children to remove" % removed_count, "Match3") + DebugManager.log_debug( + "Found %d tile children to remove" % removed_count, "Match3" + ) # First clear grid array references to prevent access to nodes being freed for y in range(grid.size()): @@ -508,20 +564,30 @@ func regenerate_grid(): func set_tile_types(new_count: int): # Input validation if new_count < 3: - DebugManager.log_error("Tile types count too low: %d (minimum 3)" % new_count, "Match3") + DebugManager.log_error( + "Tile types count too low: %d (minimum 3)" % new_count, "Match3" + ) return if new_count > MAX_TILE_TYPES: DebugManager.log_error( - "Tile types count too high: %d (maximum %d)" % [new_count, MAX_TILE_TYPES], "Match3" + ( + "Tile types count too high: %d (maximum %d)" + % [new_count, MAX_TILE_TYPES] + ), + "Match3" ) return if new_count == tile_types: - DebugManager.log_debug("Tile types count unchanged, skipping regeneration", "Match3") + 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") + 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) @@ -551,10 +617,14 @@ func set_grid_size(new_size: Vector2i): return if new_size == grid_size: - DebugManager.log_debug("Grid size unchanged, skipping regeneration", "Match3") + 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") + DebugManager.log_debug( + "Changing grid size from %s to %s" % [grid_size, new_size], "Match3" + ) grid_size = new_size # Regenerate grid with new size @@ -577,13 +647,19 @@ func reset_all_visual_states() -> void: func _debug_scene_structure() -> void: DebugManager.log_debug("=== Scene Structure Debug ===", "Match3") - DebugManager.log_debug("Match3 node children count: %d" % get_child_count(), "Match3") - DebugManager.log_debug("Match3 global position: %s" % global_position, "Match3") + DebugManager.log_debug( + "Match3 node children count: %d" % get_child_count(), "Match3" + ) + DebugManager.log_debug( + "Match3 global position: %s" % global_position, "Match3" + ) DebugManager.log_debug("Match3 scale: %s" % scale, "Match3") # Check if grid is properly initialized if not grid or grid.size() == 0: - DebugManager.log_error("Grid not initialized when debug structure called", "Match3") + DebugManager.log_error( + "Grid not initialized when debug structure called", "Match3" + ) return # Check tiles @@ -593,23 +669,33 @@ func _debug_scene_structure() -> void: 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 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" + "First tile global position: %s" % first_tile.global_position, + "Match3" + ) + DebugManager.log_debug( + "First tile local position: %s" % first_tile.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()], + ( + "Parent level %d: %s (type: %s)" + % [depth, current_node.name, current_node.get_class()] + ), "Match3" ) current_node = current_node.get_parent() @@ -618,16 +704,27 @@ func _debug_scene_structure() -> void: func _input(event: InputEvent) -> void: # Debug key to reset all visual states - if event.is_action_pressed("action_east") and DebugManager.is_debug_enabled(): + if ( + event.is_action_pressed("action_east") + and DebugManager.is_debug_enabled() + ): reset_all_visual_states() return - if current_state == GameState.SWAPPING or current_state == GameState.PROCESSING: + if ( + current_state == GameState.SWAPPING + or current_state == GameState.PROCESSING + ): return # Handle action_east (B button/ESC) to deselect selected tile - if event.is_action_pressed("action_east") and current_state == GameState.SELECTING: - DebugManager.log_debug("action_east pressed - deselecting current tile", "Match3") + if ( + event.is_action_pressed("action_east") + and current_state == GameState.SELECTING + ): + DebugManager.log_debug( + "action_east pressed - deselecting current tile", "Match3" + ) _deselect_tile() return @@ -652,18 +749,23 @@ func _input(event: InputEvent) -> void: func _move_cursor(direction: Vector2i) -> void: # Input validation for direction vector if abs(direction.x) > 1 or abs(direction.y) > 1: - DebugManager.log_error("Invalid cursor direction vector: " + str(direction), "Match3") + DebugManager.log_error( + "Invalid cursor direction vector: " + str(direction), "Match3" + ) return if direction.x != 0 and direction.y != 0: DebugManager.log_error( - "Diagonal cursor movement not supported: " + str(direction), "Match3" + "Diagonal cursor movement not supported: " + str(direction), + "Match3" ) return # Validate grid integrity before cursor operations if not _validate_grid_integrity(): - DebugManager.log_error("Grid integrity check failed in cursor movement", "Match3") + DebugManager.log_error( + "Grid integrity check failed in cursor movement", "Match3" + ) return var old_pos = cursor_position @@ -676,19 +778,30 @@ func _move_cursor(direction: Vector2i) -> void: if new_pos != cursor_position: # Safe access to old tile var old_tile = _safe_grid_access(old_pos) - if old_tile and "is_selected" in old_tile and "is_highlighted" in old_tile: + if ( + old_tile + and "is_selected" in old_tile + and "is_highlighted" in old_tile + ): 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], + ( + "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 var new_tile = _safe_grid_access(cursor_position) - if new_tile and "is_selected" in new_tile and "is_highlighted" in new_tile: + if ( + new_tile + and "is_selected" in new_tile + and "is_highlighted" in new_tile + ): if not new_tile.is_selected: new_tile.is_highlighted = true @@ -708,13 +821,19 @@ func _select_tile_at_cursor() -> void: var tile = _safe_grid_access(cursor_position) if tile: DebugManager.log_debug( - "Keyboard selection at cursor (%d,%d)" % [cursor_position.x, cursor_position.y], + ( + "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], + ( + "No valid tile at cursor position (%d,%d)" + % [cursor_position.x, cursor_position.y] + ), "Match3" ) @@ -728,9 +847,15 @@ func _on_tile_selected(tile: Node2D) -> void: SELECTING -> SWAPPING: Different tile clicked (attempt swap) """ # Block tile selection during busy states - if current_state == GameState.SWAPPING or current_state == GameState.PROCESSING: + if ( + current_state == GameState.SWAPPING + or current_state == GameState.PROCESSING + ): DebugManager.log_debug( - "Tile selection ignored - game busy (state: %s)" % [GameState.keys()[current_state]], + ( + "Tile selection ignored - game busy (state: %s)" + % [GameState.keys()[current_state]] + ), "Match3" ) return @@ -773,7 +898,11 @@ func _select_tile(tile: Node2D) -> void: tile.is_selected = true current_state = GameState.SELECTING # State transition: WAITING -> SELECTING DebugManager.log_debug( - "Selected tile at (%d, %d)" % [tile.grid_position.x, tile.grid_position.y], "Match3" + ( + "Selected tile at (%d, %d)" + % [tile.grid_position.x, tile.grid_position.y] + ), + "Match3" ) @@ -784,12 +913,17 @@ func _deselect_tile() -> void: DebugManager.log_debug( ( "Deselecting tile at (%d,%d)" - % [selected_tile.grid_position.x, selected_tile.grid_position.y] + % [ + selected_tile.grid_position.x, + selected_tile.grid_position.y + ] ), "Match3" ) else: - DebugManager.log_debug("Deselecting tile (no grid position available)", "Match3") + DebugManager.log_debug( + "Deselecting tile (no grid position available)", "Match3" + ) # Safe property access for selection state if "is_selected" in selected_tile: @@ -833,7 +967,9 @@ func _are_adjacent(tile1: Node2D, tile2: Node2D) -> bool: func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void: if not _are_adjacent(tile1, tile2): - DebugManager.log_debug("Tiles are not adjacent, selecting new tile instead", "Match3") + DebugManager.log_debug( + "Tiles are not adjacent, selecting new tile instead", "Match3" + ) _deselect_tile() _select_tile(tile2) return @@ -886,7 +1022,9 @@ func _attempt_swap(tile1: Node2D, tile2: Node2D) -> void: 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") + DebugManager.log_error( + "Cannot swap tiles - one or both tiles are null", "Match3" + ) return # Update grid positions @@ -934,7 +1072,9 @@ func serialize_grid_state() -> Array: ) if grid.size() == 0: - DebugManager.log_error("Grid array is empty during serialization!", "Match3") + DebugManager.log_error( + "Grid array is empty during serialization!", "Match3" + ) return [] var serialized_grid = [] @@ -950,7 +1090,11 @@ func serialize_grid_state() -> Array: # 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" + ( + "Serializing tile (%d,%d): type %d" + % [x, y, grid[y][x].tile_type] + ), + "Match3" ) else: row.append(-1) # Invalid/empty tile @@ -958,7 +1102,8 @@ func serialize_grid_state() -> Array: # Only log first few nulls for brevity if null_tiles <= 5: DebugManager.log_debug( - "Serializing tile (%d,%d): NULL/empty (-1)" % [x, y], "Match3" + "Serializing tile (%d,%d): NULL/empty (-1)" % [x, y], + "Match3" ) serialized_grid.append(row) @@ -1002,7 +1147,9 @@ func save_current_state(): func load_saved_state() -> bool: # Check if there's a saved grid state if not SaveManager.has_saved_grid(): - DebugManager.log_info("No saved grid state found, using default generation", "Match3") + DebugManager.log_info( + "No saved grid state found, using default generation", "Match3" + ) return false var saved_state = SaveManager.get_saved_grid_state() @@ -1015,12 +1162,21 @@ func load_saved_state() -> bool: 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 @@ -1058,7 +1214,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], + ( + "Grid size changed from %s to %s, recalculating layout" + % [old_size, saved_size] + ), "Match3" ) _calculate_grid_layout() @@ -1069,7 +1228,9 @@ func load_saved_state() -> bool: return true -func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> void: +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" @@ -1088,7 +1249,8 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v all_tile_children.append(child) DebugManager.log_debug( - "Found %d existing tile children to remove" % all_tile_children.size(), "Match3" + "Found %d existing tile children to remove" % all_tile_children.size(), + "Match3" ) # Remove all found tile children @@ -1133,17 +1295,24 @@ func _restore_grid_from_layout(grid_layout: Array, active_gems: Array[int]) -> v 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" + ( + "✓ 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 @@ -1151,13 +1320,22 @@ 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: @@ -1168,7 +1346,8 @@ func _validate_grid_integrity() -> bool: 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 @@ -1179,7 +1358,11 @@ func _validate_grid_integrity() -> bool: 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 @@ -1192,7 +1375,9 @@ func _safe_grid_access(pos: Vector2i) -> Node2D: 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") + DebugManager.log_warn( + "Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3" + ) return null var tile = grid[pos.y][pos.x] diff --git a/scenes/game/gameplays/Match3InputHandler.gd b/scenes/game/gameplays/Match3InputHandler.gd index 5fec6a6..7a1e6dc 100644 --- a/scenes/game/gameplays/Match3InputHandler.gd +++ b/scenes/game/gameplays/Match3InputHandler.gd @@ -11,7 +11,9 @@ extends RefCounted ## 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: +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 @@ -31,7 +33,9 @@ static func find_tile_at_position(grid: Array, grid_size: Vector2i, world_pos: V 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) + var sprite_bounds = get_sprite_world_bounds( + tile, sprite + ) if is_point_inside_rect(world_pos, sprite_bounds): return tile return null diff --git a/scenes/game/gameplays/Match3SaveManager.gd b/scenes/game/gameplays/Match3SaveManager.gd index fd894ae..23d27dd 100644 --- a/scenes/game/gameplays/Match3SaveManager.gd +++ b/scenes/game/gameplays/Match3SaveManager.gd @@ -51,7 +51,9 @@ static func serialize_grid_state(grid: Array, grid_size: Vector2i) -> Array: return serialized_grid -static func get_active_gem_types_from_grid(grid: Array, tile_types: int) -> Array: +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() @@ -132,9 +134,15 @@ static func restore_grid_from_layout( tile.tile_type = randi() % tile_types # Connect tile signals - if tile.has_signal("tile_selected") and match3_node.has_method("_on_tile_selected"): + 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"): + 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) diff --git a/scenes/game/gameplays/Match3Validator.gd b/scenes/game/gameplays/Match3Validator.gd index d85d252..a8d457c 100644 --- a/scenes/game/gameplays/Match3Validator.gd +++ b/scenes/game/gameplays/Match3Validator.gd @@ -25,7 +25,12 @@ static func is_valid_grid_position(pos: Vector2i, grid_size: Vector2i) -> bool: ## ## 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 + 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: @@ -46,7 +51,8 @@ static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool: 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 @@ -57,20 +63,28 @@ static func validate_grid_integrity(grid: Array, grid_size: Vector2i) -> bool: 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 return true -static func safe_grid_access(grid: Array, pos: Vector2i, grid_size: Vector2i) -> Node2D: +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") + DebugManager.log_warn( + "Grid bounds exceeded: (%d,%d)" % [pos.x, pos.y], "Match3" + ) return null var tile = grid[pos.y][pos.x] diff --git a/scenes/game/gameplays/tile.gd b/scenes/game/gameplays/Tile.gd similarity index 91% rename from scenes/game/gameplays/tile.gd rename to scenes/game/gameplays/Tile.gd index 2567b90..eee3604 100644 --- a/scenes/game/gameplays/tile.gd +++ b/scenes/game/gameplays/Tile.gd @@ -123,7 +123,9 @@ func remove_gem_type(gem_index: int) -> bool: return false if active_gem_types.size() <= 2: # Keep at least 2 gem types - DebugManager.log_warn("Cannot remove gem type - minimum 2 types required", "Tile") + DebugManager.log_warn( + "Cannot remove gem type - minimum 2 types required", "Tile" + ) return false active_gem_types.erase(gem_index) @@ -178,7 +180,12 @@ func _update_visual_feedback() -> void: DebugManager.log_debug( ( "SELECTING tile (%d,%d): target scale %.2fx, current scale %s" - % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] + % [ + grid_position.x, + grid_position.y, + scale_multiplier, + sprite.scale + ] ), "Match3" ) @@ -186,12 +193,20 @@ func _update_visual_feedback() -> void: # Highlighted: subtle glow and larger than original board size target_modulate = Color(1.1, 1.1, 1.1, 1.0) scale_multiplier = UIConstants.TILE_HIGHLIGHTED_SCALE - DebugManager.log_debug( - ( - "HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s" - % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] - ), - "Match3" + ( + DebugManager + . log_debug( + ( + "HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s" + % [ + grid_position.x, + grid_position.y, + scale_multiplier, + sprite.scale + ] + ), + "Match3" + ) ) else: # Normal state: white and original board size @@ -200,7 +215,12 @@ func _update_visual_feedback() -> void: DebugManager.log_debug( ( "NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s" - % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] + % [ + grid_position.x, + grid_position.y, + scale_multiplier, + sprite.scale + ] ), "Match3" ) @@ -228,7 +248,11 @@ func _update_visual_feedback() -> void: tween.tween_callback(_on_scale_animation_completed.bind(target_scale)) else: DebugManager.log_debug( - "No scale change needed for tile (%d,%d)" % [grid_position.x, grid_position.y], "Match3" + ( + "No scale change needed for tile (%d,%d)" + % [grid_position.x, grid_position.y] + ), + "Match3" ) @@ -264,7 +288,9 @@ func _input(event: InputEvent) -> void: if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: # Check if the mouse click is within the tile's bounds var local_position = to_local(get_global_mouse_position()) - var sprite_rect = Rect2(-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE) + var sprite_rect = Rect2( + -TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE + ) if sprite_rect.has_point(local_position): tile_selected.emit(self) diff --git a/scenes/main/Main.gd b/scenes/main/Main.gd index 6060f9a..49dc947 100644 --- a/scenes/main/Main.gd +++ b/scenes/main/Main.gd @@ -22,7 +22,9 @@ func _setup_splash_screen_connection() -> void: # Try to find SplashScreen node splash_screen = get_node_or_null("SplashScreen") if not splash_screen: - DebugManager.log_warn("SplashScreen node not found, trying alternative methods", "Main") + DebugManager.log_warn( + "SplashScreen node not found, trying alternative methods", "Main" + ) # Try to find by class or group var splash_nodes = get_tree().get_nodes_in_group("localizable") for node in splash_nodes: @@ -31,11 +33,15 @@ func _setup_splash_screen_connection() -> void: break if splash_screen: - DebugManager.log_debug("SplashScreen node found: %s" % splash_screen.name, "Main") + DebugManager.log_debug( + "SplashScreen node found: %s" % splash_screen.name, "Main" + ) # Try connecting to the signal if it exists if splash_screen.has_signal("confirm_pressed"): splash_screen.confirm_pressed.connect(_on_confirm_pressed) - DebugManager.log_debug("Connected to confirm_pressed signal", "Main") + DebugManager.log_debug( + "Connected to confirm_pressed signal", "Main" + ) else: # Fallback: use input handling directly on the main scene DebugManager.log_warn("Using fallback input handling", "Main") diff --git a/scenes/ui/Credits.gd b/scenes/ui/Credits.gd index 67929b8..bdec122 100644 --- a/scenes/ui/Credits.gd +++ b/scenes/ui/Credits.gd @@ -9,8 +9,10 @@ const YAML_SOURCES: Array[String] = [ # "res://assets/sprites/sprite-sources.yaml", ] -@onready var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer -@onready var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText +@onready +var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer +@onready +var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText @onready var back_button: Button = $MarginContainer/VBoxContainer/BackButton @@ -40,7 +42,9 @@ func _load_yaml_file(yaml_path: String) -> Dictionary: var file := FileAccess.open(yaml_path, FileAccess.READ) if not file: - DebugManager.log_warn("Could not open YAML file: %s" % yaml_path, "Credits") + DebugManager.log_warn( + "Could not open YAML file: %s" % yaml_path, "Credits" + ) return {} var content: String = file.get_as_text() @@ -67,10 +71,18 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary: continue # Top-level section (audio, sprites, textures, etc.) - if not line.begins_with(" ") and not line.begins_with("\t") and trimmed.ends_with(":"): + if ( + not line.begins_with(" ") + and not line.begins_with("\t") + and trimmed.ends_with(":") + ): if current_asset and not current_asset_data.is_empty(): _store_asset_data( - result, current_section, current_subsection, current_asset, current_asset_data + result, + current_section, + current_subsection, + current_asset, + current_asset_data ) current_section = trimmed.trim_suffix(":") current_subsection = "" @@ -80,22 +92,37 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary: result[current_section] = {} # Subsection (music, sfx, characters, etc.) - elif line.begins_with(" ") and not line.begins_with(" ") and trimmed.ends_with(":"): + elif ( + line.begins_with(" ") + and not line.begins_with(" ") + and trimmed.ends_with(":") + ): if current_asset and not current_asset_data.is_empty(): _store_asset_data( - result, current_section, current_subsection, current_asset, current_asset_data + result, + current_section, + current_subsection, + current_asset, + current_asset_data ) current_subsection = trimmed.trim_suffix(":") current_asset = "" current_asset_data = {} - if current_section and not result[current_section].has(current_subsection): + if ( + current_section + and not result[current_section].has(current_subsection) + ): result[current_section][current_subsection] = {} # Asset name elif trimmed.begins_with('"') and trimmed.contains('":'): if current_asset and not current_asset_data.is_empty(): _store_asset_data( - result, current_section, current_subsection, current_asset, current_asset_data + result, + current_section, + current_subsection, + current_asset, + current_asset_data ) var parts: Array = trimmed.split('"') current_asset = parts[1] if parts.size() > 1 else "" @@ -106,21 +133,31 @@ func _parse_yaml_content(yaml_content: String) -> Dictionary: var parts: Array = trimmed.split(":", false, 1) if parts.size() == 2: var key: String = parts[0].strip_edges() - var value: String = parts[1].strip_edges().trim_prefix('"').trim_suffix('"') + var value: String = ( + parts[1].strip_edges().trim_prefix('"').trim_suffix('"') + ) if value and value != '""': current_asset_data[key] = value # Store last asset if current_asset and not current_asset_data.is_empty(): _store_asset_data( - result, current_section, current_subsection, current_asset, current_asset_data + result, + current_section, + current_subsection, + current_asset, + current_asset_data ) return result func _store_asset_data( - result: Dictionary, section: String, subsection: String, asset: String, data: Dictionary + result: Dictionary, + section: String, + subsection: String, + asset: String, + data: Dictionary ) -> void: """Store parsed asset data into result dictionary""" if not section or not asset: @@ -150,7 +187,9 @@ func _merge_credits_data(target: Dictionary, source: Dictionary) -> void: func _display_formatted_credits(credits_data: Dictionary) -> void: """Generate BBCode formatted credits from parsed data""" if not credits_text: - DebugManager.log_error("Credits text node is null, cannot display credits", "Credits") + DebugManager.log_error( + "Credits text node is null, cannot display credits", "Credits" + ) return var credits_bbcode: String = "[center][b][font_size=32]CREDITS[/font_size][/b][/center]\n\n" @@ -227,11 +266,17 @@ func _on_back_button_pressed() -> void: func _input(event: InputEvent) -> void: - if event.is_action_pressed("ui_back") or event.is_action_pressed("action_east"): + if ( + event.is_action_pressed("ui_back") + or event.is_action_pressed("action_east") + ): _on_back_button_pressed() elif event.is_action_pressed("move_up") or event.is_action_pressed("ui_up"): _scroll_credits(-50.0) - elif event.is_action_pressed("move_down") or event.is_action_pressed("ui_down"): + elif ( + event.is_action_pressed("move_down") + or event.is_action_pressed("ui_down") + ): _scroll_credits(50.0) @@ -239,4 +284,6 @@ func _scroll_credits(amount: float) -> void: """Scroll the credits by the specified amount""" var current_scroll: float = scroll_container.scroll_vertical scroll_container.scroll_vertical = int(current_scroll + amount) - DebugManager.log_debug("Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits") + DebugManager.log_debug( + "Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits" + ) diff --git a/scenes/ui/DebugMenu.gd b/scenes/ui/DebugMenu.gd index 0b3f1ad..8f81186 100644 --- a/scenes/ui/DebugMenu.gd +++ b/scenes/ui/DebugMenu.gd @@ -17,12 +17,16 @@ func _find_target_scene(): # Fallback: search by common node names if not match3_scene: for possible_name in ["Match3", "match3", "Match3Game"]: - match3_scene = current_scene.find_child(possible_name, true, false) + match3_scene = current_scene.find_child( + possible_name, true, false + ) if match3_scene: break if match3_scene: - DebugManager.log_debug("Found match3 scene: " + match3_scene.name, log_category) + DebugManager.log_debug( + "Found match3 scene: " + match3_scene.name, log_category + ) _update_ui_from_scene() _stop_search_timer() else: diff --git a/scenes/ui/DebugMenuBase.gd b/scenes/ui/DebugMenuBase.gd index 8641db0..9fc8933 100644 --- a/scenes/ui/DebugMenuBase.gd +++ b/scenes/ui/DebugMenuBase.gd @@ -8,7 +8,8 @@ const MIN_GRID_SIZE := 3 const MIN_TILE_TYPES := 3 const SCENE_SEARCH_COOLDOWN := 0.5 -@export var target_script_path: String = "res://scenes/game/gameplays/Match3Gameplay.gd" +@export +var target_script_path: String = "res://scenes/game/gameplays/Match3Gameplay.gd" @export var log_category: String = "DebugMenu" var match3_scene: Node2D @@ -16,8 +17,10 @@ var search_timer: Timer var last_scene_search_time: float = 0.0 @onready var regenerate_button: Button = $VBoxContainer/RegenerateButton -@onready var gem_types_spinbox: SpinBox = $VBoxContainer/GemTypesContainer/GemTypesSpinBox -@onready var gem_types_label: Label = $VBoxContainer/GemTypesContainer/GemTypesLabel +@onready +var gem_types_spinbox: SpinBox = $VBoxContainer/GemTypesContainer/GemTypesSpinBox +@onready +var gem_types_label: Label = $VBoxContainer/GemTypesContainer/GemTypesLabel @onready var grid_width_spinbox: SpinBox = $VBoxContainer/GridSizeContainer/GridWidthContainer/GridWidthSpinBox @onready @@ -86,7 +89,9 @@ func _setup_scene_finding() -> void: # Virtual method - override in derived classes for specific finding logic func _find_target_scene() -> void: - DebugManager.log_error("_find_target_scene() not implemented in derived class", log_category) + DebugManager.log_error( + "_find_target_scene() not implemented in derived class", log_category + ) func _find_node_by_script(node: Node, script_path: String) -> Node: @@ -114,10 +119,14 @@ func _update_ui_from_scene() -> void: # Connect to grid state loaded signal if not already connected if ( match3_scene.has_signal("grid_state_loaded") - and not match3_scene.grid_state_loaded.is_connected(_on_grid_state_loaded) + and not match3_scene.grid_state_loaded.is_connected( + _on_grid_state_loaded + ) ): match3_scene.grid_state_loaded.connect(_on_grid_state_loaded) - DebugManager.log_debug("Connected to grid_state_loaded signal", log_category) + DebugManager.log_debug( + "Connected to grid_state_loaded signal", log_category + ) # Update gem types display if match3_scene.has_method("get") and "TILE_TYPES" in match3_scene: @@ -135,7 +144,10 @@ func _update_ui_from_scene() -> void: func _on_grid_state_loaded(grid_size: Vector2i, tile_types: int) -> void: DebugManager.log_debug( - "Grid state loaded signal received: size=%s, types=%d" % [grid_size, tile_types], + ( + "Grid state loaded signal received: size=%s, types=%d" + % [grid_size, tile_types] + ), log_category ) @@ -155,7 +167,10 @@ func _stop_search_timer() -> void: func _start_search_timer() -> void: - if search_timer and not search_timer.timeout.is_connected(_find_target_scene): + if ( + search_timer + and not search_timer.timeout.is_connected(_find_target_scene) + ): search_timer.timeout.connect(_find_target_scene) search_timer.start() @@ -176,7 +191,8 @@ func _refresh_current_values() -> void: # Refresh UI with current values from the scene if match3_scene: DebugManager.log_debug( - "Refreshing debug menu values from current scene state", log_category + "Refreshing debug menu values from current scene state", + log_category ) _update_ui_from_scene() @@ -186,14 +202,18 @@ func _on_regenerate_pressed() -> void: _find_target_scene() if not match3_scene: - DebugManager.log_error("Could not find target scene for regeneration", log_category) + DebugManager.log_error( + "Could not find target scene for regeneration", log_category + ) return if match3_scene.has_method("regenerate_grid"): DebugManager.log_debug("Calling regenerate_grid()", log_category) await match3_scene.regenerate_grid() else: - DebugManager.log_error("Target scene does not have regenerate_grid method", log_category) + DebugManager.log_error( + "Target scene does not have regenerate_grid method", log_category + ) func _on_gem_types_changed(value: float) -> void: @@ -207,7 +227,9 @@ func _on_gem_types_changed(value: float) -> void: last_scene_search_time = current_time if not match3_scene: - DebugManager.log_error("Could not find target scene for gem types change", log_category) + DebugManager.log_error( + "Could not find target scene for gem types change", log_category + ) return var new_value: int = int(value) @@ -221,15 +243,21 @@ func _on_gem_types_changed(value: float) -> void: log_category ) # Reset to valid value - gem_types_spinbox.value = clamp(new_value, MIN_TILE_TYPES, MAX_TILE_TYPES) + gem_types_spinbox.value = clamp( + new_value, MIN_TILE_TYPES, MAX_TILE_TYPES + ) return if match3_scene.has_method("set_tile_types"): - DebugManager.log_debug("Setting tile types to " + str(new_value), log_category) + DebugManager.log_debug( + "Setting tile types to " + str(new_value), log_category + ) await match3_scene.set_tile_types(new_value) gem_types_label.text = "Gem Types: " + str(new_value) else: - DebugManager.log_error("Target scene does not have set_tile_types method", log_category) + DebugManager.log_error( + "Target scene does not have set_tile_types method", log_category + ) # Fallback: try to set TILE_TYPES directly if "TILE_TYPES" in match3_scene: match3_scene.TILE_TYPES = new_value @@ -247,7 +275,9 @@ func _on_grid_width_changed(value: float) -> void: last_scene_search_time = current_time if not match3_scene: - DebugManager.log_error("Could not find target scene for grid width change", log_category) + DebugManager.log_error( + "Could not find target scene for grid width change", log_category + ) return var new_width: int = int(value) @@ -261,7 +291,9 @@ func _on_grid_width_changed(value: float) -> void: log_category ) # Reset to valid value - grid_width_spinbox.value = clamp(new_width, MIN_GRID_SIZE, MAX_GRID_SIZE) + grid_width_spinbox.value = clamp( + new_width, MIN_GRID_SIZE, MAX_GRID_SIZE + ) return grid_width_label.text = "Width: " + str(new_width) @@ -271,11 +303,19 @@ func _on_grid_width_changed(value: float) -> void: if match3_scene.has_method("set_grid_size"): DebugManager.log_debug( - "Setting grid size to " + str(new_width) + "x" + str(current_height), log_category + ( + "Setting grid size to " + + str(new_width) + + "x" + + str(current_height) + ), + log_category ) await match3_scene.set_grid_size(Vector2i(new_width, current_height)) else: - DebugManager.log_error("Target scene does not have set_grid_size method", log_category) + DebugManager.log_error( + "Target scene does not have set_grid_size method", log_category + ) func _on_grid_height_changed(value: float) -> void: @@ -289,7 +329,9 @@ func _on_grid_height_changed(value: float) -> void: last_scene_search_time = current_time if not match3_scene: - DebugManager.log_error("Could not find target scene for grid height change", log_category) + DebugManager.log_error( + "Could not find target scene for grid height change", log_category + ) return var new_height: int = int(value) @@ -303,7 +345,9 @@ func _on_grid_height_changed(value: float) -> void: log_category ) # Reset to valid value - grid_height_spinbox.value = clamp(new_height, MIN_GRID_SIZE, MAX_GRID_SIZE) + grid_height_spinbox.value = clamp( + new_height, MIN_GRID_SIZE, MAX_GRID_SIZE + ) return grid_height_label.text = "Height: " + str(new_height) @@ -313,8 +357,16 @@ func _on_grid_height_changed(value: float) -> void: if match3_scene.has_method("set_grid_size"): DebugManager.log_debug( - "Setting grid size to " + str(current_width) + "x" + str(new_height), log_category + ( + "Setting grid size to " + + str(current_width) + + "x" + + str(new_height) + ), + log_category ) await match3_scene.set_grid_size(Vector2i(current_width, new_height)) else: - DebugManager.log_error("Target scene does not have set_grid_size method", log_category) + DebugManager.log_error( + "Target scene does not have set_grid_size method", log_category + ) diff --git a/scenes/ui/MainMenu.gd b/scenes/ui/MainMenu.gd index 2a3127e..bbee1b5 100644 --- a/scenes/ui/MainMenu.gd +++ b/scenes/ui/MainMenu.gd @@ -81,13 +81,17 @@ func _navigate_menu(direction: int) -> void: if current_menu_index < 0: current_menu_index = menu_buttons.size() - 1 _update_visual_selection() - DebugManager.log_info("Menu navigation: index " + str(current_menu_index), "MainMenu") + DebugManager.log_info( + "Menu navigation: index " + str(current_menu_index), "MainMenu" + ) func _activate_current_button() -> void: if current_menu_index >= 0 and current_menu_index < menu_buttons.size(): var button: Button = menu_buttons[current_menu_index] - DebugManager.log_info("Activating button via keyboard/gamepad: " + button.text, "MainMenu") + DebugManager.log_info( + "Activating button via keyboard/gamepad: " + button.text, "MainMenu" + ) button.pressed.emit() @@ -95,7 +99,9 @@ func _update_visual_selection() -> void: for i in range(menu_buttons.size()): var button: Button = menu_buttons[i] if i == current_menu_index: - button.scale = (original_button_scales[i] * UIConstants.BUTTON_HOVER_SCALE) + button.scale = ( + original_button_scales[i] * UIConstants.BUTTON_HOVER_SCALE + ) button.modulate = Color(1.2, 1.2, 1.0) else: button.scale = original_button_scales[i] diff --git a/scenes/ui/SettingsMenu.gd b/scenes/ui/SettingsMenu.gd index 2e1b1e7..021c33b 100644 --- a/scenes/ui/SettingsMenu.gd +++ b/scenes/ui/SettingsMenu.gd @@ -14,10 +14,13 @@ var current_control_index: int = 0 var original_control_scales: Array[Vector2] = [] var original_control_modulates: Array[Color] = [] -@onready var master_slider = $SettingsContainer/MasterVolumeContainer/MasterVolumeSlider -@onready var music_slider = $SettingsContainer/MusicVolumeContainer/MusicVolumeSlider +@onready +var master_slider = $SettingsContainer/MasterVolumeContainer/MasterVolumeSlider +@onready +var music_slider = $SettingsContainer/MusicVolumeContainer/MusicVolumeSlider @onready var sfx_slider = $SettingsContainer/SFXVolumeContainer/SFXVolumeSlider -@onready var language_stepper = $SettingsContainer/LanguageContainer/LanguageStepper +@onready +var language_stepper = $SettingsContainer/LanguageContainer/LanguageStepper @onready var reset_progress_button = $ResetSettingsContainer/ResetProgressButton @@ -26,11 +29,15 @@ func _ready() -> void: DebugManager.log_info("SettingsMenu ready", "Settings") # Language selector is initialized automatically - var master_callback: Callable = _on_volume_slider_changed.bind("master_volume") + var master_callback: Callable = _on_volume_slider_changed.bind( + "master_volume" + ) if not master_slider.value_changed.is_connected(master_callback): master_slider.value_changed.connect(master_callback) - var music_callback: Callable = _on_volume_slider_changed.bind("music_volume") + var music_callback: Callable = _on_volume_slider_changed.bind( + "music_volume" + ) if not music_slider.value_changed.is_connected(music_callback): music_slider.value_changed.connect(music_callback) @@ -57,20 +64,28 @@ func _update_controls_from_settings() -> void: func _on_volume_slider_changed(value: float, setting_key: String) -> void: # Input validation for volume settings if not setting_key in ["master_volume", "music_volume", "sfx_volume"]: - DebugManager.log_error("Invalid volume setting key: " + str(setting_key), "Settings") + DebugManager.log_error( + "Invalid volume setting key: " + str(setting_key), "Settings" + ) return if typeof(value) != TYPE_FLOAT and typeof(value) != TYPE_INT: - DebugManager.log_error("Invalid volume value type: " + str(typeof(value)), "Settings") + DebugManager.log_error( + "Invalid volume value type: " + str(typeof(value)), "Settings" + ) return # Clamp value to valid range var clamped_value: float = clamp(float(value), 0.0, 1.0) if clamped_value != value: - DebugManager.log_warn("Volume value %f clamped to %f" % [value, clamped_value], "Settings") + DebugManager.log_warn( + "Volume value %f clamped to %f" % [value, clamped_value], "Settings" + ) if not settings_manager.set_setting(setting_key, clamped_value): - DebugManager.log_error("Failed to set volume setting: " + setting_key, "Settings") + DebugManager.log_error( + "Failed to set volume setting: " + setting_key, "Settings" + ) func _exit_settings() -> void: @@ -80,8 +95,13 @@ func _exit_settings() -> void: func _input(event: InputEvent) -> void: - if event.is_action_pressed("action_east") or event.is_action_pressed("pause_menu"): - DebugManager.log_debug("Cancel/back action pressed in settings", "Settings") + if ( + event.is_action_pressed("action_east") + or event.is_action_pressed("pause_menu") + ): + DebugManager.log_debug( + "Cancel/back action pressed in settings", "Settings" + ) _exit_settings() get_viewport().set_input_as_handled() return @@ -116,8 +136,12 @@ func _on_back_button_pressed() -> void: func update_text() -> void: $SettingsContainer/SettingsTitle.text = tr("settings_title") - $SettingsContainer/MasterVolumeContainer/MasterVolume.text = tr("master_volume") - $SettingsContainer/MusicVolumeContainer/MusicVolume.text = tr("music_volume") + $SettingsContainer/MasterVolumeContainer/MasterVolume.text = tr( + "master_volume" + ) + $SettingsContainer/MusicVolumeContainer/MusicVolume.text = tr( + "music_volume" + ) $SettingsContainer/SFXVolumeContainer/SFXVolume.text = tr("sfx_volume") $SettingsContainer/LanguageContainer/LanguageLabel.text = tr("language") $BackButtonContainer/BackButton.text = tr("back") @@ -129,7 +153,9 @@ func _on_reset_setting_button_pressed() -> void: DebugManager.log_info("Resetting settings", "Settings") settings_manager.reset_settings_to_defaults() _update_controls_from_settings() - localization_manager.change_language(settings_manager.get_setting("language")) + localization_manager.change_language( + settings_manager.get_setting("language") + ) func _setup_navigation_system() -> void: @@ -156,15 +182,22 @@ func _setup_navigation_system() -> void: func _navigate_controls(direction: int) -> void: AudioManager.play_ui_click() - current_control_index = ((current_control_index + direction) % navigable_controls.size()) + current_control_index = ( + (current_control_index + direction) % navigable_controls.size() + ) if current_control_index < 0: current_control_index = navigable_controls.size() - 1 _update_visual_selection() - DebugManager.log_info("Settings navigation: index " + str(current_control_index), "Settings") + DebugManager.log_info( + "Settings navigation: index " + str(current_control_index), "Settings" + ) func _adjust_current_control(direction: int) -> void: - if current_control_index < 0 or current_control_index >= navigable_controls.size(): + if ( + current_control_index < 0 + or current_control_index >= navigable_controls.size() + ): return var control: Control = navigable_controls[current_control_index] @@ -178,17 +211,26 @@ func _adjust_current_control(direction: int) -> void: slider.value = new_value AudioManager.play_ui_click() DebugManager.log_info( - "Slider adjusted: %s = %f" % [_get_control_name(control), new_value], "Settings" + ( + "Slider adjusted: %s = %f" + % [_get_control_name(control), new_value] + ), + "Settings" ) # Handle language stepper with left/right elif control == language_stepper: - if language_stepper.handle_input_action("move_left" if direction == -1 else "move_right"): + if language_stepper.handle_input_action( + "move_left" if direction == -1 else "move_right" + ): AudioManager.play_ui_click() func _activate_current_control() -> void: - if current_control_index < 0 or current_control_index >= navigable_controls.size(): + if ( + current_control_index < 0 + or current_control_index >= navigable_controls.size() + ): return var control: Control = navigable_controls[current_control_index] @@ -196,12 +238,17 @@ func _activate_current_control() -> void: # Handle buttons if control is Button: AudioManager.play_ui_click() - DebugManager.log_info("Activating button via keyboard/gamepad: " + control.text, "Settings") + DebugManager.log_info( + "Activating button via keyboard/gamepad: " + control.text, + "Settings" + ) control.pressed.emit() # Handle language stepper (no action needed on activation, left/right handles it) elif control == language_stepper: - DebugManager.log_info("Language stepper selected - use left/right to change", "Settings") + DebugManager.log_info( + "Language stepper selected - use left/right to change", "Settings" + ) func _update_visual_selection() -> void: @@ -213,7 +260,8 @@ func _update_visual_selection() -> void: language_stepper.set_highlighted(true) else: control.scale = ( - original_control_scales[i] * UIConstants.UI_CONTROL_HIGHLIGHT_SCALE + original_control_scales[i] + * UIConstants.UI_CONTROL_HIGHLIGHT_SCALE ) control.modulate = Color(1.1, 1.1, 0.9) else: @@ -237,9 +285,17 @@ func _get_control_name(control: Control) -> String: return "button" -func _on_language_stepper_value_changed(new_value: String, new_index: float) -> void: +func _on_language_stepper_value_changed( + new_value: String, new_index: float +) -> void: DebugManager.log_info( - "Language changed via ValueStepper: " + new_value + " (index: " + str(int(new_index)) + ")", + ( + "Language changed via ValueStepper: " + + new_value + + " (index: " + + str(int(new_index)) + + ")" + ), "Settings" ) diff --git a/scenes/ui/components/ValueStepper.gd b/scenes/ui/components/ValueStepper.gd index afd7429..ce60bf7 100644 --- a/scenes/ui/components/ValueStepper.gd +++ b/scenes/ui/components/ValueStepper.gd @@ -30,17 +30,25 @@ var is_highlighted: bool = false func _ready() -> void: - DebugManager.log_info("ValueStepper ready for: " + data_source, "ValueStepper") + DebugManager.log_info( + "ValueStepper ready for: " + data_source, "ValueStepper" + ) # Store original visual properties original_scale = scale original_modulate = modulate # Connect button signals - if left_button and not left_button.pressed.is_connected(_on_left_button_pressed): + if ( + left_button + and not left_button.pressed.is_connected(_on_left_button_pressed) + ): left_button.pressed.connect(_on_left_button_pressed) - if right_button and not right_button.pressed.is_connected(_on_right_button_pressed): + if ( + right_button + and not right_button.pressed.is_connected(_on_right_button_pressed) + ): right_button.pressed.connect(_on_right_button_pressed) # Initialize data @@ -58,7 +66,9 @@ func _load_data() -> void: "difficulty": _load_difficulty_data() _: - DebugManager.log_warn("Unknown data_source: " + data_source, "ValueStepper") + DebugManager.log_warn( + "Unknown data_source: " + data_source, "ValueStepper" + ) func _load_language_data() -> void: @@ -68,22 +78,30 @@ func _load_language_data() -> void: display_names.clear() for lang_code in languages_data.languages.keys(): values.append(lang_code) - display_names.append(languages_data.languages[lang_code]["display_name"]) + display_names.append( + languages_data.languages[lang_code]["display_name"] + ) # Set current index based on current language var current_lang: String = SettingsManager.get_setting("language") var index: int = values.find(current_lang) current_index = max(0, index) - DebugManager.log_info("Loaded %d languages" % values.size(), "ValueStepper") + DebugManager.log_info( + "Loaded %d languages" % values.size(), "ValueStepper" + ) func _load_resolution_data() -> void: # Example resolution data - customize as needed values = ["1920x1080", "1366x768", "1280x720", "1024x768"] - display_names = ["1920×1080 (Full HD)", "1366×768", "1280×720 (HD)", "1024×768"] + display_names = [ + "1920×1080 (Full HD)", "1366×768", "1280×720 (HD)", "1024×768" + ] current_index = 0 - DebugManager.log_info("Loaded %d resolutions" % values.size(), "ValueStepper") + DebugManager.log_info( + "Loaded %d resolutions" % values.size(), "ValueStepper" + ) func _load_difficulty_data() -> void: @@ -91,12 +109,18 @@ func _load_difficulty_data() -> void: values = ["easy", "normal", "hard", "nightmare"] display_names = ["Easy", "Normal", "Hard", "Nightmare"] current_index = 1 # Default to "normal" - DebugManager.log_info("Loaded %d difficulty levels" % values.size(), "ValueStepper") + DebugManager.log_info( + "Loaded %d difficulty levels" % values.size(), "ValueStepper" + ) ## Updates the display text based on current selection func _update_display() -> void: - if values.size() == 0 or current_index < 0 or current_index >= values.size(): + if ( + values.size() == 0 + or current_index < 0 + or current_index >= values.size() + ): value_display.text = "N/A" return @@ -109,7 +133,9 @@ func _update_display() -> void: ## Changes the current value by the specified direction (-1 for previous, +1 for next) func change_value(direction: int) -> void: if values.size() == 0: - DebugManager.log_warn("No values available for: " + data_source, "ValueStepper") + DebugManager.log_warn( + "No values available for: " + data_source, "ValueStepper" + ) return var new_index: int = (current_index + direction) % values.size() @@ -123,7 +149,14 @@ func change_value(direction: int) -> void: _apply_value_change(new_value, current_index) value_changed.emit(new_value, current_index) DebugManager.log_info( - "Value changed to: " + new_value + " (index: " + str(current_index) + ")", "ValueStepper" + ( + "Value changed to: " + + new_value + + " (index: " + + str(current_index) + + ")" + ), + "ValueStepper" ) @@ -136,10 +169,14 @@ func _apply_value_change(new_value: String, _index: int) -> void: LocalizationManager.change_language(new_value) "resolution": # Apply resolution change logic here - DebugManager.log_info("Resolution would change to: " + new_value, "ValueStepper") + DebugManager.log_info( + "Resolution would change to: " + new_value, "ValueStepper" + ) "difficulty": # Apply difficulty change logic here - DebugManager.log_info("Difficulty would change to: " + new_value, "ValueStepper") + DebugManager.log_info( + "Difficulty would change to: " + new_value, "ValueStepper" + ) ## Sets up custom values for the stepper @@ -148,16 +185,24 @@ func setup_custom_values( ) -> void: values = custom_values.duplicate() display_names = ( - custom_display_names.duplicate() if custom_display_names.size() > 0 else values.duplicate() + custom_display_names.duplicate() + if custom_display_names.size() > 0 + else values.duplicate() ) current_index = 0 _update_display() - DebugManager.log_info("Setup custom values: " + str(values.size()) + " items", "ValueStepper") + DebugManager.log_info( + "Setup custom values: " + str(values.size()) + " items", "ValueStepper" + ) ## Gets the current value func get_current_value() -> String: - if values.size() > 0 and current_index >= 0 and current_index < values.size(): + if ( + values.size() > 0 + and current_index >= 0 + and current_index < values.size() + ): return values[current_index] return "" diff --git a/src/autoloads/AudioManager.gd b/src/autoloads/AudioManager.gd index 7931924..5f7577b 100644 --- a/src/autoloads/AudioManager.gd +++ b/src/autoloads/AudioManager.gd @@ -22,7 +22,9 @@ func _ready(): var orig_stream = _load_stream() if not orig_stream: - DebugManager.log_error("Failed to load music stream: %s" % MUSIC_PATH, "AudioManager") + DebugManager.log_error( + "Failed to load music stream: %s" % MUSIC_PATH, "AudioManager" + ) return var stream = orig_stream.duplicate(true) as AudioStream @@ -52,7 +54,9 @@ func _configure_stream_loop(stream: AudioStream) -> void: func _configure_audio_bus() -> void: music_player.bus = "Music" - music_player.volume_db = linear_to_db(SettingsManager.get_setting("music_volume")) + music_player.volume_db = linear_to_db( + SettingsManager.get_setting("music_volume") + ) func update_music_volume(volume: float) -> void: diff --git a/src/autoloads/DebugManager.gd b/src/autoloads/DebugManager.gd index a848f19..67cdab8 100644 --- a/src/autoloads/DebugManager.gd +++ b/src/autoloads/DebugManager.gd @@ -83,7 +83,9 @@ func _log_level_to_string(level: LogLevel) -> String: return level_strings.get(level, "UNKNOWN") -func _format_log_message(level: LogLevel, message: String, category: String = "") -> String: +func _format_log_message( + level: LogLevel, message: String, category: String = "" +) -> String: """Format log message with timestamp, level, category, and content""" var timestamp = Time.get_datetime_string_from_system() var level_str = _log_level_to_string(level) diff --git a/src/autoloads/GameManager.gd b/src/autoloads/GameManager.gd index 4288db2..c0292fe 100644 --- a/src/autoloads/GameManager.gd +++ b/src/autoloads/GameManager.gd @@ -49,14 +49,17 @@ func start_game_with_mode(gameplay_mode: String) -> void: var packed_scene := load(GAME_SCENE_PATH) if not packed_scene or not packed_scene is PackedScene: - DebugManager.log_error("Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager") + DebugManager.log_error( + "Failed to load Game scene at: %s" % GAME_SCENE_PATH, "GameManager" + ) is_changing_scene = false return var result = get_tree().change_scene_to_packed(packed_scene) if result != OK: DebugManager.log_error( - "Failed to change to game scene (Error code: %d)" % result, "GameManager" + "Failed to change to game scene (Error code: %d)" % result, + "GameManager" ) is_changing_scene = false return @@ -67,22 +70,31 @@ func start_game_with_mode(gameplay_mode: String) -> void: # Validate scene was loaded successfully if not get_tree().current_scene: - DebugManager.log_error("Current scene is null after scene change", "GameManager") + DebugManager.log_error( + "Current scene is null after scene change", "GameManager" + ) is_changing_scene = false return # Configure game scene with requested gameplay mode if get_tree().current_scene.has_method("set_gameplay_mode"): - DebugManager.log_info("Setting gameplay mode to: %s" % pending_gameplay_mode, "GameManager") + DebugManager.log_info( + "Setting gameplay mode to: %s" % pending_gameplay_mode, + "GameManager" + ) get_tree().current_scene.set_gameplay_mode(pending_gameplay_mode) # Load saved score if get_tree().current_scene.has_method("set_global_score"): var saved_score = SaveManager.get_current_score() - DebugManager.log_info("Loading saved score: %d" % saved_score, "GameManager") + DebugManager.log_info( + "Loading saved score: %d" % saved_score, "GameManager" + ) get_tree().current_scene.set_global_score(saved_score) else: - DebugManager.log_error("Game scene does not have set_gameplay_mode method", "GameManager") + DebugManager.log_error( + "Game scene does not have set_gameplay_mode method", "GameManager" + ) is_changing_scene = false @@ -91,11 +103,16 @@ func save_game() -> void: """Save current game state and score via SaveManager""" # Get current score from the active game scene var current_score = 0 - if get_tree().current_scene and get_tree().current_scene.has_method("get_global_score"): + if ( + get_tree().current_scene + and get_tree().current_scene.has_method("get_global_score") + ): current_score = get_tree().current_scene.get_global_score() SaveManager.finish_game(current_score) - DebugManager.log_info("Game saved with score: %d" % current_score, "GameManager") + DebugManager.log_info( + "Game saved with score: %d" % current_score, "GameManager" + ) func show_credits() -> void: @@ -103,7 +120,8 @@ func show_credits() -> void: # Prevent concurrent scene changes if is_changing_scene: DebugManager.log_warn( - "Scene change already in progress, ignoring show credits request", "GameManager" + "Scene change already in progress, ignoring show credits request", + "GameManager" ) return @@ -113,7 +131,8 @@ func show_credits() -> void: var packed_scene := load(CREDITS_SCENE_PATH) if not packed_scene or not packed_scene is PackedScene: DebugManager.log_error( - "Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH, "GameManager" + "Failed to load Credits scene at: %s" % CREDITS_SCENE_PATH, + "GameManager" ) is_changing_scene = false return @@ -121,7 +140,8 @@ func show_credits() -> void: var result = get_tree().change_scene_to_packed(packed_scene) if result != OK: DebugManager.log_error( - "Failed to change to credits scene (Error code: %d)" % result, "GameManager" + "Failed to change to credits scene (Error code: %d)" % result, + "GameManager" ) is_changing_scene = false return @@ -137,8 +157,12 @@ func exit_to_main_menu() -> void: """Exit to main menu with race condition protection""" # Prevent concurrent scene changes if is_changing_scene: - DebugManager.log_warn( - "Scene change already in progress, ignoring exit to main menu request", "GameManager" + ( + DebugManager + . log_warn( + "Scene change already in progress, ignoring exit to main menu request", + "GameManager" + ) ) return @@ -147,14 +171,17 @@ func exit_to_main_menu() -> void: var packed_scene := load(MAIN_SCENE_PATH) if not packed_scene or not packed_scene is PackedScene: - DebugManager.log_error("Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager") + DebugManager.log_error( + "Failed to load Main scene at: %s" % MAIN_SCENE_PATH, "GameManager" + ) is_changing_scene = false return var result = get_tree().change_scene_to_packed(packed_scene) if result != OK: DebugManager.log_error( - "Failed to change to main scene (Error code: %d)" % result, "GameManager" + "Failed to change to main scene (Error code: %d)" % result, + "GameManager" ) is_changing_scene = false return @@ -170,25 +197,33 @@ func _validate_game_mode_request(gameplay_mode: String) -> bool: """Validate gameplay mode request with combined checks""" # Input validation if not gameplay_mode or gameplay_mode.is_empty(): - DebugManager.log_error("Empty or null gameplay mode provided", "GameManager") + DebugManager.log_error( + "Empty or null gameplay mode provided", "GameManager" + ) return false if not gameplay_mode is String: DebugManager.log_error( - "Invalid gameplay mode type: " + str(typeof(gameplay_mode)), "GameManager" + "Invalid gameplay mode type: " + str(typeof(gameplay_mode)), + "GameManager" ) return false # Prevent concurrent scene changes (race condition protection) if is_changing_scene: - DebugManager.log_warn("Scene change already in progress, ignoring request", "GameManager") + DebugManager.log_warn( + "Scene change already in progress, ignoring request", "GameManager" + ) return false # Validate gameplay mode var valid_modes = ["match3", "clickomania"] if not gameplay_mode in valid_modes: DebugManager.log_error( - "Invalid gameplay mode: '%s'. Valid modes: %s" % [gameplay_mode, str(valid_modes)], + ( + "Invalid gameplay mode: '%s'. Valid modes: %s" + % [gameplay_mode, str(valid_modes)] + ), "GameManager" ) return false diff --git a/src/autoloads/SaveManager.gd b/src/autoloads/SaveManager.gd index 5285cd0..1ed9719 100644 --- a/src/autoloads/SaveManager.gd +++ b/src/autoloads/SaveManager.gd @@ -42,7 +42,9 @@ func save_game() -> bool: """Save current game data with race condition protection and error handling""" # Prevent concurrent saves if _save_in_progress: - DebugManager.log_warn("Save already in progress, skipping", "SaveManager") + DebugManager.log_warn( + "Save already in progress, skipping", "SaveManager" + ) return false _save_in_progress = true @@ -61,10 +63,13 @@ func _perform_save() -> bool: # Calculate checksum excluding _checksum field save_data["_checksum"] = _calculate_checksum(save_data) - var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE) + var save_file: FileAccess = FileAccess.open( + SAVE_FILE_PATH, FileAccess.WRITE + ) if save_file == null: DebugManager.log_error( - "Failed to open save file for writing: %s" % SAVE_FILE_PATH, "SaveManager" + "Failed to open save file for writing: %s" % SAVE_FILE_PATH, + "SaveManager" ) return false @@ -72,7 +77,9 @@ func _perform_save() -> bool: # Validate JSON creation if json_string.is_empty(): - DebugManager.log_error("Failed to serialize save data to JSON", "SaveManager") + DebugManager.log_error( + "Failed to serialize save data to JSON", "SaveManager" + ) save_file.close() return false @@ -92,7 +99,9 @@ func _perform_save() -> bool: func load_game() -> void: """Load game data from disk with comprehensive validation and error recovery""" if not FileAccess.file_exists(SAVE_FILE_PATH): - DebugManager.log_info("No save file found, using defaults", "SaveManager") + DebugManager.log_info( + "No save file found, using defaults", "SaveManager" + ) return # Reset restore flag @@ -111,7 +120,8 @@ func _load_and_parse_save_file() -> Variant: var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ) if save_file == null: DebugManager.log_error( - "Failed to open save file for reading: %s" % SAVE_FILE_PATH, "SaveManager" + "Failed to open save file for reading: %s" % SAVE_FILE_PATH, + "SaveManager" ) return null @@ -119,7 +129,11 @@ func _load_and_parse_save_file() -> Variant: var file_size: int = save_file.get_length() if file_size > MAX_FILE_SIZE: DebugManager.log_error( - "Save file too large: %d bytes (max %d)" % [file_size, MAX_FILE_SIZE], "SaveManager" + ( + "Save file too large: %d bytes (max %d)" + % [file_size, MAX_FILE_SIZE] + ), + "SaveManager" ) save_file.close() return null @@ -128,21 +142,26 @@ func _load_and_parse_save_file() -> Variant: save_file.close() if not json_string is String: - DebugManager.log_error("Save file contains invalid data type", "SaveManager") + DebugManager.log_error( + "Save file contains invalid data type", "SaveManager" + ) return null var json: JSON = JSON.new() var parse_result: Error = json.parse(json_string) if parse_result != OK: DebugManager.log_error( - "Failed to parse save file JSON: %s" % json.error_string, "SaveManager" + "Failed to parse save file JSON: %s" % json.error_string, + "SaveManager" ) _handle_load_failure("JSON parse failed") return null var loaded_data: Variant = json.data if not loaded_data is Dictionary: - DebugManager.log_error("Save file root is not a dictionary", "SaveManager") + DebugManager.log_error( + "Save file root is not a dictionary", "SaveManager" + ) _handle_load_failure("Invalid data format") return null @@ -154,7 +173,8 @@ func _process_loaded_data(loaded_data: Variant) -> void: # Validate checksum first if not _validate_checksum(loaded_data): DebugManager.log_error( - "Save file checksum validation failed - possible tampering", "SaveManager" + "Save file checksum validation failed - possible tampering", + "SaveManager" ) _handle_load_failure("Checksum validation failed") return @@ -162,14 +182,17 @@ func _process_loaded_data(loaded_data: Variant) -> void: # Handle version migration var migrated_data: Variant = _handle_version_migration(loaded_data) if migrated_data == null: - DebugManager.log_error("Save file version migration failed", "SaveManager") + DebugManager.log_error( + "Save file version migration failed", "SaveManager" + ) _handle_load_failure("Migration failed") return # Validate and fix loaded data if not _validate_and_fix_save_data(migrated_data): DebugManager.log_error( - "Save file failed validation after migration, using defaults", "SaveManager" + "Save file failed validation after migration, using defaults", + "SaveManager" ) _handle_load_failure("Validation failed") return @@ -184,7 +207,8 @@ func _handle_load_failure(reason: String) -> void: var backup_restored = _restore_backup_if_exists() if not backup_restored: DebugManager.log_warn( - "%s and backup restore failed, using defaults" % reason, "SaveManager" + "%s and backup restore failed, using defaults" % reason, + "SaveManager" ) DebugManager.log_info( @@ -199,11 +223,14 @@ func _handle_load_failure(reason: String) -> void: func update_current_score(score: int) -> void: # Input validation if score < 0: - DebugManager.log_warn("Negative score rejected: %d" % score, "SaveManager") + DebugManager.log_warn( + "Negative score rejected: %d" % score, "SaveManager" + ) return if score > MAX_SCORE: DebugManager.log_warn( - "Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE], "SaveManager" + "Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE], + "SaveManager" ) score = MAX_SCORE @@ -219,23 +246,33 @@ func start_new_game() -> void: # Clear saved grid state game_data.grid_state.grid_layout = [] DebugManager.log_info( - "Started new game #%d (cleared grid state)" % game_data.games_played, "SaveManager" + "Started new game #%d (cleared grid state)" % game_data.games_played, + "SaveManager" ) func finish_game(final_score: int) -> void: # Input validation if final_score < 0: - DebugManager.log_warn("Negative final score rejected: %d" % final_score, "SaveManager") + DebugManager.log_warn( + "Negative final score rejected: %d" % final_score, "SaveManager" + ) return if final_score > MAX_SCORE: DebugManager.log_warn( - "Final score too high, capping: %d -> %d" % [final_score, MAX_SCORE], "SaveManager" + ( + "Final score too high, capping: %d -> %d" + % [final_score, MAX_SCORE] + ), + "SaveManager" ) final_score = MAX_SCORE DebugManager.log_info( - "Finishing game with score: %d (previous: %d)" % [final_score, game_data.current_score], + ( + "Finishing game with score: %d (previous: %d)" + % [final_score, game_data.current_score] + ), "SaveManager" ) game_data.current_score = final_score @@ -250,7 +287,9 @@ func finish_game(final_score: int) -> void: if final_score > game_data.high_score: game_data.high_score = final_score - DebugManager.log_info("New high score achieved: %d" % final_score, "SaveManager") + DebugManager.log_info( + "New high score achieved: %d" % final_score, "SaveManager" + ) save_game() @@ -271,11 +310,18 @@ func get_total_score() -> int: func save_grid_state( - grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array + grid_size: Vector2i, + tile_types_count: int, + active_gem_types: Array, + grid_layout: Array ) -> void: # Input validation - if not _validate_grid_parameters(grid_size, tile_types_count, active_gem_types, grid_layout): - DebugManager.log_error("Grid state validation failed, not saving", "SaveManager") + if not _validate_grid_parameters( + grid_size, tile_types_count, active_gem_types, grid_layout + ): + DebugManager.log_error( + "Grid state validation failed, not saving", "SaveManager" + ) return DebugManager.log_info( @@ -338,10 +384,13 @@ func reset_all_progress() -> bool: if FileAccess.file_exists(SAVE_FILE_PATH): var error: Error = DirAccess.remove_absolute(SAVE_FILE_PATH) if error == OK: - DebugManager.log_info("Main save file deleted successfully", "SaveManager") + DebugManager.log_info( + "Main save file deleted successfully", "SaveManager" + ) else: DebugManager.log_error( - "Failed to delete main save file: error %d" % error, "SaveManager" + "Failed to delete main save file: error %d" % error, + "SaveManager" ) # Delete backup file @@ -349,21 +398,27 @@ func reset_all_progress() -> bool: if FileAccess.file_exists(backup_path): var error: Error = DirAccess.remove_absolute(backup_path) if error == OK: - DebugManager.log_info("Backup save file deleted successfully", "SaveManager") + DebugManager.log_info( + "Backup save file deleted successfully", "SaveManager" + ) else: DebugManager.log_error( - "Failed to delete backup save file: error %d" % error, "SaveManager" + "Failed to delete backup save file: error %d" % error, + "SaveManager" ) DebugManager.log_info( - "Progress reset completed - all scores and save data cleared", "SaveManager" + "Progress reset completed - all scores and save data cleared", + "SaveManager" ) # Clear restore flag _restore_in_progress = false # Create fresh save file with default data - DebugManager.log_info("Creating fresh save file with default data", "SaveManager") + DebugManager.log_info( + "Creating fresh save file with default data", "SaveManager" + ) save_game() return true @@ -395,11 +450,17 @@ func _validate_save_data(data: Dictionary) -> bool: func _validate_required_fields(data: Dictionary) -> bool: """Validate that all required fields exist""" var required_fields: Array[String] = [ - "high_score", "current_score", "games_played", "total_score", "grid_state" + "high_score", + "current_score", + "games_played", + "total_score", + "grid_state" ] for field in required_fields: if not data.has(field): - DebugManager.log_error("Missing required field: %s" % field, "SaveManager") + DebugManager.log_error( + "Missing required field: %s" % field, "SaveManager" + ) return false return true @@ -409,7 +470,9 @@ func _validate_score_fields(data: Dictionary) -> bool: var score_fields = ["high_score", "current_score", "total_score"] for field in score_fields: if not _is_valid_score(data.get(field, 0)): - DebugManager.log_error("Invalid %s validation failed" % field, "SaveManager") + DebugManager.log_error( + "Invalid %s validation failed" % field, "SaveManager" + ) return false return true @@ -419,7 +482,10 @@ func _validate_games_played_field(data: Dictionary) -> bool: var games_played: Variant = data.get("games_played", 0) if not (games_played is int or games_played is float): DebugManager.log_error( - "Invalid games_played type: %s (type: %s)" % [str(games_played), typeof(games_played)], + ( + "Invalid games_played type: %s (type: %s)" + % [str(games_played), typeof(games_played)] + ), "SaveManager" ) return false @@ -427,14 +493,18 @@ func _validate_games_played_field(data: Dictionary) -> bool: # Check for NaN/Infinity in games_played if it's a float if games_played is float and (is_nan(games_played) or is_inf(games_played)): DebugManager.log_error( - "Invalid games_played float value: %s" % str(games_played), "SaveManager" + "Invalid games_played float value: %s" % str(games_played), + "SaveManager" ) return false var games_played_int: int = int(games_played) if games_played_int < 0 or games_played_int > MAX_GAMES_PLAYED: DebugManager.log_error( - "Invalid games_played value: %d (range: 0-%d)" % [games_played_int, MAX_GAMES_PLAYED], + ( + "Invalid games_played value: %d (range: 0-%d)" + % [games_played_int, MAX_GAMES_PLAYED] + ), "SaveManager" ) return false @@ -447,16 +517,23 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool: Permissive validation that fixes issues instead of rejecting data entirely. Used during migration to preserve as much user data as possible. """ - DebugManager.log_info("Running permissive validation with auto-fix", "SaveManager") + DebugManager.log_info( + "Running permissive validation with auto-fix", "SaveManager" + ) # Ensure all required fields exist, create defaults if missing var required_fields: Array[String] = [ - "high_score", "current_score", "games_played", "total_score", "grid_state" + "high_score", + "current_score", + "games_played", + "total_score", + "grid_state" ] for field in required_fields: if not data.has(field): DebugManager.log_warn( - "Missing required field '%s', adding default value" % field, "SaveManager" + "Missing required field '%s', adding default value" % field, + "SaveManager" ) match field: "high_score", "current_score", "total_score": @@ -475,15 +552,21 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool: for field in ["high_score", "current_score", "total_score"]: var value: Variant = data.get(field, 0) if not (value is int or value is float): - DebugManager.log_warn("Invalid type for %s, converting to 0" % field, "SaveManager") + DebugManager.log_warn( + "Invalid type for %s, converting to 0" % field, "SaveManager" + ) data[field] = 0 else: var numeric_value: int = int(value) if numeric_value < 0: - DebugManager.log_warn("Negative %s fixed to 0" % field, "SaveManager") + DebugManager.log_warn( + "Negative %s fixed to 0" % field, "SaveManager" + ) data[field] = 0 elif numeric_value > MAX_SCORE: - DebugManager.log_warn("%s too high, clamped to maximum" % field, "SaveManager") + DebugManager.log_warn( + "%s too high, clamped to maximum" % field, "SaveManager" + ) data[field] = MAX_SCORE else: data[field] = numeric_value @@ -491,7 +574,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool: # Fix games_played var games_played: Variant = data.get("games_played", 0) if not (games_played is int or games_played is float): - DebugManager.log_warn("Invalid games_played type, converting to 0", "SaveManager") + DebugManager.log_warn( + "Invalid games_played type, converting to 0", "SaveManager" + ) data["games_played"] = 0 else: var games_played_int: int = int(games_played) @@ -505,7 +590,9 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool: # Fix grid_state - ensure it exists and has basic structure var grid_state: Variant = data.get("grid_state", {}) if not grid_state is Dictionary: - DebugManager.log_warn("Invalid grid_state, creating default", "SaveManager") + DebugManager.log_warn( + "Invalid grid_state, creating default", "SaveManager" + ) data["grid_state"] = { "grid_size": {"x": 8, "y": 8}, "tile_types_count": 5, @@ -514,24 +601,48 @@ func _validate_and_fix_save_data(data: Dictionary) -> bool: } else: # Fix grid_state fields if they're missing or invalid - if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary: - DebugManager.log_warn("Invalid grid_size, using default", "SaveManager") + if ( + not grid_state.has("grid_size") + or not grid_state.grid_size is Dictionary + ): + DebugManager.log_warn( + "Invalid grid_size, using default", "SaveManager" + ) grid_state["grid_size"] = {"x": 8, "y": 8} - if not grid_state.has("tile_types_count") or not grid_state.tile_types_count is int: - DebugManager.log_warn("Invalid tile_types_count, using default", "SaveManager") + if ( + not grid_state.has("tile_types_count") + or not grid_state.tile_types_count is int + ): + DebugManager.log_warn( + "Invalid tile_types_count, using default", "SaveManager" + ) grid_state["tile_types_count"] = 5 - if not grid_state.has("active_gem_types") or not grid_state.active_gem_types is Array: - DebugManager.log_warn("Invalid active_gem_types, using default", "SaveManager") + if ( + not grid_state.has("active_gem_types") + or not grid_state.active_gem_types is Array + ): + DebugManager.log_warn( + "Invalid active_gem_types, using default", "SaveManager" + ) grid_state["active_gem_types"] = [0, 1, 2, 3, 4] - if not grid_state.has("grid_layout") or not grid_state.grid_layout is Array: - DebugManager.log_warn("Invalid grid_layout, clearing saved grid", "SaveManager") + if ( + not grid_state.has("grid_layout") + or not grid_state.grid_layout is Array + ): + DebugManager.log_warn( + "Invalid grid_layout, clearing saved grid", "SaveManager" + ) grid_state["grid_layout"] = [] - DebugManager.log_info( - "Permissive validation completed - data has been fixed and will be loaded", "SaveManager" + ( + DebugManager + . log_info( + "Permissive validation completed - data has been fixed and will be loaded", + "SaveManager" + ) ) return true @@ -569,7 +680,10 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary: """Validate grid size and return validation result with dimensions""" var result = {"valid": false, "width": 0, "height": 0} - if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary: + if ( + not grid_state.has("grid_size") + or not grid_state.grid_size is Dictionary + ): DebugManager.log_error("Invalid grid_size in save data", "SaveManager") return result @@ -581,8 +695,15 @@ func _validate_grid_size(grid_state: Dictionary) -> Dictionary: var height: Variant = size.y if not width is int or not height is int: return result - if width < 3 or height < 3 or width > MAX_GRID_SIZE or height > MAX_GRID_SIZE: - DebugManager.log_error("Grid size out of bounds: %dx%d" % [width, height], "SaveManager") + if ( + width < 3 + or height < 3 + or width > MAX_GRID_SIZE + or height > MAX_GRID_SIZE + ): + DebugManager.log_error( + "Grid size out of bounds: %dx%d" % [width, height], "SaveManager" + ) return result result.valid = true @@ -595,16 +716,22 @@ func _validate_tile_types(grid_state: Dictionary) -> int: """Validate tile types count and return it, or -1 if invalid""" var tile_types: Variant = grid_state.get("tile_types_count", 0) if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES: - DebugManager.log_error("Invalid tile_types_count: %s" % str(tile_types), "SaveManager") + DebugManager.log_error( + "Invalid tile_types_count: %s" % str(tile_types), "SaveManager" + ) return -1 return tile_types -func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool: +func _validate_active_gem_types( + grid_state: Dictionary, tile_types: int +) -> bool: """Validate active gem types array""" var active_gems: Variant = grid_state.get("active_gem_types", []) if not active_gems is Array: - DebugManager.log_error("active_gem_types is not an array", "SaveManager") + DebugManager.log_error( + "active_gem_types is not an array", "SaveManager" + ) return false # If active_gem_types exists, validate its contents @@ -613,12 +740,17 @@ func _validate_active_gem_types(grid_state: Dictionary, tile_types: int) -> bool var gem_type: Variant = active_gems[i] if not gem_type is int: DebugManager.log_error( - "active_gem_types[%d] is not an integer: %s" % [i, str(gem_type)], "SaveManager" + ( + "active_gem_types[%d] is not an integer: %s" + % [i, str(gem_type)] + ), + "SaveManager" ) return false if gem_type < 0 or gem_type >= tile_types: DebugManager.log_error( - "active_gem_types[%d] out of range: %d" % [i, gem_type], "SaveManager" + "active_gem_types[%d] out of range: %d" % [i, gem_type], + "SaveManager" ) return false return true @@ -629,7 +761,10 @@ func _validate_grid_layout( ) -> bool: if layout.size() != expected_height: DebugManager.log_error( - "Grid layout height mismatch: %d vs %d" % [layout.size(), expected_height], + ( + "Grid layout height mismatch: %d vs %d" + % [layout.size(), expected_height] + ), "SaveManager" ) return false @@ -637,11 +772,16 @@ func _validate_grid_layout( for y in range(layout.size()): var row: Variant = layout[y] if not row is Array: - DebugManager.log_error("Grid layout row %d is not an array" % y, "SaveManager") + DebugManager.log_error( + "Grid layout row %d is not an array" % y, "SaveManager" + ) return false if row.size() != expected_width: DebugManager.log_error( - "Grid layout row %d width mismatch: %d vs %d" % [y, row.size(), expected_width], + ( + "Grid layout row %d width mismatch: %d vs %d" + % [y, row.size(), expected_width] + ), "SaveManager" ) return false @@ -650,13 +790,20 @@ func _validate_grid_layout( var tile_type: Variant = row[x] if not tile_type is int: DebugManager.log_error( - "Grid tile [%d][%d] is not an integer: %s" % [y, x, str(tile_type)], + ( + "Grid tile [%d][%d] is not an integer: %s" + % [y, x, str(tile_type)] + ), "SaveManager" ) return false if tile_type < -1 or tile_type >= max_tile_type: DebugManager.log_error( - "Grid tile [%d][%d] type out of range: %d" % [y, x, tile_type], "SaveManager" + ( + "Grid tile [%d][%d] type out of range: %d" + % [y, x, tile_type] + ), + "SaveManager" ) return false @@ -664,7 +811,10 @@ func _validate_grid_layout( func _validate_grid_parameters( - grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array + grid_size: Vector2i, + tile_types_count: int, + active_gem_types: Array, + grid_layout: Array ) -> bool: # Validate grid size if ( @@ -685,7 +835,10 @@ func _validate_grid_parameters( # Validate tile types count if tile_types_count < 3 or tile_types_count > MAX_TILE_TYPES: DebugManager.log_error( - "Invalid tile types count: %d (min 3, max %d)" % [tile_types_count, MAX_TILE_TYPES], + ( + "Invalid tile types count: %d (min 3, max %d)" + % [tile_types_count, MAX_TILE_TYPES] + ), "SaveManager" ) return false @@ -702,14 +855,20 @@ func _validate_grid_parameters( return false # Validate grid layout - return _validate_grid_layout(grid_layout, grid_size.x, grid_size.y, tile_types_count) + return _validate_grid_layout( + grid_layout, grid_size.x, grid_size.y, tile_types_count + ) func _is_valid_score(score: Variant) -> bool: # Accept both int and float, but convert to int for validation if not (score is int or score is float): DebugManager.log_error( - "Score is not a number: %s (type: %s)" % [str(score), typeof(score)], "SaveManager" + ( + "Score is not a number: %s (type: %s)" + % [str(score), typeof(score)] + ), + "SaveManager" ) return false @@ -717,13 +876,16 @@ func _is_valid_score(score: Variant) -> bool: if score is float: if is_nan(score) or is_inf(score): DebugManager.log_error( - "Score contains invalid float value (NaN/Inf): %s" % str(score), "SaveManager" + "Score contains invalid float value (NaN/Inf): %s" % str(score), + "SaveManager" ) return false var score_int = int(score) if score_int < 0 or score_int > MAX_SCORE: - DebugManager.log_error("Score out of bounds: %d" % score_int, "SaveManager") + DebugManager.log_error( + "Score out of bounds: %d" % score_int, "SaveManager" + ) return false return true @@ -737,12 +899,16 @@ func _merge_validated_data(loaded_data: Dictionary) -> void: # Games played should always be an integer if loaded_data.has("games_played"): - game_data["games_played"] = _safe_get_numeric_value(loaded_data, "games_played", 0) + game_data["games_played"] = _safe_get_numeric_value( + loaded_data, "games_played", 0 + ) # Merge grid state carefully var loaded_grid: Variant = loaded_data.get("grid_state", {}) if loaded_grid is Dictionary: - for grid_key in ["grid_size", "tile_types_count", "active_gem_types", "grid_layout"]: + for grid_key in [ + "grid_size", "tile_types_count", "active_gem_types", "grid_layout" + ]: if loaded_grid.has(grid_key): game_data.grid_state[grid_key] = loaded_grid[grid_key] @@ -844,15 +1010,22 @@ func _validate_checksum(data: Dictionary) -> bool: # Try to be more lenient with existing saves to prevent data loss var data_version: Variant = data.get("_version", 0) if data_version <= 1: - DebugManager.log_warn( - ( - "Checksum mismatch in v%d save file - may be due to JSON serialization issue " - + ( - "(stored: %s, calculated: %s)" - % [data_version, stored_checksum, calculated_checksum] - ) - ), - "SaveManager" + ( + DebugManager + . log_warn( + ( + "Checksum mismatch in v%d save file - may be due to JSON serialization issue " + + ( + "(stored: %s, calculated: %s)" + % [ + data_version, + stored_checksum, + calculated_checksum + ] + ) + ), + "SaveManager" + ) ) ( DebugManager @@ -876,7 +1049,9 @@ func _validate_checksum(data: Dictionary) -> bool: return is_valid -func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float) -> int: +func _safe_get_numeric_value( + data: Dictionary, key: String, default_value: float +) -> int: """Safely extract and convert numeric values with comprehensive validation""" var value: Variant = data.get(key, default_value) @@ -910,13 +1085,15 @@ func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float if key in ["high_score", "current_score", "total_score"]: if int_value < 0 or int_value > MAX_SCORE: DebugManager.log_warn( - "Score %s out of bounds: %d, using default" % [key, int_value], "SaveManager" + "Score %s out of bounds: %d, using default" % [key, int_value], + "SaveManager" ) return int(default_value) elif key == "games_played": if int_value < 0 or int_value > MAX_GAMES_PLAYED: DebugManager.log_warn( - "Games played out of bounds: %d, using default" % int_value, "SaveManager" + "Games played out of bounds: %d, using default" % int_value, + "SaveManager" ) return int(default_value) @@ -930,7 +1107,8 @@ func _handle_version_migration(data: Dictionary) -> Variant: if data_version == SAVE_FORMAT_VERSION: # Current version, no migration needed DebugManager.log_info( - "Save file is current version (%d)" % SAVE_FORMAT_VERSION, "SaveManager" + "Save file is current version (%d)" % SAVE_FORMAT_VERSION, + "SaveManager" ) return data if data_version > SAVE_FORMAT_VERSION: @@ -945,7 +1123,10 @@ func _handle_version_migration(data: Dictionary) -> Variant: return null # Older version - migrate DebugManager.log_info( - "Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION], + ( + "Migrating save data from version %d to %d" + % [data_version, SAVE_FORMAT_VERSION] + ), "SaveManager" ) return _migrate_save_data(data, data_version) @@ -960,7 +1141,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary: # Add new fields that didn't exist in version 0 if not migrated_data.has("total_score"): migrated_data["total_score"] = 0 - DebugManager.log_info("Added total_score field during migration", "SaveManager") + DebugManager.log_info( + "Added total_score field during migration", "SaveManager" + ) if not migrated_data.has("grid_state"): migrated_data["grid_state"] = { @@ -969,7 +1152,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary: "active_gem_types": [0, 1, 2, 3, 4], "grid_layout": [] } - DebugManager.log_info("Added grid_state structure during migration", "SaveManager") + DebugManager.log_info( + "Added grid_state structure during migration", "SaveManager" + ) # Ensure all numeric values are within bounds after migration for score_key in ["high_score", "current_score", "total_score"]: @@ -981,11 +1166,17 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary: DebugManager.log_warn( ( "Clamping %s during migration: %d -> %d" - % [score_key, int_score, clamp(int_score, 0, MAX_SCORE)] + % [ + score_key, + int_score, + clamp(int_score, 0, MAX_SCORE) + ] ), "SaveManager" ) - migrated_data[score_key] = clamp(int_score, 0, MAX_SCORE) + migrated_data[score_key] = clamp( + int_score, 0, MAX_SCORE + ) # Future migrations would go here # if from_version < 2: @@ -997,7 +1188,9 @@ func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary: # Recalculate checksum after migration migrated_data["_checksum"] = _calculate_checksum(migrated_data) - DebugManager.log_info("Save data migration completed successfully", "SaveManager") + DebugManager.log_info( + "Save data migration completed successfully", "SaveManager" + ) return migrated_data @@ -1005,7 +1198,9 @@ func _create_backup() -> void: # Create backup of current save file if FileAccess.file_exists(SAVE_FILE_PATH): var backup_path: String = SAVE_FILE_PATH + ".backup" - var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ) + var original: FileAccess = FileAccess.open( + SAVE_FILE_PATH, FileAccess.READ + ) var backup: FileAccess = FileAccess.open(backup_path, FileAccess.WRITE) if original and backup: backup.store_var(original.get_var()) @@ -1017,7 +1212,9 @@ func _create_backup() -> void: func _restore_backup_if_exists() -> bool: var backup_path: String = SAVE_FILE_PATH + ".backup" if not FileAccess.file_exists(backup_path): - DebugManager.log_warn("No backup file found for recovery", "SaveManager") + DebugManager.log_warn( + "No backup file found for recovery", "SaveManager" + ) return false DebugManager.log_info("Attempting to restore from backup", "SaveManager") @@ -1025,12 +1222,16 @@ func _restore_backup_if_exists() -> bool: # Validate backup file size before attempting restore var backup_file: FileAccess = FileAccess.open(backup_path, FileAccess.READ) if backup_file == null: - DebugManager.log_error("Failed to open backup file for reading", "SaveManager") + DebugManager.log_error( + "Failed to open backup file for reading", "SaveManager" + ) return false var backup_size: int = backup_file.get_length() if backup_size > MAX_FILE_SIZE: - DebugManager.log_error("Backup file too large: %d bytes" % backup_size, "SaveManager") + DebugManager.log_error( + "Backup file too large: %d bytes" % backup_size, "SaveManager" + ) backup_file.close() return false @@ -1045,13 +1246,17 @@ func _restore_backup_if_exists() -> bool: # Create new save file from backup var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE) if original == null: - DebugManager.log_error("Failed to create new save file from backup", "SaveManager") + DebugManager.log_error( + "Failed to create new save file from backup", "SaveManager" + ) return false original.store_var(backup_data) original.close() - DebugManager.log_info("Backup restored successfully to main save file", "SaveManager") + DebugManager.log_info( + "Backup restored successfully to main save file", "SaveManager" + ) # Note: The restored file will be loaded on the next game restart # We don't recursively load here to prevent infinite loops return true diff --git a/src/autoloads/SettingsManager.gd b/src/autoloads/SettingsManager.gd index 1fe699e..9fd8160 100644 --- a/src/autoloads/SettingsManager.gd +++ b/src/autoloads/SettingsManager.gd @@ -10,7 +10,10 @@ const MAX_SETTING_STRING_LENGTH = 10 # Max length for string settings like lang var settings: Dictionary = {} var default_settings: Dictionary = { - "master_volume": 0.50, "music_volume": 0.40, "sfx_volume": 0.50, "language": "en" + "master_volume": 0.50, + "music_volume": 0.40, + "sfx_volume": 0.50, + "language": "en" } var languages_data: Dictionary = {} @@ -32,7 +35,9 @@ func load_settings() -> void: if load_result == OK: for key in default_settings.keys(): - var loaded_value = config.get_value("settings", key, default_settings[key]) + var loaded_value = config.get_value( + "settings", key, default_settings[key] + ) # Validate loaded settings before applying if _validate_setting_value(key, loaded_value): settings[key] = loaded_value @@ -45,10 +50,15 @@ func load_settings() -> void: "SettingsManager" ) settings[key] = default_settings[key] - DebugManager.log_info("Settings loaded: " + str(settings), "SettingsManager") + DebugManager.log_info( + "Settings loaded: " + str(settings), "SettingsManager" + ) else: DebugManager.log_warn( - "No settings file found (Error code: %d), using defaults" % load_result, + ( + "No settings file found (Error code: %d), using defaults" + % load_result + ), "SettingsManager" ) settings = default_settings.duplicate() @@ -58,7 +68,9 @@ func load_settings() -> void: func _apply_all_settings(): - DebugManager.log_info("Applying settings: " + str(settings), "SettingsManager") + DebugManager.log_info( + "Applying settings: " + str(settings), "SettingsManager" + ) # Apply language setting if "language" in settings: @@ -70,24 +82,33 @@ func _apply_all_settings(): var sfx_bus = AudioServer.get_bus_index("SFX") if master_bus >= 0 and "master_volume" in settings: - AudioServer.set_bus_volume_db(master_bus, linear_to_db(settings["master_volume"])) + AudioServer.set_bus_volume_db( + master_bus, linear_to_db(settings["master_volume"]) + ) else: DebugManager.log_warn( - "Master audio bus not found or master_volume setting missing", "SettingsManager" + "Master audio bus not found or master_volume setting missing", + "SettingsManager" ) if music_bus >= 0 and "music_volume" in settings: - AudioServer.set_bus_volume_db(music_bus, linear_to_db(settings["music_volume"])) + AudioServer.set_bus_volume_db( + music_bus, linear_to_db(settings["music_volume"]) + ) else: DebugManager.log_warn( - "Music audio bus not found or music_volume setting missing", "SettingsManager" + "Music audio bus not found or music_volume setting missing", + "SettingsManager" ) if sfx_bus >= 0 and "sfx_volume" in settings: - AudioServer.set_bus_volume_db(sfx_bus, linear_to_db(settings["sfx_volume"])) + AudioServer.set_bus_volume_db( + sfx_bus, linear_to_db(settings["sfx_volume"]) + ) else: DebugManager.log_warn( - "SFX audio bus not found or sfx_volume setting missing", "SettingsManager" + "SFX audio bus not found or sfx_volume setting missing", + "SettingsManager" ) @@ -99,7 +120,8 @@ func save_settings(): var save_result = config.save(SETTINGS_FILE) if save_result != OK: DebugManager.log_error( - "Failed to save settings (Error code: %d)" % save_result, "SettingsManager" + "Failed to save settings (Error code: %d)" % save_result, + "SettingsManager" ) return false @@ -119,7 +141,8 @@ func set_setting(key: String, value) -> bool: # Validate value type and range based on key if not _validate_setting_value(key, value): DebugManager.log_error( - "Invalid value for setting '%s': %s" % [key, str(value)], "SettingsManager" + "Invalid value for setting '%s': %s" % [key, str(value)], + "SettingsManager" ) return false @@ -157,7 +180,8 @@ func _validate_volume_setting(key: String, value) -> bool: # Check for NaN and infinity if is_nan(float_value) or is_inf(float_value): DebugManager.log_warn( - "Invalid float value for %s: %s" % [key, str(value)], "SettingsManager" + "Invalid float value for %s: %s" % [key, str(value)], + "SettingsManager" ) return false # Range validation @@ -170,7 +194,8 @@ func _validate_language_setting(value) -> bool: # Prevent extremely long strings if value.length() > MAX_SETTING_STRING_LENGTH: DebugManager.log_warn( - "Language code too long: %d characters" % value.length(), "SettingsManager" + "Language code too long: %d characters" % value.length(), + "SettingsManager" ) return false # Check for valid characters (alphanumeric and common separators only) @@ -178,11 +203,15 @@ func _validate_language_setting(value) -> bool: regex.compile("^[a-zA-Z0-9_-]+$") if not regex.search(value): DebugManager.log_warn( - "Language code contains invalid characters: %s" % value, "SettingsManager" + "Language code contains invalid characters: %s" % value, + "SettingsManager" ) return false # Check if language is supported - if languages_data.has("languages") and languages_data.languages is Dictionary: + if ( + languages_data.has("languages") + and languages_data.languages is Dictionary + ): return value in languages_data.languages # Fallback to basic validation if languages not loaded return value in ["en", "ru"] @@ -192,7 +221,9 @@ func _validate_default_setting(key: String, value) -> bool: # Default validation: accept if type matches default setting type var default_value = default_settings.get(key) if default_value == null: - DebugManager.log_warn("Unknown setting key in validation: %s" % key, "SettingsManager") + DebugManager.log_warn( + "Unknown setting key in validation: %s" % key, "SettingsManager" + ) return false return typeof(value) == typeof(default_value) @@ -210,7 +241,9 @@ func _apply_setting_side_effect(key: String, value) -> void: AudioManager.update_music_volume(value) "sfx_volume": if AudioServer.get_bus_index("SFX") >= 0: - AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value)) + AudioServer.set_bus_volume_db( + AudioServer.get_bus_index("SFX"), linear_to_db(value) + ) func load_languages(): @@ -230,7 +263,11 @@ func load_languages(): languages_data = parsed_data DebugManager.log_info( - "Languages loaded successfully: " + str(languages_data.languages.keys()), "SettingsManager" + ( + "Languages loaded successfully: " + + str(languages_data.languages.keys()) + ), + "SettingsManager" ) @@ -239,7 +276,8 @@ func _load_languages_file() -> String: if not file: var error_code = FileAccess.get_open_error() DebugManager.log_error( - "Could not open languages.json (Error code: %d)" % error_code, "SettingsManager" + "Could not open languages.json (Error code: %d)" % error_code, + "SettingsManager" ) return "" @@ -247,14 +285,19 @@ func _load_languages_file() -> String: var file_size = file.get_length() if file_size > MAX_JSON_FILE_SIZE: DebugManager.log_error( - "Languages.json file too large: %d bytes (max %d)" % [file_size, MAX_JSON_FILE_SIZE], + ( + "Languages.json file too large: %d bytes (max %d)" + % [file_size, MAX_JSON_FILE_SIZE] + ), "SettingsManager" ) file.close() return "" if file_size == 0: - DebugManager.log_error("Languages.json file is empty", "SettingsManager") + DebugManager.log_error( + "Languages.json file is empty", "SettingsManager" + ) file.close() return "" @@ -264,7 +307,8 @@ func _load_languages_file() -> String: if file_error != OK: DebugManager.log_error( - "Error reading languages.json (Error code: %d)" % file_error, "SettingsManager" + "Error reading languages.json (Error code: %d)" % file_error, + "SettingsManager" ) return "" @@ -274,27 +318,36 @@ func _load_languages_file() -> String: func _parse_languages_json(json_string: String) -> Dictionary: # Validate the JSON string is not empty if json_string.is_empty(): - DebugManager.log_error("Languages.json contains empty content", "SettingsManager") + DebugManager.log_error( + "Languages.json contains empty content", "SettingsManager" + ) return {} var json = JSON.new() var parse_result = json.parse(json_string) if parse_result != OK: DebugManager.log_error( - "JSON parsing failed at line %d: %s" % [json.error_line, json.error_string], + ( + "JSON parsing failed at line %d: %s" + % [json.error_line, json.error_string] + ), "SettingsManager" ) return {} if not json.data or not json.data is Dictionary: - DebugManager.log_error("Invalid JSON data structure in languages.json", "SettingsManager") + DebugManager.log_error( + "Invalid JSON data structure in languages.json", "SettingsManager" + ) return {} return json.data func _load_default_languages_with_fallback(reason: String): - DebugManager.log_warn("Loading default languages due to: " + reason, "SettingsManager") + DebugManager.log_warn( + "Loading default languages due to: " + reason, "SettingsManager" + ) _load_default_languages() @@ -302,9 +355,14 @@ func _load_default_languages(): # Fallback language data when JSON file fails to load languages_data = { "languages": - {"en": {"name": "English", "flag": "🇺🇸"}, "ru": {"name": "Русский", "flag": "🇷🇺"}} + { + "en": {"name": "English", "flag": "🇺🇸"}, + "ru": {"name": "Русский", "flag": "🇷🇺"} + } } - DebugManager.log_info("Default languages loaded as fallback", "SettingsManager") + DebugManager.log_info( + "Default languages loaded as fallback", "SettingsManager" + ) func get_languages_data(): @@ -312,15 +370,21 @@ func get_languages_data(): func reset_settings_to_defaults() -> void: - DebugManager.log_info("Resetting all settings to defaults", "SettingsManager") + DebugManager.log_info( + "Resetting all settings to defaults", "SettingsManager" + ) for key in default_settings.keys(): settings[key] = default_settings[key] _apply_setting_side_effect(key, settings[key]) var save_success = save_settings() if save_success: - DebugManager.log_info("Settings reset completed successfully", "SettingsManager") + DebugManager.log_info( + "Settings reset completed successfully", "SettingsManager" + ) else: - DebugManager.log_error("Failed to save reset settings", "SettingsManager") + DebugManager.log_error( + "Failed to save reset settings", "SettingsManager" + ) func _validate_languages_structure(data: Dictionary) -> bool: @@ -344,16 +408,22 @@ func _validate_languages_structure(data: Dictionary) -> bool: func _validate_languages_root_structure(data: Dictionary) -> bool: """Validate the root structure of languages data""" if not data.has("languages"): - DebugManager.log_error("Languages.json missing 'languages' key", "SettingsManager") + DebugManager.log_error( + "Languages.json missing 'languages' key", "SettingsManager" + ) return false var languages = data["languages"] if not languages is Dictionary: - DebugManager.log_error("'languages' is not a dictionary", "SettingsManager") + DebugManager.log_error( + "'languages' is not a dictionary", "SettingsManager" + ) return false if languages.is_empty(): - DebugManager.log_error("Languages dictionary is empty", "SettingsManager") + DebugManager.log_error( + "Languages dictionary is empty", "SettingsManager" + ) return false return true @@ -367,28 +437,35 @@ func _validate_individual_languages(languages: Dictionary) -> bool: return true -func _validate_single_language_entry(lang_code: Variant, lang_data: Variant) -> bool: +func _validate_single_language_entry( + lang_code: Variant, lang_data: Variant +) -> bool: """Validate a single language entry""" if not lang_code is String: DebugManager.log_error( - "Language code is not a string: %s" % str(lang_code), "SettingsManager" + "Language code is not a string: %s" % str(lang_code), + "SettingsManager" ) return false if lang_code.length() > MAX_SETTING_STRING_LENGTH: - DebugManager.log_error("Language code too long: %s" % lang_code, "SettingsManager") + DebugManager.log_error( + "Language code too long: %s" % lang_code, "SettingsManager" + ) return false if not lang_data is Dictionary: DebugManager.log_error( - "Language data for '%s' is not a dictionary" % lang_code, "SettingsManager" + "Language data for '%s' is not a dictionary" % lang_code, + "SettingsManager" ) return false # Validate required fields in language data if not lang_data.has("name") or not lang_data["name"] is String: DebugManager.log_error( - "Language '%s' missing valid 'name' field" % lang_code, "SettingsManager" + "Language '%s' missing valid 'name' field" % lang_code, + "SettingsManager" ) return false diff --git a/tests/TestAudioManager.gd b/tests/TestAudioManager.gd index 90c11de..c8f5eb5 100644 --- a/tests/TestAudioManager.gd +++ b/tests/TestAudioManager.gd @@ -69,12 +69,17 @@ func test_basic_functionality(): # Test that AudioManager has expected methods var expected_methods = ["update_music_volume", "play_ui_click"] - TestHelperClass.assert_has_methods(audio_manager, expected_methods, "AudioManager methods") + TestHelperClass.assert_has_methods( + audio_manager, expected_methods, "AudioManager methods" + ) # Test that AudioManager has expected constants - TestHelperClass.assert_true("MUSIC_PATH" in audio_manager, "MUSIC_PATH constant exists") TestHelperClass.assert_true( - "UI_CLICK_SOUND_PATH" in audio_manager, "UI_CLICK_SOUND_PATH constant exists" + "MUSIC_PATH" in audio_manager, "MUSIC_PATH constant exists" + ) + TestHelperClass.assert_true( + "UI_CLICK_SOUND_PATH" in audio_manager, + "UI_CLICK_SOUND_PATH constant exists" ) @@ -85,9 +90,12 @@ func test_audio_constants(): var music_path = audio_manager.MUSIC_PATH var click_path = audio_manager.UI_CLICK_SOUND_PATH - TestHelperClass.assert_true(music_path.begins_with("res://"), "Music path uses res:// protocol") TestHelperClass.assert_true( - click_path.begins_with("res://"), "Click sound path uses res:// protocol" + music_path.begins_with("res://"), "Music path uses res:// protocol" + ) + TestHelperClass.assert_true( + click_path.begins_with("res://"), + "Click sound path uses res:// protocol" ) # Test file extensions @@ -101,11 +109,17 @@ func test_audio_constants(): if click_path.ends_with(ext): click_has_valid_ext = true - TestHelperClass.assert_true(music_has_valid_ext, "Music file has valid audio extension") - TestHelperClass.assert_true(click_has_valid_ext, "Click sound has valid audio extension") + TestHelperClass.assert_true( + music_has_valid_ext, "Music file has valid audio extension" + ) + TestHelperClass.assert_true( + click_has_valid_ext, "Click sound has valid audio extension" + ) # Test that audio files exist - TestHelperClass.assert_true(ResourceLoader.exists(music_path), "Music file exists at path") + TestHelperClass.assert_true( + ResourceLoader.exists(music_path), "Music file exists at path" + ) TestHelperClass.assert_true( ResourceLoader.exists(click_path), "Click sound file exists at path" ) @@ -115,9 +129,12 @@ func test_audio_player_initialization(): TestHelperClass.print_step("Audio Player Initialization") # Test music player initialization - TestHelperClass.assert_not_null(audio_manager.music_player, "Music player is initialized") + TestHelperClass.assert_not_null( + audio_manager.music_player, "Music player is initialized" + ) TestHelperClass.assert_true( - audio_manager.music_player is AudioStreamPlayer, "Music player is AudioStreamPlayer type" + audio_manager.music_player is AudioStreamPlayer, + "Music player is AudioStreamPlayer type" ) TestHelperClass.assert_true( audio_manager.music_player.get_parent() == audio_manager, @@ -125,7 +142,9 @@ func test_audio_player_initialization(): ) # Test UI click player initialization - TestHelperClass.assert_not_null(audio_manager.ui_click_player, "UI click player is initialized") + TestHelperClass.assert_not_null( + audio_manager.ui_click_player, "UI click player is initialized" + ) TestHelperClass.assert_true( audio_manager.ui_click_player is AudioStreamPlayer, "UI click player is AudioStreamPlayer type" @@ -137,10 +156,14 @@ func test_audio_player_initialization(): # Test audio bus assignment TestHelperClass.assert_equal( - "Music", audio_manager.music_player.bus, "Music player assigned to Music bus" + "Music", + audio_manager.music_player.bus, + "Music player assigned to Music bus" ) TestHelperClass.assert_equal( - "SFX", audio_manager.ui_click_player.bus, "UI click player assigned to SFX bus" + "SFX", + audio_manager.ui_click_player.bus, + "UI click player assigned to SFX bus" ) @@ -148,26 +171,38 @@ func test_stream_loading_and_validation(): TestHelperClass.print_step("Stream Loading and Validation") # Test music stream loading - TestHelperClass.assert_not_null(audio_manager.music_player.stream, "Music stream is loaded") + TestHelperClass.assert_not_null( + audio_manager.music_player.stream, "Music stream is loaded" + ) if audio_manager.music_player.stream: TestHelperClass.assert_true( - audio_manager.music_player.stream is AudioStream, "Music stream is AudioStream type" + audio_manager.music_player.stream is AudioStream, + "Music stream is AudioStream type" ) # Test click stream loading - TestHelperClass.assert_not_null(audio_manager.click_stream, "Click stream is loaded") + TestHelperClass.assert_not_null( + audio_manager.click_stream, "Click stream is loaded" + ) if audio_manager.click_stream: TestHelperClass.assert_true( - audio_manager.click_stream is AudioStream, "Click stream is AudioStream type" + audio_manager.click_stream is AudioStream, + "Click stream is AudioStream type" ) # Test stream resource loading directly var loaded_music = load(audio_manager.MUSIC_PATH) - TestHelperClass.assert_not_null(loaded_music, "Music resource loads successfully") - TestHelperClass.assert_true(loaded_music is AudioStream, "Loaded music is AudioStream type") + TestHelperClass.assert_not_null( + loaded_music, "Music resource loads successfully" + ) + TestHelperClass.assert_true( + loaded_music is AudioStream, "Loaded music is AudioStream type" + ) var loaded_click = load(audio_manager.UI_CLICK_SOUND_PATH) - TestHelperClass.assert_not_null(loaded_click, "Click resource loads successfully") + TestHelperClass.assert_not_null( + loaded_click, "Click resource loads successfully" + ) TestHelperClass.assert_true( loaded_click is AudioStream, "Loaded click sound is AudioStream type" ) @@ -186,7 +221,9 @@ func test_audio_bus_configuration(): # Test player bus assignments match actual AudioServer buses if music_bus_index >= 0: TestHelperClass.assert_equal( - "Music", audio_manager.music_player.bus, "Music player correctly assigned to Music bus" + "Music", + audio_manager.music_player.bus, + "Music player correctly assigned to Music bus" ) if sfx_bus_index >= 0: @@ -208,20 +245,27 @@ func test_volume_management(): # Test volume update to valid range audio_manager.update_music_volume(0.5) TestHelperClass.assert_float_equal( - linear_to_db(0.5), audio_manager.music_player.volume_db, 0.001, "Music volume set correctly" + linear_to_db(0.5), + audio_manager.music_player.volume_db, + 0.001, + "Music volume set correctly" ) # Test volume update to zero (should stop music) audio_manager.update_music_volume(0.0) TestHelperClass.assert_equal( - linear_to_db(0.0), audio_manager.music_player.volume_db, "Zero volume set correctly" + linear_to_db(0.0), + audio_manager.music_player.volume_db, + "Zero volume set correctly" ) # Note: We don't test playing state as it depends on initialization conditions # Test volume update to maximum audio_manager.update_music_volume(1.0) TestHelperClass.assert_equal( - linear_to_db(1.0), audio_manager.music_player.volume_db, "Maximum volume set correctly" + linear_to_db(1.0), + audio_manager.music_player.volume_db, + "Maximum volume set correctly" ) # Test volume range validation @@ -248,7 +292,8 @@ func test_music_playback_control(): audio_manager.music_player, "Music player exists for playback testing" ) TestHelperClass.assert_not_null( - audio_manager.music_player.stream, "Music player has stream for playback testing" + audio_manager.music_player.stream, + "Music player has stream for playback testing" ) # Test playback state management @@ -279,8 +324,12 @@ func test_ui_sound_effects(): TestHelperClass.print_step("UI Sound Effects") # Test UI click functionality - TestHelperClass.assert_not_null(audio_manager.ui_click_player, "UI click player exists") - TestHelperClass.assert_not_null(audio_manager.click_stream, "Click stream is loaded") + TestHelperClass.assert_not_null( + audio_manager.ui_click_player, "UI click player exists" + ) + TestHelperClass.assert_not_null( + audio_manager.click_stream, "Click stream is loaded" + ) # Test that play_ui_click can be called safely var original_stream = audio_manager.ui_click_player.stream @@ -296,7 +345,9 @@ func test_ui_sound_effects(): # Test multiple rapid clicks (should not cause errors) for i in range(3): audio_manager.play_ui_click() - TestHelperClass.assert_true(true, "Rapid click %d handled safely" % (i + 1)) + TestHelperClass.assert_true( + true, "Rapid click %d handled safely" % (i + 1) + ) # Test click with null stream var backup_stream = audio_manager.click_stream @@ -315,7 +366,9 @@ func test_stream_loop_configuration(): if music_stream is AudioStreamWAV: # For WAV files, check loop mode var has_loop_mode = "loop_mode" in music_stream - TestHelperClass.assert_true(has_loop_mode, "WAV stream has loop_mode property") + TestHelperClass.assert_true( + has_loop_mode, "WAV stream has loop_mode property" + ) if has_loop_mode: TestHelperClass.assert_equal( AudioStreamWAV.LOOP_FORWARD, @@ -325,12 +378,18 @@ func test_stream_loop_configuration(): elif music_stream is AudioStreamOggVorbis: # For OGG files, check loop property var has_loop = "loop" in music_stream - TestHelperClass.assert_true(has_loop, "OGG stream has loop property") + TestHelperClass.assert_true( + has_loop, "OGG stream has loop property" + ) if has_loop: - TestHelperClass.assert_true(music_stream.loop, "OGG stream loop enabled") + TestHelperClass.assert_true( + music_stream.loop, "OGG stream loop enabled" + ) # Test loop configuration for different stream types - TestHelperClass.assert_true(true, "Stream loop configuration tested based on type") + TestHelperClass.assert_true( + true, "Stream loop configuration tested based on type" + ) func test_error_handling(): @@ -341,15 +400,18 @@ func test_error_handling(): # Test that AudioManager initializes even with potential issues TestHelperClass.assert_not_null( - audio_manager, "AudioManager initializes despite potential resource issues" + audio_manager, + "AudioManager initializes despite potential resource issues" ) # Test that players are still created even if streams fail to load TestHelperClass.assert_not_null( - audio_manager.music_player, "Music player created regardless of stream loading" + audio_manager.music_player, + "Music player created regardless of stream loading" ) TestHelperClass.assert_not_null( - audio_manager.ui_click_player, "UI click player created regardless of stream loading" + audio_manager.ui_click_player, + "UI click player created regardless of stream loading" ) # Test null stream handling in play_ui_click @@ -358,7 +420,9 @@ func test_error_handling(): # This should not crash audio_manager.play_ui_click() - TestHelperClass.assert_true(true, "play_ui_click handles null stream gracefully") + TestHelperClass.assert_true( + true, "play_ui_click handles null stream gracefully" + ) # Restore original stream audio_manager.click_stream = original_click_stream diff --git a/tests/TestGameManager.gd b/tests/TestGameManager.gd index 4b67f18..d8026f1 100644 --- a/tests/TestGameManager.gd +++ b/tests/TestGameManager.gd @@ -57,7 +57,9 @@ func test_basic_functionality(): # Test that GameManager has expected properties TestHelperClass.assert_has_properties( - game_manager, ["pending_gameplay_mode", "is_changing_scene"], "GameManager properties" + game_manager, + ["pending_gameplay_mode", "is_changing_scene"], + "GameManager properties" ) # Test that GameManager has expected methods @@ -70,13 +72,19 @@ func test_basic_functionality(): "save_game", "exit_to_main_menu" ] - TestHelperClass.assert_has_methods(game_manager, expected_methods, "GameManager methods") + TestHelperClass.assert_has_methods( + game_manager, expected_methods, "GameManager methods" + ) # Test initial state TestHelperClass.assert_equal( - "match3", game_manager.pending_gameplay_mode, "Default pending gameplay mode" + "match3", + game_manager.pending_gameplay_mode, + "Default pending gameplay mode" + ) + TestHelperClass.assert_false( + game_manager.is_changing_scene, "Initial scene change flag" ) - TestHelperClass.assert_false(game_manager.is_changing_scene, "Initial scene change flag") func test_scene_constants(): @@ -97,15 +105,23 @@ func test_scene_constants(): TestHelperClass.assert_true( game_path.begins_with("res://"), "Game scene path uses res:// protocol" ) - TestHelperClass.assert_true(game_path.ends_with(".tscn"), "Game scene path has .tscn extension") + TestHelperClass.assert_true( + game_path.ends_with(".tscn"), "Game scene path has .tscn extension" + ) TestHelperClass.assert_true( main_path.begins_with("res://"), "Main scene path uses res:// protocol" ) - TestHelperClass.assert_true(main_path.ends_with(".tscn"), "Main scene path has .tscn extension") + TestHelperClass.assert_true( + main_path.ends_with(".tscn"), "Main scene path has .tscn extension" + ) # Test that scene files exist - TestHelperClass.assert_true(ResourceLoader.exists(game_path), "Game scene file exists at path") - TestHelperClass.assert_true(ResourceLoader.exists(main_path), "Main scene file exists at path") + TestHelperClass.assert_true( + ResourceLoader.exists(game_path), "Game scene file exists at path" + ) + TestHelperClass.assert_true( + ResourceLoader.exists(main_path), "Main scene file exists at path" + ) func test_input_validation(): @@ -118,38 +134,50 @@ func test_input_validation(): # Test empty string validation game_manager.start_game_with_mode("") TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Empty string mode rejected" + original_mode, + game_manager.pending_gameplay_mode, + "Empty string mode rejected" ) TestHelperClass.assert_false( - game_manager.is_changing_scene, "Scene change flag unchanged after empty mode" + game_manager.is_changing_scene, + "Scene change flag unchanged after empty mode" ) # Test null validation - GameManager expects String, so this tests the type safety # Note: In Godot 4.4, passing null to String parameter causes script error as expected # The function properly validates empty strings instead TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Mode preserved after empty string test" + original_mode, + game_manager.pending_gameplay_mode, + "Mode preserved after empty string test" ) TestHelperClass.assert_false( - game_manager.is_changing_scene, "Scene change flag unchanged after validation tests" + game_manager.is_changing_scene, + "Scene change flag unchanged after validation tests" ) # Test invalid mode validation game_manager.start_game_with_mode("invalid_mode") TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Invalid mode rejected" + original_mode, + game_manager.pending_gameplay_mode, + "Invalid mode rejected" ) TestHelperClass.assert_false( - game_manager.is_changing_scene, "Scene change flag unchanged after invalid mode" + game_manager.is_changing_scene, + "Scene change flag unchanged after invalid mode" ) # Test case sensitivity game_manager.start_game_with_mode("MATCH3") TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Case-sensitive mode validation" + original_mode, + game_manager.pending_gameplay_mode, + "Case-sensitive mode validation" ) TestHelperClass.assert_false( - game_manager.is_changing_scene, "Scene change flag unchanged after wrong case" + game_manager.is_changing_scene, + "Scene change flag unchanged after wrong case" ) @@ -165,14 +193,19 @@ func test_race_condition_protection(): # Verify second request was rejected TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Concurrent scene change blocked" + original_mode, + game_manager.pending_gameplay_mode, + "Concurrent scene change blocked" + ) + TestHelperClass.assert_true( + game_manager.is_changing_scene, "Scene change flag preserved" ) - TestHelperClass.assert_true(game_manager.is_changing_scene, "Scene change flag preserved") # Test exit to main menu during scene change game_manager.exit_to_main_menu() TestHelperClass.assert_true( - game_manager.is_changing_scene, "Exit request blocked during scene change" + game_manager.is_changing_scene, + "Exit request blocked during scene change" ) # Reset state @@ -191,13 +224,17 @@ func test_gameplay_mode_validation(): # Create a temporary mock to test validation var test_mode_valid = mode in ["match3", "clickomania"] - TestHelperClass.assert_true(test_mode_valid, "Valid mode accepted: " + mode) + TestHelperClass.assert_true( + test_mode_valid, "Valid mode accepted: " + mode + ) # Test whitelist enforcement var invalid_modes = ["puzzle", "arcade", "adventure", "rpg", "action"] for mode in invalid_modes: var test_mode_invalid = not (mode in ["match3", "clickomania"]) - TestHelperClass.assert_true(test_mode_invalid, "Invalid mode rejected: " + mode) + TestHelperClass.assert_true( + test_mode_invalid, "Invalid mode rejected: " + mode + ) func test_scene_transition_safety(): @@ -209,12 +246,20 @@ func test_scene_transition_safety(): # Test scene resource loading var game_scene = load(game_scene_path) - TestHelperClass.assert_not_null(game_scene, "Game scene resource loads successfully") - TestHelperClass.assert_true(game_scene is PackedScene, "Game scene is PackedScene type") + TestHelperClass.assert_not_null( + game_scene, "Game scene resource loads successfully" + ) + TestHelperClass.assert_true( + game_scene is PackedScene, "Game scene is PackedScene type" + ) var main_scene = load(main_scene_path) - TestHelperClass.assert_not_null(main_scene, "Main scene resource loads successfully") - TestHelperClass.assert_true(main_scene is PackedScene, "Main scene is PackedScene type") + TestHelperClass.assert_not_null( + main_scene, "Main scene resource loads successfully" + ) + TestHelperClass.assert_true( + main_scene is PackedScene, "Main scene is PackedScene type" + ) # Test that current scene exists TestHelperClass.assert_not_null(current_scene, "Current scene exists") @@ -234,10 +279,14 @@ func test_error_handling(): # Verify state preservation after invalid inputs game_manager.start_game_with_mode("") TestHelperClass.assert_equal( - original_changing, game_manager.is_changing_scene, "State preserved after empty mode error" + original_changing, + game_manager.is_changing_scene, + "State preserved after empty mode error" ) TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Mode preserved after empty mode error" + original_mode, + game_manager.pending_gameplay_mode, + "Mode preserved after empty mode error" ) game_manager.start_game_with_mode("invalid") @@ -247,7 +296,9 @@ func test_error_handling(): "State preserved after invalid mode error" ) TestHelperClass.assert_equal( - original_mode, game_manager.pending_gameplay_mode, "Mode preserved after invalid mode error" + original_mode, + game_manager.pending_gameplay_mode, + "Mode preserved after invalid mode error" ) @@ -263,9 +314,15 @@ func test_scene_method_validation(): var has_set_global_score = mock_scene.has_method("set_global_score") var has_get_global_score = mock_scene.has_method("get_global_score") - TestHelperClass.assert_false(has_set_gameplay_mode, "Mock scene lacks set_gameplay_mode method") - TestHelperClass.assert_false(has_set_global_score, "Mock scene lacks set_global_score method") - TestHelperClass.assert_false(has_get_global_score, "Mock scene lacks get_global_score method") + TestHelperClass.assert_false( + has_set_gameplay_mode, "Mock scene lacks set_gameplay_mode method" + ) + TestHelperClass.assert_false( + has_set_global_score, "Mock scene lacks set_global_score method" + ) + TestHelperClass.assert_false( + has_get_global_score, "Mock scene lacks get_global_score method" + ) # Clean up mock scene mock_scene.queue_free() @@ -284,7 +341,9 @@ func test_pending_mode_management(): # This simulates what would happen in start_game_with_mode game_manager.pending_gameplay_mode = test_mode TestHelperClass.assert_equal( - test_mode, game_manager.pending_gameplay_mode, "Pending mode set correctly" + test_mode, + game_manager.pending_gameplay_mode, + "Pending mode set correctly" ) # Test mode preservation during errors diff --git a/tests/TestMatch3Gameplay.gd b/tests/TestMatch3Gameplay.gd index 6337cda..b3dd21a 100644 --- a/tests/TestMatch3Gameplay.gd +++ b/tests/TestMatch3Gameplay.gd @@ -51,7 +51,9 @@ func setup_test_environment(): # Load Match3 scene match3_scene = load("res://scenes/game/gameplays/Match3Gameplay.tscn") - TestHelperClass.assert_not_null(match3_scene, "Match3 scene loads successfully") + TestHelperClass.assert_not_null( + match3_scene, "Match3 scene loads successfully" + ) # Create test viewport for isolated testing test_viewport = SubViewport.new() @@ -62,7 +64,9 @@ func setup_test_environment(): if match3_scene: match3_instance = match3_scene.instantiate() test_viewport.add_child(match3_instance) - TestHelperClass.assert_not_null(match3_instance, "Match3 instance created successfully") + TestHelperClass.assert_not_null( + match3_instance, "Match3 instance created successfully" + ) # Wait for initialization await process_frame @@ -73,28 +77,44 @@ func test_basic_functionality(): TestHelperClass.print_step("Basic Functionality") if not match3_instance: - TestHelperClass.assert_true(false, "Match3 instance not available for testing") + TestHelperClass.assert_true( + false, "Match3 instance not available for testing" + ) return # Test that Match3 has expected properties var expected_properties = [ - "GRID_SIZE", "TILE_TYPES", "grid", "current_state", "selected_tile", "cursor_position" + "GRID_SIZE", + "TILE_TYPES", + "grid", + "current_state", + "selected_tile", + "cursor_position" ] for prop in expected_properties: - TestHelperClass.assert_true(prop in match3_instance, "Match3 has property: " + prop) + TestHelperClass.assert_true( + prop in match3_instance, "Match3 has property: " + prop + ) # Test that Match3 has expected methods var expected_methods = [ - "_has_match_at", "_check_for_matches", "_get_match_line", "_clear_matches" + "_has_match_at", + "_check_for_matches", + "_get_match_line", + "_clear_matches" ] - TestHelperClass.assert_has_methods(match3_instance, expected_methods, "Match3 gameplay methods") + TestHelperClass.assert_has_methods( + match3_instance, expected_methods, "Match3 gameplay methods" + ) # Test signals TestHelperClass.assert_true( - match3_instance.has_signal("score_changed"), "Match3 has score_changed signal" + match3_instance.has_signal("score_changed"), + "Match3 has score_changed signal" ) TestHelperClass.assert_true( - match3_instance.has_signal("grid_state_loaded"), "Match3 has grid_state_loaded signal" + match3_instance.has_signal("grid_state_loaded"), + "Match3 has grid_state_loaded signal" ) @@ -105,26 +125,41 @@ func test_constants_and_safety_limits(): return # Test safety constants exist - TestHelperClass.assert_true("MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists") + TestHelperClass.assert_true( + "MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists" + ) TestHelperClass.assert_true( "MAX_TILE_TYPES" in match3_instance, "MAX_TILE_TYPES constant exists" ) TestHelperClass.assert_true( - "MAX_CASCADE_ITERATIONS" in match3_instance, "MAX_CASCADE_ITERATIONS constant exists" + "MAX_CASCADE_ITERATIONS" in match3_instance, + "MAX_CASCADE_ITERATIONS constant exists" + ) + TestHelperClass.assert_true( + "MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists" ) - TestHelperClass.assert_true("MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists") TestHelperClass.assert_true( "MIN_TILE_TYPES" in match3_instance, "MIN_TILE_TYPES constant exists" ) # Test safety limit values are reasonable - TestHelperClass.assert_equal(15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable") - TestHelperClass.assert_equal(10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable") TestHelperClass.assert_equal( - 20, match3_instance.MAX_CASCADE_ITERATIONS, "MAX_CASCADE_ITERATIONS prevents infinite loops" + 15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable" + ) + TestHelperClass.assert_equal( + 10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable" + ) + TestHelperClass.assert_equal( + 20, + match3_instance.MAX_CASCADE_ITERATIONS, + "MAX_CASCADE_ITERATIONS prevents infinite loops" + ) + TestHelperClass.assert_equal( + 3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable" + ) + TestHelperClass.assert_equal( + 3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable" ) - TestHelperClass.assert_equal(3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable") - TestHelperClass.assert_equal(3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable") # Test current values are within safety limits TestHelperClass.assert_in_range( @@ -148,13 +183,16 @@ func test_constants_and_safety_limits(): # Test timing constants TestHelperClass.assert_true( - "CASCADE_WAIT_TIME" in match3_instance, "CASCADE_WAIT_TIME constant exists" + "CASCADE_WAIT_TIME" in match3_instance, + "CASCADE_WAIT_TIME constant exists" ) TestHelperClass.assert_true( - "SWAP_ANIMATION_TIME" in match3_instance, "SWAP_ANIMATION_TIME constant exists" + "SWAP_ANIMATION_TIME" in match3_instance, + "SWAP_ANIMATION_TIME constant exists" ) TestHelperClass.assert_true( - "TILE_DROP_WAIT_TIME" in match3_instance, "TILE_DROP_WAIT_TIME constant exists" + "TILE_DROP_WAIT_TIME" in match3_instance, + "TILE_DROP_WAIT_TIME constant exists" ) @@ -165,8 +203,12 @@ func test_grid_initialization(): return # Test grid structure - TestHelperClass.assert_not_null(match3_instance.grid, "Grid array is initialized") - TestHelperClass.assert_true(match3_instance.grid is Array, "Grid is Array type") + TestHelperClass.assert_not_null( + match3_instance.grid, "Grid array is initialized" + ) + TestHelperClass.assert_true( + match3_instance.grid is Array, "Grid is Array type" + ) # Test grid dimensions var expected_height = match3_instance.GRID_SIZE.y @@ -180,7 +222,9 @@ func test_grid_initialization(): for y in range(match3_instance.grid.size()): if y < expected_height: TestHelperClass.assert_equal( - expected_width, match3_instance.grid[y].size(), "Grid row %d has correct width" % y + expected_width, + match3_instance.grid[y].size(), + "Grid row %d has correct width" % y ) # Test tiles are properly instantiated @@ -195,10 +239,12 @@ func test_grid_initialization(): if tile and is_instance_valid(tile): valid_tile_count += 1 TestHelperClass.assert_true( - "tile_type" in tile, "Tile at (%d,%d) has tile_type property" % [x, y] + "tile_type" in tile, + "Tile at (%d,%d) has tile_type property" % [x, y] ) TestHelperClass.assert_true( - "grid_position" in tile, "Tile at (%d,%d) has grid_position property" % [x, y] + "grid_position" in tile, + "Tile at (%d,%d) has grid_position property" % [x, y] ) # Test tile type is within valid range @@ -222,15 +268,24 @@ func test_grid_layout_calculation(): return # Test tile size calculation - TestHelperClass.assert_true(match3_instance.tile_size > 0, "Tile size is positive") TestHelperClass.assert_true( - match3_instance.tile_size <= 200, "Tile size is reasonable (not too large)" + match3_instance.tile_size > 0, "Tile size is positive" + ) + TestHelperClass.assert_true( + match3_instance.tile_size <= 200, + "Tile size is reasonable (not too large)" ) # Test grid offset - TestHelperClass.assert_not_null(match3_instance.grid_offset, "Grid offset is set") - TestHelperClass.assert_true(match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative") - TestHelperClass.assert_true(match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative") + TestHelperClass.assert_not_null( + match3_instance.grid_offset, "Grid offset is set" + ) + TestHelperClass.assert_true( + match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative" + ) + TestHelperClass.assert_true( + match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative" + ) # Test layout constants TestHelperClass.assert_equal( @@ -242,7 +297,9 @@ func test_grid_layout_calculation(): TestHelperClass.assert_equal( 50.0, match3_instance.GRID_LEFT_MARGIN, "Grid left margin constant" ) - TestHelperClass.assert_equal(50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant") + TestHelperClass.assert_equal( + 50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant" + ) func test_state_management(): @@ -253,23 +310,30 @@ func test_state_management(): # Test GameState enum exists and has expected values var game_state_class = match3_instance.get_script().get_global_class() - TestHelperClass.assert_true("GameState" in match3_instance, "GameState enum accessible") + TestHelperClass.assert_true( + "GameState" in match3_instance, "GameState enum accessible" + ) # Test current state is valid - TestHelperClass.assert_not_null(match3_instance.current_state, "Current state is set") + TestHelperClass.assert_not_null( + match3_instance.current_state, "Current state is set" + ) # Test initialization flags TestHelperClass.assert_true( "grid_initialized" in match3_instance, "Grid initialized flag exists" ) - TestHelperClass.assert_true(match3_instance.grid_initialized, "Grid is marked as initialized") + TestHelperClass.assert_true( + match3_instance.grid_initialized, "Grid is marked as initialized" + ) # Test instance ID for debugging TestHelperClass.assert_true( "instance_id" in match3_instance, "Instance ID exists for debugging" ) TestHelperClass.assert_true( - match3_instance.instance_id.begins_with("Match3_"), "Instance ID has correct format" + match3_instance.instance_id.begins_with("Match3_"), + "Instance ID has correct format" ) @@ -281,13 +345,16 @@ func test_match_detection(): # Test match detection methods exist and can be called safely TestHelperClass.assert_true( - match3_instance.has_method("_has_match_at"), "_has_match_at method exists" + match3_instance.has_method("_has_match_at"), + "_has_match_at method exists" ) TestHelperClass.assert_true( - match3_instance.has_method("_check_for_matches"), "_check_for_matches method exists" + match3_instance.has_method("_check_for_matches"), + "_check_for_matches method exists" ) TestHelperClass.assert_true( - match3_instance.has_method("_get_match_line"), "_get_match_line method exists" + match3_instance.has_method("_get_match_line"), + "_get_match_line method exists" ) # Test boundary checking with invalid positions @@ -310,7 +377,10 @@ func test_match_detection(): ) TestHelperClass.assert_true( is_invalid, - "Invalid position (%d,%d) is correctly identified as invalid" % [pos.x, pos.y] + ( + "Invalid position (%d,%d) is correctly identified as invalid" + % [pos.x, pos.y] + ) ) # Test valid positions through public interface @@ -324,7 +394,8 @@ func test_match_detection(): and pos.y < match3_instance.GRID_SIZE.y ) TestHelperClass.assert_true( - is_valid, "Valid position (%d,%d) is within grid bounds" % [x, y] + is_valid, + "Valid position (%d,%d) is within grid bounds" % [x, y] ) @@ -339,12 +410,14 @@ func test_scoring_system(): # Test that the match3 instance can handle scoring (indirectly through clearing matches) TestHelperClass.assert_true( - match3_instance.has_method("_clear_matches"), "Scoring system method exists" + match3_instance.has_method("_clear_matches"), + "Scoring system method exists" ) # Test that score_changed signal exists TestHelperClass.assert_true( - match3_instance.has_signal("score_changed"), "Score changed signal exists" + match3_instance.has_signal("score_changed"), + "Score changed signal exists" ) # Test scoring formula logic (based on the documented formula) @@ -360,7 +433,9 @@ func test_scoring_system(): calculated_score = match_size + max(0, match_size - 2) TestHelperClass.assert_equal( - expected_score, calculated_score, "Scoring formula correct for %d gems" % match_size + expected_score, + calculated_score, + "Scoring formula correct for %d gems" % match_size ) @@ -375,22 +450,26 @@ func test_input_validation(): match3_instance.cursor_position, "Cursor position is initialized" ) TestHelperClass.assert_true( - match3_instance.cursor_position is Vector2i, "Cursor position is Vector2i type" + match3_instance.cursor_position is Vector2i, + "Cursor position is Vector2i type" ) # Test keyboard navigation flag TestHelperClass.assert_true( - "keyboard_navigation_enabled" in match3_instance, "Keyboard navigation flag exists" + "keyboard_navigation_enabled" in match3_instance, + "Keyboard navigation flag exists" ) TestHelperClass.assert_true( - match3_instance.keyboard_navigation_enabled is bool, "Keyboard navigation flag is boolean" + match3_instance.keyboard_navigation_enabled is bool, + "Keyboard navigation flag is boolean" ) # Test selected tile safety # selected_tile can be null initially, which is valid if match3_instance.selected_tile: TestHelperClass.assert_true( - is_instance_valid(match3_instance.selected_tile), "Selected tile is valid if not null" + is_instance_valid(match3_instance.selected_tile), + "Selected tile is valid if not null" ) @@ -412,20 +491,25 @@ func test_memory_safety(): var tile = match3_instance.grid[y][x] if tile: TestHelperClass.assert_true( - is_instance_valid(tile), "Grid tile at (%d,%d) is valid instance" % [x, y] + is_instance_valid(tile), + "Grid tile at (%d,%d) is valid instance" % [x, y] ) TestHelperClass.assert_true( - tile.get_parent() == match3_instance, "Tile properly parented to Match3" + tile.get_parent() == match3_instance, + "Tile properly parented to Match3" ) # Test position validation TestHelperClass.assert_true( - match3_instance.has_method("_is_valid_grid_position"), "Position validation method exists" + match3_instance.has_method("_is_valid_grid_position"), + "Position validation method exists" ) # Test safe tile access patterns exist # The Match3 code uses comprehensive bounds checking and null validation - TestHelperClass.assert_true(true, "Memory safety patterns implemented in Match3 code") + TestHelperClass.assert_true( + true, "Memory safety patterns implemented in Match3 code" + ) func test_performance_requirements(): @@ -449,13 +533,16 @@ func test_performance_requirements(): # Test timing constants are reasonable for 60fps gameplay TestHelperClass.assert_true( - match3_instance.CASCADE_WAIT_TIME >= 0.05, "Cascade wait time allows for smooth animation" + match3_instance.CASCADE_WAIT_TIME >= 0.05, + "Cascade wait time allows for smooth animation" ) TestHelperClass.assert_true( - match3_instance.SWAP_ANIMATION_TIME <= 0.5, "Swap animation time is responsive" + match3_instance.SWAP_ANIMATION_TIME <= 0.5, + "Swap animation time is responsive" ) TestHelperClass.assert_true( - match3_instance.TILE_DROP_WAIT_TIME <= 0.3, "Tile drop wait time is responsive" + match3_instance.TILE_DROP_WAIT_TIME <= 0.3, + "Tile drop wait time is responsive" ) # Test grid initialization performance diff --git a/tests/TestMigrationCompatibility.gd b/tests/TestMigrationCompatibility.gd index 46d2c3c..9824666 100644 --- a/tests/TestMigrationCompatibility.gd +++ b/tests/TestMigrationCompatibility.gd @@ -58,7 +58,9 @@ func test_migration_compatibility(): # The checksums should be different (old system broken) TestHelperClass.assert_not_equal( - old_checksum, new_checksum, "Old and new checksum formats should be different" + old_checksum, + new_checksum, + "Old and new checksum formats should be different" ) print("Old checksum: %s" % old_checksum) print("New checksum: %s" % new_checksum) @@ -85,9 +87,13 @@ func test_migration_compatibility(): print("Consistent checksum: %s" % first_checksum) TestHelperClass.print_step("Migration Strategy Verification") - TestHelperClass.assert_true(true, "Version-based checksum handling implemented") + TestHelperClass.assert_true( + true, "Version-based checksum handling implemented" + ) print("✓ Files without _checksum: Allow (backward compatibility)") - print("✓ Files with version < current: Recalculate checksum after migration") + print( + "✓ Files with version < current: Recalculate checksum after migration" + ) print("✓ Files with current version: Use new checksum validation") diff --git a/tests/TestSceneValidation.gd b/tests/TestSceneValidation.gd index 9cdb649..f529c80 100644 --- a/tests/TestSceneValidation.gd +++ b/tests/TestSceneValidation.gd @@ -46,7 +46,9 @@ func test_scene_discovery(): for directory in scene_directories: discover_scenes_in_directory(directory) - TestHelperClass.assert_true(discovered_scenes.size() > 0, "Found scenes in project") + TestHelperClass.assert_true( + discovered_scenes.size() > 0, "Found scenes in project" + ) print("Discovered %d scene files" % discovered_scenes.size()) # List discovered scenes for reference @@ -89,23 +91,31 @@ func validate_scene_loading(scene_path: String): # Check if resource exists if not ResourceLoader.exists(scene_path): validation_results[scene_path] = "Resource does not exist" - TestHelperClass.assert_false(true, "%s - Resource does not exist" % scene_name) + TestHelperClass.assert_false( + true, "%s - Resource does not exist" % scene_name + ) return # Attempt to load the scene var packed_scene = load(scene_path) if not packed_scene: validation_results[scene_path] = "Failed to load scene" - TestHelperClass.assert_false(true, "%s - Failed to load scene" % scene_name) + TestHelperClass.assert_false( + true, "%s - Failed to load scene" % scene_name + ) return if not packed_scene is PackedScene: validation_results[scene_path] = "Resource is not a PackedScene" - TestHelperClass.assert_false(true, "%s - Resource is not a PackedScene" % scene_name) + TestHelperClass.assert_false( + true, "%s - Resource is not a PackedScene" % scene_name + ) return validation_results[scene_path] = "Loading successful" - TestHelperClass.assert_true(true, "%s - Scene loads successfully" % scene_name) + TestHelperClass.assert_true( + true, "%s - Scene loads successfully" % scene_name + ) func test_scene_instantiation(): @@ -127,12 +137,15 @@ func validate_scene_instantiation(scene_path: String): var scene_instance = packed_scene.instantiate() if not scene_instance: validation_results[scene_path] = "Failed to instantiate scene" - TestHelperClass.assert_false(true, "%s - Failed to instantiate scene" % scene_name) + TestHelperClass.assert_false( + true, "%s - Failed to instantiate scene" % scene_name + ) return # Validate the instance TestHelperClass.assert_not_null( - scene_instance, "%s - Scene instantiation creates valid node" % scene_name + scene_instance, + "%s - Scene instantiation creates valid node" % scene_name ) # Clean up the instance @@ -160,10 +173,15 @@ func test_critical_scenes(): TestHelperClass.assert_equal( "Full validation successful", status, - "Critical scene %s must pass all validation" % scene_path.get_file() + ( + "Critical scene %s must pass all validation" + % scene_path.get_file() + ) ) else: - TestHelperClass.assert_false(true, "Critical scene missing: %s" % scene_path) + TestHelperClass.assert_false( + true, "Critical scene missing: %s" % scene_path + ) func print_validation_summary(): @@ -175,7 +193,10 @@ func print_validation_summary(): for scene_path in discovered_scenes: var status = validation_results.get(scene_path, "Not tested") - if status == "Full validation successful" or status == "Loading successful": + if ( + status == "Full validation successful" + or status == "Loading successful" + ): successful_scenes += 1 else: failed_scenes += 1 diff --git a/tests/TestSettingsManager.gd b/tests/TestSettingsManager.gd index f772a54..44354f5 100644 --- a/tests/TestSettingsManager.gd +++ b/tests/TestSettingsManager.gd @@ -64,23 +64,35 @@ func test_basic_functionality(): # Test that SettingsManager has expected methods var expected_methods = [ - "get_setting", "set_setting", "save_settings", "load_settings", "reset_settings_to_defaults" + "get_setting", + "set_setting", + "save_settings", + "load_settings", + "reset_settings_to_defaults" ] TestHelperClass.assert_has_methods( settings_manager, expected_methods, "SettingsManager methods" ) # Test default settings structure - var expected_defaults = ["master_volume", "music_volume", "sfx_volume", "language"] + var expected_defaults = [ + "master_volume", "music_volume", "sfx_volume", "language" + ] for key in expected_defaults: TestHelperClass.assert_has_key( - settings_manager.default_settings, key, "Default setting key: " + key + settings_manager.default_settings, + key, + "Default setting key: " + key ) # Test getting settings var master_volume = settings_manager.get_setting("master_volume") - TestHelperClass.assert_not_null(master_volume, "Can get master_volume setting") - TestHelperClass.assert_true(master_volume is float, "master_volume is float type") + TestHelperClass.assert_not_null( + master_volume, "Can get master_volume setting" + ) + TestHelperClass.assert_true( + master_volume is float, "master_volume is float type" + ) func test_input_validation_security(): @@ -88,38 +100,56 @@ func test_input_validation_security(): # Test NaN validation var nan_result = settings_manager.set_setting("master_volume", NAN) - TestHelperClass.assert_false(nan_result, "NaN values rejected for volume settings") + TestHelperClass.assert_false( + nan_result, "NaN values rejected for volume settings" + ) # Test Infinity validation var inf_result = settings_manager.set_setting("master_volume", INF) - TestHelperClass.assert_false(inf_result, "Infinity values rejected for volume settings") + TestHelperClass.assert_false( + inf_result, "Infinity values rejected for volume settings" + ) # Test negative infinity validation var neg_inf_result = settings_manager.set_setting("master_volume", -INF) - TestHelperClass.assert_false(neg_inf_result, "Negative infinity values rejected") + TestHelperClass.assert_false( + neg_inf_result, "Negative infinity values rejected" + ) # Test range validation for volumes var negative_volume = settings_manager.set_setting("master_volume", -0.5) - TestHelperClass.assert_false(negative_volume, "Negative volume values rejected") + TestHelperClass.assert_false( + negative_volume, "Negative volume values rejected" + ) var excessive_volume = settings_manager.set_setting("master_volume", 1.5) - TestHelperClass.assert_false(excessive_volume, "Volume values > 1.0 rejected") + TestHelperClass.assert_false( + excessive_volume, "Volume values > 1.0 rejected" + ) # Test valid volume range var valid_volume = settings_manager.set_setting("master_volume", 0.5) TestHelperClass.assert_true(valid_volume, "Valid volume values accepted") TestHelperClass.assert_equal( - 0.5, settings_manager.get_setting("master_volume"), "Volume value set correctly" + 0.5, + settings_manager.get_setting("master_volume"), + "Volume value set correctly" ) # Test string length validation for language var long_language = "a".repeat(20) # Exceeds MAX_SETTING_STRING_LENGTH - var long_lang_result = settings_manager.set_setting("language", long_language) - TestHelperClass.assert_false(long_lang_result, "Excessively long language codes rejected") + var long_lang_result = settings_manager.set_setting( + "language", long_language + ) + TestHelperClass.assert_false( + long_lang_result, "Excessively long language codes rejected" + ) # Test invalid characters in language code var invalid_chars = settings_manager.set_setting("language", "en