feat: Enhance key selection with additional key presets and improve random key generation logic

This commit is contained in:
Z. Cliffe Schreuders
2025-10-28 16:03:50 +00:00
parent 0ded5cae05
commit 5436abe538
4 changed files with 225 additions and 43 deletions

View File

@@ -256,10 +256,15 @@ export class KeyOperations {
// Create a visual representation of a key for the selection UI by building the actual key and scaling it down
const keyContainer = this.parent.scene.add.container(0, 0);
// Save the original key data before temporarily changing it
// Save the original key data and pin count before temporarily changing them
const originalKeyData = this.parent.keyData;
const originalPinCount = this.parent.pinCount;
// Temporarily set the key data to create the key
// Temporarily set the key data and pin count to create this specific key
this.parent.keyData = keyData;
this.parent.pinCount = keyData.pinCount || 5;
// Create the key with this specific key data
this.createKey();
// Get the key group and scale it down
@@ -295,8 +300,9 @@ export class KeyOperations {
keyContainer.add(keyGroup);
}
// Restore the original key data
// Restore the original key data and pin count
this.parent.keyData = originalKeyData;
this.parent.pinCount = originalPinCount;
return keyContainer;
}

View File

@@ -44,8 +44,8 @@ export class KeySelection {
// 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);
// Generate random cut depth between 25-65 (middle range for realistic key variation)
cuts.push(Math.floor(Math.random() * 40) + 25);
}
return {
id: `random_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,

View File

@@ -505,8 +505,9 @@ export class LockpickingMinigamePhaser extends MinigameScene {
// Create a UI for selecting between multiple keys
// keys: array of key objects with id, cuts, and optional name properties
// correctKeyId: ID of the correct key (if null, uses index 0 as fallback)
// Shows 3 keys at a time with navigation buttons for more than 3 keys
// Find the correct key index
// Find the correct key index in the original array
let correctKeyIndex = 0;
if (correctKeyId) {
correctKeyIndex = keys.findIndex(key => key.id === correctKeyId);
@@ -530,6 +531,23 @@ export class LockpickingMinigamePhaser extends MinigameScene {
// Reset pins to their original positions before showing key selection
this.lockConfig.resetPinsToOriginalPositions();
// Layout constants
const keyWidth = 140;
const keyHeight = 80;
const spacing = 20;
const padding = 20;
const labelHeight = 30; // Space for key label below each key
const keysPerPage = 3; // Always show 3 keys at a time
const buttonWidth = 30;
const buttonHeight = 30;
const buttonSpacing = 10; // Space between button and keys
// Calculate container dimensions (always 3 keys wide + buttons on sides with minimal spacing)
// For 3 keys: [button] padding [key1] spacing [key2] spacing [key3] padding [button]
const keysWidth = (keysPerPage - 1) * (keyWidth + spacing) + keyWidth; // 3 keys with spacing between them
const containerWidth = keysWidth + (keys.length > keysPerPage ? buttonWidth * 2 + buttonSpacing * 2 + padding * 2 : padding * 2);
const containerHeight = keyHeight + labelHeight + spacing + padding * 2 + 50; // +50 for title
// Create container for key selection - positioned in the middle but below pins
const keySelectionContainer = this.scene.add.container(0, 230);
keySelectionContainer.setDepth(1000); // High z-index to appear above everything
@@ -537,13 +555,14 @@ export class LockpickingMinigamePhaser extends MinigameScene {
// Add background
const background = this.scene.add.graphics();
background.fillStyle(0x000000, 0.8);
background.fillRect(0, 0, 700, 180);
background.fillRect(0, 0, containerWidth, containerHeight);
background.lineStyle(2, 0xffffff);
background.strokeRect(0, 0, 600, 170);
background.strokeRect(0, 0, containerWidth - 1, containerHeight - 1);
keySelectionContainer.add(background);
// Add title
const title = this.scene.add.text(300, 15, 'Select the correct key', {
const titleX = containerWidth / 2;
const title = this.scene.add.text(titleX, 15, 'Select the correct key', {
fontSize: '24px',
fill: '#ffffff',
fontFamily: 'VT323',
@@ -551,41 +570,171 @@ export class LockpickingMinigamePhaser extends MinigameScene {
title.setOrigin(0.5, 0);
keySelectionContainer.add(title);
// Create key options
const keyWidth = 140;
const keyHeight = 80;
const spacing = 20;
const startX = 50;
const startY = 50;
// Track current page
let currentPage = 0;
const totalPages = Math.ceil(keys.length / keysPerPage);
keys.forEach((keyData, index) => {
const keyX = startX + index * (keyWidth + spacing);
const keyY = startY;
// Create navigation buttons if more than 3 keys
let prevButton = null;
let nextButton = null;
let prevText = null;
let nextText = null;
let pageIndicator = null;
let itemsToRemoveNext = null; // Track items for cleanup
// Create a function to render the current page of keys
const renderKeyPage = () => {
// Remove any existing key visuals and labels from the previous page
const itemsToRemove = [];
keySelectionContainer.list.forEach(item => {
if (item !== background && item !== title && item !== prevButton && item !== nextButton && item !== pageIndicator && item !== prevText && item !== nextText) {
itemsToRemove.push(item);
}
});
itemsToRemove.forEach(item => item.destroy());
// Create key visual representation
const keyVisual = this.keyOps.createKeyVisual(keyData, keyWidth, keyHeight);
keyVisual.setPosition(keyX, keyY);
keySelectionContainer.add(keyVisual);
// Calculate which keys to show on this page
const startIndex = currentPage * keysPerPage;
const endIndex = Math.min(startIndex + keysPerPage, keys.length);
const pageKeys = keys.slice(startIndex, endIndex);
// Make key clickable
keyVisual.setInteractive(new Phaser.Geom.Rectangle(0, 0, keyWidth, keyHeight), Phaser.Geom.Rectangle.Contains);
keyVisual.on('pointerdown', () => {
// Close the popup
keySelectionContainer.destroy();
// Trigger key selection and insertion
this.keyOps.selectKey(index, correctKeyIndex, keyData);
// Display keys for this page
// Position: [button] buttonSpacing [keys] buttonSpacing [button]
const keysStartX = (keys.length > keysPerPage ? buttonWidth + buttonSpacing : padding);
const startX = keysStartX + padding / 2;
const startY = 50;
pageKeys.forEach((keyData, pageIndex) => {
const actualIndex = startIndex + pageIndex;
const keyX = startX + pageIndex * (keyWidth + spacing);
const keyY = startY;
// Create key visual representation
const keyVisual = this.keyOps.createKeyVisual(keyData, keyWidth, keyHeight);
keyVisual.setPosition(keyX, keyY);
keySelectionContainer.add(keyVisual);
// Make key clickable
keyVisual.setInteractive(new Phaser.Geom.Rectangle(0, 0, keyWidth, keyHeight), Phaser.Geom.Rectangle.Contains);
keyVisual.on('pointerdown', () => {
// Close the popup
keySelectionContainer.destroy();
// Trigger key selection and insertion
this.keyOps.selectKey(actualIndex, correctKeyIndex, keyData);
});
// Add key label (use name if available, otherwise use number)
const keyName = keyData.name || `Key ${actualIndex + 1}`;
const keyLabel = this.scene.add.text(keyX + keyWidth/2, keyY + keyHeight + 5, keyName, {
fontSize: '16px',
fill: '#ffffff',
fontFamily: 'VT323'
});
keyLabel.setOrigin(0.5, 0);
keySelectionContainer.add(keyLabel);
});
// Add key label (use name if available, otherwise use number)
const keyName = keyData.name || `Key ${index + 1}`;
const keyLabel = this.scene.add.text(keyX + keyWidth/2, keyY + keyHeight + 5, keyName, {
fontSize: '16px',
// Update page indicator
if (pageIndicator) {
pageIndicator.setText(`${currentPage + 1}/${totalPages}`);
}
// Update button visibility
if (prevButton) {
if (currentPage > 0) {
prevButton.setVisible(true);
prevText.setVisible(true);
} else {
prevButton.setVisible(false);
prevText.setVisible(false);
}
}
if (nextButton) {
if (currentPage < totalPages - 1) {
nextButton.setVisible(true);
nextText.setVisible(true);
} else {
nextButton.setVisible(false);
nextText.setVisible(false);
}
}
};
if (keys.length > keysPerPage) {
// Position buttons on the sides of the keys, vertically centered
const keysAreaCenterY = 50 + (keyHeight + labelHeight) / 2;
// Previous button (left side)
prevButton = this.scene.add.graphics();
prevButton.fillStyle(0x444444);
prevButton.fillRect(0, 0, buttonWidth, buttonHeight);
prevButton.lineStyle(2, 0xffffff);
prevButton.strokeRect(0, 0, buttonWidth, buttonHeight);
prevButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonWidth, buttonHeight), Phaser.Geom.Rectangle.Contains);
prevButton.on('pointerdown', () => {
if (currentPage > 0) {
currentPage--;
renderKeyPage();
}
});
prevButton.setPosition(padding / 2, keysAreaCenterY - buttonHeight / 2);
prevButton.setDepth(1001);
prevButton.setVisible(false); // Initially hidden
keySelectionContainer.add(prevButton);
// Previous button text
prevText = this.scene.add.text(padding / 2 + buttonWidth / 2, keysAreaCenterY, '', {
fontSize: '20px',
fill: '#ffffff',
fontFamily: 'VT323'
});
keyLabel.setOrigin(0.5, 0);
keySelectionContainer.add(keyLabel);
});
prevText.setOrigin(0.5, 0.5);
prevText.setDepth(1002);
prevText.setVisible(false); // Initially hidden
keySelectionContainer.add(prevText);
// Next button (right side)
nextButton = this.scene.add.graphics();
nextButton.fillStyle(0x444444);
nextButton.fillRect(0, 0, buttonWidth, buttonHeight);
nextButton.lineStyle(2, 0xffffff);
nextButton.strokeRect(0, 0, buttonWidth, buttonHeight);
nextButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonWidth, buttonHeight), Phaser.Geom.Rectangle.Contains);
nextButton.on('pointerdown', () => {
if (currentPage < totalPages - 1) {
currentPage++;
renderKeyPage();
}
});
nextButton.setPosition(containerWidth - padding / 2 - buttonWidth, keysAreaCenterY - buttonHeight / 2);
nextButton.setDepth(1001);
nextButton.setVisible(false); // Initially hidden
keySelectionContainer.add(nextButton);
// Next button text
nextText = this.scene.add.text(containerWidth - padding / 2 - buttonWidth / 2, keysAreaCenterY, '', {
fontSize: '20px',
fill: '#ffffff',
fontFamily: 'VT323'
});
nextText.setOrigin(0.5, 0.5);
nextText.setDepth(1002);
nextText.setVisible(false); // Initially hidden
keySelectionContainer.add(nextText);
// Page indicator - centered below all keys
pageIndicator = this.scene.add.text(containerWidth / 2, containerHeight - 20, `1/${totalPages}`, {
fontSize: '12px',
fill: '#888888',
fontFamily: 'VT323'
});
pageIndicator.setOrigin(0.5, 0.5);
keySelectionContainer.add(pageIndicator);
}
// Render the first page
renderKeyPage();
this.keySelectionContainer = keySelectionContainer;
}

View File

@@ -254,6 +254,9 @@
<div class="key-preset" data-key="key3">Key 3 (Partial)</div>
<div class="key-preset" data-key="key4">Key 4 (Random)</div>
<div class="key-preset" data-key="key5">Key 5 (Deep)</div>
<div class="key-preset" data-key="key6">Key 6 (Shallow)</div>
<div class="key-preset" data-key="key7">Key 7 (Mixed)</div>
<div class="key-preset" data-key="key8">Key 8 (Reverse)</div>
<button class="key-preset" id="keySelectionToggle">🎲 Random Key Selection</button>
</div>
@@ -293,28 +296,43 @@
this.keyPresets = {
key1: {
name: "Key 1 (Correct)",
cuts: [25, 50, 75, 30, 60], // This will be overridden by auto-generation
cuts: [35, 50, 65, 40, 55], // This will be overridden by auto-generation
description: "This key is auto-generated to match the actual pin heights"
},
key2: {
name: "Key 2 (Wrong)",
cuts: [10, 20, 30, 40, 50],
cuts: [25, 30, 35, 40, 45],
description: "This key has cuts that don't match the pins"
},
key3: {
name: "Key 3 (Partial)",
cuts: [25, 50, 75, 40, 60],
cuts: [35, 50, 65, 45, 55],
description: "This key has some correct cuts but not all"
},
key4: {
name: "Key 4 (Random)",
cuts: [15, 45, 80, 35, 55],
cuts: [28, 48, 62, 38, 52],
description: "Random cut pattern - might work by chance"
},
key5: {
name: "Key 5 (Deep)",
cuts: [80, 90, 95, 85, 88],
cuts: [60, 63, 65, 62, 61],
description: "Very deep cuts - probably too deep"
},
key6: {
name: "Key 6 (Shallow)",
cuts: [25, 26, 27, 28, 29],
description: "Very shallow cuts - not deep enough"
},
key7: {
name: "Key 7 (Mixed)",
cuts: [40, 52, 60, 35, 58],
description: "Similar cuts but slightly off in each position"
},
key8: {
name: "Key 8 (Reverse)",
cuts: [55, 40, 65, 50, 35],
description: "Cuts in reverse order - interesting pattern"
}
};
@@ -388,7 +406,7 @@
// Generate new random keys for selection
this.currentGame.keySelectionMode = true; // Mark that we're in key selection mode
this.currentGame.createKeysForChallenge('correct_key');
this.currentGame.keySelection.createKeysForChallenge('correct_key');
// Update feedback
document.getElementById('feedback').textContent =
@@ -484,7 +502,16 @@
// If in key mode, automatically show key selection
if (this.currentMode === 'key') {
setTimeout(() => {
this.currentGame.startWithKeySelection();
// Prepare all preset keys for selection
const allKeys = Object.values(this.keyPresets).map((preset, index) => ({
id: `key_${index}`,
cuts: preset.cuts,
name: preset.name,
pinCount: preset.cuts.length
}));
// Start with key selection showing all 8 keys
this.currentGame.startWithKeySelection(allKeys, 'key_0');
}, 500); // Small delay to ensure game is fully initialized
}
} catch (error) {