Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>AI Architecture Collector</title> | |
<style> | |
body { | |
margin: 0; | |
padding: 10px; | |
background: #1a1a1a; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
max-height: 100vh; | |
font-family: "Courier New", monospace; | |
image-rendering: pixelated; | |
overflow: hidden; /* Prevent scrolling */ | |
box-sizing: border-box; | |
} | |
#gameContainer { | |
background: #2c3e50; | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8); | |
max-width: 95vw; | |
max-height: 95vh; | |
display: flex; | |
flex-direction: column; | |
box-sizing: border-box; | |
} | |
#gameCanvas { | |
border: 3px solid #000; | |
display: block; | |
image-rendering: pixelated; | |
max-width: 100%; | |
max-height: 60vh; | |
object-fit: contain; | |
} | |
#ui { | |
margin-top: 8px; | |
color: white; | |
text-align: center; | |
flex-shrink: 0; | |
} | |
.stats { | |
display: flex; | |
justify-content: space-around; | |
margin-top: 8px; | |
padding: 8px; | |
background: #1a1a1a; | |
border-radius: 5px; | |
font-size: 14px; | |
} | |
#dialogue { | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: rgba(0, 0, 0, 0.95); | |
color: white; | |
padding: 15px; | |
border-radius: 5px; | |
max-width: 400px; | |
display: none; | |
border: 2px solid #fff; | |
} | |
.game-title { | |
text-align: center; | |
margin-bottom: 8px; | |
color: #ecf0f1; | |
font-size: 24px; | |
font-weight: bold; | |
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.8); | |
letter-spacing: 2px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="gameContainer"> | |
<div | |
style=" | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin-bottom: 8px; | |
gap: 12px; | |
" | |
> | |
<div class="game-title" style="color: #dc143c; margin: 0"> | |
TAKARA.AI RESEARCH LAB | |
</div> | |
<img | |
src="https://takara.ai/images/logo-24/TakaraAi.svg" | |
width="50" | |
alt="Takara.ai Logo" | |
style="filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5))" | |
/> | |
</div> | |
<canvas id="gameCanvas" width="560" height="420"></canvas> | |
<div id="ui"> | |
<div class="stats"> | |
<div>π€ AI: <span id="aiCount">0</span>/β</div> | |
<div>π Room: <span id="roomId">0,0</span></div> | |
<div>π Biome: <span id="biome">LAB</span></div> | |
<div>π₯ NPCs Met: <span id="npcCount">0</span></div> | |
</div> | |
<div | |
id="questDisplay" | |
style=" | |
margin-top: 8px; | |
padding: 8px; | |
background: rgba(220, 20, 60, 0.1); | |
border-radius: 5px; | |
border: 1px solid #dc143c; | |
" | |
> | |
<div | |
style=" | |
color: #dc143c; | |
font-weight: bold; | |
margin-bottom: 4px; | |
font-size: 13px; | |
" | |
> | |
Current Quest: | |
</div> | |
<div | |
id="questTitle" | |
style="color: #ecf0f1; font-size: 13px; margin-bottom: 2px" | |
> | |
Welcome to Takara.ai | |
</div> | |
<div id="questDesc" style="color: #bdc3c7; font-size: 11px"> | |
Find your first AI architecture! | |
</div> | |
</div> | |
</div> | |
<div | |
style=" | |
text-align: center; | |
color: #ecf0f1; | |
margin-top: 8px; | |
font-size: 13px; | |
" | |
> | |
β¨οΈ WASD/Arrows to move β’ β‘ SPACE to interact β’ π Explore Takara.ai | |
Research Lab! | |
</div> | |
</div> | |
<div id="dialogue"></div> | |
<script> | |
const canvas = document.getElementById("gameCanvas"); | |
const ctx = canvas.getContext("2d"); | |
ctx.imageSmoothingEnabled = false; | |
// Constants - Pokemon-style tile system | |
const TILE_SIZE = 32; | |
const VIEW_TILES = 20; | |
const VIEW_DIST = 8; | |
const ROOM_SIZE = 15; | |
// Tile types for collision and interaction | |
const TILE_TYPES = { | |
FLOOR: 0, | |
WALL: 1, | |
GLASS: 2, | |
STAIRS: 3, | |
LAB_ENTRANCE: 4, | |
DATACENTER_ENTRANCE: 5, | |
MAIN_ENTRANCE: 6, | |
}; | |
// Collision data - which tiles can be walked on | |
const WALKABLE_TILES = new Set([ | |
TILE_TYPES.FLOOR, | |
TILE_TYPES.STAIRS, | |
TILE_TYPES.LAB_ENTRANCE, | |
TILE_TYPES.DATACENTER_ENTRANCE, | |
TILE_TYPES.MAIN_ENTRANCE, | |
]); | |
// Takara.ai Lab Elements | |
const LAB_COLORS = { | |
takaraRed: "#DC143C", | |
labWall: "#34495e", | |
labFloor: "#ecf0f1", | |
equipment: "#2c3e50", | |
// Biome colors | |
outside: { floor: "#2d5016", wall: "#8B4513", sky: "#87CEEB" }, | |
underground: { floor: "#1a1a1a", wall: "#2c2c2c", server: "#0066cc" }, | |
office: { floor: "#f8f9fa", wall: "#e9ecef", accent: "#DC143C" }, | |
}; | |
// Biome system - simplified to 3 levels | |
const BIOMES = { | |
OUTSIDE: { minY: 1, maxY: 999, fogRadius: 12 }, | |
LAB: { minY: 0, maxY: 0, fogRadius: 8 }, | |
DATACENTER: { minY: -999, maxY: -1, fogRadius: 6 }, | |
}; | |
function getBiome(roomY) { | |
if (roomY >= BIOMES.OUTSIDE.minY) return "OUTSIDE"; | |
if (roomY >= BIOMES.LAB.minY) return "LAB"; | |
return "DATACENTER"; | |
} | |
// Environment objects (non-interactive, part of scenery) | |
const ENVIRONMENT = { | |
OUTSIDE: [ | |
{ | |
name: "Tree Cluster", | |
color: "#228B22", | |
symbol: "β ", | |
size: "large", | |
}, | |
{ name: "Bush", color: "#32CD32", symbol: "β", size: "small" }, | |
{ name: "Path Marker", color: "#8B4513", symbol: "βͺ", size: "small" }, | |
], | |
LAB: [ | |
{ name: "Computer", color: "#3498db", symbol: "β£", size: "medium" }, | |
{ name: "Equipment", color: "#95a5a6", symbol: "β€", size: "medium" }, | |
{ name: "Monitor", color: "#2c3e50", symbol: "β‘", size: "small" }, | |
], | |
DATACENTER: [ | |
{ name: "Server Bank", color: "#0066cc", symbol: "β", size: "large" }, | |
{ name: "Cable Tray", color: "#696969", symbol: "β", size: "medium" }, | |
{ | |
name: "Cooling Vent", | |
color: "#4169E1", | |
symbol: "β", | |
size: "small", | |
}, | |
], | |
}; | |
// Interactive furniture (can be examined) | |
const LAB_FURNITURE = { | |
OUTSIDE: [ | |
{ name: "Entrance Sign", color: "#DC143C", symbol: "β‘" }, | |
{ name: "Information Kiosk", color: "#3498db", symbol: "βΉ" }, | |
], | |
LAB: [ | |
{ name: "Research Terminal", color: "#DC143C", symbol: "β‘" }, | |
{ name: "Exit to Surface", color: "#2ecc71", symbol: "β" }, | |
{ name: "Stairs Down", color: "#8B4513", symbol: "βΌ" }, | |
], | |
DATACENTER: [ | |
{ name: "Main Terminal", color: "#FF4500", symbol: "β‘" }, | |
{ name: "Exit to Surface", color: "#2ecc71", symbol: "β" }, | |
{ name: "Stairs Up", color: "#8B4513", symbol: "β²" }, | |
], | |
}; | |
// AI Architectures with rarity system | |
const AI_TYPES = [ | |
// Common (60% spawn rate) | |
{ | |
name: "Perceptron", | |
color: "#95a5a6", | |
desc: "The foundation of neural networks", | |
rarity: "common", | |
year: 1957, | |
}, | |
{ | |
name: "MLP", | |
color: "#7f8c8d", | |
desc: "Multi-layer learning", | |
rarity: "common", | |
year: 1986, | |
}, | |
{ | |
name: "CNN", | |
color: "#2ecc71", | |
desc: "Sees patterns in images", | |
rarity: "common", | |
year: 1989, | |
}, | |
{ | |
name: "RNN", | |
color: "#e74c3c", | |
desc: "Remembers sequential data", | |
rarity: "common", | |
year: 1990, | |
}, | |
// Uncommon (25% spawn rate) | |
{ | |
name: "LSTM", | |
color: "#f39c12", | |
desc: "Long-term memory master", | |
rarity: "uncommon", | |
year: 1997, | |
}, | |
{ | |
name: "ResNet", | |
color: "#c0392b", | |
desc: "Skip connections revolution", | |
rarity: "uncommon", | |
year: 2015, | |
}, | |
{ | |
name: "YOLO", | |
color: "#16a085", | |
desc: "Real-time object detection", | |
rarity: "uncommon", | |
year: 2016, | |
}, | |
{ | |
name: "U-Net", | |
color: "#8e44ad", | |
desc: "Medical image segmentation", | |
rarity: "uncommon", | |
year: 2015, | |
}, | |
// Rare (10% spawn rate) | |
{ | |
name: "Transformer", | |
color: "#3498db", | |
desc: "Attention is all you need!", | |
rarity: "rare", | |
year: 2017, | |
}, | |
{ | |
name: "BERT", | |
color: "#1abc9c", | |
desc: "Bidirectional understanding", | |
rarity: "rare", | |
year: 2018, | |
}, | |
{ | |
name: "GPT", | |
color: "#e67e22", | |
desc: "Generative language model", | |
rarity: "rare", | |
year: 2018, | |
}, | |
{ | |
name: "GAN", | |
color: "#9b59b6", | |
desc: "Adversarial creativity", | |
rarity: "rare", | |
year: 2014, | |
}, | |
// Epic (4% spawn rate) | |
{ | |
name: "GPT-3", | |
color: "#f1c40f", | |
desc: "175B parameter giant", | |
rarity: "epic", | |
year: 2020, | |
}, | |
{ | |
name: "CLIP", | |
color: "#e91e63", | |
desc: "Vision-language understanding", | |
rarity: "epic", | |
year: 2021, | |
}, | |
{ | |
name: "DALL-E", | |
color: "#ff5722", | |
desc: "Text-to-image generation", | |
rarity: "epic", | |
year: 2021, | |
}, | |
// Legendary (1% spawn rate) | |
{ | |
name: "GPT-4", | |
color: "#ffd700", | |
desc: "Multimodal AI breakthrough", | |
rarity: "legendary", | |
year: 2023, | |
}, | |
{ | |
name: "AlphaGo", | |
color: "#4caf50", | |
desc: "Game-changing AI", | |
rarity: "legendary", | |
year: 2016, | |
}, | |
]; | |
// Rarity spawn rates | |
const RARITY_RATES = { | |
common: 0.55, | |
uncommon: 0.25, | |
rare: 0.12, | |
epic: 0.06, | |
legendary: 0.02, | |
}; | |
// NPCs | |
const NPC_TYPES = [ | |
{ | |
name: "Dr. Takara Mono", | |
dialogue: [ | |
"Hmph! You're the new researcher? About time someone showed up.", | |
"This facility contains the most important AI architectures in history.", | |
"Your mission: collect these architectures and learn from the masters.", | |
"Don't waste my time - get to work! The lab entrance is behind me.", | |
"Each AI architecture you find will teach you something valuable.", | |
"The deeper you go, the rarer the discoveries become.", | |
], | |
isStoryNPC: true, | |
}, | |
{ | |
name: "Geoffrey Hinton", | |
dialogue: [ | |
"I'm the Godfather of Deep Learning!", | |
"Backpropagation changed everything!", | |
"Neural networks were once considered dead ends.", | |
"Persistence and belief in the impossible led to breakthroughs.", | |
], | |
}, | |
{ | |
name: "Yann LeCun", | |
dialogue: [ | |
"CNNs are inspired by the visual cortex!", | |
"I pioneered convolutional networks.", | |
"Computer vision was transformed by hierarchical learning.", | |
"The key is learning good representations automatically.", | |
], | |
}, | |
{ | |
name: "Yoshua Bengio", | |
dialogue: [ | |
"The key is learning representations.", | |
"Keep collecting architectures!", | |
"Deep learning is about discovering abstractions.", | |
"Unsupervised learning holds great promise.", | |
], | |
}, | |
{ | |
name: "Andrew Ng", | |
dialogue: [ | |
"AI is the new electricity!", | |
"It's about solving real problems.", | |
"Machine learning should be democratized.", | |
"Focus on applications that help humanity.", | |
], | |
}, | |
{ | |
name: "Fei-Fei Li", | |
dialogue: [ | |
"Human-centered AI is crucial.", | |
"Visual intelligence is just the beginning!", | |
"AI should augment human capabilities.", | |
"Diversity in AI research is essential.", | |
], | |
}, | |
]; | |
// Storyline and quest system | |
const STORY_QUESTS = [ | |
{ | |
id: "welcome", | |
title: "Welcome to Takara.ai", | |
description: | |
"Dr. Takara has invited you to explore the research facility. Find your first AI architecture!", | |
target: { type: "collect_ai", count: 1 }, | |
reward: "Access to deeper facility levels", | |
completed: false, | |
}, | |
{ | |
id: "meet_pioneers", | |
title: "Meet the AI Pioneers", | |
description: | |
"Speak with 3 legendary AI researchers to learn about the field's history.", | |
target: { type: "meet_npcs", count: 3 }, | |
reward: "Advanced AI detection equipment", | |
completed: false, | |
}, | |
{ | |
id: "rare_collector", | |
title: "Rare Architecture Hunter", | |
description: | |
"Collect 5 different AI architectures to prove your dedication.", | |
target: { type: "collect_ai", count: 5 }, | |
reward: "Access to experimental AI lab", | |
completed: false, | |
}, | |
{ | |
id: "deep_learning", | |
title: "Deep Learning Explorer", | |
description: | |
"Venture into the datacenter and collect a rare or better AI architecture.", | |
target: { | |
type: "collect_rare_ai", | |
rarity: ["rare", "epic", "legendary"], | |
}, | |
reward: "Dr. Takara's personal research notes", | |
completed: false, | |
}, | |
{ | |
id: "master_collector", | |
title: "AI Architecture Master", | |
description: | |
"Collect 15 different AI architectures to become a true expert.", | |
target: { type: "collect_ai", count: 15 }, | |
reward: "Honorary Takara.ai Research Badge", | |
completed: false, | |
}, | |
]; | |
// Game state with storyline | |
const game = { | |
player: { | |
x: 0, | |
y: 0, | |
facing: "down", | |
animFrame: 0, | |
roomX: 0, | |
roomY: 0, | |
}, | |
collectedAI: new Set(), | |
metNPCs: new Set(), | |
visitedRooms: new Set(["0,1"]), | |
rooms: new Map(), | |
particles: [], | |
// Story progression | |
currentQuest: 0, | |
completedQuests: new Set(), | |
storyFlags: new Set(), | |
// Game stats | |
totalAICollected: 0, | |
rareAICollected: 0, | |
gameStartTime: Date.now(), | |
// Timing | |
lastTime: 0, | |
deltaTime: 0, | |
}; | |
// Room generation with deterministic seeding | |
function hashCode(str) { | |
let hash = 0; | |
for (let i = 0; i < str.length; i++) { | |
hash = (hash << 5) - hash + str.charCodeAt(i); | |
hash = hash & hash; | |
} | |
return Math.abs(hash); | |
} | |
function seededRandom(seed) { | |
const x = Math.sin(seed) * 10000; | |
return x - Math.floor(x); | |
} | |
function hexToRgb(hex) { | |
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); | |
return result | |
? { | |
r: parseInt(result[1], 16), | |
g: parseInt(result[2], 16), | |
b: parseInt(result[3], 16), | |
} | |
: { r: 255, g: 255, b: 255 }; | |
} | |
class Room { | |
constructor(roomX, roomY) { | |
this.roomX = roomX; | |
this.roomY = roomY; | |
this.biome = getBiome(roomY); | |
this.seed = hashCode(`${roomX},${roomY}`); | |
this.tiles = []; | |
this.entities = []; | |
this.generate(); | |
} | |
generate() { | |
// Initialize tiles | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y] = []; | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
this.tiles[y][x] = 0; | |
} | |
} | |
// Generate room structure based on biome and type | |
this.generateBiomeStructure(); | |
// Add doorways - ensure they're always clear | |
const doorX = Math.floor(ROOM_SIZE / 2); | |
const doorY = Math.floor(ROOM_SIZE / 2); | |
this.tiles[0][doorX] = 0; // North | |
this.tiles[ROOM_SIZE - 1][doorX] = 0; // South | |
this.tiles[doorY][0] = 0; // West | |
this.tiles[doorY][ROOM_SIZE - 1] = 0; // East | |
// Clear paths to doors to prevent getting stuck | |
this.tiles[1][doorX] = 0; // North path | |
this.tiles[ROOM_SIZE - 2][doorX] = 0; // South path | |
this.tiles[doorY][1] = 0; // West path | |
this.tiles[doorY][ROOM_SIZE - 2] = 0; // East path | |
// Spawn entities | |
this.spawnEntities(); | |
} | |
generateBiomeStructure() { | |
// Clear all tiles first | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
this.tiles[y][x] = 0; | |
} | |
} | |
const roomType = Math.floor(seededRandom(this.seed) * 4); | |
switch (this.biome) { | |
case "OUTSIDE": | |
this.generateOutdoorArea(roomType); | |
break; | |
case "LAB": | |
this.generateLabComplex(roomType); | |
break; | |
case "DATACENTER": | |
this.generateDatacenterFacility(roomType); | |
break; | |
} | |
// Add multi-tile structures | |
this.addComplexStructures(); | |
} | |
generateOutdoorArea(type) { | |
// Special starting area at 0,0 | |
if (this.roomX === 0 && this.roomY === 1) { | |
this.createStartingArea(); | |
return; | |
} | |
switch (type) { | |
case 0: | |
this.createForestArea(); | |
break; | |
case 1: | |
this.createTakaraCompound(); | |
break; | |
case 2: | |
this.createJapaneseGarden(); | |
break; | |
default: | |
this.createResearchCampus(); | |
} | |
} | |
createForestArea() { | |
// Create natural forest with clearings and paths | |
const numGroves = 2 + Math.floor(seededRandom(this.seed + 50) * 3); | |
for (let g = 0; g < numGroves; g++) { | |
const centerX = | |
2 + | |
Math.floor(seededRandom(this.seed + 100 + g) * (ROOM_SIZE - 4)); | |
const centerY = | |
2 + | |
Math.floor(seededRandom(this.seed + 200 + g) * (ROOM_SIZE - 4)); | |
const groveSize = | |
2 + Math.floor(seededRandom(this.seed + 300 + g) * 3); | |
// Create organic tree cluster | |
for (let dx = -groveSize; dx <= groveSize; dx++) { | |
for (let dy = -groveSize; dy <= groveSize; dy++) { | |
const x = centerX + dx; | |
const y = centerY + dy; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if ( | |
x >= 0 && | |
x < ROOM_SIZE && | |
y >= 0 && | |
y < ROOM_SIZE && | |
distance <= groveSize && | |
seededRandom(this.seed + x * 17 + y * 23) > | |
0.4 - (distance / groveSize) * 0.3 | |
) { | |
this.tiles[y][x] = 1; // Tree | |
} | |
} | |
} | |
} | |
// Create winding paths | |
this.createNaturalPath(); | |
} | |
createTakaraCompound() { | |
// Create impressive Takara.ai corporate compound | |
// Main building (large central structure) | |
const buildingX = 4; | |
const buildingY = 2; | |
const buildingW = 7; | |
const buildingH = 6; | |
// Building perimeter | |
for (let x = buildingX; x < buildingX + buildingW; x++) { | |
for (let y = buildingY; y < buildingY + buildingH; y++) { | |
if ( | |
x === buildingX || | |
x === buildingX + buildingW - 1 || | |
y === buildingY || | |
y === buildingY + buildingH - 1 | |
) { | |
this.tiles[y][x] = 1; // Wall | |
} | |
} | |
} | |
// Glass entrance with automatic doors | |
const entranceX = buildingX + Math.floor(buildingW / 2); | |
this.tiles[buildingY + buildingH - 1][entranceX - 1] = 2; // Glass | |
this.tiles[buildingY + buildingH - 1][entranceX] = 0; // Door | |
this.tiles[buildingY + buildingH - 1][entranceX + 1] = 2; // Glass | |
// Corporate plaza in front | |
for (let x = buildingX + 1; x < buildingX + buildingW - 1; x++) { | |
for ( | |
let y = buildingY + buildingH; | |
y < buildingY + buildingH + 3; | |
y++ | |
) { | |
if (y < ROOM_SIZE) { | |
// Leave as open plaza (floor tiles) | |
} | |
} | |
} | |
// Decorative elements | |
this.createCorporateLandscaping( | |
buildingX, | |
buildingY, | |
buildingW, | |
buildingH | |
); | |
} | |
createJapaneseGarden() { | |
// Beautiful Japanese-inspired garden with zen elements | |
// Central meditation area | |
const centerX = Math.floor(ROOM_SIZE / 2); | |
const centerY = Math.floor(ROOM_SIZE / 2); | |
// Rock garden (zen stones) | |
for (let dx = -2; dx <= 2; dx++) { | |
for (let dy = -2; dy <= 2; dy++) { | |
const x = centerX + dx; | |
const y = centerY + dy; | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if ( | |
distance <= 2 && | |
seededRandom(this.seed + x * 13 + y * 17) > 0.7 | |
) { | |
this.tiles[y][x] = 1; // Zen stones | |
} | |
} | |
} | |
} | |
// Carefully placed trees (bonsai-style arrangement) | |
const treePositions = [ | |
{ x: 3, y: 3 }, | |
{ x: 11, y: 3 }, | |
{ x: 3, y: 11 }, | |
{ x: 11, y: 11 }, | |
{ x: 7, y: 2 }, | |
{ x: 7, y: 12 }, | |
{ x: 2, y: 7 }, | |
{ x: 12, y: 7 }, | |
]; | |
treePositions.forEach((pos) => { | |
if (pos.x < ROOM_SIZE && pos.y < ROOM_SIZE) { | |
this.tiles[pos.y][pos.x] = 1; | |
} | |
}); | |
// Winding path through garden | |
this.createZenPath(); | |
} | |
createResearchCampus() { | |
// Modern research campus with multiple buildings | |
// Building cluster 1 (top-left) | |
this.createCampusBuilding(1, 1, 4, 3); | |
// Building cluster 2 (top-right) | |
this.createCampusBuilding(9, 1, 4, 3); | |
// Building cluster 3 (bottom-left) | |
this.createCampusBuilding(1, 10, 4, 3); | |
// Building cluster 4 (bottom-right) | |
this.createCampusBuilding(9, 10, 4, 3); | |
// Central courtyard with pathways | |
this.createCampusCourtyard(); | |
// Research equipment scattered around | |
this.addOutdoorResearchEquipment(); | |
} | |
generateOfficeBuilding(type) { | |
switch (type) { | |
case 0: | |
this.createOpenOffice(); | |
break; | |
case 1: | |
this.createOfficeHallway(); | |
break; | |
case 2: | |
this.createConferenceArea(); | |
break; | |
default: | |
this.createExecutiveOffices(); | |
} | |
} | |
createOpenOffice() { | |
// Create large open office with cubicle clusters and walkways | |
// Main walkway through center | |
const mainHallY = Math.floor(ROOM_SIZE / 2); | |
// Create cubicle clusters | |
const clusters = [ | |
{ x: 2, y: 2, width: 4, height: 3 }, | |
{ x: 8, y: 2, width: 4, height: 3 }, | |
{ x: 2, y: 9, width: 4, height: 3 }, | |
{ x: 8, y: 9, width: 4, height: 3 }, | |
]; | |
clusters.forEach((cluster) => { | |
this.createCubicleCluster( | |
cluster.x, | |
cluster.y, | |
cluster.width, | |
cluster.height | |
); | |
}); | |
// Add glass partitions | |
this.tiles[6][1] = 2; // Glass wall marker | |
this.tiles[6][13] = 2; | |
// Reception area | |
this.tiles[1][6] = 1; | |
this.tiles[1][7] = 1; | |
this.tiles[1][8] = 1; | |
} | |
createOfficeHallway() { | |
// Create hallway with offices on both sides | |
// Main hallway | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y][6] = 0; // Clear hallway | |
this.tiles[y][7] = 0; | |
this.tiles[y][8] = 0; | |
} | |
// Left side offices | |
this.createOfficeRoom(1, 2, 4, 4); | |
this.createOfficeRoom(1, 8, 4, 4); | |
// Right side offices | |
this.createOfficeRoom(10, 2, 4, 4); | |
this.createOfficeRoom(10, 8, 4, 4); | |
// Doors | |
this.tiles[4][5] = 0; // Left office door | |
this.tiles[10][5] = 0; | |
this.tiles[4][9] = 0; // Right office door | |
this.tiles[10][9] = 0; | |
} | |
createConferenceArea() { | |
// Large conference room with glass walls | |
const roomX = 3; | |
const roomY = 3; | |
const roomW = 8; | |
const roomH = 6; | |
// Outer walls | |
for (let x = roomX; x < roomX + roomW; x++) { | |
this.tiles[roomY][x] = 2; // Glass wall | |
this.tiles[roomY + roomH - 1][x] = 2; | |
} | |
for (let y = roomY; y < roomY + roomH; y++) { | |
this.tiles[y][roomX] = 2; | |
this.tiles[y][roomX + roomW - 1] = 2; | |
} | |
// Conference table (3x5) | |
for (let x = roomX + 2; x < roomX + 5; x++) { | |
for (let y = roomY + 2; y < roomY + 4; y++) { | |
this.tiles[y][x] = 1; | |
} | |
} | |
// Door | |
this.tiles[roomY + roomH - 1][roomX + Math.floor(roomW / 2)] = 0; | |
} | |
createExecutiveOffices() { | |
// Corner offices with secretary areas | |
// Executive office 1 (top-left) | |
this.createOfficeRoom(1, 1, 5, 5); | |
this.tiles[3][5] = 0; // Door | |
// Executive office 2 (top-right) | |
this.createOfficeRoom(9, 1, 5, 5); | |
this.tiles[3][9] = 0; // Door | |
// Secretary/assistant area (center) | |
this.tiles[6][6] = 1; // Desk | |
this.tiles[6][7] = 1; | |
this.tiles[7][6] = 1; | |
this.tiles[7][7] = 1; | |
// Waiting area (bottom) | |
for (let x = 4; x < 11; x++) { | |
for (let y = 10; y < 13; y++) { | |
if ((x + y) % 3 === 0) { | |
this.tiles[y][x] = 1; // Chairs | |
} | |
} | |
} | |
} | |
generateLabComplex(type) { | |
switch (type) { | |
case 0: | |
this.createResearchLab(); | |
break; | |
case 1: | |
this.createCleanRoom(); | |
break; | |
case 2: | |
this.createLabHallway(); | |
break; | |
default: | |
this.createExperimentChamber(); | |
} | |
} | |
createResearchLab() { | |
// Large research lab with equipment stations | |
// Perimeter walls with glass sections | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
this.tiles[0][x] = 1; | |
this.tiles[ROOM_SIZE - 1][x] = 1; | |
} | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y][0] = 1; | |
this.tiles[y][ROOM_SIZE - 1] = 1; | |
} | |
// Glass observation windows | |
this.tiles[0][5] = 2; | |
this.tiles[0][6] = 2; | |
this.tiles[0][7] = 2; | |
this.tiles[0][8] = 2; | |
this.tiles[0][9] = 2; | |
// Equipment stations | |
this.createEquipmentStation(2, 2, 3, 2); | |
this.createEquipmentStation(7, 2, 3, 2); | |
this.createEquipmentStation(2, 6, 3, 2); | |
this.createEquipmentStation(7, 6, 3, 2); | |
// Central workbench | |
this.tiles[5][6] = 1; | |
this.tiles[5][7] = 1; | |
this.tiles[6][6] = 1; | |
this.tiles[6][7] = 1; | |
// Safety shower (corner) | |
this.tiles[11][2] = 1; | |
this.tiles[12][2] = 1; | |
} | |
createCleanRoom() { | |
// Sterile environment with airlocks | |
// Double walls for airlock | |
for (let x = 1; x < ROOM_SIZE - 1; x++) { | |
this.tiles[1][x] = 1; | |
this.tiles[2][x] = 1; | |
this.tiles[ROOM_SIZE - 2][x] = 1; | |
this.tiles[ROOM_SIZE - 3][x] = 1; | |
} | |
for (let y = 1; y < ROOM_SIZE - 1; y++) { | |
this.tiles[y][1] = 1; | |
this.tiles[y][2] = 1; | |
this.tiles[y][ROOM_SIZE - 2] = 1; | |
this.tiles[y][ROOM_SIZE - 3] = 1; | |
} | |
// Airlock entrance | |
this.tiles[1][7] = 0; | |
this.tiles[2][7] = 0; | |
// Clean room equipment (minimal) | |
this.tiles[5][5] = 1; | |
this.tiles[9][9] = 1; | |
// Air filtration units | |
this.tiles[4][4] = 1; | |
this.tiles[10][4] = 1; | |
this.tiles[4][10] = 1; | |
this.tiles[10][10] = 1; | |
} | |
createLabHallway() { | |
// Main lab corridor with side rooms | |
// Central corridor | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y][6] = 0; | |
this.tiles[y][7] = 0; | |
this.tiles[y][8] = 0; | |
} | |
// Side lab rooms | |
this.createLabRoom(1, 1, 4, 5); | |
this.createLabRoom(10, 1, 4, 5); | |
this.createLabRoom(1, 8, 4, 5); | |
this.createLabRoom(10, 8, 4, 5); | |
// Lab doors | |
this.tiles[3][5] = 0; | |
this.tiles[3][9] = 0; | |
this.tiles[10][5] = 0; | |
this.tiles[10][9] = 0; | |
// Emergency equipment | |
this.tiles[0][6] = 1; // Fire extinguisher | |
this.tiles[0][8] = 1; // Emergency shower | |
} | |
createExperimentChamber() { | |
// Specialized experiment chamber | |
// Reinforced walls | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
this.tiles[0][x] = 1; | |
this.tiles[1][x] = 1; | |
this.tiles[ROOM_SIZE - 1][x] = 1; | |
this.tiles[ROOM_SIZE - 2][x] = 1; | |
} | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y][0] = 1; | |
this.tiles[y][1] = 1; | |
this.tiles[y][ROOM_SIZE - 1] = 1; | |
this.tiles[y][ROOM_SIZE - 2] = 1; | |
} | |
// Observation window | |
this.tiles[1][6] = 2; | |
this.tiles[1][7] = 2; | |
this.tiles[1][8] = 2; | |
// Central experiment platform | |
for (let x = 5; x < 10; x++) { | |
for (let y = 5; y < 10; y++) { | |
this.tiles[y][x] = 1; | |
} | |
} | |
// Control panels around perimeter | |
this.tiles[3][3] = 1; | |
this.tiles[3][11] = 1; | |
this.tiles[11][3] = 1; | |
this.tiles[11][11] = 1; | |
} | |
generateDatacenterFacility(type) { | |
switch (type) { | |
case 0: | |
this.createServerFarm(); | |
break; | |
case 1: | |
this.createNetworkingHub(); | |
break; | |
case 2: | |
this.createCoolingCenter(); | |
break; | |
default: | |
this.createMainframe(); | |
} | |
} | |
createServerFarm() { | |
// Massive server farm with cooling aisles | |
// Hot aisle / cold aisle design | |
for (let row = 0; row < 3; row++) { | |
const startY = 2 + row * 4; | |
// Server racks (back-to-back) | |
for (let x = 1; x < ROOM_SIZE - 1; x += 2) { | |
this.tiles[startY][x] = 1; | |
this.tiles[startY + 1][x] = 1; | |
} | |
// Leave cooling aisles clear | |
// Cold aisle at startY - 1 | |
// Hot aisle at startY + 2 | |
} | |
// Power distribution units | |
this.tiles[1][1] = 1; | |
this.tiles[1][ROOM_SIZE - 2] = 1; | |
this.tiles[ROOM_SIZE - 2][1] = 1; | |
this.tiles[ROOM_SIZE - 2][ROOM_SIZE - 2] = 1; | |
// Cable management overhead | |
for (let x = 3; x < ROOM_SIZE - 3; x += 4) { | |
this.tiles[0][x] = 1; | |
this.tiles[ROOM_SIZE - 1][x] = 1; | |
} | |
} | |
createNetworkingHub() { | |
// Central networking equipment | |
// Core switch room | |
const centerX = Math.floor(ROOM_SIZE / 2); | |
const centerY = Math.floor(ROOM_SIZE / 2); | |
// Central networking rack | |
for (let dx = -1; dx <= 1; dx++) { | |
for (let dy = -1; dy <= 1; dy++) { | |
this.tiles[centerY + dy][centerX + dx] = 1; | |
} | |
} | |
// Patch panels around perimeter | |
for (let x = 2; x < ROOM_SIZE - 2; x += 3) { | |
this.tiles[2][x] = 1; | |
this.tiles[ROOM_SIZE - 3][x] = 1; | |
} | |
for (let y = 2; y < ROOM_SIZE - 2; y += 3) { | |
this.tiles[y][2] = 1; | |
this.tiles[y][ROOM_SIZE - 3] = 1; | |
} | |
// Cable trays | |
this.tiles[1][centerX] = 1; | |
this.tiles[ROOM_SIZE - 2][centerX] = 1; | |
this.tiles[centerY][1] = 1; | |
this.tiles[centerY][ROOM_SIZE - 2] = 1; | |
} | |
createCoolingCenter() { | |
// Cooling and power infrastructure | |
// HVAC units (large) | |
this.createHVACUnit(2, 2, 4, 3); | |
this.createHVACUnit(9, 2, 4, 3); | |
this.createHVACUnit(2, 9, 4, 3); | |
this.createHVACUnit(9, 9, 4, 3); | |
// Central air handling | |
for (let x = 6; x < 9; x++) { | |
for (let y = 6; y < 9; y++) { | |
this.tiles[y][x] = 1; | |
} | |
} | |
// Chilled water pipes | |
this.tiles[0][7] = 1; | |
this.tiles[ROOM_SIZE - 1][7] = 1; | |
this.tiles[7][0] = 1; | |
this.tiles[7][ROOM_SIZE - 1] = 1; | |
} | |
createMainframe() { | |
// Legacy mainframe computer room | |
// Raised floor perimeter | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
this.tiles[0][x] = 1; | |
this.tiles[ROOM_SIZE - 1][x] = 1; | |
} | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
this.tiles[y][0] = 1; | |
this.tiles[y][ROOM_SIZE - 1] = 1; | |
} | |
// Central mainframe (large) | |
for (let x = 5; x < 10; x++) { | |
for (let y = 5; y < 10; y++) { | |
this.tiles[y][x] = 1; | |
} | |
} | |
// Operator consoles | |
this.tiles[3][3] = 1; | |
this.tiles[3][11] = 1; | |
this.tiles[11][3] = 1; | |
this.tiles[11][11] = 1; | |
// Tape drives | |
this.tiles[2][6] = 1; | |
this.tiles[2][7] = 1; | |
this.tiles[2][8] = 1; | |
} | |
// Helper functions for complex structures | |
addComplexStructures() { | |
// Add multi-tile stairs | |
this.addMultiTileStairs(); | |
// Ensure door connectivity | |
this.ensureDoorConnectivity(); | |
} | |
addMultiTileStairs() { | |
// Only add stairs in underground areas (LAB and DATACENTER) | |
if (this.biome === "LAB" || this.biome === "DATACENTER") { | |
if (seededRandom(this.seed + 1500) > 0.6) { | |
// Find suitable location for 3x3 staircase | |
let placed = false; | |
for (let attempts = 0; attempts < 20 && !placed; attempts++) { | |
const x = | |
2 + | |
Math.floor( | |
seededRandom(this.seed + 1700 + attempts) * (ROOM_SIZE - 5) | |
); | |
const y = | |
2 + | |
Math.floor( | |
seededRandom(this.seed + 1800 + attempts) * (ROOM_SIZE - 5) | |
); | |
// Check if area is clear | |
let canPlace = true; | |
for (let dx = 0; dx < 3; dx++) { | |
for (let dy = 0; dy < 3; dy++) { | |
if (this.tiles[y + dy][x + dx] !== 0) { | |
canPlace = false; | |
break; | |
} | |
} | |
if (!canPlace) break; | |
} | |
if (canPlace) { | |
this.createStaircase(x, y); | |
placed = true; | |
} | |
} | |
} | |
} else if (this.biome === "OUTSIDE") { | |
// Add building entrances instead of stairs | |
this.addBuildingEntrances(); | |
} | |
} | |
createStaircase(x, y) { | |
// Create 3x3 staircase - universal stairs that go between levels | |
for (let dx = 0; dx < 3; dx++) { | |
for (let dy = 0; dy < 3; dy++) { | |
this.tiles[y + dy][x + dx] = TILE_TYPES.STAIRS; // Universal stair tiles | |
} | |
} | |
} | |
createNaturalPath() { | |
// Create winding path through forest | |
const pathPoints = []; | |
const numPoints = 4 + Math.floor(seededRandom(this.seed + 2000) * 3); | |
for (let i = 0; i < numPoints; i++) { | |
pathPoints.push({ | |
x: Math.floor(seededRandom(this.seed + 2100 + i) * ROOM_SIZE), | |
y: Math.floor(seededRandom(this.seed + 2200 + i) * ROOM_SIZE), | |
}); | |
} | |
// Connect path points | |
for (let i = 0; i < pathPoints.length - 1; i++) { | |
this.createPathBetween(pathPoints[i], pathPoints[i + 1]); | |
} | |
} | |
createPathBetween(start, end) { | |
let x = start.x; | |
let y = start.y; | |
while (x !== end.x || y !== end.y) { | |
this.tiles[y][x] = 0; // Clear path | |
if (x < end.x) x++; | |
else if (x > end.x) x--; | |
if (y < end.y) y++; | |
else if (y > end.y) y--; | |
} | |
} | |
addLandscaping(buildingX, buildingY, width, height) { | |
// Add decorative elements around building | |
for (let i = 0; i < 8; i++) { | |
const x = Math.floor( | |
seededRandom(this.seed + 2300 + i) * ROOM_SIZE | |
); | |
const y = Math.floor( | |
seededRandom(this.seed + 2400 + i) * ROOM_SIZE | |
); | |
// Don't place on building | |
if ( | |
x < buildingX || | |
x >= buildingX + width || | |
y < buildingY || | |
y >= buildingY + height | |
) { | |
if (seededRandom(this.seed + 2500 + i) > 0.6) { | |
this.tiles[y][x] = 1; // Decorative tree/bush | |
} | |
} | |
} | |
} | |
createCorporateLandscaping(buildingX, buildingY, width, height) { | |
// Modern corporate landscaping with geometric patterns | |
// Decorative planters | |
if (buildingY > 0) { | |
this.tiles[buildingY - 1][buildingX + 1] = 1; | |
this.tiles[buildingY - 1][buildingX + width - 2] = 1; | |
} | |
// Corporate signage area | |
if (buildingY + height + 1 < ROOM_SIZE) { | |
this.tiles[buildingY + height + 1][ | |
buildingX + Math.floor(width / 2) | |
] = 1; | |
} | |
// Symmetrical tree placement | |
if (buildingY + height + 2 < ROOM_SIZE) { | |
this.tiles[buildingY + height + 2][buildingX + 2] = 1; | |
this.tiles[buildingY + height + 2][buildingX + width - 3] = 1; | |
} | |
} | |
createZenPath() { | |
// Curved path through garden | |
const pathPoints = [ | |
{ x: 1, y: 7 }, | |
{ x: 4, y: 5 }, | |
{ x: 7, y: 7 }, | |
{ x: 10, y: 9 }, | |
{ x: 13, y: 7 }, | |
]; | |
for (let i = 0; i < pathPoints.length - 1; i++) { | |
this.createSmoothPath(pathPoints[i], pathPoints[i + 1]); | |
} | |
} | |
createSmoothPath(start, end) { | |
// Create smooth curved path between points | |
const steps = Math.max( | |
Math.abs(end.x - start.x), | |
Math.abs(end.y - start.y) | |
); | |
for (let i = 0; i <= steps; i++) { | |
const t = i / steps; | |
const x = Math.round(start.x + (end.x - start.x) * t); | |
const y = Math.round(start.y + (end.y - start.y) * t); | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
this.tiles[y][x] = 0; // Clear path | |
} | |
} | |
} | |
createCampusBuilding(x, y, width, height) { | |
// Modern research building | |
for (let dx = 0; dx < width; dx++) { | |
for (let dy = 0; dy < height; dy++) { | |
if ( | |
dx === 0 || | |
dx === width - 1 || | |
dy === 0 || | |
dy === height - 1 | |
) { | |
if (x + dx < ROOM_SIZE && y + dy < ROOM_SIZE) { | |
this.tiles[y + dy][x + dx] = 1; | |
} | |
} | |
} | |
} | |
// Glass entrance | |
const entranceX = x + Math.floor(width / 2); | |
if (entranceX < ROOM_SIZE && y + height - 1 < ROOM_SIZE) { | |
this.tiles[y + height - 1][entranceX] = 2; | |
} | |
} | |
createCampusCourtyard() { | |
// Central courtyard with modern design | |
const centerX = Math.floor(ROOM_SIZE / 2); | |
const centerY = Math.floor(ROOM_SIZE / 2); | |
// Modern sculpture/fountain (represented as decorative tile) | |
this.tiles[centerY][centerX] = 1; | |
// Geometric pathways | |
for (let i = 0; i < ROOM_SIZE; i++) { | |
if (i !== centerX) { | |
this.tiles[centerY][i] = 0; // Horizontal path | |
} | |
if (i !== centerY) { | |
this.tiles[i][centerX] = 0; // Vertical path | |
} | |
} | |
} | |
createStartingArea() { | |
// Beautiful starting area with Dr. Takara's office | |
// Central office building (Dr. Takara's office) | |
const officeX = 5; | |
const officeY = 2; | |
const officeW = 5; | |
const officeH = 4; | |
// Office building walls | |
for (let x = officeX; x < officeX + officeW; x++) { | |
for (let y = officeY; y < officeY + officeH; y++) { | |
if ( | |
x === officeX || | |
x === officeX + officeW - 1 || | |
y === officeY || | |
y === officeY + officeH - 1 | |
) { | |
this.tiles[y][x] = TILE_TYPES.WALL; | |
} | |
} | |
} | |
// Glass entrance | |
this.tiles[officeY + officeH - 1][officeX + 2] = TILE_TYPES.GLASS; | |
// Lab entrance behind the office | |
this.tiles[officeY + officeH + 2][officeX + 2] = | |
TILE_TYPES.LAB_ENTRANCE; | |
// Clear path from office to lab entrance | |
this.tiles[officeY + officeH][officeX + 2] = TILE_TYPES.FLOOR; | |
this.tiles[officeY + officeH + 1][officeX + 2] = TILE_TYPES.FLOOR; | |
// Decorative garden elements | |
// Cherry blossom trees (Japanese style) | |
const treePositions = [ | |
{ x: 2, y: 2 }, | |
{ x: 12, y: 2 }, | |
{ x: 2, y: 12 }, | |
{ x: 12, y: 12 }, | |
{ x: 1, y: 7 }, | |
{ x: 13, y: 7 }, | |
{ x: 7, y: 1 }, | |
{ x: 7, y: 13 }, | |
]; | |
treePositions.forEach((pos) => { | |
if (pos.x < ROOM_SIZE && pos.y < ROOM_SIZE) { | |
this.tiles[pos.y][pos.x] = TILE_TYPES.WALL; // Trees | |
} | |
}); | |
// Stone pathways | |
for (let x = 3; x < 12; x++) { | |
if (x !== officeX + 2) { | |
this.tiles[officeY + officeH + 2][x] = TILE_TYPES.FLOOR; | |
} | |
} | |
// Zen rock garden | |
this.tiles[3][3] = TILE_TYPES.WALL; | |
this.tiles[11][3] = TILE_TYPES.WALL; | |
this.tiles[3][11] = TILE_TYPES.WALL; | |
this.tiles[11][11] = TILE_TYPES.WALL; | |
// Welcome sign | |
this.tiles[1][7] = TILE_TYPES.WALL; | |
} | |
addOutdoorResearchEquipment() { | |
// Weather monitoring stations and outdoor equipment | |
const equipmentPositions = [ | |
{ x: 6, y: 3 }, | |
{ x: 8, y: 3 }, | |
{ x: 6, y: 11 }, | |
{ x: 8, y: 11 }, | |
]; | |
equipmentPositions.forEach((pos) => { | |
if ( | |
pos.x < ROOM_SIZE && | |
pos.y < ROOM_SIZE && | |
this.tiles[pos.y][pos.x] === 0 | |
) { | |
this.tiles[pos.y][pos.x] = 1; | |
} | |
}); | |
} | |
addBuildingEntrances() { | |
// Add discrete building entrances that lead to underground levels | |
const entranceType = Math.floor(seededRandom(this.seed + 3000) * 3); | |
switch (entranceType) { | |
case 0: | |
this.createLabEntrance(); | |
break; | |
case 1: | |
this.createDatacenterEntrance(); | |
break; | |
default: | |
this.createMainEntrance(); | |
} | |
} | |
createLabEntrance() { | |
// Modern lab building entrance | |
const entranceX = Math.floor(ROOM_SIZE / 2); | |
const entranceY = 2; | |
// Building structure around entrance | |
for (let dx = -2; dx <= 2; dx++) { | |
for (let dy = 0; dy <= 3; dy++) { | |
const x = entranceX + dx; | |
const y = entranceY + dy; | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
if (dx === -2 || dx === 2 || dy === 0) { | |
this.tiles[y][x] = 1; // Building walls | |
} else if (dy === 3 && dx === 0) { | |
this.tiles[y][x] = TILE_TYPES.LAB_ENTRANCE; // Lab entrance marker | |
} else if (dy === 3 && Math.abs(dx) === 1) { | |
this.tiles[y][x] = TILE_TYPES.GLASS; // Glass doors | |
} | |
} | |
} | |
} | |
// Clear path to entrance | |
for (let y = entranceY + 4; y < ROOM_SIZE; y++) { | |
this.tiles[y][entranceX] = 0; | |
} | |
} | |
createDatacenterEntrance() { | |
// Secure datacenter facility entrance | |
const entranceX = Math.floor(ROOM_SIZE / 2); | |
const entranceY = ROOM_SIZE - 6; | |
// Security building | |
for (let dx = -3; dx <= 3; dx++) { | |
for (let dy = 0; dy <= 4; dy++) { | |
const x = entranceX + dx; | |
const y = entranceY + dy; | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
if (dx === -3 || dx === 3 || dy === 0 || dy === 4) { | |
this.tiles[y][x] = 1; // Security walls | |
} else if (dy === 4 && dx === 0) { | |
this.tiles[y][x] = TILE_TYPES.DATACENTER_ENTRANCE; // Datacenter entrance marker | |
} else if (dy === 4 && Math.abs(dx) === 1) { | |
this.tiles[y][x] = 1; // Security barriers | |
} | |
} | |
} | |
} | |
// Clear approach path | |
for (let y = 0; y < entranceY; y++) { | |
this.tiles[y][entranceX] = 0; | |
} | |
} | |
createMainEntrance() { | |
// Grand main entrance that can access both levels | |
const entranceX = Math.floor(ROOM_SIZE / 2); | |
const entranceY = Math.floor(ROOM_SIZE / 2) - 2; | |
// Large entrance building | |
for (let dx = -3; dx <= 3; dx++) { | |
for (let dy = -2; dy <= 2; dy++) { | |
const x = entranceX + dx; | |
const y = entranceY + dy; | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
if (Math.abs(dx) === 3 || Math.abs(dy) === 2) { | |
this.tiles[y][x] = 1; // Main building walls | |
} else if (dy === 2 && dx === 0) { | |
this.tiles[y][x] = TILE_TYPES.MAIN_ENTRANCE; // Main entrance marker | |
} else if (dy === 2 && Math.abs(dx) === 1) { | |
this.tiles[y][x] = TILE_TYPES.GLASS; // Glass entrance | |
} | |
} | |
} | |
} | |
// Clear pathways in all directions | |
for (let i = 0; i < ROOM_SIZE; i++) { | |
if (i !== entranceY + 2) { | |
this.tiles[i][entranceX] = 0; // Vertical path | |
} | |
if (i !== entranceX) { | |
this.tiles[entranceY + 2][i] = 0; // Horizontal path | |
} | |
} | |
} | |
createCubicleCluster(x, y, width, height) { | |
// Create cubicle layout with partitions | |
for (let dx = 0; dx < width; dx++) { | |
for (let dy = 0; dy < height; dy++) { | |
if (dx % 2 === 0 || dy % 2 === 0) { | |
this.tiles[y + dy][x + dx] = 1; // Partition or desk | |
} | |
} | |
} | |
} | |
createOfficeRoom(x, y, width, height) { | |
// Create enclosed office room | |
for (let dx = 0; dx < width; dx++) { | |
this.tiles[y][x + dx] = 1; // Top wall | |
this.tiles[y + height - 1][x + dx] = 1; // Bottom wall | |
} | |
for (let dy = 0; dy < height; dy++) { | |
this.tiles[y + dy][x] = 1; // Left wall | |
this.tiles[y + dy][x + width - 1] = 1; // Right wall | |
} | |
// Add desk in corner | |
this.tiles[y + 1][x + 1] = 1; | |
this.tiles[y + 1][x + 2] = 1; | |
} | |
createEquipmentStation(x, y, width, height) { | |
// Create lab equipment cluster | |
for (let dx = 0; dx < width; dx++) { | |
for (let dy = 0; dy < height; dy++) { | |
this.tiles[y + dy][x + dx] = 1; | |
} | |
} | |
} | |
createLabRoom(x, y, width, height) { | |
// Create lab room with equipment | |
this.createOfficeRoom(x, y, width, height); | |
// Add lab equipment inside | |
if (width > 2 && height > 2) { | |
this.tiles[y + 1][x + width - 2] = 1; // Equipment | |
this.tiles[y + height - 2][x + 1] = 1; // More equipment | |
} | |
} | |
createHVACUnit(x, y, width, height) { | |
// Create HVAC unit | |
for (let dx = 0; dx < width; dx++) { | |
for (let dy = 0; dy < height; dy++) { | |
this.tiles[y + dy][x + dx] = 1; | |
} | |
} | |
} | |
ensureDoorConnectivity() { | |
// Ensure all doors have clear paths | |
const doorX = Math.floor(ROOM_SIZE / 2); | |
const doorY = Math.floor(ROOM_SIZE / 2); | |
// Clear door areas | |
this.tiles[0][doorX] = 0; // North | |
this.tiles[ROOM_SIZE - 1][doorX] = 0; // South | |
this.tiles[doorY][0] = 0; // West | |
this.tiles[doorY][ROOM_SIZE - 1] = 0; // East | |
// Clear wider paths to doors (3-tile wide corridors) | |
for (let i = -1; i <= 1; i++) { | |
// North-South corridor | |
if (doorX + i >= 0 && doorX + i < ROOM_SIZE) { | |
this.tiles[1][doorX + i] = 0; | |
this.tiles[ROOM_SIZE - 2][doorX + i] = 0; | |
this.tiles[2][doorX + i] = 0; | |
this.tiles[ROOM_SIZE - 3][doorX + i] = 0; | |
} | |
// East-West corridor | |
if (doorY + i >= 0 && doorY + i < ROOM_SIZE) { | |
this.tiles[doorY + i][1] = 0; | |
this.tiles[doorY + i][ROOM_SIZE - 2] = 0; | |
this.tiles[doorY + i][2] = 0; | |
this.tiles[doorY + i][ROOM_SIZE - 3] = 0; | |
} | |
} | |
// Ensure center area is always clear (for stairs/entrances) | |
for (let dx = -1; dx <= 1; dx++) { | |
for (let dy = -1; dy <= 1; dy++) { | |
const x = doorX + dx; | |
const y = doorY + dy; | |
if (x >= 0 && x < ROOM_SIZE && y >= 0 && y < ROOM_SIZE) { | |
this.tiles[y][x] = 0; | |
} | |
} | |
} | |
} | |
spawnEntities() { | |
// Balanced AI spawn rate | |
const numAI = Math.floor(seededRandom(this.seed + 1000) * 3) + 1; | |
const hasNPC = seededRandom(this.seed + 2000) > 0.4; // More NPCs | |
const numFurniture = | |
Math.floor(seededRandom(this.seed + 9000) * 2) + 1; | |
const numEnvironment = | |
Math.floor(seededRandom(this.seed + 8000) * 5) + 3; | |
// Spawn AI architectures with rarity system | |
for (let i = 0; i < numAI; i++) { | |
const ai = this.selectAIByRarity(this.seed + 3000 + i); | |
let x, | |
y, | |
attempts = 0; | |
do { | |
x = Math.floor( | |
seededRandom(this.seed + 4000 + i + attempts) * ROOM_SIZE | |
); | |
y = Math.floor( | |
seededRandom(this.seed + 5000 + i + attempts) * ROOM_SIZE | |
); | |
attempts++; | |
} while (!this.isValidSpawnPosition(x, y) && attempts < 50); | |
// Only spawn if we found a valid position | |
if (attempts < 50) { | |
this.entities.push({ | |
type: "ai", | |
x, | |
y, | |
data: ai, | |
id: `${this.roomX},${this.roomY},${i}`, | |
}); | |
} | |
} | |
// Spawn NPC | |
if (hasNPC) { | |
let npc; | |
// Special case: Dr. Takara in starting area | |
if (this.roomX === 0 && this.roomY === 1) { | |
npc = NPC_TYPES.find((n) => n.name === "Dr. Takara Mono"); | |
} else { | |
// Regular NPCs (exclude Dr. Takara from random spawns) | |
const regularNPCs = NPC_TYPES.filter((n) => !n.isStoryNPC); | |
npc = | |
regularNPCs[ | |
Math.floor( | |
seededRandom(this.seed + 6000) * regularNPCs.length | |
) | |
]; | |
} | |
let x, | |
y, | |
attempts = 0; | |
do { | |
x = Math.floor( | |
seededRandom(this.seed + 7000 + attempts) * ROOM_SIZE | |
); | |
y = Math.floor( | |
seededRandom(this.seed + 8000 + attempts) * ROOM_SIZE | |
); | |
attempts++; | |
} while (!this.isValidSpawnPosition(x, y) && attempts < 50); | |
// Only spawn if we found a valid position | |
if (attempts < 50) { | |
// Special positioning for Dr. Takara | |
if (npc.name === "Dr. Takara Mono") { | |
x = 7; // In front of his office | |
y = 7; | |
} | |
this.entities.push({ | |
type: "npc", | |
x, | |
y, | |
data: npc, | |
dialogueIndex: 0, | |
}); | |
} | |
} | |
// Spawn biome-specific furniture | |
const biomeFurniture = LAB_FURNITURE[this.biome] || LAB_FURNITURE.LAB; | |
for (let i = 0; i < numFurniture; i++) { | |
const furniture = | |
biomeFurniture[ | |
Math.floor( | |
seededRandom(this.seed + 10000 + i) * biomeFurniture.length | |
) | |
]; | |
let x, | |
y, | |
attempts = 0; | |
do { | |
x = Math.floor( | |
seededRandom(this.seed + 11000 + i + attempts) * ROOM_SIZE | |
); | |
y = Math.floor( | |
seededRandom(this.seed + 12000 + i + attempts) * ROOM_SIZE | |
); | |
attempts++; | |
} while (!this.isValidSpawnPosition(x, y) && attempts < 50); | |
if (attempts < 50) { | |
this.entities.push({ | |
type: "furniture", | |
x, | |
y, | |
data: furniture, | |
id: `furniture_${this.roomX},${this.roomY},${i}`, | |
}); | |
} | |
} | |
// Spawn environment objects | |
const biomeEnvironment = ENVIRONMENT[this.biome] || ENVIRONMENT.LAB; | |
for (let i = 0; i < numEnvironment; i++) { | |
const envObj = | |
biomeEnvironment[ | |
Math.floor( | |
seededRandom(this.seed + 13000 + i) * biomeEnvironment.length | |
) | |
]; | |
let x, | |
y, | |
attempts = 0; | |
do { | |
x = Math.floor( | |
seededRandom(this.seed + 14000 + i + attempts) * ROOM_SIZE | |
); | |
y = Math.floor( | |
seededRandom(this.seed + 15000 + i + attempts) * ROOM_SIZE | |
); | |
attempts++; | |
} while (!this.isValidSpawnPosition(x, y) && attempts < 50); | |
if (attempts < 50) { | |
this.entities.push({ | |
type: "environment", | |
x, | |
y, | |
data: envObj, | |
id: `env_${this.roomX},${this.roomY},${i}`, | |
}); | |
} | |
} | |
} | |
// AI rarity selection system | |
selectAIByRarity(seed) { | |
const roll = seededRandom(seed); | |
let cumulativeRate = 0; | |
for (const rarity of [ | |
"legendary", | |
"epic", | |
"rare", | |
"uncommon", | |
"common", | |
]) { | |
cumulativeRate += RARITY_RATES[rarity]; | |
if (roll <= cumulativeRate) { | |
const aiOfRarity = AI_TYPES.filter((ai) => ai.rarity === rarity); | |
return aiOfRarity[ | |
Math.floor(seededRandom(seed + 100) * aiOfRarity.length) | |
]; | |
} | |
} | |
// Fallback to common | |
const commonAI = AI_TYPES.filter((ai) => ai.rarity === "common"); | |
return commonAI[ | |
Math.floor(seededRandom(seed + 200) * commonAI.length) | |
]; | |
} | |
// Improved spawn position validation | |
isValidSpawnPosition(x, y) { | |
// Check if tile is walkable | |
if (!WALKABLE_TILES.has(this.tiles[y][x])) { | |
return false; | |
} | |
// Check if position conflicts with existing entities | |
for (const entity of this.entities) { | |
if (entity.x === x && entity.y === y) { | |
return false; | |
} | |
} | |
// Don't spawn too close to doors | |
const doorX = Math.floor(ROOM_SIZE / 2); | |
const doorY = Math.floor(ROOM_SIZE / 2); | |
if (Math.abs(x - doorX) <= 1 && (y === 0 || y === ROOM_SIZE - 1)) | |
return false; | |
if (Math.abs(y - doorY) <= 1 && (x === 0 || x === ROOM_SIZE - 1)) | |
return false; | |
return true; | |
} | |
} | |
// Get or create room | |
function getRoom(roomX, roomY) { | |
const key = `${roomX},${roomY}`; | |
if (!game.rooms.has(key)) { | |
game.rooms.set(key, new Room(roomX, roomY)); | |
} | |
return game.rooms.get(key); | |
} | |
// Input with arrow key scroll prevention | |
const keys = {}; | |
document.addEventListener("keydown", (e) => { | |
keys[e.key.toLowerCase()] = true; | |
if (e.key === " ") interact(); | |
// Prevent arrow keys from scrolling the page | |
if ( | |
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key) | |
) { | |
e.preventDefault(); | |
} | |
}); | |
document.addEventListener("keyup", (e) => { | |
keys[e.key.toLowerCase()] = false; | |
// Prevent arrow keys from scrolling the page | |
if ( | |
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key) | |
) { | |
e.preventDefault(); | |
} | |
}); | |
// Interaction | |
function interact() { | |
const room = getRoom(game.player.roomX, game.player.roomY); | |
const px = Math.round(game.player.x); | |
const py = Math.round(game.player.y); | |
room.entities.forEach((entity) => { | |
const dist = Math.abs(entity.x - px) + Math.abs(entity.y - py); | |
if (dist <= 1) { | |
if (entity.type === "ai" && !game.collectedAI.has(entity.id)) { | |
game.collectedAI.add(entity.id); | |
game.totalAICollected++; | |
// Track rare AI | |
if (["rare", "epic", "legendary"].includes(entity.data.rarity)) { | |
game.rareAICollected++; | |
} | |
// Show rarity in dialogue with emoji | |
const rarityEmoji = { | |
common: "βͺ", | |
uncommon: "π’", | |
rare: "π΅", | |
epic: "π£", | |
legendary: "π‘", | |
}; | |
const rarityText = entity.data.rarity.toUpperCase(); | |
const yearText = entity.data.year ? ` (${entity.data.year})` : ""; | |
showDialogue( | |
`${rarityEmoji[entity.data.rarity]} [${rarityText}] ${ | |
entity.data.name | |
}${yearText} - ${entity.data.desc}` | |
); | |
// Enhanced particles based on rarity | |
const particleCount = | |
entity.data.rarity === "legendary" | |
? 16 | |
: entity.data.rarity === "epic" | |
? 12 | |
: 8; | |
for (let i = 0; i < particleCount; i++) { | |
game.particles.push({ | |
x: entity.x * TILE_SIZE + TILE_SIZE / 2, | |
y: entity.y * TILE_SIZE + TILE_SIZE / 2, | |
vx: (Math.random() - 0.5) * 4, | |
vy: (Math.random() - 0.5) * 4, | |
life: entity.data.rarity === "legendary" ? 30 : 20, | |
color: entity.data.color, | |
}); | |
} | |
updateUI(); | |
checkQuestProgress(); | |
} else if (entity.type === "npc") { | |
const dialogue = entity.data.dialogue[entity.dialogueIndex]; | |
showDialogue(`${entity.data.name}: "${dialogue}"`); | |
entity.dialogueIndex = | |
(entity.dialogueIndex + 1) % entity.data.dialogue.length; | |
if (!game.metNPCs.has(entity.data.name)) { | |
game.metNPCs.add(entity.data.name); | |
// Special case: First meeting with Dr. Takara starts the quest | |
if (entity.data.name === "Dr. Takara Mono") { | |
setTimeout(() => { | |
const firstQuest = STORY_QUESTS[0]; | |
showDialogue( | |
`Quest Started: ${firstQuest.title} - ${firstQuest.description}` | |
); | |
}, 3000); | |
} | |
updateUI(); | |
checkQuestProgress(); | |
} | |
} else if (entity.type === "furniture") { | |
if (entity.data.name.includes("Stairs")) { | |
const direction = entity.data.name.includes("Up") ? 1 : -1; | |
game.player.roomY += direction; | |
game.player.x = Math.floor(ROOM_SIZE / 2); | |
game.player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add( | |
`${game.player.roomX},${game.player.roomY}` | |
); | |
updateUI(); | |
showDialogue( | |
`${direction > 0 ? "Going up" : "Going down"} to ${getBiome( | |
game.player.roomY | |
)} level` | |
); | |
} else if (entity.data.name.includes("Exit to Surface")) { | |
game.player.roomY = 1; // Return to surface | |
game.player.x = Math.floor(ROOM_SIZE / 2); | |
game.player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add( | |
`${game.player.roomX},${game.player.roomY}` | |
); | |
updateUI(); | |
showDialogue("Returning to surface via secure elevator"); | |
} else if (entity.data.name.includes("Information Kiosk")) { | |
showDialogue( | |
"Takara.ai Research Campus - Use building entrances to access underground facilities" | |
); | |
} else { | |
showDialogue( | |
`Takara.ai ${entity.data.name} - Advanced research equipment` | |
); | |
} | |
} | |
} | |
}); | |
} | |
let dialogueTimeout; | |
function showDialogue(text) { | |
const el = document.getElementById("dialogue"); | |
// Clear any existing timeout to prevent overlapping | |
if (dialogueTimeout) { | |
clearTimeout(dialogueTimeout); | |
} | |
el.textContent = text; | |
el.style.display = "block"; | |
// Dynamic timing based on text length (minimum 4s, +100ms per character over 50) | |
const baseTime = 4000; | |
const extraTime = Math.max(0, text.length - 50) * 100; | |
const totalTime = baseTime + extraTime; | |
dialogueTimeout = setTimeout(() => { | |
el.style.display = "none"; | |
dialogueTimeout = null; | |
}, totalTime); | |
} | |
// Quest and storyline management | |
function checkQuestProgress() { | |
const currentQuest = STORY_QUESTS[game.currentQuest]; | |
if (!currentQuest || currentQuest.completed) return; | |
let completed = false; | |
switch (currentQuest.target.type) { | |
case "collect_ai": | |
completed = game.collectedAI.size >= currentQuest.target.count; | |
break; | |
case "meet_npcs": | |
completed = game.metNPCs.size >= currentQuest.target.count; | |
break; | |
case "collect_rare_ai": | |
completed = game.rareAICollected > 0; | |
break; | |
} | |
if (completed && !currentQuest.completed) { | |
currentQuest.completed = true; | |
game.completedQuests.add(currentQuest.id); | |
showDialogue( | |
`Quest Complete: ${currentQuest.title}! Reward: ${currentQuest.reward}` | |
); | |
// Move to next quest | |
if (game.currentQuest < STORY_QUESTS.length - 1) { | |
game.currentQuest++; | |
setTimeout(() => { | |
const nextQuest = STORY_QUESTS[game.currentQuest]; | |
showDialogue( | |
`New Quest: ${nextQuest.title} - ${nextQuest.description}` | |
); | |
}, 3000); | |
} else { | |
setTimeout(() => { | |
showDialogue( | |
"Congratulations! You've completed all quests and become a true AI Architecture Master!" | |
); | |
}, 3000); | |
} | |
} | |
} | |
function updateUI() { | |
document.getElementById("aiCount").textContent = game.collectedAI.size; | |
document.getElementById( | |
"roomId" | |
).textContent = `${game.player.roomX},${game.player.roomY}`; | |
document.getElementById("biome").textContent = getBiome( | |
game.player.roomY | |
); | |
document.getElementById("npcCount").textContent = game.metNPCs.size; | |
// Update quest display | |
updateQuestDisplay(); | |
} | |
function updateQuestDisplay() { | |
const currentQuest = STORY_QUESTS[game.currentQuest]; | |
if (currentQuest && !currentQuest.completed) { | |
document.getElementById("questTitle").textContent = | |
currentQuest.title; | |
document.getElementById("questDesc").textContent = | |
currentQuest.description; | |
} else if (game.currentQuest >= STORY_QUESTS.length) { | |
document.getElementById("questTitle").textContent = | |
"All Quests Complete!"; | |
document.getElementById("questDesc").textContent = | |
"You are now an AI Architecture Master!"; | |
} | |
} | |
// Update | |
function update(deltaTime) { | |
const player = game.player; | |
const speed = 4.0; // tiles per second | |
const frameSpeed = speed * (deltaTime / 1000); // convert to per-frame movement | |
let dx = 0, | |
dy = 0; | |
if (keys["arrowup"] || keys["w"]) { | |
dy = -frameSpeed; | |
player.facing = "up"; | |
} else if (keys["arrowdown"] || keys["s"]) { | |
dy = frameSpeed; | |
player.facing = "down"; | |
} else if (keys["arrowleft"] || keys["a"]) { | |
dx = -frameSpeed; | |
player.facing = "left"; | |
} else if (keys["arrowright"] || keys["d"]) { | |
dx = frameSpeed; | |
player.facing = "right"; | |
} | |
if (dx || dy) { | |
const room = getRoom(player.roomX, player.roomY); | |
const newX = player.x + dx; | |
const newY = player.y + dy; | |
// Room transitions | |
if (newX < 0) { | |
player.roomX--; | |
player.x = ROOM_SIZE - 3; // Place player safely away from edge | |
player.y = Math.floor(ROOM_SIZE / 2); // Center on door | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
} else if (newX >= ROOM_SIZE) { | |
player.roomX++; | |
player.x = 2; // Place player safely away from edge | |
player.y = Math.floor(ROOM_SIZE / 2); // Center on door | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
} else if (newY < 0) { | |
player.roomY--; | |
player.y = ROOM_SIZE - 3; // Place player safely away from edge | |
player.x = Math.floor(ROOM_SIZE / 2); // Center on door | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
} else if (newY >= ROOM_SIZE) { | |
player.roomY++; | |
player.y = 2; // Place player safely away from edge | |
player.x = Math.floor(ROOM_SIZE / 2); // Center on door | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
} else { | |
// Normal movement - check if tile is walkable (Pokemon-style collision) | |
const tileType = room.tiles[Math.floor(newY)][Math.floor(newX)]; | |
if (WALKABLE_TILES.has(tileType)) { | |
player.x = newX; | |
player.y = newY; | |
// Handle different transition types | |
if (tileType === TILE_TYPES.STAIRS) { | |
// Stair interaction - only in underground areas | |
const centerX = Math.floor(newX); | |
const centerY = Math.floor(newY); | |
if ( | |
Math.abs(newX - (centerX + 0.5)) < 0.3 && | |
Math.abs(newY - (centerY + 0.5)) < 0.3 | |
) { | |
// Smart stair direction based on current biome | |
let direction; | |
const currentBiome = getBiome(player.roomY); | |
if (currentBiome === "LAB") { | |
// Can go up to outside or down to datacenter | |
direction = seededRandom(Date.now()) > 0.5 ? 1 : -1; | |
} else { | |
// DATACENTER | |
direction = 1; // Go up to lab | |
} | |
player.roomY += direction; | |
player.x = Math.floor(ROOM_SIZE / 2); | |
player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
showDialogue( | |
`Moving to ${getBiome( | |
player.roomY | |
)} level via internal stairway` | |
); | |
} | |
} else if (tileType === TILE_TYPES.LAB_ENTRANCE) { | |
// Lab entrance - go to lab level | |
player.roomY = 0; // Lab level | |
player.x = Math.floor(ROOM_SIZE / 2); | |
player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
showDialogue("Entering Takara.ai Research Laboratory"); | |
} else if (tileType === TILE_TYPES.DATACENTER_ENTRANCE) { | |
// Datacenter entrance - go to datacenter level | |
player.roomY = -1; // Datacenter level | |
player.x = Math.floor(ROOM_SIZE / 2); | |
player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
showDialogue("Accessing Takara.ai Secure Datacenter"); | |
} else if (tileType === TILE_TYPES.MAIN_ENTRANCE) { | |
// Main entrance - choose destination | |
const destination = seededRandom(Date.now()) > 0.5 ? 0 : -1; | |
player.roomY = destination; | |
player.x = Math.floor(ROOM_SIZE / 2); | |
player.y = Math.floor(ROOM_SIZE / 2); | |
game.visitedRooms.add(`${player.roomX},${player.roomY}`); | |
updateUI(); | |
showDialogue( | |
destination === 0 | |
? "Welcome to Takara.ai Research Laboratory" | |
: "Authorized access to Secure Datacenter" | |
); | |
} | |
} | |
} | |
player.animFrame += 5.0 * (deltaTime / 1000); // 5 animation cycles per second | |
} | |
// Update particles | |
game.particles = game.particles.filter((p) => { | |
const frameMultiplier = deltaTime / 16.67; // normalize to 60fps | |
p.x += p.vx * frameMultiplier; | |
p.y += p.vy * frameMultiplier; | |
p.vy += 0.5 * frameMultiplier; | |
p.life -= frameMultiplier; | |
return p.life > 0; | |
}); | |
} | |
// Pokemon-style tile drawing system | |
function drawFloorTile(x, y, biome, tileX, tileY, seed) { | |
const variation = Math.floor( | |
seededRandom(seed + tileX * 100 + tileY) * 4 | |
); | |
switch (biome) { | |
case "OUTSIDE": | |
drawGrassTile(x, y, variation); | |
break; | |
case "OFFICE": | |
drawOfficeTile(x, y, variation); | |
break; | |
case "DATACENTER": | |
drawDatacenterTile(x, y, variation); | |
break; | |
default: | |
drawLabTile(x, y, variation); | |
} | |
} | |
function drawWallTile(x, y, biome, tileX, tileY, room) { | |
// Check surrounding tiles for wall connections | |
const connections = { | |
top: tileY > 0 && room.tiles[tileY - 1][tileX] === 1, | |
bottom: tileY < ROOM_SIZE - 1 && room.tiles[tileY + 1][tileX] === 1, | |
left: tileX > 0 && room.tiles[tileY][tileX - 1] === 1, | |
right: tileX < ROOM_SIZE - 1 && room.tiles[tileY][tileX + 1] === 1, | |
}; | |
switch (biome) { | |
case "OUTSIDE": | |
drawTreeWall(x, y, connections); | |
break; | |
case "OFFICE": | |
drawOfficeWall(x, y, connections); | |
break; | |
case "DATACENTER": | |
drawServerWall(x, y, connections); | |
break; | |
default: | |
drawLabWall(x, y, connections); | |
} | |
} | |
function drawGrassTile(x, y, variation) { | |
// Base grass | |
ctx.fillStyle = "#4a7c59"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Grass details | |
ctx.fillStyle = "#5d8a6b"; | |
switch (variation) { | |
case 0: | |
// Small grass tufts | |
for (let i = 0; i < 6; i++) { | |
const gx = x + (i % 3) * 10 + 6; | |
const gy = y + Math.floor(i / 3) * 16 + 8; | |
ctx.fillRect(gx, gy, 2, 4); | |
} | |
break; | |
case 1: | |
// Diagonal pattern | |
ctx.fillRect(x + 4, y + 4, 8, 2); | |
ctx.fillRect(x + 16, y + 16, 8, 2); | |
ctx.fillRect(x + 8, y + 24, 8, 2); | |
break; | |
case 2: | |
// Flower spots | |
ctx.fillStyle = "#7fb069"; | |
ctx.fillRect(x + 8, y + 8, 4, 4); | |
ctx.fillRect(x + 20, y + 20, 4, 4); | |
ctx.fillStyle = "#ff6b6b"; | |
ctx.fillRect(x + 9, y + 9, 2, 2); | |
ctx.fillRect(x + 21, y + 21, 2, 2); | |
break; | |
default: | |
// Clean grass | |
break; | |
} | |
} | |
function drawOfficeTile(x, y, variation) { | |
// Base carpet | |
ctx.fillStyle = "#e8e8e8"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Carpet pattern | |
ctx.fillStyle = "#d0d0d0"; | |
switch (variation) { | |
case 0: | |
// Grid pattern | |
ctx.fillRect(x, y + 15, TILE_SIZE, 2); | |
ctx.fillRect(x + 15, y, 2, TILE_SIZE); | |
break; | |
case 1: | |
// Diamond pattern | |
ctx.beginPath(); | |
ctx.moveTo(x + 16, y + 8); | |
ctx.lineTo(x + 24, y + 16); | |
ctx.lineTo(x + 16, y + 24); | |
ctx.lineTo(x + 8, y + 16); | |
ctx.closePath(); | |
ctx.fill(); | |
break; | |
default: | |
// Subtle texture | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(x + i * 8 + 2, y + 2, 1, 1); | |
ctx.fillRect(x + i * 8 + 6, y + 18, 1, 1); | |
} | |
} | |
} | |
function drawLabTile(x, y, variation) { | |
// Base lab floor | |
ctx.fillStyle = "#f0f0f0"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Lab floor details | |
ctx.fillStyle = "#e0e0e0"; | |
ctx.strokeStyle = "#d0d0d0"; | |
ctx.lineWidth = 1; | |
// Tile borders | |
ctx.strokeRect(x, y, TILE_SIZE, TILE_SIZE); | |
if (variation === 1) { | |
// Ventilation grate | |
for (let i = 0; i < 3; i++) { | |
ctx.fillRect(x + 6 + i * 6, y + 8, 4, 2); | |
ctx.fillRect(x + 6 + i * 6, y + 16, 4, 2); | |
ctx.fillRect(x + 6 + i * 6, y + 24, 4, 2); | |
} | |
} | |
} | |
function drawDatacenterTile(x, y, variation) { | |
// Base metal floor | |
ctx.fillStyle = "#2c2c2c"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Metal grating | |
ctx.fillStyle = "#404040"; | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(x + i * 8, y + 4, 6, 2); | |
ctx.fillRect(x + i * 8, y + 12, 6, 2); | |
ctx.fillRect(x + i * 8, y + 20, 6, 2); | |
ctx.fillRect(x + i * 8, y + 28, 6, 2); | |
} | |
if (variation === 2) { | |
// Cable channels | |
ctx.fillStyle = "#1a1a1a"; | |
ctx.fillRect(x + 12, y, 8, TILE_SIZE); | |
} | |
} | |
function drawTreeWall(x, y, connections) { | |
// Tree trunk base | |
ctx.fillStyle = "#8b4513"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Tree bark texture | |
ctx.fillStyle = "#654321"; | |
for (let i = 0; i < 3; i++) { | |
ctx.fillRect(x + 4 + i * 8, y + 2, 2, 6); | |
ctx.fillRect(x + 2 + i * 8, y + 12, 3, 4); | |
ctx.fillRect(x + 6 + i * 8, y + 22, 2, 8); | |
} | |
// Canopy parts based on connections | |
ctx.fillStyle = "#228b22"; | |
if (!connections.top) { | |
// Top canopy | |
ctx.fillRect(x, y - 8, TILE_SIZE, 12); | |
} | |
if (!connections.left) { | |
// Left canopy | |
ctx.fillRect(x - 8, y, 12, TILE_SIZE); | |
} | |
if (!connections.right) { | |
// Right canopy | |
ctx.fillRect(x + TILE_SIZE - 4, y, 12, TILE_SIZE); | |
} | |
} | |
function drawOfficeWall(x, y, connections) { | |
// Base wall | |
ctx.fillStyle = "#f5f5f5"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Wall panels | |
ctx.fillStyle = "#e8e8e8"; | |
ctx.fillRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4); | |
// Borders based on connections | |
ctx.fillStyle = "#d0d0d0"; | |
if (!connections.top) ctx.fillRect(x, y, TILE_SIZE, 4); | |
if (!connections.bottom) | |
ctx.fillRect(x, y + TILE_SIZE - 4, TILE_SIZE, 4); | |
if (!connections.left) ctx.fillRect(x, y, 4, TILE_SIZE); | |
if (!connections.right) | |
ctx.fillRect(x + TILE_SIZE - 4, y, 4, TILE_SIZE); | |
// Decorative elements | |
if (!connections.top && !connections.left) { | |
// Corner decoration | |
ctx.fillStyle = "#c0c0c0"; | |
ctx.fillRect(x + 4, y + 4, 8, 8); | |
} | |
} | |
function drawLabWall(x, y, connections) { | |
// Base lab wall | |
ctx.fillStyle = "#34495e"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Panel lines | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(x + 8, y, 2, TILE_SIZE); | |
ctx.fillRect(x + 22, y, 2, TILE_SIZE); | |
ctx.fillRect(x, y + 8, TILE_SIZE, 2); | |
ctx.fillRect(x, y + 22, TILE_SIZE, 2); | |
// Connection shadows | |
ctx.fillStyle = "#1a252f"; | |
if (connections.top) ctx.fillRect(x, y, TILE_SIZE, 2); | |
if (connections.left) ctx.fillRect(x, y, 2, TILE_SIZE); | |
} | |
function drawServerWall(x, y, connections) { | |
// Server rack | |
ctx.fillStyle = "#1a1a1a"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Server units | |
for (let i = 0; i < 4; i++) { | |
ctx.fillStyle = i % 2 === 0 ? "#2c2c2c" : "#1a1a1a"; | |
ctx.fillRect(x + 2, y + i * 8, TILE_SIZE - 4, 6); | |
// LED indicators | |
ctx.fillStyle = "#00ff00"; | |
ctx.fillRect(x + TILE_SIZE - 6, y + i * 8 + 2, 2, 2); | |
ctx.fillStyle = "#ff4444"; | |
ctx.fillRect(x + TILE_SIZE - 6, y + i * 8 + 5, 2, 1); | |
} | |
// Cables | |
if (connections.bottom) { | |
ctx.fillStyle = "#666"; | |
ctx.fillRect(x + 4, y + TILE_SIZE - 2, 4, 2); | |
ctx.fillRect(x + 12, y + TILE_SIZE - 2, 4, 2); | |
} | |
} | |
function drawFurnitureTile(x, y, name) { | |
if (name.includes("Stairs")) { | |
// Stairs tile | |
const direction = name.includes("Up") ? "up" : "down"; | |
ctx.fillStyle = "#8B4513"; | |
if (direction === "up") { | |
// Ascending stairs | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(x + i * 8, y + 24 - i * 6, 8, 6); | |
} | |
// Up arrow | |
ctx.fillStyle = "#FFD700"; | |
ctx.beginPath(); | |
ctx.moveTo(x + 16, y + 4); | |
ctx.lineTo(x + 8, y + 12); | |
ctx.lineTo(x + 24, y + 12); | |
ctx.closePath(); | |
ctx.fill(); | |
} else { | |
// Descending stairs | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(x + i * 8, y + i * 6, 8, 6); | |
} | |
// Down arrow | |
ctx.fillStyle = "#FFD700"; | |
ctx.beginPath(); | |
ctx.moveTo(x + 16, y + 28); | |
ctx.lineTo(x + 8, y + 20); | |
ctx.lineTo(x + 24, y + 20); | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
} else if (name.includes("Terminal") || name.includes("Display")) { | |
// Terminal tile | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(x + 4, y + 8, 24, 16); | |
// Screen | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(x + 6, y + 10, 20, 12); | |
// Takara.ai logo | |
ctx.fillStyle = "#DC143C"; | |
ctx.font = "12px monospace"; | |
ctx.textAlign = "center"; | |
ctx.fillText("ιΆ΄", x + 16, y + 18); | |
// Glow effect | |
ctx.shadowColor = "#DC143C"; | |
ctx.shadowBlur = 4; | |
ctx.strokeStyle = "#DC143C"; | |
ctx.strokeRect(x + 6, y + 10, 20, 12); | |
ctx.shadowBlur = 0; | |
} else if (name.includes("Coffee")) { | |
// Coffee station tile | |
ctx.fillStyle = "#654321"; | |
ctx.fillRect(x + 6, y + 6, 20, 20); | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(x + 8, y + 8, 16, 16); | |
// Coffee pot | |
ctx.fillStyle = "#2F4F4F"; | |
ctx.fillRect(x + 12, y + 10, 8, 10); | |
ctx.fillStyle = "#D2691E"; | |
ctx.fillRect(x + 14, y + 12, 4, 6); | |
} else { | |
// Default interactive furniture | |
ctx.fillStyle = "#DC143C"; | |
ctx.fillRect(x + 8, y + 8, 16, 16); | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(x + 12, y + 12, 8, 8); | |
} | |
} | |
function drawGlassWall(x, y, biome) { | |
// Glass wall with transparency effect | |
ctx.fillStyle = "#e6f3ff"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Glass frame | |
ctx.strokeStyle = "#b3d9ff"; | |
ctx.lineWidth = 2; | |
ctx.strokeRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4); | |
// Glass panels | |
ctx.strokeStyle = "#cce7ff"; | |
ctx.lineWidth = 1; | |
ctx.strokeRect(x + 6, y + 6, TILE_SIZE - 12, TILE_SIZE - 12); | |
// Reflection effect | |
ctx.fillStyle = "rgba(255, 255, 255, 0.3)"; | |
ctx.fillRect(x + 4, y + 4, 8, TILE_SIZE - 8); | |
} | |
function drawStairsTile(x, y) { | |
// Multi-tile staircase - beautiful stone steps | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Stone steps with depth | |
const numSteps = 4; | |
const stepHeight = TILE_SIZE / numSteps; | |
for (let i = 0; i < numSteps; i++) { | |
const stepY = y + i * stepHeight; | |
// Step surface | |
ctx.fillStyle = i % 2 === 0 ? "#A0522D" : "#8B4513"; | |
ctx.fillRect(x + 2, stepY, TILE_SIZE - 4, stepHeight - 1); | |
// Step edge highlight | |
ctx.fillStyle = "#D2B48C"; | |
ctx.fillRect(x + 2, stepY, TILE_SIZE - 4, 1); | |
// Step shadow | |
ctx.fillStyle = "#654321"; | |
ctx.fillRect(x + 2, stepY + stepHeight - 2, TILE_SIZE - 4, 1); | |
} | |
// Ornate center design | |
ctx.fillStyle = "#FFD700"; | |
ctx.font = "20px Arial"; | |
ctx.textAlign = "center"; | |
ctx.fillText("β‘", x + TILE_SIZE / 2, y + TILE_SIZE / 2 + 8); | |
// Handrails with decorative elements | |
ctx.strokeStyle = "#654321"; | |
ctx.lineWidth = 3; | |
ctx.beginPath(); | |
ctx.moveTo(x + 4, y + 4); | |
ctx.lineTo(x + 4, y + TILE_SIZE - 4); | |
ctx.moveTo(x + TILE_SIZE - 4, y + 4); | |
ctx.lineTo(x + TILE_SIZE - 4, y + TILE_SIZE - 4); | |
ctx.stroke(); | |
// Decorative rail caps | |
ctx.fillStyle = "#FFD700"; | |
ctx.fillRect(x + 2, y + 2, 4, 4); | |
ctx.fillRect(x + TILE_SIZE - 6, y + 2, 4, 4); | |
} | |
function drawLabEntrance(x, y) { | |
// Modern lab entrance with clean design | |
ctx.fillStyle = "#f0f0f0"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Entrance frame | |
ctx.fillStyle = "#34495e"; | |
ctx.fillRect(x + 4, y + 4, TILE_SIZE - 8, TILE_SIZE - 8); | |
// Glass center | |
ctx.fillStyle = "#e6f3ff"; | |
ctx.fillRect(x + 8, y + 8, TILE_SIZE - 16, TILE_SIZE - 16); | |
// Lab symbol | |
ctx.fillStyle = "#DC143C"; | |
ctx.font = "16px Arial"; | |
ctx.textAlign = "center"; | |
ctx.fillText("π¬", x + TILE_SIZE / 2, y + TILE_SIZE / 2 + 6); | |
// Entry indicator | |
ctx.fillStyle = "#2ecc71"; | |
ctx.fillRect(x + 2, y + TILE_SIZE - 4, TILE_SIZE - 4, 2); | |
} | |
function drawDatacenterEntrance(x, y) { | |
// Secure datacenter entrance | |
ctx.fillStyle = "#2c2c2c"; | |
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE); | |
// Security frame | |
ctx.fillStyle = "#1a1a1a"; | |
ctx.fillRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4); | |
// Security scanner | |
ctx.fillStyle = "#ff4444"; | |
ctx.fillRect(x + 6, y + 6, TILE_SIZE - 12, TILE_SIZE - 12); | |
// Security symbol | |
ctx.fillStyle = "#FFD700"; | |
ctx.font = "16px Arial"; | |
ctx.textAlign = "center"; | |
ctx.fillText("π", x + TILE_SIZE / 2, y + TILE_SIZE / 2 + 6); | |
// Security lights | |
ctx.fillStyle = "#ff0000"; | |
ctx.fillRect(x + 4, y + 2, 2, 2); | |
ctx.fillRect(x + TILE_SIZE - 6, y + 2, 2, 2); | |
} | |
function drawMainEntrance(x, y) { | |
// Simple entrance - just draw as floor tile | |
drawFloorTile(x, y, "OUTSIDE", 0, 0, 12345); | |
} | |
// Draw sprites for different objects | |
function drawTree(x, y, size = 1) { | |
ctx.save(); | |
ctx.translate(x, y); | |
const s = size * 8; | |
// Tree trunk | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(-s / 4, s / 2, s / 2, s); | |
// Tree canopy | |
ctx.fillStyle = "#228B22"; | |
ctx.beginPath(); | |
ctx.arc(0, 0, s, 0, Math.PI * 2); | |
ctx.fill(); | |
// Darker green details | |
ctx.fillStyle = "#1F5F1F"; | |
ctx.beginPath(); | |
ctx.arc(-s / 3, -s / 3, s / 2, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(s / 3, s / 4, s / 3, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.restore(); | |
} | |
function drawDesk(x, y) { | |
ctx.save(); | |
ctx.translate(x, y); | |
// Desk surface | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(-12, -8, 24, 16); | |
// Desk legs | |
ctx.fillStyle = "#654321"; | |
ctx.fillRect(-10, 6, 3, 6); | |
ctx.fillRect(7, 6, 3, 6); | |
ctx.fillRect(-10, -12, 3, 6); | |
ctx.fillRect(7, -12, 3, 6); | |
// Computer monitor | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-6, -10, 12, 8); | |
ctx.fillStyle = "#3498db"; | |
ctx.fillRect(-5, -9, 10, 6); | |
ctx.restore(); | |
} | |
function drawServerRack(x, y) { | |
ctx.save(); | |
ctx.translate(x, y); | |
// Main rack | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-8, -12, 16, 24); | |
// Server units | |
for (let i = 0; i < 6; i++) { | |
ctx.fillStyle = i % 2 === 0 ? "#34495e" : "#2c3e50"; | |
ctx.fillRect(-7, -10 + i * 3, 14, 2); | |
// LED lights | |
ctx.fillStyle = "#00ff00"; | |
ctx.fillRect(4, -9 + i * 3, 2, 1); | |
} | |
ctx.restore(); | |
} | |
function drawStairs(x, y, direction) { | |
ctx.save(); | |
ctx.translate(x, y); | |
ctx.fillStyle = "#8B4513"; | |
if (direction === "up") { | |
// Ascending stairs | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(-8 + i * 4, 8 - i * 4, 4, 4); | |
} | |
// Arrow indicator | |
ctx.fillStyle = "#FFD700"; | |
ctx.beginPath(); | |
ctx.moveTo(0, -8); | |
ctx.lineTo(-4, -4); | |
ctx.lineTo(4, -4); | |
ctx.closePath(); | |
ctx.fill(); | |
} else { | |
// Descending stairs | |
for (let i = 0; i < 4; i++) { | |
ctx.fillRect(-8 + i * 4, -8 + i * 4, 4, 4); | |
} | |
// Arrow indicator | |
ctx.fillStyle = "#FFD700"; | |
ctx.beginPath(); | |
ctx.moveTo(0, 8); | |
ctx.lineTo(-4, 4); | |
ctx.lineTo(4, 4); | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
ctx.restore(); | |
} | |
function drawTerminal(x, y) { | |
ctx.save(); | |
ctx.translate(x, y); | |
// Terminal base | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-8, -6, 16, 12); | |
// Screen | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(-6, -4, 12, 8); | |
// Takara.ai logo | |
ctx.fillStyle = "#DC143C"; | |
ctx.font = "8px monospace"; | |
ctx.textAlign = "center"; | |
ctx.fillText("ιΆ΄", 0, 0); | |
// Screen glow | |
ctx.shadowColor = "#DC143C"; | |
ctx.shadowBlur = 3; | |
ctx.strokeStyle = "#DC143C"; | |
ctx.strokeRect(-6, -4, 12, 8); | |
ctx.restore(); | |
} | |
function drawBush(x, y) { | |
ctx.save(); | |
ctx.translate(x, y); | |
// Bush clusters | |
ctx.fillStyle = "#32CD32"; | |
ctx.beginPath(); | |
ctx.arc(-3, 0, 4, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(3, 0, 4, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(0, -2, 4, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.restore(); | |
} | |
function drawFilingCabinet(x, y) { | |
ctx.save(); | |
ctx.translate(x, y); | |
// Cabinet body | |
ctx.fillStyle = "#696969"; | |
ctx.fillRect(-6, -8, 12, 16); | |
// Drawers | |
ctx.fillStyle = "#808080"; | |
ctx.fillRect(-5, -6, 10, 3); | |
ctx.fillRect(-5, -2, 10, 3); | |
ctx.fillRect(-5, 2, 10, 3); | |
// Handles | |
ctx.fillStyle = "#C0C0C0"; | |
ctx.fillRect(3, -5, 1, 1); | |
ctx.fillRect(3, -1, 1, 1); | |
ctx.fillRect(3, 3, 1, 1); | |
ctx.restore(); | |
} | |
// Draw Jordan (main character) | |
function drawJordan(x, y, facing, frame) { | |
const s = 2; // pixel size | |
ctx.save(); | |
ctx.translate(x, y); | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Blonde hair - visible under cap | |
ctx.fillStyle = "#f4e04d"; | |
ctx.fillRect(-4 * s, -5 * s, 8 * s, 2 * s); // Hair sides showing under cap | |
ctx.fillRect(-3 * s, -3 * s, 6 * s, s); // Hair in front | |
// Black baseball cap (backwards) | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); // Main cap | |
// Backwards brim | |
ctx.fillRect(-6 * s, -6 * s, 12 * s, 2 * s); // Brim at back | |
// Clear glasses frames | |
ctx.strokeStyle = "#888"; | |
ctx.lineWidth = 1; | |
ctx.strokeRect(-4 * s, -4 * s, 3 * s, 2 * s); | |
ctx.strokeRect(s, -4 * s, 3 * s, 2 * s); | |
// Bridge | |
ctx.fillStyle = "#888"; | |
ctx.fillRect(-s, -4 * s, 2 * s, s); | |
// Blue eyes | |
ctx.fillStyle = "#3498db"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -3 * s, s, s); | |
ctx.fillRect(2 * s, -3 * s, s, s); | |
} | |
// Casual clothing - hoodie | |
ctx.fillStyle = "#34495e"; // Dark gray hoodie | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Hoodie details | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-s, 0, 2 * s, 6 * s); // Zipper | |
ctx.fillRect(-3 * s, s, 6 * s, s); // Hood line | |
// Jeans | |
ctx.fillStyle = "#2980b9"; // Blue jeans | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms & legs with animation | |
const wave = Math.sin(frame) * 2; | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-6 * s, 2 * s + wave, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s - wave, 2 * s, 4 * s); | |
// Sneakers | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(-3 * s, 12 * s, 2 * s, 2 * s + wave); | |
ctx.fillRect(s, 12 * s, 2 * s, 2 * s - wave); | |
ctx.restore(); | |
} | |
// Draw NPCs with unique appearances | |
function drawNPC(x, y, facing, frame, npcData) { | |
const s = 2; // pixel size | |
ctx.save(); | |
ctx.translate(x, y); | |
// Different appearance based on NPC | |
switch (npcData.name) { | |
case "Dr. Takara Mono": | |
drawTakaraMono(s, facing); | |
break; | |
case "Geoffrey Hinton": | |
drawGeoffreyHinton(s, facing); | |
break; | |
case "Yann LeCun": | |
drawYannLeCun(s, facing); | |
break; | |
case "Yoshua Bengio": | |
drawYoshuaBengio(s, facing); | |
break; | |
case "Andrew Ng": | |
drawAndrewNg(s, facing); | |
break; | |
case "Fei-Fei Li": | |
drawFeiFeiLi(s, facing); | |
break; | |
default: | |
drawGenericNPC(s, facing); | |
} | |
ctx.restore(); | |
} | |
function drawGeoffreyHinton(s, facing) { | |
// Older gentleman with gray hair and beard | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Gray hair and beard | |
ctx.fillStyle = "#c0c0c0"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); // Hair | |
ctx.fillRect(-4 * s, -2 * s, 8 * s, 3 * s); // Beard | |
// Eyes | |
ctx.fillStyle = "#654321"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Academic suit | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(-s, 0, 2 * s, 4 * s); // White shirt | |
// Arms and legs | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-3 * s, 8 * s, 2 * s, 4 * s); | |
ctx.fillRect(s, 8 * s, 2 * s, 4 * s); | |
} | |
function drawYannLeCun(s, facing) { | |
// Distinguished with dark hair | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Dark hair | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); | |
// Eyes | |
ctx.fillStyle = "#2c3e50"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Casual academic look | |
ctx.fillStyle = "#3498db"; // Blue shirt | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Dark pants | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
function drawYoshuaBengio(s, facing) { | |
// Friendly academic with glasses | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Brown hair | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); | |
// Glasses | |
ctx.strokeStyle = "#000"; | |
ctx.lineWidth = 1; | |
ctx.strokeRect(-4 * s, -4 * s, 3 * s, 2 * s); | |
ctx.strokeRect(s, -4 * s, 3 * s, 2 * s); | |
// Eyes | |
ctx.fillStyle = "#654321"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Green sweater | |
ctx.fillStyle = "#27ae60"; | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Khaki pants | |
ctx.fillStyle = "#d4af37"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
function drawAndrewNg(s, facing) { | |
// Professional tech look | |
// Head | |
ctx.fillStyle = "#deb887"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Black hair | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); | |
// Eyes | |
ctx.fillStyle = "#2c3e50"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Tech company polo | |
ctx.fillStyle = "#e74c3c"; // Red polo | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Dark jeans | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms | |
ctx.fillStyle = "#deb887"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
function drawFeiFeiLi(s, facing) { | |
// Professional woman | |
// Head | |
ctx.fillStyle = "#deb887"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Black hair (longer style) | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 4 * s); | |
ctx.fillRect(-4 * s, -4 * s, 8 * s, 2 * s); // Hair sides | |
// Eyes | |
ctx.fillStyle = "#2c3e50"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Professional blazer | |
ctx.fillStyle = "#8e44ad"; // Purple blazer | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(-s, 0, 2 * s, 4 * s); // White blouse | |
// Professional pants | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms | |
ctx.fillStyle = "#deb887"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
function drawTakaraMono(s, facing) { | |
// Old grumpy Dr. Takara Mono | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Balding gray hair | |
ctx.fillStyle = "#c0c0c0"; | |
ctx.fillRect(-4 * s, -7 * s, 2 * s, 2 * s); // Left side | |
ctx.fillRect(2 * s, -7 * s, 2 * s, 2 * s); // Right side | |
ctx.fillRect(-2 * s, -7 * s, 4 * s, s); // Top fringe | |
// Thick glasses | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(-4 * s, -4 * s, 3 * s, 3 * s); | |
ctx.fillRect(s, -4 * s, 3 * s, 3 * s); | |
ctx.fillRect(-s, -4 * s, 2 * s, s); | |
// Stern eyes behind glasses | |
ctx.fillStyle = "#654321"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -3 * s, s, s); | |
ctx.fillRect(2 * s, -3 * s, s, s); | |
} | |
// Frown lines | |
ctx.fillStyle = "#d4af37"; | |
ctx.fillRect(-3 * s, -2 * s, s, s); | |
ctx.fillRect(2 * s, -2 * s, s, s); | |
// Traditional Japanese coat (dark blue) | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Red Takara.ai emblem | |
ctx.fillStyle = "#DC143C"; | |
ctx.fillRect(-s, s, 2 * s, 2 * s); | |
// Dark formal pants | |
ctx.fillStyle = "#1a1a1a"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms (crossed when facing down - grumpy pose) | |
ctx.fillStyle = "#fdbcb4"; | |
if (facing === "down") { | |
ctx.fillRect(-2 * s, 3 * s, 4 * s, 2 * s); // Crossed arms | |
} else { | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
} | |
function drawGenericNPC(s, facing) { | |
// Generic researcher | |
// Head | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-4 * s, -6 * s, 8 * s, 6 * s); | |
// Brown hair | |
ctx.fillStyle = "#8B4513"; | |
ctx.fillRect(-5 * s, -7 * s, 10 * s, 3 * s); | |
// Eyes | |
ctx.fillStyle = "#654321"; | |
if (facing !== "up") { | |
ctx.fillRect(-3 * s, -4 * s, s, s); | |
ctx.fillRect(2 * s, -4 * s, s, s); | |
} | |
// Lab coat | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(-4 * s, 0, 8 * s, 8 * s); | |
// Dark pants | |
ctx.fillStyle = "#2c3e50"; | |
ctx.fillRect(-3 * s, 8 * s, 6 * s, 4 * s); | |
// Arms | |
ctx.fillStyle = "#fdbcb4"; | |
ctx.fillRect(-6 * s, 2 * s, 2 * s, 4 * s); | |
ctx.fillRect(4 * s, 2 * s, 2 * s, 4 * s); | |
} | |
// Render | |
function render() { | |
ctx.fillStyle = "#000"; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
const room = getRoom(game.player.roomX, game.player.roomY); | |
const centerX = canvas.width / 2; | |
const centerY = canvas.height / 2; | |
// Draw detailed tile-based world like Pokemon | |
const biome = room.biome; | |
const viewRadius = BIOMES[biome].fogRadius; | |
for (let y = 0; y < ROOM_SIZE; y++) { | |
for (let x = 0; x < ROOM_SIZE; x++) { | |
const screenX = (x - game.player.x) * TILE_SIZE + centerX; | |
const screenY = (y - game.player.y) * TILE_SIZE + centerY; | |
const dist = Math.sqrt( | |
(x - game.player.x) ** 2 + (y - game.player.y) ** 2 | |
); | |
if (dist < viewRadius) { | |
const brightness = | |
biome === "OUTSIDE" ? 1 : 1 - (dist / viewRadius) * 0.7; | |
ctx.globalAlpha = brightness; | |
const tileType = room.tiles[y][x]; | |
if (tileType === TILE_TYPES.FLOOR) { | |
drawFloorTile(screenX, screenY, biome, x, y, room.seed); | |
} else if (tileType === TILE_TYPES.WALL) { | |
drawWallTile(screenX, screenY, biome, x, y, room); | |
} else if (tileType === TILE_TYPES.GLASS) { | |
drawGlassWall(screenX, screenY, biome); | |
} else if (tileType === TILE_TYPES.STAIRS) { | |
drawStairsTile(screenX, screenY); | |
} else if (tileType === TILE_TYPES.LAB_ENTRANCE) { | |
drawLabEntrance(screenX, screenY); | |
} else if (tileType === TILE_TYPES.DATACENTER_ENTRANCE) { | |
drawDatacenterEntrance(screenX, screenY); | |
} else if (tileType === TILE_TYPES.MAIN_ENTRANCE) { | |
drawMainEntrance(screenX, screenY); | |
} | |
ctx.globalAlpha = 1; | |
} | |
} | |
} | |
// Draw entities | |
room.entities.forEach((entity) => { | |
const dist = Math.sqrt( | |
(entity.x - game.player.x) ** 2 + (entity.y - game.player.y) ** 2 | |
); | |
if (dist < viewRadius) { | |
const screenX = | |
(entity.x - game.player.x) * TILE_SIZE + centerX + TILE_SIZE / 2; | |
const screenY = | |
(entity.y - game.player.y) * TILE_SIZE + centerY + TILE_SIZE / 2; | |
const brightness = | |
biome === "OUTSIDE" ? 1 : 1 - (dist / viewRadius) * 0.7; | |
ctx.globalAlpha = brightness; | |
if (entity.type === "ai" && !game.collectedAI.has(entity.id)) { | |
// AI architecture sprite with rarity effects | |
const bounce = Math.sin(Date.now() / 300) * 3; | |
const rarity = entity.data.rarity; | |
ctx.save(); | |
ctx.translate(screenX, screenY + bounce); | |
// Rarity glow effects | |
if (rarity === "legendary") { | |
ctx.shadowColor = "#ffd700"; | |
ctx.shadowBlur = 15; | |
} else if (rarity === "epic") { | |
ctx.shadowColor = "#ff6b6b"; | |
ctx.shadowBlur = 10; | |
} else if (rarity === "rare") { | |
ctx.shadowColor = "#74b9ff"; | |
ctx.shadowBlur = 8; | |
} else if (rarity === "uncommon") { | |
ctx.shadowColor = "#00b894"; | |
ctx.shadowBlur = 5; | |
} | |
// Main AI sprite | |
ctx.fillStyle = entity.data.color; | |
ctx.fillRect(-12, -12, 24, 24); | |
// Rarity border | |
ctx.strokeStyle = | |
rarity === "legendary" | |
? "#ffd700" | |
: rarity === "epic" | |
? "#ff6b6b" | |
: rarity === "rare" | |
? "#74b9ff" | |
: rarity === "uncommon" | |
? "#00b894" | |
: "#95a5a6"; | |
ctx.lineWidth = rarity === "legendary" ? 3 : 2; | |
ctx.strokeRect(-12, -12, 24, 24); | |
// Circuit pattern | |
ctx.fillStyle = "#fff"; | |
ctx.fillRect(-8, -8, 4, 4); | |
ctx.fillRect(4, -8, 4, 4); | |
ctx.fillRect(-8, 4, 4, 4); | |
ctx.fillRect(4, 4, 4, 4); | |
ctx.fillRect(-2, -2, 4, 4); | |
// Reset shadow | |
ctx.shadowBlur = 0; | |
ctx.restore(); | |
} else if (entity.type === "npc") { | |
// Draw NPC with unique appearance | |
drawNPC(screenX, screenY, "down", 0, entity.data); | |
ctx.font = "10px monospace"; | |
ctx.fillStyle = "#fff"; | |
ctx.textAlign = "center"; | |
ctx.fillText( | |
entity.data.name.split(" ")[1], | |
screenX, | |
screenY - 20 | |
); | |
} else if (entity.type === "furniture") { | |
// Draw interactive furniture as tile overlays | |
drawFurnitureTile( | |
screenX - TILE_SIZE / 2, | |
screenY - TILE_SIZE / 2, | |
entity.data.name | |
); | |
} else if (entity.type === "environment") { | |
// Environment objects are now part of the tile system - skip individual rendering | |
// They're drawn as part of the floor/wall tiles | |
} | |
ctx.globalAlpha = 1; | |
} | |
}); | |
// Draw particles | |
game.particles.forEach((p) => { | |
const screenX = p.x - game.player.x * TILE_SIZE + centerX; | |
const screenY = p.y - game.player.y * TILE_SIZE + centerY; | |
ctx.fillStyle = p.color; | |
ctx.globalAlpha = p.life / 20; | |
ctx.fillRect(screenX - 2, screenY - 2, 4, 4); | |
}); | |
ctx.globalAlpha = 1; | |
// Draw player (Jordan) | |
drawJordan(centerX, centerY, game.player.facing, game.player.animFrame); | |
// Biome-specific vignette effect | |
if (biome !== "OUTSIDE") { | |
const gradient = ctx.createRadialGradient( | |
centerX, | |
centerY, | |
0, | |
centerX, | |
centerY, | |
viewRadius * TILE_SIZE | |
); | |
gradient.addColorStop(0.7, "rgba(0,0,0,0)"); | |
gradient.addColorStop( | |
1, | |
biome === "DATACENTER" ? "rgba(0,0,0,0.95)" : "rgba(0,0,0,0.9)" | |
); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
} | |
} | |
// Game loop | |
function gameLoop(currentTime) { | |
// Calculate delta time | |
if (game.lastTime === 0) { | |
game.lastTime = currentTime; | |
} | |
game.deltaTime = currentTime - game.lastTime; | |
game.lastTime = currentTime; | |
// Cap delta time to prevent large jumps (e.g., when tab is inactive) | |
game.deltaTime = Math.min(game.deltaTime, 33.33); // Max 30fps minimum | |
update(game.deltaTime); | |
render(); | |
requestAnimationFrame(gameLoop); | |
} | |
// Initialize - start outside at the entrance | |
game.player.x = 7; | |
game.player.y = 10; | |
game.player.roomX = 0; | |
game.player.roomY = 1; // Start outside | |
updateUI(); | |
// Start the storyline | |
showDialogue( | |
"Welcome to Takara.ai Research Campus! Speak with Dr. Takara Mono to begin your mission." | |
); | |
gameLoop(); | |
</script> | |
</body> | |
</html> | |