1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5<!-- This site is hosted on Netlify. Anyone can build and deploy a site 6 like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=6c2f0266-a311-41bd-8eaa-291f32a2a992 7 Netlify hosting facts for this site: static/SSR served via Netlify Edge. --> 8 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> 9 <title>Dragon Punks Hero</title> 10 <style> 11 body { 12 margin: 0; 13 overflow: hidden; 14 background-color: #222; 15 text-align: center; 16 touch-action: none; 17 } 18 #gameContainer { 19 position: relative; 20 margin: auto; 21 max-width: 768px; 22 width: 100%; 23 height: 100vh; 24 } 25 #backgroundImage { 26 position: absolute; 27 top: 0; 28 left: 0; 29 width: 100%; 30 height: 100%; 31 object-fit: cover; 32 z-index: 0; 33 } 34 canvas { 35 display: block; 36 background: transparent; 37 margin: auto; 38 max-width: 100%; 39 max-height: 100vh; 40 position: relative; 41 z-index: 1; 42 } 43 #playAgain { 44 display: none; 45 position: absolute; 46 top: 60%; 47 left: 50%; 48 transform: translate(-50%, -50%); 49 font-size: 20px; 50 padding: 10px 20px; 51 cursor: pointer; 52 background-color: #000000; 53 color: #00FF00; 54 border: none; 55 border-radius: 8px; 56 z-index: 10; 57 touch-action: manipulation; 58 text-shadow: 0 0 5px #00FF00, 0 0 10px #00FF00; 59 animation: pulseGlow 1.5s infinite alternate; 60 } 61 #playAgain:hover { 62 color: #FFFFFF; 63 text-shadow: 0 0 5px #FFFFFF, 0 0 10px #FFFFFF; 64 } 65 #tapToStart { 66 display: none; 67 position: absolute; 68 top: 70%; 69 left: 50%; 70 transform: translate(-50%, -50%); 71 font-size: 24px; 72 padding: 10px 20px; 73 cursor: pointer; 74 background-color: #000000; 75 color: #00FF00; 76 border: none; 77 border-radius: 8px; 78 z-index: 10; 79 touch-action: manipulation; 80 text-shadow: 0 0 5px #00FF00, 0 0 10px #00FF00; 81 animation: pulseGlow 1.5s infinite alternate; 82 } 83 #tapToStart:hover { 84 color: #FFFFFF; 85 text-shadow: 0 0 5px #FFFFFF, 0 0 10px #FFFFFF; 86 } 87 #showLeaderboard { 88 display: none; 89 position: absolute; 90 top: 80%; 91 left: 50%; 92 transform: translate(-50%, -50%); 93 font-size: 24px; 94 padding: 10px 20px; 95 cursor: pointer; 96 background-color: #000000; 97 color: #00FF00; 98 border: none; 99 border-radius: 8px; 100 z-index: 10; 101 touch-action: manipulation; 102 text-shadow: 0 0 5px #00FF00, 0 0 10px #00FF00; 103 animation: pulseGlow 1.5s infinite alternate; 104 } 105 #showLeaderboard:hover { 106 color: #FFFFFF; 107 text-shadow: 0 0 5px #FFFFFF, 0 0 10px #FFFFFF; 108 } 109 #leaderboard { 110 position: absolute; 111 top: 50%; 112 left: 50%; 113 transform: translate(-50%, -50%); 114 background: rgba(255, 255, 255, 0.9); 115 padding: 10px; 116 border-radius: 5px; 117 font-size: 14px; 118 max-height: 200px; 119 overflow-y: auto; 120 z-index: 10; 121 display: none; 122 } 123 @keyframes pulseGlow { 124 0% { 125 text-shadow: 0 0 5px #00FF00, 0 0 10px #00FF00, 0 0 15px #00FF00; 126 } 127 100% { 128 text-shadow: 0 0 10px #00FF00, 0 0 20px #00FF00, 0 0 30px #00FF00; 129 } 130 } 131 </style> 132</head> 133<body> 134 <div id="gameContainer">
135 <img id="backgroundImage" src="bg.jpg" alt="Background"> 136 <canvas id="gameCanvas"></canvas> 137 <button id="playAgain" onclick="restartGame()">Play Again</button> 138 <button id="tapToStart" onclick="startGame()">TAP TO START</button> 139 <!-- Temporarily disabled Leaderboard button --> 140 <!-- <button id="showLeaderboard" onclick="toggleLeaderboard()">LEADERBOARD</button> --> 141 <div id="leaderboard"></div> 142 </div> 143 <audio id="bgMusic" loop preload="auto"> 144 <source src="cyberpunk_8bit_soundtrack.wav" type="audio/wav"> 145 </audio> 146 <audio id="hitSound" preload="auto"> 147 <source src="oof.mp3" type="audio/mpeg"> 148 </audio> 149 <audio id="coinSound" preload="auto"> 150 <source src="coin.mp3" type="audio/mpeg"> 151 </audio> 152 <audio id="powerUpSound" preload="auto"> 153 <source src="yes.mp3" type="audio/mpeg"> 154 </audio> 155 <audio id="evilLaughSound" preload="auto"> 156 <source src="evil-laugh.mp3" type="audio/mpeg"> 157 </audio> 158 <!-- Temporarily disabled ethers.js script --> 159 <!--
159<script src="https://cdn.ethers.io/lib/ethers-5.7.2.umd.min.js" type="text/javascript"></script>
159 --> 160
160<script> 161 const canvas = document.getElementById("gameCanvas"); 162 const ctx = canvas.getContext("2d"); 163 const bgMusic = document.getElementById("bgMusic"); 164 const hitSound = document.getElementById("hitSound"); 165 const coinSound = document.getElementById("coinSound"); 166 const powerUpSound = document.getElementById("powerUpSound"); 167 const evilLaughSound = document.getElementById("evilLaughSound"); 168 const tapToStartButton = document.getElementById("tapToStart"); 169 170 // Temporarily disabled Web3 variables 171 /* 172 let provider; 173 let signer; 174 let walletAddress = null; 175 176 // Abstract Testnet configuration 177 const abstractTestnet = { 178 chainId: "0x2B8C", // 11124 in hex 179 chainName: "Abstract Testnet", 180 rpcUrls: ["https://api.testnet.abs.xyz"], 181 nativeCurrency: { 182 name: "Ether", 183 symbol: "ETH", 184 decimals: 18 185 }, 186 blockExplorerUrls: ["https://explorer.testnet.abs.xyz"] 187 }; 188 189 // Replace with your deployed contract address on Abstract Testnet 190 const contractAddress = "YOUR_CONTRACT_ADDRESS"; // e.g., "0x1234..." 191 const abi = [ 192 { 193 "inputs": [{"internalType": "uint256", "name": "_score", "type": "uint256"}], 194 "name": "submitScore", 195 "outputs": [], 196 "stateMutability": "nonpayable", 197 "type": "function" 198 }, 199 { 200 "inputs": [], 201 "name": "getLeaderboard", 202 "outputs": [ 203 { 204 "components": [ 205 {"internalType": "address", "name": "player", "type": "address"}, 206 {"internalType": "uint256", "name": "score", "type": "uint256"} 207 ], 208 "internalType": "struct Leaderboard.PlayerScore[]", 209 "name": "", 210 "type": "tuple[]" 211 } 212 ], 213 "stateMutability": "view", 214 "type": "function" 215 } 216 ]; 217 let contract; 218 */ 219 220 function resizeCanvas() { 221 canvas.width = window.innerWidth; 222 canvas.height = window.innerHeight; 223 if (window.innerWidth > 768) { 224 canvas.width = Math.min(768, window.innerWidth); 225 canvas.style.margin = "0 auto"; 226 } 227 document.getElementById("gameContainer").style.width = canvas.width + "px"; 228 document.getElementById("gameContainer").style.height = canvas.height + "px"; 229 } 230 resizeCanvas(); 231 window.addEventListener('resize', resizeCanvas); 232 233 const dragonImg = new Image(); 234 const rockImg = new Image(); 235 const coinImg = new Image(); 236 const speedBoostImg = new Image(); 237 const invincibilityImg = new Image(); 238 const torchBombImg = new Image(); 239 const titleLogoImg = new Image(); 240 let imagesLoaded = 0; 241 let audiosLoaded = 0; 242 const totalImages = 7; 243 const totalAudios = 5; 244 245 let frameTime = 0; 246 let gameState = "loading"; 247 let fallSpeedMultiplier = 1.0; 248 let touchHeld = false; 249 250 const loadingTimeout = setTimeout(() => { 251 if (gameState === "loading") { 252 console.warn(`Timeout triggered. Not all assets loaded (Images: ${imagesLoaded}/${totalImages}, Audios: ${audiosLoaded}/${totalAudios}). Forcing transition to title screen.`); 253 gameState = "title"; 254 tapToStartButton.style.display = "block"; 255 // document.getElementById("showLeaderboard").style.display = "block"; // Disabled 256 } 257 }, 10000); 258 259 function drawLoadingMessage() { 260 ctx.clearRect(0, 0, canvas.width, canvas.height); 261 ctx.fillStyle = "white"; 262 ctx.font = "40px Arial"; 263 ctx.textAlign = "center"; 264 ctx.fillText("Loading...", canvas.width / 2, canvas.height / 2); 265 266 const dotSpacing = 20; 267 const dotRadius = 5; 268 const dotY = canvas.height / 2 + 30; 269 const dotStartX = canvas.width / 2 - dotSpacing * 2; 270 271 for (let i = 0; i < 5; i++) {
272 const alpha = Math.sin((frameTime * 0.1 + i * Math.PI / 2.5)) * 0.5 + 0.5; 273 ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`; 274 ctx.beginPath(); 275 ctx.arc(dotStartX + i * dotSpacing, dotY, dotRadius, 0, Math.PI * 2); 276 ctx.fill(); 277 ctx.closePath(); 278 } 279 frameTime++; 280 } 281 282 function assetLoaded() { 283 if (this instanceof HTMLImageElement) { 284 imagesLoaded++; 285 console.log(`Image loaded: ${this.src}, Total: ${imagesLoaded}/${totalImages}`); 286 } else if (this instanceof HTMLAudioElement) { 287 audiosLoaded++; 288 console.log(`Audio loaded: ${this.id}, Total: ${audiosLoaded}/${totalAudios}`); 289 } 290 if (imagesLoaded === totalImages && audiosLoaded === totalAudios) { 291 console.log("All assets loaded, transitioning to title screen"); 292 gameState = "title"; 293 tapToStartButton.style.display = "block"; 294 // document.getElementById("showLeaderboard").style.display = "block"; // Disabled 295 } 296 } 297 298 dragonImg.onerror = () => console.error("Failed to load dragon.png"); 299 rockImg.onerror = () => console.error("Failed to load rock.png"); 300 coinImg.onerror = () => console.error("Failed to load coin.png"); 301 speedBoostImg.onerror = () => console.error("Failed to load speedBoost.png"); 302 invincibilityImg.onerror = () => console.error("Failed to load invincibility.png"); 303 torchBombImg.onerror = () => console.error("Failed to load torchBomb.png"); 304 titleLogoImg.onerror = () => console.error("Failed to load dp-hero-title.png"); 305 bgMusic.onerror = () => console.error("Failed to load bgMusic"); 306 hitSound.onerror = () => console.error("Failed to load oof.mp3"); 307 coinSound.onerror = () => console.error("Failed to load coin.mp3"); 308 powerUpSound.onerror = () => console.error("Failed to load yes.mp3"); 309 evilLaughSound.onerror = () => console.error("Failed to load evil-laugh.mp3"); 310 311 dragonImg.onload = assetLoaded; 312 rockImg.onload = assetLoaded; 313 coinImg.onload = assetLoaded; 314 speedBoostImg.onload = assetLoaded; 315 invincibilityImg.onload = assetLoaded; 316 torchBombImg.onload = assetLoaded; 317 titleLogoImg.onload = assetLoaded; 318 bgMusic.oncanplay = assetLoaded; 319 hitSound.oncanplay = assetLoaded; 320 coinSound.oncanplay = assetLoaded; 321 powerUpSound.oncanplay = assetLoaded; 322 evilLaughSound.oncanplay = assetLoaded; 323 324 dragonImg.src = "dragon.png"; 325 rockImg.src = "rock.png"; 326 coinImg.src = "coin.png"; 327 speedBoostImg.src = "speedBoost.png"; 328 invincibilityImg.src = "invincibility.png"; 329 torchBombImg.src = "torchBomb.png"; 330 titleLogoImg.src = "dp-hero-title.png"; 331 332 class Particle { 333 constructor(x, y, radius, color, velocity) { 334 this.x = x; 335 this.y = y; 336 this.radius = radius; 337 this.color = color; 338 this.velocity = velocity; 339 this.life = 1; 340 this.lifeDecay = 0.03; 341 } 342 update() { 343 this.x += this.velocity.x; 344 this.y += this.velocity.y; 345 this.life -= this.lifeDecay; 346 } 347 draw() { 348 ctx.save(); 349 ctx.globalAlpha = this.life; 350 ctx.beginPath(); 351 ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); 352 ctx.fillStyle = this.color; 353 ctx.fill(); 354 ctx.closePath(); 355 ctx.restore(); 356 } 357 } 358 359 class PowerUp { 360 constructor(x, y, type) { 361 this.x = x; 362 this.y = y; 363 this.width = 40; 364 this.height = 40; 365 this.type = type; 366 this.image = type === "speed" ? speedBoostImg : (type === "invincibility" ? invincibilityImg : torchBombImg); 367 this.rotation = 0; 368 this.rotationSpeed = Math.random() * 0.1 - 0.05; 369 } 370 update() { 371 this.y += 1.5 * fallSpeedMultiplier; 372 this.rotation += this.rotationSpeed; 373 } 374 draw() { 375 ctx.save(); 376 ctx.translate(this.x + this.width / 2, this.y + this.height / 2); 377 ctx.rotate(this.rotation); 378 ctx.drawImage(this.image, -this.width / 2, -this.height / 2, this.width, this.height);
379 ctx.restore(); 380 } 381 } 382 383 class Laser { 384 constructor(x, y) { 385 this.x = x; 386 this.y = y; 387 this.width = 7; 388 this.height = 40; 389 this.speed = -10; 390 } 391 update() { 392 this.y += this.speed; 393 } 394 draw() { 395 ctx.fillStyle = "#FF0000"; 396 ctx.fillRect(this.x, this.y, this.width, this.height); 397 } 398 } 399 400 class Rock { 401 constructor(x, y, size, health) { 402 this.x = x; 403 this.y = y; 404 this.width = size; 405 this.height = size; 406 this.rotation = 0; 407 this.rotationSpeed = Math.random() * 0.1 - 0.05; 408 this.health = health; 409 } 410 update() { 411 this.y += 1.5 * fallSpeedMultiplier; 412 this.rotation += this.rotationSpeed; 413 } 414 draw() { 415 ctx.save(); 416 ctx.translate(this.x + this.width / 2, this.y + this.height / 2); 417 const glowRadius = this.width * 0.8; 418 const glowAlpha = 0.3 + Math.sin(frameTime * 0.05) * 0.2; 419 const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, glowRadius); 420 gradient.addColorStop(0, `rgba(255, 165, 0, ${glowAlpha})`); 421 gradient.addColorStop(1, "rgba(255, 165, 0, 0)"); 422 ctx.fillStyle = gradient; 423 ctx.beginPath(); 424 ctx.arc(0, 0, glowRadius, 0, Math.PI * 2, false); 425 ctx.fill(); 426 ctx.closePath(); 427 ctx.rotate(this.rotation); 428 ctx.drawImage(rockImg, -this.width / 2, -this.height / 2, this.width, this.height); 429 ctx.restore(); 430 } 431 } 432 433 class MagmaShockwave { 434 constructor() { 435 this.x = 0; 436 this.y = -50; 437 this.width = canvas.width; 438 this.height = 50; 439 this.speed = 5; 440 } 441 update() { 442 this.y += this.speed; 443 } 444 draw() { 445 ctx.fillStyle = "#FF4500"; 446 ctx.fillRect(this.x, this.y, this.width, this.height); 447 } 448 } 449 450 const dragon = { 451 x: canvas.width / 2 - 50, 452 y: canvas.height / 2, 453 width: 100, 454 height: 100, 455 velocityY: 0, 456 gravity: 0.2, 457 speedX: 0, 458 flashing: 0, 459 flying: false, 460 targetScaleX: 1, 461 currentScaleX: 1, 462 speedBoost: false, 463 speedBoostTimer: 0, 464 invincible: false, 465 invincibleTimer: 0, 466 isFlyingInputActive: false, 467 laserCooldowns: [0], 468 invincibilityCount: 0, 469 torchBombTimer: 0, 470 flyScoreTimer: 0, 471 torchBombFlyBonusTimer: 0 472 }; 473 474 let rocks = []; 475 let coins = []; 476 let powerUps = []; 477 let particles = []; 478 let lasers = []; 479 let magmaShockwaves = []; 480 let score = 0; 481 let lastLifeScore = 0; 482 let rockSpawnRate = 0.0025; 483 let lives = 3; 484 let musicPlayed = false; 485 let evilLaughTimer = 7200; 486 let lastTapTime = 0; 487 let lastSpacePress = 0; 488 const doubleTapThreshold = 300; 489 490 setInterval(() => { 491 if (gameState === "playing") { 492 fallSpeedMultiplier *= 1.05; 493 console.log(`Fall speed increased to ${fallSpeedMultiplier.toFixed(2)}x`); 494 } 495 }, 33000); 496 497 setInterval(() => { 498 if (gameState === "playing" && rockSpawnRate < 0.015) { 499 rockSpawnRate += 0.001; 500 console.log("Rock spawn rate increased to: ", rockSpawnRate); 501 } 502 }, 15000); 503 504 function tryPlayMusic() { 505 if (!musicPlayed) { 506 bgMusic.play() 507 .then(() => {
508 console.log("Background music started successfully"); 509 musicPlayed = true; 510 }) 511 .catch(err => console.error("Failed to play background music: ", err)); 512 } 513 } 514 515 function playEvilLaugh() { 516 evilLaughSound.currentTime = 0; 517 evilLaughSound.play().catch(err => console.error("Failed to play evil laugh: ", err)); 518 } 519 520 function playHitSound() { 521 hitSound.currentTime = 0; 522 hitSound.play() 523 .then(() => console.log("Hit sound (oof.mp3) played successfully")) 524 .catch(err => console.error("Failed to play hit sound (oof.mp3): ", err)); 525 } 526 527 canvas.addEventListener('touchstart', (e) => { 528 e.preventDefault(); 529 const touch = e.touches[0]; 530 const currentTime = Date.now(); 531 const tapX = touch.clientX; 532 533 if (gameState === "title") { 534 startGame(); 535 } else if (gameState === "playing") { 536 if (currentTime - lastTapTime <= doubleTapThreshold) { 537 dragon.isFlyingInputActive = true; 538 } 539 lastTapTime = currentTime; 540 const halfScreen = canvas.width / 2; 541 if (tapX < halfScreen) { 542 dragon.speedX = -2.5; 543 dragon.targetScaleX = -1; 544 } else { 545 dragon.speedX = 2.5; 546 dragon.targetScaleX = 1; 547 } 548 } 549 }); 550 551 canvas.addEventListener('touchend', (e) => { 552 e.preventDefault(); 553 dragon.speedX = 0; 554 dragon.isFlyingInputActive = false; 555 touchHeld = false; 556 }); 557 558 canvas.addEventListener('touchmove', (e) => { 559 e.preventDefault(); 560 touchHeld = true; 561 }); 562 563 window.addEventListener("keydown", (e) => { 564 if (gameState === "title" && (e.code === "Space" || e.code === "Enter")) { 565 startGame(); 566 } else if (gameState === "playing") { 567 if (e.code === "Space") { 568 const currentTime = Date.now(); 569 if (currentTime - lastSpacePress <= doubleTapThreshold) { 570 dragon.isFlyingInputActive = true; 571 } 572 lastSpacePress = currentTime; 573 } 574 if (e.code === "ArrowLeft") { 575 dragon.speedX = -2.5; 576 dragon.targetScaleX = -1; 577 } 578 if (e.code === "ArrowRight") { 579 dragon.speedX = 2.5; 580 dragon.targetScaleX = 1; 581 } 582 } 583 }); 584 585 window.addEventListener("keyup", (e) => { 586 if (e.code === "ArrowLeft" || e.code === "ArrowRight") { 587 dragon.speedX = 0; 588 } 589 if (e.code === "Space") { 590 dragon.isFlyingInputActive = false; 591 } 592 }); 593 594 function drawDragon() { 595 if (dragon.flashing % 2 === 0) { 596 ctx.save(); 597 ctx.translate(dragon.x + dragon.width / 2, dragon.y + dragon.height / 2); 598 ctx.scale(dragon.currentScaleX, 1); 599 ctx.drawImage(dragonImg, -dragon.width / 2, -dragon.height / 2, dragon.width, dragon.height); 600 if (dragon.invincible) { 601 ctx.globalAlpha = 0.5; 602 ctx.drawImage(dragonImg, -dragon.width / 2, -dragon.height / 2, dragon.width, dragon.height); 603 } 604 ctx.restore(); 605 } 606 } 607 608 function drawRocks() { 609 rocks.forEach(rock => rock.draw()); 610 } 611 612 function drawCoins() { 613 coins.forEach(coin => { 614 ctx.save(); 615 ctx.translate(coin.x + coin.width / 2, coin.y + coin.height / 2); 616 ctx.rotate(coin.rotation); 617 ctx.drawImage(coinImg, -coin.width / 2, -coin.height / 2, coin.width, coin.height);
618 ctx.restore(); 619 }); 620 } 621 622 function drawPowerUps() { 623 powerUps.forEach(powerUp => powerUp.draw()); 624 } 625 626 function drawParticles() { 627 particles.forEach(particle => particle.draw()); 628 } 629 630 function drawLasers() { 631 lasers.forEach(laser => laser.draw()); 632 } 633 634 function drawMagmaShockwaves() { 635 magmaShockwaves.forEach(shockwave => shockwave.draw()); 636 } 637 638 // Temporarily disabled Web3 connect wallet function 639 /* 640 async function connectWallet() { 641 if (typeof window.ethereum !== 'undefined') { 642 try { 643 await window.ethereum.request({ 644 method: 'wallet_addEthereumChain', 645 params: [abstractTestnet] 646 }); 647 provider = new ethers.providers.Web3Provider(window.ethereum); 648 await provider.send("eth_requestAccounts", []); 649 signer = provider.getSigner(); 650 walletAddress = await signer.getAddress(); 651 const currentChainId = await window.ethereum.request({ method: 'eth_chainId' }); 652 if (currentChainId !== abstractTestnet.chainId) { 653 await window.ethereum.request({ 654 method: 'wallet_switchEthereumChain', 655 params: [{ chainId: abstractTestnet.chainId }] 656 }); 657 } 658 contract = new ethers.Contract(contractAddress, abi, signer); 659 console.log("Connected to wallet on Abstract Testnet:", walletAddress); 660 fetchLeaderboard(); 661 } catch (error) { 662 console.error("Failed to connect wallet:", error); 663 if (error.code === 4902) { 664 console.log("Abstract Testnet not added to MetaMask."); 665 } 666 } 667 } else { 668 console.log("MetaMask not detected. Please install MetaMask to play with blockchain features."); 669 } 670 } 671 */ 672 673 // Temporarily disabled fetchLeaderboard function 674 /* 675 async function fetchLeaderboard() { 676 if (!walletAddress || !contract) { 677 console.log("Wallet not connected. Leaderboard unavailable."); 678 return; 679 } 680 try { 681 const data = await contract.getLeaderboard(); 682 const leaderboardData = data.map(entry => ({ 683 address: entry.player, 684 score: entry.score.toNumber() 685 })); 686 leaderboardData.sort((a, b) => b.score - a.score); 687 const top5 = leaderboardData.slice(0, 5); 688 const leaderboardDiv = document.getElementById('leaderboard'); 689 leaderboardDiv.style.display = 'block'; 690 leaderboardDiv.innerHTML = '<h3>Leaderboard</h3>' + 691 top5.map((entry, index) => 692 `<div>${index + 1}. ${entry.address.slice(0, 6)}...${entry.address.slice(-4)}: ${entry.score}</div>` 693 ).join(''); 694 } catch (error) { 695 console.error("Error fetching leaderboard:", error); 696 } 697 } 698 */ 699 700 // Temporarily disabled submitScore function 701 /* 702 async function submitScore() { 703 if (walletAddress && contract && gameState === "gameOver") { 704 try { 705 const tx = await contract.submitScore(score); 706 await tx.wait(); 707 console.log("Score submitted to Abstract Testnet:", score); 708 fetchLeaderboard(); 709 } catch (error) { 710 console.error("Error submitting score:", error); 711 } 712 } 713 } 714 */ 715 716 // Temporarily disabled toggleLeaderboard function 717 /* 718 function toggleLeaderboard() { 719 if (gameState !== "title") return; 720 const leaderboardDiv = document.getElementById("leaderboard"); 721 if (leaderboardDiv.style.display === "block") { 722 leaderboardDiv.style.display = "none"; 723 } else { 724 fetchLeaderboard(); 725 } 726 } 727 */ 728 729 function startGame() { 730 console.log("Starting game..."); 731 clearTimeout(loadingTimeout); 732 gameState = "playing"; 733 tapToStartButton.style.display = "none"; 734 // document.getElementById("showLeaderboard").style.display = "none"; // Disabled 735 document.getElementById("leaderboard").style.display = "none"; 736 tryPlayMusic(); 737 } 738
739 function update() { 740 if (gameState !== "playing") return; 741 742 frameTime++; 743 744 if (dragon.isFlyingInputActive) { 745 dragon.flying = true; 746 dragon.velocityY -= 0.3; 747 if (dragon.velocityY < -3) dragon.velocityY = -3; 748 dragon.flyScoreTimer++; // Increment fly time counter 749 if (dragon.flyScoreTimer >= 30) { // Every 30 frames (0.5 seconds at 60 FPS) 750 score += 1; // Add 1 point for every half second of fly time 751 dragon.flyScoreTimer = 0; // Reset timer 752 } 753 if (dragon.torchBombFlyBonusTimer > 0) { 754 // Existing bonus logic remains 755 if (dragon.flyScoreTimer >= 60) { 756 score += 100; 757 dragon.flyScoreTimer = 0; 758 } 759 } 760 } else { 761 dragon.flying = false; 762 dragon.velocityY += dragon.gravity; 763 if (dragon.velocityY > 5) dragon.velocityY = 5; 764 dragon.flyScoreTimer = 0; // Reset fly time when not flying 765 } 766 767 dragon.y += dragon.velocityY; 768 dragon.x += dragon.speedX; 769 770 const flipSpeed = 0.1; 771 dragon.currentScaleX += (dragon.targetScaleX - dragon.currentScaleX) * flipSpeed; 772 773 evilLaughTimer--; 774 if (evilLaughTimer === 180) { 775 magmaShockwaves.push(new MagmaShockwave()); 776 } 777 if (evilLaughTimer <= 0) { 778 playEvilLaugh(); 779 evilLaughTimer = 7200; 780 } 781 782 for (let i = 0; i < dragon.laserCooldowns.length; i++) { 783 dragon.laserCooldowns[i]--; 784 if (dragon.laserCooldowns[i] <= 0) { 785 const laserXOffset = (i - Math.floor(dragon.laserCooldowns.length / 2)) * 25; 786 lasers.push(new Laser(dragon.x + dragon.width / 2 - 3.5 + laserXOffset, dragon.y)); 787 dragon.laserCooldowns[i] = dragon.speedBoost ? 24 : 120; 788 } 789 } 790 791 if (dragon.speedBoost) { 792 dragon.speedBoostTimer--; 793 if (dragon.speedBoostTimer <= 0) dragon.speedBoost = false; 794 } 795 if (dragon.invincible) { 796 dragon.invincibleTimer--; 797 if (dragon.invincibleTimer <= 0) dragon.invincible = false; 798 } 799 800 if (dragon.torchBombTimer > 0) { 801 dragon.torchBombTimer--; 802 if (dragon.torchBombTimer === 299) { 803 dragon.torchBombFlyBonusTimer = 600; 804 } 805 rocks.forEach((rock, rIndex) => { 806 for (let i = 0; i < 5; i++) { 807 particles.push(new Particle( 808 rock.x + rock.width / 2, 809 rock.y + rock.height / 2, 810 Math.random() * 5 + 2, 811 "orange", 812 { x: (Math.random() - 0.5) * 6, y: (Math.random() - 0.5) * 6 } 813 )); 814 } 815 }); 816 if (dragon.torchBombTimer <= 0) { 817 while (rocks.length > 0) { 818 const rock = rocks.pop(); 819 coins.push({ 820 x: rock.x, 821 y: rock.y, 822 width: 30, 823 height: 30, 824 rotation: 0, 825 rotationSpeed: Math.random() * 0.1 - 0.05 826 }); 827 score += 5; 828 } 829 } 830 } 831 832 if (dragon.torchBombFlyBonusTimer > 0) { 833 dragon.torchBombFlyBonusTimer--; 834 } 835 836 if (dragon.y + dragon.height > canvas.height) { 837 dragon.y = canvas.height - dragon.height; 838 dragon.velocityY = 0; 839 } 840 if (dragon.y < 0) { 841 dragon.y = 0; 842 dragon.velocityY = 0; 843 } 844 if (dragon.x < 0) dragon.x = 0; 845 if (dragon.x + dragon.width > canvas.width) dragon.x = canvas.width - dragon.width; 846
847 lasers.forEach((laser, lIndex) => { 848 laser.update(); 849 if (lasers.length === 0) return; 850 851 if (laser.y < 0) { 852 lasers.splice(lIndex, 1); 853 return; 854 } 855 856 rocks.forEach((rock, rIndex) => { 857 const collision = ( 858 laser.x < rock.x + rock.width && 859 laser.x + laser.width > rock.x && 860 laser.y < rock.y + rock.height && 861 laser.y + laser.height > rock.y 862 ); 863 if (collision) { 864 rock.health--; 865 lasers.splice(lIndex, 1); 866 if (rock.health <= 0) { 867 for (let i = 0; i < 10; i++) { 868 particles.push(new Particle( 869 rock.x + rock.width / 2, 870 rock.y + rock.height / 2, 871 Math.random() * 5 + 2, 872 "orange", 873 { x: (Math.random() - 0.5) * 6, y: (Math.random() - 0.5) * 6 } 874 )); 875 } 876 rocks.splice(rIndex, 1); 877 score += 5; 878 } else if (rock.width > 30) { 879 const newSize = rock.width / 2; 880 rocks.splice(rIndex, 1); 881 rocks.push(new Rock(rock.x - newSize / 2, rock.y, newSize, 1)); 882 rocks.push(new Rock(rock.x + newSize / 2, rock.y, newSize, 1)); 883 } 884 } 885 }); 886 }); 887 888 rocks.forEach((rock, rIndex) => { 889 rock.update(); 890 if (rock.y > canvas.height) { 891 rocks.splice(rIndex, 1); 892 } else if (dragon.torchBombTimer <= 0) { 893 const collision = ( 894 dragon.x + dragon.width * 0.15 < rock.x + rock.width && 895 dragon.x + dragon.width * 0.85 > rock.x && 896 dragon.y + dragon.height * 0.15 < rock.y + rock.height && 897 dragon.y + dragon.height * 0.85 > rock.y 898 ); 899 if (collision && !dragon.invincible) { 900 console.log("Dragon hit by rock"); 901 lives--; 902 rocks.splice(rIndex, 1); 903 playHitSound(); 904 dragon.flashing = 90; 905 dragon.invincible = true; 906 dragon.invincibleTimer = 180; 907 for (let i = 0; i < 10; i++) { 908 particles.push(new Particle( 909 dragon.x + dragon.width / 2, 910 dragon.y + dragon.height / 2, 911 Math.random() * 5 + 2, 912 "red", 913 { x: (Math.random() - 0.5) * 6, y: (Math.random() - 0.5) * 6 } 914 )); 915 } 916 if (lives < 1) { 917 gameState = "gameOver"; 918 playEvilLaugh(); 919 } 920 } 921 } 922 }); 923 924 coins.forEach((coin, cIndex) => { 925 coin.y += 1.5 * fallSpeedMultiplier; 926 coin.rotation += coin.rotationSpeed; 927 if (coin.y > canvas.height) { 928 coins.splice(cIndex, 1); 929 } else { 930 const coinCollision = ( 931 dragon.x + dragon.width * 0.2 < coin.x + coin.width && 932 dragon.x + dragon.width * 0.8 > coin.x && 933 dragon.y + dragon.height * 0.2 < coin.y + coin.height && 934 dragon.y + dragon.height * 0.8 > coin.y 935 ); 936 if (coinCollision) { 937 score += 1; 938 coinSound.play(); 939 coins.splice(cIndex, 1); 940 for (let i = 0; i < 8; i++) { 941 particles.push(new Particle( 942 coin.x + coin.width / 2, 943 coin.y + coin.height / 2, 944 Math.random() * 3 + 1, 945 "yellow", 946 { x: (Math.random() - 0.5) * 4, y: (Math.random() - 0.5) * 4 } 947 )); 948 } 949 if (score >= lastLifeScore + 1000) { 950 lives++; 951 lastLifeScore = Math.floor(score / 1000) * 1000; 952 } 953 } 954 } 955 }); 956
957 powerUps.forEach((powerUp, pIndex) => { 958 powerUp.update(); 959 if (powerUp.y > canvas.height) { 960 powerUps.splice(pIndex, 1); 961 } else { 962 const powerUpCollision = ( 963 dragon.x + dragon.width * 0.2 < powerUp.x + powerUp.width && 964 dragon.x + dragon.width * 0.8 > powerUp.x && 965 dragon.y + dragon.height * 0.2 < powerUp.y + powerUp.height && 966 dragon.y + dragon.height * 0.8 > powerUp.y 967 ); 968 if (powerUpCollision) { 969 powerUpSound.play(); 970 if (powerUp.type === "speed") { 971 dragon.speedBoost = true; 972 dragon.speedBoostTimer = 780; 973 } else if (powerUp.type === "invincibility") { 974 dragon.invincible = true; 975 dragon.invincibleTimer = 420; 976 dragon.invincibilityCount++; 977 if (dragon.invincibilityCount % 3 === 0 && dragon.laserCooldowns.length < 5) { 978 dragon.laserCooldowns.push(120 - dragon.laserCooldowns.length * 30); 979 } 980 } else if (powerUp.type === "torchBomb") { 981 dragon.torchBombTimer = 300; 982 } 983 powerUps.splice(pIndex, 1); 984 for (let i = 0; i < 10; i++) { 985 particles.push(new Particle( 986 powerUp.x + powerUp.width / 2, 987 powerUp.y + powerUp.height / 2, 988 Math.random() * 4 + 2, 989 powerUp.type === "speed" ? "blue" : (powerUp.type === "invincibility" ? "green" : "gold"), 990 { x: (Math.random() - 0.5) * 5, y: (Math.random() - 0.5) * 5 } 991 )); 992 } 993 } 994 } 995 }); 996 997 magmaShockwaves.forEach((shockwave, sIndex) => { 998 shockwave.update(); 999 if (shockwave.y > canvas.height) { 1000 magmaShockwaves.splice(sIndex, 1); 1001 } else { 1002 const shockwaveCollision = ( 1003 dragon.x + dragon.width * 0.15 < shockwave.x + shockwave.width && 1004 dragon.x + dragon.width * 0.85 > shockwave.x && 1005 dragon.y + dragon.height * 0.15 < shockwave.y + shockwave.height && 1006 dragon.y + dragon.height * 0.85 > shockwave.y 1007 ); 1008 if (shockwaveCollision && !dragon.invincible) { 1009 lives--; 1010 magmaShockwaves.splice(sIndex, 1); 1011 playHitSound(); 1012 dragon.flashing = 90; 1013 dragon.invincible = true; 1014 dragon.invincibleTimer = 180; 1015 for (let i = 0; i < 10; i++) { 1016 particles.push(new Particle( 1017 dragon.x + dragon.width / 2, 1018 dragon.y + dragon.height / 2, 1019 Math.random() * 5 + 2, 1020 "red", 1021 { x: (Math.random() - 0.5) * 6, y: (Math.random() - 0.5) * 6 } 1022 )); 1023 } 1024 if (lives < 1) { 1025 gameState = "gameOver"; 1026 playEvilLaugh(); 1027 } 1028 } 1029 } 1030 }); 1031 1032 particles.forEach((particle, pIndex) => { 1033 particle.update(); 1034 if (particle.life <= 0) { 1035 particles.splice(pIndex, 1); 1036 } 1037 }); 1038 1039 if (Math.random() < rockSpawnRate) { 1040 const rockSize = Math.random() * (120 - 30) + 30; 1041 rocks.push(new Rock(Math.random() * canvas.width, -50, rockSize, score >= 5000 ? (rockSize > 60 ? 4 : 2) : (rockSize > 60 ? 2 : 1))); 1042 } 1043 if (Math.random() < 0.01) { 1044 coins.push({ 1045 x: Math.random() * canvas.width, 1046 y: -50, 1047 width: 30, 1048 height: 30, 1049 rotation: 0, 1050 rotationSpeed: Math.random() * 0.1 - 0.05 1051 }); 1052 } 1053 if (Math.random() < 0.0015) {
1054 const powerUpType = Math.random() < 0.33 ? "speed" : (Math.random() < 0.66 ? "invincibility" : "torchBomb"); 1055 powerUps.push(new PowerUp(Math.random() * canvas.width, -50, powerUpType)); 1056 } 1057 1058 if (dragon.flashing > 0) dragon.flashing--; 1059 } 1060 1061 function gameLoop() { 1062 ctx.clearRect(0, 0, canvas.width, canvas.height); 1063 1064 if (gameState === "loading") { 1065 drawLoadingMessage(); 1066 } else if (gameState === "title") { 1067 console.log("Drawing title screen"); 1068 const logoWidth = titleLogoImg.width; 1069 const logoHeight = titleLogoImg.height; 1070 ctx.drawImage(titleLogoImg, canvas.width / 2 - logoWidth / 2, canvas.height / 2 - logoHeight / 2 - 50, logoWidth, logoHeight); 1071 } else if (gameState === "playing") { 1072 ctx.fillStyle = "white"; 1073 ctx.font = "20px Arial"; 1074 ctx.fillText("Score: " + score, 30, 30); 1075 ctx.fillText("Lives: " + lives, 30, 60); 1076 if (dragon.speedBoost) { 1077 ctx.fillStyle = "blue"; 1078 ctx.fillText(`Rapid Fire: ${Math.ceil(dragon.speedBoostTimer / 60)}s`, 30, 90); 1079 } 1080 if (dragon.invincible) { 1081 ctx.fillStyle = "green"; 1082 ctx.fillText(`Invincibility: ${Math.ceil(dragon.invincibleTimer / 60)}s`, 30, 120); 1083 } 1084 if (dragon.flying && dragon.torchBombFlyBonusTimer > 0) { 1085 ctx.fillStyle = "cyan"; 1086 ctx.fillText(`Flying Bonus: ${Math.ceil(dragon.torchBombFlyBonusTimer / 60)}s`, 30, 150); 1087 } 1088 drawDragon(); 1089 drawRocks(); 1090 drawCoins(); 1091 drawPowerUps(); 1092 drawParticles(); 1093 drawLasers(); 1094 drawMagmaShockwaves(); 1095 update(); 1096 document.getElementById("playAgain").style.display = "none"; 1097 } else if (gameState === "gameOver") { 1098 ctx.fillStyle = "rgba(50, 0, 0, 0.5)"; 1099 ctx.fillRect(0, 0, canvas.width, canvas.height); 1100 ctx.fillStyle = "white"; 1101 ctx.textAlign = "center"; 1102 ctx.font = "40px Arial"; 1103 ctx.fillText("Game Over!", canvas.width / 2, canvas.height / 2 - 60); 1104 ctx.font = "20px Arial"; 1105 ctx.fillText("Score: " + score, canvas.width / 2, canvas.height / 2 - 20); 1106 document.getElementById("playAgain").style.display = "block"; 1107 // submitScore(); // Disabled 1108 ctx.textAlign = "left"; 1109 } 1110 1111 requestAnimationFrame(gameLoop); 1112 } 1113 1114 function restartGame() { 1115 document.getElementById("playAgain").style.display = "none"; 1116 gameState = "playing"; 1117 score = 0; 1118 lastLifeScore = 0; 1119 lives = 3; 1120 dragon.x = canvas.width / 2 - 50; 1121 dragon.y = canvas.height / 2; 1122 dragon.velocityY = 0; 1123 dragon.speedX = 0; 1124 dragon.flashing = 0; 1125 dragon.flying = false; 1126 dragon.targetScaleX = 1; 1127 dragon.currentScaleX = 1; 1128 dragon.speedBoost = false; 1129 dragon.speedBoostTimer = 0; 1130 dragon.invincible = false; 1131 dragon.invincibleTimer = 0; 1132 dragon.isFlyingInputActive = false; 1133 dragon.laserCooldowns = [0]; 1134 dragon.invincibilityCount = 0; 1135 dragon.torchBombTimer = 0; 1136 dragon.flyScoreTimer = 0; 1137 dragon.torchBombFlyBonusTimer = 0; 1138 rocks = []; 1139 coins = []; 1140 powerUps = []; 1141 particles = []; 1142 lasers = []; 1143 magmaShockwaves = []; 1144 rockSpawnRate = 0.0025; 1145 fallSpeedMultiplier = 1.0; 1146 musicPlayed = false; 1147 lastTapTime = 0; 1148 lastSpacePress = 0; 1149 touchHeld = false; 1150 evilLaughTimer = 7200; 1151 frameTime = 0; 1152 tryPlayMusic(); 1153 } 1154 1155 // Temporarily disabled Web3 connect wallet call 1156 // connectWallet(); 1157 1158 console.log("Starting game loop..."); 1159 gameLoop(); 1160 </script>
1160 1161</body> 1162</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.