mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Refactor lockpicking minigame: Extract lock configuration logic into LockConfiguration class
- Created a new LockConfiguration class in lock-configuration.js to encapsulate lock configuration methods. - Removed lock configuration methods from lockpicking-game-phaser.js and replaced them with an instance of LockConfiguration. - Added methods for saving, loading, clearing, and resetting lock configurations in the new class. - Updated lockpicking-game-phaser.js to utilize the new LockConfiguration class for managing lock states. - Introduced scripts for extracting methods and listing JavaScript functions for better code organization and maintainability.
This commit is contained in:
350
FUNCTION_INVENTORY.md
Normal file
350
FUNCTION_INVENTORY.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# Complete Function Inventory - All 78 Functions
|
||||
|
||||
## Summary
|
||||
|
||||
**Total Functions:** 78
|
||||
**File:** `js/minigames/lockpicking/lockpicking-game-phaser.js`
|
||||
**File Size:** 4,669 lines
|
||||
|
||||
---
|
||||
|
||||
## All Functions by Category (Planned Refactoring Phases)
|
||||
|
||||
### Phase 1: Lock Configuration (6 functions)
|
||||
Lines 100-205 | Lock state persistence
|
||||
|
||||
```
|
||||
1. saveLockConfiguration (100-125)
|
||||
2. getLockPinConfiguration (128-141)
|
||||
3. loadLockConfiguration (143-151)
|
||||
4. clearLockConfiguration (153-170)
|
||||
5. clearAllLockConfigurations (172-184)
|
||||
6. resetPinsToOriginalPositions (186-205)
|
||||
```
|
||||
|
||||
### Phase 2: Lock Graphics (3 functions)
|
||||
Lines 465-778 | Visual rendering of lock
|
||||
|
||||
```
|
||||
7. createLockBackground (465-485)
|
||||
8. createTensionWrench (487-621)
|
||||
9. createHookPick (623-778)
|
||||
```
|
||||
|
||||
### Phase 3: Key Data Generator (8 functions)
|
||||
Lines 780-918 | Key creation and calculations
|
||||
|
||||
```
|
||||
10. generateKeyDataFromPins (780-821)
|
||||
11. createKeyFromPinSizes (823-843)
|
||||
12. generateRandomKey (845-858)
|
||||
13. createKeysFromInventory (860-891)
|
||||
14. createKeysForChallenge (893-918)
|
||||
15. startWithKeySelection (920-934)
|
||||
16. createKeySelectionUI (957-1044)
|
||||
17. createKeyVisual (1046-1094)
|
||||
```
|
||||
|
||||
### Phase 4: Pin System (13 functions)
|
||||
Lines 2904-3233 | Pin creation, physics, state
|
||||
|
||||
```
|
||||
18. createPins (2904-3195)
|
||||
19. createShearLine (3197-3233)
|
||||
20. liftPin (3488-3758) [Input handling]
|
||||
21. applyGravity (3760-3857)
|
||||
22. checkAllPinsCorrect (3859-3912)
|
||||
23. checkPinSet (3914-4095)
|
||||
24. shouldPinBind (4097-4108)
|
||||
25. updateBindingPins (4110-4157)
|
||||
26. resetAllPins (4159-4208)
|
||||
27. updatePinHighlighting (2817-2844)
|
||||
28. updatePinVisuals (2846-2902)
|
||||
29. liftCollidedPin (2797-2815)
|
||||
30. checkHookCollisions (2695-2793)
|
||||
```
|
||||
|
||||
### Phase 5: Key Rendering (17 functions)
|
||||
Lines 1176-2298 | Key visual generation and rendering
|
||||
|
||||
```
|
||||
31. createKey (1176-1282)
|
||||
32. drawKeyWithRenderTexture (1284-1332)
|
||||
33. drawKeyBladeAsSolidShape (1334-1441)
|
||||
34. addTriangularSectionToPath (1443-1466)
|
||||
35. addFirstCutPeakToPath (1468-1500)
|
||||
36. addTriangularPeakToPath (1502-1531)
|
||||
37. addPointedTipToPath (1533-1567)
|
||||
38. addRightPointingTriangleToPath (1569-1612)
|
||||
39. drawCircleAsPolygon (1614-1629)
|
||||
40. drawPixelArtCircleToGraphics (1631-1658)
|
||||
41. generateKeyPolygonPoints (2224-2298)
|
||||
42. addTriangularPeakToPoints (2326-2348)
|
||||
43. addPointedTipToPoints (2350-2376)
|
||||
44. getTriangularSectionHeightAtX (2380-2466)
|
||||
45. getTriangularSectionHeightAsKeyMoves (2468-2524)
|
||||
46. getKeySurfaceHeightAtPosition (2565-2581)
|
||||
47. findVerticalIntersection (2300-2324)
|
||||
```
|
||||
|
||||
### Phase 6: Key Selection UI (4 functions)
|
||||
Lines 1098-1174 | Key selection interface
|
||||
|
||||
```
|
||||
48. selectKey (1098-1148)
|
||||
49. showWrongKeyFeedback (1150-1161)
|
||||
50. flashLockRed (1163-1174)
|
||||
51. createKeyBladeCollision (2526-2563)
|
||||
```
|
||||
|
||||
### Phase 7: Input Handlers (4 functions)
|
||||
Lines 3235-3758 | User input and interaction
|
||||
|
||||
```
|
||||
52. setupInputHandlers (3235-3458)
|
||||
53. liftPin (3488-3758) [Already in Pin System]
|
||||
54. updateHookPosition (2601-2662)
|
||||
55. returnHookToStart (2664-2693)
|
||||
```
|
||||
|
||||
### Phase 8: Completion Handler (2 functions)
|
||||
Lines 3859-4212 | Lock picking completion logic
|
||||
|
||||
```
|
||||
56. checkAllPinsCorrect (3859-3912) [Already in Pin System]
|
||||
57. lockPickingSuccess (4214-4465)
|
||||
```
|
||||
|
||||
### Phase 9: UI Elements (6 functions)
|
||||
Lines 207-330 | Buttons, labels, display setup
|
||||
|
||||
```
|
||||
58. init (207-267)
|
||||
59. createLockableItemDisplay (269-330)
|
||||
60. updateFeedback (4210-4212)
|
||||
61. hideLockpickingTools (2583-2599)
|
||||
62. showLockpickingTools (4583-4599)
|
||||
63. setupPhaserGame (332-461)
|
||||
```
|
||||
|
||||
### Phase 10: Mode Switching (2 functions)
|
||||
Lines 4532-4669 | Switch between pick and key mode
|
||||
|
||||
```
|
||||
64. switchToPickMode (4532-4581)
|
||||
65. switchToKeyMode (4601-4669)
|
||||
```
|
||||
|
||||
### Phase 11: Key Insertion & Animation (5 functions)
|
||||
Lines 1662-2133 | Key insertion and movement
|
||||
|
||||
```
|
||||
66. startKeyInsertion (1662-1713)
|
||||
67. updateKeyPosition (1715-1730)
|
||||
68. checkKeyCorrectness (1732-1802)
|
||||
69. snapPinsToExactPositions (1804-1871)
|
||||
70. startKeyRotationAnimationWithChamberHoles (1873-2093)
|
||||
71. liftPinsWithKey (2099-2133)
|
||||
72. updatePinsWithKeyInsertion (2135-2198)
|
||||
73. getKeySurfaceHeightAtPinPosition (2200-2222)
|
||||
```
|
||||
|
||||
### Phase 12: Utilities & Other (7 functions)
|
||||
Lines 4491-4670 | Helper functions and lifecycle
|
||||
|
||||
```
|
||||
74. shuffleArray (4491-4497)
|
||||
75. flashWrenchRed (4499-4530)
|
||||
76. start (4467-4473)
|
||||
77. complete (4475-4481)
|
||||
78. cleanup (4483-4489)
|
||||
79. update (3460-3486) [Main update loop]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Function Coverage Analysis
|
||||
|
||||
### By Phase
|
||||
|
||||
| Phase | Name | Count | Lines | Coverage |
|
||||
|-------|------|-------|-------|----------|
|
||||
| 1 | Lock Configuration | 6 | 106 | 2.3% |
|
||||
| 2 | Lock Graphics | 3 | 314 | 6.7% |
|
||||
| 3 | Key Data Generator | 8 | 139 | 3.0% |
|
||||
| 4 | Pin System | 13 | 329 | 7.0% |
|
||||
| 5 | Key Rendering | 17 | 1,122 | 24% |
|
||||
| 6 | Key Selection UI | 4 | 48 | 1.0% |
|
||||
| 7 | Input Handlers | 4 | 224 | 4.8% |
|
||||
| 8 | Completion Handler | 2 | 252 | 5.4% |
|
||||
| 9 | UI Elements | 6 | 255 | 5.5% |
|
||||
| 10 | Mode Switching | 2 | 138 | 3.0% |
|
||||
| 11 | Key Insertion | 8 | 471 | 10% |
|
||||
| 12 | Utilities | 7 | 207 | 4.4% |
|
||||
| **TOTAL** | | **78** | **4,669** | **100%** |
|
||||
|
||||
---
|
||||
|
||||
## Function Quick Reference
|
||||
|
||||
### All 78 Functions (Alphabetical)
|
||||
|
||||
```
|
||||
1. addFirstCutPeakToPath
|
||||
2. addPointedTipToPath
|
||||
3. addPointedTipToPoints
|
||||
4. addRightPointingTriangleToPath
|
||||
5. addTriangularPeakToPath
|
||||
6. addTriangularPeakToPoints
|
||||
7. addTriangularSectionToPath
|
||||
8. applyGravity
|
||||
9. checkAllPinsCorrect
|
||||
10. checkHookCollisions
|
||||
11. checkKeyCorrectness
|
||||
12. checkPinSet
|
||||
13. cleanup
|
||||
14. clearAllLockConfigurations
|
||||
15. clearLockConfiguration
|
||||
16. complete
|
||||
17. constructor
|
||||
18. createKey
|
||||
19. createKeyBladeCollision
|
||||
20. createKeyFromPinSizes
|
||||
21. createKeySelectionUI
|
||||
22. createKeyVisual
|
||||
23. createKeysForChallenge
|
||||
24. createKeysFromInventory
|
||||
25. createLockableItemDisplay
|
||||
26. createLockBackground
|
||||
27. createPins
|
||||
28. createShearLine
|
||||
29. createTensionWrench
|
||||
30. createHookPick
|
||||
31. drawCircleAsPolygon
|
||||
32. drawKeyBladeAsSolidShape
|
||||
33. drawKeyWithRenderTexture
|
||||
34. drawPixelArtCircleToGraphics
|
||||
35. findVerticalIntersection
|
||||
36. flashLockRed
|
||||
37. flashWrenchRed
|
||||
38. generateKeyDataFromPins
|
||||
39. generateKeyPolygonPoints
|
||||
40. generateRandomKey
|
||||
41. getLockPinConfiguration
|
||||
42. getKeySurfaceHeightAtPinPosition
|
||||
43. getKeySurfaceHeightAtPosition
|
||||
44. getTriangularSectionHeightAsKeyMoves
|
||||
45. getTriangularSectionHeightAtX
|
||||
46. hideLockpickingTools
|
||||
47. init
|
||||
48. liftCollidedPin
|
||||
49. liftPin
|
||||
50. liftPinsWithKey
|
||||
51. loadLockConfiguration
|
||||
52. lockPickingSuccess
|
||||
53. resetAllPins
|
||||
54. resetPinsToOriginalPositions
|
||||
55. returnHookToStart
|
||||
56. saveLockConfiguration
|
||||
57. selectKey
|
||||
58. setupInputHandlers
|
||||
59. setupPhaserGame
|
||||
60. shouldPinBind
|
||||
61. showLockpickingTools
|
||||
62. showWrongKeyFeedback
|
||||
63. shuffleArray
|
||||
64. snapPinsToExactPositions
|
||||
65. start
|
||||
66. startKeyInsertion
|
||||
67. startKeyRotationAnimationWithChamberHoles
|
||||
68. startWithKeySelection
|
||||
69. switchToKeyMode
|
||||
70. switchToPickMode
|
||||
71. update
|
||||
72. updateBindingPins
|
||||
73. updateFeedback
|
||||
74. updateHookPosition
|
||||
75. updateKeyPosition
|
||||
76. updatePinHighlighting
|
||||
77. updatePinVisuals
|
||||
78. updatePinsWithKeyInsertion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Use this to verify nothing is missed in refactoring:
|
||||
|
||||
- [ ] Phase 1: 6 functions accounted for
|
||||
- [ ] Phase 2: 3 functions accounted for
|
||||
- [ ] Phase 3: 8 functions accounted for
|
||||
- [ ] Phase 4: 13 functions accounted for
|
||||
- [ ] Phase 5: 17 functions accounted for
|
||||
- [ ] Phase 6: 4 functions accounted for
|
||||
- [ ] Phase 7: 4 functions accounted for
|
||||
- [ ] Phase 8: 2 functions accounted for
|
||||
- [ ] Phase 9: 6 functions accounted for
|
||||
- [ ] Phase 10: 2 functions accounted for
|
||||
- [ ] Phase 11: 8 functions accounted for
|
||||
- [ ] Phase 12: 7 functions accounted for
|
||||
- [ ] **Total: 78 functions** ✓
|
||||
|
||||
---
|
||||
|
||||
## How to Use This List
|
||||
|
||||
### 1. Verify After Each Phase
|
||||
|
||||
After extracting a phase, verify the functions were moved:
|
||||
|
||||
```bash
|
||||
# After Phase 1 extraction:
|
||||
python3 scripts/list_js_functions.py --file js/minigames/lockpicking/lockpicking-game-phaser.js | grep -E "saveLockConfiguration|loadLockConfiguration|clearLockConfiguration"
|
||||
|
||||
# Should show 0 results if fully extracted
|
||||
```
|
||||
|
||||
### 2. Generate List for Copy-Pasting
|
||||
|
||||
```bash
|
||||
# Get copy-paste friendly list for command line:
|
||||
python3 scripts/list_js_functions.py --file js/minigames/lockpicking/lockpicking-game-phaser.js --format copy-paste
|
||||
```
|
||||
|
||||
### 3. Filter by Phase
|
||||
|
||||
```bash
|
||||
# List functions with keyword:
|
||||
python3 scripts/list_js_functions.py --file js/minigames/lockpicking/lockpicking-game-phaser.js --grep "Key" --format list
|
||||
|
||||
# Result: All functions with "Key" in name
|
||||
```
|
||||
|
||||
### 4. Track Progress
|
||||
|
||||
Before refactoring: **78 functions**
|
||||
After Phase 1: **78 - 6 = 72 remaining in main file**
|
||||
After Phase 2: **72 - 3 = 69 remaining in main file**
|
||||
... and so on
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Constructor:** Should remain in main class (line 5)
|
||||
- **Update method:** Core Phaser method, keep in main class
|
||||
- **Start/Complete/Cleanup:** Lifecycle methods, keep in main class
|
||||
- Some functions (like `liftPin`) have multiple responsibilities and appear in multiple phases
|
||||
- Line numbers are from current state of file (may shift during extraction)
|
||||
|
||||
---
|
||||
|
||||
## Generated With
|
||||
|
||||
`scripts/list_js_functions.py` - Simple script to list all JS functions
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
python3 scripts/list_js_functions.py --file <file.js> --format <table|list|csv|copy-paste>
|
||||
```
|
||||
127
js/minigames/lockpicking/lock-configuration.js
Normal file
127
js/minigames/lockpicking/lock-configuration.js
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
/**
|
||||
* LockConfiguration
|
||||
*
|
||||
* Extracted from lockpicking-game-phaser.js
|
||||
* Instantiate with: new LockConfiguration(this)
|
||||
*
|
||||
* All 'this' references replaced with 'parent' to access parent instance state:
|
||||
* - parent.pins (array of pin objects)
|
||||
* - parent.scene (Phaser scene)
|
||||
* - parent.lockId (lock identifier)
|
||||
* - parent.lockState (lock state object)
|
||||
* etc.
|
||||
*/
|
||||
export class LockConfiguration {
|
||||
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
saveLockConfiguration() {
|
||||
// Save the current lock configuration to global storage and localStorage
|
||||
if (this.parent.pins && this.parent.pins.length > 0) {
|
||||
const pinHeights = this.parent.pins.map(pin => pin.originalHeight);
|
||||
const config = {
|
||||
pinHeights: pinHeights,
|
||||
pinCount: this.parent.pinCount,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Save to memory
|
||||
window.lockConfigurations[this.parent.lockId] = config;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
try {
|
||||
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
|
||||
const parsed = JSON.parse(savedConfigs);
|
||||
parsed[this.parent.lockId] = config;
|
||||
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
|
||||
} catch (error) {
|
||||
console.warn('Failed to save lock configuration to localStorage:', error);
|
||||
}
|
||||
|
||||
console.log(`Saved lock configuration for ${this.parent.lockId}:`, pinHeights);
|
||||
}
|
||||
}
|
||||
|
||||
loadLockConfiguration() {
|
||||
// Load lock configuration from global storage
|
||||
const config = window.lockConfigurations[this.parent.lockId];
|
||||
if (config && config.pinHeights && config.pinHeights.length === this.parent.pinCount) {
|
||||
console.log(`Loaded lock configuration for ${this.parent.lockId}:`, config.pinHeights);
|
||||
return config.pinHeights;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
clearLockConfiguration() {
|
||||
// Clear the lock configuration for this lock
|
||||
if (window.lockConfigurations[this.parent.lockId]) {
|
||||
delete window.lockConfigurations[this.parent.lockId];
|
||||
|
||||
// Also remove from localStorage
|
||||
try {
|
||||
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
|
||||
const parsed = JSON.parse(savedConfigs);
|
||||
delete parsed[this.parent.lockId];
|
||||
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear lock configuration from localStorage:', error);
|
||||
}
|
||||
|
||||
console.log(`Cleared lock configuration for ${this.parent.lockId}`);
|
||||
}
|
||||
}
|
||||
|
||||
getLockPinConfiguration() {
|
||||
if (!this.parent.pins || this.parent.pins.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pinCount: this.parent.pinCount,
|
||||
pinHeights: this.parent.pins.map(pin => pin.originalHeight),
|
||||
pinLengths: this.parent.pins.map(pin => ({
|
||||
keyPinLength: pin.keyPinLength,
|
||||
driverPinLength: pin.driverPinLength
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
clearAllLockConfigurations() {
|
||||
// Clear all lock configurations (useful for testing)
|
||||
window.lockConfigurations = {};
|
||||
|
||||
// Also clear from localStorage
|
||||
try {
|
||||
localStorage.removeItem('lockConfigurations');
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear all lock configurations from localStorage:', error);
|
||||
}
|
||||
|
||||
console.log('Cleared all lock configurations');
|
||||
}
|
||||
|
||||
resetPinsToOriginalPositions() {
|
||||
// Reset all pins to their original positions (before any key insertion)
|
||||
this.parent.pins.forEach(pin => {
|
||||
pin.currentHeight = 0;
|
||||
pin.isSet = false;
|
||||
|
||||
// Clear any highlights
|
||||
if (pin.shearHighlight) {
|
||||
pin.shearHighlight.setVisible(false);
|
||||
}
|
||||
if (pin.setHighlight) {
|
||||
pin.setHighlight.setVisible(false);
|
||||
}
|
||||
|
||||
// Update pin visuals
|
||||
this.parent.updatePinVisuals(pin);
|
||||
});
|
||||
|
||||
console.log('Reset all pins to original positions');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MinigameScene } from '../framework/base-minigame.js';
|
||||
import { LockConfiguration } from './lock-configuration.js';
|
||||
|
||||
// Phaser Lockpicking Minigame Scene implementation
|
||||
export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
@@ -95,115 +96,12 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
|
||||
this.game = null;
|
||||
this.scene = null;
|
||||
}
|
||||
|
||||
saveLockConfiguration() {
|
||||
// Save the current lock configuration to global storage and localStorage
|
||||
if (this.pins && this.pins.length > 0) {
|
||||
const pinHeights = this.pins.map(pin => pin.originalHeight);
|
||||
const config = {
|
||||
pinHeights: pinHeights,
|
||||
pinCount: this.pinCount,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Save to memory
|
||||
window.lockConfigurations[this.lockId] = config;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
try {
|
||||
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
|
||||
const parsed = JSON.parse(savedConfigs);
|
||||
parsed[this.lockId] = config;
|
||||
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
|
||||
} catch (error) {
|
||||
console.warn('Failed to save lock configuration to localStorage:', error);
|
||||
}
|
||||
|
||||
console.log(`Saved lock configuration for ${this.lockId}:`, pinHeights);
|
||||
}
|
||||
|
||||
// Initialize lock configuration module
|
||||
this.lockConfig = new LockConfiguration(this);
|
||||
}
|
||||
|
||||
// Method to get the lock's pin configuration for key generation
|
||||
getLockPinConfiguration() {
|
||||
if (!this.pins || this.pins.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pinCount: this.pinCount,
|
||||
pinHeights: this.pins.map(pin => pin.originalHeight),
|
||||
pinLengths: this.pins.map(pin => ({
|
||||
keyPinLength: pin.keyPinLength,
|
||||
driverPinLength: pin.driverPinLength
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
loadLockConfiguration() {
|
||||
// Load lock configuration from global storage
|
||||
const config = window.lockConfigurations[this.lockId];
|
||||
if (config && config.pinHeights && config.pinHeights.length === this.pinCount) {
|
||||
console.log(`Loaded lock configuration for ${this.lockId}:`, config.pinHeights);
|
||||
return config.pinHeights;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
clearLockConfiguration() {
|
||||
// Clear the lock configuration for this lock
|
||||
if (window.lockConfigurations[this.lockId]) {
|
||||
delete window.lockConfigurations[this.lockId];
|
||||
|
||||
// Also remove from localStorage
|
||||
try {
|
||||
const savedConfigs = localStorage.getItem('lockConfigurations') || '{}';
|
||||
const parsed = JSON.parse(savedConfigs);
|
||||
delete parsed[this.lockId];
|
||||
localStorage.setItem('lockConfigurations', JSON.stringify(parsed));
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear lock configuration from localStorage:', error);
|
||||
}
|
||||
|
||||
console.log(`Cleared lock configuration for ${this.lockId}`);
|
||||
}
|
||||
}
|
||||
|
||||
clearAllLockConfigurations() {
|
||||
// Clear all lock configurations (useful for testing)
|
||||
window.lockConfigurations = {};
|
||||
|
||||
// Also clear from localStorage
|
||||
try {
|
||||
localStorage.removeItem('lockConfigurations');
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear all lock configurations from localStorage:', error);
|
||||
}
|
||||
|
||||
console.log('Cleared all lock configurations');
|
||||
}
|
||||
|
||||
resetPinsToOriginalPositions() {
|
||||
// Reset all pins to their original positions (before any key insertion)
|
||||
this.pins.forEach(pin => {
|
||||
pin.currentHeight = 0;
|
||||
pin.isSet = false;
|
||||
|
||||
// Clear any highlights
|
||||
if (pin.shearHighlight) {
|
||||
pin.shearHighlight.setVisible(false);
|
||||
}
|
||||
if (pin.setHighlight) {
|
||||
pin.setHighlight.setVisible(false);
|
||||
}
|
||||
|
||||
// Update pin visuals
|
||||
this.updatePinVisuals(pin);
|
||||
});
|
||||
|
||||
console.log('Reset all pins to original positions');
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
|
||||
@@ -981,7 +879,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
// Reset pins to their original positions before showing key selection
|
||||
this.resetPinsToOriginalPositions();
|
||||
this.lockConfig.resetPinsToOriginalPositions();
|
||||
|
||||
// Create container for key selection - positioned in the middle but below pins
|
||||
const keySelectionContainer = this.scene.add.container(0, 230);
|
||||
@@ -1117,7 +1015,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
// Reset pins to their original positions before creating the new key
|
||||
this.resetPinsToOriginalPositions();
|
||||
this.lockConfig.resetPinsToOriginalPositions();
|
||||
|
||||
// Store the original correct key data (this determines if the key is correct)
|
||||
const originalKeyData = this.keyData;
|
||||
@@ -2913,7 +2811,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
const margin = pinSpacing * 0.75; // 25% smaller margins
|
||||
|
||||
// Try to load saved pin heights for this lock
|
||||
const savedPinHeights = this.loadLockConfiguration();
|
||||
const savedPinHeights = this.lockConfig.loadLockConfiguration();
|
||||
|
||||
// Check if predefined pin heights were passed
|
||||
const predefinedPinHeights = this.params?.predefinedPinHeights;
|
||||
@@ -3191,7 +3089,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
// Save the lock configuration after all pins are created
|
||||
this.saveLockConfiguration();
|
||||
this.lockConfig.saveLockConfiguration();
|
||||
}
|
||||
|
||||
createShearLine() {
|
||||
@@ -4574,7 +4472,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
// Reset pins to original positions
|
||||
this.resetPinsToOriginalPositions();
|
||||
this.lockConfig.resetPinsToOriginalPositions();
|
||||
|
||||
// Update feedback
|
||||
this.updateFeedback("Lockpicking mode - Apply tension first, then lift pins in binding order");
|
||||
@@ -4627,7 +4525,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
// Reset pins to original positions
|
||||
this.resetPinsToOriginalPositions();
|
||||
this.lockConfig.resetPinsToOriginalPositions();
|
||||
|
||||
// Add mode switch back button (can switch back to lockpicking if available)
|
||||
if (this.canSwitchToPickMode) {
|
||||
|
||||
739
scripts/extract_lockpicking_methods.py
Normal file
739
scripts/extract_lockpicking_methods.py
Normal file
@@ -0,0 +1,739 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Extract methods from lockpicking-game-phaser.js into separate modules.
|
||||
|
||||
Usage:
|
||||
python3 extract_lockpicking_methods.py --methods "method1,method2,method3" --output-file "output.js" [--class-name "ClassName"]
|
||||
|
||||
Example:
|
||||
python3 extract_lockpicking_methods.py \\
|
||||
--methods "createLockBackground,createTensionWrench,createHookPick" \\
|
||||
--output-file "lock-graphics.js" \\
|
||||
--class-name "LockGraphics"
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple, Optional, Set
|
||||
|
||||
|
||||
class MethodExtractor:
|
||||
"""Extract methods from JavaScript class files."""
|
||||
|
||||
def __init__(self, input_file: str):
|
||||
"""Initialize with input file path."""
|
||||
self.input_file = Path(input_file)
|
||||
self.content = self.input_file.read_text(encoding='utf-8')
|
||||
self.lines = self.content.split('\n')
|
||||
|
||||
def replace_this_with_parent(self, code: str, use_parent_keyword: bool = True) -> str:
|
||||
"""
|
||||
Replace 'this' references with 'this.parent' for extracted modules.
|
||||
|
||||
This allows extracted methods to access the parent instance state properly.
|
||||
Uses 'this.parent' so it works within instance methods.
|
||||
|
||||
Args:
|
||||
code: Method code containing 'this' references
|
||||
use_parent_keyword: If True, replace 'this' with 'this.parent'; if False, leave as-is
|
||||
|
||||
Returns:
|
||||
Modified code with replacements
|
||||
"""
|
||||
if not use_parent_keyword:
|
||||
return code
|
||||
|
||||
lines = code.split('\n')
|
||||
modified_lines = []
|
||||
|
||||
for line in lines:
|
||||
# Skip comment lines
|
||||
if line.strip().startswith('//'):
|
||||
modified_lines.append(line)
|
||||
continue
|
||||
|
||||
modified_line = line
|
||||
|
||||
# Replace 'this.' with 'this.parent.' for method bodies
|
||||
# This allows instance methods to access parent state via this.parent
|
||||
modified_line = re.sub(r'\bthis\.', 'this.parent.', modified_line)
|
||||
|
||||
modified_lines.append(modified_line)
|
||||
|
||||
return '\n'.join(modified_lines)
|
||||
|
||||
def find_method(self, method_name: str) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
Find method definition and return start/end line numbers (0-indexed).
|
||||
|
||||
Returns:
|
||||
Tuple of (start_line, end_line) or None if not found
|
||||
"""
|
||||
# Pattern: optional whitespace, method name, optional whitespace, parentheses
|
||||
method_pattern = rf'^\s*{re.escape(method_name)}\s*\('
|
||||
|
||||
start_line = None
|
||||
for i, line in enumerate(self.lines):
|
||||
if re.match(method_pattern, line):
|
||||
start_line = i
|
||||
break
|
||||
|
||||
if start_line is None:
|
||||
return None
|
||||
|
||||
# Find the opening brace
|
||||
brace_line = start_line
|
||||
for i in range(start_line, len(self.lines)):
|
||||
if '{' in self.lines[i]:
|
||||
brace_line = i
|
||||
break
|
||||
|
||||
# Count braces to find the matching closing brace
|
||||
brace_count = 0
|
||||
found_opening = False
|
||||
end_line = None
|
||||
|
||||
for i in range(brace_line, len(self.lines)):
|
||||
line = self.lines[i]
|
||||
|
||||
for char in line:
|
||||
if char == '{':
|
||||
brace_count += 1
|
||||
found_opening = True
|
||||
elif char == '}':
|
||||
if found_opening:
|
||||
brace_count -= 1
|
||||
if brace_count == 0:
|
||||
end_line = i
|
||||
break
|
||||
|
||||
if end_line is not None:
|
||||
break
|
||||
|
||||
if end_line is None:
|
||||
return None
|
||||
|
||||
return (start_line, end_line)
|
||||
|
||||
def extract_method(self, method_name: str, replace_this: bool = False) -> Optional[str]:
|
||||
"""
|
||||
Extract a single method as a string.
|
||||
|
||||
Args:
|
||||
method_name: Name of method to extract
|
||||
replace_this: If True, replace 'this' with 'parent' for module usage
|
||||
|
||||
Returns:
|
||||
Method code as string, or None if not found
|
||||
"""
|
||||
result = self.find_method(method_name)
|
||||
if result is None:
|
||||
print(f"❌ Method '{method_name}' not found", file=sys.stderr)
|
||||
return None
|
||||
|
||||
start_line, end_line = result
|
||||
# Include the full lines, joining them back with newlines
|
||||
method_lines = self.lines[start_line:end_line+1]
|
||||
method_code = '\n'.join(method_lines)
|
||||
|
||||
if replace_this:
|
||||
method_code = self.replace_this_with_parent(method_code, use_parent_keyword=True)
|
||||
|
||||
return method_code
|
||||
|
||||
def extract_methods(self, method_names: List[str], replace_this: bool = False) -> Dict[str, str]:
|
||||
"""
|
||||
Extract multiple methods.
|
||||
|
||||
Args:
|
||||
method_names: List of method names to extract
|
||||
replace_this: If True, replace 'this' with 'parent' in extracted code
|
||||
|
||||
Returns:
|
||||
Dict mapping method_name -> method_code
|
||||
"""
|
||||
extracted = {}
|
||||
for method_name in method_names:
|
||||
code = self.extract_method(method_name, replace_this=replace_this)
|
||||
if code:
|
||||
extracted[method_name] = code
|
||||
print(f"✓ Extracted: {method_name}")
|
||||
else:
|
||||
print(f"✗ Failed to extract: {method_name}")
|
||||
|
||||
return extracted
|
||||
|
||||
def find_dependencies(self, methods: Dict[str, str]) -> Set[str]:
|
||||
"""
|
||||
Find method dependencies (methods called by extracted methods).
|
||||
|
||||
Returns:
|
||||
Set of method names that are called but not in the extraction list
|
||||
"""
|
||||
# Pattern for method calls: this.methodName( or other_object.methodName(
|
||||
method_call_pattern = r'(?:this\.|[\w]+\.)?(\w+)\s*\('
|
||||
|
||||
dependencies = set()
|
||||
all_method_names = set(methods.keys())
|
||||
|
||||
for method_code in methods.values():
|
||||
matches = re.finditer(method_call_pattern, method_code)
|
||||
for match in matches:
|
||||
called_method = match.group(1)
|
||||
# Skip standard JS functions and common names
|
||||
if not self._is_builtin_or_common(called_method):
|
||||
if called_method not in all_method_names:
|
||||
dependencies.add(called_method)
|
||||
|
||||
return dependencies
|
||||
|
||||
@staticmethod
|
||||
def _is_builtin_or_common(name: str) -> bool:
|
||||
"""Check if name is a builtin or common function."""
|
||||
builtins = {
|
||||
'console', 'Math', 'Object', 'Array', 'String', 'Number',
|
||||
'parseInt', 'parseFloat', 'isNaN', 'JSON', 'Date', 'RegExp',
|
||||
'Error', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
||||
'document', 'window', 'localStorage', 'addEventListener',
|
||||
'removeEventListener', 'querySelector', 'getElementById', 'createElement',
|
||||
'appendChild', 'removeChild', 'insertBefore', 'textContent', 'innerHTML',
|
||||
'setAttribute', 'getAttribute', 'classList', 'add', 'remove', 'contains',
|
||||
'push', 'pop', 'shift', 'unshift', 'splice', 'slice', 'concat', 'join',
|
||||
'split', 'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex',
|
||||
'includes', 'indexOf', 'length', 'keys', 'values', 'entries',
|
||||
'Object', 'assign', 'create', 'defineProperty', 'defineProperties',
|
||||
'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'seal', 'freeze',
|
||||
'prototype', 'constructor', 'instanceof', 'typeof', 'in', 'of',
|
||||
'delete', 'new', 'super', 'this', 'return', 'if', 'else', 'for',
|
||||
'while', 'do', 'switch', 'case', 'break', 'continue', 'try',
|
||||
'catch', 'finally', 'throw', 'async', 'await', 'yield', 'static',
|
||||
'class', 'extends', 'import', 'export', 'default', 'from', 'as',
|
||||
'let', 'const', 'var', 'true', 'false', 'null', 'undefined',
|
||||
'add', 'set', 'get', 'on', 'once', 'off', 'emit', 'listen',
|
||||
'startX', 'startY', 'endX', 'endY', 'width', 'height', 'x', 'y',
|
||||
'fill', 'stroke', 'draw', 'render', 'create', 'update', 'init',
|
||||
'tweens', 'time', 'scene', 'add', 'graphics', 'text', 'container',
|
||||
'setAngle', 'setDepth', 'setOrigin', 'setVisible', 'setTint', 'setPosition',
|
||||
'destroy', 'setScale', 'setAlpha', 'setInteractive', 'on', 'once',
|
||||
'rotationCenterX', 'rotationCenterY', 'targetPin', 'lastTargetedPin',
|
||||
'log', 'warn', 'error', 'debug', 'info', 'assert', 'time', 'timeEnd',
|
||||
}
|
||||
return name in builtins
|
||||
|
||||
|
||||
class MainFileUpdater:
|
||||
"""Update the main lockpicking file to use extracted modules."""
|
||||
|
||||
def __init__(self, main_file: str):
|
||||
"""Initialize with main file path."""
|
||||
self.main_file = Path(main_file)
|
||||
self.content = self.main_file.read_text(encoding='utf-8')
|
||||
self.lines = self.content.split('\n')
|
||||
|
||||
def remove_methods(self, method_names: List[str]) -> str:
|
||||
"""
|
||||
Remove method definitions from the main file.
|
||||
|
||||
Args:
|
||||
method_names: List of method names to remove
|
||||
|
||||
Returns:
|
||||
Updated file content
|
||||
"""
|
||||
updated_lines = self.lines.copy()
|
||||
|
||||
for method_name in method_names:
|
||||
# Find the method
|
||||
start_idx = None
|
||||
for i, line in enumerate(updated_lines):
|
||||
if re.match(rf'^\s*{re.escape(method_name)}\s*\(', line):
|
||||
start_idx = i
|
||||
break
|
||||
|
||||
if start_idx is None:
|
||||
print(f"⚠️ Method '{method_name}' not found in main file")
|
||||
continue
|
||||
|
||||
# Find opening brace
|
||||
brace_idx = start_idx
|
||||
for i in range(start_idx, len(updated_lines)):
|
||||
if '{' in updated_lines[i]:
|
||||
brace_idx = i
|
||||
break
|
||||
|
||||
# Count braces to find matching closing brace
|
||||
brace_count = 0
|
||||
found_opening = False
|
||||
end_idx = None
|
||||
|
||||
for i in range(brace_idx, len(updated_lines)):
|
||||
line = updated_lines[i]
|
||||
|
||||
for char in line:
|
||||
if char == '{':
|
||||
brace_count += 1
|
||||
found_opening = True
|
||||
elif char == '}':
|
||||
if found_opening:
|
||||
brace_count -= 1
|
||||
if brace_count == 0:
|
||||
end_idx = i
|
||||
break
|
||||
|
||||
if end_idx is not None:
|
||||
break
|
||||
|
||||
if end_idx is not None:
|
||||
# Remove the method and surrounding whitespace
|
||||
del updated_lines[start_idx:end_idx+1]
|
||||
# Remove empty lines that follow
|
||||
while updated_lines and updated_lines[start_idx].strip() == '':
|
||||
del updated_lines[start_idx]
|
||||
|
||||
print(f"✓ Removed method: {method_name}")
|
||||
|
||||
return '\n'.join(updated_lines)
|
||||
|
||||
def add_import(self, class_name: str, module_path: str) -> str:
|
||||
"""
|
||||
Add import statement at the top of the file.
|
||||
|
||||
Args:
|
||||
class_name: Name of class/object being imported
|
||||
module_path: Relative path to module (e.g., './lock-configuration.js')
|
||||
|
||||
Returns:
|
||||
Updated content with import added
|
||||
"""
|
||||
lines = self.content.split('\n')
|
||||
|
||||
# Find where to insert import (after existing imports, before class definition)
|
||||
insert_idx = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('import '):
|
||||
insert_idx = i + 1
|
||||
elif line.startswith('export class'):
|
||||
break
|
||||
|
||||
import_stmt = f"import {{ {class_name} }} from '{module_path}';"
|
||||
lines.insert(insert_idx, import_stmt)
|
||||
|
||||
# Update content for next operations
|
||||
self.content = '\n'.join(lines)
|
||||
return self.content
|
||||
|
||||
def add_module_initialization(self, instance_name: str, class_name: str) -> str:
|
||||
"""
|
||||
Add module initialization in constructor.
|
||||
|
||||
Args:
|
||||
instance_name: Name for the instance (e.g., 'lockConfig')
|
||||
class_name: Class name (e.g., 'LockConfiguration')
|
||||
|
||||
Returns:
|
||||
Updated content with initialization added
|
||||
"""
|
||||
lines = self.content.split('\n')
|
||||
|
||||
# Find constructor and its opening brace
|
||||
constructor_idx = None
|
||||
for i, line in enumerate(lines):
|
||||
if 'constructor(' in line:
|
||||
constructor_idx = i
|
||||
break
|
||||
|
||||
if constructor_idx is None:
|
||||
print("⚠️ Constructor not found")
|
||||
return self.content
|
||||
|
||||
# Find the end of super() call or end of constructor body setup
|
||||
init_idx = constructor_idx + 1
|
||||
for i in range(constructor_idx, min(constructor_idx + 50, len(lines))):
|
||||
line = lines[i]
|
||||
# Look for lines that initialize properties (this.xxx = ...)
|
||||
# We want to add after all the initialization lines
|
||||
if line.strip() and not line.strip().startswith('//') and '=' in line:
|
||||
init_idx = i + 1
|
||||
# Stop at closing brace of constructor
|
||||
elif line.strip() == '}':
|
||||
break
|
||||
|
||||
# Add initialization before the closing brace
|
||||
# Go back to find the right spot (before closing brace)
|
||||
for i in range(init_idx, min(init_idx + 10, len(lines))):
|
||||
if lines[i].strip() == '}':
|
||||
init_idx = i
|
||||
break
|
||||
|
||||
# Create the initialization line with proper indentation
|
||||
init_stmt = f" \n // Initialize {class_name} module"
|
||||
init_stmt += f"\n this.{instance_name} = new {class_name}(this);"
|
||||
lines.insert(init_idx, init_stmt)
|
||||
|
||||
# Update content for next operations
|
||||
self.content = '\n'.join(lines)
|
||||
return self.content
|
||||
|
||||
def replace_method_calls(self, method_names: List[str], module_instance: str) -> str:
|
||||
"""
|
||||
Replace method calls in the main file.
|
||||
|
||||
Args:
|
||||
method_names: Methods that were extracted
|
||||
module_instance: Name of the module instance (e.g., 'lockConfig')
|
||||
|
||||
Returns:
|
||||
Updated content with method calls replaced
|
||||
"""
|
||||
updated = self.content
|
||||
|
||||
for method_name in method_names:
|
||||
# Pattern: this.methodName(
|
||||
# Replace with: this.moduleInstance.methodName(
|
||||
pattern = rf'this\.{method_name}\('
|
||||
replacement = f'this.{module_instance}.{method_name}('
|
||||
updated = re.sub(pattern, replacement, updated)
|
||||
|
||||
# Update content for next operations
|
||||
self.content = updated
|
||||
return updated
|
||||
|
||||
|
||||
class ModuleGenerator:
|
||||
"""Generate JavaScript module files."""
|
||||
|
||||
def __init__(self, import_statements: Optional[str] = None):
|
||||
"""Initialize with optional import statements."""
|
||||
self.import_statements = import_statements or ""
|
||||
|
||||
def generate_module(
|
||||
self,
|
||||
methods: Dict[str, str],
|
||||
class_name: str,
|
||||
export_as_class: bool = True,
|
||||
extends: Optional[str] = None,
|
||||
additional_imports: Optional[List[str]] = None,
|
||||
use_parent_instance: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Generate a complete JavaScript module.
|
||||
|
||||
Args:
|
||||
methods: Dict of method_name -> method_code
|
||||
class_name: Name of the exported class/object
|
||||
export_as_class: If True, export as class; if False, as object
|
||||
extends: Class to extend (e.g., "MinigameScene")
|
||||
additional_imports: List of import statements
|
||||
use_parent_instance: If True, generate with parent instance pattern
|
||||
|
||||
Returns:
|
||||
Complete module code as string
|
||||
"""
|
||||
# Build imports
|
||||
imports = []
|
||||
if additional_imports:
|
||||
imports.extend(additional_imports)
|
||||
|
||||
imports_section = '\n'.join(imports) + '\n' if imports else ''
|
||||
|
||||
# Build class or object
|
||||
if export_as_class:
|
||||
code = self._generate_class(methods, class_name, extends, imports_section, use_parent_instance)
|
||||
else:
|
||||
code = self._generate_object(methods, class_name, imports_section, use_parent_instance)
|
||||
|
||||
return code
|
||||
|
||||
@staticmethod
|
||||
def _generate_class(
|
||||
methods: Dict[str, str],
|
||||
class_name: str,
|
||||
extends: Optional[str],
|
||||
imports_section: str,
|
||||
use_parent_instance: bool = True
|
||||
) -> str:
|
||||
"""Generate a class module."""
|
||||
extends_str = f" extends {extends}" if extends else ""
|
||||
|
||||
# Join all methods with proper spacing
|
||||
methods_code = '\n\n '.join(methods.values())
|
||||
|
||||
# Add constructor if using parent instance pattern
|
||||
if use_parent_instance:
|
||||
constructor = """constructor(parent) {
|
||||
this.parent = parent;
|
||||
}"""
|
||||
methods_code = constructor + '\n\n ' + methods_code
|
||||
|
||||
code = f"""{imports_section}
|
||||
/**
|
||||
* {class_name}
|
||||
*
|
||||
* Extracted from lockpicking-game-phaser.js
|
||||
* Instantiate with: new {class_name}(this)
|
||||
*
|
||||
* All 'this' references replaced with 'this.parent' to access parent instance state:
|
||||
* - this.parent.pins (array of pin objects)
|
||||
* - this.parent.scene (Phaser scene)
|
||||
* - this.parent.lockId (lock identifier)
|
||||
* - this.parent.lockState (lock state object)
|
||||
* etc.
|
||||
*/
|
||||
export class {class_name}{extends_str} {{
|
||||
|
||||
{methods_code}
|
||||
|
||||
}}
|
||||
"""
|
||||
return code
|
||||
|
||||
@staticmethod
|
||||
def _generate_object(
|
||||
methods: Dict[str, str],
|
||||
object_name: str,
|
||||
imports_section: str,
|
||||
use_parent_instance: bool = True
|
||||
) -> str:
|
||||
"""Generate an object/namespace module."""
|
||||
# Convert methods to object methods
|
||||
methods_code = '\n\n '.join(methods.values())
|
||||
|
||||
# Add init function if using parent instance pattern
|
||||
if use_parent_instance:
|
||||
init_func = """init(parent) {
|
||||
return {
|
||||
parent: parent
|
||||
};
|
||||
}"""
|
||||
methods_code = init_func + '\n\n ' + methods_code
|
||||
|
||||
code = f"""{imports_section}
|
||||
/**
|
||||
* {object_name}
|
||||
*
|
||||
* Extracted from lockpicking-game-phaser.js
|
||||
* Usage: {object_name}.methodName(parent, ...args)
|
||||
*
|
||||
* All 'this' references replaced with 'parent' to access parent instance state:
|
||||
* - parent.pins (array of pin objects)
|
||||
* - parent.scene (Phaser scene)
|
||||
* - parent.lockId (lock identifier)
|
||||
* - parent.lockState (lock state object)
|
||||
* etc.
|
||||
*/
|
||||
export const {object_name} = {{
|
||||
|
||||
{methods_code}
|
||||
|
||||
}};
|
||||
"""
|
||||
return code
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Extract methods from lockpicking-game-phaser.js',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Extract lock graphics methods
|
||||
python3 extract_lockpicking_methods.py \\
|
||||
--methods "createLockBackground,createTensionWrench,createHookPick" \\
|
||||
--output-file "lock-graphics.js" \\
|
||||
--class-name "LockGraphics" \\
|
||||
--extends "LockpickingComponent"
|
||||
|
||||
# Extract lock configuration methods as object
|
||||
python3 extract_lockpicking_methods.py \\
|
||||
--methods "saveLockConfiguration,loadLockConfiguration,clearLockConfiguration" \\
|
||||
--output-file "lock-configuration.js" \\
|
||||
--object-mode
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--input-file',
|
||||
default='js/minigames/lockpicking/lockpicking-game-phaser.js',
|
||||
help='Path to input JavaScript file (default: %(default)s)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--methods',
|
||||
required=True,
|
||||
help='Comma-separated list of method names to extract'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output-file',
|
||||
required=True,
|
||||
help='Path to output JavaScript file'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--class-name',
|
||||
help='Name for exported class (default: auto-generated from filename)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--extends',
|
||||
help='Parent class to extend'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--object-mode',
|
||||
action='store_true',
|
||||
help='Export as object instead of class'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--show-dependencies',
|
||||
action='store_true',
|
||||
help='Show method dependencies before extraction'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--imports',
|
||||
help='Comma-separated list of import statements to add'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--replace-this',
|
||||
action='store_true',
|
||||
help='Replace "this" with "parent" in extracted methods for state sharing'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--update-main-file',
|
||||
help='Path to main file to update with imports and method calls'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--module-instance-name',
|
||||
help='Name for module instance in main file (e.g., "lockConfig")'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--auto-integrate',
|
||||
action='store_true',
|
||||
help='Automatically remove methods from main file and add imports (requires --update-main-file)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse method names
|
||||
method_names = [m.strip() for m in args.methods.split(',')]
|
||||
|
||||
# Parse imports if provided
|
||||
additional_imports = []
|
||||
if args.imports:
|
||||
additional_imports = [i.strip() for i in args.imports.split(',')]
|
||||
|
||||
# Generate class name from output file if not provided
|
||||
class_name = args.class_name
|
||||
if not class_name:
|
||||
# Convert filename to PascalCase class name
|
||||
filename = Path(args.output_file).stem
|
||||
parts = filename.split('-')
|
||||
class_name = ''.join(word.capitalize() for word in parts)
|
||||
|
||||
try:
|
||||
# Extract methods
|
||||
print(f"📂 Reading: {args.input_file}")
|
||||
extractor = MethodExtractor(args.input_file)
|
||||
|
||||
print(f"\n📋 Extracting {len(method_names)} methods...")
|
||||
methods = extractor.extract_methods(method_names, replace_this=args.replace_this)
|
||||
|
||||
if not methods:
|
||||
print("❌ No methods extracted!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Show dependencies if requested
|
||||
if args.show_dependencies:
|
||||
deps = extractor.find_dependencies(methods)
|
||||
if deps:
|
||||
print(f"\n⚠️ Dependencies (methods called but not extracted):")
|
||||
for dep in sorted(deps):
|
||||
print(f" - {dep}")
|
||||
else:
|
||||
print(f"\n✓ No external dependencies found")
|
||||
|
||||
# Generate module
|
||||
print(f"\n🔨 Generating module: {class_name}")
|
||||
generator = ModuleGenerator()
|
||||
|
||||
module_code = generator.generate_module(
|
||||
methods=methods,
|
||||
class_name=class_name,
|
||||
export_as_class=not args.object_mode,
|
||||
extends=args.extends,
|
||||
additional_imports=additional_imports,
|
||||
use_parent_instance=args.replace_this
|
||||
)
|
||||
|
||||
# Write output
|
||||
output_path = Path(args.output_file)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(module_code, encoding='utf-8')
|
||||
|
||||
print(f"\n✅ Success! Created: {args.output_file}")
|
||||
print(f" Lines of code: {len(module_code.split(chr(10)))}")
|
||||
|
||||
# Update main file if requested
|
||||
if args.update_main_file:
|
||||
print(f"\n📝 Updating main file: {args.update_main_file}")
|
||||
|
||||
main_updater = MainFileUpdater(args.update_main_file)
|
||||
module_instance_name = args.module_instance_name or class_name[0].lower() + class_name[1:] # camelCase
|
||||
|
||||
if args.auto_integrate:
|
||||
print(f"\n 🔧 Auto-integrating...")
|
||||
|
||||
# 1. Add import statement
|
||||
import_path = Path(args.output_file).name
|
||||
main_updater.add_import(class_name, f'./{import_path}')
|
||||
print(f" ✓ Added import statement")
|
||||
|
||||
# 2. Add module initialization in constructor
|
||||
main_updater.add_module_initialization(module_instance_name, class_name)
|
||||
print(f" ✓ Added module initialization in constructor")
|
||||
|
||||
# 3. Remove old methods from main file
|
||||
try:
|
||||
main_updater.remove_methods(method_names)
|
||||
print(f" ✓ Removed {len(method_names)} methods from main file")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error removing methods: {e}")
|
||||
|
||||
# 4. Replace method calls to use module instance
|
||||
try:
|
||||
main_updater.replace_method_calls(method_names, module_instance_name)
|
||||
print(f" ✓ Updated method calls to use this.{module_instance_name}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error updating calls: {e}")
|
||||
|
||||
# Write updated main file
|
||||
try:
|
||||
main_path = Path(args.update_main_file)
|
||||
main_path.write_text(main_updater.content, encoding='utf-8')
|
||||
print(f"\n✅ Updated: {args.update_main_file}")
|
||||
print(f" Instance name: this.{module_instance_name}")
|
||||
print(f" Usage: new {class_name}(this) in constructor")
|
||||
except Exception as e:
|
||||
print(f"❌ Error writing main file: {e}", file=sys.stderr)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"❌ File not found: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
224
scripts/list_js_functions.py
Normal file
224
scripts/list_js_functions.py
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
List all JavaScript functions in a file.
|
||||
|
||||
Usage:
|
||||
python3 list_js_functions.py [--file path/to/file.js] [--format table|list|csv]
|
||||
|
||||
Example:
|
||||
python3 list_js_functions.py --file js/minigames/lockpicking/lockpicking-game-phaser.js --format table
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
class JSFunctionLister:
|
||||
"""Extract and list all functions from a JavaScript file."""
|
||||
|
||||
def __init__(self, input_file: str):
|
||||
"""Initialize with input file path."""
|
||||
self.input_file = Path(input_file)
|
||||
self.content = self.input_file.read_text(encoding='utf-8')
|
||||
self.lines = self.content.split('\n')
|
||||
|
||||
def find_all_functions(self) -> List[Tuple[str, int, int]]:
|
||||
"""
|
||||
Find all function definitions in the file.
|
||||
|
||||
Returns:
|
||||
List of tuples: (function_name, start_line, end_line)
|
||||
"""
|
||||
functions = []
|
||||
|
||||
# Pattern for method definitions (class methods)
|
||||
method_pattern = r'^\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\('
|
||||
|
||||
i = 0
|
||||
while i < len(self.lines):
|
||||
line = self.lines[i]
|
||||
|
||||
# Match method definition
|
||||
match = re.match(method_pattern, line)
|
||||
if match:
|
||||
method_name = match.group(1)
|
||||
start_line = i
|
||||
|
||||
# Find the end of the method by counting braces
|
||||
brace_count = 0
|
||||
found_opening = False
|
||||
end_line = None
|
||||
|
||||
for j in range(i, len(self.lines)):
|
||||
current_line = self.lines[j]
|
||||
|
||||
for char in current_line:
|
||||
if char == '{':
|
||||
brace_count += 1
|
||||
found_opening = True
|
||||
elif char == '}':
|
||||
if found_opening:
|
||||
brace_count -= 1
|
||||
if brace_count == 0:
|
||||
end_line = j
|
||||
break
|
||||
|
||||
if end_line is not None:
|
||||
break
|
||||
|
||||
if end_line is not None:
|
||||
functions.append((method_name, start_line + 1, end_line + 1)) # +1 for 1-based indexing
|
||||
i = end_line + 1
|
||||
else:
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return functions
|
||||
|
||||
def format_table(self, functions: List[Tuple[str, int, int]]) -> str:
|
||||
"""Format functions as a table."""
|
||||
if not functions:
|
||||
return "No functions found"
|
||||
|
||||
# Calculate column widths
|
||||
max_name_len = max(len(name) for name, _, _ in functions)
|
||||
max_name_len = max(max_name_len, len("Function Name"))
|
||||
|
||||
# Header
|
||||
lines = []
|
||||
lines.append("┌─" + "─" * max_name_len + "─┬──────────┬──────────┐")
|
||||
lines.append(f"│ {'Function Name':<{max_name_len}} │ Start │ End │")
|
||||
lines.append("├─" + "─" * max_name_len + "─┼──────────┼──────────┤")
|
||||
|
||||
# Rows
|
||||
for name, start, end in functions:
|
||||
lines.append(f"│ {name:<{max_name_len}} │ {start:>8} │ {end:>8} │")
|
||||
|
||||
lines.append("└─" + "─" * max_name_len + "─┴──────────┴──────────┘")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def format_list(self, functions: List[Tuple[str, int, int]]) -> str:
|
||||
"""Format functions as a simple list."""
|
||||
if not functions:
|
||||
return "No functions found"
|
||||
|
||||
lines = []
|
||||
for i, (name, start, end) in enumerate(functions, 1):
|
||||
lines.append(f"{i:2}. {name:40} (lines {start:5}-{end:5})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def format_csv(self, functions: List[Tuple[str, int, int]]) -> str:
|
||||
"""Format functions as CSV."""
|
||||
if not functions:
|
||||
return "No functions found"
|
||||
|
||||
lines = ["Function Name,Start Line,End Line"]
|
||||
for name, start, end in functions:
|
||||
lines.append(f'"{name}",{start},{end}')
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def format_copy_paste(self, functions: List[Tuple[str, int, int]]) -> str:
|
||||
"""Format as comma-separated list for copy-pasting to command line."""
|
||||
if not functions:
|
||||
return "No functions found"
|
||||
|
||||
names = [name for name, _, _ in functions]
|
||||
return ",".join(names)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='List all JavaScript functions in a file',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# List functions from lockpicking file
|
||||
python3 list_js_functions.py --file js/minigames/lockpicking/lockpicking-game-phaser.js
|
||||
|
||||
# Show as table
|
||||
python3 list_js_functions.py --file lockpicking-game-phaser.js --format table
|
||||
|
||||
# Show as CSV
|
||||
python3 list_js_functions.py --file lockpicking-game-phaser.js --format csv
|
||||
|
||||
# Get copy-paste friendly list
|
||||
python3 list_js_functions.py --file lockpicking-game-phaser.js --format copy-paste
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
default='js/minigames/lockpicking/lockpicking-game-phaser.js',
|
||||
help='Path to input JavaScript file (default: %(default)s)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
choices=['table', 'list', 'csv', 'copy-paste'],
|
||||
default='table',
|
||||
help='Output format (default: %(default)s)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--grep',
|
||||
help='Filter functions by name (case-insensitive)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--count',
|
||||
action='store_true',
|
||||
help='Show only count of functions'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# List functions
|
||||
print(f"📂 Reading: {args.file}")
|
||||
lister = JSFunctionLister(args.file)
|
||||
|
||||
print(f"\n🔍 Extracting functions...")
|
||||
functions = lister.find_all_functions()
|
||||
|
||||
# Filter if grep specified
|
||||
if args.grep:
|
||||
grep_lower = args.grep.lower()
|
||||
functions = [(n, s, e) for n, s, e in functions if grep_lower in n.lower()]
|
||||
print(f"📋 Filtered to functions matching '{args.grep}':")
|
||||
|
||||
# Show count
|
||||
print(f"✅ Found {len(functions)} functions\n")
|
||||
|
||||
if args.count:
|
||||
print(f"Total: {len(functions)}")
|
||||
else:
|
||||
# Format and display
|
||||
if args.format == 'table':
|
||||
print(lister.format_table(functions))
|
||||
elif args.format == 'list':
|
||||
print(lister.format_list(functions))
|
||||
elif args.format == 'csv':
|
||||
print(lister.format_csv(functions))
|
||||
elif args.format == 'copy-paste':
|
||||
print("\n📋 Copy-paste this list of function names:\n")
|
||||
print(lister.format_copy_paste(functions))
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"❌ File not found: {e}")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user