
Godot Genre Puzzle
- 151 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-puzzle for development tasks
About
godot-genre-puzzle: A skill for development. This provides functionality for development workflows.
- godot-genre-puzzle
Godot Genre Puzzle by the numbers
- 151 all-time installs (skills.sh)
- +8 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,468 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-puzzleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 151 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-puzzle for development tasks
Files
Genre: Puzzle
Expert blueprint for puzzle games emphasizing clarity, experimentation, and "Aha!" moments.
NEVER Do (Expert Anti-Patterns)
Design & Player Experience
- NEVER punish experimentation; strictly provide Undo/Reset functionality to allow risk-free hypothesis testing.
- NEVER require pixel-perfect input for logic puzzles; strictly use Grid Snapping or large, forgiving hitboxes.
- NEVER allow undetected Soft-Locks (unsolvable states); strictly notify the player or provide immediate backtracking.
- NEVER hide the rules of the world; strictly ensure visual feedback is instant and unambiguous (e.g., powered wires must glow).
- NEVER skip the Non-Verbal Tutorial phase; strictly introduce mechanics in isolation before combining them.
Grid Logic & State
- NEVER use floating-point numbers (
Vector2) for grid coordinates; strictly use Vector2i to prevent precision drift. - NEVER use
_process()for grid-state or win-condition validation; strictly trigger checks only when a piece moves. - NEVER rely on the
SceneTreestructure as the source of truth; strictly maintain grid data in a separate script/dictionary. - NEVER modify a Dictionary or Array size while iterating over it; strictly use a copy or a separate queue for modifications.
- NEVER calculate heavy recursive solvers in
_process(); strictly cache results or use threaded workers for solve-checks. - NEVER ignore diagonal rules in pathfinding; strictly configure
AStarGrid2D.diagonal_modecorrectly.
Architecture & Performance
- NEVER program custom command history queues manually; strictly use Godot's built-in UndoRedo system for reliability.
- NEVER intermingle "do" and "undo" logic in the same function; strictly maintain separation for predictable rollbacks.
- NEVER use exact floating-point equality (==); strictly use
is_equal_approx()for spatial constraints. - NEVER use
load()for resetting large rooms dynamically; strictly useResourceLoader.load_threaded_request(). - NEVER leave Tween objects unreferenced; strictly kill active tweens before starting new movement on the same object.
---
🛠 Expert Components (scripts/)
Original Expert Patterns
- command_undo_redo.gd - Professional-grade Command Pattern for non-destructive state reversal and experimentation.
- grid_manager.gd - Decoupled grid logic data structure for raycast-free move validation (Sokoban/Match-3).
Modular Components
- puzzle_pathfinder.gd - AStarGrid2D configuration for optimized pathfinding on 2D grids.
- puzzle_history.gd - UndoRedo system implementation using the Action Command pattern.
- puzzle_saver.gd - JSON-based serialization for saving/restoring complex puzzle states.
- shuffle_bag.gd - Non-repeating randomizer for fair distribution of puzzle elements.
- perspective_overlay.gd - 3D-to-2D projection bridge for world-space puzzle mechanics.
- tile_animator.gd - Safe tween-based movement system using Callables for logic sync.
- match_three_logic.gd - Recursive flood-fill and match detection logic.
- grid_input_manager.gd - Device-agnostic input routing for grid interaction.
- sleepy_block.gd - Physics object stabilizer to prevent unintended solver jitter.
- puzzle_validator.gd - Array reduction component for evaluating complex win conditions.
---
Core Loop
1. Observation: Player assesses the level layout and mechanics. 2. Experimentation: Player interacts with elements (push, pull, toggle). 3. Feedback: Game reacts (door opens, laser blocked). 4. Epiphany: Player understands the logic ("Aha!" moment). 5. Execution: Player executes the solution to advance.
Skill Chain
| Phase | Skills | Purpose |
|---|---|---|
| 1. Interaction | godot-input-handling, raycasting | Clicking, dragging, grid movement |
| 2. Logic | command-pattern, state-management | Undo/Redo, tracking level state |
| 3. Feedback | godot-tweening, juice | Visual confirmation of valid moves |
| 4. Progression | godot-save-load-systems, level-design | Unlocking levels, tracking stars/score |
| 5. Polish | ui-minimalism | Non-intrusive HUD |
Architecture Overview
1. Command Pattern (Undo System)
Essential for puzzle games. Never punish testing.
# command.gd
class_name Command extends RefCounted
func execute() -> void: pass
func undo() -> void: pass
# level_manager.gd
var history: Array[Command] = []
var history_index: int = -1
func commit_command(cmd: Command) -> void:
# Clear redo history if diverging
if history_index < history.size() - 1:
history = history.slice(0, history_index + 1)
cmd.execute()
history.append(cmd)
history_index += 1
func undo() -> void:
if history_index >= 0:
history[history_index].undo()
history_index -= 12. Grid System (TileMap vs Custom)
For grid-based puzzles (Sokoban), a custom data structure is often better than just reading physics.
# grid_manager.gd
var grid_size: Vector2i = Vector2i(16, 16)
var objects: Dictionary = {} # Vector2i -> Node
func move_object(obj: Node, direction: Vector2i) -> bool:
var start_pos = grid_pos(obj.position)
var target_pos = start_pos + direction
if is_wall(target_pos):
return false
if objects.has(target_pos):
# Handle pushing logic here
return false
# Execute move
objects.erase(start_pos)
objects[target_pos] = obj
tween_movement(obj, target_pos)
return trueKey Mechanics Implementation
Win Condition Checking
Check victory state after every move.
func check_win_condition() -> void:
for target in targets:
if not is_satisfied(target):
return
level_complete.emit()
save_progress()Non-Verbal Tutorials
Teach mechanics through level design, not text. 1. Isolation: Level 1 introduces only the new mechanic in a safe room. 2. Reinforcement: Level 2 requires using it to solve a trivial problem. 3. Combination: Level 3 combines it with previous mechanics.
Common Pitfalls
1. Strictness: Requiring pixel-perfect input for logic puzzles. Fix: Use grid snapping or forgiving hitboxes. 2. Dead Ends: Allowing the player to get into an unsolvable state without realizing it. Fix: Auto-detect failure or provide a prominent "Reset" button. 3. Obscurity: Hiding the rules. Fix: Visual feedback must be instant and clear (e.g., a wire lights up when connected).
Godot-Specific Tips
- Tweens: Use
create_tween()for all grid movements. It feels much better than instant snapping. - Custom Resources: Store level data (layout, starting positions) in
.tresfiles for easy editing in the Inspector. - Signals: Use signals like
state_changedto update UI/Visuals decoupled from the logic.
---
🚀 Elite Technical Implementations (Batch 09)
1. Level-Editor Serialization Pattern
For puzzle games with custom editors, avoid using .tscn at runtime. Instead, use FileAccess and JSON to serialize grid data into compact, human-readable files in the user:// directory.
class_name LevelSerializer extends Node
const LEVEL_DIR := "user://levels/"
## Serializes the grid state into a JSON file.
static func save_level(level_name: String, grid_data: Dictionary) -> void:
var path := LEVEL_DIR + level_name + ".json"
var file := FileAccess.open(path, FileAccess.WRITE)
if file:
file.store_string(JSON.stringify(grid_data, "\t"))
file.close()
print("Level saved successfully!")
## Deserializes a JSON file back into a Dictionary.
static func load_level(level_name: String) -> Dictionary:
var path := LEVEL_DIR + level_name + ".json"
var file := FileAccess.open(path, FileAccess.READ)
if file:
var json_string := file.get_as_text()
var parsed_data = JSON.parse_string(json_string)
if parsed_data is Dictionary:
return parsed_data as Dictionary
return {}2. Hint-Systems (A* Solvers)
Use AStarGrid2D to provide logical hints. It is optimized for uniform grids and supports Jump Point Search (JPS) via jumping_enabled to drastically speed up pathfinding on large puzzle layouts.
class_name PuzzleHintSystem extends Node
var _astar_grid: AStarGrid2D
func _ready() -> void:
_astar_grid = AStarGrid2D.new()
_astar_grid.region = Rect2i(0, 0, 32, 32)
_astar_grid.cell_size = Vector2(1, 1)
_astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
_astar_grid.jumping_enabled = true
_astar_grid.update()
## Queries the solver for the next logical step.
func get_next_hint_step(player_pos: Vector2i, goal_pos: Vector2i) -> Vector2i:
var path := _astar_grid.get_id_path(player_pos, goal_pos)
if path.size() > 1:
return path[1] # Return next step in sequence
return player_pos3. State-Snapshot Pattern (Instant Resets)
Avoid reload_current_scene() for resets to prevent frame drops and UI flickering. Instead, capture the initial positions of all pieces into a Dictionary and restore them instantly.
class_name StateSnapshotManager extends Node
signal state_restored()
var _initial_state_snapshot: Dictionary[NodePath, Vector2] = {}
## Capture initial positions of all puzzle pieces.
func capture_initial_state() -> void:
var pieces = get_tree().get_nodes_in_group("puzzle_pieces")
for piece in pieces:
if piece is Node2D:
_initial_state_snapshot[piece.get_path()] = piece.global_position
## Instantly restore pieces to their original state.
func reset_to_snapshot() -> void:
for node_path in _initial_state_snapshot.keys():
var piece = get_node_or_null(node_path) as Node2D
if piece:
piece.global_position = _initial_state_snapshot[node_path]
state_restored.emit()- Master Skill: godot-master
# skills/genre-puzzle/scripts/command_undo_redo.gd
class_name CommandUndoRedo
extends Node
## Command Pattern Undo/Redo System (Expert Pattern)
## Manages a history of commands to allow unlimited undo/redo functionality.
signal state_changed(can_undo: bool, can_redo: bool)
var history: Array[Command] = []
var history_index: int = -1
const MAX_HISTORY: int = 100
# Abstract Command Class
class Command extends RefCounted:
func execute() -> void: pass
func undo() -> void: pass
func commit_command(cmd: Command) -> void:
# If we are in the middle of history, clear the future
if history_index < history.size() - 1:
history = history.slice(0, history_index + 1)
cmd.execute()
history.append(cmd)
if history.size() > MAX_HISTORY:
history.pop_front()
else:
history_index += 1
_emit_state()
func undo() -> void:
if history_index >= 0:
history[history_index].undo()
history_index -= 1
_emit_state()
func redo() -> void:
if history_index < history.size() - 1:
history_index += 1
history[history_index].execute()
_emit_state()
func clear_history() -> void:
history.clear()
history_index = -1
_emit_state()
func _emit_state() -> void:
state_changed.emit(history_index >= 0, history_index < history.size() - 1)
## EXPERT USAGE:
## Define subclasses of Command (e.g. MoveCommand).
## Instantiate and pass to commit_command().
# grid_input_manager.gd
extends Node
class_name GridInputManager
# Routing Unhandled Grid Input
# Intercepts clicks strictly when the GUI has not consumed them.
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
var grid_pos = _screen_to_grid(event.position)
_handle_grid_click(grid_pos)
# Pattern: Consume immediately to stop propagation.
get_viewport().set_input_as_handled()
func _screen_to_grid(_pos: Vector2) -> Vector2i:
return Vector2i.ZERO
func _handle_grid_click(_grid_pos: Vector2i) -> void:
pass
# skills/genre-puzzle/scripts/grid_manager.gd
extends Node2D
## Grid Manager (Expert Pattern)
## Manages grid-based movement and logic (Sokoban style).
## Uses Tweens for smooth movement between grid cells.
class_name GridManager
signal object_moved(obj: Node2D, from: Vector2i, to: Vector2i)
@export var grid_size: Vector2i = Vector2i(64, 64)
@export var movement_duration: float = 0.2
var grid_objects: Dictionary = {} # Vector2i: Node2D
func register_object(obj: Node2D, grid_pos: Vector2i) -> void:
grid_objects[grid_pos] = obj
obj.position = _grid_to_world(grid_pos)
func try_move(obj: Node2D, direction: Vector2i) -> bool:
var start_pos = _world_to_grid(obj.position)
var target_pos = start_pos + direction
# Check bounds (optional)
# Check obstacles
if grid_objects.has(target_pos):
var obstacle = grid_objects[target_pos]
# Push logic?
if obstacle.has_method("is_pushable") and obstacle.is_pushable():
if not try_move(obstacle, direction):
return false # Chain blocked
else:
return false # Blocked by static object
# Execute Move
grid_objects.erase(start_pos)
grid_objects[target_pos] = obj
var tween = create_tween()
tween.tween_property(obj, "position", _grid_to_world(target_pos), movement_duration)
object_moved.emit(obj, start_pos, target_pos)
return true
func _grid_to_world(grid_pos: Vector2i) -> Vector2:
return Vector2(grid_pos) * Vector2(grid_size) + Vector2(grid_size) / 2.0
func _world_to_grid(world_pos: Vector2) -> Vector2i:
return Vector2i((world_pos - Vector2(grid_size)/2.0) / Vector2(grid_size))
## EXPERT USAGE:
## Call register_object() in _ready().
## Connect Input to try_move().
extends Node2D
class_name GridTweenMover
## Expert Grid Movement (Godot 4.6).
## Separates logic (Vector2i) from visuals (Tween).
@export var tile_map: TileMapLayer
@export var move_time: float = 0.2
var logical_pos: Vector2i = Vector2i.ZERO
var _is_moving: bool = false
func move(direction: Vector2i) -> void:
if _is_moving: return
var target = logical_pos + direction
if _can_move(target):
_execute_move(target)
func _can_move(target: Vector2i) -> bool:
var data = tile_map.get_cell_tile_data(target)
return data != null and data.get_custom_data("walkable")
func _execute_move(target: Vector2i) -> void:
_is_moving = true
logical_pos = target # Update logic instantly
var world_pos = tile_map.map_to_local(logical_pos)
var tween = get_tree().create_tween().set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "position", world_pos, move_time)
tween.finished.connect(func(): _is_moving = false)
## [SKILL NOTICE]: Resolve logical state (Vector2i) IMMEDIATELY upon
## valid input. Use Tweens ONLY for visual representation to avoid race conditions.
# match_three_logic.gd
extends Node
class_name MatchThreeLogic
# Dictionary-Based Grid Flood Fill
# Evaluates spatial logic using a strict Dictionary for board representation.
var board: Dictionary = {} # Vector2i -> gem_id
func check_match_at(pos: Vector2i, gem_id: int) -> void:
# Pattern: Always verify coordinate existence before key access.
if pos in board:
if board[pos] == gem_id:
# Handle recursion or removal queue here.
board.erase(pos)
_notify_match(pos)
func _notify_match(_pos: Vector2i) -> void:
pass
# perspective_overlay.gd
extends Node3D
class_name PerspectiveOverlay
# 3D to 2D Perspective Projection
# Projects 3D points onto 2D viewport for UI alignment.
@export var camera: Camera3D
@export var ui_element: Control
func update_ui_position(world_point: Vector3) -> void:
if not camera or not ui_element: return
# Pattern: Use is_position_behind to hide elements behind the lens.
if not camera.is_position_behind(world_point):
var screen_pos := camera.unproject_position(world_point)
ui_element.position = screen_pos
ui_element.show()
else:
ui_element.hide()
# puzzle_history.gd
extends Node
class_name PuzzleHistory
# Action Command Pattern (Undo/Redo System)
# Robust undo history keeping "do" and "undo" methods strictly separated.
var undo_redo := UndoRedo.new()
func execute_move(node: Node2D, target_position: Vector2) -> void:
undo_redo.create_action("Move Piece")
# Expert Pattern: Group 'do' on one side and 'undo' on the other.
undo_redo.add_do_property(node, "position", target_position)
undo_redo.add_undo_property(node, "position", node.position)
undo_redo.commit_action()
func undo_last_move() -> void:
if undo_redo.has_undo():
undo_redo.undo()
func redo_last_move() -> void:
if undo_redo.has_redo():
undo_redo.redo()
# puzzle_pathfinder.gd
extends Node
class_name PuzzlePathfinder
# High-Performance Grid Pathfinding (AStarGrid2D)
# Specialized grid for puzzles, avoiding manual point connections.
var astar_grid := AStarGrid2D.new()
func setup_grid(grid_rect: Rect2i, cell_dimensions: Vector2) -> void:
astar_grid.region = grid_rect
astar_grid.cell_size = cell_dimensions
# Pattern: Update is MANDATORY after modifying parameters.
astar_grid.update()
func get_grid_path(start: Vector2i, end: Vector2i) -> Array[Vector2i]:
# Returns an optimized array of Vector2i grid coordinates.
return astar_grid.get_id_path(start, end)
# puzzle_saver.gd
extends Node
class_name PuzzleSaver
# Saving Persistent Puzzle State
# Serializes object properties safely into the user:// directory.
func save_game(save_name: String = "puzzle_save.json") -> void:
var save_dict := {}
var save_nodes := get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
save_dict[node.name] = {
# Pattern: Manually split complex types (Vector2) for JSON.
"pos_x": node.position.x,
"pos_y": node.position.y,
"state": node.get("puzzle_state") if "puzzle_state" in node else 0
}
var path := "user://" + save_name
var file := FileAccess.open(path, FileAccess.WRITE)
if file:
file.store_line(JSON.stringify(save_dict))
extends Node
class_name PuzzleStateValidator
## Expert Puzzle Validation (Godot 4.6).
## Uses recursive dictionary comparisons for win conditions.
signal solved
@export var target_state: Dictionary = {}
var current_state: Dictionary = {}
func update_state(piece_id: String, state: Variant) -> void:
current_state[piece_id] = state
_check_win()
func _check_win() -> void:
# Expert Pattern: Compare dictionaries directly
if current_state.recursive_equal(target_state):
solved.emit()
print("Puzzle Solved!")
## [SKILL NOTICE]: Use 'Dictionary.recursive_equal()' for multi-layered
## win conditions. It is significantly faster than manual nested loops.
extends Node
class_name PuzzleUndoManager
## Expert Undo/Redo (Godot 4.6).
## Leverages the built-in UndoRedo class for command tracking.
var history := UndoRedo.new()
func record_move(piece: Node2D, from: Vector2i, to: Vector2i) -> void:
history.create_action("Move Piece")
history.add_do_method(piece.move_to.bind(to))
history.add_undo_method(piece.move_to.bind(from))
history.commit_action()
func undo() -> void:
if history.has_undo(): history.undo()
func redo() -> void:
if history.has_redo(): history.redo()
## [SKILL NOTICE]: Do not build custom stacks. Use Godot's 'UndoRedo'
## object to handle the Command pattern and memory limits automatically.
# puzzle_validator.gd
extends Node
class_name PuzzleValidator
# Array Reduction for Victory Conditions
# Validates completion using optimized functional reduction lambdas.
func check_all_resolved(objectives: Array) -> bool:
# Pattern: Use Godot 4's functional reduction for concise win checking.
var completed: int = objectives.reduce(
func(count, next): return count + 1 if next.get("is_resolved") else count, 0
)
return completed == objectives.size()
# shuffle_bag.gd
extends Node
class_name ShuffleBag
# Shuffle Bag for Procedural Generation
# Generates non-repeating random values (e.g., Match-3 block types).
var _items: Array[Variant] = []
var _full_items: Array[Variant] = []
func initialize(items_to_shuffle: Array) -> void:
_full_items = items_to_shuffle.duplicate()
_refill_and_shuffle()
func _refill_and_shuffle() -> void:
_items = _full_items.duplicate()
_items.shuffle()
func get_next() -> Variant:
if _items.is_empty():
# Pattern: Reinitialize when empty to guarantee fair distribution.
_refill_and_shuffle()
return _items.pop_front()
# sleepy_block.gd
extends RigidBody2D
class_name SleepyBlock
# Interactive Physics Puzzle Sleep State
# Interfaces with low-level body state to force sleep on demand.
func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
# Pattern: Optimize interactive puzzles by sleeping when movement is negligible.
if state.get_linear_velocity().length() < 5.0 and state.get_angular_velocity() < 0.1:
state.sleeping = true
# tile_animator.gd
extends Node
class_name TileAnimator
# Safe Tween Callbacks for Move Validation
# Uses First-Class Callables to securely chain animation logic.
func animate_tile_removal(node: Node2D, target_dict: Dictionary) -> void:
var tween := get_tree().create_tween()
tween.tween_property(node, "scale", Vector2.ZERO, 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN)
# Pattern: Use Callable.create for methods of built-in types.
tween.tween_callback(Callable.create(target_dict, "clear"))
tween.tween_callback(node.queue_free)