|
24 | 24 |
|
25 | 25 | <a href="#pacman-game" class="pacman-link"><h1 id="pacman-game">Pacman Game</h1></a> |
26 | 26 | <canvas id="pacmanCanvas" width="896" height="992"></canvas> |
27 | | -<p>Use arrow keys to move Pacman. Eat all the features!</p> |
| 27 | +<p>Use arrow keys to move Pacman. Collect all the whale icons while avoiding the monsters.</p> |
28 | 28 | <script> |
29 | 29 | // Larger Pacman game (dev container themed, fewer collectibles, bigger whales) |
30 | 30 | const canvas = document.getElementById('pacmanCanvas'); |
|
62 | 62 | let pacman = { x: 14, y: 17, dx: 0, dy: 0, nextDx: 0, nextDy: 0 }; |
63 | 63 | let score = 0; |
64 | 64 | let dots = 0; |
| 65 | +let gameOver = false; |
| 66 | +let lives = 3; |
65 | 67 | for (let row of map) for (let c of row) if (c === '.' || c === 'o') dots++; |
66 | 68 | function drawMap() { |
67 | 69 | for (let y = 0; y < map.length; y++) { |
|
238 | 240 | if (canMove(pacman.x + pacman.dx, pacman.y + pacman.dy)) { |
239 | 241 | pacman.x += pacman.dx; |
240 | 242 | pacman.y += pacman.dy; |
| 243 | + // Check for collisions after each move |
| 244 | + checkCollisions(); |
241 | 245 | } |
242 | 246 | // Wrap |
243 | 247 | if (pacman.x < 0) pacman.x = map[0].length - 1; |
|
257 | 261 | ctx.fillStyle = '#FFF'; |
258 | 262 | ctx.font = '28px Arial'; |
259 | 263 | ctx.fillText('Score: ' + score, 20, canvas.height - 20); |
| 264 | + |
| 265 | + // Draw lives |
| 266 | + ctx.fillStyle = '#FFFF00'; |
| 267 | + ctx.font = '28px Arial'; |
| 268 | + ctx.fillText('Lives: ' + lives, canvas.width - 150, canvas.height - 20); |
| 269 | +} |
| 270 | + |
| 271 | +// Check if Pacman collides with any monster |
| 272 | +function checkCollisions() { |
| 273 | + for (const monster of globalThis.monsterStates) { |
| 274 | + if (monster.x === pacman.x && monster.y === pacman.y) { |
| 275 | + lives--; |
| 276 | + if (lives <= 0) { |
| 277 | + gameOver = true; |
| 278 | + } else { |
| 279 | + // Reset Pacman position |
| 280 | + pacman.x = 14; |
| 281 | + pacman.y = 17; |
| 282 | + pacman.dx = 0; |
| 283 | + pacman.dy = 0; |
| 284 | + pacman.nextDx = 0; |
| 285 | + pacman.nextDy = 0; |
| 286 | + } |
| 287 | + return true; |
| 288 | + } |
| 289 | + } |
| 290 | + return false; |
| 291 | +} |
| 292 | + |
| 293 | +// Draw game over screen |
| 294 | +function drawGameOver() { |
| 295 | + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; |
| 296 | + ctx.fillRect(0, 0, canvas.width, canvas.height); |
| 297 | + |
| 298 | + ctx.fillStyle = '#e74c3c'; |
| 299 | + ctx.font = '48px Arial'; |
| 300 | + ctx.textAlign = 'center'; |
| 301 | + ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 50); |
| 302 | + |
| 303 | + ctx.fillStyle = '#FFF'; |
| 304 | + ctx.font = '24px Arial'; |
| 305 | + ctx.fillText('Final Score: ' + score, canvas.width/2, canvas.height/2); |
| 306 | + |
| 307 | + ctx.fillStyle = '#25d6a2'; |
| 308 | + ctx.font = '28px Arial'; |
| 309 | + ctx.fillText('Press SPACE to restart', canvas.width/2, canvas.height/2 + 50); |
| 310 | + |
| 311 | + ctx.textAlign = 'left'; // Reset text align |
| 312 | +} |
| 313 | + |
| 314 | +// Reset game state |
| 315 | +function resetGame() { |
| 316 | + // Reset map (restore dots) |
| 317 | + map = [ |
| 318 | + '############################', |
| 319 | + '# ....##.... ....#', |
| 320 | + '#.####.#####.##.#####.####.#', |
| 321 | + '#o####.#####.##.#####.####o#', |
| 322 | + '#.####.#####.##.#####.####.#', |
| 323 | + '# #', |
| 324 | + '#.####.##.########.##.####.#', |
| 325 | + '#.####.##.########.##.####.#', |
| 326 | + '# ## ## ## #', |
| 327 | + '######.##### ## #####.######', |
| 328 | + '######.##### ## #####.######', |
| 329 | + '######.## ##.######', |
| 330 | + '######.## ######## ##.######', |
| 331 | + '######.## ######## ##.######', |
| 332 | + '# ....##.... ....#', |
| 333 | + '#.####.#####.##.#####.####.#', |
| 334 | + '#.####.#####.##.#####.####.#', |
| 335 | + '#o..## ##..o#', |
| 336 | + '###.##.##.########.##.##.###', |
| 337 | + '###.##.##.########.##.##.###', |
| 338 | + '# ## ## ## #', |
| 339 | + '#.##########.##.##########.#', |
| 340 | + '#.##########.##.##########.#', |
| 341 | + '# #', |
| 342 | + '############################' |
| 343 | + ]; |
| 344 | + |
| 345 | + // Reset pacman |
| 346 | + pacman = { x: 14, y: 17, dx: 0, dy: 0, nextDx: 0, nextDy: 0 }; |
| 347 | + |
| 348 | + // Reset monsters to original positions |
| 349 | + globalThis.monsterStates = monsters.map(m => ({...m, dx: 0, dy: 0})); |
| 350 | + |
| 351 | + // Reset game state |
| 352 | + score = 0; |
| 353 | + lives = 3; |
| 354 | + gameOver = false; |
| 355 | + dots = 0; |
| 356 | + for (let row of map) for (let c of row) if (c === '.' || c === 'o') dots++; |
| 357 | + |
| 358 | + // Restart game loop |
| 359 | + requestAnimationFrame(gameLoop); |
260 | 360 | } |
261 | 361 |
|
262 | 362 | document.addEventListener('keydown', e => { |
| 363 | + if (gameOver) { |
| 364 | + if (e.key === ' ' || e.code === 'Space') { |
| 365 | + resetGame(); |
| 366 | + } |
| 367 | + return; |
| 368 | + } |
| 369 | + |
263 | 370 | if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.key)) { |
264 | 371 | e.preventDefault(); |
265 | 372 | let moved = false; |
|
290 | 397 | function gameLoop(timestamp) { |
291 | 398 | ctx.clearRect(0, 0, canvas.width, canvas.height); |
292 | 399 | drawMap(); |
| 400 | + |
| 401 | + if (gameOver) { |
| 402 | + drawGameOver(); |
| 403 | + return; |
| 404 | + } |
| 405 | + |
293 | 406 | if (!lastMonsterMove) lastMonsterMove = timestamp; |
294 | 407 | if (timestamp - lastMonsterMove > MONSTER_MOVE_INTERVAL) { |
295 | 408 | moveMonsters(); |
296 | 409 | lastMonsterMove = timestamp; |
| 410 | + // Check for collisions after monsters move |
| 411 | + checkCollisions(); |
297 | 412 | } |
| 413 | + |
298 | 414 | drawMonsters(); |
299 | 415 | drawPacman(); |
300 | 416 | drawScore(); |
| 417 | + |
301 | 418 | if (score >= dots) { |
302 | 419 | ctx.fillStyle = '#25d6a2'; |
303 | 420 | ctx.font = '48px Arial'; |
304 | | - ctx.fillText('All Features Collected!', canvas.width/2 - 260, canvas.height/2); |
| 421 | + ctx.textAlign = 'center'; |
| 422 | + ctx.fillText('All Features Collected!', canvas.width/2, canvas.height/2); |
| 423 | + ctx.font = '28px Arial'; |
| 424 | + ctx.fillText('Press SPACE to play again', canvas.width/2, canvas.height/2 + 50); |
| 425 | + gameOver = true; |
| 426 | + ctx.textAlign = 'left'; |
305 | 427 | return; |
306 | 428 | } |
| 429 | + |
307 | 430 | requestAnimationFrame(gameLoop); |
308 | 431 | } |
309 | 432 |
|
|
0 commit comments