Implement streak bonus system with visual feedback

- Added streak tracking: currentStreak, totalStreakBonuses, lastStreakMilestone
- 10 bonus points awarded every 10 consecutive completed regular tasks
- Streak resets to 0 when task is skipped (consequence tasks don't affect streak)
- Real-time streak display in game stats with fire emoji and milestone highlighting
- Animated streak bonus notification with fire icon and gradient background
- Final stats display includes best streak and total streak bonus points earned
- Enhanced visual feedback with glowing effects for streak milestones (10+)
- Auto-save functionality preserves streak data across game sessions
This commit is contained in:
fritzsenpai 2025-09-26 11:47:40 -05:00
parent a74fefd1a3
commit f1f88d5f23
3 changed files with 166 additions and 5 deletions

94
game.js
View File

@ -26,7 +26,10 @@ class TaskChallengeGame {
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)
scoreTarget: 1000, // Score target (default 1000 points)
currentStreak: 0, // Track consecutive completed regular tasks
totalStreakBonuses: 0, // Track total streak bonus points earned
lastStreakMilestone: 0 // Track the last streak milestone reached
};
this.timerInterval = null;
@ -2751,6 +2754,44 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
}
}
checkStreakBonus() {
// Award streak bonus every 10 completed tasks
const streakMilestone = Math.floor(this.gameState.currentStreak / 10);
if (streakMilestone > this.gameState.lastStreakMilestone) {
this.gameState.lastStreakMilestone = streakMilestone;
return 10; // 10 bonus points per streak milestone
}
return 0;
}
showStreakBonusNotification(streak, bonusPoints) {
// Create streak bonus notification
const notification = document.createElement('div');
notification.className = 'streak-bonus-notification';
notification.innerHTML = `
<div class="streak-bonus-content">
<div class="streak-icon">🔥</div>
<div class="streak-text">
<div class="streak-title">${streak} Task Streak!</div>
<div class="streak-bonus">+${bonusPoints} Bonus Points</div>
</div>
</div>
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => notification.classList.add('show'), 10);
// Remove after 3 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => document.body.removeChild(notification), 300);
}, 3000);
}
completeTask() {
if (!this.gameState.isRunning || this.gameState.isPaused) return;
@ -2760,14 +2801,26 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
this.gameState.consequenceCount++;
// Clear the last skipped task when consequence is completed
this.gameState.lastSkippedTask = null;
// Consequence tasks don't award points
// Consequence tasks don't award points or affect streak
} else {
this.gameState.usedMainTasks.push(this.gameState.currentTask.id);
this.gameState.completedCount++;
// Increment streak for regular tasks
this.gameState.currentStreak++;
// Award points for completing main tasks
const difficulty = this.gameState.currentTask.difficulty || 'Medium';
const points = this.getPointsForDifficulty(difficulty);
this.gameState.score += points;
const basePoints = this.getPointsForDifficulty(difficulty);
this.gameState.score += basePoints;
// Check for streak bonus (every 10 consecutive completed tasks)
const streakBonus = this.checkStreakBonus();
if (streakBonus > 0) {
this.gameState.score += streakBonus;
this.gameState.totalStreakBonuses += streakBonus;
this.showStreakBonusNotification(this.gameState.currentStreak, streakBonus);
}
// Check for score target win condition
if (this.gameState.gameMode === 'score-target' && this.gameState.score >= this.gameState.scoreTarget) {
@ -2800,6 +2853,10 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
this.gameState.usedMainTasks.push(this.gameState.currentTask.id);
this.gameState.skippedCount++;
// Reset streak when task is skipped
this.gameState.currentStreak = 0;
this.gameState.lastStreakMilestone = 0;
// Load a consequence task
this.gameState.isConsequenceTask = true;
this.loadNextTask();
@ -2861,7 +2918,10 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
usedConsequenceImages: [],
gameMode: 'complete-all',
timeLimit: 300,
scoreTarget: 1000
scoreTarget: 1000,
currentStreak: 0,
totalStreakBonuses: 0,
lastStreakMilestone: 0
};
this.stopTimer();
@ -2944,6 +3004,20 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
document.getElementById('skipped-count').textContent = this.gameState.skippedCount;
document.getElementById('consequence-count').textContent = this.gameState.consequenceCount;
document.getElementById('score').textContent = this.gameState.score;
// Update streak display
const streakElement = document.getElementById('current-streak');
if (streakElement) {
streakElement.textContent = this.gameState.currentStreak;
// Add visual indicator for streak milestones
const streakContainer = streakElement.parentElement;
if (this.gameState.currentStreak >= 10) {
streakContainer.classList.add('streak-milestone');
} else {
streakContainer.classList.remove('streak-milestone');
}
}
}
showFinalStats(reason = 'complete-all') {
@ -2977,6 +3051,16 @@ ${usagePercent > 85 ? '⚠️ Storage getting full - consider deleting some imag
document.getElementById('final-skipped').textContent = this.gameState.skippedCount;
document.getElementById('final-consequences').textContent = this.gameState.consequenceCount;
// Update streak bonus stats
const bestStreakElement = document.getElementById('final-best-streak');
const streakBonusesElement = document.getElementById('final-streak-bonuses');
if (bestStreakElement) {
bestStreakElement.textContent = this.gameState.currentStreak;
}
if (streakBonusesElement) {
streakBonusesElement.textContent = this.gameState.totalStreakBonuses;
}
// Add game mode info to stats
let gameModeText = '';
switch (this.gameState.gameMode) {

View File

@ -378,6 +378,10 @@
<span class="stat-label">Completed:</span>
<span id="completed-count" class="stat-value">0</span>
</div>
<div class="stat streak-stat">
<span class="stat-label">Streak:</span>
<span id="current-streak" class="stat-value">0</span>🔥
</div>
<div class="stat">
<span class="stat-label">Skipped:</span>
<span id="skipped-count" class="stat-value">0</span>
@ -408,6 +412,8 @@
<p>Tasks Completed: <span id="final-completed"></span></p>
<p>Tasks Skipped: <span id="final-skipped"></span></p>
<p>Consequence Tasks: <span id="final-consequences"></span></p>
<p>Best Streak: <span id="final-best-streak"></span> 🔥</p>
<p>Streak Bonus Points: <span id="final-streak-bonuses"></span></p>
</div>
<button id="play-again-btn" class="btn btn-primary">Play Again</button>
</div>

View File

@ -378,6 +378,77 @@ body {
border-color: var(--primary-color);
background: rgba(var(--primary-color-rgb), 0.1);
}
/* Streak Bonus Notification */
.streak-bonus-notification {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
opacity: 0;
background: linear-gradient(135deg, #ff6b35, #f7931e);
color: white;
padding: 20px;
border-radius: 15px;
text-align: center;
box-shadow: 0 10px 30px rgba(255, 107, 53, 0.4);
z-index: 1002;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.2);
}
.streak-bonus-notification.show {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
.streak-bonus-content {
display: flex;
align-items: center;
gap: 15px;
}
.streak-icon {
font-size: 2.5em;
animation: fireFlicker 1s ease-in-out infinite alternate;
}
.streak-text {
text-align: left;
}
.streak-title {
font-size: 1.2em;
font-weight: bold;
margin-bottom: 5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.streak-bonus {
font-size: 1em;
opacity: 0.9;
font-weight: 500;
}
@keyframes fireFlicker {
0% { transform: rotate(-2deg) scale(1); }
100% { transform: rotate(2deg) scale(1.05); }
}
/* Streak Milestone Styling */
.streak-stat.streak-milestone {
background: linear-gradient(45deg, rgba(255, 107, 53, 0.1), rgba(247, 147, 30, 0.1));
border-radius: 8px;
padding: 5px 8px;
border: 1px solid rgba(255, 107, 53, 0.3);
animation: streakGlow 2s ease-in-out infinite alternate;
}
@keyframes streakGlow {
0% { box-shadow: 0 0 5px rgba(255, 107, 53, 0.3); }
100% { box-shadow: 0 0 15px rgba(255, 107, 53, 0.5); }
}
.audio-bar:nth-child(4) { animation-delay: 0.6s; }
.audio-bar:nth-child(5) { animation-delay: 0.8s; }