Meta Description" name="description" />
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FIFA 2D - Creado por Lionel Navarro</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
color: #fff;
overflow: hidden;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#game-container {
width: 1200px;
height: 700px;
position: relative;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.8);
border-radius: 10px;
overflow: hidden;
background: #000;
}
/* Pantallas */
.screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(to bottom, #0a0a0a, #1a1a1a);
transition: transform 0.5s ease;
z-index: 10;
}
.hidden {
transform: translateX(-100%);
z-index: 1;
}
/* Pantalla de título */
#title-screen {
background: linear-gradient(rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.9)),
url('https://images.unsplash.com/photo-1575361204480-aadea25e6e68?ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&q=80');
background-size: cover;
background-position: center;
}
.game-title {
font-size: 5rem;
text-align: center;
margin-bottom: 10px;
background: linear-gradient(to right, #00a8ff, #0097e6);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 5px 20px rgba(0, 168, 255, 0.5);
font-weight: 900;
letter-spacing: 3px;
font-family: 'Impact', 'Arial Black', sans-serif;
}
.creator {
font-size: 1.2rem;
color: #999;
margin-bottom: 50px;
font-style: italic;
}
.menu-button {
width: 350px;
padding: 18px 30px;
margin: 15px;
font-size: 1.5rem;
font-weight: bold;
background: linear-gradient(to right, #00a8ff, #005a8c);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
text-align: center;
position: relative;
overflow: hidden;
}
.menu-button:hover:not(:disabled) {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 168, 255, 0.4);
background: linear-gradient(to right, #0097e6, #004d73);
}
.menu-button:disabled {
opacity: 0.5;
cursor: not-allowed;
background: #333;
}
.menu-button:active:not(:disabled) {
transform: translateY(0);
}
.coming-soon {
position: relative;
}
.coming-soon::after {
content: "PRÓXIMAMENTE";
position: absolute;
top: -10px;
right: -10px;
background: #e84118;
color: white;
font-size: 0.8rem;
padding: 3px 8px;
border-radius: 10px;
font-weight: bold;
}
/* Pantalla de selección de modo */
.screen-title {
font-size: 3.2rem;
margin-bottom: 40px;
text-align: center;
color: #00a8ff;
text-shadow: 0 3px 10px rgba(0, 168, 255, 0.5);
}
.modes-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 25px;
width: 90%;
max-width: 900px;
margin-bottom: 40px;
}
.mode-button {
padding: 25px 20px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
backdrop-filter: blur(5px);
}
.mode-button:hover {
background: rgba(0, 168, 255, 0.1);
border-color: #00a8ff;
transform: translateY(-5px);
}
.mode-button.selected {
background: rgba(0, 168, 255, 0.2);
border-color: #00a8ff;
box-shadow: 0 5px 20px rgba(0, 168, 255, 0.3);
}
.mode-icon {
font-size: 3rem;
margin-bottom: 15px;
}
/* Pantalla de selección de competición */
.competitions-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
width: 90%;
max-width: 1000px;
height: 400px;
overflow-y: auto;
padding: 15px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
margin-bottom: 30px;
scrollbar-width: thin;
scrollbar-color: #00a8ff #222;
}
.competitions-container::-webkit-scrollbar {
width: 8px;
}
.competitions-container::-webkit-scrollbar-track {
background: #222;
border-radius: 10px;
}
.competitions-container::-webkit-scrollbar-thumb {
background: #00a8ff;
border-radius: 10px;
}
.competition-button {
padding: 20px 15px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.competition-button:hover {
background: rgba(0, 168, 255, 0.1);
border-color: #00a8ff;
transform: scale(1.05);
}
.competition-button.selected {
background: rgba(0, 168, 255, 0.2);
border-color: #00a8ff;
}
.competition-logo {
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
/* Pantalla de selección de equipo */
.teams-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 15px;
width: 95%;
max-width: 1100px;
height: 400px;
overflow-y: auto;
padding: 15px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
margin-bottom: 30px;
scrollbar-width: thin;
scrollbar-color: #00a8ff #222;
}
.team-button {
padding: 15px 10px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.team-button:hover {
background: rgba(0, 168, 255, 0.1);
border-color: #00a8ff;
transform: scale(1.05);
}
.team-button.selected {
background: rgba(0, 168, 255, 0.2);
border-color: #00a8ff;
}
.team-logo {
width: 50px;
height: 50px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
/* Pantalla de selección de dificultad */
.difficulty-container {
display: flex;
flex-direction: column;
gap: 20px;
width: 80%;
max-width: 700px;
margin-bottom: 40px;
}
.difficulty-button {
padding: 20px 30px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
font-size: 1.4rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
position: relative;
overflow: hidden;
}
.difficulty-button:hover {
background: rgba(0, 168, 255, 0.1);
transform: translateY(-3px);
}
.difficulty-button.selected {
background: rgba(0, 168, 255, 0.2);
border-color: #00a8ff;
}
.difficulty-very-easy {
border-left: 8px solid #4CAF50;
}
.difficulty-easy {
border-left: 8px solid #8BC34A;
}
.difficulty-normal {
border-left: 8px solid #FFC107;
}
.difficulty-hard {
border-left: 8px solid #FF9800;
}
.difficulty-extreme {
border-left: 8px solid #F44336;
}
/* Pantalla de juego */
#game-screen {
padding: 0;
background: #2a623d;
z-index: 5;
}
#canvas-container {
width: 100%;
height: 100%;
position: relative;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
/* HUD del juego */
.game-hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.scoreboard {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
background: rgba(0, 0, 0, 0.85);
padding: 10px 40px;
border-radius: 15px;
font-size: 2.2rem;
font-weight: bold;
min-width: 300px;
justify-content: center;
border: 3px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.7);
}
.scoreboard.libertadores {
border-color: #FFD700;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(139, 0, 0, 0.7));
}
.scoreboard.sudamericana {
border-color: #00a8ff;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 50, 160, 0.7));
}
.scoreboard.champions {
border-color: #00a8ff;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 20, 100, 0.7));
}
.scoreboard.mundial {
border-color: #F44336;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(200, 0, 0, 0.7));
}
.scoreboard.copa-america {
border-color: #00a8ff;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 100, 200, 0.7));
}
.scoreboard.eurocopa {
border-color: #FFD700;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(255, 215, 0, 0.2));
}
.team-score {
padding: 0 20px;
font-size: 2.8rem;
text-shadow: 0 0 10px currentColor;
}
.player-score {
color: #00a8ff;
}
.cpu-score {
color: #e84118;
}
.score-separator {
color: #fff;
font-size: 2.5rem;
margin: 0 10px;
}
.team-name-hud {
position: absolute;
top: 90px;
font-size: 1.4rem;
font-weight: bold;
background: rgba(0, 0, 0, 0.7);
padding: 8px 20px;
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.team-name-hud.player {
left: 30px;
color: #00a8ff;
border-color: #00a8ff;
}
.team-name-hud.cpu {
right: 30px;
color: #e84118;
border-color: #e84118;
}
.game-time {
position: absolute;
top: 25px;
right: 30px;
background: rgba(0, 0, 0, 0.8);
padding: 10px 20px;
border-radius: 8px;
font-size: 1.5rem;
font-weight: bold;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.controls-info {
position: absolute;
bottom: 25px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
padding: 12px 25px;
border-radius: 10px;
font-size: 1.1rem;
text-align: center;
border: 2px solid rgba(255, 255, 255, 0.1);
}
.key {
display: inline-block;
background: rgba(0, 168, 255, 0.3);
padding: 5px 12px;
border-radius: 5px;
margin: 0 5px;
font-weight: bold;
border: 1px solid rgba(0, 168, 255, 0.5);
}
/* Panel de mensajes */
.message-panel {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 70%;
max-width: 700px;
background: rgba(0, 0, 0, 0.9);
padding: 15px;
border-radius: 10px;
font-size: 1.2rem;
text-align: center;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
border-left: 5px solid #00a8ff;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.7);
}
/* Animaciones */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@keyframes goalAnimation {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
@keyframes passAnimation {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes shotAnimation {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 0.5s ease;
}
.goal-animation {
animation: goalAnimation 0.8s ease;
}
.pass-animation {
animation: passAnimation 0.3s ease;
}
.shot-animation {
animation: shotAnimation 0.4s ease;
}
/* Botones de navegación */
.nav-buttons {
display: flex;
gap: 25px;
margin-top: 20px;
}
.nav-button {
padding: 15px 30px;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 10px;
color: white;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.nav-button:hover {
background: rgba(255, 255, 255, 0.2);
}
.nav-button.primary {
background: linear-gradient(to right, #00a8ff, #005a8c);
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
padding: 12px 25px;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 10px;
color: white;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
z-index: 20;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.2);
}
/* Modal de penales */
.penalty-modal {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
.penalty-title {
font-size: 3rem;
color: #FFD700;
margin-bottom: 30px;
text-shadow: 0 0 10px #FFD700;
}
.penalty-instructions {
font-size: 1.5rem;
margin-bottom: 40px;
text-align: center;
}
.penalty-meter {
width: 300px;
height: 30px;
background: #333;
border-radius: 15px;
margin: 20px 0;
overflow: hidden;
position: relative;
}
.penalty-fill {
height: 100%;
width: 0%;
background: linear-gradient(to right, #4CAF50, #FF9800, #F44336);
transition: width 0.1s linear;
}
.penalty-target {
position: absolute;
top: 0;
width: 40px;
height: 100%;
background: rgba(255, 255, 255, 0.3);
border: 2px solid #FFD700;
left: 50%;
transform: translateX(-50%);
}
/* Responsive */
@media (max-width: 1250px) {
#game-container {
width: 1000px;
height: 600px;
}
.teams-container {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 1050px) {
#game-container {
width: 900px;
height: 550px;
}
.game-title {
font-size: 4rem;
}
.teams-container {
grid-template-columns: repeat(3, 1fr);
}
.competitions-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 950px) {
#game-container {
width: 100%;
height: 100%;
border-radius: 0;
}
}
</style>
</head>
<body>
<div id="game-container">
<!-- Pantalla de título -->
<div id="title-screen" class="screen">
<h1 class="game-title">FIFA 2D</h1>
<p class="creator">Creado por Lionel Navarro</p>
<button id="friendly-match" class="menu-button">Partido Amistoso</button>
<button id="cups-mode" class="menu-button">Copas</button>
<button id="career-mode" class="menu-button coming-soon" disabled>Modo Carrera</button>
<button id="exit-game" class="menu-button">Salir del Juego</button>
</div>
<!-- Pantalla de selección de modo -->
<div id="mode-screen" class="screen hidden">
<h2 class="screen-title">Selecciona un Modo</h2>
<div class="modes-container" id="modes-container">
<!-- Los modos se generarán con JavaScript -->
</div>
<div class="nav-buttons">
<button id="back-from-mode" class="nav-button">Volver</button>
<button id="select-mode" class="nav-button primary" disabled>Seleccionar</button>
</div>
</div>
<!-- Pantalla de selección de competición -->
<div id="competition-screen" class="screen hidden">
<h2 class="screen-title" id="competition-title">Selecciona una Competición</h2>
<div class="competitions-container" id="competitions-container">
<!-- Las competiciones se generarán con JavaScript -->
</div>
<div class="nav-buttons">
<button id="back-from-competition" class="nav-button">Volver</button>
<button id="select-competition" class="nav-button primary" disabled>Seleccionar</button>
</div>
</div>
<!-- Pantalla de selección de equipo -->
<div id="team-screen" class="screen hidden">
<h2 class="screen-title" id="team-selection-title">Selecciona tu Equipo</h2>
<div class="teams-container" id="teams-container">
<!-- Los equipos se generarán con JavaScript -->
</div>
<div class="nav-buttons">
<button id="back-from-team" class="nav-button">Volver</button>
<button id="select-team" class="nav-button primary" disabled>Seleccionar Equipo</button>
</div>
</div>
<!-- Pantalla de selección de equipo rival -->
<div id="opponent-screen" class="screen hidden">
<h2 class="screen-title" id="opponent-selection-title">Selecciona el Equipo Rival</h2>
<div class="teams-container" id="opponent-teams-container">
<!-- Los equipos rivales se generarán con JavaScript -->
</div>
<div class="nav-buttons">
<button id="back-from-opponent" class="nav-button">Volver</button>
<button id="select-opponent" class="nav-button primary" disabled>Seleccionar Rival</button>
</div>
</div>
<!-- Pantalla de selección de dificultad -->
<div id="difficulty-screen" class="screen hidden">
<h2 class="screen-title">Selecciona la Dificultad</h2>
<div class="difficulty-container" id="difficulty-container">
<!-- Las dificultades se generarán con JavaScript -->
</div>
<div class="nav-buttons">
<button id="back-from-difficulty" class="nav-button">Volver</button>
<button id="start-game" class="nav-button primary" disabled>Comenzar Partido</button>
</div>
</div>
<!-- Pantalla de juego -->
<div id="game-screen" class="screen hidden">
<button id="back-from-game" class="back-button">Menú Principal</button>
<div id="canvas-container">
<canvas id="game-canvas"></canvas>
<div class="game-hud">
<div class="scoreboard" id="scoreboard">
<div class="team-score player-score" id="player-score">0</div>
<div class="score-separator">-</div>
<div class="team-score cpu-score" id="cpu-score">0</div>
</div>
<div class="team-name-hud player" id="player-team-name">Jugador</div>
<div class="team-name-hud cpu" id="cpu-team-name">CPU</div>
<div class="game-time" id="game-time">00:00</div>
<div class="controls-info">
<span>PASE: <span class="key">ESPACIO (rápido)</span></span> |
<span>CHUTAR: <span class="key">ESPACIO (mantener)</span></span> |
<span>MOVIMIENTO: <span class="key">W A S D</span></span> |
<span>CAMBIAR JUGADOR: <span class="key">TAB</span></span>
</div>
<div class="message-panel" id="message-panel">
<div id="message-text">¡Comienza el partido!</div>
</div>
</div>
<!-- Modal de penales -->
<div id="penalty-modal" class="penalty-modal hidden">
<h2 class="penalty-title">PENALES</h2>
<div class="penalty-instructions" id="penalty-instructions">
Presiona ESPACIO cuando la barra esté en la zona dorada
</div>
<div class="penalty-meter">
<div class="penalty-fill" id="penalty-fill"></div>
<div class="penalty-target"></div>
</div>
<div id="penalty-score" style="font-size: 2rem; margin: 20px 0;">
Jugador: 0 - 0 :CPU
</div>
<button id="start-penalty" class="menu-button" style="width: 200px;">Comenzar Penal</button>
</div>
</div>
</div>
</div>
<script>
// Datos del juego
const gameData = {
modes: {
'amistoso': {
name: 'Partido Amistoso',
icon: '⚽',
competitions: ['amistoso']
},
'copas': {
name: 'Copas',
icon: '🏆',
competitions: ['libertadores', 'sudamericana', 'champions', 'mundial', 'copa-america', 'eurocopa']
}
},
competitions: {
'amistoso': {
name: 'Partido Amistoso',
icon: '⚽',
color: '#00a8ff',
allTeams: {}, // Se llenará con todos los equipos
tournamentMode: false
},
'libertadores': {
name: 'Copa Libertadores',
icon: '🏆',
color: '#FFD700',
teams: [
'River Plate', 'Boca Juniors', 'Flamengo', 'Palmeiras',
'Atlético Mineiro', 'Corinthians', 'Nacional (URU)', 'Peñarol',
'Olimpia (PAR)', 'Cerro Porteño', 'Independiente del Valle', 'LDU Quito',
'Universidad de Chile', 'Colo-Colo', 'Atlético Nacional', 'Millonarios'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
},
'sudamericana': {
name: 'Copa Sudamericana',
icon: '🌎',
color: '#00a8ff',
teams: [
'Racing Club', 'San Lorenzo', 'Internacional', 'São Paulo',
'Athletico Paranaense', 'Emelec', 'Blooming', 'The Strongest',
'Deportivo Cali', 'Junior', 'Always Ready', 'Guaraní',
'Universitario', 'Sport Huancayo', 'Deportivo Lara', 'Metropolitano'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
},
'champions': {
name: 'Champions League',
icon: '⭐',
color: '#00a8ff',
teams: [
'Real Madrid', 'Barcelona', 'Manchester City', 'Liverpool',
'Bayern Munich', 'PSG', 'Chelsea', 'AC Milan',
'Inter Milan', 'Juventus', 'Borussia Dortmund', 'Atlético Madrid',
'Benfica', 'Porto', 'Ajax', 'RB Leipzig'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
},
'mundial': {
name: 'Mundial de Selecciones',
icon: '🌍',
color: '#F44336',
teams: [
'Argentina', 'Brasil', 'Francia', 'Alemania',
'España', 'Inglaterra', 'Italia', 'Portugal',
'Países Bajos', 'Bélgica', 'Croacia', 'Uruguay',
'México', 'Estados Unidos', 'Japón', 'Senegal'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
},
'copa-america': {
name: 'Copa América',
icon: '🇦🇷',
color: '#00a8ff',
teams: [
'Argentina', 'Brasil', 'Uruguay', 'Colombia',
'Chile', 'Perú', 'Paraguay', 'Ecuador',
'Venezuela', 'Bolivia', 'Costa Rica', 'México',
'Estados Unidos', 'Canadá', 'Jamaica', 'Panamá'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
},
'eurocopa': {
name: 'Eurocopa',
icon: '🇪🇺',
color: '#FFD700',
teams: [
'Francia', 'Alemania', 'España', 'Inglaterra',
'Italia', 'Portugal', 'Países Bajos', 'Bélgica',
'Croacia', 'Dinamarca', 'Suiza', 'Polonia',
'Suecia', 'Gales', 'Ucrania', 'Austria'
],
tournamentMode: true,
currentRound: 0,
tournamentBracket: []
}
},
// Todas las ligas con sus equipos
leagues: {
'española': {
name: 'LaLiga Española',
flag: '🇪🇸',
teams: [
'Real Madrid', 'Barcelona', 'Atlético Madrid', 'Sevilla',
'Real Sociedad', 'Betis', 'Villarreal', 'Athletic Club',
'Valencia', 'Osasuna', 'Celta Vigo', 'Mallorca',
'Rayo Vallecano', 'Girona', 'Getafe', 'Almería'
]
},
'premier': {
name: 'Premier League',
flag: '🏴',
teams: [
'Manchester City', 'Arsenal', 'Manchester United', 'Liverpool',
'Chelsea', 'Tottenham', 'Newcastle', 'Aston Villa',
'Brighton', 'West Ham', 'Brentford', 'Crystal Palace',
'Everton', 'Leicester', 'Leeds', 'Southampton'
]
},
'francesa': {
name: 'Ligue 1',
flag: '🇫🇷',
teams: [
'PSG', 'Marsella', 'Mónaco', 'Lyon',
'Lille', 'Rennes', 'Niza', 'Lens',
'Montpellier', 'Toulouse', 'Strasbourg', 'Nantes',
'Reims', 'Ajaccio', 'Auxerre', 'Troyes'
]
},
'alemana': {
name: 'Bundesliga',
flag: '🇩🇪',
teams: [
'Bayern Munich', 'Borussia Dortmund', 'RB Leipzig', 'Bayer Leverkusen',
'Union Berlin', 'Freiburg', 'Wolfsburg', 'Eintracht Frankfurt',
'Mainz', 'Borussia Mönchengladbach', 'Köln', 'Hoffenheim',
'Werder Bremen', 'Schalke 04', 'Augsburg', 'Stuttgart'
]
},
'italiana': {
name: 'Serie A',
flag: '🇮🇹',
teams: [
'Juventus', 'Inter Milan', 'AC Milan', 'Napoli',
'Roma', 'Lazio', 'Atalanta', 'Fiorentina',
'Bologna', 'Torino', 'Udinese', 'Sassuolo',
'Empoli', 'Salernitana', 'Lecce', 'Spezia'
]
},
'argentina': {
name: 'Torneo Betano 2025',
flag: '🇦🇷',
teams: [
'Argentinos Juniors',
'Atlético Tucumán',
'Banfield',
'Barracas Central',
'Belgrano',
'Boca Juniors',
'Central Córdoba',
'Defensa y Justicia',
'Deportivo Riestra',
'Estudiantes',
'Gimnasia LP',
'Godoy Cruz',
'Huracán',
'Independiente',
'Independiente Rivadavia',
'Instituto',
'Lanús',
'Newell\'s',
'Platense',
'Racing Club',
'River Plate',
'Rosario Central',
'San Lorenzo',
'San Martín de San Juan',
'Sarmiento',
'Talleres',
'Tigre',
'Unión',
'Vélez'
]
},
'nacional': {
name: 'Primera Nacional',
flag: '🇦🇷',
teams: [
'Racing Córdoba',
'Deportivo Madryn',
'Atlanta',
'Tristán Suárez',
'Gimnasia y Tiro',
'San Miguel',
'San Martín Tucumán',
'Deportivo Maipú',
'Patronato',
'Colegiales',
'All Boys',
'Ferro Carril Oeste',
'Los Andes',
'Güemes',
'Quilmes',
'Almagro'
]
},
'brasileña': {
name: 'Brasileirão',
flag: '🇧🇷',
teams: [
'Flamengo', 'Palmeiras', 'Corinthians', 'São Paulo',
'Santos', 'Grêmio', 'Internacional', 'Atlético Mineiro',
'Cruzeiro', 'Fluminense', 'Botafogo', 'Vasco da Gama',
'Bahia', 'Sport Recife', 'Fortaleza', 'Ceará'
]
},
'especial': {
name: 'Equipo Especial',
flag: '⭐',
teams: ['Equipo Especial']
}
},
difficulties: [
{ id: 'very-easy', name: 'Muy Fácil', color: '#4CAF50', speed: 0.7, aiDelay: 1000, aiAccuracy: 0.3, aiAggression: 0.3 },
{ id: 'easy', name: 'Fácil', color: '#8BC34A', speed: 0.9, aiDelay: 700, aiAccuracy: 0.5, aiAggression: 0.5 },
{ id: 'normal', name: 'Normal', color: '#FFC107', speed: 1.0, aiDelay: 500, aiAccuracy: 0.7, aiAggression: 0.7 },
{ id: 'hard', name: 'Difícil', color: '#FF9800', speed: 1.2, aiDelay: 300, aiAccuracy: 0.8, aiAggression: 0.8 },
{ id: 'extreme', name: 'Extremo', color: '#F44336', speed: 1.5, aiDelay: 150, aiAccuracy: 0.9, aiAggression: 0.9 }
],
selectedMode: null,
selectedCompetition: null,
selectedLeague: null,
selectedTeam: null,
selectedOpponent: null,
selectedDifficulty: null,
gameState: 'menu',
score: { player: 0, cpu: 0 },
matchTime: 0,
maxMatchTime: 300, // 5 minutos
messages: [
"¡Comienza el partido!",
"El balón está en juego",
"Qué jugada más interesante",
"Se avecina una oportunidad de gol",
"El portero está atento",
"¡Cuidado con el contraataque!",
"La defensa se mantiene firme",
"El mediocampo controla el ritmo",
"¡Qué pase tan preciso!",
"La afición está vibrante",
"Falta en una zona peligrosa",
"¡Otra oportunidad desaprovechada!",
"El entrenador da instrucciones",
"Cambio de ritmo en el juego",
"¡GOLAZO! ¡Increíble!",
"El árbitro señala falta",
"Tiempo de descuento",
"El partido está muy parejo",
"La posesión es clave en este encuentro",
"¡Qué remate! Por poco y entra"
],
specialTeamPlayers: {
goalkeeper: { name: "Fran Burela", number: 1 },
defenders: [
{ name: "Masma", number: 4 },
{ name: "Benja Perez", number: 5 }
],
forwards: [
{ name: "Lio", number: 10 },
{ name: "Chochi", number: 7 }
]
},
// Sistema de torneo
tournament: {
active: false,
currentRound: 0,
bracket: [],
playerTeam: null,
eliminated: false
},
// Sistema de penales
penalties: {
active: false,
playerScore: 0,
cpuScore: 0,
currentAttempt: 0,
maxAttempts: 5,
penaltyInProgress: false,
penaltyPower: 0,
penaltyDirection: 0,
penaltyTimer: 0,
penaltySpeed: 0.05
}
};
// Elementos del DOM
const screens = {
title: document.getElementById('title-screen'),
mode: document.getElementById('mode-screen'),
competition: document.getElementById('competition-screen'),
team: document.getElementById('team-screen'),
opponent: document.getElementById('opponent-screen'),
difficulty: document.getElementById('difficulty-screen'),
game: document.getElementById('game-screen')
};
// Variables del juego
let canvas, ctx;
let gameLoopId;
let keys = {};
let playerTeam = [];
let cpuTeam = [];
let ball = {};
let activePlayer = null;
let lastMessageTime = 0;
let messageInterval = 5000;
let lastAIAction = 0;
let aiActionDelay = 500;
let gameActive = false;
let animationTimer = 0;
let celebrationTimer = 0;
let isCelebrating = false;
let celebratingTeam = null;
let corners = 0;
let freeKicks = 0;
let lastGoalBy = null;
let goalScorer = null;
let ballTrail = [];
let ballRotation = 0;
let lastPlayerSwitch = 0;
let playerSwitchDelay = 300; // ms entre cambios
let goalkeepers = { player: null, cpu: null };
// Inicialización del juego
document.addEventListener('DOMContentLoaded', () => {
initEventListeners();
renderModes();
renderDifficulties();
// Prepara todos los equipos para amistosos
prepareAllTeamsForFriendly();
});
// Preparar todos los equipos para amistosos
function prepareAllTeamsForFriendly() {
gameData.competitions.amistoso.allTeams = {};
for (const leagueId in gameData.leagues) {
gameData.competitions.amistoso.allTeams[leagueId] = gameData.leagues[leagueId];
}
}
// Inicializar event listeners
function initEventListeners() {
// Botones de navegación
document.getElementById('friendly-match').addEventListener('click', () => {
gameData.selectedMode = 'amistoso';
showScreen('mode');
});
document.getElementById('cups-mode').addEventListener('click', () => {
gameData.selectedMode = 'copas';
showScreen('mode');
});
document.getElementById('exit-game').addEventListener('click', () => {
alert('¡Gracias por jugar FIFA 2D!');
});
// Navegación entre pantallas
document.getElementById('back-from-mode').addEventListener('click', () => showScreen('title'));
document.getElementById('select-mode').addEventListener('click', () => showScreen('competition'));
document.getElementById('back-from-competition').addEventListener('click', () => showScreen('mode'));
document.getElementById('select-competition').addEventListener('click', selectCompetition);
document.getElementById('back-from-team').addEventListener('click', () => {
if (gameData.selectedMode === 'amistoso' && gameData.selectedCompetition === 'amistoso') {
showScreen('competition');
} else {
showScreen('competition');
}
});
document.getElementById('select-team').addEventListener('click', () => showScreen('opponent'));
document.getElementById('back-from-opponent').addEventListener('click', () => showScreen('team'));
document.getElementById('select-opponent').addEventListener('click', () => showScreen('difficulty'));
document.getElementById('back-from-difficulty').addEventListener('click', () => showScreen('opponent'));
document.getElementById('start-game').addEventListener('click', startGame);
document.getElementById('back-from-game').addEventListener('click', () => {
stopGame();
showScreen('title');
resetGameData();
});
// Botón de penales
document.getElementById('start-penalty').addEventListener('click', startPenaltyShot);
// Controles de teclado
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
keys[key] = true;
// Espacio para pase/chute/penal
if (key === ' ' && gameActive && !isCelebrating) {
e.preventDefault();
if (gameData.penalties.active) {
if (!gameData.penalties.penaltyInProgress) {
startPenaltyShot();
} else {
executePenaltyShot();
}
} else {
if (!ball.spacePressed) {
ball.spacePressed = true;
ball.spacePressTime = Date.now();
ball.spaceStartX = ball.x;
ball.spaceStartY = ball.y;
}
}
}
// Cambiar jugador con TAB
if (key === 'tab' && gameActive && Date.now() - lastPlayerSwitch > playerSwitchDelay) {
e.preventDefault();
switchPlayer();
lastPlayerSwitch = Date.now();
}
// Pausa con P
if (key === 'p' && gameActive) {
togglePause();
}
});
document.addEventListener('keyup', (e) => {
const key = e.key.toLowerCase();
keys[key] = false;
// Espacio liberado (solo si no estamos en penales)
if (key === ' ' && gameActive && !isCelebrating && !gameData.penalties.active) {
if (ball.spacePressed) {
const pressDuration = Date.now() - ball.spacePressTime;
// Si fue un tap rápido (menos de 200ms) es un pase
if (pressDuration < 200) {
passBall();
} else {
// Si fue más largo, es un chute
shootBall(pressDuration);
}
ball.spacePressed = false;
ball.spacePressTime = 0;
}
}
});
}
// Renderizar modos
function renderModes() {
const container = document.getElementById('modes-container');
container.innerHTML = '';
Object.keys(gameData.modes).forEach(modeId => {
const mode = gameData.modes[modeId];
const button = document.createElement('button');
button.className = 'mode-button';
button.innerHTML = `
<div class="mode-icon">${mode.icon}</div>
<div>${mode.name}</div>
`;
button.addEventListener('click', () => {
document.querySelectorAll('.mode-button').forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
gameData.selectedMode = modeId;
document.getElementById('select-mode').disabled = false;
// Renderizar competiciones para este modo
renderCompetitions();
});
container.appendChild(button);
});
}
// Renderizar competiciones
function renderCompetitions() {
const container = document.getElementById('competitions-container');
container.innerHTML = '';
if (!gameData.selectedMode) return;
const mode = gameData.modes[gameData.selectedMode];
mode.competitions.forEach(compId => {
const comp = gameData.competitions[compId];
const button = document.createElement('button');
button.className = 'competition-button';
button.innerHTML = `
<div class="competition-logo">${comp.icon}</div>
<div>${comp.name}</div>
`;
button.addEventListener('click', () => {
document.querySelectorAll('.competition-button').forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
gameData.selectedCompetition = compId;
document.getElementById('select-competition').disabled = false;
});
container.appendChild(button);
});
// Actualizar título
document.getElementById('competition-title').textContent =
`Selecciona una Competición - ${mode.name}`;
}
// Seleccionar competición
function selectCompetition() {
if (gameData.selectedCompetition === 'amistoso') {
// Para amistosos, mostrar pantalla de selección de liga para el jugador
renderLeaguesForAmistoso('player');
showScreen('team');
} else {
// Para copas, inicializar torneo y mostrar equipos
initTournament();
renderTeamsForCompetition();
showScreen('team');
}
}
// Inicializar torneo para copas
function initTournament() {
if (!gameData.selectedCompetition) return;
const competition = gameData.competitions[gameData.selectedCompetition];
if (competition.tournamentMode) {
gameData.tournament.active = true;
gameData.tournament.currentRound = 0;
gameData.tournament.bracket = [];
gameData.tournament.playerTeam = null;
gameData.tournament.eliminated = false;
// Crear bracket aleatorio (simplificado)
const teams = [...competition.teams];
shuffleArray(teams);
// Crear emparejamientos
for (let i = 0; i < teams.length; i += 2) {
if (i + 1 < teams.length) {
gameData.tournament.bracket.push({
team1: teams[i],
team2: teams[i + 1],
winner: null,
played: false
});
}
}
competition.tournamentBracket = gameData.tournament.bracket;
competition.currentRound = 0;
}
}
// Función para mezclar array
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Renderizar ligas para amistosos (para selección de equipo del jugador)
function renderLeaguesForAmistoso(type) {
const container = type === 'player' ?
document.getElementById('teams-container') :
document.getElementById('opponent-teams-container');
container.innerHTML = '';
if (type === 'player') {
document.getElementById('team-selection-title').textContent = 'Selecciona una Liga';
} else {
document.getElementById('opponent-selection-title').textContent = 'Selecciona una Liga';
}
// Agregar opción "Todas las Ligas"
const allButton = document.createElement('button');
allButton.className = 'team-button';
allButton.innerHTML = `
<div class="team-logo">🌍</div>
<div>Todas las Ligas</div>
`;
allButton.addEventListener('click', () => {
document.querySelectorAll(`#${container.id} .team-button`).forEach(btn => btn.classList.remove('selected'));
allButton.classList.add('selected');
if (type === 'player') {
gameData.selectedLeague = 'all';
renderAllTeamsForFriendly('player');
} else {
gameData.selectedOpponentLeague = 'all';
renderAllTeamsForFriendly('opponent');
}
});
container.appendChild(allButton);
// Agregar cada liga
for (const leagueId in gameData.leagues) {
const league = gameData.leagues[leagueId];
const button = document.createElement('button');
button.className = 'team-button';
button.innerHTML = `
<div class="team-logo">${league.flag}</div>
<div>${league.name}</div>
`;
button.addEventListener('click', () => {
document.querySelectorAll(`#${container.id} .team-button`).forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
if (type === 'player') {
gameData.selectedLeague = leagueId;
renderTeamsFromLeague('player');
} else {
gameData.selectedOpponentLeague = leagueId;
renderTeamsFromLeague('opponent');
}
});
container.appendChild(button);
}
}
// Renderizar todos los equipos para amistosos
function renderAllTeamsForFriendly(type) {
const container = type === 'player' ?
document.getElementById('teams-container') :
document.getElementById('opponent-teams-container');
container.innerHTML = '';
if (type === 'player') {
document.getElementById('team-selection-title').textContent = 'Selecciona tu Equipo';
} else {
document.getElementById('opponent-selection-title').textContent = 'Selecciona el Equipo Rival';
}
// Recopilar todos los equipos
const allTeams = [];
for (const leagueId in gameData.leagues) {
const league = gameData.leagues[leagueId];
league.teams.forEach(team => {
allTeams.push({
name: team,
league: league.name
});
});
}
// Mostrar equipos
allTeams.forEach(team => {
const button = document.createElement('button');
button.className = 'team-button';
button.innerHTML = `
<div class="team-logo">${team.name.charAt(0)}</div>
<div>${team.name}</div>
<small>${team.league}</small>
`;
button.addEventListener('click', () => {
document.querySelectorAll(`#${container.id} .team-button`).forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
if (type === 'player') {
gameData.selectedTeam = team.name;
document.getElementById('select-team').disabled = false;
} else {
gameData.selectedOpponent = team.name;
document.getElementById('select-opponent').disabled = false;
}
});
container.appendChild(button);
});
}
// Renderizar equipos desde liga específica
function renderTeamsFromLeague(type) {
const container = type === 'player' ?
document.getElementById('teams-container') :
document.getElementById('opponent-teams-container');
container.innerHTML = '';
const leagueId = type === 'player' ? gameData.selectedLeague : gameData.selectedOpponentLeague;
const league = gameData.leagues[leagueId];
if (type === 'player') {
document.getElementById('team-selection-title').textContent =
`Selecciona tu Equipo - ${league.name}`;
} else {
document.getElementById('opponent-selection-title').textContent =
`Selecciona Rival - ${league.name}`;
}
league.teams.forEach(team => {
const button = document.createElement('button');
button.className = 'team-button';
button.innerHTML = `
<div class="team-logo">${team.charAt(0)}</div>
<div>${team}</div>
`;
button.addEventListener('click', () => {
document.querySelectorAll(`#${container.id} .team-button`).forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
if (type === 'player') {
gameData.selectedTeam = team;
document.getElementById('select-team').disabled = false;
} else {
gameData.selectedOpponent = team;
document.getElementById('select-opponent').disabled = false;
}
});
container.appendChild(button);
});
}
// Renderizar equipos para competición
function renderTeamsForCompetition() {
const container = document.getElementById('teams-container');
container.innerHTML = '';
document.getElementById('team-selection-title').textContent =
`Selecciona tu Equipo - ${gameData.competitions[gameData.selectedCompetition].name}`;
const competition = gameData.competitions[gameData.selectedCompetition];
competition.teams.forEach(team => {
const button = document.createElement('button');
button.className = 'team-button';
button.innerHTML = `
<div class="team-logo">${team.charAt(0)}</div>
<div>${team}</div>
`;
button.addEventListener('click', () => {
document.querySelectorAll('#teams-container .team-button').forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
gameData.selectedTeam = team;
gameData.tournament.playerTeam = team;
document.getElementById('select-team').disabled = false;
// Encontrar oponente en el bracket
const bracket = gameData.tournament.bracket[0];
let opponent = null;
if (bracket.team1 === team) {
opponent = bracket.team2;
} else if (bracket.team2 === team) {
opponent = bracket.team1;
} else {
// Si el equipo no está en el primer enfrentamiento, elegir aleatorio
opponent = competition.teams.find(t => t !== team);
}
gameData.selectedOpponent = opponent;
// Renderizar oponentes para confirmación
renderOpponentsForCompetition();
});
container.appendChild(button);
});
}
// Renderizar oponentes para competición
function renderOpponentsForCompetition() {
const container = document.getElementById('opponent-teams-container');
container.innerHTML = '';
document.getElementById('opponent-selection-title').textContent =
`Tu rival es: ${gameData.selectedOpponent}`;
// Solo mostrar el oponente asignado
const button = document.createElement('button');
button.className = 'team-button selected';
button.innerHTML = `
<div class="team-logo">${gameData.selectedOpponent.charAt(0)}</div>
<div>${gameData.selectedOpponent}</div>
`;
container.appendChild(button);
document.getElementById('select-opponent').disabled = false;
}
// Renderizar dificultades
function renderDifficulties() {
const container = document.getElementById('difficulty-container');
container.innerHTML = '';
gameData.difficulties.forEach(diff => {
const button = document.createElement('button');
button.className = `difficulty-button difficulty-${diff.id}`;
button.textContent = diff.name;
button.addEventListener('click', () => {
document.querySelectorAll('.difficulty-button').forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
gameData.selectedDifficulty = diff;
document.getElementById('start-game').disabled = false;
});
container.appendChild(button);
});
}
// Mostrar pantalla específica
function showScreen(screenName) {
Object.values(screens).forEach(screen => {
screen.classList.add('hidden');
});
screens[screenName].classList.remove('hidden');
// Configuraciones específicas para la pantalla de juego
if (screenName === 'game') {
document.getElementById('player-team-name').textContent = gameData.selectedTeam;
document.getElementById('cpu-team-name').textContent = gameData.selectedOpponent;
// Configurar color del marcador según la competición
const scoreboard = document.getElementById('scoreboard');
scoreboard.className = 'scoreboard';
if (gameData.selectedCompetition && gameData.selectedCompetition !== 'amistoso') {
scoreboard.classList.add(gameData.selectedCompetition);
}
}
}
// Iniciar el juego
function startGame() {
showScreen('game');
initCanvas();
initGameObjects();
gameActive = true;
gameData.gameState = 'playing';
gameData.matchTime = 0;
gameData.score = { player: 0, cpu: 0 };
updateScoreboard();
updateMessage("¡Comienza el partido!");
gameLoopId = requestAnimationFrame(gameLoop);
}
// Cambiar jugador activo
function switchPlayer() {
if (!playerTeam.length || isCelebrating) return;
// Encontrar índice del jugador actual
let currentIndex = playerTeam.findIndex(player => player === activePlayer);
// Buscar siguiente jugador que no sea el portero
let nextIndex = (currentIndex + 1) % playerTeam.length;
let attempts = 0;
while (playerTeam[nextIndex].isGoalkeeper && attempts < playerTeam.length) {
nextIndex = (nextIndex + 1) % playerTeam.length;
attempts++;
}
// Cambiar jugador activo
activePlayer = playerTeam[nextIndex];
updateMessage(`Controlas a: ${activePlayer.name} (${activePlayer.number})`);
}
// Detener el juego
function stopGame() {
gameActive = false;
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
}
}
// Pausar/reanudar juego
function togglePause() {
if (gameData.gameState === 'playing') {
gameData.gameState = 'paused';
updateMessage("Partido en pausa");
} else if (gameData.gameState === 'paused') {
gameData.gameState = 'playing';
updateMessage("El partido se reanuda");
gameLoopId = requestAnimationFrame(gameLoop);
}
}
// Reiniciar datos del juego
function resetGameData() {
gameData.selectedMode = null;
gameData.selectedCompetition = null;
gameData.selectedLeague = null;
gameData.selectedTeam = null;
gameData.selectedOpponent = null;
gameData.selectedDifficulty = null;
document.getElementById('select-mode').disabled = true;
document.getElementById('select-competition').disabled = true;
document.getElementById('select-team').disabled = true;
document.getElementById('select-opponent').disabled = true;
document.getElementById('start-game').disabled = true;
document.querySelectorAll('.selected').forEach(el => el.classList.remove('selected'));
}
// Inicializar canvas
function initCanvas() {
canvas = document.getElementById('game-canvas');
ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
// Inicializar objetos del juego
function initGameObjects() {
const fieldWidth = canvas.width;
const fieldHeight = canvas.height;
const centerX = fieldWidth / 2;
const centerY = fieldHeight / 2;
// Limpiar equipos
playerTeam = [];
cpuTeam = [];
goalkeepers = { player: null, cpu: null };
// Crear equipo especial si está seleccionado
const isSpecialTeam = gameData.selectedTeam === 'Equipo Especial' ||
gameData.selectedOpponent === 'Equipo Especial';
// Formación 2-2 (2 defensores, 2 delanteros + arquero)
// Equipo del jugador (lado izquierdo)
const playerFormation = [
// Arquero
{ x: 80, y: centerY, type: 'gk', number: 1, name: isSpecialTeam && gameData.selectedTeam === 'Equipo Especial' ?
gameData.specialTeamPlayers.goalkeeper.name : 'Arquero' },
// Defensores
{ x: 250, y: centerY - 80, type: 'df', number: 4, name: isSpecialTeam && gameData.selectedTeam === 'Equipo Especial' ?
gameData.specialTeamPlayers.defenders[0].name : 'Defensor 1' },
{ x: 250, y: centerY + 80, type: 'df', number: 5, name: isSpecialTeam && gameData.selectedTeam === 'Equipo Especial' ?
gameData.specialTeamPlayers.defenders[1].name : 'Defensor 2' },
// Delanteros
{ x: 400, y: centerY - 60, type: 'fw', number: 10, name: isSpecialTeam && gameData.selectedTeam === 'Equipo Especial' ?
gameData.specialTeamPlayers.forwards[0].name : 'Delantero 1' },
{ x: 400, y: centerY + 60, type: 'fw', number: 7, name: isSpecialTeam && gameData.selectedTeam === 'Equipo Especial' ?
gameData.specialTeamPlayers.forwards[1].name : 'Delantero 2' }
];
playerFormation.forEach((pos, i) => {
const player = {
x: pos.x,
y: pos.y,
radius: 20,
color: '#00a8ff',
type: pos.type,
number: pos.number,
name: pos.name,
speed: 3.5,
hasBall: i === 3, // El primer delantero tiene la pelota al inicio
isGoalkeeper: pos.type === 'gk',
targetX: pos.x,
targetY: pos.y,
isMoving: false,
passTarget: null,
velocityX: 0,
velocityY: 0,
preferredPosition: { x: pos.x, y: pos.y },
moveTimer: 0,
state: 'idle'
};
playerTeam.push(player);
if (player.isGoalkeeper) {
goalkeepers.player = player;
}
});
// Equipo de la CPU (lado derecho)
const cpuFormation = [
// Arquero
{ x: fieldWidth - 80, y: centerY, type: 'gk', number: 1, name: isSpecialTeam && gameData.selectedOpponent === 'Equipo Especial' ?
gameData.specialTeamPlayers.goalkeeper.name : 'Arquero' },
// Defensores
{ x: fieldWidth - 250, y: centerY - 80, type: 'df', number: 4, name: isSpecialTeam && gameData.selectedOpponent === 'Equipo Especial' ?
gameData.specialTeamPlayers.defenders[0].name : 'Defensor 1' },
{ x: fieldWidth - 250, y: centerY + 80, type: 'df', number: 5, name: isSpecialTeam && gameData.selectedOpponent === 'Equipo Especial' ?
gameData.specialTeamPlayers.defenders[1].name : 'Defensor 2' },
// Delanteros
{ x: fieldWidth - 400, y: centerY - 60, type: 'fw', number: 10, name: isSpecialTeam && gameData.selectedOpponent === 'Equipo Especial' ?
gameData.specialTeamPlayers.forwards[0].name : 'Delantero 1' },
{ x: fieldWidth - 400, y: centerY + 60, type: 'fw', number: 7, name: isSpecialTeam && gameData.selectedOpponent === 'Equipo Especial' ?
gameData.specialTeamPlayers.forwards[1].name : 'Delantero 2' }
];
cpuFormation.forEach((pos, i) => {
const player = {
x: pos.x,
y: pos.y,
radius: 20,
color: '#e84118',
type: pos.type,
number: pos.number,
name: pos.name,
speed: 3.5,
hasBall: false,
isGoalkeeper: pos.type === 'gk',
targetX: pos.x,
targetY: pos.y,
isMoving: false,
passTarget: null,
velocityX: 0,
velocityY: 0,
preferredPosition: { x: pos.x, y: pos.y },
moveTimer: 0,
state: 'idle'
};
cpuTeam.push(player);
if (player.isGoalkeeper) {
goalkeepers.cpu = player;
}
});
// Crear pelota
const playerWithBall = playerTeam.find(player => player.hasBall);
ball = {
x: playerWithBall ? playerWithBall.x + 30 : centerX,
y: playerWithBall ? playerWithBall.y : centerY,
radius: 14,
color: '#FFD700', // Amarillo
speed: 0,
direction: 0,
owner: playerWithBall ? 'player' : null,
spacePressed: false,
spacePressTime: 0,
spaceStartX: 0,
spaceStartY: 0,
isMoving: false,
trail: [],
rotation: 0,
rotationSpeed: 0
};
// Establecer jugador activo
activePlayer = playerWithBall || playerTeam[3];
// Configurar IA según dificultad
if (gameData.selectedDifficulty) {
aiActionDelay = gameData.selectedDifficulty.aiDelay;
cpuTeam.forEach(player => {
player.speed = 3.5 * gameData.selectedDifficulty.speed;
});
}
// Inicializar rastro de la pelota
ballTrail = [];
isCelebrating = false;
celebratingTeam = null;
celebrationTimer = 0;
corners = 0;
freeKicks = 0;
lastGoalBy = null;
goalScorer = null;
ballRotation = 0;
}
// Bucle principal del juego
function gameLoop(timestamp) {
if (gameData.gameState !== 'playing') return;
// Si estamos en penales, manejar penales
if (gameData.penalties.active) {
updatePenalties();
requestAnimationFrame(gameLoop);
return;
}
// Actualizar tiempo del partido
updateMatchTime();
// Actualizar animaciones
animationTimer += 1/60;
// Actualizar celebración si está activa
if (isCelebrating) {
updateCelebration();
drawField();
drawPlayers();
drawBall();
requestAnimationFrame(gameLoop);
return;
}
// Actualizar posiciones
updatePlayers();
updateBall();
updateAI();
updateTeammateAI();
// Dibujar todo
drawField();
drawPlayers();
drawBall();
// Verificar colisiones y goles
checkCollisions();
checkGoal();
checkOutOfBounds();
// Mensajes aleatorios
if (timestamp - lastMessageTime > messageInterval) {
randomMessage();
lastMessageTime = timestamp;
}
// Verificar si hay que ir a penales (empate después de 5 minutos)
if (gameData.matchTime >= gameData.maxMatchTime &&
gameData.score.player === gameData.score.cpu) {
startPenaltyShootout();
return;
}
// Verificar eliminación en copas
if (gameData.tournament.active) {
if (gameData.score.player >= 5) {
// Jugador gana el partido
if (gameData.tournament.eliminated) return;
winTournamentMatch();
return;
} else if (gameData.score.cpu >= 5) {
// Jugador es eliminado
if (gameData.tournament.eliminated) return;
loseTournamentMatch();
return;
}
}
// Continuar el bucle
gameLoopId = requestAnimationFrame(gameLoop);
}
// Actualizar tiempo del partido
function updateMatchTime() {
gameData.matchTime += 1/60;
const minutes = Math.floor(gameData.matchTime / 60);
const seconds = Math.floor(gameData.matchTime % 60);
document.getElementById('game-time').textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (gameData.matchTime >= gameData.maxMatchTime &&
!gameData.tournament.active) {
endMatch();
}
}
// Actualizar jugadores
function updatePlayers() {
// Mover jugador activo con teclado (puede moverse aunque no tenga pelota)
if (activePlayer && !isCelebrating) {
let moveX = 0, moveY = 0;
if (keys['w'] || keys['arrowup']) moveY -= activePlayer.speed;
if (keys['s'] || keys['arrowdown']) moveY += activePlayer.speed;
if (keys['a'] || keys['arrowleft']) moveX -= activePlayer.speed;
if (keys['d'] || keys['arrowright']) moveX += activePlayer.speed;
// Normalizar movimiento diagonal
if (moveX !== 0 && moveY !== 0) {
moveX *= 0.7071;
moveY *= 0.7071;
}
// Aplicar velocidad suavizada
activePlayer.velocityX = moveX * 0.8 + activePlayer.velocityX * 0.2;
activePlayer.velocityY = moveY * 0.8 + activePlayer.velocityY * 0.2;
// Mover jugador
const newX = activePlayer.x + activePlayer.velocityX;
const newY = activePlayer.y + activePlayer.velocityY;
// Verificar que no salga del campo
if (newX >= 50 && newX <= canvas.width - 50) {
activePlayer.x = newX;
}
if (newY >= 50 && newY <= canvas.height - 50) {
activePlayer.y = newY;
}
// Si tiene la pelota, moverla junto al jugador (pero no dentro)
if (activePlayer.hasBall && ball.owner === 'player') {
const angle = Math.atan2(activePlayer.velocityY, activePlayer.velocityX);
const distanceFromPlayer = 35; // Distancia de la pelota al jugador
ball.x = activePlayer.x + Math.cos(angle) * distanceFromPlayer;
ball.y = activePlayer.y + Math.sin(angle) * distanceFromPlayer;
// Rotar pelota cuando se mueve
if (Math.abs(activePlayer.velocityX) > 0.1 || Math.abs(activePlayer.velocityY) > 0.1) {
ball.rotation += 0.1;
ball.rotationSpeed = Math.sqrt(activePlayer.velocityX * activePlayer.velocityX +
activePlayer.velocityY * activePlayer.velocityY) * 0.1;
}
// Añadir al rastro de la pelota
if (ballTrail.length < 10 || Math.random() < 0.3) {
ballTrail.push({
x: ball.x,
y: ball.y,
alpha: 1,
radius: ball.radius * 0.7
});
}
}
}
// Actualizar rastro de la pelota
for (let i = 0; i < ballTrail.length; i++) {
ballTrail[i].alpha -= 0.05;
ballTrail[i].radius -= 0.1;
}
ballTrail = ballTrail.filter(point => point.alpha > 0 && point.radius > 0);
// Actualizar porteros
updateGoalkeepers();
// Aplicar inercia a todos los jugadores
playerTeam.forEach(player => {
if (player !== activePlayer) {
player.velocityX *= 0.9;
player.velocityY *= 0.9;
player.x += player.velocityX;
player.y += player.velocityY;
}
});
cpuTeam.forEach(player => {
player.velocityX *= 0.9;
player.velocityY *= 0.9;
player.x += player.velocityX;
player.y += player.velocityY;
});
}
// Actualizar porteros
function updateGoalkeepers() {
// Portero del jugador
if (goalkeepers.player) {
// Seguir el movimiento vertical de la pelota cuando está en su área
if (ball.x < canvas.width / 3) {
const targetY = ball.y;
const dy = targetY - goalkeepers.player.y;
// Mover suavemente hacia la pelota
goalkeepers.player.y += dy * 0.05;
// Limitar movimiento dentro del área
const goalY = canvas.height / 2;
const maxMove = 100;
goalkeepers.player.y = Math.max(goalY - maxMove,
Math.min(goalY + maxMove, goalkeepers.player.y));
} else {
// Volver a posición central
const centerY = canvas.height / 2;
const dy = centerY - goalkeepers.player.y;
goalkeepers.player.y += dy * 0.05;
}
}
// Portero de la CPU
if (goalkeepers.cpu) {
// Seguir el movimiento vertical de la pelota cuando está en su área
if (ball.x > canvas.width * 2/3) {
const targetY = ball.y;
const dy = targetY - goalkeepers.cpu.y;
// Mover suavemente hacia la pelota
goalkeepers.cpu.y += dy * 0.05;
// Limitar movimiento dentro del área
const goalY = canvas.height / 2;
const maxMove = 100;
goalkeepers.cpu.y = Math.max(goalY - maxMove,
Math.min(goalY + maxMove, goalkeepers.cpu.y));
} else {
// Volver a posición central
const centerY = canvas.height / 2;
const dy = centerY - goalkeepers.cpu.y;
goalkeepers.cpu.y += dy * 0.05;
}
}
}
// Actualizar IA de compañeros
function updateTeammateAI() {
playerTeam.forEach(player => {
if (player === activePlayer || player.isGoalkeeper) return;
player.moveTimer -= 1/60;
if (player.moveTimer <= 0) {
// Cambiar estado
const states = ['move_to_position', 'support_attack', 'defend'];
player.state = states[Math.floor(Math.random() * states.length)];
player.moveTimer = 2 + Math.random() * 3; // Cambiar cada 2-5 segundos
}
// Ejecutar comportamiento según estado
switch(player.state) {
case 'move_to_position':
// Moverse a posición preferida
const dx = player.preferredPosition.x - player.x;
const dy = player.preferredPosition.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance > 10) {
player.velocityX += (dx / distance) * 0.1;
player.velocityY += (dy / distance) * 0.1;
}
break;
case 'support_attack':
// Apoyar al ataque si el jugador tiene la pelota
if (ball.owner === 'player' && ball.x > canvas.width / 3) {
// Moverse hacia adelante
const targetX = player.x + 50;
const targetY = player.y + (Math.random() * 40 - 20);
const dx = targetX - player.x;
const dy = targetY - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance > 5) {
player.velocityX += (dx / distance) * 0.15;
player.velocityY += (dy / distance) * 0.15;
}
}
break;
case 'defend':
// Defender si la pelota está en nuestro campo
if (ball.x < canvas.width / 3) {
// Moverse hacia la pelota
const dx = ball.x - player.x;
const dy = ball.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance > 30) {
player.velocityX += (dx / distance) * 0.2;
player.velocityY += (dy / distance) * 0.2;
}
}
break;
}
// Limitar velocidad
const speed = Math.sqrt(player.velocityX * player.velocityX +
player.velocityY * player.velocityY);
if (speed > player.speed) {
player.velocityX = (player.velocityX / speed) * player.speed;
player.velocityY = (player.velocityY / speed) * player.speed;
}
});
}
// Actualizar pelota
function updateBall() {
ballRotation += ball.rotationSpeed;
ball.rotationSpeed *= 0.95; // Reducir rotación por fricción
// Si la pelota tiene dueño, sigue al jugador (pero no dentro de él)
if (ball.owner && !isCelebrating) {
const team = ball.owner === 'player' ? playerTeam : cpuTeam;
const playerWithBall = team.find(player => player.hasBall);
if (playerWithBall) {
// Calcular posición al lado del jugador
const angle = Math.atan2(playerWithBall.velocityY, playerWithBall.velocityX);
const targetX = playerWithBall.x + Math.cos(angle) * 35;
const targetY = playerWithBall.y + Math.sin(angle) * 35;
// Suavizar movimiento hacia el jugador
const dx = targetX - ball.x;
const dy = targetY - ball.y;
ball.x += dx * 0.3;
ball.y += dy * 0.3;
// Rotar pelota
ball.rotation += Math.sqrt(dx*dx + dy*dy) * 0.01;
}
} else if (ball.speed > 0.5 && !isCelebrating) {
// Si no tiene dueño y tiene velocidad, moverse
ball.x += Math.cos(ball.direction) * ball.speed;
ball.y += Math.sin(ball.direction) * ball.speed;
// Reducir velocidad por fricción
ball.speed *= 0.98;
ball.rotation += ball.speed * 0.05;
// Añadir al rastro
if (ballTrail.length < 15 && ball.speed > 3) {
ballTrail.push({
x: ball.x,
y: ball.y,
alpha: 1,
radius: ball.radius * 0.8
});
}
// Rebotar en los bordes del campo
if (ball.x <= ball.radius || ball.x >= canvas.width - ball.radius) {
ball.direction = Math.PI - ball.direction;
ball.speed *= 0.8;
updateMessage("¡La pelota sale por la línea!");
}
if (ball.y <= ball.radius || ball.y >= canvas.height - ball.radius) {
ball.direction = -ball.direction;
ball.speed *= 0.8;
updateMessage("¡La pelota sale por la línea!");
}
// Mantener dentro del campo
ball.x = Math.max(ball.radius, Math.min(canvas.width - ball.radius, ball.x));
ball.y = Math.max(ball.radius, Math.min(canvas.height - ball.radius, ball.y));
}
}
// Actualizar IA de la CPU
function updateAI() {
const now = Date.now();
if (now - lastAIAction < aiActionDelay || isCelebrating) return;
// Encontrar jugador de la CPU con la pelota
const cpuWithBall = cpuTeam.find(player => player.hasBall);
if (cpuWithBall) {
// Si la CPU tiene la pelota
const aggression = gameData.selectedDifficulty ? gameData.selectedDifficulty.aiAggression : 0.5;
if (Math.random() < 0.01 * aggression) { // Probabilidad de chutar
shootBallCPU(cpuWithBall);
} else if (Math.random() < 0.02 * aggression) { // Probabilidad de pasar
passBallCPU(cpuWithBall);
} else {
// Moverse hacia la portería del jugador
const targetX = 80;
const targetY = canvas.height / 2 + (Math.random() * 100 - 50);
const dx = targetX - cpuWithBall.x;
const dy = targetY - cpuWithBall.y;
const distance = Math.sqrt(dx*dx + dy*dy);
// Movimiento inteligente
if (distance > 50) {
cpuWithBall.velocityX += (dx / distance) * cpuWithBall.speed * 0.1;
cpuWithBall.velocityY += (dy / distance) * cpuWithBall.speed * 0.1;
}
// Limitar velocidad
const speed = Math.sqrt(cpuWithBall.velocityX * cpuWithBall.velocityX +
cpuWithBall.velocityY * cpuWithBall.velocityY);
if (speed > cpuWithBall.speed) {
cpuWithBall.velocityX = (cpuWithBall.velocityX / speed) * cpuWithBall.speed;
cpuWithBall.velocityY = (cpuWithBall.velocityY / speed) * cpuWithBall.speed;
}
}
} else if (ball.owner === null) {
// Si la pelota está libre, buscar el jugador más cercano
let closestPlayer = null;
let closestDistance = Infinity;
cpuTeam.forEach(player => {
if (player.isGoalkeeper) return;
const dx = ball.x - player.x;
const dy = ball.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < closestDistance && distance < 200) {
closestDistance = distance;
closestPlayer = player;
}
});
// Mover al jugador más cercano hacia la pelota
if (closestPlayer && closestDistance > 30) {
const dx = ball.x - closestPlayer.x;
const dy = ball.y - closestPlayer.y;
const distance = Math.sqrt(dx*dx + dy*dy);
closestPlayer.velocityX += (dx / distance) * closestPlayer.speed * 0.15;
closestPlayer.velocityY += (dy / distance) * closestPlayer.speed * 0.15;
}
} else if (ball.owner === 'player') {
// Si el jugador tiene la pelota, presionar
const playerWithBall = playerTeam.find(player => player.hasBall);
if (playerWithBall) {
// Encontrar el defensor más cercano
let closestDefender = null;
let closestDistance = Infinity;
cpuTeam.forEach(player => {
if (!player.isGoalkeeper) {
const dx = playerWithBall.x - player.x;
const dy = playerWithBall.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < closestDistance && distance < 300) {
closestDistance = distance;
closestDefender = player;
}
}
});
// Mover el defensor hacia el jugador con la pelota
if (closestDefender && closestDistance > 20) {
const dx = playerWithBall.x - closestDefender.x;
const dy = playerWithBall.y - closestDefender.y;
const distance = Math.sqrt(dx*dx + dy*dy);
closestDefender.velocityX += (dx / distance) * closestDefender.speed * 0.2;
closestDefender.velocityY += (dy / distance) * closestDefender.speed * 0.2;
}
// Otros jugadores de la CPU se posicionan para cubrir
cpuTeam.forEach(player => {
if (player !== closestDefender && !player.isGoalkeeper) {
// Moverse a posiciones defensivas
const targetX = player.preferredPosition.x;
const targetY = player.preferredPosition.y;
const dx = targetX - player.x;
const dy = targetY - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance > 10) {
player.velocityX += (dx / distance) * 0.1;
player.velocityY += (dy / distance) * 0.1;
}
}
});
}
}
lastAIAction = now;
}
// Dibujar campo
function drawField() {
const width = canvas.width;
const height = canvas.height;
// Césped con patrón
ctx.fillStyle = '#2a623d';
ctx.fillRect(0, 0, width, height);
// Patrón de césped
ctx.strokeStyle = '#3a7d4d';
ctx.lineWidth = 1;
for (let i = 0; i < width; i += 40) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, height);
ctx.stroke();
}
for (let i = 0; i < height; i += 40) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(width, i);
ctx.stroke();
}
// Líneas del campo
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 3;
// Líneas exteriores
ctx.strokeRect(20, 20, width - 40, height - 40);
// Línea de mitad de cancha
ctx.beginPath();
ctx.moveTo(width / 2, 20);
ctx.lineTo(width / 2, height - 20);
ctx.stroke();
// Círculo central
ctx.beginPath();
ctx.arc(width / 2, height / 2, 80, 0, Math.PI * 2);
ctx.stroke();
// Punto central
ctx.beginPath();
ctx.arc(width / 2, height / 2, 5, 0, Math.PI * 2);
ctx.fillStyle = '#FFFFFF';
ctx.fill();
// Áreas
// Área izquierda (jugador)
ctx.strokeRect(20, height / 2 - 120, 100, 240);
// Área derecha (CPU)
ctx.strokeRect(width - 120, height / 2 - 120, 100, 240);
// Porterías
ctx.lineWidth = 4;
// Portería izquierda
ctx.strokeRect(10, height / 2 - 60, 10, 120);
// Portería derecha
ctx.strokeRect(width - 20, height / 2 - 60, 10, 120);
// Tribuna (gradas)
ctx.fillStyle = '#333333';
ctx.fillRect(0, 0, width, 20);
ctx.fillRect(0, height - 20, width, 20);
ctx.fillRect(0, 0, 20, height);
ctx.fillRect(width - 20, 0, 20, height);
// Detalles de la tribuna
ctx.fillStyle = '#555555';
for (let i = 40; i < width - 40; i += 40) {
ctx.fillRect(i, 5, 20, 10);
ctx.fillRect(i, height - 15, 20, 10);
}
for (let i = 40; i < height - 40; i += 40) {
ctx.fillRect(5, i, 10, 20);
ctx.fillRect(width - 15, i, 10, 20);
}
// Línea de medio campo resaltada
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 5;
ctx.setLineDash([10, 10]);
ctx.beginPath();
ctx.moveTo(width / 2, 20);
ctx.lineTo(width / 2, height - 20);
ctx.stroke();
ctx.setLineDash([]);
}
// Dibujar jugadores
function drawPlayers() {
// Dibujar rastro de la pelota
for (let i = 0; i < ballTrail.length; i++) {
const point = ballTrail[i];
ctx.beginPath();
ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 215, 0, ${point.alpha})`;
ctx.fill();
}
// Dibujar jugadores del equipo del jugador
playerTeam.forEach(player => {
// Cuerpo del jugador
ctx.beginPath();
ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2);
ctx.fillStyle = player.color;
ctx.fill();
// Borde (resaltar al jugador activo)
if (player === activePlayer) {
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 4;
ctx.stroke();
} else {
ctx.strokeStyle = player.hasBall ? '#FFD700' : '#FFFFFF';
ctx.lineWidth = player.hasBall ? 3 : 2;
ctx.stroke();
}
// Número y nombre
ctx.fillStyle = '#FFFFFF';
ctx.font = player.hasBall ? 'bold 14px Arial' : '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(player.number, player.x, player.y);
// Nombre pequeño arriba
ctx.font = '10px Arial';
ctx.fillText(player.name, player.x, player.y - player.radius - 8);
// Indicador de jugador activo
if (player === activePlayer) {
ctx.beginPath();
ctx.arc(player.x, player.y - player.radius - 15, 3, 0, Math.PI * 2);
ctx.fillStyle = '#FFD700';
ctx.fill();
}
});
// Dibujar jugadores de la CPU
cpuTeam.forEach(player => {
// Cuerpo del jugador
ctx.beginPath();
ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2);
ctx.fillStyle = player.color;
ctx.fill();
// Borde
ctx.strokeStyle = player.hasBall ? '#FFD700' : '#FFFFFF';
ctx.lineWidth = player.hasBall ? 3 : 2;
ctx.stroke();
// Número y nombre
ctx.fillStyle = '#FFFFFF';
ctx.font = player.hasBall ? 'bold 14px Arial' : '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(player.number, player.x, player.y);
// Nombre pequeño arriba
ctx.font = '10px Arial';
ctx.fillText(player.name, player.x, player.y - player.radius - 8);
});
}
// Dibujar pelota
function drawBall() {
// Guardar estado del contexto
ctx.save();
// Aplicar rotación
ctx.translate(ball.x, ball.y);
ctx.rotate(ballRotation);
// Pelota con efecto de animación si está en movimiento
const ballScale = ball.speed > 5 ? 1.1 : 1.0;
ctx.scale(ballScale, ballScale);
// Cuerpo de la pelota (amarilla)
ctx.beginPath();
ctx.arc(0, 0, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = '#FFD700';
ctx.fill();
// Diseño de la pelota (rayas negras)
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
// Círculo exterior
ctx.beginPath();
ctx.arc(0, 0, ball.radius - 1, 0, Math.PI * 2);
ctx.stroke();
// Patrón hexagonal (simplificado)
ctx.beginPath();
ctx.moveTo(0, -ball.radius + 3);
ctx.lineTo(-3, -1);
ctx.lineTo(-2, 3);
ctx.lineTo(2, 3);
ctx.lineTo(3, -1);
ctx.closePath();
ctx.stroke();
// Puntos adicionales
ctx.beginPath();
ctx.arc(0, ball.radius - 4, 2, 0, Math.PI * 2);
ctx.fill();
// Restaurar estado del contexto
ctx.restore();
// Efecto de velocidad (estela)
if (ball.speed > 8) {
ctx.beginPath();
const trailLength = ball.speed * 1.5;
const trailX = ball.x - Math.cos(ball.direction) * trailLength;
const trailY = ball.y - Math.sin(ball.direction) * trailLength;
const gradient = ctx.createLinearGradient(trailX, trailY, ball.x, ball.y);
gradient.addColorStop(0, 'rgba(255, 215, 0, 0)');
gradient.addColorStop(1, 'rgba(255, 215, 0, 0.5)');
ctx.strokeStyle = gradient;
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(trailX, trailY);
ctx.lineTo(ball.x, ball.y);
ctx.stroke();
}
}
// Verificar colisiones
function checkCollisions() {
// Colisiones entre jugadores y pelota (cuando la pelota no tiene dueño o está en movimiento)
if ((!ball.owner || ball.speed > 3) && !isCelebrating) {
// Con jugadores del equipo del jugador
playerTeam.forEach(player => {
if (!player.hasBall) {
const dx = ball.x - player.x;
const dy = ball.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < player.radius + ball.radius + 5) {
// El jugador toma la pelota
player.hasBall = true;
ball.owner = 'player';
activePlayer = player;
ball.speed = 0;
// Quitar la pelota a cualquier otro jugador
playerTeam.forEach(p => {
if (p !== player) p.hasBall = false;
});
cpuTeam.forEach(p => p.hasBall = false);
updateMessage("¡Recuperación del balón!");
animatePass();
}
}
});
// Con jugadores de la CPU
cpuTeam.forEach(player => {
if (!player.hasBall) {
const dx = ball.x - player.x;
const dy = ball.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < player.radius + ball.radius + 5) {
// La CPU toma la pelota
player.hasBall = true;
ball.owner = 'cpu';
ball.speed = 0;
// Quitar la pelota a cualquier otro jugador
cpuTeam.forEach(p => {
if (p !== player) p.hasBall = false;
});
playerTeam.forEach(p => p.hasBall = false);
updateMessage("¡La CPU recupera el balón!");
animatePass();
}
}
});
}
// Colisiones entre jugadores (empujón)
if (!isCelebrating) {
playerTeam.forEach(player => {
cpuTeam.forEach(cpuPlayer => {
const dx = player.x - cpuPlayer.x;
const dy = player.y - cpuPlayer.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < player.radius + cpuPlayer.radius) {
// Empujar a los jugadores separados
const angle = Math.atan2(dy, dx);
const force = 1.5;
player.velocityX += Math.cos(angle) * force;
player.velocityY += Math.sin(angle) * force;
cpuPlayer.velocityX -= Math.cos(angle) * force;
cpuPlayer.velocityY -= Math.sin(angle) * force;
}
});
});
}
}
// Verificar goles
function checkGoal() {
const goalWidth = 10;
const goalHeight = 120;
const goalY = canvas.height / 2 - goalHeight / 2;
// Gol del jugador (portería derecha)
if (ball.x > canvas.width - 25 && ball.x < canvas.width - 15 &&
ball.y > goalY && ball.y < goalY + goalHeight) {
// Verificar si el portero atajó
if (goalkeepers.cpu) {
const dx = ball.x - goalkeepers.cpu.x;
const dy = ball.y - goalkeepers.cpu.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < goalkeepers.cpu.radius + ball.radius + 10) {
// El portero atajó
updateMessage("¡Increíble atajada del portero!");
// Rebotar la pelota
ball.direction = Math.atan2(dy, dx) + Math.PI;
ball.speed = 8 + Math.random() * 4;
ball.owner = null;
// Animación de atajada
animateSave();
return;
}
}
scoreGoal('player');
return;
}
// Gol de la CPU (portería izquierda)
if (ball.x < 25 && ball.x > 15 &&
ball.y > goalY && ball.y < goalY + goalHeight) {
// Verificar si el portero atajó
if (goalkeepers.player) {
const dx = ball.x - goalkeepers.player.x;
const dy = ball.y - goalkeepers.player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < goalkeepers.player.radius + ball.radius + 10) {
// El portero atajó
updateMessage("¡Gran atajada!");
// Rebotar la pelota
ball.direction = Math.atan2(dy, dx) + Math.PI;
ball.speed = 8 + Math.random() * 4;
ball.owner = null;
// Animación de atajada
animateSave();
return;
}
}
scoreGoal('cpu');
return;
}
}
// Verificar si la pelota sale del campo
function checkOutOfBounds() {
// Si la pelota sale por los laterales (no por las porterías)
if ((ball.x <= 20 || ball.x >= canvas.width - 20) &&
(ball.y < canvas.height / 2 - 60 || ball.y > canvas.height / 2 + 60)) {
if (ball.x <= 20) {
// Saque de banda para la CPU
updateMessage("¡Saque de banda para la CPU!");
ball.x = 40;
ball.y = Math.max(60, Math.min(canvas.height - 60, ball.y));
ball.speed = 0;
ball.owner = null;
} else {
// Saque de banda para el jugador
updateMessage("¡Saque de banda para el jugador!");
ball.x = canvas.width - 40;
ball.y = Math.max(60, Math.min(canvas.height - 60, ball.y));
ball.speed = 0;
ball.owner = null;
}
}
// Corner
if (ball.x <= 20 && ball.y >= canvas.height / 2 - 60 && ball.y <= canvas.height / 2 + 60) {
corners++;
updateMessage("¡Corner para la CPU!");
ball.x = 40;
ball.y = Math.random() > 0.5 ? canvas.height / 2 - 50 : canvas.height / 2 + 50;
ball.speed = 0;
ball.owner = null;
} else if (ball.x >= canvas.width - 20 && ball.y >= canvas.height / 2 - 60 && ball.y <= canvas.height / 2 + 60) {
corners++;
updateMessage("¡Corner para el jugador!");
ball.x = canvas.width - 40;
ball.y = Math.random() > 0.5 ? canvas.height / 2 - 50 : canvas.height / 2 + 50;
ball.speed = 0;
ball.owner = null;
}
}
// Marcar gol
function scoreGoal(team) {
if (isCelebrating) return;
goalScorer = team === 'player' ?
playerTeam.find(p => p.hasBall)?.name || "Un jugador" :
cpuTeam.find(p => p.hasBall)?.name || "Un jugador";
if (team === 'player') {
gameData.score.player++;
updateMessage(`¡GOOOOOOL! ${goalScorer} marca para ${gameData.selectedTeam}!`);
lastGoalBy = 'player';
} else {
gameData.score.cpu++;
updateMessage(`¡GOOOOOOL! ${goalScorer} marca para ${gameData.selectedOpponent}!`);
lastGoalBy = 'cpu';
}
updateScoreboard();
// Efecto visual en el marcador
const playerScore = document.getElementById('player-score');
const cpuScore = document.getElementById('cpu-score');
playerScore.classList.add('goal-animation');
cpuScore.classList.add('goal-animation');
setTimeout(() => {
playerScore.classList.remove('goal-animation');
cpuScore.classList.remove('goal-animation');
}, 800);
// Iniciar celebración (solo si no es gol en contra)
if ((team === 'player' && lastGoalBy === 'player') ||
(team === 'cpu' && lastGoalBy === 'cpu')) {
startCelebration(team);
} else {
updateMessage("Gol en contra... sin celebración");
}
// Verificar eliminación en copas
if (gameData.tournament.active) {
if (gameData.score.player >= 5 || gameData.score.cpu >= 5) {
// El partido termina inmediatamente
return;
}
}
setTimeout(() => resetAfterGoal(), 2000);
}
// Iniciar celebración
function startCelebration(team) {
isCelebrating = true;
celebratingTeam = team;
celebrationTimer = 0;
// Posicionar jugadores para la celebración
if (team === 'player') {
const celebrationX = canvas.width * 0.3;
const celebrationY = canvas.height / 2;
playerTeam.forEach(player => {
player.targetX = celebrationX + (Math.random() * 80 - 40);
player.targetY = celebrationY + (Math.random() * 80 - 40);
player.isMoving = true;
});
} else {
const celebrationX = canvas.width * 0.7;
const celebrationY = canvas.height / 2;
cpuTeam.forEach(player => {
player.targetX = celebrationX + (Math.random() * 80 - 40);
player.targetY = celebrationY + (Math.random() * 80 - 40);
player.isMoving = true;
});
}
}
// Actualizar celebración
function updateCelebration() {
celebrationTimer += 1/60;
// La celebración dura 2 segundos
if (celebrationTimer >= 2) {
isCelebrating = false;
celebratingTeam = null;
}
}
// Actualizar marcador
function updateScoreboard() {
document.getElementById('player-score').textContent = gameData.score.player;
document.getElementById('cpu-score').textContent = gameData.score.cpu;
}
// Reiniciar después de un gol
function resetAfterGoal() {
// Colocar la pelota en el centro
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
ball.owner = null;
ball.speed = 0;
ballTrail = [];
// Quitar la pelota a todos los jugadores
playerTeam.forEach(player => player.hasBall = false);
cpuTeam.forEach(player => player.hasBall = false);
// Dar la pelota al equipo que no hizo gol
if (lastGoalBy === 'player') {
// La CPU saca
const cpuPlayer = cpuTeam.find(p => !p.isGoalkeeper);
if (cpuPlayer) {
cpuPlayer.hasBall = true;
ball.owner = 'cpu';
ball.x = cpuPlayer.x - 30;
ball.y = cpuPlayer.y;
}
} else {
// El jugador saca
const player = playerTeam.find(p => !p.isGoalkeeper);
if (player) {
player.hasBall = true;
ball.owner = 'player';
ball.x = player.x + 30;
ball.y = player.y;
activePlayer = player;
}
}
// Volver a posiciones iniciales
resetPlayerPositions();
updateMessage("¡Reanudación del partido!");
}
// Resetear posiciones de los jugadores
function resetPlayerPositions() {
playerTeam.forEach((player, i) => {
player.x = player.preferredPosition.x;
player.y = player.preferredPosition.y;
player.velocityX = 0;
player.velocityY = 0;
});
cpuTeam.forEach((player, i) => {
player.x = player.preferredPosition.x;
player.y = player.preferredPosition.y;
player.velocityX = 0;
player.velocityY = 0;
});
}
// Ganar partido de torneo
function winTournamentMatch() {
gameData.tournament.eliminated = false;
updateMessage(`¡${gameData.selectedTeam} avanza a la siguiente ronda!`);
// Actualizar bracket
const bracket = gameData.tournament.bracket[gameData.tournament.currentRound];
if (bracket) {
bracket.winner = gameData.selectedTeam;
bracket.played = true;
}
// Avanzar a siguiente ronda o ganar torneo
gameData.tournament.currentRound++;
if (gameData.tournament.currentRound * 2 < gameData.tournament.bracket.length) {
// Hay más rondas
setTimeout(() => {
alert(`¡Has ganado!\n\nPasas a la siguiente ronda del ${gameData.competitions[gameData.selectedCompetition].name}.`);
// Aquí podrías continuar con el siguiente partido
endMatch();
}, 2000);
} else {
// Ganó el torneo
setTimeout(() => {
alert(`¡CAMPEÓN!\n\nHas ganado el ${gameData.competitions[gameData.selectedCompetition].name} con ${gameData.selectedTeam}!`);
endMatch();
}, 2000);
}
}
// Perder partido de torneo
function loseTournamentMatch() {
gameData.tournament.eliminated = true;
updateMessage(`¡${gameData.selectedTeam} ha sido eliminado!`);
// Actualizar bracket
const bracket = gameData.tournament.bracket[gameData.tournament.currentRound];
if (bracket) {
bracket.winner = gameData.selectedOpponent;
bracket.played = true;
}
setTimeout(() => {
alert(`Eliminado\n\n${gameData.selectedTeam} ha sido eliminado del ${gameData.competitions[gameData.selectedCompetition].name}.`);
endMatch();
}, 2000);
}
// Finalizar partido
function endMatch() {
gameActive = false;
gameData.gameState = 'finished';
let resultMessage = "";
if (gameData.score.player > gameData.score.cpu) {
resultMessage = `¡Victoria del ${gameData.selectedTeam}! ${gameData.score.player} - ${gameData.score.cpu}`;
} else if (gameData.score.player < gameData.score.cpu) {
resultMessage = `Derrota contra ${gameData.selectedOpponent}. ${gameData.score.player} - ${gameData.score.cpu}`;
} else {
resultMessage = `Empate. ¡Buen partido! ${gameData.score.player} - ${gameData.score.cpu}`;
}
updateMessage("¡Fin del partido! " + resultMessage);
setTimeout(() => {
alert(`Partido finalizado\n\n${gameData.selectedTeam} ${gameData.score.player} - ${gameData.score.cpu} ${gameData.selectedOpponent}\n\n${resultMessage}`);
stopGame();
showScreen('title');
resetGameData();
}, 2000);
}
// Pasar la pelota
function passBall() {
if (!activePlayer || !activePlayer.hasBall || isCelebrating) return;
// Encontrar el jugador más cercano en línea de pase
let closestTeammate = null;
let closestDistance = Infinity;
playerTeam.forEach(player => {
if (player !== activePlayer && !player.isGoalkeeper) {
const dx = player.x - activePlayer.x;
const dy = player.y - activePlayer.y;
const distance = Math.sqrt(dx*dx + dy*dy);
// Verificar que esté dentro de un rango razonable
if (distance < 250 && distance < closestDistance) {
closestTeammate = player;
closestDistance = distance;
}
}
});
if (closestTeammate) {
// Calcular dirección del pase
const dx = closestTeammate.x - activePlayer.x;
const dy = closestTeammate.y - activePlayer.y;
const distance = Math.sqrt(dx*dx + dy*dy);
const direction = Math.atan2(dy, dx);
// Pasar la pelota
activePlayer.hasBall = false;
closestTeammate.hasBall = true;
activePlayer = closestTeammate;
ball.owner = 'player';
ball.speed = 12; // Velocidad realista
ball.direction = direction;
ball.rotationSpeed = 0.2;
// Animación de pase
animatePass();
updateMessage("¡Pase preciso!");
} else {
updateMessage("¡No hay compañeros para pasar!");
}
}
// Pasar la pelota (CPU)
function passBallCPU(player) {
if (!player || !player.hasBall || isCelebrating) return;
// Encontrar un compañero
const teammates = cpuTeam.filter(p => p !== player && !p.isGoalkeeper);
if (teammates.length > 0) {
const teammate = teammates[Math.floor(Math.random() * teammates.length)];
const dx = teammate.x - player.x;
const dy = teammate.y - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
const direction = Math.atan2(dy, dx);
player.hasBall = false;
teammate.hasBall = true;
ball.owner = 'cpu';
ball.speed = 12;
ball.direction = direction;
ball.rotationSpeed = 0.2;
animatePass();
updateMessage("¡La CPU pasa el balón!");
}
}
// Chutar la pelota
function shootBall(pressDuration) {
if (!activePlayer || !activePlayer.hasBall || isCelebrating) return;
// Calcular potencia del chute (más realista)
const power = Math.min(pressDuration / 600, 1.5);
// Dirección hacia la portería rival (con algo de aleatoriedad)
const goalX = canvas.width - 30;
const goalY = canvas.height / 2 + (Math.random() * 60 - 30);
const dx = goalX - activePlayer.x;
const dy = goalY - activePlayer.y;
const distance = Math.sqrt(dx*dx + dy*dy);
// Soltar la pelota
activePlayer.hasBall = false;
ball.owner = null;
// Aplicar velocidad a la pelota (más realista)
ball.speed = 15 + power * 10; // Entre 15 y 25
ball.direction = Math.atan2(dy, dx) + (Math.random() * 0.2 - 0.1);
ball.rotationSpeed = 0.3;
animateShot();
updateMessage("¡Chute al arco!");
}
// Chutar la pelota (CPU)
function shootBallCPU(player) {
if (!player || !player.hasBall || isCelebrating) return;
// Dirección hacia la portería del jugador
const goalX = 30;
const goalY = canvas.height / 2 + (Math.random() * 80 - 40);
const dx = goalX - player.x;
const dy = goalY - player.y;
const distance = Math.sqrt(dx*dx + dy*dy);
// Soltar la pelota
player.hasBall = false;
ball.owner = null;
// Aplicar velocidad a la pelota
ball.speed = 18 + Math.random() * 8; // Entre 18 y 26
ball.direction = Math.atan2(dy, dx);
ball.rotationSpeed = 0.25;
animateShot();
updateMessage("¡La CPU chuta al arco!");
}
// Animar pase
function animatePass() {
const passElements = document.querySelectorAll('.team-score');
passElements.forEach(el => {
el.classList.add('pass-animation');
setTimeout(() => el.classList.remove('pass-animation'), 300);
});
}
// Animar chute
function animateShot() {
const shotElements = document.querySelectorAll('.team-score');
shotElements.forEach(el => {
el.classList.add('shot-animation');
setTimeout(() => el.classList.remove('shot-animation'), 400);
});
}
// Animar atajada
function animateSave() {
updateMessage("¡Gran atajada del portero!");
}
// Iniciar tanda de penales
function startPenaltyShootout() {
gameData.penalties.active = true;
gameData.penalties.playerScore = 0;
gameData.penalties.cpuScore = 0;
gameData.penalties.currentAttempt = 0;
gameData.penalties.penaltyInProgress = false;
// Mostrar modal de penales
document.getElementById('penalty-modal').classList.remove('hidden');
document.getElementById('penalty-score').textContent =
`Jugador: ${gameData.penalties.playerScore} - ${gameData.penalties.cpuScore} :CPU`;
updateMessage("¡Tanda de penales!");
}
// Actualizar penales
function updatePenalties() {
if (!gameData.penalties.active || !gameData.penalties.penaltyInProgress) return;
// Actualizar barra de potencia
gameData.penalties.penaltyTimer += gameData.penalties.penaltySpeed;
const fillPercentage = (Math.sin(gameData.penalties.penaltyTimer) + 1) / 2 * 100;
document.getElementById('penalty-fill').style.width = fillPercentage + '%';
}
// Comenzar tiro penal
function startPenaltyShot() {
if (gameData.penalties.currentAttempt >= gameData.penalties.maxAttempts * 2) {
// Fin de la tanda
endPenaltyShootout();
return;
}
gameData.penalties.penaltyInProgress = true;
gameData.penalties.penaltyTimer = Math.random() * Math.PI * 2;
gameData.penalties.penaltySpeed = 0.08 + Math.random() * 0.04;
document.getElementById('start-penalty').textContent = 'Presiona ESPACIO para tirar';
document.getElementById('penalty-instructions').textContent =
gameData.penalties.currentAttempt % 2 === 0 ?
'Tu turno: presiona ESPACIO cuando la barra esté en la zona dorada' :
'Turno de la CPU';
// Si es turno de la CPU, tirar automáticamente después de un tiempo
if (gameData.penalties.currentAttempt % 2 === 1) {
setTimeout(() => {
executeCPUPenalty();
}, 1500 + Math.random() * 1000);
}
}
// Ejecutar tiro penal del jugador
function executePenaltyShot() {
if (!gameData.penalties.penaltyInProgress) return;
gameData.penalties.penaltyInProgress = false;
// Calcular precisión
const fillPercentage = (Math.sin(gameData.penalties.penaltyTimer) + 1) / 2 * 100;
const targetZone = 40; // Zona dorada en el centro (30-70%)
const isInZone = fillPercentage > 30 && fillPercentage < 70;
// Determinar si fue gol
let isGoal = false;
if (isInZone) {
// En la zona dorada = gol seguro
isGoal = true;
} else {
// Fuera de la zona = probabilidad más baja
const distanceFromCenter = Math.abs(fillPercentage - 50);
isGoal = Math.random() > (distanceFromCenter / 50);
}
// Actualizar marcador
if (isGoal) {
gameData.penalties.playerScore++;
updateMessage("¡Gol en el penal!");
} else {
updateMessage("¡Falló el penal!");
}
document.getElementById('penalty-score').textContent =
`Jugador: ${gameData.penalties.playerScore} - ${gameData.penalties.cpuScore} :CPU`;
// Siguiente intento
gameData.penalties.currentAttempt++;
if (gameData.penalties.currentAttempt < gameData.penalties.maxAttempts * 2) {
setTimeout(() => startPenaltyShot(), 2000);
} else {
setTimeout(() => endPenaltyShootout(), 2000);
}
}
// Ejecutar tiro penal de la CPU
function executeCPUPenalty() {
if (!gameData.penalties.penaltyInProgress) return;
gameData.penalties.penaltyInProgress = false;
// La CPU tiene probabilidad según la dificultad
const difficulty = gameData.selectedDifficulty ?
gameData.selectedDifficulty.aiAccuracy : 0.7;
const isGoal = Math.random() < difficulty;
// Actualizar marcador
if (isGoal) {
gameData.penalties.cpuScore++;
updateMessage("La CPU anota el penal");
} else {
updateMessage("¡La CPU falla el penal!");
}
document.getElementById('penalty-score').textContent =
`Jugador: ${gameData.penalties.playerScore} - ${gameData.penalties.cpuScore} :CPU`;
// Siguiente intento
gameData.penalties.currentAttempt++;
if (gameData.penalties.currentAttempt < gameData.penalties.maxAttempts * 2) {
setTimeout(() => startPenaltyShot(), 2000);
} else {
setTimeout(() => endPenaltyShootout(), 2000);
}
}
// Finalizar tanda de penales
function endPenaltyShootout() {
gameData.penalties.active = false;
document.getElementById('penalty-modal').classList.add('hidden');
let winner = null;
if (gameData.penalties.playerScore > gameData.penalties.cpuScore) {
winner = 'player';
updateMessage(`¡${gameData.selectedTeam} gana en penales!`);
} else if (gameData.penalties.cpuScore > gameData.penalties.playerScore) {
winner = 'cpu';
updateMessage(`¡${gameData.selectedOpponent} gana en penales!`);
} else {
// Empate en penales - muerte súbita
updateMessage("¡Empate en penales! Muerte súbita...");
// En una implementación completa, aquí continuaría la muerte súbita
setTimeout(() => {
// Por simplicidad, el jugador gana en muerte súbita
alert(`¡${gameData.selectedTeam} gana en muerte súbita!\n\nPenales: ${gameData.penalties.playerScore} - ${gameData.penalties.cpuScore}`);
endMatch();
}, 2000);
return;
}
setTimeout(() => {
alert(`Final de penales\n\n${gameData.selectedTeam} ${gameData.penalties.playerScore} - ${gameData.penalties.cpuScore} ${gameData.selectedOpponent}\n\n${winner === 'player' ? '¡Ganaste!' : 'Perdiste'}`);
endMatch();
}, 2000);
}
// Actualizar mensaje
function updateMessage(text) {
const messageElement = document.getElementById('message-text');
messageElement.textContent = text;
const panel = document.getElementById('message-panel');
panel.classList.add('pulse');
setTimeout(() => panel.classList.remove('pulse'), 300);
}
// Mensaje aleatorio
function randomMessage() {
const randomIndex = Math.floor(Math.random() * gameData.messages.length);
updateMessage(gameData.messages[randomIndex]);
}
</script>
</body>
</html>2
2
427KB
426KB
115.0ms
204.0ms
197.0ms