// ==UserScript==
// @name Drawaria Pac-Man Game
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Recreación precisa del Pac-Man clásico en el canvas de Drawaria.online, con laberinto, fantasmas, puntos, y un panel de control arrastrable.
// @author YouTubeDrawaria
// @match https://drawaria.online/*
// @grant none
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==
(function() {
'use strict';
/* ---------- COMPONENTES COMPARTIDOS DEL SISTEMA ---------- */
let drawariaSocket = null;
let drawariaCanvas = null;
let drawariaCtx = null;
const commandQueue = [];
let batchProcessor = null;
const BATCH_SIZE = 8;
const BATCH_INTERVAL = 60; // 60ms para un balance entre rendimiento y fluidez
const originalWebSocketSend = WebSocket.prototype.send;
WebSocket.prototype.send = function(...args) {
if (!drawariaSocket && this.url && this.url.includes('drawaria')) {
drawariaSocket = this;
console.log('🔗 Drawaria WebSocket capturado para el juego de Pac-Man.');
startBatchProcessor();
}
return originalWebSocketSend.apply(this, args);
};
function startBatchProcessor() {
if (batchProcessor) return;
batchProcessor = setInterval(() => {
if (!drawariaSocket || drawariaSocket.readyState !== WebSocket.OPEN || commandQueue.length === 0) {
return;
}
const batch = commandQueue.splice(0, BATCH_SIZE);
batch.forEach(cmd => {
try {
drawariaSocket.send(cmd);
} catch (e) {
console.warn('⚠️ Fallo al enviar el comando:', e);
}
});
}, BATCH_INTERVAL);
}
/**
* Dibuja una línea o forma rellena. Asegura la retroalimentación visual local.
*/
function enqueueDrawCommand(x1, y1, x2, y2, color, thickness, isArc = false, startAngle = 0, endAngle = 0) {
if (!drawariaCanvas || !drawariaSocket) return;
// Renderizado local para retroalimentación visual inmediata
if (drawariaCtx) {
drawariaCtx.fillStyle = color;
drawariaCtx.strokeStyle = color;
drawariaCtx.lineWidth = thickness;
drawariaCtx.lineCap = 'round';
drawariaCtx.lineJoin = 'round';
if (isArc) {
drawariaCtx.beginPath();
drawariaCtx.arc(x1, y1, thickness / 2, startAngle, endAngle);
drawariaCtx.lineTo(x1, y1); // Para cerrar el Pac-Man
drawariaCtx.fill();
} else {
drawariaCtx.beginPath();
drawariaCtx.moveTo(x1, y1);
drawariaCtx.lineTo(x2, y2);
if (thickness < 0) { // Para formas rellenas
drawariaCtx.lineCap = 'butt';
drawariaCtx.stroke();
} else { // Para líneas
drawariaCtx.stroke();
}
}
}
// Comando para Drawaria.online
const normX1 = (x1 / drawariaCanvas.width).toFixed(4);
const normY1 = (y1 / drawariaCanvas.height).toFixed(4);
const normX2 = (x2 / drawariaCanvas.width).toFixed(4);
const normY2 = (y2 / drawariaCanvas.height).toFixed(4);
let cmd;
if (isArc) {
// Para arcos, el "thickness" se usa como radio. Esto es una simplificación
// y puede no ser exacto con el comando nativo de Drawaria sin más manipulación del lienzo.
// Para el propósito actual, enviamos una forma rellena simulando un círculo.
cmd = `42["drawcmd",0,[${normX1},${normY1},${normX1},${normY1},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`; // Simula un círculo rellenando un punto grande
} else {
cmd = `42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`;
}
commandQueue.push(cmd);
}
/* ---------- LÓGICA DEL JUEGO DE PAC-MAN ---------- */
class PacmanGame {
constructor() {
this.isActive = false;
this.cellSize = 16; // Tamaño de celda ajustado para el mapa
this.pacman = { x: 0, y: 0, direction: 'right', nextDirection: 'right', frame: 0, speed: 2 };
this.ghost = { x: 0, y: 0, direction: 'left', speed: 2, frightened: false, frightenedTimer: 0 };
this.dots = []; // Pequeños puntos
this.powerPills = []; // Píldoras de poder (puntos grandes)
this.score = 0;
this.gameInterval = null;
this.animationFrame = 0; // Para animación de Pac-Man y fantasmas
this.isGameOver = false;
this.gameEndedMessage = '';
// Mapa del laberinto (0: vacío, 1: pared, 2: punto, 3: píldora de poder)
// Este mapa es una adaptación del original de Pac-Man
this.map = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
[1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1],
[1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1],
[1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1],
[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
[1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1],
[1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1],
[1, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1], // Zona de fantasmas
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], // Túnel
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1],
[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
[1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1],
[1, 3, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 0, 0, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 3, 1],
[1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1],
[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
this.mapWidth = this.map[0].length;
this.mapHeight = this.map.length;
this.init();
}
init() {
const checkGameReady = () => {
const gameCanvas = document.getElementById('canvas');
if (gameCanvas) {
drawariaCanvas = gameCanvas;
drawariaCtx = gameCanvas.getContext('2d');
// Aseguramos que el canvas tenga un tamaño manejable para nuestro mapa
if (drawariaCanvas.width < this.mapWidth * this.cellSize || drawariaCanvas.height < this.mapHeight * this.cellSize) {
console.warn("⚠️ Canvas de Drawaria es más pequeño de lo esperado. El mapa podría no verse completo.");
// Ajustar cellSize si el canvas es muy pequeño para que el mapa quepa.
this.cellSize = Math.min(drawariaCanvas.width / this.mapWidth, drawariaCanvas.height / this.mapHeight);
console.log(`Cambiando cellSize a ${this.cellSize} para adaptarse al canvas.`);
}
this.createGamePanel();
this.resetGamePositions(); // Establecer posiciones iniciales
this.generateDotsAndPowerPills(); // Generar puntos y píldoras
console.log('✅ Juego de Pac-Man inicializado.');
} else {
setTimeout(checkGameReady, 100);
}
};
checkGameReady();
}
createGamePanel() {
const existingPanel = document.getElementById('pacman-game-panel');
if (existingPanel) existingPanel.remove();
const panel = document.createElement('div');
panel.id = 'pacman-game-panel';
panel.style.cssText = `
position: fixed !important;
top: 20px !important;
right: 20px !important;
width: 250px !important;
z-index: 2147483647 !important;
background: linear-gradient(135deg, #000033, #00001a) !important;
border: 2px solid #FFD700 !important;
border-radius: 12px !important;
color: white !important;
font-family: 'Press Start 2P', 'Segoe UI', Arial, sans-serif !important;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3) !important;
padding: 15px !important;
text-align: center !important;
`;
panel.innerHTML = `
<h3 style="margin-top: 0; color: #FFD700;">🟡 Pac-Man</h3>
<div style="margin-bottom: 10px;">
Score: <span id="pacman-score">0</span>
</div>
<button id="draw-maze-btn" style="
width: 100%;
padding: 10px;
background: #2196F3;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
transition: background 0.3s ease;
">🖼️ Dibuja el Laberinto</button>
<button id="toggle-game" style="
width: 100%;
padding: 10px;
background: #FFD700;
color: black;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: background 0.3s ease;
">▶️ Iniciar Juego</button>
<div id="game-message" style="
margin-top: 10px;
color: #ff4d4d;
font-weight: bold;
display: none;
"></div>
<div style="
margin-top: 15px;
font-size: 10px;
color: rgba(255,255,255,0.6);
">
Usa las teclas de flecha para moverte.<br>
Evita al fantasma y come todos los puntos.
</div>
`;
document.body.appendChild(panel);
this.setupEventListeners();
this.makePanelDraggable(panel);
// Efectos hover
document.getElementById('draw-maze-btn').onmouseover = function() { this.style.background = '#1a75c2'; };
document.getElementById('draw-maze-btn').onmouseout = function() { this.style.background = '#2196F3'; };
document.getElementById('toggle-game').onmouseover = function() { this.style.background = '#e6b800'; };
document.getElementById('toggle-game').onmouseout = function() { this.style.background = '#FFD700'; };
}
setupEventListeners() {
document.getElementById('toggle-game').addEventListener('click', () => this.toggleGame());
document.getElementById('draw-maze-btn').addEventListener('click', () => {
this.drawMaze(true); // Dibuja y fuerza el borrado inicial
this.drawDotsAndPowerPills(); // Dibuja todos los puntos
this.drawPacman(); // Dibuja a Pac-Man en su posición inicial
this.drawGhost(); // Dibuja al fantasma en su posición inicial
});
document.addEventListener('keydown', (e) => this.handleKeyInput(e));
}
toggleGame() {
if (!this.isActive) {
this.startGame();
document.getElementById('toggle-game').textContent = '⏸️ Pausar Juego';
document.getElementById('toggle-game').style.background = '#ffc107';
document.getElementById('game-message').style.display = 'none';
} else {
this.pauseGame();
document.getElementById('toggle-game').textContent = '▶️ Reanudar Juego';
document.getElementById('toggle-game').style.background = '#FFD700';
}
}
startGame() {
if (this.isActive && !this.isGameOver) return;
this.isActive = true;
this.isGameOver = false;
this.score = 0;
this.updateScoreDisplay();
this.resetGamePositions();
this.generateDotsAndPowerPills();
this.drawMaze(true); // Dibuja el laberinto y lo limpia para el juego
this.gameLoop();
}
pauseGame() {
this.isActive = false;
if (this.gameInterval) clearInterval(this.gameInterval);
this.gameInterval = null;
}
endGame(message) {
this.isGameOver = true;
this.pauseGame();
document.getElementById('toggle-game').textContent = '🔄 Reiniciar Juego';
document.getElementById('toggle-game').style.background = '#cc0000';
const messageEl = document.getElementById('game-message');
messageEl.textContent = `¡${message}! Puntos: ${this.score}`;
messageEl.style.display = 'block';
console.log(`💀 ¡Juego Terminado! ${message}. Puntos: ${this.score}`);
}
resetGamePositions() {
// Posiciones iniciales basadas en el mapa
this.pacman.x = 1.5 * this.cellSize;
this.pacman.y = 1.5 * this.cellSize;
this.pacman.direction = 'right';
this.pacman.nextDirection = 'right';
this.pacman.frame = 0;
this.ghost.x = 13.5 * this.cellSize; // Central en el área de fantasmas
this.ghost.y = 12.5 * this.cellSize;
this.ghost.direction = 'left';
this.ghost.frightened = false;
this.ghost.frightenedTimer = 0;
}
generateDotsAndPowerPills() {
this.dots = [];
this.powerPills = [];
for (let y = 0; y < this.mapHeight; y++) {
for (let x = 0; x < this.mapWidth; x++) {
const centerX = (x + 0.5) * this.cellSize;
const centerY = (y + 0.5) * this.cellSize;
if (this.map[y][x] === 2) {
this.dots.push({ x: centerX, y: centerY, eaten: false });
} else if (this.map[y][x] === 3) {
this.powerPills.push({ x: centerX, y: centerY, eaten: false });
}
}
}
}
gameLoop() {
if (!this.isActive || this.isGameOver) return;
this.updateGame();
this.drawGame();
this.gameInterval = setTimeout(() => this.gameLoop(), 100); // Velocidad del juego
}
updateGame() {
// Animación de Pac-Man
this.pacman.frame = (this.pacman.frame + 1) % 2; // Alterna entre 0 y 1
// Mover Pac-Man
this.moveCharacter(this.pacman);
// Mover Fantasma (lógica simple de persecución/aleatoria)
this.moveGhostAI();
// Actualizar estado de miedo del fantasma
if (this.ghost.frightened && this.ghost.frightenedTimer > 0) {
this.ghost.frightenedTimer--;
if (this.ghost.frightenedTimer === 0) {
this.ghost.frightened = false;
}
}
// Verificar colisión con puntos
this.checkDotCollision();
// Verificar colisión con píldoras de poder
this.checkPowerPillCollision();
// Verificar colisión con fantasma
if (this.checkCollision(this.pacman, this.ghost, this.cellSize * 0.7)) {
if (this.ghost.frightened) {
// Pac-Man come al fantasma
this.score += 200; // Puntos por comer fantasma
this.updateScoreDisplay();
this.resetGhostPosition(); // El fantasma regresa a su punto de inicio
this.ghost.frightened = false; // Ya no está asustado
this.ghost.frightenedTimer = 0;
} else {
this.endGame('Fuiste comido por el fantasma');
}
}
// Comprobar si todos los puntos han sido comidos
if (this.dots.every(d => d.eaten) && this.powerPills.every(p => p.eaten)) {
this.endGame('¡Ganaste!');
}
}
drawGame() {
// Borra solo las entidades que se mueven, no el laberinto
this.clearMovingEntities();
this.drawDotsAndPowerPills(); // Para redibujar los puntos no comidos
this.drawPacman();
this.drawGhost();
}
clearCanvas() {
enqueueDrawCommand(0, 0, drawariaCanvas.width, drawariaCanvas.height, '#000000', Math.max(1400, 1400));
}
clearMovingEntities() {
const clearRadius = this.cellSize * 1.5; // Un radio mayor para asegurar la limpieza
enqueueDrawCommand(this.ghost.x, this.ghost.y, this.ghost.x, this.ghost.y, '#000000', clearRadius);
enqueueDrawCommand(this.pacman.x, this.pacman.y, this.pacman.x, this.pacman.y, '#000000', clearRadius);
}
drawMaze(clearFirst = false) {
if (clearFirst) {
this.clearCanvas();
// Retraso para que Drawaria procese el borrado antes de dibujar el laberinto
setTimeout(() => this._drawMazeElements(), 50);
} else {
this._drawMazeElements();
}
}
_drawMazeElements() {
const wallColor = '#2196F3'; // Azul clásico de Pac-Man
const wallThickness = 2; // Grosor de la línea para las paredes
for (let y = 0; y < this.mapHeight; y++) {
for (let x = 0; x < this.mapWidth; x++) {
const currentCell = this.map[y][x];
if (currentCell === 1) { // Es una pared
const px = x * this.cellSize;
const py = y * this.cellSize;
// Dibujar las líneas de pared individualmente para hacer un laberinto
// Arriba
if (y === 0 || this.map[y - 1][x] !== 1) {
enqueueDrawCommand(px, py, px + this.cellSize, py, wallColor, wallThickness);
}
// Abajo
if (y === this.mapHeight - 1 || this.map[y + 1][x] !== 1) {
enqueueDrawCommand(px, py + this.cellSize, px + this.cellSize, py + this.cellSize, wallColor, wallThickness);
}
// Izquierda
if (x === 0 || this.map[y][x - 1] !== 1) {
enqueueDrawCommand(px, py, px, py + this.cellSize, wallColor, wallThickness);
}
// Derecha
if (x === this.mapWidth - 1 || this.map[y][x + 1] !== 1) {
enqueueDrawCommand(px + this.cellSize, py, px + this.cellSize, py + this.cellSize, wallColor, wallThickness);
}
}
}
}
console.log('✅ Laberinto dibujado.');
}
drawDotsAndPowerPills() {
const dotColor = '#FFFFFF';
const powerPillColor = '#FFFFFF';
this.dots.forEach(dot => {
if (!dot.eaten) {
enqueueDrawCommand(dot.x, dot.y, dot.x, dot.y, dotColor, 4); // Punto pequeño
}
});
this.powerPills.forEach(pill => {
if (!pill.eaten) {
enqueueDrawCommand(pill.x, pill.y, pill.x, pill.y, powerPillColor, 8); // Punto grande
}
});
console.log('✅ Puntos y píldoras dibujados.');
}
drawPacman() {
const pacmanRadius = this.cellSize * 0.4; // Radio para Pac-Man
const x = this.pacman.x;
const y = this.pacman.y;
const color = '#FFD700'; // Amarillo Pac-Man
let startAngle = 0;
let endAngle = 2 * Math.PI; // Círculo completo
let mouthAngle = Math.PI / 6 * this.pacman.frame; // Apertura de boca
// Ajustar el ángulo de la boca según la dirección
if (this.pacman.direction === 'right') {
startAngle = mouthAngle;
endAngle = 2 * Math.PI - mouthAngle;
} else if (this.pacman.direction === 'left') {
startAngle = Math.PI + mouthAngle;
endAngle = Math.PI - mouthAngle;
} else if (this.pacman.direction === 'up') {
startAngle = 1.5 * Math.PI + mouthAngle;
endAngle = 1.5 * Math.PI - mouthAngle;
} else if (this.pacman.direction === 'down') {
startAngle = 0.5 * Math.PI + mouthAngle;
endAngle = 0.5 * Math.PI - mouthAngle;
}
// Dibuja Pac-Man como un arco relleno
enqueueDrawCommand(x, y, x, y, color, pacmanRadius * 2, true, startAngle, endAngle);
}
drawGhost() {
const ghostBodyRadius = this.cellSize * 0.4;
const ghostLegRadius = this.cellSize * 0.15;
const x = this.ghost.x;
const y = this.ghost.y;
const color = this.ghost.frightened ? '#0000FF' : '#FF4D4D'; // Azul si está asustado, rojo normal
// Cuerpo del fantasma (semi-círculo superior + rectángulo inferior)
// Círculo superior
enqueueDrawCommand(x, y - ghostBodyRadius / 2, x, y - ghostBodyRadius / 2, color, ghostBodyRadius * 2, true, Math.PI, 0);
// Rectángulo inferior (simulado con línea gruesa)
enqueueDrawCommand(x - ghostBodyRadius, y - ghostBodyRadius / 2, x + ghostBodyRadius, y + ghostBodyRadius, color, ghostBodyRadius);
// Pies del fantasma (simulados con círculos pequeños)
enqueueDrawCommand(x - ghostBodyRadius * 0.7, y + ghostBodyRadius, x - ghostBodyRadius * 0.7, y + ghostBodyRadius, color, ghostLegRadius * 2);
enqueueDrawCommand(x, y + ghostBodyRadius, x, y + ghostBodyRadius, color, ghostLegRadius * 2);
enqueueDrawCommand(x + ghostBodyRadius * 0.7, y + ghostBodyRadius, x + ghostBodyRadius * 0.7, y + ghostBodyRadius, color, ghostLegRadius * 2);
}
moveCharacter(character) {
// Intentar mover en la dirección deseada primero (si es Pac-Man)
if (character === this.pacman && character.direction !== character.nextDirection) {
if (this.canMove(character.x, character.y, character.nextDirection, character.speed)) {
character.direction = character.nextDirection;
}
}
// Mover en la dirección actual
let newX = character.x;
let newY = character.y;
if (character.direction === 'up') newY -= character.speed;
else if (character.direction === 'down') newY += character.speed;
else if (character.direction === 'left') newX -= character.speed;
else if (character.direction === 'right') newX += character.speed;
// Verificar colisiones con paredes antes de actualizar la posición
if (this.canMove(character.x, character.y, character.direction, character.speed)) {
character.x = newX;
character.y = newY;
}
// Manejar teletransporte en los túneles laterales
if (character.x < 0) {
character.x = this.mapWidth * this.cellSize;
} else if (character.x > this.mapWidth * this.cellSize) {
character.x = 0;
}
}
canMove(x, y, direction, speed) {
const nextCellX = Math.floor((x + (direction === 'right' ? speed : direction === 'left' ? -speed : 0)) / this.cellSize);
const nextCellY = Math.floor((y + (direction === 'down' ? speed : direction === 'up' ? -speed : 0)) / this.cellSize);
// Asegurarse de que las coordenadas estén dentro de los límites del mapa
if (nextCellX < 0 || nextCellX >= this.mapWidth || nextCellY < 0 || nextCellY >= this.mapHeight) {
// Permitir movimiento a través del túnel (coordenadas 0, 14 y 27, 14 del mapa)
if ((y / this.cellSize >= 14 && y / this.cellSize < 15) && (nextCellX < 0 || nextCellX >= this.mapWidth)) {
return true;
}
return false; // Colisión con el límite del canvas si no es el túnel
}
const currentMapValue = this.map[nextCellY][nextCellX];
return currentMapValue !== 1; // No se puede mover si la siguiente celda es una pared
}
moveGhostAI() {
// Implementación simple: el fantasma se mueve aleatoriamente o hacia Pac-Man
const targetX = this.pacman.x;
const targetY = this.pacman.y;
const possibleDirections = [];
const currentMapX = Math.floor(this.ghost.x / this.cellSize);
const currentMapY = Math.floor(this.ghost.y / this.cellSize);
// Probar todas las direcciones posibles que no sean paredes o la dirección opuesta
['up', 'down', 'left', 'right'].forEach(dir => {
let checkX = currentMapX;
let checkY = currentMapY;
if (dir === 'up') checkY--;
else if (dir === 'down') checkY++;
else if (dir === 'left') checkX--;
else if (dir === 'right') checkX++;
// Asegurar que no se salga del mapa antes de verificar la pared
if (checkX >= 0 && checkX < this.mapWidth && checkY >= 0 && checkY < this.mapHeight && this.map[checkY][checkX] !== 1) {
// Evitar invertir la dirección a menos que sea un callejón sin salida
const oppositeDir = { 'up': 'down', 'down': 'up', 'left': 'right', 'right': 'left' }[dir];
if (this.ghost.direction !== oppositeDir || this.isDeadEnd(currentMapX, currentMapY, dir)) {
possibleDirections.push(dir);
}
}
});
let bestDirection = this.ghost.direction;
let minDistance = Infinity;
if (possibleDirections.length > 0) {
if (this.ghost.frightened) {
// Si está asustado, se mueve en dirección opuesta a Pac-Man
// o aleatoriamente si no hay una "mejor" dirección para huir
let farthestDistance = -1;
possibleDirections.forEach(dir => {
const testX = this.ghost.x + (dir === 'right' ? this.ghost.speed : dir === 'left' ? -this.ghost.speed : 0);
const testY = this.ghost.y + (dir === 'down' ? this.ghost.speed : dir === 'up' ? -this.ghost.speed : 0);
const dist = Math.sqrt(Math.pow(testX - targetX, 2) + Math.pow(testY - targetY, 2));
if (dist > farthestDistance) {
farthestDistance = dist;
bestDirection = dir;
}
});
} else {
// Comportamiento normal: buscar Pac-Man (heurística simple)
possibleDirections.forEach(dir => {
const testX = this.ghost.x + (dir === 'right' ? this.ghost.speed : dir === 'left' ? -this.ghost.speed : 0);
const testY = this.ghost.y + (dir === 'down' ? this.ghost.speed : dir === 'up' ? -this.ghost.speed : 0);
const dist = Math.sqrt(Math.pow(testX - targetX, 2) + Math.pow(testY - targetY, 2));
if (dist < minDistance) {
minDistance = dist;
bestDirection = dir;
}
});
}
}
this.ghost.direction = bestDirection;
this.moveCharacter(this.ghost);
}
isDeadEnd(x, y, currentDir) {
let exits = 0;
const directions = ['up', 'down', 'left', 'right'];
directions.forEach(dir => {
let checkX = x;
let checkY = y;
if (dir === 'up') checkY--;
else if (dir === 'down') checkY++;
else if (dir === 'left') checkX--;
else if (dir === 'right') checkX++;
if (checkX >= 0 && checkX < this.mapWidth && checkY >= 0 && checkY < this.mapHeight && this.map[checkY][checkX] !== 1) {
exits++;
}
});
return exits <= 1; // Un callejón sin salida si solo hay 0 o 1 salida
}
resetGhostPosition() {
this.ghost.x = 13.5 * this.cellSize;
this.ghost.y = 12.5 * this.cellSize;
this.ghost.direction = 'left';
this.ghost.frightened = false;
this.ghost.frightenedTimer = 0;
}
checkDotCollision() {
this.dots.forEach(dot => {
if (!dot.eaten) {
const distance = Math.sqrt(Math.pow(this.pacman.x - dot.x, 2) + Math.pow(this.pacman.y - dot.y, 2));
if (distance < this.cellSize * 0.3) { // Colisión más precisa para puntos
dot.eaten = true;
this.score += 10;
this.updateScoreDisplay();
}
}
});
}
checkPowerPillCollision() {
this.powerPills.forEach(pill => {
if (!pill.eaten) {
const distance = Math.sqrt(Math.pow(this.pacman.x - pill.x, 2) + Math.pow(this.pacman.y - pill.y, 2));
if (distance < this.cellSize * 0.5) { // Colisión para píldoras grandes
pill.eaten = true;
this.score += 50;
this.updateScoreDisplay();
this.ghost.frightened = true;
this.ghost.frightenedTimer = 50; // Fantasma asustado por 5 segundos (50 * 100ms)
}
}
});
}
checkCollision(obj1, obj2, distThreshold) {
const distance = Math.sqrt(Math.pow(obj1.x - obj2.x, 2) + Math.pow(obj1.y - obj2.y, 2));
return distance < distThreshold;
}
handleKeyInput(e) {
const key = e.key;
if (key === 'ArrowUp') this.pacman.nextDirection = 'up';
else if (key === 'ArrowDown') this.pacman.nextDirection = 'down';
else if (key === 'ArrowLeft') this.pacman.nextDirection = 'left';
else if (key === 'ArrowRight') this.pacman.nextDirection = 'right';
}
updateScoreDisplay() {
const scoreDisplay = document.getElementById('pacman-score');
if (scoreDisplay) scoreDisplay.textContent = this.score;
}
makePanelDraggable(panel) {
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
const dragStart = (e) => {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
isDragging = true;
panel.style.cursor = 'grabbing';
};
const dragEnd = () => {
initialX = currentX;
initialY = currentY;
isDragging = false;
panel.style.cursor = 'grab';
};
const drag = (e) => {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
panel.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
}
};
const header = panel.querySelector('h3');
if (header) {
header.style.cursor = 'grab';
header.addEventListener("mousedown", dragStart);
document.addEventListener("mouseup", dragEnd);
document.addEventListener("mousemove", drag);
}
}
}
const initPacmanGame = () => {
new PacmanGame();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPacmanGame);
} else {
setTimeout(initPacmanGame, 500);
}
})();