mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
Add KeySelection module to lockpicking minigame: Extract key generation logic and integrate into LockpickingMinigamePhaser for improved modularity.
This commit is contained in:
118
js/minigames/lockpicking/key-selection.js
Normal file
118
js/minigames/lockpicking/key-selection.js
Normal file
@@ -0,0 +1,118 @@
|
||||
|
||||
/**
|
||||
* KeySelection
|
||||
*
|
||||
* Extracted from lockpicking-game-phaser.js
|
||||
* Instantiate with: new KeySelection(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 KeySelection {
|
||||
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
createKeyFromPinSizes(pinSizes) {
|
||||
// Create a complete key object based on a set of pin sizes
|
||||
// pinSizes: array of numbers representing the depth of each cut (0-100)
|
||||
|
||||
const keyConfig = {
|
||||
pinCount: pinSizes.length,
|
||||
cuts: pinSizes,
|
||||
// Standard key dimensions
|
||||
circleRadius: 20,
|
||||
shoulderWidth: 30,
|
||||
shoulderHeight: 130,
|
||||
bladeWidth: 420,
|
||||
bladeHeight: 110,
|
||||
keywayStartX: 100,
|
||||
keywayStartY: 170,
|
||||
keywayWidth: 400,
|
||||
keywayHeight: 120
|
||||
};
|
||||
|
||||
return keyConfig;
|
||||
}
|
||||
|
||||
generateRandomKey(pinCount = 5) {
|
||||
// Generate a random key with the specified number of pins
|
||||
const cuts = [];
|
||||
for (let i = 0; i < pinCount; i++) {
|
||||
// Generate random cut depth between 20-80 (avoiding extremes)
|
||||
cuts.push(Math.floor(Math.random() * 60) + 20);
|
||||
}
|
||||
return {
|
||||
id: `random_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
cuts,
|
||||
name: `Random Key`,
|
||||
pinCount: pinCount
|
||||
};
|
||||
}
|
||||
|
||||
createKeysFromInventory(inventoryKeys, correctKeyId) {
|
||||
// Create key selection from inventory keys
|
||||
// inventoryKeys: array of key objects from player inventory
|
||||
// correctKeyId: ID of the key that should work with this lock
|
||||
|
||||
// Filter keys to only include those with cuts data
|
||||
const validKeys = inventoryKeys.filter(key => key.cuts && Array.isArray(key.cuts));
|
||||
|
||||
if (validKeys.length === 0) {
|
||||
// No valid keys in inventory, generate random ones
|
||||
const key1 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
const key2 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
const key3 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
|
||||
// Make the first key correct
|
||||
key1.cuts = this.parent.keyData.cuts;
|
||||
key1.id = correctKeyId || 'correct_key';
|
||||
key1.name = 'Correct Key';
|
||||
|
||||
// Randomize the order
|
||||
const keys = [key1, key2, key3];
|
||||
this.parent.shuffleArray(keys);
|
||||
|
||||
return this.parent.createKeySelectionUI(keys, correctKeyId);
|
||||
}
|
||||
|
||||
// Use inventory keys and randomize their order
|
||||
const shuffledKeys = [...validKeys];
|
||||
this.parent.shuffleArray(shuffledKeys);
|
||||
|
||||
return this.parent.createKeySelectionUI(shuffledKeys, correctKeyId);
|
||||
}
|
||||
|
||||
createKeysForChallenge(correctKeyId = 'challenge_key') {
|
||||
// Create keys for challenge mode (like locksmith-forge.html)
|
||||
// Generates 3 keys with one guaranteed correct key
|
||||
|
||||
const key1 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
const key2 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
const key3 = this.parent.generateRandomKey(this.parent.pinCount);
|
||||
|
||||
// Make the first key correct by copying the actual key cuts
|
||||
key1.cuts = this.parent.keyData.cuts;
|
||||
key1.id = correctKeyId;
|
||||
key1.name = 'Correct Key';
|
||||
|
||||
// Give other keys descriptive names
|
||||
key2.name = 'Wrong Key 1';
|
||||
key3.name = 'Wrong Key 2';
|
||||
|
||||
// Randomize the order of keys
|
||||
const keys = [key1, key2, key3];
|
||||
this.parent.shuffleArray(keys);
|
||||
|
||||
// Find the new index of the correct key after shuffling
|
||||
const correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
|
||||
|
||||
return this.parent.createKeySelectionUI(keys, correctKeyId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { MinigameScene } from '../framework/base-minigame.js';
|
||||
import { LockConfiguration } from './lock-configuration.js';
|
||||
import { LockGraphics } from './lock-graphics.js';
|
||||
import { KeyDataGenerator } from './key-data-generator.js';
|
||||
import { KeySelection } from './key-selection.js';
|
||||
|
||||
// Phaser Lockpicking Minigame Scene implementation
|
||||
export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
@@ -26,6 +27,9 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
|
||||
// Initialize KeyDataGenerator module
|
||||
this.keyDataGen = new KeyDataGenerator(this);
|
||||
|
||||
// Initialize KeySelection module
|
||||
this.keySelection = new KeySelection(this);
|
||||
}
|
||||
|
||||
// Also try to load from localStorage for persistence across sessions
|
||||
@@ -368,103 +372,6 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
|
||||
|
||||
|
||||
createKeyFromPinSizes(pinSizes) {
|
||||
// Create a complete key object based on a set of pin sizes
|
||||
// pinSizes: array of numbers representing the depth of each cut (0-100)
|
||||
|
||||
const keyConfig = {
|
||||
pinCount: pinSizes.length,
|
||||
cuts: pinSizes,
|
||||
// Standard key dimensions
|
||||
circleRadius: 20,
|
||||
shoulderWidth: 30,
|
||||
shoulderHeight: 130,
|
||||
bladeWidth: 420,
|
||||
bladeHeight: 110,
|
||||
keywayStartX: 100,
|
||||
keywayStartY: 170,
|
||||
keywayWidth: 400,
|
||||
keywayHeight: 120
|
||||
};
|
||||
|
||||
return keyConfig;
|
||||
}
|
||||
|
||||
generateRandomKey(pinCount = 5) {
|
||||
// Generate a random key with the specified number of pins
|
||||
const cuts = [];
|
||||
for (let i = 0; i < pinCount; i++) {
|
||||
// Generate random cut depth between 20-80 (avoiding extremes)
|
||||
cuts.push(Math.floor(Math.random() * 60) + 20);
|
||||
}
|
||||
return {
|
||||
id: `random_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
cuts,
|
||||
name: `Random Key`,
|
||||
pinCount: pinCount
|
||||
};
|
||||
}
|
||||
|
||||
createKeysFromInventory(inventoryKeys, correctKeyId) {
|
||||
// Create key selection from inventory keys
|
||||
// inventoryKeys: array of key objects from player inventory
|
||||
// correctKeyId: ID of the key that should work with this lock
|
||||
|
||||
// Filter keys to only include those with cuts data
|
||||
const validKeys = inventoryKeys.filter(key => key.cuts && Array.isArray(key.cuts));
|
||||
|
||||
if (validKeys.length === 0) {
|
||||
// No valid keys in inventory, generate random ones
|
||||
const key1 = this.generateRandomKey(this.pinCount);
|
||||
const key2 = this.generateRandomKey(this.pinCount);
|
||||
const key3 = this.generateRandomKey(this.pinCount);
|
||||
|
||||
// Make the first key correct
|
||||
key1.cuts = this.keyData.cuts;
|
||||
key1.id = correctKeyId || 'correct_key';
|
||||
key1.name = 'Correct Key';
|
||||
|
||||
// Randomize the order
|
||||
const keys = [key1, key2, key3];
|
||||
this.shuffleArray(keys);
|
||||
|
||||
return this.createKeySelectionUI(keys, correctKeyId);
|
||||
}
|
||||
|
||||
// Use inventory keys and randomize their order
|
||||
const shuffledKeys = [...validKeys];
|
||||
this.shuffleArray(shuffledKeys);
|
||||
|
||||
return this.createKeySelectionUI(shuffledKeys, correctKeyId);
|
||||
}
|
||||
|
||||
createKeysForChallenge(correctKeyId = 'challenge_key') {
|
||||
// Create keys for challenge mode (like locksmith-forge.html)
|
||||
// Generates 3 keys with one guaranteed correct key
|
||||
|
||||
const key1 = this.generateRandomKey(this.pinCount);
|
||||
const key2 = this.generateRandomKey(this.pinCount);
|
||||
const key3 = this.generateRandomKey(this.pinCount);
|
||||
|
||||
// Make the first key correct by copying the actual key cuts
|
||||
key1.cuts = this.keyData.cuts;
|
||||
key1.id = correctKeyId;
|
||||
key1.name = 'Correct Key';
|
||||
|
||||
// Give other keys descriptive names
|
||||
key2.name = 'Wrong Key 1';
|
||||
key3.name = 'Wrong Key 2';
|
||||
|
||||
// Randomize the order of keys
|
||||
const keys = [key1, key2, key3];
|
||||
this.shuffleArray(keys);
|
||||
|
||||
// Find the new index of the correct key after shuffling
|
||||
const correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
|
||||
|
||||
return this.createKeySelectionUI(keys, correctKeyId);
|
||||
}
|
||||
|
||||
startWithKeySelection(inventoryKeys = null, correctKeyId = null) {
|
||||
// Start the minigame with key selection instead of a default key
|
||||
// inventoryKeys: array of keys from inventory (optional)
|
||||
@@ -474,10 +381,10 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
|
||||
if (inventoryKeys && inventoryKeys.length > 0) {
|
||||
// Use provided inventory keys
|
||||
this.createKeysFromInventory(inventoryKeys, correctKeyId);
|
||||
this.keySelection.createKeysFromInventory(inventoryKeys, correctKeyId);
|
||||
} else {
|
||||
// Generate random keys for challenge
|
||||
this.createKeysForChallenge(correctKeyId || 'challenge_key');
|
||||
this.keySelection.createKeysForChallenge(correctKeyId || 'challenge_key');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1339,7 +1246,7 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
// For challenge mode (locksmith-forge.html), use the training interface
|
||||
if (this.params?.lockable?.id === 'progressive-challenge') {
|
||||
// This is the locksmith-forge.html challenge mode
|
||||
this.createKeysForChallenge('correct_key');
|
||||
this.keySelection.createKeysForChallenge('correct_key');
|
||||
} else {
|
||||
// This is the main game - go back to key selection
|
||||
this.startWithKeySelection();
|
||||
|
||||
@@ -134,9 +134,19 @@ class MethodExtractor:
|
||||
return None
|
||||
|
||||
start_line, end_line = result
|
||||
# Include the full lines, joining them back with newlines
|
||||
# Extract lines as-is from the source
|
||||
method_lines = self.lines[start_line:end_line+1]
|
||||
method_code = '\n'.join(method_lines)
|
||||
|
||||
# Strip the leading 4-space class indentation from all non-empty lines,
|
||||
# since the module template will apply the correct indentation level
|
||||
dedented_lines = []
|
||||
for line in method_lines:
|
||||
if line.startswith(' ') and line.strip(): # Has 4-space indent and not empty
|
||||
dedented_lines.append(line[4:]) # Remove the 4-space class indent
|
||||
else:
|
||||
dedented_lines.append(line) # Keep as-is (empty or already correct)
|
||||
|
||||
method_code = '\n'.join(dedented_lines)
|
||||
|
||||
if replace_this:
|
||||
method_code = self.replace_this_with_parent(method_code, use_parent_keyword=True)
|
||||
@@ -294,7 +304,10 @@ class MainFileUpdater:
|
||||
|
||||
print(f"✓ Removed method: {method_name}")
|
||||
|
||||
return '\n'.join(updated_lines)
|
||||
# Update both self.lines and self.content
|
||||
self.lines = updated_lines
|
||||
self.content = '\n'.join(updated_lines)
|
||||
return self.content
|
||||
|
||||
def add_import(self, class_name: str, module_path: str) -> str:
|
||||
"""
|
||||
@@ -309,6 +322,13 @@ class MainFileUpdater:
|
||||
"""
|
||||
lines = self.content.split('\n')
|
||||
|
||||
# Check if this import already exists
|
||||
import_stmt = f"import {{ {class_name} }} from '{module_path}';"
|
||||
for line in lines:
|
||||
if import_stmt in line:
|
||||
# Import already exists, no need to add
|
||||
return self.content
|
||||
|
||||
# Find where to insert import (after existing imports, before class definition)
|
||||
insert_idx = 0
|
||||
for i, line in enumerate(lines):
|
||||
@@ -317,11 +337,12 @@ class MainFileUpdater:
|
||||
elif line.startswith('export class'):
|
||||
break
|
||||
|
||||
import_stmt = f"import {{ {class_name} }} from '{module_path}';"
|
||||
# Insert the new import statement
|
||||
lines.insert(insert_idx, import_stmt)
|
||||
|
||||
# Update content for next operations
|
||||
self.content = '\n'.join(lines)
|
||||
self.lines = lines
|
||||
return self.content
|
||||
|
||||
def add_module_initialization(self, instance_name: str, class_name: str) -> str:
|
||||
@@ -335,6 +356,12 @@ class MainFileUpdater:
|
||||
Returns:
|
||||
Updated content with initialization added
|
||||
"""
|
||||
# Check if initialization already exists to prevent duplicates
|
||||
init_pattern = f'this.{instance_name} = new {class_name}(this)'
|
||||
if init_pattern in self.content:
|
||||
print(f"ℹ️ Initialization for {instance_name} already exists, skipping")
|
||||
return self.content
|
||||
|
||||
lines = self.content.split('\n')
|
||||
|
||||
# Find constructor and its opening brace
|
||||
@@ -372,7 +399,8 @@ class MainFileUpdater:
|
||||
init_stmt += f"\n this.{instance_name} = new {class_name}(this);"
|
||||
lines.insert(init_idx, init_stmt)
|
||||
|
||||
# Update content for next operations
|
||||
# Update content and lines for next operations
|
||||
self.lines = lines
|
||||
self.content = '\n'.join(lines)
|
||||
return self.content
|
||||
|
||||
@@ -390,11 +418,16 @@ class MainFileUpdater:
|
||||
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)
|
||||
# Pattern 1: this.methodName( -> this.moduleInstance.methodName(
|
||||
pattern_this = rf'this\.{method_name}\('
|
||||
replacement_this = f'this.{module_instance}.{method_name}('
|
||||
updated = re.sub(pattern_this, replacement_this, updated)
|
||||
|
||||
# Pattern 2: self.methodName( -> self.moduleInstance.methodName(
|
||||
# (common pattern in Phaser where scenes save const self = this)
|
||||
pattern_self = rf'self\.{method_name}\('
|
||||
replacement_self = f'self.{module_instance}.{method_name}('
|
||||
updated = re.sub(pattern_self, replacement_self, updated)
|
||||
|
||||
# Update content for next operations
|
||||
self.content = updated
|
||||
@@ -456,16 +489,23 @@ class ModuleGenerator:
|
||||
) -> str:
|
||||
"""Generate a class module."""
|
||||
extends_str = f" extends {extends}" if extends else ""
|
||||
|
||||
# Join all methods without adding additional indentation. Extracted
|
||||
# methods already contain their original leading whitespace, so we
|
||||
# preserve them exactly by joining with blank lines only.
|
||||
methods_code = '\n\n'.join(methods.values())
|
||||
|
||||
# Join all methods with proper spacing
|
||||
methods_code = '\n\n '.join(methods.values())
|
||||
|
||||
# Add constructor if using parent instance pattern
|
||||
# Add 4-space indentation to every line of methods_code to indent at class level
|
||||
indented_methods = '\n'.join(' ' + line if line.strip() else line
|
||||
for line in methods_code.split('\n'))
|
||||
|
||||
# Add constructor if using parent instance pattern. Constructor
|
||||
# should use the same 4-space method indentation level.
|
||||
if use_parent_instance:
|
||||
constructor = """constructor(parent) {
|
||||
constructor = """ constructor(parent) {
|
||||
this.parent = parent;
|
||||
}"""
|
||||
methods_code = constructor + '\n\n ' + methods_code
|
||||
indented_methods = constructor + '\n\n' + indented_methods
|
||||
|
||||
code = f"""{imports_section}
|
||||
/**
|
||||
@@ -483,7 +523,7 @@ class ModuleGenerator:
|
||||
*/
|
||||
export class {class_name}{extends_str} {{
|
||||
|
||||
{methods_code}
|
||||
{indented_methods}
|
||||
|
||||
}}
|
||||
"""
|
||||
@@ -497,17 +537,23 @@ export class {class_name}{extends_str} {{
|
||||
use_parent_instance: bool = True
|
||||
) -> str:
|
||||
"""Generate an object/namespace module."""
|
||||
# Convert methods to object methods
|
||||
methods_code = '\n\n '.join(methods.values())
|
||||
# Convert methods to object methods. Preserve original leading
|
||||
# whitespace from extracted methods by joining with blank lines only.
|
||||
methods_code = '\n\n'.join(methods.values())
|
||||
|
||||
# Add init function if using parent instance pattern
|
||||
# Add 4-space indentation to every line of methods_code to indent at object level
|
||||
indented_methods = '\n'.join(' ' + line if line.strip() else line
|
||||
for line in methods_code.split('\n'))
|
||||
|
||||
# Add init function if using parent instance pattern. Use 4-space
|
||||
# indentation for the init function to match method indentation.
|
||||
if use_parent_instance:
|
||||
init_func = """init(parent) {
|
||||
init_func = """ init(parent) {
|
||||
return {
|
||||
parent: parent
|
||||
};
|
||||
}"""
|
||||
methods_code = init_func + '\n\n ' + methods_code
|
||||
indented_methods = init_func + '\n\n' + indented_methods
|
||||
|
||||
code = f"""{imports_section}
|
||||
/**
|
||||
@@ -525,7 +571,7 @@ export class {class_name}{extends_str} {{
|
||||
*/
|
||||
export const {object_name} = {{
|
||||
|
||||
{methods_code}
|
||||
{indented_methods}
|
||||
|
||||
}};
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user