Implement 3 Game Modes: Complete All, Timed Challenge, and Score Target
Complete game mode system with dynamic UI and win conditions:
## Three Game Modes:
### 1. Complete All Tasks (Default)
- Finish every main task in the game
- Original gameplay experience
- Game ends when all tasks completed
### 2. Timed Challenge
- Race against the clock to complete as many tasks as possible
- Configurable time limits: 5, 10, 15, 20, or 30 minutes
- Countdown timer with color warnings (red < 30s, orange < 60s)
- Game ends when time runs out
### 3. Score Target
- Reach a target score to win
- Configurable targets: 500, 1000, 1500, 2000, or 3000 points
- Game ends immediately when target reached
- Strategic gameplay focusing on high-value tasks
## Enhanced UI Features:
### Game Mode Selection:
- Beautiful radio button interface on start screen
- Mode-specific configuration options (time/score targets)
- Dynamic show/hide of relevant settings
- Visual feedback with hover effects and selection states
### Smart Timer Display:
- Elapsed time for Complete All and Score Target modes
- Countdown timer for Timed Challenge mode
- Color-coded time warnings (red/orange when running low)
- Dynamic timer status indicators
### Dynamic Game Over Screen:
- Mode-specific victory messages and titles
- Shows selected game mode in final stats
- Different celebration text based on completion reason
- Comprehensive final statistics display
## Technical Implementation:
### Game State Enhancement:
- Added gameMode, timeLimit, scoreTarget to gameState
- Proper game state reset handling for all modes
- Mode persistence and configuration management
### Win Condition Logic:
- Score target checking on task completion
- Timer countdown with automatic game end
- Complete-all detection (existing functionality preserved)
- Multiple end game reasons ('time', 'score-target', 'complete-all')
### Event System:
- Game mode selection event listeners
- Dynamic configuration updates
- Real-time UI state management
- Mode-specific timer updates
**Ready to challenge yourself in 3 different ways! **
This commit is contained in:
parent
927c19c45c
commit
cc853ad667
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
135
game.js
135
game.js
|
|
@ -23,7 +23,10 @@ class TaskChallengeGame {
|
|||
usedMainTasks: [],
|
||||
usedConsequenceTasks: [],
|
||||
usedTaskImages: [], // Track which task images have been shown
|
||||
usedConsequenceImages: [] // Track which consequence images have been shown
|
||||
usedConsequenceImages: [], // Track which consequence images have been shown
|
||||
gameMode: 'complete-all', // Game mode: 'complete-all', 'timed', 'score-target'
|
||||
timeLimit: 300, // Time limit in seconds (5 minutes default)
|
||||
scoreTarget: 1000 // Score target (default 1000 points)
|
||||
};
|
||||
|
||||
this.timerInterval = null;
|
||||
|
|
@ -332,6 +335,9 @@ class TaskChallengeGame {
|
|||
document.getElementById('quit-btn').addEventListener('click', () => this.quitGame());
|
||||
document.getElementById('play-again-btn').addEventListener('click', () => this.resetGame());
|
||||
|
||||
// Game mode selection
|
||||
this.initializeGameModeListeners();
|
||||
|
||||
// Game actions
|
||||
document.getElementById('complete-btn').addEventListener('click', () => this.completeTask());
|
||||
document.getElementById('skip-btn').addEventListener('click', () => this.skipTask());
|
||||
|
|
@ -381,6 +387,36 @@ class TaskChallengeGame {
|
|||
this.loadSavedTheme();
|
||||
}
|
||||
|
||||
initializeGameModeListeners() {
|
||||
const gameModeRadios = document.querySelectorAll('input[name="gameMode"]');
|
||||
gameModeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', () => this.handleGameModeChange());
|
||||
});
|
||||
|
||||
// Initialize with default mode
|
||||
this.handleGameModeChange();
|
||||
}
|
||||
|
||||
handleGameModeChange() {
|
||||
const selectedMode = document.querySelector('input[name="gameMode"]:checked').value;
|
||||
this.gameState.gameMode = selectedMode;
|
||||
|
||||
// Show/hide configuration options based on selected mode
|
||||
document.getElementById('timed-config').style.display =
|
||||
selectedMode === 'timed' ? 'block' : 'none';
|
||||
document.getElementById('score-target-config').style.display =
|
||||
selectedMode === 'score-target' ? 'block' : 'none';
|
||||
|
||||
// Update game state with selected values
|
||||
if (selectedMode === 'timed') {
|
||||
this.gameState.timeLimit = parseInt(document.getElementById('time-limit-select').value);
|
||||
} else if (selectedMode === 'score-target') {
|
||||
this.gameState.scoreTarget = parseInt(document.getElementById('score-target-select').value);
|
||||
}
|
||||
|
||||
console.log(`Game mode changed to: ${selectedMode}`, this.gameState);
|
||||
}
|
||||
|
||||
setupKeyboardShortcuts() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Don't trigger shortcuts when typing in inputs or textareas
|
||||
|
|
@ -2607,6 +2643,13 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
const difficulty = this.gameState.currentTask.difficulty || 'Medium';
|
||||
const points = this.getPointsForDifficulty(difficulty);
|
||||
this.gameState.score += points;
|
||||
|
||||
// Check for score target win condition
|
||||
if (this.gameState.gameMode === 'score-target' && this.gameState.score >= this.gameState.scoreTarget) {
|
||||
this.updateStats(); // Update stats before ending game
|
||||
this.endGame('score-target');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load next main task (consequence tasks don't chain)
|
||||
|
|
@ -2666,10 +2709,10 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
this.endGame();
|
||||
}
|
||||
|
||||
endGame() {
|
||||
endGame(reason = 'complete-all') {
|
||||
this.gameState.isRunning = false;
|
||||
this.stopTimer();
|
||||
this.showFinalStats();
|
||||
this.showFinalStats(reason);
|
||||
this.showScreen('game-over-screen');
|
||||
}
|
||||
|
||||
|
|
@ -2685,8 +2728,15 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
completedCount: 0,
|
||||
skippedCount: 0,
|
||||
consequenceCount: 0,
|
||||
score: 0,
|
||||
lastSkippedTask: null,
|
||||
usedMainTasks: [],
|
||||
usedConsequenceTasks: []
|
||||
usedConsequenceTasks: [],
|
||||
usedTaskImages: [],
|
||||
usedConsequenceImages: [],
|
||||
gameMode: 'complete-all',
|
||||
timeLimit: 300,
|
||||
scoreTarget: 1000
|
||||
};
|
||||
|
||||
this.stopTimer();
|
||||
|
|
@ -2711,7 +2761,37 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
|
||||
const currentTime = Date.now();
|
||||
const elapsed = currentTime - this.gameState.startTime - this.gameState.totalPausedTime;
|
||||
const formattedTime = this.formatTime(elapsed);
|
||||
|
||||
let formattedTime;
|
||||
|
||||
if (this.gameState.gameMode === 'timed') {
|
||||
// Countdown timer for timed mode
|
||||
const remainingMs = (this.gameState.timeLimit * 1000) - elapsed;
|
||||
if (remainingMs <= 0) {
|
||||
// Time's up!
|
||||
formattedTime = '00:00';
|
||||
document.getElementById('timer').textContent = formattedTime;
|
||||
this.endGame('time');
|
||||
return;
|
||||
}
|
||||
formattedTime = this.formatTime(remainingMs);
|
||||
|
||||
// Change color when time is running low (less than 30 seconds)
|
||||
const timerElement = document.getElementById('timer');
|
||||
if (remainingMs <= 30000) {
|
||||
timerElement.style.color = '#ff4757';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else if (remainingMs <= 60000) {
|
||||
timerElement.style.color = '#ffa502';
|
||||
timerElement.style.fontWeight = 'bold';
|
||||
} else {
|
||||
timerElement.style.color = '';
|
||||
timerElement.style.fontWeight = '';
|
||||
}
|
||||
} else {
|
||||
// Normal elapsed timer for other modes
|
||||
formattedTime = this.formatTime(elapsed);
|
||||
}
|
||||
|
||||
document.getElementById('timer').textContent = formattedTime;
|
||||
|
||||
|
|
@ -2719,6 +2799,8 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
const timerStatus = document.getElementById('timer-status');
|
||||
if (this.gameState.isPaused) {
|
||||
timerStatus.textContent = '(PAUSED)';
|
||||
} else if (this.gameState.gameMode === 'timed') {
|
||||
timerStatus.textContent = '(TIME LEFT)';
|
||||
} else {
|
||||
timerStatus.textContent = '';
|
||||
}
|
||||
|
|
@ -2739,16 +2821,57 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
|
|||
document.getElementById('score').textContent = this.gameState.score;
|
||||
}
|
||||
|
||||
showFinalStats() {
|
||||
showFinalStats(reason = 'complete-all') {
|
||||
const currentTime = Date.now();
|
||||
const totalTime = currentTime - this.gameState.startTime - this.gameState.totalPausedTime;
|
||||
const formattedTime = this.formatTime(totalTime);
|
||||
|
||||
// Update the game over title based on end reason
|
||||
const gameOverTitle = document.querySelector('#game-over-screen h2');
|
||||
const gameOverMessage = document.querySelector('#game-over-screen p');
|
||||
|
||||
switch (reason) {
|
||||
case 'time':
|
||||
gameOverTitle.textContent = '⏰ Time\'s Up!';
|
||||
gameOverMessage.textContent = 'You ran out of time! See how many tasks you completed.';
|
||||
break;
|
||||
case 'score-target':
|
||||
gameOverTitle.textContent = '🏆 Target Reached!';
|
||||
gameOverMessage.textContent = `Congratulations! You reached the target score of ${this.gameState.scoreTarget} points!`;
|
||||
break;
|
||||
case 'complete-all':
|
||||
default:
|
||||
gameOverTitle.textContent = '🎉 All Tasks Complete!';
|
||||
gameOverMessage.textContent = 'Congratulations! You\'ve completed all available tasks!';
|
||||
break;
|
||||
}
|
||||
|
||||
document.getElementById('final-score').textContent = this.gameState.score;
|
||||
document.getElementById('final-time').textContent = formattedTime;
|
||||
document.getElementById('final-completed').textContent = this.gameState.completedCount;
|
||||
document.getElementById('final-skipped').textContent = this.gameState.skippedCount;
|
||||
document.getElementById('final-consequences').textContent = this.gameState.consequenceCount;
|
||||
|
||||
// Add game mode info to stats
|
||||
let gameModeText = '';
|
||||
switch (this.gameState.gameMode) {
|
||||
case 'timed':
|
||||
gameModeText = `Timed Challenge (${this.gameState.timeLimit / 60} minutes)`;
|
||||
break;
|
||||
case 'score-target':
|
||||
gameModeText = `Score Target (${this.gameState.scoreTarget} points)`;
|
||||
break;
|
||||
case 'complete-all':
|
||||
default:
|
||||
gameModeText = 'Complete All Tasks';
|
||||
break;
|
||||
}
|
||||
|
||||
// Show game mode info if element exists
|
||||
const gameModeElement = document.getElementById('final-game-mode');
|
||||
if (gameModeElement) {
|
||||
gameModeElement.textContent = gameModeText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
52
index.html
52
index.html
|
|
@ -51,6 +51,56 @@
|
|||
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
|
||||
</div>
|
||||
|
||||
<!-- Game Mode Selection -->
|
||||
<div class="game-mode-selection">
|
||||
<h3>Game Mode</h3>
|
||||
<div class="game-mode-options">
|
||||
<div class="game-mode-option">
|
||||
<input type="radio" id="mode-complete-all" name="gameMode" value="complete-all" checked>
|
||||
<label for="mode-complete-all">
|
||||
<strong>🎯 Complete All Tasks</strong>
|
||||
<p>Finish every task in the game</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="game-mode-option">
|
||||
<input type="radio" id="mode-timed" name="gameMode" value="timed">
|
||||
<label for="mode-timed">
|
||||
<strong>⏱️ Timed Challenge</strong>
|
||||
<p>Complete as many tasks as possible within the time limit</p>
|
||||
<div class="mode-config" id="timed-config" style="display: none;">
|
||||
<label>Time Limit:
|
||||
<select id="time-limit-select">
|
||||
<option value="300">5 minutes</option>
|
||||
<option value="600">10 minutes</option>
|
||||
<option value="900">15 minutes</option>
|
||||
<option value="1200">20 minutes</option>
|
||||
<option value="1800">30 minutes</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="game-mode-option">
|
||||
<input type="radio" id="mode-score-target" name="gameMode" value="score-target">
|
||||
<label for="mode-score-target">
|
||||
<strong>🏆 Score Target</strong>
|
||||
<p>Reach the target score to win</p>
|
||||
<div class="mode-config" id="score-target-config" style="display: none;">
|
||||
<label>Target Score:
|
||||
<select id="score-target-select">
|
||||
<option value="500">500 points</option>
|
||||
<option value="1000">1000 points</option>
|
||||
<option value="1500">1500 points</option>
|
||||
<option value="2000">2000 points</option>
|
||||
<option value="3000">3000 points</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options Menu -->
|
||||
<div class="options-section">
|
||||
<button id="options-menu-btn" class="btn btn-tertiary">⚙️ Options</button>
|
||||
|
|
@ -328,7 +378,9 @@
|
|||
<!-- Game Over Screen -->
|
||||
<div id="game-over-screen" class="screen">
|
||||
<h2>Game Complete!</h2>
|
||||
<p>Congratulations! You've completed all available tasks!</p>
|
||||
<div id="final-stats" class="final-stats">
|
||||
<p><strong>Game Mode:</strong> <span id="final-game-mode"></span></p>
|
||||
<p>Final Score: <span id="final-score"></span> points</p>
|
||||
<p>Final Time: <span id="final-time"></span></p>
|
||||
<p>Tasks Completed: <span id="final-completed"></span></p>
|
||||
|
|
|
|||
90
styles.css
90
styles.css
|
|
@ -949,6 +949,96 @@ body {
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Game Mode Selection */
|
||||
.game-mode-selection {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.game-mode-selection h3 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.game-mode-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.game-mode-option {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.game-mode-option input[type="radio"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-mode-option label {
|
||||
display: block;
|
||||
background: white;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.game-mode-option label:hover {
|
||||
border-color: #4facfe;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.2);
|
||||
}
|
||||
|
||||
.game-mode-option input[type="radio"]:checked + label {
|
||||
border-color: #4facfe;
|
||||
background: linear-gradient(135deg, #4facfe22, #00f2fe11);
|
||||
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.3);
|
||||
}
|
||||
|
||||
.game-mode-option label strong {
|
||||
color: #333;
|
||||
font-size: 1.1em;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.game-mode-option label p {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mode-config {
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.mode-config label {
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mode-config select {
|
||||
margin-left: 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.mercy-cost {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
|
|
|
|||
Loading…
Reference in New Issue