mutable gameState = ({
level: 1,
score: 0,
lives: 3,
isPlaying: false,
isPaused: false,
currentChallenge: null,
challengeIndex: 0,
eventsProcessed: 0,
correctDecisions: 0,
throughput: 0,
avgLatency: 0,
streak: 0,
bestStreak: 0,
levelComplete: false,
gameOver: false,
showFeedback: false,
feedbackMessage: "",
feedbackType: "info",
timeRemaining: 60,
dataBuffer: [],
windowResults: [],
detectedAnomalies: [],
lateEvents: []
})
// Level Definitions
levelConfigs = [
{
level: 1,
name: "Basic Windowing",
description: "Learn to apply tumbling, sliding, and session windows to aggregate sensor data",
challengeCount: 8,
timeLimit: 90,
eventRate: 1500,
latencyPenalty: 50,
accuracyBonus: 100,
requiredScore: 500
},
{
level: 2,
name: "Complex Event Processing",
description: "Detect patterns, handle late events, and correlate multiple sensor streams",
challengeCount: 10,
timeLimit: 120,
eventRate: 1000,
latencyPenalty: 75,
accuracyBonus: 150,
requiredScore: 1000
},
{
level: 3,
name: "Anomaly Detection",
description: "Identify anomalies, manage backpressure, and optimize latency vs accuracy",
challengeCount: 12,
timeLimit: 150,
eventRate: 800,
latencyPenalty: 100,
accuracyBonus: 200,
requiredScore: 1500
}
]
// Challenge Scenarios for Each Level - Level 1
level1Challenges = [
{
id: "l1_tumbling_1",
type: "window_selection",
scenario: "Temperature sensors report every second. You need to calculate the average temperature for each distinct 10-second period with NO overlap between calculations.",
dataPreview: [22.1, 22.3, 22.8, 23.1, 22.9, 23.4, 23.2, 22.7, 22.5, 22.8],
sensorType: "temperature",
options: [
{ id: "tumbling", text: "Tumbling Window (10s)", correct: true },
{ id: "sliding", text: "Sliding Window (10s, slide 2s)", correct: false },
{ id: "session", text: "Session Window (gap 5s)", correct: false }
],
explanation: "Tumbling windows divide data into non-overlapping, fixed-size segments. Perfect for calculating distinct period averages without double-counting any data point.",
points: 100
},
{
id: "l1_sliding_1",
type: "window_selection",
scenario: "You're monitoring factory pressure readings and need a MOVING AVERAGE that updates every 2 seconds based on the last 10 seconds of data.",
dataPreview: [101.2, 101.5, 101.8, 102.1, 101.9, 102.3, 102.0, 101.7, 101.4, 101.6],
sensorType: "pressure",
options: [
{ id: "tumbling", text: "Tumbling Window (10s)", correct: false },
{ id: "sliding", text: "Sliding Window (10s, slide 2s)", correct: true },
{ id: "session", text: "Session Window (gap 3s)", correct: false }
],
explanation: "Sliding windows overlap, providing continuous updates. A 10-second window sliding every 2 seconds gives you a moving average that smooths out noise while staying responsive.",
points: 100
},
{
id: "l1_session_1",
type: "window_selection",
scenario: "Motion sensors in a smart home detect sporadic movement. You want to group activity into 'sessions' - periods of continuous activity separated by at least 30 seconds of inactivity.",
dataPreview: ["motion", "motion", "motion", "(gap 45s)", "motion", "motion"],
sensorType: "motion",
options: [
{ id: "tumbling", text: "Tumbling Window (30s)", correct: false },
{ id: "sliding", text: "Sliding Window (30s, slide 10s)", correct: false },
{ id: "session", text: "Session Window (gap 30s)", correct: true }
],
explanation: "Session windows dynamically group events based on activity gaps. When no events arrive for the gap duration, the session closes. Perfect for user sessions or activity periods.",
points: 100
},
{
id: "l1_aggregate_1",
type: "aggregation",
scenario: "Given these humidity readings in a 10-second tumbling window: [45, 47, 52, 48, 51, 49, 46, 50, 53, 48], what is the correct window output?",
dataPreview: [45, 47, 52, 48, 51, 49, 46, 50, 53, 48],
sensorType: "humidity",
options: [
{ id: "avg_only", text: "Average: 48.9", correct: false },
{ id: "full_stats", text: "Count: 10, Avg: 48.9, Min: 45, Max: 53", correct: true },
{ id: "sum_only", text: "Sum: 489", correct: false },
{ id: "latest", text: "Latest: 48", correct: false }
],
explanation: "Stream processing typically outputs comprehensive aggregations including count, average, min, and max. This provides complete context for downstream analysis and alerting.",
points: 75
},
{
id: "l1_event_time_1",
type: "concept",
scenario: "A sensor event occurred at 10:00:00 (event time) but arrived at the processing system at 10:00:05 (processing time) due to network delay. For accurate analytics, which timestamp should determine which window the event belongs to?",
dataPreview: ["Event time: 10:00:00", "Processing time: 10:00:05", "Network delay: 5s"],
sensorType: "timing",
options: [
{ id: "processing", text: "Processing time (10:00:05)", correct: false },
{ id: "event", text: "Event time (10:00:00)", correct: true },
{ id: "either", text: "Either works - they're interchangeable", correct: false }
],
explanation: "Event time reflects when the condition actually occurred. Using processing time would group delayed events into wrong windows, corrupting analytics. IoT systems with variable network latency must use event time.",
points: 100
}
]
// Level 2 Challenges
level2Challenges = [
{
id: "l2_late_1",
type: "late_event",
scenario: "Your window closed at 10:01:00. A sensor reading with event time 10:00:45 arrives at 10:01:10 (10 seconds late). Your watermark allows 15 seconds of lateness. What should happen?",
dataPreview: ["Window: 10:00:00 - 10:01:00", "Late event: 10:00:45", "Arrival: 10:01:10", "Allowed lateness: 15s"],
sensorType: "timing",
options: [
{ id: "drop", text: "Drop the event - window already closed", correct: false },
{ id: "accept", text: "Accept and update the window result", correct: true },
{ id: "next_window", text: "Put in next window (10:01:00 - 10:02:00)", correct: false }
],
explanation: "The event is only 10 seconds late, within the 15-second allowed lateness. Stream processors re-open windows to incorporate late data and emit updated results (a 'refinement').",
points: 150
},
{
id: "l2_pattern_1",
type: "pattern_detection",
scenario: "You're implementing a temperature spike detector. Define a pattern: 'temperature increases by more than 5 degrees within 10 seconds.' Given this stream, does a spike occur?",
dataPreview: [
{ time: "00:00", temp: 22.0 },
{ time: "00:03", temp: 23.5 },
{ time: "00:05", temp: 25.0 },
{ time: "00:08", temp: 28.5 },
{ time: "00:12", temp: 29.0 }
],
sensorType: "temperature",
options: [
{ id: "yes_pattern", text: "Yes - 22.0 to 28.5 in 8 seconds (+6.5C)", correct: true },
{ id: "no_pattern", text: "No - increases are gradual", correct: false },
{ id: "maybe", text: "Cannot determine from this data", correct: false }
],
explanation: "From 00:00 (22.0C) to 00:08 (28.5C) is a 6.5C increase in 8 seconds, exceeding the +5C/10s threshold. Pattern detected! This would trigger an alert in a CEP system.",
points: 150
},
{
id: "l2_backpressure_1",
type: "system_management",
scenario: "Your stream processor is receiving 10,000 events/second but can only process 8,000 events/second. Messages are backing up. What's the best immediate response?",
dataPreview: ["Input rate: 10,000 evt/s", "Processing capacity: 8,000 evt/s", "Buffer: Growing"],
sensorType: "system",
options: [
{ id: "drop", text: "Drop excess events randomly", correct: false },
{ id: "backpressure", text: "Apply backpressure - signal upstream to slow down", correct: true },
{ id: "buffer_infinite", text: "Keep buffering - memory is cheap", correct: false }
],
explanation: "Backpressure propagates congestion upstream, allowing producers to adapt (slow down, buffer, or drop intentionally). Random dropping loses important events; infinite buffering leads to OOM crashes.",
points: 150
}
]
// Level 3 Challenges
level3Challenges = [
{
id: "l3_anomaly_zscore_1",
type: "anomaly_detection",
scenario: "Temperature readings have mean=25C and stddev=2C. A new reading of 32C arrives. Using Z-score threshold of 2.5, is this an anomaly?",
dataPreview: ["Mean: 25C", "StdDev: 2C", "New reading: 32C", "Threshold: |Z| > 2.5"],
sensorType: "temperature",
options: [
{ id: "anomaly", text: "Yes - Z-score is 3.5, exceeds threshold 2.5", correct: true },
{ id: "normal", text: "No - 32C is within normal range", correct: false },
{ id: "need_more", text: "Need more data to determine", correct: false }
],
explanation: "Z-score = (32 - 25) / 2 = 3.5. Since |3.5| > 2.5 threshold, this is a statistical anomaly. In a normal distribution, values beyond 2.5 standard deviations occur less than 1.2% of the time.",
points: 200
},
{
id: "l3_tradeoff_1",
type: "latency_decision",
scenario: "You can detect anomalies in two ways: (A) Check each reading immediately (latency: 10ms, accuracy: 85%) or (B) Analyze 30-second windows with statistical context (latency: 30s, accuracy: 98%). Application: Detecting fraud in payment transactions.",
dataPreview: ["Option A: 10ms latency, 85% accuracy", "Option B: 30s latency, 98% accuracy"],
sensorType: "fraud",
options: [
{ id: "immediate", text: "Immediate (10ms, 85%) - speed matters for fraud", correct: true },
{ id: "windowed", text: "Windowed (30s, 98%) - accuracy is paramount", correct: false },
{ id: "hybrid", text: "It depends on the fraud value threshold", correct: false }
],
explanation: "For fraud detection, a 30-second delay means the fraudulent transaction completes before detection. Even 85% accuracy with immediate blocking is better than 98% accuracy after the money is gone. Speed trumps accuracy for real-time fraud prevention.",
points: 200
}
]
// Get challenges for current level
currentLevelChallenges = {
if (gameState.level === 1) return level1Challenges;
if (gameState.level === 2) return level2Challenges;
if (gameState.level === 3) return level3Challenges;
return level1Challenges;
}
currentLevelConfig = levelConfigs[gameState.level - 1]
// Game Functions
function startGame() {
mutable gameState = {
...gameState,
isPlaying: true,
isPaused: false,
score: 0,
lives: 3,
challengeIndex: 0,
streak: 0,
correctDecisions: 0,
eventsProcessed: 0,
showFeedback: false,
levelComplete: false,
gameOver: false,
currentChallenge: currentLevelChallenges[0],
timeRemaining: currentLevelConfig.timeLimit,
dataBuffer: []
};
}
function handleAnswer(optionId, isCorrect) {
const challenge = gameState.currentChallenge;
let newScore = gameState.score;
let newLives = gameState.lives;
let newStreak = gameState.streak;
let newCorrect = gameState.correctDecisions;
let feedbackType = "info";
let feedbackMessage = "";
if (isCorrect) {
newScore += challenge.points + (gameState.streak * 10);
newStreak += 1;
newCorrect += 1;
feedbackType = "success";
feedbackMessage = `Correct! +${challenge.points} points. ${challenge.explanation}`;
} else {
newLives -= 1;
newStreak = 0;
feedbackType = "error";
feedbackMessage = `Incorrect. ${challenge.explanation}`;
}
mutable gameState = {
...gameState,
score: newScore,
lives: newLives,
streak: newStreak,
bestStreak: Math.max(gameState.bestStreak, newStreak),
correctDecisions: newCorrect,
eventsProcessed: gameState.eventsProcessed + 1,
showFeedback: true,
feedbackMessage: feedbackMessage,
feedbackType: feedbackType,
gameOver: newLives <= 0
};
}
function nextChallenge() {
const nextIndex = gameState.challengeIndex + 1;
const challenges = currentLevelChallenges;
if (nextIndex >= challenges.length) {
// Level complete
if (gameState.score >= currentLevelConfig.requiredScore) {
if (gameState.level < 3) {
// Advance to next level
mutable gameState = {
...gameState,
level: gameState.level + 1,
challengeIndex: 0,
showFeedback: false,
levelComplete: true,
currentChallenge: null
};
} else {
// Game complete!
mutable gameState = {
...gameState,
levelComplete: true,
gameOver: true,
showFeedback: false
};
}
} else {
// Not enough points, try again
mutable gameState = {
...gameState,
challengeIndex: 0,
showFeedback: false,
currentChallenge: challenges[0]
};
}
} else {
mutable gameState = {
...gameState,
challengeIndex: nextIndex,
showFeedback: false,
currentChallenge: challenges[nextIndex]
};
}
}
function resetGame() {
mutable gameState = {
level: 1,
score: 0,
lives: 3,
isPlaying: false,
isPaused: false,
currentChallenge: null,
challengeIndex: 0,
eventsProcessed: 0,
correctDecisions: 0,
throughput: 0,
avgLatency: 0,
streak: 0,
bestStreak: 0,
levelComplete: false,
gameOver: false,
showFeedback: false,
feedbackMessage: "",
feedbackType: "info",
timeRemaining: 60,
dataBuffer: [],
windowResults: [],
detectedAnomalies: [],
lateEvents: []
};
}