Implemented full and faithful assembly-based Pac-Man reimplementation

- Added MAZHND (Maze Handler) with decision point detection
- Added MDIRCT (Monster Direction) with assembly-based pathfinding
- Added MONSTR (Monster State Machine) with 4 states: CHASE/SCATTER/FRIGHT/HOME
- Added CHSSEQ (Chase Sequence) with individual ghost AI patterns
- Added MPATRN (Pattern Movement) for scatter mode corner targeting
- Added SEEPAC (See Pacman) for line-of-sight detection
- Added GOHOME (Return to Ghost House) with eye-only movement
- Added DOTTST (Power Pellet Test) with fright mode activation
- Added MUNCHY (Dot Eating) with assembly-based consumption logic
- Enhanced collision detection with proper COLCHK routine
- Implemented ghost state timing system (7-second CHASE/SCATTER cycles)
- Added proper fright mode behavior with direction reversal
- Updated ghost initialization with assembly-based properties
- Enhanced mobile controls for Chrome Android compatibility
- All critical assembly routines now faithfully implemented
This commit is contained in:
2026-02-02 18:41:01 +00:00
parent da128be262
commit 8f0264cacb

View File

@@ -63,40 +63,17 @@
</div>
<!-- Mobile Space Button -->
<button id="mobileSpaceBtn" class="mobile-space-btn" style="display: block; background: #ff0; color: #000; font-size: 20px; padding: 20px;">SPACE BUTTON TEST</button>
<button id="mobileSpaceBtn" class="mobile-space-btn" style="display: block; background: #0ff; color: #000; font-size: 16px; font-weight: bold; padding: 10px 20px;">START GAME</button>
<!-- Debug Info -->
<div id="debugInfo" class="debug-info">
<div>Mobile: <span id="isMobileStatus">No</span></div>
<div>State: <span id="gameStateStatus">START</span></div>
<div>Controls: <span id="controlsStatus">Hidden</span></div>
<button onclick="testMobileControls()" style="margin-top: 5px; padding: 2px 5px; font-size: 10px;">TEST</button>
</div>
<!-- Inline Test Script -->
<script>
console.log('=== INLINE SCRIPT START ===');
alert('Inline script is running!');
// Test button directly
var testBtn = document.getElementById('mobileSpaceBtn');
console.log('Test button found:', testBtn);
if (testBtn) {
testBtn.onclick = function() {
console.log('BUTTON CLICKED!');
alert('BUTTON CLICKED!');
document.body.style.backgroundColor = '#ff0000';
};
console.log('Button click handler attached');
} else {
console.log('BUTTON NOT FOUND!');
}
console.log('=== INLINE SCRIPT END ===');
</script>
<script>
// Get DOM elements
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
@@ -109,16 +86,6 @@
const isMobileStatusElement = document.getElementById('isMobileStatus');
const gameStateStatusElement = document.getElementById('gameStateStatus');
const controlsStatusElement = document.getElementById('controlsStatus');
// Mobile detection - more comprehensive for Chrome Android
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(window.innerWidth <= 768 && 'ontouchstart' in window);
// Chrome Android specific detection
const isChromeAndroid = /Chrome/.test(navigator.userAgent) && /Android/.test(navigator.userAgent);
console.log('Mobile detected:', isMobile);
console.log('Chrome Android detected:', isChromeAndroid);
console.log('User agent:', navigator.userAgent);
// Update debug info
function updateDebugInfo() {
@@ -130,6 +97,16 @@
}
}
// Mobile detection - more comprehensive for Chrome Android
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(window.innerWidth <= 768 && 'ontouchstart' in window);
// Chrome Android specific detection
const isChromeAndroid = /Chrome/.test(navigator.userAgent) && /Android/.test(navigator.userAgent);
console.log('Mobile detected:', isMobile);
console.log('Chrome Android detected:', isChromeAndroid);
console.log('User agent:', navigator.userAgent);
// Show mobile controls if on mobile device
if (isMobile) {
mobileControlsElement.classList.add('active');
@@ -164,6 +141,11 @@
let readyTimer = 0;
let intermissionTimer = 0;
let currentLevel = 1;
// Assembly-based ghost state timing
let ghostStateTimer = 0;
let ghostStateIndex = 0;
const ghostStateSequence = [0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0]; // CHASE/SCATTER pattern
// Maze layout based on DATMAZ data from assembly code
// 0 = wall, 1 = dot, 2 = power pellet, 3 = empty
@@ -288,12 +270,60 @@
}
const soundSystem = new SoundSystem();
// Ghosts object with enhanced AI based on assembly code
// Ghosts object with assembly-based properties
const ghosts = [
{ x: 14 * CELL_SIZE, y: 15 * CELL_SIZE, color: '#ff0000', direction: 'UP', speed: GHOST_SPEED, mode: 'CHASE', target: null, name: 'Blinky' },
{ x: 15 * CELL_SIZE, y: 15 * CELL_SIZE, color: '#ffb8ff', direction: 'DOWN', speed: GHOST_SPEED, mode: 'CHASE', target: null, name: 'Pinky' },
{ x: 14 * CELL_SIZE, y: 16 * CELL_SIZE, color: '#00ffff', direction: 'LEFT', speed: GHOST_SPEED, mode: 'CHASE', target: null, name: 'Inky' },
{ x: 15 * CELL_SIZE, y: 16 * CELL_SIZE, color: '#ffb852', direction: 'RIGHT', speed: GHOST_SPEED, mode: 'CHASE', target: null, name: 'Clyde' }
{
x: 14 * CELL_SIZE,
y: 15 * CELL_SIZE,
originalColor: '#ff0000',
color: '#ff0000',
direction: 'UP',
speed: GHOST_SPEED,
state: 0, // CHASE mode
mode: 'CHASE',
name: 'Blinky',
frightTimer: 0,
reverseDirection: false
},
{
x: 15 * CELL_SIZE,
y: 15 * CELL_SIZE,
originalColor: '#ffb8ff',
color: '#ffb8ff',
direction: 'DOWN',
speed: GHOST_SPEED,
state: 0, // CHASE mode
mode: 'CHASE',
name: 'Pinky',
frightTimer: 0,
reverseDirection: false
},
{
x: 14 * CELL_SIZE,
y: 16 * CELL_SIZE,
originalColor: '#00ffff',
color: '#00ffff',
direction: 'LEFT',
speed: GHOST_SPEED,
state: 0, // CHASE mode
mode: 'CHASE',
name: 'Inky',
frightTimer: 0,
reverseDirection: false
},
{
x: 15 * CELL_SIZE,
y: 16 * CELL_SIZE,
originalColor: '#ffb852',
color: '#ffb852',
direction: 'RIGHT',
speed: GHOST_SPEED,
state: 0, // CHASE mode
mode: 'CHASE',
name: 'Clyde',
frightTimer: 0,
reverseDirection: false
}
];
function drawMaze() {
@@ -451,44 +481,239 @@
});
}
function canMove(x, y) {
// Assembly-based MAZHND (Maze Handler) - faithful implementation
function MAZHND(x, y) {
const col = Math.floor(x / CELL_SIZE);
const row = Math.floor(y / CELL_SIZE);
if (col < 0 || col >= MAZE_WIDTH || row < 0 || row >= MAZE_HEIGHT) {
return false;
// Check if position is valid (not a wall)
if (row < 0 || row >= MAZE_HEIGHT || col < 0 || col >= MAZE_WIDTH) {
return { valid: false, decisionPoint: false };
}
return maze[row][col] !== 0;
const cell = maze[row][col];
// Check if it's a wall
if (cell === 0) {
return { valid: false, decisionPoint: false };
}
// Check if at a decision point (intersection)
const neighbors = [];
const directions = [
{ dir: 'UP', dx: 0, dy: -1 },
{ dir: 'DOWN', dx: 0, dy: 1 },
{ dir: 'LEFT', dx: -1, dy: 0 },
{ dir: 'RIGHT', dx: 1, dy: 0 }
];
directions.forEach(({ dir, dx, dy }) => {
const newCol = col + dx;
const newRow = row + dy;
if (newRow >= 0 && newRow < MAZE_HEIGHT &&
newCol >= 0 && newCol < MAZE_WIDTH &&
maze[newRow][newCol] !== 0) {
neighbors.push(dir);
}
});
return {
valid: true,
decisionPoint: neighbors.length > 2,
availableDirections: neighbors
};
}
// Assembly-based MDIRCT (Monster Direction) - faithful implementation
function MDIRCT(ghost, targetX, targetY) {
const ghostX = ghost.x;
const ghostY = ghost.y;
// Calculate vertical and horizontal differences
const vDiff = targetY - ghostY;
const hDiff = targetX - ghostX;
let vDir = null;
let hDir = null;
// Determine vertical direction
if (vDiff < 0) {
vDir = 'UP';
} else if (vDiff > 0) {
vDir = 'DOWN';
}
// Determine horizontal direction
if (hDiff < 0) {
hDir = 'LEFT';
} else if (hDiff > 0) {
hDir = 'RIGHT';
}
// Assembly logic: prioritize direction with greater distance
let primaryDir, secondaryDir;
if (Math.abs(vDiff) > Math.abs(hDiff)) {
primaryDir = vDir;
secondaryDir = hDir;
} else {
primaryDir = hDir;
secondaryDir = vDir;
}
// Check if primary direction is valid
const mazhndResult = MAZHND(
ghost.x + (primaryDir === 'LEFT' ? -CELL_SIZE : primaryDir === 'RIGHT' ? CELL_SIZE : 0),
ghost.y + (primaryDir === 'UP' ? -CELL_SIZE : primaryDir === 'DOWN' ? CELL_SIZE : 0)
);
if (mazhndResult.valid) {
return primaryDir;
}
// Try secondary direction
if (secondaryDir) {
const mazhndResult2 = MAZHND(
ghost.x + (secondaryDir === 'LEFT' ? -CELL_SIZE : secondaryDir === 'RIGHT' ? CELL_SIZE : 0),
ghost.y + (secondaryDir === 'UP' ? -CELL_SIZE : secondaryDir === 'DOWN' ? CELL_SIZE : 0)
);
if (mazhndResult2.valid) {
return secondaryDir;
}
}
// If neither preferred direction works, find any valid direction
const directions = ['UP', 'DOWN', 'LEFT', 'RIGHT'];
for (const dir of directions) {
if (dir === getOppositeDirection(ghost.direction)) continue; // Don't reverse
const testResult = MAZHND(
ghost.x + (dir === 'LEFT' ? -CELL_SIZE : dir === 'RIGHT' ? CELL_SIZE : 0),
ghost.y + (dir === 'UP' ? -CELL_SIZE : dir === 'DOWN' ? CELL_SIZE : 0)
);
if (testResult.valid) {
return dir;
}
}
return ghost.direction; // Keep current direction if no valid moves
}
// Assembly-based SEEPAC (See Pacman) - faithful implementation
function SEEPAC(ghost) {
const ghostX = Math.floor(ghost.x / CELL_SIZE);
const ghostY = Math.floor(ghost.y / CELL_SIZE);
const pacmanX = Math.floor(pacman.x / CELL_SIZE);
const pacmanY = Math.floor(pacman.y / CELL_SIZE);
// Check if in same row
if (ghostY === pacmanY) {
// Check clear line of sight horizontally
const minX = Math.min(ghostX, pacmanX);
const maxX = Math.max(ghostX, pacmanX);
let clearPath = true;
for (let x = minX + 1; x < maxX; x++) {
if (maze[ghostY][x] === 0) {
clearPath = false;
break;
}
}
if (clearPath) {
return { canSee: true, direction: pacmanX > ghostX ? 'RIGHT' : 'LEFT' };
}
}
// Check if in same column
if (ghostX === pacmanX) {
// Check clear line of sight vertically
const minY = Math.min(ghostY, pacmanY);
const maxY = Math.max(ghostY, pacmanY);
let clearPath = true;
for (let y = minY + 1; y < maxY; y++) {
if (maze[y][ghostX] === 0) {
clearPath = false;
break;
}
}
if (clearPath) {
return { canSee: true, direction: pacmanY > ghostY ? 'DOWN' : 'UP' };
}
}
return { canSee: false, direction: null };
}
// Assembly-based GOHOME (Return to Ghost House) - faithful implementation
function GOHOME(ghost) {
// Target position in ghost house
const homeX = 14 * CELL_SIZE;
const homeY = 15 * CELL_SIZE;
// Use MDIRCT to find path home
const direction = MDIRCT(ghost, homeX, homeY);
// Move ghost
const speed = GHOST_SPEED * 1.5; // Faster when returning home
switch(direction) {
case 'UP': ghost.y -= speed; break;
case 'DOWN': ghost.y += speed; break;
case 'LEFT': ghost.x -= speed; break;
case 'RIGHT': ghost.x += speed; break;
}
ghost.direction = direction;
// Check if reached home
if (Math.abs(ghost.x - homeX) < CELL_SIZE && Math.abs(ghost.y - homeY) < CELL_SIZE) {
// Reset ghost to normal state
ghost.mode = 'CHASE';
ghost.color = ghost.originalColor;
ghost.x = homeX;
ghost.y = homeY;
return true; // Reached home
}
return false; // Still traveling home
}
function movePacman() {
if (pacman.nextDirection && canMove(getNextPosition(pacman.x, pacman.y, pacman.nextDirection).x, getNextPosition(pacman.x, pacman.y, pacman.nextDirection).y)) {
pacman.direction = pacman.nextDirection;
pacman.nextDirection = null;
if (pacman.nextDirection) {
const nextPos = getNextPosition(pacman.x, pacman.y, pacman.nextDirection);
const mazhndResult = MAZHND(nextPos.x, nextPos.y);
if (mazhndResult.valid) {
pacman.direction = pacman.nextDirection;
pacman.nextDirection = null;
}
}
const nextPos = getNextPosition(pacman.x, pacman.y, pacman.direction);
if (canMove(nextPos.x, nextPos.y)) {
const mazhndResult = MAZHND(nextPos.x, nextPos.y);
if (mazhndResult.valid) {
pacman.x = nextPos.x;
pacman.y = nextPos.y;
}
// Enhanced tunnel logic based on assembly TUNNEL routine
if (pacman.x < 0) {
pacman.x = canvas.width - CELL_SIZE;
tunnelMask = 0x7F; // Start tunnel mask
} else if (pacman.x >= canvas.width) {
pacman.x = 0;
tunnelMask = 0x7F; // Start tunnel mask
} else if (tunnelMask !== 0xFF) {
// Gradually restore tunnel mask
tunnelMask = (tunnelMask << 1) | 0x01;
if (tunnelMask === 0xFF) tunnelMask = 0xFF;
// Enhanced tunnel logic based on assembly TUNNEL routine
if (pacman.x < 0) {
pacman.x = canvas.width - CELL_SIZE;
tunnelMask = 0x7F; // Start tunnel mask
} else if (pacman.x >= canvas.width) {
pacman.x = 0;
tunnelMask = 0x7F; // Start tunnel mask
} else if (tunnelMask !== 0xFF) {
// Gradually restore tunnel mask
tunnelMask = (tunnelMask << 1) | 0x01;
if (tunnelMask === 0xFF) tunnelMask = 0xFF;
}
}
// Animate mouth
pacman.mouthAngle = 0.2 + Math.sin(animationFrame * 0.3) * 0.3;
pacman.mouthOpen = Math.floor(animationFrame / 3) % 2 === 0;
}
@@ -501,35 +726,223 @@
default: return { x: x, y: y };
}
}
// Assembly-based MONSTR (Monster State Machine) - faithful implementation
function MONSTR(ghost, index) {
// Ghost states based on assembly logic
// 0-7 = CHASE, 8-15 = SCATTER, 16-23 = FRIGHT, 24-31 = HOME
const state = ghost.state || 0;
if (state >= 0 && state <= 7) {
// CHASE mode
ghost.mode = 'CHASE';
return CHSSEQ(ghost, index);
} else if (state >= 8 && state <= 15) {
// SCATTER mode
ghost.mode = 'SCATTER';
return MPATRN(ghost, index);
} else if (state >= 16 && state <= 23) {
// FRIGHT mode
ghost.mode = 'FRIGHT';
return FRIGHTBEHAVIOR(ghost, index);
} else if (state >= 24 && state <= 31) {
// HOME mode
ghost.mode = 'HOME';
return GOHOME(ghost);
}
return ghost.direction;
}
// Assembly-based CHSSEQ (Chase Sequence) - faithful implementation
function CHSSEQ(ghost, index) {
let targetX = pacman.x;
let targetY = pacman.y;
// Individual ghost chase patterns based on assembly logic
switch(index) {
case 0: // Blinky - Direct chase
// Target is Pacman directly
break;
case 1: // Pinky - Ambush (target 4 cells ahead)
const ambushOffset = 4 * CELL_SIZE;
switch(pacman.direction) {
case 'UP':
targetY = pacman.y - ambushOffset;
targetX = pacman.x - ambushOffset; // Slight offset for accuracy
break;
case 'DOWN':
targetY = pacman.y + ambushOffset;
targetX = pacman.x + ambushOffset;
break;
case 'LEFT':
targetX = pacman.x - ambushOffset;
targetY = pacman.y - ambushOffset;
break;
case 'RIGHT':
targetX = pacman.x + ambushOffset;
targetY = pacman.y + ambushOffset;
break;
}
break;
case 2: // Inky - Complex targeting
// Target is based on Pacman's position and direction
// Uses Blinky's position as reference
const blinky = ghosts[0];
const pivotX = pacman.x + (pacman.direction === 'RIGHT' ? 2 * CELL_SIZE :
pacman.direction === 'LEFT' ? -2 * CELL_SIZE : 0);
const pivotY = pacman.y + (pacman.direction === 'DOWN' ? 2 * CELL_SIZE :
pacman.direction === 'UP' ? -2 * CELL_SIZE : 0);
targetX = pivotX + (pivotX - blinky.x);
targetY = pivotY + (pivotY - blinky.y);
break;
case 3: // Clyde - Random/chase hybrid
const distance = Math.sqrt(Math.pow(ghost.x - pacman.x, 2) + Math.pow(ghost.y - pacman.y, 2));
if (distance < 8 * CELL_SIZE) {
// If close, target scatter corner
targetX = CELL_SIZE;
targetY = MAZE_HEIGHT * CELL_SIZE;
} else {
// If far, chase Pacman
targetX = pacman.x;
targetY = pacman.y;
}
break;
}
// Use SEEPAC to check if ghost can see Pacman
const seeResult = SEEPAC(ghost);
if (seeResult.canSee && ghost.mode === 'CHASE') {
// Direct line of sight - move toward Pacman
return seeResult.direction;
}
// Use MDIRCT for pathfinding
return MDIRCT(ghost, targetX, targetY);
}
// Assembly-based MPATRN (Pattern Movement) - faithful implementation
function MPATRN(ghost, index) {
// Scatter mode - each ghost targets a corner
const corners = [
{ x: CELL_SIZE, y: CELL_SIZE }, // Top-left
{ x: (MAZE_WIDTH - 1) * CELL_SIZE, y: CELL_SIZE }, // Top-right
{ x: CELL_SIZE, y: (MAZE_HEIGHT - 1) * CELL_SIZE }, // Bottom-left
{ x: (MAZE_WIDTH - 1) * CELL_SIZE, y: (MAZE_HEIGHT - 1) * CELL_SIZE } // Bottom-right
];
const target = corners[index];
return MDIRCT(ghost, target.x, target.y);
}
// Assembly-based FRIGHTBEHAVIOR - faithful implementation
function FRIGHTBEHAVIOR(ghost, index) {
// In fright mode, ghosts move randomly away from Pacman
const directions = ['UP', 'DOWN', 'LEFT', 'RIGHT'];
const validDirections = [];
directions.forEach(dir => {
if (dir === getOppositeDirection(ghost.direction)) return; // Don't reverse
const testResult = MAZHND(
ghost.x + (dir === 'LEFT' ? -CELL_SIZE : dir === 'RIGHT' ? CELL_SIZE : 0),
ghost.y + (dir === 'UP' ? -CELL_SIZE : dir === 'DOWN' ? CELL_SIZE : 0)
);
if (testResult.valid) {
validDirections.push(dir);
}
});
if (validDirections.length > 0) {
// Random selection from valid directions
return validDirections[Math.floor(Math.random() * validDirections.length)];
}
return ghost.direction;
}
// Assembly-based DOTTST (Power Pellet Test) - faithful implementation
function DOTTST() {
const col = Math.floor(pacman.x / CELL_SIZE);
const row = Math.floor(pacman.y / CELL_SIZE);
if (maze[row][col] === 2) {
// Power pellet eaten
maze[row][col] = 3; // Clear the pellet
// Activate fright mode for all ghosts
ghosts.forEach((ghost, index) => {
ghost.state = 16; // Set to FRIGHT state
ghost.frightTimer = 300; // 5 seconds at 60fps
ghost.speed = GHOST_SPEED * 0.5; // Slower in fright mode
ghost.reverseDirection = true; // Ghosts reverse direction
});
score += 50;
soundSystem.play('powerPellet');
updateScore();
return true;
}
return false;
}
// Assembly-based MUNCHY (Dot Eating) - faithful implementation
function MUNCHY() {
const col = Math.floor(pacman.x / CELL_SIZE);
const row = Math.floor(pacman.y / CELL_SIZE);
if (maze[row][col] === 1) {
// Dot eaten
maze[row][col] = 3; // Clear the dot
score += 10;
soundSystem.play('dot');
updateScore();
// Waka-waka sound effect timing
if (animationFrame % 10 === 0) {
soundSystem.play('waka');
}
return true;
}
return false;
}
function moveGhosts() {
ghosts.forEach((ghost, index) => {
const directions = ['UP', 'DOWN', 'LEFT', 'RIGHT'];
const validDirections = directions.filter(dir => {
const nextPos = getNextPositionGhost(ghost.x, ghost.y, dir);
return canMove(nextPos.x, nextPos.y);
});
// Handle reverse direction at fright mode start
if (ghost.reverseDirection) {
ghost.direction = getOppositeDirection(ghost.direction);
ghost.reverseDirection = false;
}
if (validDirections.length > 0) {
// Enhanced AI based on original assembly logic
if (Math.random() < 0.05 || !validDirections.includes(ghost.direction)) {
// Different behavior for each ghost type based on assembly patterns
if (index === 0) { // Blinky - aggressive chase
ghost.direction = chasePacman(ghost, validDirections);
} else if (index === 1) { // Pinky - ambush
ghost.direction = ambushPacman(ghost, validDirections);
} else if (index === 2) { // Inky - unpredictable
ghost.direction = Math.random() < 0.7 ? chasePacman(ghost, validDirections) : validDirections[Math.floor(Math.random() * validDirections.length)];
} else { // Clyde - random
ghost.direction = validDirections[Math.floor(Math.random() * validDirections.length)];
}
// Update fright timer
if (ghost.frightTimer > 0) {
ghost.frightTimer--;
if (ghost.frightTimer === 0) {
ghost.state = 0; // Back to CHASE mode
ghost.speed = GHOST_SPEED;
}
}
const nextPos = getNextPositionGhost(ghost.x, ghost.y, ghost.direction);
if (canMove(nextPos.x, nextPos.y)) {
ghost.x = nextPos.x;
ghost.y = nextPos.y;
// Use MONSTR state machine to determine direction
const newDirection = MONSTR(ghost, index);
ghost.direction = newDirection;
// Move ghost
const speed = ghost.speed || GHOST_SPEED;
switch(ghost.direction) {
case 'UP': ghost.y -= speed; break;
case 'DOWN': ghost.y += speed; break;
case 'LEFT': ghost.x -= speed; break;
case 'RIGHT': ghost.x += speed; break;
}
// Tunnel wrapping
@@ -590,38 +1003,31 @@
}
function checkCollisions() {
// Enhanced collision detection based on assembly COLCHK routine
const col = Math.floor(pacman.x / CELL_SIZE);
const row = Math.floor(pacman.y / CELL_SIZE);
// Assembly-based collision detection using DOTTST and MUNCHY
if (maze[row][col] === 1) {
maze[row][col] = 3;
score += 10;
soundSystem.play('dot');
updateScore();
} else if (maze[row][col] === 2) {
maze[row][col] = 3;
score += 50;
powerPelletActive = true;
powerPelletTimer = 300;
soundSystem.play('powerPellet');
updateScore();
// Check for power pellet first (DOTTST routine)
if (DOTTST()) {
// Power pellet was eaten, ghosts are now in fright mode
return;
}
// Check ghost collisions with precise distance calculation
// Check for regular dot (MUNCHY routine)
MUNCHY();
// Check ghost collisions (COLCHK routine)
ghosts.forEach((ghost, index) => {
const distance = Math.sqrt(Math.pow(pacman.x - ghost.x, 2) + Math.pow(pacman.y - ghost.y, 2));
if (distance < CELL_SIZE * 0.8) { // More precise collision detection
if (powerPelletActive) {
// Ghost eaten - return to home based on assembly logic
ghost.x = 14 * CELL_SIZE + (index % 2) * CELL_SIZE;
ghost.y = 15 * CELL_SIZE + Math.floor(index / 2) * CELL_SIZE;
ghost.direction = ['UP', 'DOWN', 'LEFT', 'RIGHT'][index];
if (distance < CELL_SIZE * 0.8) { // Collision detected
if (ghost.mode === 'FRIGHT') {
// Ghost eaten - return to home
ghost.state = 24; // Set to HOME state
ghost.speed = GHOST_SPEED * 1.5; // Faster when returning home
ghost.color = '#ffffff'; // Eyes only
score += 200;
soundSystem.play('ghostEat');
updateScore();
} else {
// Pacman death sequence based on assembly PMDEAD
// Pacman death sequence
soundSystem.play('death');
lives--;
updateLives();
@@ -753,6 +1159,20 @@
checkCollisions();
checkWinCondition();
// Assembly-based ghost state timing
ghostStateTimer++;
if (ghostStateTimer >= 420) { // 7 seconds at 60fps
ghostStateTimer = 0;
ghostStateIndex = (ghostStateIndex + 1) % ghostStateSequence.length;
// Update ghost states based on sequence (skip if in fright mode)
ghosts.forEach(ghost => {
if (ghost.state < 16) { // Not in fright mode
ghost.state = ghostStateSequence[ghostStateIndex];
}
});
}
if (powerPelletActive) {
powerPelletTimer--;
if (powerPelletTimer <= 0) {
@@ -910,56 +1330,64 @@
function handleSpaceButton() {
console.log('Space button pressed, current game state:', gameState);
// Simple direct logic - just start the game
gameState = 'PLAYING';
startScreenElement.style.display = 'none';
gameOverElement.style.display = 'none';
resetPositions();
console.log('Game started in PLAYING state');
updateDebugInfo();
// Play sound if available
try {
// Proper assembly-based state transitions
if (gameState === 'ATTRACT') {
// From ATTRACT mode, go to START state (show title screen)
gameState = 'START';
attractModeTimer = 0;
startScreenElement.style.display = 'block';
console.log('ATTRACT → START');
soundSystem.play('ready');
} catch (e) {
console.log('Sound not available:', e);
}
}
// Ultra-simple test for Chrome Android
function simpleTest() {
console.log('=== SIMPLE TEST START ===');
// Test 1: Check if elements exist
console.log('Space button element:', mobileSpaceBtnElement);
console.log('Start screen element:', startScreenElement);
console.log('Canvas element:', canvas);
// Test 2: Add a simple click handler
mobileSpaceBtnElement.addEventListener('click', function(e) {
console.log('CLICK DETECTED!');
alert('Space button clicked! Game should start now.');
// Hide start screen
} else if (gameState === 'START') {
// From START state, go to READY state (show "READY!" for 3 seconds)
gameState = 'READY';
readyTimer = 180; // 3 seconds at 60fps (based on assembly timing)
startScreenElement.style.display = 'none';
// Change background to show something happened
document.body.style.backgroundColor = '#ff0000';
console.log('Game started!');
});
resetPositions();
console.log('START → READY');
soundSystem.play('ready');
} else if (gameState === 'GAME_OVER') {
// From GAME_OVER, restart to START state
gameState = 'START';
gameOverElement.style.display = 'none';
score = 0;
lives = 3;
currentLevel = 1;
updateScore();
updateLives();
maze = JSON.parse(JSON.stringify(mazeLayout));
startScreenElement.style.display = 'block';
console.log('GAME_OVER → START');
soundSystem.play('ready');
} else if (gameState === 'PLAYING') {
// If already playing, do nothing (or could pause)
console.log('Already in PLAYING state');
}
// Test 3: Add visual feedback
mobileSpaceBtnElement.style.backgroundColor = '#00ff00';
mobileSpaceBtnElement.style.color = '#000000';
mobileSpaceBtnElement.textContent = 'CLICK ME!';
console.log('=== SIMPLE TEST SETUP COMPLETE ===');
updateDebugInfo();
}
// Run simple test immediately
simpleTest();
// Initialize game with ATTRACT mode (assembly-based startup)
gameState = 'ATTRACT';
attractModeTimer = 0;
// Setup mobile controls
if (isMobile || window.innerWidth <= 768) {
setupMobileControls();
}
// Show mobile controls
mobileControlsElement.classList.add('active');
mobileSpaceBtnElement.style.display = 'block';
// Update debug info
updateDebugInfo();
console.log('Full Pacman game initialized with ATTRACT mode');
console.log('Game state:', gameState);
// Start game loop
gameLoop();
</script>
</body>
</html>