viewof gameState = {
const initialState = {
currentLevel: 1,
score: 0,
totalPlaced: 0,
selectedComponent: null,
placedComponents: {
device: [],
network: [],
cloud: [],
application: []
},
feedback: null,
levelComplete: false,
gameComplete: false
};
return Inputs.input(initialState);
}
// Component definitions for all levels
componentDatabase = ({
// Level 1: Basic components (6 components)
level1: [
{ id: "temp-sensor", name: "Temperature Sensor", layer: "device", icon: "T", description: "Measures ambient temperature" },
{ id: "wifi-module", name: "Wi-Fi Module", layer: "network", icon: "W", description: "Wireless connectivity" },
{ id: "time-series-db", name: "Time-Series DB", layer: "cloud", icon: "DB", description: "Stores sensor data over time" },
{ id: "dashboard", name: "Dashboard", layer: "application", icon: "D", description: "Visualizes data for users" },
{ id: "mcu", name: "Microcontroller (MCU)", layer: "device", icon: "M", description: "Processes sensor data locally" },
{ id: "mqtt-broker", name: "MQTT Broker", layer: "network", icon: "Q", description: "Message queue for IoT" }
],
// Level 2: Intermediate components (10 components)
level2: [
{ id: "accelerometer", name: "Accelerometer", layer: "device", icon: "A", description: "Measures motion/vibration" },
{ id: "actuator", name: "Actuator/Motor", layer: "device", icon: "G", description: "Physical output device" },
{ id: "battery", name: "Battery Module", layer: "device", icon: "B", description: "Power source for devices" },
{ id: "lora-gateway", name: "LoRa Gateway", layer: "network", icon: "L", description: "Long-range wireless gateway" },
{ id: "edge-gateway", name: "Edge Gateway", layer: "network", icon: "E", description: "Local processing hub" },
{ id: "stream-processor", name: "Stream Processor", layer: "cloud", icon: "S", description: "Real-time data processing" },
{ id: "ml-engine", name: "ML Engine", layer: "cloud", icon: "ML", description: "Machine learning inference" },
{ id: "mobile-app", name: "Mobile App", layer: "application", icon: "P", description: "User mobile interface" },
{ id: "alert-service", name: "Alert Service", layer: "application", icon: "!", description: "Notification system" },
{ id: "analytics-tool", name: "Analytics Tool", layer: "application", icon: "C", description: "Data analysis interface" }
],
// Level 3: Advanced components (12 components)
level3: [
{ id: "pressure-sensor", name: "Pressure Sensor", layer: "device", icon: "P", description: "Measures pressure/force" },
{ id: "humidity-sensor", name: "Humidity Sensor", layer: "device", icon: "H", description: "Measures moisture levels" },
{ id: "gps-module", name: "GPS Module", layer: "device", icon: "G", description: "Location tracking" },
{ id: "zigbee-coordinator", name: "Zigbee Coordinator", layer: "network", icon: "Z", description: "Mesh network hub" },
{ id: "protocol-translator", name: "Protocol Translator", layer: "network", icon: "T", description: "Converts between protocols" },
{ id: "vpn-gateway", name: "VPN Gateway", layer: "network", icon: "V", description: "Secure network tunnel" },
{ id: "api-gateway", name: "API Gateway", layer: "cloud", icon: "A", description: "REST API endpoint" },
{ id: "data-lake", name: "Data Lake", layer: "cloud", icon: "L", description: "Raw data storage" },
{ id: "event-hub", name: "Event Hub", layer: "cloud", icon: "E", description: "Event ingestion service" },
{ id: "web-portal", name: "Web Portal", layer: "application", icon: "W", description: "Browser-based interface" },
{ id: "automation-engine", name: "Automation Engine", layer: "application", icon: "R", description: "Rule-based automation" },
{ id: "reporting-service", name: "Reporting Service", layer: "application", icon: "R", description: "Generate reports" }
]
})
// Get components for current level
currentLevelComponents = {
const level = gameState.currentLevel;
if (level === 1) return componentDatabase.level1;
if (level === 2) return componentDatabase.level2;
return componentDatabase.level3;
}
// Layer definitions with colors
layerDefinitions = [
{ id: "device", name: "Layer 1: Device/Perception", color: "#16A085", description: "Physical sensors, actuators, MCUs, power" },
{ id: "network", name: "Layer 2: Network/Gateway", color: "#27AE60", description: "Connectivity, protocols, gateways, brokers" },
{ id: "cloud", name: "Layer 3: Processing/Cloud", color: "#2980B9", description: "Storage, processing, ML, APIs" },
{ id: "application", name: "Layer 4: Application", color: "#8E44AD", description: "Dashboards, apps, alerts, analytics" }
]
// Check if component placement is correct
checkPlacement = (componentId, targetLayer) => {
const component = currentLevelComponents.find(c => c.id === componentId);
return component && component.layer === targetLayer;
}
// Calculate available (unplaced) components
availableComponents = {
const placed = [
...gameState.placedComponents.device,
...gameState.placedComponents.network,
...gameState.placedComponents.cloud,
...gameState.placedComponents.application
];
return currentLevelComponents.filter(c => !placed.includes(c.id));
}
// Check if level is complete
isLevelComplete = availableComponents.length === 0 &&
(gameState.placedComponents.device.length +
gameState.placedComponents.network.length +
gameState.placedComponents.cloud.length +
gameState.placedComponents.application.length) === currentLevelComponents.length
// Game UI
html`
<div style="font-family: system-ui, -apple-system, sans-serif; max-width: 900px; margin: 0 auto;">
<!-- Header with Score and Level -->
<div style="display: flex; justify-content: space-between; align-items: center; padding: 15px; background: linear-gradient(135deg, #2C3E50, #34495E); color: white; border-radius: 10px; margin-bottom: 20px;">
<div>
<span style="font-size: 1.4em; font-weight: bold;">Level ${gameState.currentLevel} of 3</span>
<span style="margin-left: 15px; opacity: 0.9;">
${gameState.currentLevel === 1 ? "Basic Components" : gameState.currentLevel === 2 ? "Intermediate" : "Advanced"}
</span>
</div>
<div style="text-align: right;">
<div style="font-size: 1.2em;">Score: <strong>${gameState.score}</strong></div>
<div style="font-size: 0.9em; opacity: 0.8;">
${availableComponents.length} components remaining
</div>
</div>
</div>
<!-- Feedback Message -->
${gameState.feedback ? html`
<div style="padding: 12px 20px; margin-bottom: 15px; border-radius: 8px; text-align: center; font-weight: 500;
background: ${gameState.feedback.correct ? '#d4edda' : '#f8d7da'};
color: ${gameState.feedback.correct ? '#155724' : '#721c24'};
border: 1px solid ${gameState.feedback.correct ? '#c3e6cb' : '#f5c6cb'};">
${gameState.feedback.correct ? 'Correct! ' : 'Not quite. '}${gameState.feedback.message}
</div>
` : ''}
<!-- Level Complete Message -->
${isLevelComplete ? html`
<div style="padding: 20px; margin-bottom: 20px; background: linear-gradient(135deg, #27ae60, #2ecc71); color: white; border-radius: 10px; text-align: center;">
<div style="font-size: 1.5em; margin-bottom: 10px;">Level ${gameState.currentLevel} Complete!</div>
<div style="margin-bottom: 15px;">You correctly placed all ${currentLevelComponents.length} components!</div>
${gameState.currentLevel < 3 ? html`
<button onclick=${() => {
gameState.currentLevel = gameState.currentLevel + 1;
gameState.placedComponents = { device: [], network: [], cloud: [], application: [] };
gameState.feedback = null;
gameState.selectedComponent = null;
}} style="padding: 12px 30px; font-size: 1.1em; background: white; color: #27ae60; border: none; border-radius: 25px; cursor: pointer; font-weight: bold;">
Next Level
</button>
` : html`
<div style="font-size: 1.3em; margin-top: 10px;">Congratulations! You've mastered IoT Architecture Layers!</div>
<div style="margin-top: 10px;">Final Score: <strong>${gameState.score}</strong> points</div>
`}
</div>
` : ''}
<!-- Available Components Section -->
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 20px; border: 2px solid #dee2e6;">
<h4 style="margin: 0 0 15px 0; color: #2C3E50;">
Available Components
<span style="font-weight: normal; font-size: 0.9em; color: #666;">
(Click to select, then click a layer to place)
</span>
</h4>
<div style="display: flex; flex-wrap: wrap; gap: 10px; min-height: 60px;">
${availableComponents.length === 0 ? html`
<div style="color: #666; font-style: italic; padding: 20px;">
All components placed! ${isLevelComplete ? "Level complete!" : ""}
</div>
` : availableComponents.map(comp => html`
<div
onclick=${() => {
gameState.selectedComponent = gameState.selectedComponent === comp.id ? null : comp.id;
gameState.feedback = null;
}}
style="
padding: 10px 15px;
background: ${gameState.selectedComponent === comp.id ? '#3498db' : 'white'};
color: ${gameState.selectedComponent === comp.id ? 'white' : '#2C3E50'};
border: 2px solid ${gameState.selectedComponent === comp.id ? '#2980b9' : '#bdc3c7'};
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
font-size: 0.95em;
"
title="${comp.description}"
>
<span style="font-size: 1.3em; font-weight: bold;">[${comp.icon}]</span>
<span>${comp.name}</span>
</div>
`)}
</div>
${gameState.selectedComponent ? html`
<div style="margin-top: 15px; padding: 10px; background: #e8f4fd; border-radius: 6px; border-left: 4px solid #3498db;">
<strong>Selected:</strong> ${currentLevelComponents.find(c => c.id === gameState.selectedComponent)?.name}
- <em>${currentLevelComponents.find(c => c.id === gameState.selectedComponent)?.description}</em>
</div>
` : ''}
</div>
<!-- Architecture Layers -->
<div style="display: flex; flex-direction: column; gap: 15px;">
${layerDefinitions.map(layer => {
const placedInLayer = gameState.placedComponents[layer.id] || [];
const componentsInLayer = currentLevelComponents.filter(c => placedInLayer.includes(c.id));
return html`
<div
onclick=${() => {
if (gameState.selectedComponent) {
const isCorrect = checkPlacement(gameState.selectedComponent, layer.id);
if (isCorrect) {
gameState.placedComponents[layer.id] = [...placedInLayer, gameState.selectedComponent];
gameState.score = gameState.score + 10;
gameState.feedback = {
correct: true,
message: `${currentLevelComponents.find(c => c.id === gameState.selectedComponent)?.name} belongs in ${layer.name}`
};
} else {
gameState.score = Math.max(0, gameState.score - 5);
gameState.feedback = {
correct: false,
message: `${currentLevelComponents.find(c => c.id === gameState.selectedComponent)?.name} doesn't belong in ${layer.name}. Try another layer!`
};
}
if (isCorrect) {
gameState.selectedComponent = null;
}
}
}}
style="
padding: 20px;
background: linear-gradient(135deg, ${layer.color}15, ${layer.color}08);
border: 3px solid ${layer.color};
border-radius: 12px;
cursor: ${gameState.selectedComponent ? 'pointer' : 'default'};
transition: all 0.2s;
${gameState.selectedComponent ? `box-shadow: 0 0 0 3px ${layer.color}40;` : ''}
"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="margin: 0; color: ${layer.color}; font-size: 1.1em;">
${layer.name}
</h4>
<span style="font-size: 0.85em; color: #666; background: white; padding: 4px 10px; border-radius: 15px;">
${componentsInLayer.length} placed
</span>
</div>
<div style="font-size: 0.9em; color: #666; margin-bottom: 12px; font-style: italic;">
${layer.description}
</div>
<div style="display: flex; flex-wrap: wrap; gap: 8px; min-height: 45px; padding: 10px; background: white; border-radius: 8px; border: 1px dashed #ccc;">
${componentsInLayer.length === 0 ? html`
<span style="color: #aaa; font-size: 0.9em;">
${gameState.selectedComponent ? 'Click here to place component' : 'Drop components here...'}
</span>
` : componentsInLayer.map(comp => html`
<div style="
padding: 6px 12px;
background: ${layer.color};
color: white;
border-radius: 6px;
font-size: 0.9em;
display: flex;
align-items: center;
gap: 6px;
">
<span>[${comp.icon}]</span>
<span>${comp.name}</span>
</div>
`)}
</div>
</div>
`;
})}
</div>
<!-- Reset Button -->
<div style="text-align: center; margin-top: 25px;">
<button onclick=${() => {
gameState.currentLevel = 1;
gameState.score = 0;
gameState.placedComponents = { device: [], network: [], cloud: [], application: [] };
gameState.feedback = null;
gameState.selectedComponent = null;
}} style="padding: 10px 25px; background: #95a5a6; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 1em;">
Reset Game
</button>
</div>
</div>
`