| |
| |
|
|
| let game; |
|
|
| |
| document.addEventListener('DOMContentLoaded', function() { |
| console.log('DOM loaded, initializing game...'); |
| game = new GameEngine(); |
| }); |
|
|
| class GameEngine { |
| constructor() { |
| this.gameState = 'landing'; |
| this.player = null; |
| this.resonanceSystem = new ResonanceSystem(); |
| this.narrativeEngine = new NarrativeEngine(); |
| this.eventSystem = new EventSystem(); |
| |
| console.log('GameEngine constructor called'); |
| this.init(); |
| } |
|
|
| init() { |
| console.log('Initializing game...'); |
| this.setupEventListeners(); |
| this.loadGameData(); |
| this.startGameLoop(); |
| console.log('Game initialized'); |
| } |
|
|
| setupEventListeners() { |
| console.log('Setting up event listeners...'); |
| |
| |
| setTimeout(() => { |
| |
| const startButton = document.getElementById('start-game'); |
| console.log('Start button:', startButton); |
| if (startButton) { |
| startButton.onclick = () => { |
| console.log('Start button clicked'); |
| this.transitionToScreen('character-creation'); |
| }; |
| } |
|
|
| |
| const createButton = document.getElementById('create-character'); |
| console.log('Create button:', createButton); |
| if (createButton) { |
| createButton.onclick = () => { |
| console.log('Create character button clicked'); |
| this.createCharacter(); |
| }; |
| } |
|
|
| |
| document.onclick = (e) => { |
| if (e.target.classList.contains('decision-btn')) { |
| console.log('Decision button clicked:', e.target.dataset.decision); |
| const decision = e.target.dataset.decision; |
| this.processDecision(decision); |
| } |
|
|
| |
| if (e.target.classList.contains('event-join')) { |
| console.log('Event join button clicked'); |
| const eventElement = e.target.closest('.event-item'); |
| const eventName = eventElement.querySelector('h5').textContent; |
| this.joinEvent(eventName); |
| } |
|
|
| |
| if (e.target.classList.contains('modal-close')) { |
| console.log('Modal close clicked'); |
| this.closeModal(); |
| } |
| |
| if (e.target.id === 'continue-game') { |
| console.log('Continue game clicked'); |
| this.closeModal(); |
| this.generateNextScenario(); |
| } |
|
|
| |
| if (e.target.closest('.npc-item')) { |
| console.log('NPC clicked'); |
| const npcElement = e.target.closest('.npc-item'); |
| const npcId = npcElement.dataset.npc; |
| this.showNPCInfo(npcId); |
| } |
| }; |
| }, 100); |
| } |
|
|
| transitionToScreen(screenId) { |
| console.log('Transitioning to screen:', screenId); |
| |
| |
| const screens = document.querySelectorAll('.screen'); |
| screens.forEach(screen => { |
| screen.classList.remove('active'); |
| }); |
| |
| |
| const targetScreen = document.getElementById(screenId); |
| if (targetScreen) { |
| targetScreen.classList.add('active'); |
| this.gameState = screenId; |
| console.log('Successfully transitioned to:', screenId); |
| } else { |
| console.error('Screen not found:', screenId); |
| } |
| } |
|
|
| createCharacter() { |
| console.log('Creating character...'); |
| |
| const nameInput = document.getElementById('player-name'); |
| const techSelect = document.getElementById('tech-background'); |
| const culturalRadio = document.querySelector('input[name="cultural-preference"]:checked'); |
| const socialSelect = document.getElementById('social-style'); |
| |
| const name = nameInput ? nameInput.value || 'Jugador' : 'Jugador'; |
| const techBackground = techSelect ? techSelect.value : 'frontend'; |
| const culturalPreference = culturalRadio ? culturalRadio.value : 'mixed'; |
| const socialStyle = socialSelect ? socialSelect.value : 'adaptable'; |
|
|
| console.log('Character data:', { name, techBackground, culturalPreference, socialStyle }); |
|
|
| this.player = new Player({ |
| name, |
| techBackground, |
| culturalPreference, |
| socialStyle |
| }); |
|
|
| this.updatePlayerDisplay(); |
| this.transitionToScreen('game-interface'); |
| |
| |
| setTimeout(() => { |
| this.generateNextScenario(); |
| }, 500); |
| } |
|
|
| updatePlayerDisplay() { |
| if (!this.player) return; |
|
|
| console.log('Updating player display'); |
|
|
| const nameElement = document.getElementById('player-display-name'); |
| const roleElement = document.getElementById('player-role'); |
| |
| if (nameElement) nameElement.textContent = this.player.name; |
| if (roleElement) roleElement.textContent = this.getRoleDescription(this.player.techBackground); |
| |
| this.updateStats(); |
| this.updateNPCRelationships(); |
| this.updateResonanceDisplay(); |
| } |
|
|
| updateStats() { |
| if (!this.player) return; |
|
|
| const stats = ['carrera', 'cultura', 'social', 'bienestar']; |
| stats.forEach(stat => { |
| const value = this.player.stats[stat]; |
| const fillElement = document.getElementById(`${stat}-fill`); |
| const valueElement = document.getElementById(`${stat}-value`); |
| |
| if (fillElement && valueElement) { |
| fillElement.style.width = `${value}%`; |
| valueElement.textContent = value; |
| } |
| }); |
| } |
|
|
| updateNPCRelationships() { |
| if (!this.player) return; |
|
|
| Object.entries(this.player.relationships).forEach(([npcId, relationship]) => { |
| const npcElement = document.querySelector(`[data-npc="${npcId}"]`); |
| if (npcElement) { |
| const fillElement = npcElement.querySelector('.relationship-fill'); |
| if (fillElement) { |
| fillElement.style.width = `${relationship}%`; |
| } |
| } |
| }); |
| } |
|
|
| updateResonanceDisplay() { |
| const resonanceWaves = document.querySelectorAll('.resonance-wave'); |
| const activeWaves = Math.min(this.resonanceSystem.getResonanceLevel(), resonanceWaves.length); |
| |
| resonanceWaves.forEach((wave, index) => { |
| if (index < activeWaves) { |
| wave.classList.add('active'); |
| } else { |
| wave.classList.remove('active'); |
| } |
| }); |
|
|
| const resonanceText = document.querySelector('.resonance-text'); |
| if (resonanceText) { |
| resonanceText.textContent = this.resonanceSystem.getResonanceDescription(); |
| } |
| } |
|
|
| processDecision(decisionKey) { |
| console.log('Processing decision:', decisionKey); |
| |
| const currentScenario = this.narrativeEngine.getCurrentScenario(); |
| if (!currentScenario) { |
| console.log('No current scenario'); |
| return; |
| } |
| |
| const decision = currentScenario.decisions.find(d => d.key === decisionKey); |
| |
| if (!decision) { |
| console.log('Decision not found:', decisionKey); |
| return; |
| } |
|
|
| console.log('Found decision:', decision); |
|
|
| |
| if (decision.effects) { |
| Object.entries(decision.effects).forEach(([stat, change]) => { |
| this.player.changeStat(stat, change); |
| }); |
| } |
|
|
| |
| if (decision.relationshipChanges) { |
| Object.entries(decision.relationshipChanges).forEach(([npcId, change]) => { |
| this.player.changeRelationship(npcId, change); |
| }); |
| } |
|
|
| |
| this.resonanceSystem.addDecision(decision); |
|
|
| |
| this.showResonanceEffect(decision); |
|
|
| |
| this.updatePlayerDisplay(); |
|
|
| |
| setTimeout(() => { |
| this.generateNextScenario(); |
| }, 3000); |
| } |
|
|
| showResonanceEffect(decision) { |
| console.log('Showing resonance effect'); |
| const modal = document.getElementById('resonance-modal'); |
| if (modal) { |
| modal.classList.add('active'); |
| this.drawResonanceVisualization(decision); |
| |
| |
| setTimeout(() => { |
| this.closeModal(); |
| }, 2000); |
| } |
| } |
|
|
| drawResonanceVisualization(decision) { |
| const canvas = document.getElementById('resonance-canvas'); |
| if (!canvas) return; |
| |
| const ctx = canvas.getContext('2d'); |
| |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| const centerX = canvas.width / 2; |
| const centerY = canvas.height / 2; |
| |
| |
| let radius = 10; |
| const maxRadius = 150; |
| |
| const animate = () => { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| for (let i = 0; i < 3; i++) { |
| const waveRadius = radius - (i * 30); |
| if (waveRadius > 0) { |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY, waveRadius, 0, 2 * Math.PI); |
| ctx.strokeStyle = `rgba(33, 128, 141, ${0.8 - (waveRadius / maxRadius)})`; |
| ctx.lineWidth = 2; |
| ctx.stroke(); |
| } |
| } |
| |
| radius += 2; |
| if (radius < maxRadius) { |
| requestAnimationFrame(animate); |
| } |
| }; |
| |
| animate(); |
| } |
|
|
| generateNextScenario() { |
| console.log('Generating next scenario'); |
| const scenario = this.narrativeEngine.generateScenario(this.player, this.resonanceSystem); |
| this.displayScenario(scenario); |
| this.updateAvailableEvents(); |
| } |
|
|
| displayScenario(scenario) { |
| console.log('Displaying scenario:', scenario); |
| |
| const locationElement = document.getElementById('current-location'); |
| const descriptionElement = document.getElementById('location-description'); |
| const storyElement = document.getElementById('story-content'); |
| const decisionsContainer = document.getElementById('decisions-container'); |
| |
| if (locationElement) locationElement.textContent = scenario.location.name; |
| if (descriptionElement) descriptionElement.textContent = scenario.location.description; |
| if (storyElement) storyElement.innerHTML = `<p>${scenario.narrative}</p>`; |
|
|
| if (decisionsContainer) { |
| decisionsContainer.innerHTML = ` |
| <div class="decision-prompt"> |
| <p>${scenario.prompt}</p> |
| </div> |
| <div class="decisions-list"> |
| ${scenario.decisions.map(decision => ` |
| <button class="decision-btn" data-decision="${decision.key}"> |
| <span class="decision-text">${decision.text}</span> |
| <span class="decision-impact">${this.formatDecisionImpact(decision.effects)}</span> |
| </button> |
| `).join('')} |
| </div> |
| `; |
| } |
| } |
|
|
| formatDecisionImpact(effects) { |
| if (!effects) return ''; |
| |
| return Object.entries(effects) |
| .map(([stat, change]) => { |
| const prefix = change > 0 ? '+' : ''; |
| const statName = stat.charAt(0).toUpperCase() + stat.slice(1); |
| return `${prefix}${statName}`; |
| }) |
| .join(' '); |
| } |
|
|
| updateAvailableEvents() { |
| const eventsList = document.getElementById('events-list'); |
| if (!eventsList || !this.player) return; |
| |
| const availableEvents = this.eventSystem.getAvailableEvents(this.player); |
| |
| eventsList.innerHTML = availableEvents.map(event => ` |
| <div class="event-item ${event.available ? 'available' : 'locked'}"> |
| <h5>${event.name}</h5> |
| <p>${event.description}</p> |
| ${event.available |
| ? `<span class="event-date">${event.date}</span> |
| <button class="btn btn--sm btn--secondary event-join">Asistir</button>` |
| : `<span class="event-requirement">${event.requirement}</span>` |
| } |
| </div> |
| `).join(''); |
| } |
|
|
| joinEvent(eventName) { |
| console.log('Joining event:', eventName); |
| const result = this.eventSystem.joinEvent(eventName, this.player); |
| if (result) { |
| this.showEventResult(result); |
| this.resonanceSystem.addEvent(result); |
| this.updatePlayerDisplay(); |
| } |
| } |
|
|
| showEventResult(result) { |
| const modal = document.getElementById('event-modal'); |
| const title = document.getElementById('event-title'); |
| const content = document.getElementById('event-result-content'); |
| |
| if (title) title.textContent = result.eventName; |
| if (content) { |
| content.innerHTML = ` |
| <div class="event-result"> |
| <p>${result.narrative}</p> |
| <div class="event-outcomes"> |
| <h4>Resultados:</h4> |
| <ul> |
| ${result.outcomes.map(outcome => `<li>${outcome}</li>`).join('')} |
| </ul> |
| </div> |
| ${result.newConnections ? ` |
| <div class="new-connections"> |
| <h4>Nuevas Conexiones:</h4> |
| <p>${result.newConnections}</p> |
| </div> |
| ` : ''} |
| </div> |
| `; |
| } |
| |
| if (modal) modal.classList.add('active'); |
| } |
|
|
| closeModal() { |
| document.querySelectorAll('.modal').forEach(modal => { |
| modal.classList.remove('active'); |
| }); |
| } |
|
|
| showNPCInfo(npcId) { |
| const npcData = { |
| sofia: "Sof铆a es una frontend developer especializada en React. Viene de trabajar en San Francisco y lidera el tech team.", |
| mateo: "Mateo es product manager, ex-consultor de BCG obsesionado con m茅tricas y user acquisition.", |
| luna: "Luna es artista digital freelance que organiza eventos culturales alternativos en la escena underground." |
| }; |
| |
| const info = npcData[npcId] || "Informaci贸n no disponible"; |
| alert(info); |
| } |
|
|
| getRoleDescription(techBackground) { |
| const roles = { |
| frontend: 'Frontend Developer', |
| backend: 'Backend Developer', |
| fullstack: 'Full Stack Developer', |
| product: 'Product Manager', |
| design: 'UX/UI Designer', |
| data: 'Data Scientist' |
| }; |
| return roles[techBackground] || 'Desarrollador'; |
| } |
|
|
| loadGameData() { |
| this.gameData = { |
| locations: [ |
| { |
| name: "Vicente L贸pez", |
| description: "Hub tech moderno con startups y coworking spaces", |
| events: ["North Valley Meetup", "Startup Showcase", "After Office Tech"] |
| }, |
| { |
| name: "San Isidro", |
| description: "Zona cultural con venues indies y espacios de arte", |
| events: ["Showcase Indie", "Exhibici贸n Arte Digital", "Centro Cultural"] |
| }, |
| { |
| name: "Olivos", |
| description: "Barrio residencial con cafeter铆as y espacios de networking", |
| events: ["Coffee Networking", "Encuentro Freelancers", "Workshop Design"] |
| } |
| ], |
| culturalPhrases: [ |
| "驴Todo bien?", "Dale, joya", "Un golazo ese proyecto", "Re copado el evento", |
| "驴Qu茅 tal?", "Est谩 b谩rbaro", "Me parece genial", "S煤per interesante" |
| ] |
| }; |
| } |
|
|
| startGameLoop() { |
| setInterval(() => { |
| if (this.gameState === 'game-interface' && this.player) { |
| this.resonanceSystem.update(); |
| this.updateResonanceDisplay(); |
| } |
| }, 1000); |
| } |
| } |
|
|
| class Player { |
| constructor({ name, techBackground, culturalPreference, socialStyle }) { |
| this.name = name; |
| this.techBackground = techBackground; |
| this.culturalPreference = culturalPreference; |
| this.socialStyle = socialStyle; |
| |
| this.stats = { |
| carrera: this.getInitialStat('carrera'), |
| cultura: this.getInitialStat('cultura'), |
| social: this.getInitialStat('social'), |
| bienestar: this.getInitialStat('bienestar') |
| }; |
| |
| this.relationships = { |
| sofia: 50, |
| mateo: 40, |
| luna: 30 |
| }; |
| |
| this.memory = []; |
| this.achievements = []; |
| |
| console.log('Player created:', this); |
| } |
|
|
| getInitialStat(statName) { |
| const baseStats = { carrera: 50, cultura: 30, social: 40, bienestar: 60 }; |
| let value = baseStats[statName]; |
| |
| if (statName === 'carrera') { |
| value += ['frontend', 'backend', 'fullstack'].includes(this.techBackground) ? 10 : 0; |
| value += this.techBackground === 'product' ? 15 : 0; |
| } |
| |
| if (statName === 'cultura') { |
| value += this.culturalPreference === 'music' ? 20 : 0; |
| value += this.culturalPreference === 'art' ? 15 : 0; |
| } |
| |
| if (statName === 'social') { |
| value += this.socialStyle === 'networking' ? 20 : 0; |
| value += this.socialStyle === 'introvert' ? -10 : 0; |
| } |
| |
| return Math.max(0, Math.min(100, value)); |
| } |
|
|
| changeStat(statName, change) { |
| if (this.stats[statName] !== undefined) { |
| this.stats[statName] = Math.max(0, Math.min(100, this.stats[statName] + change)); |
| |
| const fillElement = document.getElementById(`${statName}-fill`); |
| if (fillElement) { |
| fillElement.classList.add(change > 0 ? 'increase' : 'decrease'); |
| setTimeout(() => { |
| fillElement.classList.remove('increase', 'decrease'); |
| }, 500); |
| } |
| } |
| } |
|
|
| changeRelationship(npcId, change) { |
| if (this.relationships[npcId] !== undefined) { |
| this.relationships[npcId] = Math.max(0, Math.min(100, this.relationships[npcId] + change)); |
| } |
| } |
|
|
| addMemory(event) { |
| this.memory.push({ |
| event, |
| timestamp: Date.now(), |
| impact: event.impact || 'minor' |
| }); |
| } |
| } |
|
|
| class ResonanceSystem { |
| constructor() { |
| this.decisions = []; |
| this.resonanceLevel = 0; |
| this.activeWaves = []; |
| } |
|
|
| addDecision(decision) { |
| const resonanceWave = { |
| decision, |
| timestamp: Date.now(), |
| strength: this.calculateDecisionStrength(decision), |
| decayRate: 0.1 |
| }; |
| |
| this.activeWaves.push(resonanceWave); |
| this.updateResonanceLevel(); |
| } |
|
|
| addEvent(event) { |
| const eventWave = { |
| event, |
| timestamp: Date.now(), |
| strength: event.resonanceImpact || 0.5, |
| decayRate: 0.05 |
| }; |
| |
| this.activeWaves.push(eventWave); |
| this.updateResonanceLevel(); |
| } |
|
|
| calculateDecisionStrength(decision) { |
| const weights = { minor: 0.3, moderate: 0.6, major: 1.0, life_changing: 1.5 }; |
| return weights[decision.weight] || 0.6; |
| } |
|
|
| update() { |
| const now = Date.now(); |
| |
| this.activeWaves = this.activeWaves.filter(wave => { |
| const age = (now - wave.timestamp) / 1000; |
| wave.strength *= Math.exp(-wave.decayRate * age); |
| return wave.strength > 0.01; |
| }); |
| |
| this.updateResonanceLevel(); |
| } |
|
|
| updateResonanceLevel() { |
| this.resonanceLevel = this.activeWaves.reduce((total, wave) => total + wave.strength, 0); |
| } |
|
|
| getResonanceLevel() { |
| return Math.min(3, Math.floor(this.resonanceLevel)); |
| } |
|
|
| getResonanceDescription() { |
| const level = this.getResonanceLevel(); |
| const descriptions = [ |
| "Calma total, sin ondas activas", |
| "Ligeras ondas de resonancia", |
| "Resonancia moderada en progreso", |
| "Intensa actividad de resonancia" |
| ]; |
| return descriptions[level] || descriptions[0]; |
| } |
|
|
| getResonanceEffects(player) { |
| const effects = []; |
| |
| if (this.resonanceLevel > 1.5) { |
| effects.push("unexpected_opportunity"); |
| } |
| |
| if (this.resonanceLevel > 2.0) { |
| effects.push("personality_echo"); |
| } |
| |
| return effects; |
| } |
| } |
|
|
| class NarrativeEngine { |
| constructor() { |
| this.currentScenario = null; |
| this.scenarioHistory = []; |
| this.scenarioTemplates = this.initializeScenarios(); |
| } |
|
|
| initializeScenarios() { |
| return [ |
| { |
| id: 'startup_first_day', |
| location: { name: 'Vicente L贸pez', description: 'Hub tech moderno con startups y coworking spaces' }, |
| narrative: 'Lleg谩s a tu primer d铆a en la nueva startup en Vicente L贸pez. El coworking space est谩 lleno de energ铆a, pantallas con c贸digo y el aroma de caf茅 de especialidad.', |
| prompt: 'Sof铆a, la frontend lead, se acerca durante el coffee break. Te comenta sobre un proyecto React que est谩 armando y menciona que buscan alguien para el equipo.', |
| decisions: [ |
| { |
| key: 'collaborate', |
| text: '"Me copa la propuesta, 驴cu谩ndo arrancamos?"', |
| effects: { carrera: 10, social: 5 }, |
| relationshipChanges: { sofia: 15 }, |
| weight: 'moderate' |
| }, |
| { |
| key: 'cautious', |
| text: '"Suena interesante, 驴me cont谩s m谩s detalles?"', |
| effects: { bienestar: 5 }, |
| relationshipChanges: { sofia: 5 }, |
| weight: 'minor' |
| }, |
| { |
| key: 'independent', |
| text: '"Estoy enfocado en mi proyecto actual, pero gracias"', |
| effects: { carrera: 5, social: -5 }, |
| relationshipChanges: { sofia: -10 }, |
| weight: 'moderate' |
| } |
| ] |
| }, |
| { |
| id: 'cultural_invitation', |
| location: { name: 'San Isidro', description: 'Zona cultural con venues indies y espacios de arte' }, |
| narrative: 'Luna te escribe por Slack sobre un showcase de El Mat贸 a un Polic铆a Motorizado en un venue 铆ntimo de San Isidro.', |
| prompt: 'Es viernes por la tarde y ten茅s que elegir entre quedarte terminando un sprint o ir al show.', |
| decisions: [ |
| { |
| key: 'show', |
| text: '"Dale, me re copa. 驴Nos vemos ah铆?"', |
| effects: { cultura: 15, bienestar: 10, carrera: -5 }, |
| relationshipChanges: { luna: 20 }, |
| weight: 'moderate' |
| }, |
| { |
| key: 'work_first', |
| text: '"Me encantar铆a, pero tengo que cerrar este sprint"', |
| effects: { carrera: 10, bienestar: -5 }, |
| relationshipChanges: { luna: -5 }, |
| weight: 'minor' |
| }, |
| { |
| key: 'compromise', |
| text: '"Si termino temprano, me sumo al after"', |
| effects: { carrera: 5, cultura: 5 }, |
| relationshipChanges: { luna: 5 }, |
| weight: 'minor' |
| } |
| ] |
| } |
| ]; |
| } |
|
|
| generateScenario(player, resonanceSystem) { |
| const availableScenarios = this.scenarioTemplates.filter(scenario => |
| !this.scenarioHistory.includes(scenario.id) |
| ); |
| |
| let selectedScenario; |
| if (availableScenarios.length === 0) { |
| selectedScenario = this.generateProceduralScenario(player, resonanceSystem); |
| } else { |
| selectedScenario = availableScenarios[0]; |
| } |
| |
| this.currentScenario = selectedScenario; |
| this.scenarioHistory.push(selectedScenario.id); |
| |
| return selectedScenario; |
| } |
|
|
| generateProceduralScenario(player, resonanceSystem) { |
| const locations = [ |
| { name: 'Vicente L贸pez', description: 'Hub tech moderno con startups y coworking spaces' }, |
| { name: 'San Isidro', description: 'Zona cultural con venues indies y espacios de arte' }, |
| { name: 'Olivos', description: 'Barrio residencial con cafeter铆as y espacios de networking' } |
| ]; |
| |
| return { |
| id: `procedural_${Date.now()}`, |
| location: locations[Math.floor(Math.random() * locations.length)], |
| narrative: `Una nueva oportunidad aparece en tu camino. Las ondas de resonancia de tus decisiones pasadas est谩n convergiendo...`, |
| prompt: "驴C贸mo vas a responder a esta nueva situaci贸n?", |
| decisions: [ |
| { |
| key: 'bold', |
| text: '"Voy con todo, sin dudas"', |
| effects: { carrera: 10, social: 5, bienestar: -5 }, |
| weight: 'moderate' |
| }, |
| { |
| key: 'balanced', |
| text: '"Analizo bien antes de decidir"', |
| effects: { carrera: 5, bienestar: 5 }, |
| weight: 'minor' |
| }, |
| { |
| key: 'creative', |
| text: '"Busco una soluci贸n creativa"', |
| effects: { cultura: 10, social: 5 }, |
| weight: 'moderate' |
| } |
| ] |
| }; |
| } |
|
|
| getCurrentScenario() { |
| return this.currentScenario; |
| } |
| } |
|
|
| class EventSystem { |
| constructor() { |
| this.events = [ |
| { |
| name: "North Valley Meetup", |
| description: "Networking tech en Vicente L贸pez", |
| date: "Viernes 18:30", |
| requirements: { carrera: 20, social: 15 }, |
| outcomes: ["networking_boost", "tech_knowledge", "startup_contacts"], |
| resonanceImpact: 0.7 |
| }, |
| { |
| name: "Innovation Tech Week", |
| description: "Semana tech m谩s importante de BA", |
| date: "Pr贸xima semana", |
| requirements: { carrera: 40, social: 30 }, |
| outcomes: ["major_networking", "tech_skills_gain", "startup_opportunity"], |
| resonanceImpact: 1.2 |
| }, |
| { |
| name: "Showcase Indie", |
| description: "Show en venue de San Isidro", |
| date: "S谩bado 21:00", |
| requirements: { cultura: 25, social: 20 }, |
| outcomes: ["cultural_inspiration", "creative_boost", "underground_contacts"], |
| resonanceImpact: 0.8 |
| } |
| ]; |
| } |
|
|
| getAvailableEvents(player) { |
| return this.events.map(event => { |
| const available = this.checkRequirements(event.requirements, player); |
| return { |
| ...event, |
| available, |
| requirement: available ? null : this.formatRequirements(event.requirements, player) |
| }; |
| }); |
| } |
|
|
| checkRequirements(requirements, player) { |
| return Object.entries(requirements).every(([stat, required]) => |
| player.stats[stat] >= required |
| ); |
| } |
|
|
| formatRequirements(requirements, player) { |
| const unmet = Object.entries(requirements).find(([stat, required]) => |
| player.stats[stat] < required |
| ); |
| |
| if (unmet) { |
| const [stat, required] = unmet; |
| const statName = stat.charAt(0).toUpperCase() + stat.slice(1); |
| return `Requiere ${statName}: ${required}`; |
| } |
| |
| return ""; |
| } |
|
|
| joinEvent(eventName, player) { |
| const event = this.events.find(e => e.name === eventName); |
| if (!event || !this.checkRequirements(event.requirements, player)) { |
| return null; |
| } |
|
|
| return { |
| eventName: event.name, |
| narrative: `Particip谩s en ${event.name} y la experiencia es incre铆ble. Conect谩s con gente nueva y aprend茅s un mont贸n.`, |
| outcomes: ["Expandiste tu red de contactos", "Aprendiste sobre nuevas tecnolog铆as"], |
| statChanges: { carrera: 10, social: 8 }, |
| relationshipChanges: {}, |
| newConnections: "Conociste a un founder de una startup prometedora", |
| resonanceImpact: event.resonanceImpact |
| }; |
| } |
| } |