| name | gentleman-trainer |
| description | Vim Trainer RPG system patterns for Gentleman.Dots. Trigger: When editing files in installer/internal/tui/trainer/, adding exercises, modules, or game mechanics. |
| license | Apache-2.0 |
| metadata | [object Object] |
When to Use
Use this skill when:
- Adding new Vim training modules
- Creating exercises or boss fights
- Modifying progression/unlock system
- Working on the Vim command simulator
- Adding practice mode features
Critical Patterns
Pattern 1: ModuleID Constants
All modules MUST be defined as ModuleID constants in types.go:
type ModuleID string
const (
ModuleHorizontal ModuleID = "horizontal"
ModuleVertical ModuleID = "vertical"
ModuleTextObjects ModuleID = "textobjects"
ModuleChangeRepeat ModuleID = "cgn"
ModuleSubstitution ModuleID = "substitution"
ModuleRegex ModuleID = "regex"
ModuleMacros ModuleID = "macros"
// Add new modules here
)
Pattern 2: Exercise Structure
Every exercise follows this structure:
type Exercise struct {
ID string // "horizontal_001"
Module ModuleID // Parent module
Level int // 1-10 difficulty
Type ExerciseType // lesson, practice, boss
Code []string // Lines of code shown
CursorPos Position // Initial cursor
CursorTarget *Position // Target position (movement exercises)
Mission string // What user must do
Solutions []string // ALL valid solutions
Optimal string // Best/shortest solution
Hint string // Help text
Explanation string // Post-answer teaching
TimeoutSecs int // Before showing solution
Points int // Base score
}
Pattern 3: Module Unlock Order
Modules unlock sequentially - user must defeat boss to unlock next:
var moduleUnlockOrder = []ModuleID{
ModuleHorizontal, // Always unlocked
ModuleVertical, // After horizontal boss
ModuleTextObjects, // After vertical boss
ModuleChangeRepeat, // After textobjects boss
// ... etc
}
Pattern 4: Progression Flow
Lessons (sequential) → Practice (80% accuracy) → Boss Fight → Next Module
Decision Tree
Adding new module?
├── Add ModuleID constant in types.go
├── Add to moduleUnlockOrder slice
├── Add ModuleInfo in GetAllModules()
├── Create exercises_{module}.go file
├── Implement GetLessons(moduleID)
├── Implement GetBoss(moduleID)
└── Add practice exercises
Adding exercises?
├── Create Exercise with unique ID format: "{module}_{number}"
├── Provide multiple Solutions (all valid answers)
├── Set Optimal to shortest/best solution
├── Include Hint for learning
└── Add Explanation for post-answer
Adding boss fight?
├── Create BossExercise in exercises_{module}.go
├── Add 5-7 BossSteps (exercise chain)
├── Set Lives (usually 3)
├── Include variety of module skills
└── Return from GetBoss(moduleID)
Code Examples
Example 1: Creating a Module's Exercises File
// exercises_newmodule.go
package trainer
// NewModule lessons
func getNewModuleLessons() []Exercise {
return []Exercise{
{
ID: "newmodule_001",
Module: ModuleNewModule,
Level: 1,
Type: ExerciseLesson,
Code: []string{"function example() {", " return true;", "}"},
CursorPos: Position{Line: 0, Col: 0},
Mission: "Use 'xx' to delete two characters",
Solutions: []string{"xx", "2x", "dl dl"},
Optimal: "xx",
Hint: "x deletes character under cursor",
Explanation: "x is Vim's character delete. 2x or xx deletes two.",
Points: 10,
},
// ... more exercises
}
}
Example 2: Registering Module in GetAllModules
func GetAllModules() []ModuleInfo {
return []ModuleInfo{
// ... existing modules
{
ID: ModuleNewModule,
Name: "New Module",
Icon: "🆕",
Description: "Commands: xx, yy, zz",
BossName: "The New Boss",
},
}
}
Example 3: Boss Fight Structure
func getNewModuleBoss() *BossExercise {
return &BossExercise{
ID: "newmodule_boss",
Module: ModuleNewModule,
Name: "The New Boss",
Lives: 3,
Steps: []BossStep{
{
Exercise: Exercise{
ID: "newmodule_boss_1",
Module: ModuleNewModule,
Code: []string{"challenge code here"},
CursorPos: Position{Line: 0, Col: 0},
Mission: "First boss challenge",
Solutions: []string{"w", "W"},
Optimal: "w",
},
TimeLimit: 10,
},
// ... more steps (5-7 total)
},
}
}
Example 4: Exercise Validation
// Validation checks if answer is in Solutions
func ValidateAnswer(exercise *Exercise, answer string) bool {
answer = strings.TrimSpace(answer)
for _, solution := range exercise.Solutions {
if answer == solution {
return true
}
}
// Also check via simulator for creative solutions
return validateViaSimulator(exercise, answer)
}
Exercise Guidelines
Good Exercise Design
- Clear Mission: User knows exactly what to do
- Multiple Solutions: Accept all valid Vim ways
- Optimal Marked: Teach the best approach
- Progressive Difficulty: Level 1-10 within module
- Real Code: Use realistic code snippets
Solutions Array Rules
// GOOD: Accept all valid variations
Solutions: []string{"w", "W", "e", "E", "f "},
// BAD: Only accept one way
Solutions: []string{"w"},
Exercise ID Format
{module}_{number} → "horizontal_001"
{module}_boss_{step} → "horizontal_boss_1"
Commands
cd installer && go test ./internal/tui/trainer/... # Run all trainer tests
cd installer && go test -run TestExercise # Test exercises
cd installer && go test -run TestSimulator # Test Vim simulator
cd installer && go test -run TestProgression # Test unlock system
Resources
- Types: See
installer/internal/tui/trainer/types.gofor data structures - Exercises: See
installer/internal/tui/trainer/exercises_*.gofor patterns - Simulator: See
installer/internal/tui/trainer/simulator.gofor Vim emulation - Validation: See
installer/internal/tui/trainer/validation.gofor answer checking - Stats: See
installer/internal/tui/trainer/stats.gofor persistence