Spaces:
Running
Running
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>欢乐捕鱼</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @keyframes bubbleFloat { | |
| 0% { transform: translateY(0) scale(0.8); opacity: 0; } | |
| 50% { opacity: 0.8; } | |
| 100% { transform: translateY(-100px) scale(1.2); opacity: 0; } | |
| } | |
| @keyframes fishSwim { | |
| 0% { transform: translateX(0) rotateY(0deg); } | |
| 50% { transform: translateX(10px) rotateY(0deg); } | |
| 51% { transform: translateX(10px) rotateY(180deg); } | |
| 100% { transform: translateX(0) rotateY(180deg); } | |
| } | |
| .bubble { | |
| animation: bubbleFloat 2s forwards; | |
| } | |
| .fish-animation { | |
| animation: fishSwim 3s infinite; | |
| } | |
| #gameCanvas { | |
| background: linear-gradient(to bottom, #1a237e, #00bcd4); | |
| border-radius: 10px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); | |
| } | |
| .wave { | |
| background: url('data:image/svg+xml;utf8,<svg viewBox="0 0 1200 120" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none"><path d="M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.21,104.45-29.34C989.49,25,1113-14.29,1200,52.47V0Z" opacity=".25" fill="%2300bcd4"/><path d="M0,0V15.81C13,36.92,27.64,56.86,47.69,72.05,99.41,111.27,165,111,224.58,91.58c31.15-10.15,60.09-26.07,89.67-39.8,40.92-19,84.73-46,130.83-49.67,36.26-2.85,70.9,9.42,98.6,31.56,31.77,25.39,62.32,62,103.63,73,40.44,10.79,81.35-6.69,119.13-24.28s75.16-39,116.92-43.05c59.73-5.85,113.28,22.88,168.9,38.84,30.2,8.66,59,6.17,87.09-7.5,22.43-10.89,48-26.93,60.65-49.24V0Z" opacity=".5" fill="%2300bcd4"/><path d="M0,0V5.63C149.93,59,314.09,71.32,475.83,42.57c43-7.64,84.23-20.12,127.61-26.46,59-8.63,112.48,12.24,165.56,35.4C827.93,77.22,886,95.24,951.2,90c86.53-7,172.46-45.71,248.8-84.81V0Z" fill="%2300bcd4"/></svg>'); | |
| background-size: cover; | |
| height: 60px; | |
| position: absolute; | |
| bottom: 0; | |
| width: 100%; | |
| } | |
| .powerup { | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.1); } | |
| 100% { transform: scale(1); } | |
| } | |
| .hook-line { | |
| transition: all 0.1s ease-out; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <header class="flex justify-between items-center mb-6"> | |
| <h1 class="text-4xl font-bold text-blue-300 flex items-center"> | |
| <i class="fas fa-fish mr-3"></i> 欢乐捕鱼 | |
| </h1> | |
| <div class="flex items-center space-x-4"> | |
| <div class="bg-blue-900 bg-opacity-50 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-yellow-300 mr-2">分数:</span> | |
| <span id="score" class="font-bold text-xl">0</span> | |
| </div> | |
| <div class="bg-blue-900 bg-opacity-50 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-green-300 mr-2">金币:</span> | |
| <span id="coins" class="font-bold text-xl">100</span> | |
| </div> | |
| <div class="bg-blue-900 bg-opacity-50 px-4 py-2 rounded-lg flex items-center"> | |
| <span class="text-red-300 mr-2">时间:</span> | |
| <span id="time" class="font-bold text-xl">60</span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="relative"> | |
| <canvas id="gameCanvas" width="800" height="500" class="w-full border-4 border-blue-800 relative overflow-hidden"></canvas> | |
| <div class="wave"></div> | |
| <!-- 游戏开始界面 --> | |
| <div id="startScreen" class="absolute inset-0 flex flex-col items-center justify-center bg-black bg-opacity-70 rounded-lg"> | |
| <div class="text-center p-8 bg-blue-900 bg-opacity-80 rounded-xl max-w-md"> | |
| <h2 class="text-3xl font-bold text-blue-300 mb-4">欢乐捕鱼</h2> | |
| <p class="mb-6 text-blue-100">使用鼠标移动鱼钩,点击放下鱼钩捕鱼。收集金币升级装备,捕获稀有鱼类获得高分!</p> | |
| <div class="mb-6"> | |
| <label for="difficulty" class="block text-blue-200 mb-2">选择难度:</label> | |
| <select id="difficulty" class="bg-blue-800 text-white rounded px-3 py-2 w-full"> | |
| <option value="easy">简单</option> | |
| <option value="medium" selected>中等</option> | |
| <option value="hard">困难</option> | |
| </select> | |
| </div> | |
| <button id="startButton" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-8 rounded-full text-lg transition-all transform hover:scale-105"> | |
| <i class="fas fa-play mr-2"></i> 开始游戏 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- 游戏结束界面 --> | |
| <div id="gameOverScreen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black bg-opacity-70 rounded-lg"> | |
| <div class="text-center p-8 bg-blue-900 bg-opacity-80 rounded-xl max-w-md"> | |
| <h2 class="text-3xl font-bold text-red-400 mb-2">游戏结束</h2> | |
| <p class="text-xl mb-1">最终得分: <span id="finalScore" class="text-yellow-300">0</span></p> | |
| <p class="text-lg mb-1">捕获鱼类: <span id="totalFish" class="text-blue-300">0</span></p> | |
| <p class="text-lg mb-6">获得金币: <span id="totalCoins" class="text-green-300">0</span></p> | |
| <div class="flex space-x-4 justify-center"> | |
| <button id="restartButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-full transition-all transform hover:scale-105"> | |
| <i class="fas fa-redo mr-2"></i> 重新开始 | |
| </button> | |
| <button id="menuButton" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-6 rounded-full transition-all transform hover:scale-105"> | |
| <i class="fas fa-home mr-2"></i> 主菜单 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 升级商店 --> | |
| <div id="upgradeScreen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black bg-opacity-70 rounded-lg"> | |
| <div class="text-center p-6 bg-blue-900 bg-opacity-80 rounded-xl max-w-2xl w-full"> | |
| <h2 class="text-2xl font-bold text-blue-300 mb-4">渔具商店</h2> | |
| <div class="grid grid-cols-3 gap-4 mb-6"> | |
| <div class="bg-blue-800 bg-opacity-60 p-4 rounded-lg"> | |
| <h3 class="font-bold text-yellow-300 mb-2">鱼钩尺寸</h3> | |
| <p class="text-sm text-blue-100 mb-2">当前: <span id="hookSizeLevel" class="text-white">1级</span></p> | |
| <p class="text-xs text-blue-200 mb-3">增加捕获大鱼几率</p> | |
| <button class="upgrade-btn bg-blue-600 hover:bg-blue-700 text-white text-sm py-1 px-3 rounded" data-upgrade="hookSize"> | |
| 升级 (<span class="cost" data-cost="50">50</span>金币) | |
| </button> | |
| </div> | |
| <div class="bg-blue-800 bg-opacity-60 p-4 rounded-lg"> | |
| <h3 class="font-bold text-yellow-300 mb-2">鱼线长度</h3> | |
| <p class="text-sm text-blue-100 mb-2">当前: <span id="lineLengthLevel" class="text-white">1级</span></p> | |
| <p class="text-xs text-blue-200 mb-3">可以到达更深水域</p> | |
| <button class="upgrade-btn bg-blue-600 hover:bg-blue-700 text-white text-sm py-1 px-3 rounded" data-upgrade="lineLength"> | |
| 升级 (<span class="cost" data-cost="40">40</span>金币) | |
| </button> | |
| </div> | |
| <div class="bg-blue-800 bg-opacity-60 p-4 rounded-lg"> | |
| <h3 class="font-bold text-yellow-300 mb-2">鱼饵质量</h3> | |
| <p class="text-sm text-blue-100 mb-2">当前: <span id="baitQualityLevel" class="text-white">1级</span></p> | |
| <p class="text-xs text-blue-200 mb-3">吸引更多稀有鱼类</p> | |
| <button class="upgrade-btn bg-blue-600 hover:bg-blue-700 text-white text-sm py-1 px-3 rounded" data-upgrade="baitQuality"> | |
| 升级 (<span class="cost" data-cost="60">60</span>金币) | |
| </button> | |
| </div> | |
| </div> | |
| <button id="continueButton" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-full"> | |
| <i class="fas fa-arrow-right mr-2"></i> 继续游戏 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-between items-center"> | |
| <div class="bg-blue-900 bg-opacity-50 px-4 py-3 rounded-lg"> | |
| <h3 class="text-lg font-semibold text-blue-300 mb-2">稀有鱼类</h3> | |
| <div class="flex space-x-4"> | |
| <div class="flex items-center"> | |
| <div class="w-6 h-6 bg-yellow-400 rounded-full mr-2"></div> | |
| <span>小黄鱼 (10分)</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-6 h-6 bg-red-500 rounded-full mr-2"></div> | |
| <span>红鲤鱼 (30分)</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-6 h-6 bg-purple-500 rounded-full mr-2"></div> | |
| <span>紫金鱼 (50分)</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button id="upgradeButton" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-6 rounded-full hidden"> | |
| <i class="fas fa-store mr-2"></i> 升级渔具 | |
| </button> | |
| <button id="soundButton" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-6 rounded-full"> | |
| <i class="fas fa-volume-up mr-2"></i> 音效 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 游戏音效 --> | |
| <audio id="castSound" src="https://assets.mixkit.co/sfx/preview/mixkit-arrow-whoosh-1491.mp3" preload="auto"></audio> | |
| <audio id="catchSound" src="https://assets.mixkit.co/sfx/preview/mixkit-winning-chimes-2015.mp3" preload="auto"></audio> | |
| <audio id="coinSound" src="https://assets.mixkit.co/sfx/preview/mixkit-coins-handling-1939.mp3" preload="auto"></audio> | |
| <audio id="upgradeSound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio> | |
| <audio id="gameOverSound" src="https://assets.mixkit.co/sfx/preview/mixkit-sad-game-over-trombone-471.mp3" preload="auto"></audio> | |
| <audio id="bgMusic" loop src="https://assets.mixkit.co/music/preview/mixkit-relaxing-waves-1391.mp3" preload="auto"></audio> | |
| <script> | |
| // 游戏状态 | |
| const gameState = { | |
| score: 0, | |
| coins: 100, | |
| time: 60, | |
| gameRunning: false, | |
| soundEnabled: true, | |
| paused: false, | |
| casting: false, | |
| hookDepth: 0, | |
| maxDepth: 300, | |
| fishCaught: 0, | |
| upgrades: { | |
| hookSize: { level: 1, cost: 50, value: 1 }, | |
| lineLength: { level: 1, cost: 40, value: 300 }, | |
| baitQuality: { level: 1, cost: 60, value: 1 } | |
| }, | |
| difficulty: 'medium' | |
| }; | |
| // 获取DOM元素 | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const scoreElement = document.getElementById('score'); | |
| const coinsElement = document.getElementById('coins'); | |
| const timeElement = document.getElementById('time'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const upgradeScreen = document.getElementById('upgradeScreen'); | |
| const finalScoreElement = document.getElementById('finalScore'); | |
| const totalFishElement = document.getElementById('totalFish'); | |
| const totalCoinsElement = document.getElementById('totalCoins'); | |
| const startButton = document.getElementById('startButton'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const menuButton = document.getElementById('menuButton'); | |
| const upgradeButton = document.getElementById('upgradeButton'); | |
| const continueButton = document.getElementById('continueButton'); | |
| const soundButton = document.getElementById('soundButton'); | |
| const difficultySelect = document.getElementById('difficulty'); | |
| // 升级按钮元素 | |
| const hookSizeLevel = document.getElementById('hookSizeLevel'); | |
| const lineLengthLevel = document.getElementById('lineLengthLevel'); | |
| const baitQualityLevel = document.getElementById('baitQualityLevel'); | |
| const upgradeButtons = document.querySelectorAll('.upgrade-btn'); | |
| // 获取音效元素 | |
| const castSound = document.getElementById('castSound'); | |
| const catchSound = document.getElementById('catchSound'); | |
| const coinSound = document.getElementById('coinSound'); | |
| const upgradeSound = document.getElementById('upgradeSound'); | |
| const gameOverSound = document.getElementById('gameOverSound'); | |
| const bgMusic = document.getElementById('bgMusic'); | |
| // 游戏对象 | |
| const player = { | |
| x: canvas.width / 2, | |
| y: 30, | |
| hookX: canvas.width / 2, | |
| hookY: 30, | |
| rodLength: 150, | |
| hookSize: 10, | |
| lineCast: false, | |
| lineRetracting: false, | |
| fishOnLine: null | |
| }; | |
| const fishes = []; | |
| const bubbles = []; | |
| let gameTimer; | |
| let countdownTimer; | |
| // 鱼类数据 | |
| const fishTypes = [ | |
| { name: '小黄鱼', color: '#FBBF24', score: 10, speed: 1.5, size: 20, rarity: 0.6 }, | |
| { name: '蓝刀鱼', color: '#60A5FA', score: 20, speed: 2, size: 25, rarity: 0.3 }, | |
| { name: '红鲤鱼', color: '#F87171', score: 30, speed: 1.8, size: 30, rarity: 0.2 }, | |
| { name: '绿草鱼', color: '#34D399', score: 15, speed: 1.2, size: 35, rarity: 0.5 }, | |
| { name: '紫金鱼', color: '#A78BFA', score: 50, speed: 2.5, size: 40, rarity: 0.1 } | |
| ]; | |
| // 事件监听器 | |
| startButton.addEventListener('click', startGame); | |
| restartButton.addEventListener('click', startGame); | |
| menuButton.addEventListener('click', showMenu); | |
| upgradeButton.addEventListener('click', showUpgradeScreen); | |
| continueButton.addEventListener('click', resumeFromUpgrade); | |
| soundButton.addEventListener('click', toggleSound); | |
| // 升级按钮事件 | |
| upgradeButtons.forEach(button => { | |
| button.addEventListener('click', function() { | |
| const upgradeType = this.getAttribute('data-upgrade'); | |
| purchaseUpgrade(upgradeType); | |
| }); | |
| }); | |
| canvas.addEventListener('mousemove', (e) => { | |
| if (!gameState.gameRunning || gameState.paused) return; | |
| const rect = canvas.getBoundingClientRect(); | |
| player.x = e.clientX - rect.left; | |
| if (!player.lineCast) { | |
| player.hookX = player.x; | |
| } | |
| }); | |
| canvas.addEventListener('click', (e) => { | |
| if (!gameState.gameRunning || gameState.paused) return; | |
| if (!player.lineCast && !player.lineRetracting) { | |
| castLine(); | |
| } else if (player.lineCast && !player.lineRetracting && player.hookDepth >= player.hookY) { | |
| // 尝试收线 | |
| startReeling(); | |
| } | |
| }); | |
| // 游戏函数 | |
| function startGame() { | |
| // 设置难度 | |
| gameState.difficulty = difficultySelect.value; | |
| // 重置游戏状态 | |
| gameState.score = 0; | |
| gameState.coins = 100; | |
| gameState.time = getInitialTime(); | |
| gameState.gameRunning = true; | |
| gameState.paused = false; | |
| gameState.casting = false; | |
| gameState.hookDepth = 0; | |
| gameState.fishCaught = 0; | |
| // 重置玩家状态 | |
| player.x = canvas.width / 2; | |
| player.hookX = canvas.width / 2; | |
| player.hookY = 30; | |
| player.lineCast = false; | |
| player.lineRetracting = false; | |
| player.fishOnLine = null; | |
| // 清空游戏对象 | |
| fishes.length = 0; | |
| bubbles.length = 0; | |
| // 更新UI | |
| scoreElement.textContent = gameState.score; | |
| coinsElement.textContent = gameState.coins; | |
| timeElement.textContent = gameState.time; | |
| // 隐藏界面 | |
| startScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| upgradeScreen.classList.add('hidden'); | |
| upgradeButton.classList.remove('hidden'); | |
| // 开始背景音乐 | |
| if (gameState.soundEnabled) { | |
| bgMusic.currentTime = 0; | |
| bgMusic.play(); | |
| } | |
| // 开始游戏计时 | |
| clearInterval(gameTimer); | |
| clearInterval(countdownTimer); | |
| gameTimer = setInterval(spawnFish, 1000); | |
| countdownTimer = setInterval(updateCountdown, 1000); | |
| // 开始游戏循环 | |
| requestAnimationFrame(gameLoop); | |
| } | |
| function getInitialTime() { | |
| switch (gameState.difficulty) { | |
| case 'easy': return 90; | |
| case 'medium': return 60; | |
| case 'hard': return 45; | |
| default: return 60; | |
| } | |
| } | |
| function showMenu() { | |
| gameState.gameRunning = false; | |
| clearInterval(gameTimer); | |
| clearInterval(countdownTimer); | |
| gameOverScreen.classList.add('hidden'); | |
| upgradeScreen.classList.add('hidden'); | |
| startScreen.classList.remove('hidden'); | |
| upgradeButton.classList.add('hidden'); | |
| if (gameState.soundEnabled) { | |
| bgMusic.pause(); | |
| } | |
| } | |
| function showUpgradeScreen() { | |
| gameState.paused = true; | |
| updateUpgradeDisplay(); | |
| upgradeScreen.classList.remove('hidden'); | |
| } | |
| function resumeFromUpgrade() { | |
| gameState.paused = false; | |
| upgradeScreen.classList.add('hidden'); | |
| } | |
| function updateUpgradeDisplay() { | |
| hookSizeLevel.textContent = `${gameState.upgrades.hookSize.level}级`; | |
| lineLengthLevel.textContent = `${gameState.upgrades.lineLength.level}级`; | |
| baitQualityLevel.textContent = `${gameState.upgrades.baitQuality.level}级`; | |
| // 更新升级按钮成本和状态 | |
| upgradeButtons.forEach(button => { | |
| const upgradeType = button.getAttribute('data-upgrade'); | |
| const cost = gameState.upgrades[upgradeType].cost; | |
| const costElement = button.querySelector('.cost'); | |
| costElement.textContent = cost; | |
| costElement.setAttribute('data-cost', cost); | |
| // 禁用不够钱的升级 | |
| if (gameState.coins < cost) { | |
| button.disabled = true; | |
| button.classList.add('opacity-50'); | |
| button.classList.remove('hover:bg-blue-700'); | |
| } else { | |
| button.disabled = false; | |
| button.classList.remove('opacity-50'); | |
| button.classList.add('hover:bg-blue-700'); | |
| } | |
| }); | |
| } | |
| function purchaseUpgrade(type) { | |
| const upgrade = gameState.upgrades[type]; | |
| if (gameState.coins >= upgrade.cost) { | |
| gameState.coins -= upgrade.cost; | |
| coinsElement.textContent = gameState.coins; | |
| upgrade.level++; | |
| // 根据升级类型增加效果 | |
| switch (type) { | |
| case 'hookSize': | |
| upgrade.value = upgrade.level; | |
| player.hookSize = 10 + (upgrade.level - 1) * 3; | |
| break; | |
| case 'lineLength': | |
| upgrade.value = 300 + (upgrade.level - 1) * 50; | |
| gameState.maxDepth = upgrade.value; | |
| break; | |
| case 'baitQuality': | |
| upgrade.value = upgrade.level; | |
| break; | |
| } | |
| // 增加下一次升级成本 | |
| upgrade.cost = Math.floor(upgrade.cost * 1.5); | |
| // 更新显示 | |
| updateUpgradeDisplay(); | |
| if (gameState.soundEnabled) { | |
| upgradeSound.currentTime = 0; | |
| upgradeSound.play(); | |
| } | |
| } | |
| } | |
| function toggleSound() { | |
| gameState.soundEnabled = !gameState.soundEnabled; | |
| if (gameState.soundEnabled) { | |
| soundButton.innerHTML = '<i class="fas fa-volume-up mr-2"></i> 音效'; | |
| if (gameState.gameRunning && !gameState.paused) { | |
| bgMusic.play(); | |
| } | |
| } else { | |
| soundButton.innerHTML = '<i class="fas fa-volume-mute mr-2"></i> 音效'; | |
| bgMusic.pause(); | |
| } | |
| } | |
| function gameOver() { | |
| gameState.gameRunning = false; | |
| clearInterval(gameTimer); | |
| clearInterval(countdownTimer); | |
| finalScoreElement.textContent = gameState.score; | |
| totalFishElement.textContent = gameState.fishCaught; | |
| totalCoinsElement.textContent = gameState.coins; | |
| gameOverScreen.classList.remove('hidden'); | |
| upgradeButton.classList.add('hidden'); | |
| if (gameState.soundEnabled) { | |
| bgMusic.pause(); | |
| gameOverSound.play(); | |
| } | |
| } | |
| function updateCountdown() { | |
| gameState.time--; | |
| timeElement.textContent = gameState.time; | |
| if (gameState.time <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| function castLine() { | |
| player.lineCast = true; | |
| player.hookY = 30; | |
| player.hookDepth = 0; | |
| if (gameState.soundEnabled) { | |
| castSound.currentTime = 0; | |
| castSound.play(); | |
| } | |
| } | |
| function startReeling() { | |
| player.lineRetracting = true; | |
| } | |
| function spawnFish() { | |
| if (!gameState.gameRunning || gameState.paused) return; | |
| // 根据难度调整生成频率 | |
| let spawnChance = 0.7; // 默认中等难度 | |
| if (gameState.difficulty === 'easy') spawnChance = 0.9; | |
| if (gameState.difficulty === 'hard') spawnChance = 0.5; | |
| if (Math.random() > spawnChance) return; | |
| // 根据鱼饵质量调整稀有鱼出现几率 | |
| const baitFactor = gameState.upgrades.baitQuality.value * 0.1; | |
| // 选择鱼类型,考虑稀有度 | |
| let availableFish = fishTypes.map(fish => { | |
| // 提高稀有鱼的几率基于鱼饵质量 | |
| const adjustedRarity = Math.min(1, fish.rarity + baitFactor); | |
| return { ...fish, selectionWeight: adjustedRarity }; | |
| }); | |
| // 根据权重随机选择鱼 | |
| const totalWeight = availableFish.reduce((sum, fish) => sum + fish.selectionWeight, 0); | |
| let random = Math.random() * totalWeight; | |
| let selectedFish; | |
| for (const fish of availableFish) { | |
| if (random < fish.selectionWeight) { | |
| selectedFish = fish; | |
| break; | |
| } | |
| random -= fish.selectionWeight; | |
| } | |
| // 确保总是选择一种鱼 | |
| selectedFish = selectedFish || availableFish[0]; | |
| // 随机位置 (从左右两侧出现) | |
| const side = Math.random() > 0.5 ? 1 : -1; | |
| const x = side > 0 ? -selectedFish.size : canvas.width + selectedFish.size; | |
| const y = 100 + Math.random() * (canvas.height - 200); | |
| // 随机速度 (基于难度) | |
| let speed = selectedFish.speed; | |
| if (gameState.difficulty === 'easy') speed *= 0.8; | |
| if (gameState.difficulty === 'hard') speed *= 1.3; | |
| fishes.push({ | |
| x: x, | |
| y: y, | |
| width: selectedFish.size, | |
| height: selectedFish.size / 2, | |
| speed: speed * side, | |
| color: selectedFish.color, | |
| score: selectedFish.score, | |
| name: selectedFish.name, | |
| direction: side, | |
| value: selectedFish.score, | |
| wiggle: Math.random() * 5 | |
| }); | |
| } | |
| function spawnBubble(x, y, size) { | |
| bubbles.push({ | |
| x: x, | |
| y: y, | |
| size: size, | |
| speed: 1 + Math.random() * 2, | |
| opacity: 0.3 + Math.random() * 0.7 | |
| }); | |
| } | |
| function updatePlayer() { | |
| // 更新鱼钩位置 | |
| if (player.lineCast && !player.lineRetracting) { | |
| // 放下鱼线 | |
| player.hookY += 3; | |
| player.hookDepth = player.hookY; | |
| // 随机生成气泡 | |
| if (Math.random() < 0.05) { | |
| spawnBubble( | |
| player.hookX + (Math.random() * 20 - 10), | |
| player.hookY, | |
| 3 + Math.random() * 7 | |
| ); | |
| } | |
| // 检查是否到达最大深度 | |
| if (player.hookY >= gameState.maxDepth) { | |
| startReeling(); | |
| } | |
| } else if (player.lineRetracting) { | |
| // 收回鱼线 | |
| player.hookY -= 5; | |
| // 如果有鱼在钩上,一起拉上来 | |
| if (player.fishOnLine) { | |
| player.fishOnLine.y -= 5; | |
| // 检查是否到达水面 | |
| if (player.hookY <= 30) { | |
| // 捕获成功 | |
| gameState.score += player.fishOnLine.value; | |
| gameState.coins += Math.floor(player.fishOnLine.value / 2); | |
| gameState.fishCaught++; | |
| scoreElement.textContent = gameState.score; | |
| coinsElement.textContent = gameState.coins; | |
| // 从鱼数组中移除 | |
| const index = fishes.indexOf(player.fishOnLine); | |
| if (index !== -1) { | |
| fishes.splice(index, 1); | |
| } | |
| player.fishOnLine = null; | |
| if (gameState.soundEnabled) { | |
| catchSound.currentTime = 0; | |
| catchSound.play(); | |
| coinSound.currentTime = 0; | |
| coinSound.play(); | |
| } | |
| } | |
| } | |
| // 检查是否完全收回 | |
| if (player.hookY <= 30) { | |
| player.lineCast = false; | |
| player.lineRetracting = false; | |
| player.hookY = 30; | |
| } | |
| } | |
| } | |
| function updateFishes() { | |
| for (let i = fishes.length - 1; i >= 0; i--) { | |
| const fish = fishes[i]; | |
| // 更新鱼的位置 | |
| fish.x += fish.speed; | |
| fish.wiggle += 0.1; | |
| // 随机生成气泡 | |
| if (Math.random() < 0.01) { | |
| spawnBubble( | |
| fish.x - fish.width/2 + (Math.random() * fish.width), | |
| fish.y + (Math.random() * fish.height) - fish.height/2, | |
| 2 + Math.random() * 5 | |
| ); | |
| } | |
| // 检查是否游出屏幕 | |
| if ((fish.direction > 0 && fish.x > canvas.width + fish.width) || | |
| (fish.direction < 0 && fish.x < -fish.width)) { | |
| fishes.splice(i, 1); | |
| } | |
| // 检查是否咬钩 | |
| if (player.lineCast && !player.lineRetracting && !player.fishOnLine) { | |
| const hookSize = player.hookSize * gameState.upgrades.hookSize.value; | |
| // 简单碰撞检测 | |
| if (Math.abs(fish.x - player.hookX) < fish.width/2 + hookSize/2 && | |
| Math.abs(fish.y - player.hookY) < fish.height/2 + hookSize/2) { | |
| // 根据鱼的大小和钩的大小计算捕获几率 | |
| const catchChance = Math.min(0.8, hookSize / fish.width); | |
| if (Math.random() < catchChance) { | |
| player.fishOnLine = fish; | |
| // 改变鱼的颜色表示被捕获 | |
| fish.color = '#FFFFFF'; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function updateBubbles() { | |
| for (let i = bubbles.length - 1; i >= 0; i--) { | |
| const bubble = bubbles[i]; | |
| bubble.y -= bubble.speed; | |
| // 移除超出屏幕的气泡 | |
| if (bubble.y < -bubble.size) { | |
| bubbles.splice(i, 1); | |
| } | |
| } | |
| } | |
| function drawBackground() { | |
| // 绘制渐变背景 | |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); | |
| gradient.addColorStop(0, '#1a237e'); | |
| gradient.addColorStop(1, '#00bcd4'); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // 绘制海底 | |
| ctx.fillStyle = '#5C4033'; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, canvas.height); | |
| // 创建波浪形海底 | |
| for (let x = 0; x <= canvas.width; x += 20) { | |
| const y = canvas.height - 30 + Math.sin(x * 0.02) * 15; | |
| ctx.lineTo(x, y); | |
| } | |
| ctx.lineTo(canvas.width, canvas.height); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // 绘制一些海底植物 | |
| drawSeaPlants(); | |
| } | |
| function drawSeaPlants() { | |
| // 海草 | |
| ctx.save(); | |
| for (let i = 0; i < 10; i++) { | |
| const x = Math.random() * canvas.width; | |
| const height = 30 + Math.random() * 70; | |
| ctx.fillStyle = `hsl(${100 + Math.random() * 40}, 70%, 40%)`; | |
| ctx.beginPath(); | |
| ctx.moveTo(x, canvas.height); | |
| // 弯曲的海草 | |
| for (let y = canvas.height; y >= canvas.height - height; y -= 5) { | |
| const offset = Math.sin((canvas.height - y) * 0.1) * 15; | |
| ctx.lineTo(x + offset, y); | |
| } | |
| ctx.lineTo(x, canvas.height); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| function drawPlayer() { | |
| ctx.save(); | |
| // 绘制钓竿 | |
| ctx.strokeStyle = '#78350F'; | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(player.x, 0); | |
| ctx.lineTo(player.x, player.rodLength); | |
| ctx.stroke(); | |
| // 绘制鱼线 | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(player.x, player.rodLength); | |
| ctx.lineTo(player.hookX, player.hookY); | |
| ctx.stroke(); | |
| // 绘制鱼钩 | |
| ctx.fillStyle = '#D4D4D8'; | |
| ctx.beginPath(); | |
| ctx.arc(player.hookX, player.hookY, player.hookSize/2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // 如果有鱼在钩上,绘制鱼 | |
| if (player.fishOnLine) { | |
| const fish = player.fishOnLine; | |
| drawFish(fish, true); | |
| } | |
| ctx.restore(); | |
| } | |
| function drawFishes() { | |
| for (const fish of fishes) { | |
| drawFish(fish); | |
| } | |
| } | |
| function drawFish(fish, onHook = false) { | |
| ctx.save(); | |
| if (onHook) { | |
| // 被钩住的鱼 | |
| ctx.translate(player.hookX, player.hookY); | |
| ctx.rotate(Math.PI / 2); | |
| } else { | |
| // 游动的鱼 | |
| ctx.translate(fish.x, fish.y); | |
| if (fish.direction < 0) { | |
| ctx.scale(-1, 1); | |
| } | |
| // 摆动效果 | |
| ctx.rotate(Math.sin(fish.wiggle) * 0.1); | |
| } | |
| // 鱼身 | |
| ctx.fillStyle = fish.color; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 0, fish.width / 2, fish.height / 2, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // 鱼尾 | |
| ctx.beginPath(); | |
| ctx.moveTo(-fish.width / 2, 0); | |
| ctx.lineTo(-fish.width / 2 - 10, -fish.height / 2); | |
| ctx.lineTo(-fish.width / 2 - 10, fish.height / 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // 鱼眼 | |
| ctx.fillStyle = 'white'; | |
| ctx.beginPath(); | |
| ctx.arc(fish.width / 4, -fish.height / 4, fish.width / 8, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = 'black'; | |
| ctx.beginPath(); | |
| ctx.arc(fish.width / 4, -fish.height / 4, fish.width / 16, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| function drawBubbles() { | |
| ctx.save(); | |
| for (const bubble of bubbles) { | |
| ctx.fillStyle = `rgba(173, 216, 230, ${bubble.opacity})`; | |
| ctx.beginPath(); | |
| ctx.arc(bubble.x, bubble.y, bubble.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // 气泡高光 | |
| ctx.fillStyle = `rgba(255, 255, 255, ${bubble.opacity * 0.7})`; | |
| ctx.beginPath(); | |
| ctx.arc(bubble.x - bubble.size/3, bubble.y - bubble.size/3, bubble.size/4, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| function drawDepthMeter() { | |
| if (!player.lineCast) return; | |
| ctx.save(); | |
| // 背景 | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.fillRect(20, 50, 20, gameState.maxDepth); | |
| // 当前深度 | |
| const depthPercentage = player.hookDepth / gameState.maxDepth; | |
| ctx.fillStyle = '#3B82F6'; | |
| ctx.fillRect(20, 50 + depthPercentage * gameState.maxDepth, 20, 5); | |
| // 文本 | |
| ctx.fillStyle = 'white'; | |
| ctx.font = '12px Arial'; | |
| ctx.fillText('深度', 25, 40); | |
| ctx.restore(); | |
| } | |
| // 游戏循环 | |
| function gameLoop() { | |
| if (!gameState.gameRunning || gameState.paused) return; | |
| // 清除画布 | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // 绘制背景 | |
| drawBackground(); | |
| // 更新游戏状态 | |
| updatePlayer(); | |
| updateFishes(); | |
| updateBubbles(); | |
| // 绘制游戏对象 | |
| drawFishes(); | |
| drawBubbles(); | |
| drawPlayer(); | |
| drawDepthMeter(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // 初始显示开始界面 | |
| showMenu(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=GeminiLight/capture-fish" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |