GuangyuanSD commited on
Commit
39f7c06
·
verified ·
1 Parent(s): 9dcadb3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +623 -318
index.html CHANGED
@@ -371,333 +371,637 @@
371
  <source src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" type="audio/mpeg">
372
  </audio>
373
 
374
- <script>
375
  document.addEventListener('DOMContentLoaded', () => {
376
- // 更新游戏状态对象
377
- const state = {
378
- score: 0,
379
- hits: 0,
380
- health: 10,
381
- targets: [],
382
- projectiles: [],
383
- stars: [],
384
- currentWeapon: 'basic',
385
- weapons: {
386
- basic: { price: 0, fireRate: 500, projectileCount: 1, spread: 0, bouncy: false },
387
- rapid: { price: 50, fireRate: 250, projectileCount: 1, spread: 0, bouncy: false },
388
- double: { price: 150, fireRate: 250, projectileCount: 2, spread: 10, bouncy: false },
389
- shotgun: { price: 300, fireRate: 500, projectileCount: 5, spread: 20, bouncy: false },
390
- bouncy: {
391
- price: 500,
392
- fireRate: 250,
393
- projectileCount: 8,
394
- spread: 360,
395
- bouncy: true
396
- }
397
- },
398
- lastShot: 0,
399
- colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'],
400
- currentColorIndex: 0,
401
- targetSpeed: 2,
402
- targetCount: 1,
403
- gameOver: false,
404
- centerRadius: 300,
405
- targetSpawnBuffer: 100
406
- };
407
-
408
- // 初始化游戏
409
- function initGame() {
410
- initHealthBar();
411
- createTargets(state.targetCount);
412
- setupCursor();
413
- gameLoop();
414
- }
415
-
416
- // 游戏主循环
417
- function gameLoop() {
418
- if (!state.gameOver) {
419
- updateTargets();
420
- updateProjectiles();
421
- checkCollisions();
422
- checkCenterCollision();
423
- requestAnimationFrame(gameLoop);
424
- }
425
- }
426
-
427
- // 创建目标
428
- function createTargets(count) {
429
- const targetsContainer = document.getElementById('targets-container');
430
- targetsContainer.innerHTML = '';
431
- state.targets = [];
432
-
433
- for (let i = 0; i < count; i++) {
434
- createTarget(i);
435
- }
436
- }
437
-
438
- function createTarget(index) {
439
- const target = document.createElement('div');
440
- target.className = 'target new-target-flash';
441
- target.style.backgroundImage = 'url("https://huggingface.co/front/assets/huggingface_logo-noborder.svg")';
442
-
443
- const centerX = window.innerWidth / 2;
444
- const centerY = window.innerHeight / 2;
445
-
446
- let x, y;
447
- let attempts = 0;
448
- const maxAttempts = 10;
449
-
450
- // 确保目标不会生成在中心区域附近
451
- do {
452
- x = Math.random() * (window.innerWidth - 250);
453
- y = Math.random() * (window.innerHeight - 250);
454
- attempts++;
455
-
456
- const dx = x - centerX;
457
- const dy = y - centerY;
458
- const distance = Math.sqrt(dx * dx + dy * dy);
459
-
460
- if (distance > state.centerRadius || attempts >= maxAttempts) {
461
- break;
462
- }
463
- } while (true);
464
-
465
- target.style.left = `${x}px`;
466
- target.style.top = `${y}px`;
467
-
468
- // 移除闪烁动画
469
- setTimeout(() => {
470
- target.classList.remove('new-target-flash');
471
- }, 500);
472
-
473
- document.getElementById('targets-container').appendChild(target);
474
-
475
- state.targets.push({
476
- element: target,
477
- x: x,
478
- y: y,
479
- vx: (Math.random() - 0.5) * state.targetSpeed,
480
- vy: (Math.random() - 0.5) * state.targetSpeed,
481
- index: index
482
- });
483
- }
484
-
485
- // 更新目标位置
486
- function updateTargets() {
487
- state.targets.forEach(target => {
488
- target.x += target.vx;
489
- target.y += target.vy;
490
-
491
- // 边界反弹
492
- if (target.x <= 0 || target.x >= window.innerWidth - 250) {
493
- target.vx = -target.vx;
494
- }
495
-
496
- if (target.y <= 0 || target.y >= window.innerHeight - 250) {
497
- target.vy = -target.vy;
498
- }
499
-
500
- target.element.style.left = `${target.x}px`;
501
- target.element.style.top = `${target.y}px`;
502
- });
503
- }
504
-
505
- // 射击处理
506
- function handleShoot(e) {
507
- if (state.gameOver) return;
508
-
509
- const now = Date.now();
510
- const weapon = state.weapons[state.currentWeapon];
511
-
512
- if (now - state.lastShot < weapon.fireRate) return;
513
-
514
- state.lastShot = now;
515
-
516
- for (let i = 0; i < weapon.projectileCount; i++) {
517
- createProjectile(e.clientX, e.clientY, i);
518
- }
519
- }
520
-
521
- // 创建子弹
522
- function createProjectile(mouseX, mouseY, index) {
523
- const projectile = document.createElement('div');
524
- projectile.className = 'projectile';
525
-
526
- const centerX = window.innerWidth / 2;
527
- const centerY = window.innerHeight / 2;
528
-
529
- let dx, dy;
530
- const weapon = state.weapons[state.currentWeapon];
531
-
532
- if (state.currentWeapon === 'bouncy') {
533
- const angle = (index / weapon.projectileCount) * Math.PI * 2;
534
- dx = Math.cos(angle);
535
- dy = Math.sin(angle);
536
- } else {
537
- dx = mouseX - centerX;
538
- dy = mouseY - centerY;
539
-
540
- const distance = Math.sqrt(dx * dx + dy * dy);
541
- dx /= distance;
542
- dy /= distance;
543
-
544
- const spreadAngle = (index - (weapon.projectileCount - 1) / 2) * weapon.spread * (Math.PI / 180);
545
- const cos = Math.cos(spreadAngle);
546
- const sin = Math.sin(spreadAngle);
547
- const tx = dx * cos - dy * sin;
548
- const ty = dx * sin + dy * cos;
549
-
550
- dx = tx;
551
- dy = ty;
552
- }
553
-
554
- projectile.style.backgroundColor = state.colors[state.currentColorIndex];
555
- projectile.style.left = `${centerX}px`;
556
- projectile.style.top = `${centerY}px`;
557
-
558
- document.getElementById('projectiles-container').appendChild(projectile);
559
-
560
- state.projectiles.push({
561
- element: projectile,
562
- x: centerX,
563
- y: centerY,
564
- vx: dx * 15,
565
- vy: dy * 15,
566
- bounces: 0,
567
- maxBounces: weapon.bouncy ? 3 : 0
568
- });
569
- }
570
-
571
- // 更新子弹位置
572
- function updateProjectiles() {
573
- state.projectiles.forEach((projectile, index) => {
574
- projectile.x += projectile.vx;
575
- projectile.y += projectile.vy;
576
-
577
- projectile.element.style.left = `${projectile.x}px`;
578
- projectile.element.style.top = `${projectile.y}px`;
579
-
580
- // 边界反弹
581
- if (projectile.bounces < projectile.maxBounces) {
582
- if (projectile.x <= 0 || projectile.x >= window.innerWidth) {
583
- projectile.vx = -projectile.vx;
584
- projectile.bounces++;
585
  }
586
 
587
- if (projectile.y <= 0 || projectile.y >= window.innerHeight) {
588
- projectile.vy = -projectile.vy;
589
- projectile.bounces++;
 
 
 
 
 
 
 
590
  }
591
- }
592
-
593
- // 移除超出边界的子弹
594
- if (projectile.x < -50 || projectile.x > window.innerWidth + 50 ||
595
- projectile.y < -50 || projectile.y > window.innerHeight + 50) {
596
- projectile.element.remove();
597
- state.projectiles.splice(index, 1);
598
- }
599
- });
600
- }
601
-
602
- // 碰撞检测
603
- function checkCollisions() {
604
- state.projectiles.forEach((projectile, pIndex) => {
605
- state.targets.forEach((target, tIndex) => {
606
- if (isColliding(projectile, target)) {
607
- handleHit(target, projectile);
608
- projectile.element.remove();
609
- state.projectiles.splice(pIndex, 1);
610
  }
611
- });
612
- });
613
- }
614
-
615
- // 击中处理
616
- function handleHit(target, projectile) {
617
- // 添加击中闪烁效果
618
- target.element.classList.add('hit-flash');
619
- setTimeout(() => {
620
- target.element.classList.remove('hit-flash');
621
- }, 300);
622
-
623
- // 增强击退效果
624
- const dx = target.x - projectile.x;
625
- const dy = target.y - projectile.y;
626
- const distance = Math.sqrt(dx * dx + dy * dy);
627
-
628
- const force = 15;
629
- target.vx = (dx / distance) * force;
630
- target.vy = (dy / distance) * force;
631
-
632
- state.score += 100;
633
- state.hits++;
634
- updateScore();
635
- }
636
-
637
- // 中心区域碰撞检测
638
- function checkCenterCollision() {
639
- const centerX = window.innerWidth / 2;
640
- const centerY = window.innerHeight / 2;
641
- const centerRadius = 50;
642
-
643
- state.targets.forEach(target => {
644
- const targetCenterX = target.x + 125;
645
- const targetCenterY = target.y + 125;
646
-
647
- const dx = targetCenterX - centerX;
648
- const dy = targetCenterY - centerY;
649
- const distance = Math.sqrt(dx * dx + dy * dy);
650
-
651
- if (distance < centerRadius) {
652
- handleCenterHit();
653
- }
654
- });
655
- }
656
-
657
- function handleCenterHit() {
658
- if (state.health > 0) {
659
- state.health--;
660
- updateHealth();
661
-
662
- // 全屏红色闪烁
663
- const screenFlash = document.createElement('div');
664
- screenFlash.className = 'screen-flash active';
665
- document.body.appendChild(screenFlash);
666
-
667
- setTimeout(() => {
668
- screenFlash.remove();
669
- }, 500);
670
-
671
- // 将所有目标弹出到画面边缘
672
- state.targets.forEach(target => {
673
- const centerX = window.innerWidth / 2;
674
- const centerY = window.innerHeight / 2;
675
- const targetCenterX = target.x + 125;
676
- const targetCenterY = target.y + 125;
677
 
678
- let dx = targetCenterX - centerX;
679
- let dy = targetCenterY - centerY;
 
 
 
 
 
 
680
 
681
- const distance = Math.sqrt(dx * dx + dy * dy);
682
- dx /= distance;
683
- dy /= distance;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
- target.vx = dx * 20;
686
- target.vy = dy * 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687
 
688
- target.element.classList.add('hit-flash');
689
- setTimeout(() => {
690
- target.element.classList.remove('hit-flash');
691
- }, 300);
692
- });
693
-
694
- if (state.health <= 0) {
695
- setTimeout(() => {
696
- gameOver();
697
- }, 500);
698
- }
699
- }
700
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
 
702
  // Game over
703
  function gameOver() {
@@ -725,6 +1029,7 @@ function handleCenterHit() {
725
  state.targetSpeed = 2;
726
  state.targetCount = 1;
727
  state.gameOver = false;
 
728
 
729
  // Reset UI
730
  hitCountElement.textContent = '0';
 
371
  <source src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" type="audio/mpeg">
372
  </audio>
373
 
374
+ <script>
375
  document.addEventListener('DOMContentLoaded', () => {
376
+ // Game state
377
+ const state = {
378
+ score: 0,
379
+ hits: 0,
380
+ health: 10,
381
+ targets: [],
382
+ projectiles: [],
383
+ stars: [],
384
+ currentWeapon: 'basic',
385
+ weapons: {
386
+ basic: { price: 0, fireRate: 500, projectileCount: 1, spread: 0, bouncy: false },
387
+ rapid: { price: 50, fireRate: 250, projectileCount: 1, spread: 0, bouncy: false },
388
+ double: { price: 150, fireRate: 500, projectileCount: 2, spread: 10, bouncy: false },
389
+ shotgun: { price: 300, fireRate: 800, projectileCount: 5, spread: 20, bouncy: false },
390
+ bouncy: { price: 500, fireRate: 100, projectileCount: 18, spread: 360, bouncy: true }
391
+ },
392
+ lastShot: 0,
393
+ colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'],
394
+ currentColorIndex: 0,
395
+ targetSpeed: 2,
396
+ targetCount: 1,
397
+ gameOver: false,
398
+ knockbackDistance: 30 // Added knockback distance
399
+ };
400
+
401
+ // DOM elements
402
+ const gameContainer = document.getElementById('game-container');
403
+ const starfield = document.getElementById('starfield');
404
+ const targetsContainer = document.getElementById('targets-container');
405
+ const projectilesContainer = document.getElementById('projectiles-container');
406
+ const hitMarkersContainer = document.getElementById('hit-markers-container');
407
+ const scoreDisplay = document.getElementById('score-display');
408
+ const hitCountElement = document.getElementById('hit-count');
409
+ const scoreElement = document.getElementById('score');
410
+ const customCursor = document.getElementById('custom-cursor');
411
+ const hitSound = document.getElementById('hit-sound');
412
+ const shootSound = document.getElementById('shoot-sound');
413
+ const bgMusic = document.getElementById('bg-music');
414
+ const weaponElements = document.querySelectorAll('.weapon');
415
+ const healthBar = document.getElementById('health-bar');
416
+ const gameOverScreen = document.getElementById('game-over');
417
+ const gameOverText = document.getElementById('game-over-text');
418
+ const retryButton = document.getElementById('retry-button');
419
+
420
+ // Initialize game
421
+ initHealthBar();
422
+ initStarfield();
423
+ createTargets(state.targetCount);
424
+ setupCursor();
425
+ setupWeapons();
426
+ playMusic();
427
+
428
+ // Event listeners
429
+ gameContainer.addEventListener('mousemove', handleMouseMove);
430
+ gameContainer.addEventListener('click', handleShoot);
431
+ retryButton.addEventListener('click', resetGame);
432
+
433
+ // Game loop
434
+ function gameLoop() {
435
+ if (!state.gameOver) {
436
+ updateTargets();
437
+ updateProjectiles();
438
+ checkCollisions();
439
+ checkCenterCollision();
440
+ requestAnimationFrame(gameLoop);
441
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  }
443
 
444
+ gameLoop();
445
+
446
+ // Initialize health bar
447
+ function initHealthBar() {
448
+ healthBar.innerHTML = '';
449
+ for (let i = 0; i < 10; i++) {
450
+ const healthPoint = document.createElement('div');
451
+ healthPoint.className = 'health-point';
452
+ healthBar.appendChild(healthPoint);
453
+ }
454
  }
455
+
456
+ // Update health display
457
+ function updateHealth() {
458
+ const healthPoints = document.querySelectorAll('.health-point');
459
+ healthPoints.forEach((point, index) => {
460
+ if (index < state.health) {
461
+ point.classList.remove('lost');
462
+ } else {
463
+ point.classList.add('lost');
464
+ }
465
+ });
 
 
 
 
 
 
 
 
466
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
+ // Initialize starfield
469
+ function initStarfield() {
470
+ const starCount = Math.floor(window.innerWidth * window.innerHeight / 1000);
471
+
472
+ for (let i = 0; i < starCount; i++) {
473
+ createStar();
474
+ }
475
+ }
476
 
477
+ function createStar() {
478
+ const star = document.createElement('div');
479
+ star.className = 'star';
480
+
481
+ const x = Math.random() * window.innerWidth;
482
+ const y = Math.random() * window.innerHeight;
483
+ const size = Math.random() * 3;
484
+ const opacity = Math.random();
485
+
486
+ star.style.left = `${x}px`;
487
+ star.style.top = `${y}px`;
488
+ star.style.width = `${size}px`;
489
+ star.style.height = `${size}px`;
490
+ star.style.opacity = opacity;
491
+
492
+ starfield.appendChild(star);
493
+
494
+ // Animate star (parallax effect)
495
+ const speed = 0.5 + Math.random() * 2;
496
+
497
+ function animateStar() {
498
+ const currentX = parseFloat(star.style.left);
499
+ const currentY = parseFloat(star.style.top);
500
+
501
+ // Move star outward from center
502
+ const centerX = window.innerWidth / 2;
503
+ const centerY = window.innerHeight / 2;
504
+
505
+ let dx = currentX - centerX;
506
+ let dy = currentY - centerY;
507
+
508
+ // Normalize direction vector
509
+ const distance = Math.sqrt(dx * dx + dy * dy);
510
+ dx /= distance;
511
+ dy /= distance;
512
+
513
+ // Move star
514
+ star.style.left = `${currentX + dx * speed}px`;
515
+ star.style.top = `${currentY + dy * speed}px`;
516
+
517
+ // Reset star if it goes out of bounds
518
+ if (parseFloat(star.style.left) < 0 || parseFloat(star.style.left) > window.innerWidth ||
519
+ parseFloat(star.style.top) < 0 || parseFloat(star.style.top) > window.innerHeight) {
520
+ star.style.left = `${Math.random() * window.innerWidth}px`;
521
+ star.style.top = `${Math.random() * window.innerHeight}px`;
522
+ }
523
+
524
+ requestAnimationFrame(animateStar);
525
+ }
526
+
527
+ animateStar();
528
+ }
529
 
530
+ // Create targets
531
+ function createTargets(count) {
532
+ // Flash screen when adding new targets
533
+ const screenFlash = document.createElement('div');
534
+ screenFlash.className = 'screen-flash';
535
+ screenFlash.style.backgroundColor = state.colors[state.currentColorIndex];
536
+ document.body.appendChild(screenFlash);
537
+
538
+ setTimeout(() => {
539
+ screenFlash.remove();
540
+ }, 300);
541
+
542
+ targetsContainer.innerHTML = '';
543
+ state.targets = [];
544
+
545
+ for (let i = 0; i < count; i++) {
546
+ createTarget(i);
547
+ }
548
+ }
549
 
550
+ function createTarget(index) {
551
+ const target = document.createElement('div');
552
+ target.className = 'target';
553
+
554
+ // Use huggingface emoji as target
555
+ target.style.backgroundImage = 'url("https://huggingface.co/front/assets/huggingface_logo-noborder.svg")';
556
+
557
+ // Position randomly but ensure it's fully visible and not near center
558
+ const maxX = window.innerWidth - 250;
559
+ const maxY = window.innerHeight - 250;
560
+ const centerX = window.innerWidth / 2;
561
+ const centerY = window.innerHeight / 2;
562
+ const minDistanceFromCenter = 300; // Minimum distance from center
563
+
564
+ let x, y;
565
+ let attempts = 0;
566
+ const maxAttempts = 100;
567
+
568
+ do {
569
+ x = Math.max(0, Math.min(maxX, Math.random() * maxX));
570
+ y = Math.max(0, Math.min(maxY, Math.random() * maxY));
571
+ attempts++;
572
+
573
+ // Check distance from center
574
+ const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
575
+
576
+ // If we've tried too many times, just place it anywhere
577
+ if (attempts >= maxAttempts) break;
578
+
579
+ } while (Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) < minDistanceFromCenter);
580
+
581
+ target.style.left = `${x}px`;
582
+ target.style.top = `${y}px`;
583
+
584
+ // Add hit effect
585
+ const hitEffect = document.createElement('div');
586
+ hitEffect.className = 'hit-effect';
587
+ hitEffect.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><circle cx=\'50\' cy=\'50\' r=\'40\' fill=\'' + state.colors[state.currentColorIndex] + '\' opacity=\'0.7\'/></svg>")';
588
+
589
+ target.appendChild(hitEffect);
590
+ targetsContainer.appendChild(target);
591
+
592
+ // Store target data
593
+ state.targets.push({
594
+ element: target,
595
+ hitEffect: hitEffect,
596
+ x: x,
597
+ y: y,
598
+ vx: (Math.random() - 0.5) * state.targetSpeed,
599
+ vy: (Math.random() - 0.5) * state.targetSpeed,
600
+ index: index
601
+ });
602
+ }
603
+
604
+ // Update targets position
605
+ function updateTargets() {
606
+ state.targets.forEach(target => {
607
+ // Update position
608
+ target.x += target.vx;
609
+ target.y += target.vy;
610
+
611
+ // Bounce off edges
612
+ if (target.x <= 0 || target.x >= window.innerWidth - 250) {
613
+ target.vx = -target.vx * (0.9 + Math.random() * 0.2);
614
+ }
615
+
616
+ if (target.y <= 0 || target.y >= window.innerHeight - 250) {
617
+ target.vy = -target.vy * (0.9 + Math.random() * 0.2);
618
+ }
619
+
620
+ // Random direction changes
621
+ if (Math.random() < 0.02) {
622
+ target.vx += (Math.random() - 0.5) * 0.5;
623
+ target.vy += (Math.random() - 0.5) * 0.5;
624
+
625
+ // Limit speed
626
+ const speed = Math.sqrt(target.vx * target.vx + target.vy * target.vy);
627
+ const maxSpeed = state.targetSpeed * 1.5;
628
+
629
+ if (speed > maxSpeed) {
630
+ target.vx = (target.vx / speed) * maxSpeed;
631
+ target.vy = (target.vy / speed) * maxSpeed;
632
+ }
633
+ }
634
+
635
+ // Apply position
636
+ target.element.style.left = `${target.x}px`;
637
+ target.element.style.top = `${target.y}px`;
638
+ });
639
+ }
640
+
641
+ // Setup custom cursor
642
+ function setupCursor() {
643
+ customCursor.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[state.currentColorIndex] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/></svg>")';
644
+ customCursor.style.display = 'block';
645
+ }
646
+
647
+ function handleMouseMove(e) {
648
+ customCursor.style.left = `${e.clientX - 20}px`;
649
+ customCursor.style.top = `${e.clientY - 20}px`;
650
+
651
+ // Rotate cursor slightly for visual feedback
652
+ const rotation = Math.sin(Date.now() / 200) * 5;
653
+ customCursor.style.transform = `rotate(${rotation}deg)`;
654
+ }
655
+
656
+ // Handle shooting
657
+ function handleShoot(e) {
658
+ if (state.gameOver) return;
659
+
660
+ const now = Date.now();
661
+ const weapon = state.weapons[state.currentWeapon];
662
+
663
+ if (now - state.lastShot < weapon.fireRate) return;
664
+
665
+ state.lastShot = now;
666
+
667
+ // Play shoot sound
668
+ shootSound.currentTime = 0;
669
+ shootSound.play();
670
+
671
+ // Create projectiles based on weapon type
672
+ if (state.currentWeapon === 'bouncy') {
673
+ // Special handling for bouncy weapon - 360 degree spread
674
+ for (let i = 0; i < weapon.projectileCount; i++) {
675
+ const angle = (i / weapon.projectileCount) * Math.PI * 2;
676
+ createBouncyProjectile(angle);
677
+ }
678
+ } else {
679
+ // Normal weapons
680
+ for (let i = 0; i < weapon.projectileCount; i++) {
681
+ createProjectile(e.clientX, e.clientY, i);
682
+ }
683
+ }
684
+
685
+ // Animate cursor
686
+ customCursor.style.transform = 'scale(0.8)';
687
+ setTimeout(() => {
688
+ customCursor.style.transform = 'scale(1)';
689
+ }, 100);
690
+ }
691
+
692
+ function createBouncyProjectile(angle) {
693
+ const projectile = document.createElement('div');
694
+ projectile.className = 'projectile';
695
+
696
+ const centerX = window.innerWidth / 2;
697
+ const centerY = window.innerHeight / 2;
698
+
699
+ // Set projectile color
700
+ projectile.style.backgroundColor = state.colors[state.currentColorIndex];
701
+
702
+ // Set initial position at center
703
+ projectile.style.left = `${centerX}px`;
704
+ projectile.style.top = `${centerY}px`;
705
+
706
+ projectilesContainer.appendChild(projectile);
707
+
708
+ // Calculate direction based on angle
709
+ const dx = Math.cos(angle);
710
+ const dy = Math.sin(angle);
711
+
712
+ // Store projectile data with higher speed for bouncy weapon
713
+ const projectileData = {
714
+ element: projectile,
715
+ x: centerX,
716
+ y: centerY,
717
+ vx: dx * 15, // Faster speed for bouncy weapon
718
+ vy: dy * 15, // Faster speed for bouncy weapon
719
+ bounces: 0,
720
+ maxBounces: 3
721
+ };
722
+
723
+ state.projectiles.push(projectileData);
724
+ }
725
+
726
+ function createProjectile(mouseX, mouseY, index) {
727
+ const projectile = document.createElement('div');
728
+ projectile.className = 'projectile';
729
+
730
+ // Calculate direction from center to mouse
731
+ const centerX = window.innerWidth / 2;
732
+ const centerY = window.innerHeight / 2;
733
+
734
+ let dx = mouseX - centerX;
735
+ let dy = mouseY - centerY;
736
+
737
+ // Normalize direction vector
738
+ const distance = Math.sqrt(dx * dx + dy * dy);
739
+ dx /= distance;
740
+ dy /= distance;
741
+
742
+ // Apply weapon spread
743
+ const weapon = state.weapons[state.currentWeapon];
744
+ const spreadAngle = (index - (weapon.projectileCount - 1) / 2) * weapon.spread * (Math.PI / 180);
745
+
746
+ // Rotate direction by spread angle
747
+ const cos = Math.cos(spreadAngle);
748
+ const sin = Math.sin(spreadAngle);
749
+ const tx = dx * cos - dy * sin;
750
+ const ty = dx * sin + dy * cos;
751
+
752
+ dx = tx;
753
+ dy = ty;
754
+
755
+ // Set projectile color
756
+ projectile.style.backgroundColor = state.colors[state.currentColorIndex];
757
+
758
+ // Set initial position at center
759
+ projectile.style.left = `${centerX}px`;
760
+ projectile.style.top = `${centerY}px`;
761
+
762
+ projectilesContainer.appendChild(projectile);
763
+
764
+ // Store projectile data
765
+ const projectileData = {
766
+ element: projectile,
767
+ x: centerX,
768
+ y: centerY,
769
+ vx: dx * 10,
770
+ vy: dy * 10,
771
+ bounces: 0,
772
+ maxBounces: weapon.bouncy ? 3 : 0
773
+ };
774
+
775
+ state.projectiles.push(projectileData);
776
+ }
777
+
778
+ // Update projectiles position
779
+ function updateProjectiles() {
780
+ state.projectiles.forEach((projectile, index) => {
781
+ // Update position
782
+ projectile.x += projectile.vx;
783
+ projectile.y += projectile.vy;
784
+
785
+ // Bounce off edges if bouncy
786
+ if (projectile.maxBounces > 0) {
787
+ if (projectile.x <= 0 || projectile.x >= window.innerWidth) {
788
+ projectile.vx = -projectile.vx;
789
+ projectile.bounces++;
790
+ projectile.x = Math.max(0, Math.min(window.innerWidth, projectile.x));
791
+ }
792
+
793
+ if (projectile.y <= 0 || projectile.y >= window.innerHeight) {
794
+ projectile.vy = -projectile.vy;
795
+ projectile.bounces++;
796
+ projectile.y = Math.max(0, Math.min(window.innerHeight, projectile.y));
797
+ }
798
+ }
799
+
800
+ // Apply position
801
+ projectile.element.style.left = `${projectile.x}px`;
802
+ projectile.element.style.top = `${projectile.y}px`;
803
+
804
+ // Remove if out of bounds or max bounces reached
805
+ if (projectile.x < 0 || projectile.x > window.innerWidth ||
806
+ projectile.y < 0 || projectile.y > window.innerHeight ||
807
+ projectile.bounces > projectile.maxBounces) {
808
+ projectile.element.remove();
809
+ state.projectiles.splice(index, 1);
810
+ }
811
+ });
812
+ }
813
+
814
+ // Check for collisions with targets
815
+ function checkCollisions() {
816
+ state.projectiles.forEach((projectile, pIndex) => {
817
+ state.targets.forEach(target => {
818
+ // Simple circle-rectangle collision detection
819
+ const targetRect = {
820
+ x: target.x,
821
+ y: target.y,
822
+ width: 250,
823
+ height: 250
824
+ };
825
+
826
+ // Find closest point on rectangle to circle
827
+ const closestX = Math.max(targetRect.x, Math.min(projectile.x, targetRect.x + targetRect.width));
828
+ const closestY = Math.max(targetRect.y, Math.min(projectile.y, targetRect.y + targetRect.height));
829
+
830
+ // Calculate distance
831
+ const distanceX = projectile.x - closestX;
832
+ const distanceY = projectile.y - closestY;
833
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
834
+
835
+ if (distance < 10) { // Projectile radius
836
+ // Hit!
837
+ handleHit(target, projectile);
838
+
839
+ // Remove projectile
840
+ projectile.element.remove();
841
+ state.projectiles.splice(pIndex, 1);
842
+ }
843
+ });
844
+ });
845
+ }
846
+
847
+ // Check for collisions with center
848
+ function checkCenterCollision() {
849
+ if (state.gameOver) return;
850
+
851
+ const centerX = window.innerWidth / 2;
852
+ const centerY = window.innerHeight / 2;
853
+ const centerRadius = 10;
854
+
855
+ state.targets.forEach(target => {
856
+ // Check if any part of target is within center radius
857
+ const targetCenterX = target.x + 125;
858
+ const targetCenterY = target.y + 125;
859
+ const distance = Math.sqrt(
860
+ Math.pow(targetCenterX - centerX, 2) +
861
+ Math.pow(targetCenterY - centerY, 2)
862
+ );
863
+
864
+ if (distance < centerRadius + 125) {
865
+ handleCenterHit();
866
+ }
867
+ });
868
+ }
869
+
870
+ // 修改handleCenterHit函数
871
+ function handleHit(target, projectile) {
872
+ // Play hit sound
873
+ hitSound.currentTime = 0;
874
+ hitSound.play();
875
+
876
+ // 添加击中闪烁效果
877
+ target.element.classList.add('hit-flash');
878
+ setTimeout(() => {
879
+ target.element.classList.remove('hit-flash');
880
+ }, 300);
881
+
882
+ // Show hit effect
883
+ target.hitEffect.classList.add('active');
884
+ setTimeout(() => {
885
+ target.hitEffect.classList.remove('active');
886
+ }, 500);
887
+
888
+ // Create hit marker
889
+ const hitMarker = document.createElement('div');
890
+ hitMarker.className = 'hit-marker';
891
+ hitMarker.style.left = `${projectile.x - 15}px`;
892
+ hitMarker.style.top = `${projectile.y - 15}px`;
893
+ hitMarkersContainer.appendChild(hitMarker);
894
+
895
+ setTimeout(() => {
896
+ hitMarker.remove();
897
+ }, 1000);
898
+
899
+ // Update score
900
+ state.hits++;
901
+ state.score += 10;
902
+
903
+ hitCountElement.textContent = state.hits;
904
+ scoreElement.textContent = state.score;
905
+
906
+ // Show "Ejection FACE!" text
907
+ const ejectionText = document.createElement('div');
908
+ ejectionText.className = 'ejection-text';
909
+ ejectionText.textContent = 'Ejection FACE!';
910
+ ejectionText.style.left = `${window.innerWidth / 2 - 150}px`;
911
+ ejectionText.style.top = `${window.innerHeight / 2 - 50}px`;
912
+
913
+ gameContainer.appendChild(ejectionText);
914
+ setTimeout(() => {
915
+ ejectionText.remove();
916
+ }, 1500);
917
+
918
+ // Change target direction on hit with increased knockback
919
+ const knockbackDirectionX = (projectile.x - (target.x + 125)) / 125;
920
+ const knockbackDirectionY = (projectile.y - (target.y + 125)) / 125;
921
+
922
+ // Apply knockback with increasing distance based on hits
923
+ const knockbackMultiplier = state.knockbackDistance + (state.hits * 0.5);
924
+ target.vx = knockbackDirectionX * knockbackMultiplier;
925
+ target.vy = knockbackDirectionY * knockbackMultiplier;
926
+
927
+ // Increase difficulty every 10 hits
928
+ if (state.hits % 10 === 0) {
929
+ state.targetSpeed += 0.2;
930
+
931
+ // Make targets change direction more often
932
+ state.targets.forEach(t => {
933
+ t.vx *= 1.1;
934
+ t.vy *= 1.1;
935
+ });
936
+ }
937
+
938
+ // Add new target every 10 hits
939
+ if (state.hits % 10 === 0) {
940
+ state.targetCount++;
941
+ state.currentColorIndex = (state.currentColorIndex + 1) % state.colors.length;
942
+ setupCursor();
943
+ createTargets(state.targetCount);
944
+ }
945
+
946
+ // Unlock weapons if enough score
947
+ weaponElements.forEach(weapon => {
948
+ const weaponType = weapon.dataset.weapon;
949
+ const price = parseInt(weapon.dataset.price);
950
+
951
+ if (state.score >= price) {
952
+ weapon.classList.remove('locked');
953
+ }
954
+ });
955
+ }
956
+
957
+ // 修改handleCenterHit函数,添加全屏红色闪烁
958
+ function handleCenterHit() {
959
+ if (state.health > 0) {
960
+ state.health--;
961
+ updateHealth();
962
+
963
+ // 全屏红色闪烁
964
+ const screenFlash = document.getElementById('screen-flash');
965
+ screenFlash.classList.add('active');
966
+ setTimeout(() => {
967
+ screenFlash.classList.remove('active');
968
+ }, 500);
969
+
970
+ // 将所有目标弹出到画面边缘
971
+ state.targets.forEach(target => {
972
+ // 计算从中心到目标的向量
973
+ const centerX = window.innerWidth / 2;
974
+ const centerY = window.innerHeight / 2;
975
+ const targetCenterX = target.x + 125;
976
+ const targetCenterY = target.y + 125;
977
+
978
+ let dx = targetCenterX - centerX;
979
+ let dy = targetCenterY - centerY;
980
+
981
+ // 标准化向量并放大(弹出效果)
982
+ const distance = Math.sqrt(dx * dx + dy * dy);
983
+ dx /= distance;
984
+ dy /= distance;
985
+
986
+ // 设置很大的速度向边缘移动
987
+ target.vx = dx * 20;
988
+ target.vy = dy * 20;
989
+
990
+ // 添加闪烁效果
991
+ target.element.classList.add('hit-flash');
992
+ setTimeout(() => {
993
+ target.element.classList.remove('hit-flash');
994
+ }, 300);
995
+ });
996
+
997
+ if (state.health <= 0) {
998
+ // 延迟游戏结束,让玩家看到所有目标被弹出的效果
999
+ setTimeout(() => {
1000
+ gameOver();
1001
+ }, 500);
1002
+ }
1003
+ }
1004
+ }
1005
 
1006
  // Game over
1007
  function gameOver() {
 
1029
  state.targetSpeed = 2;
1030
  state.targetCount = 1;
1031
  state.gameOver = false;
1032
+ state.knockbackDistance = 30;
1033
 
1034
  // Reset UI
1035
  hitCountElement.textContent = '0';