🎮ArcadeLab

角色选择 · 吃吃乐

by MysticLion44
695 lines25.1 KB
▶ Play
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>角色选择 · 吃吃乐</title>
    <style>
        * { touch-action: none; user-select: none; box-sizing: border-box; }
        html, body {
            margin: 0;
            padding: 0;
            height: 100%;
            overflow: hidden;
        }
        body {
            background: #1a1a2e;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: space-between;
            font-family: Arial, sans-serif;
            padding: 4px 6px 6px;
            height: 100vh;
            max-height: 100vh;
        }

        /* ----- 顶部:分数 + 最高分 ----- */
        .top-area {
            display: flex;
            flex-direction: column;
            align-items: center;
            flex-shrink: 0;
            margin-bottom: 2px;
            gap: 0px;
        }
        #score {
            color: #eee;
            font-size: 20px;
            font-weight: bold;
            line-height: 1.2;
        }
        #highScore {
            color: #888;
            font-size: 13px;
            font-weight: normal;
            line-height: 1.2;
        }

        /* ----- 设置区域(角色+速度 合并为一行,紧凑) ----- */
        .settings-row {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 4px;
            width: 100%;
            flex-shrink: 0;
            margin-bottom: 2px;
            flex-wrap: nowrap; /* 强制一行 */
        }
        .settings-label {
            color: #888;
            font-size: 11px;
            font-weight: bold;
            margin-right: 0px;
            flex-shrink: 0;
        }
        .role-wrap, .speed-wrap {
            display: flex;
            align-items: center;
            background: #16213e;
            padding: 1px 4px;
            border-radius: 20px;
            border: 1px solid #0f3460;
            gap: 1px;
            flex-shrink: 1;
            overflow: hidden;
        }
        .role-btn {
            padding: 0px 4px;
            border-radius: 16px;
            border: none;
            font-size: 15px;
            background: transparent;
            cursor: pointer;
            opacity: 0.4;
            transition: all 0.2s;
            line-height: 1.6;
        }
        .role-btn.active {
            opacity: 1;
            background: #e94560;
            box-shadow: 0 0 6px #e94560aa;
            transform: scale(1.04);
        }
        .role-btn:active { transform: scale(0.9); }

        .speed-btn {
            padding: 0px 5px;
            border-radius: 16px;
            border: none;
            font-size: 11px;
            font-weight: bold;
            color: #888;
            background: transparent;
            cursor: pointer;
            transition: all 0.2s;
            line-height: 1.8;
            white-space: nowrap;
        }
        .speed-btn.active {
            color: #fff;
            background: #e94560;
            box-shadow: 0 0 6px #e94560aa;
        }
        .speed-btn:active { transform: scale(0.92); }

        /* ----- 画布(大幅放大) ----- */
        .canvas-wrap {
            flex: 1 1 auto;
            display: flex;
            justify-content: center;
            align-items: center;
            width: 100%;
            min-height: 0;
            max-height: 55vh;
        }
        canvas {
            background: #1a1a2e;
            border: 3px solid #0f3460;
            border-radius: 12px;
            width: min(88vw, 420px);
            height: min(88vw, 420px);
            max-width: 420px;
            max-height: 420px;
            touch-action: none;
            display: block;
        }

        /* ----- 方向键(放大) ----- */
        .dpad {
            display: grid;
            grid-template-columns: 62px 62px 62px;
            grid-template-rows: 62px 62px 62px;
            gap: 6px;
            margin-top: 4px;
            flex-shrink: 0;
        }
        .dpad-btn {
            border: none;
            border-radius: 16px;
            font-size: 24px;
            font-weight: bold;
            color: white;
            background: #2d4059;
            box-shadow: 0 4px 0 #111;
            cursor: pointer;
            touch-action: manipulation;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.05s linear;
            width: 100%;
            height: 100%;
        }
        .dpad-btn:active {
            transform: translateY(4px);
            box-shadow: none;
        }
        .dpad-btn.up    { grid-column: 2; grid-row: 1; }
        .dpad-btn.left  { grid-column: 1; grid-row: 2; }
        .dpad-btn.center { grid-column: 2; grid-row: 2; background: #f39c12; box-shadow: 0 4px 0 #b86d0e; font-size: 18px; }
        .dpad-btn.right { grid-column: 3; grid-row: 2; }
        .dpad-btn.down  { grid-column: 2; grid-row: 3; }

        /* ----- 功能按钮 ----- */
        .action-row {
            display: flex;
            gap: 8px;
            margin-top: 4px;
            flex-shrink: 0;
            flex-wrap: wrap;
            justify-content: center;
        }
        .action-row button {
            padding: 4px 12px;
            font-size: 13px;
            font-weight: bold;
            border-radius: 30px;
            border: none;
            color: white;
            background: #2d4059;
            box-shadow: 0 3px 0 #111;
            cursor: pointer;
            min-width: 58px;
        }
        .action-row button:active { transform: translateY(3px); box-shadow: none; }
        #saveBtn { background: #2980b9; box-shadow: 0 3px 0 #1a5276; }
        #loadBtn { background: #8e44ad; box-shadow: 0 3px 0 #5b2d6e; }
        #restartBtn { background: #e74c3c; box-shadow: 0 3px 0 #922b21; }

        .status-tip {
            color: #666;
            font-size: 11px;
            margin-top: 2px;
            flex-shrink: 0;
            text-align: center;
            line-height: 1.2;
        }

        /* ----- 小屏适配 ----- */
        @media (max-width: 400px) {
            .dpad { grid-template-columns: 52px 52px 52px; grid-template-rows: 52px 52px 52px; gap: 5px; }
            .dpad-btn { font-size: 20px; border-radius: 14px; }
            .dpad-btn.center { font-size: 16px; }
            canvas { width: min(85vw, 350px); height: min(85vw, 350px); }
            .action-row button { padding: 3px 10px; font-size: 12px; min-width: 50px; }
            .role-btn { font-size: 13px; padding: 0px 3px; }
            .speed-btn { font-size: 10px; padding: 0px 4px; }
            #score { font-size: 17px; }
            #highScore { font-size: 12px; }
            .settings-label { font-size: 10px; }
            .role-wrap, .speed-wrap { padding: 1px 3px; gap: 1px; }
        }
        @media (max-width: 350px) {
            .dpad { grid-template-columns: 44px 44px 44px; grid-template-rows: 44px 44px 44px; gap: 4px; }
            .dpad-btn { font-size: 17px; border-radius: 12px; }
            .dpad-btn.center { font-size: 13px; }
            canvas { width: min(80vw, 300px); height: min(80vw, 300px); }
            .speed-btn { font-size: 9px; padding: 0px 3px; }
            .role-btn { font-size: 12px; }
        }
    </style>
</head>
<body>

    <!-- 顶部:分数 + 最高分 -->
    <div class="top-area">
        <div id="score">🧀 0</div>
        <div id="highScore">🏆 最高: 0</div>
    </div>

    <!-- 设置区域(角色 + 速度 合并为一行,紧凑) -->
    <div class="settings-row">
        <span class="settings-label">🎭</span>
        <div class="role-wrap">
            <button class="role-btn active" data-role="mouse">🐭</button>
            <button class="role-btn" data-role="snake">🐍</button>
            <button class="role-btn" data-role="sheep">🐑</button>
        </div>
        <span class="settings-label" style="margin-left:2px;">⚡</span>
        <div class="speed-wrap">
            <!-- 新增极慢(400ms) -->
            <button class="speed-btn" data-speed="400">🐢极慢</button>
            <button class="speed-btn" data-speed="200">🐑慢</button>
            <button class="speed-btn active" data-speed="130">🐭中</button>
            <button class="speed-btn" data-speed="70">🐍快</button>
        </div>
    </div>

    <!-- 画布 -->
    <div class="canvas-wrap">
        <canvas id="gameCanvas" width="400" height="400"></canvas>
    </div>

    <!-- 方向键 -->
    <div class="dpad">
        <button class="dpad-btn up" id="btnUp">▲</button>
        <button class="dpad-btn left" id="btnLeft">◀</button>
        <button class="dpad-btn center" id="pauseBtn">⏸️</button>
        <button class="dpad-btn right" id="btnRight">▶</button>
        <button class="dpad-btn down" id="btnDown">▼</button>
    </div>

    <!-- 功能按钮 -->
    <div class="action-row">
        <button id="saveBtn">💾 保存</button>
        <button id="loadBtn">📂 读取</button>
        <button id="restartBtn">🔄 重启</button>
    </div>

    <div class="status-tip" id="statusTip">💡 保存后,下次打开点“读取”继续</div>

    <script>
        // ===================== 游戏脚本(同前,增加极慢速度400ms) =====================
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const gridSize = 20;
        const tileCount = canvas.width / gridSize;

        let currentRole = 'mouse';
        const roleData = {
            mouse: { head: '🐭', body: '🧀', food: '🧀', scoreIcon: '🧀' },
            snake: { head: '🐍', body: '💣', food: '💣', scoreIcon: '💣' },
            sheep: { head: '🐑', body: '🧋', food: '🧋', scoreIcon: '🧋' }
        };

        let snake, food, direction, nextDirection, score, gameOverFlag, gameInterval;
        let currentSpeed = 130;  // 默认中速
        let isPaused = false;

        // ========== 最高分管理 ==========
        const HIGH_SCORE_KEY = 'snakeHighScore';

        function getHighScore() {
            return parseInt(localStorage.getItem(HIGH_SCORE_KEY)) || 0;
        }

        function setHighScore(val) {
            localStorage.setItem(HIGH_SCORE_KEY, String(val));
        }

        function updateHighScoreDisplay() {
            document.getElementById('highScore').innerText = '🏆 最高: ' + getHighScore();
        }

        // ========== 绘制函数 ==========
        function drawFoodEmoji(ctx, x, y, size, emoji) {
            const cx = x + size / 2;
            const cy = y + size / 2;
            ctx.font = `${size * 0.85}px Arial`;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(emoji, cx, cy + 2);
        }
        function drawBody(ctx, x, y, size, emoji) {
            const cx = x + size / 2;
            const cy = y + size / 2;
            ctx.font = `${size * 0.8}px Arial`;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(emoji, cx, cy + 2);
        }

        // ========== 游戏核心 ==========
        function initGame() {
            snake = [{ x: 10, y: 10 }];
            direction = { x: 0, y: 0 };
            nextDirection = { x: 0, y: 0 };
            score = 0;
            gameOverFlag = false;
            isPaused = false;
            document.getElementById('pauseBtn').innerText = '⏸️';
            updateScoreDisplay();
            updateHighScoreDisplay();
            placeFood();
            clearGameInterval();
            startGameInterval();
            drawGame();
        }

        function updateScoreDisplay() {
            const icon = roleData[currentRole].scoreIcon;
            document.getElementById('score').innerText = icon + ' ' + score;
        }

        function startGameInterval() {
            if (gameInterval) clearInterval(gameInterval);
            gameInterval = setInterval(gameLoop, currentSpeed);
        }
        function clearGameInterval() {
            if (gameInterval) {
                clearInterval(gameInterval);
                gameInterval = null;
            }
        }

        function gameLoop() {
            if (isPaused) {
                drawGame();
                ctx.fillStyle = 'rgba(0,0,0,0.6)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = '#fff';
                ctx.font = 'bold 36px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('⏸ Paused', canvas.width/2, canvas.height/2 - 10);
                ctx.font = '16px Arial';
                ctx.fillStyle = '#aaa';
                ctx.fillText('Press "Continue" below', canvas.width/2, canvas.height/2 + 40);
                return;
            }
            if (gameOverFlag) return;

            direction = { ...nextDirection };
            if (direction.x === 0 && direction.y === 0) return;

            const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
            snake.unshift(head);

            if (head.x === food.x && head.y === food.y) {
                score++;
                updateScoreDisplay();
                placeFood();
            } else {
                snake.pop();
            }

            if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
                gameOver(); return;
            }
            for (let i = 1; i < snake.length; i++) {
                if (snake[i].x === head.x && snake[i].y === head.y) {
                    gameOver(); return;
                }
            }
            drawGame();
        }

        function drawGame() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = 'rgba(255,255,255,0.03)';
            ctx.lineWidth = 0.5;
            for (let i = 0; i <= tileCount; i++) {
                ctx.beginPath();
                ctx.moveTo(i * gridSize, 0);
                ctx.lineTo(i * gridSize, canvas.height);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, i * gridSize);
                ctx.lineTo(canvas.width, i * gridSize);
                ctx.stroke();
            }

            const foodEmoji = roleData[currentRole].food;
            drawFoodEmoji(ctx, food.x * gridSize, food.y * gridSize, gridSize, foodEmoji);

            const bodyEmoji = roleData[currentRole].body;
            const headEmoji = roleData[currentRole].head;
            snake.forEach((part, index) => {
                const cx = part.x * gridSize + gridSize/2;
                const cy = part.y * gridSize + gridSize/2;
                if (index === 0) {
                    ctx.font = `${gridSize * 0.9}px Arial`;
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.fillText(headEmoji, cx, cy + 1);
                } else {
                    drawBody(ctx, part.x * gridSize, part.y * gridSize, gridSize, bodyEmoji);
                }
            });
        }

        function placeFood() {
            let newFood, valid;
            do {
                valid = true;
                newFood = { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) };
                for (let part of snake) {
                    if (part.x === newFood.x && part.y === newFood.y) { valid = false; break; }
                }
            } while (!valid);
            food = newFood;
        }

        // ========== 游戏结束(更新最高分) ==========
        function gameOver() {
            if (gameOverFlag) return;
            gameOverFlag = true;
            clearGameInterval();

            const currentHigh = getHighScore();
            if (score > currentHigh) {
                setHighScore(score);
                updateHighScoreDisplay();
            }

            drawGame();
            ctx.fillStyle = 'rgba(0,0,0,0.7)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            const headEmoji = roleData[currentRole].head;
            ctx.fillStyle = '#ff6b6b';
            ctx.font = 'bold 32px Arial';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(headEmoji + ' Game Over', canvas.width/2, canvas.height/2 - 20);
            ctx.fillStyle = '#eee';
            ctx.font = '20px Arial';
            ctx.fillText('Score: ' + score + '  最高: ' + getHighScore(), canvas.width/2, canvas.height/2 + 40);
            setTimeout(() => {
                alert('Game Over! Score: ' + score + '  最高: ' + getHighScore());
            }, 50);
        }

        // ========== 切换角色 ==========
        function setRole(role) {
            if (role === currentRole) return;
            currentRole = role;
            document.querySelectorAll('.role-btn').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.role === role);
            });
            updateScoreDisplay();
            placeFood();
            drawGame();
        }

        // ========== 速度切换 ==========
        function setSpeed(speed) {
            currentSpeed = speed;
            document.querySelectorAll('.speed-btn').forEach(btn => {
                btn.classList.remove('active');
                if (parseInt(btn.dataset.speed) === speed) btn.classList.add('active');
            });
            if (isPaused) {
                isPaused = false;
                document.getElementById('pauseBtn').innerText = '⏸️';
            }
            if (!gameOverFlag) {
                clearGameInterval();
                startGameInterval();
            }
        }

        // ========== 暂停 ==========
        function togglePause() {
            if (gameOverFlag) {
                alert('Game has ended, please click "Restart"');
                return;
            }
            isPaused = !isPaused;
            document.getElementById('pauseBtn').innerText = isPaused ? '▶️' : '⏸️';
            if (isPaused) {
                clearGameInterval();
                drawGame();
                ctx.fillStyle = 'rgba(0,0,0,0.6)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = '#fff';
                ctx.font = 'bold 36px Arial';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('⏸ Paused', canvas.width/2, canvas.height/2 - 10);
                ctx.font = '16px Arial';
                ctx.fillStyle = '#aaa';
                ctx.fillText('Press "Continue" below', canvas.width/2, canvas.height/2 + 40);
            } else {
                if (!gameOverFlag) startGameInterval();
            }
        }

        // ========== 方向控制 ==========
        function setDirection(newDir) {
            if (gameOverFlag || isPaused) return;
            if ((direction.x !== 0 && newDir.x !== 0) || (direction.y !== 0 && newDir.y !== 0)) return;
            if (direction.x === 0 && direction.y === 0) { nextDirection = newDir; return; }
            if (direction.x === -newDir.x && direction.y === -newDir.y) return;
            nextDirection = newDir;
        }

        // ========== 保存 & 读取 ==========
        function saveGame() {
            const state = {
                snake: snake,
                food: food,
                direction: direction,
                nextDirection: nextDirection,
                score: score,
                currentSpeed: currentSpeed,
                gameOverFlag: gameOverFlag,
                isPaused: false,
                role: currentRole
            };
            try {
                localStorage.setItem('snakeGameSave', JSON.stringify(state));
                document.getElementById('statusTip').innerText = '✅ Game saved!';
                setTimeout(() => {
                    document.getElementById('statusTip').innerText = '💡 保存后,下次打开点“读取”继续';
                }, 3000);
            } catch (e) {
                alert('Save failed');
            }
        }

        function loadGame() {
            const data = localStorage.getItem('snakeGameSave');
            if (!data) {
                alert('No save file found');
                return;
            }
            try {
                const state = JSON.parse(data);
                snake = state.snake;
                food = state.food;
                direction = state.direction;
                nextDirection = state.nextDirection;
                score = state.score;
                currentSpeed = state.currentSpeed;
                gameOverFlag = state.gameOverFlag;
                if (state.role) {
                    currentRole = state.role;
                    document.querySelectorAll('.role-btn').forEach(btn => {
                        btn.classList.toggle('active', btn.dataset.role === currentRole);
                    });
                }

                updateScoreDisplay();
                updateHighScoreDisplay();
                document.querySelectorAll('.speed-btn').forEach(btn => {
                    btn.classList.remove('active');
                    if (parseInt(btn.dataset.speed) === currentSpeed) btn.classList.add('active');
                });

                clearGameInterval();
                isPaused = false;
                document.getElementById('pauseBtn').innerText = '⏸️';

                if (!gameOverFlag) {
                    startGameInterval();
                } else {
                    drawGame();
                    ctx.fillStyle = 'rgba(0,0,0,0.7)';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    const headEmoji = roleData[currentRole].head;
                    ctx.fillStyle = '#ff6b6b';
                    ctx.font = 'bold 32px Arial';
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.fillText(headEmoji + ' Game Over', canvas.width/2, canvas.height/2 - 20);
                    ctx.fillStyle = '#eee';
                    ctx.font = '20px Arial';
                    ctx.fillText('Score: ' + score + '  最高: ' + getHighScore(), canvas.width/2, canvas.height/2 + 40);
                }
                drawGame();
                document.getElementById('statusTip').innerText = '✅ Load successful!';
                setTimeout(() => {
                    document.getElementById('statusTip').innerText = '💡 保存后,下次打开点“读取”继续';
                }, 3000);
            } catch (e) {
                alert('Load failed');
            }
        }

        // ========== 触摸滑动 ==========
        let touchStartX = 0, touchStartY = 0;
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            const touch = e.touches[0];
            touchStartX = touch.clientX;
            touchStartY = touch.clientY;
        }, { passive: false });

        canvas.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (!touchStartX || !touchStartY) return;
            const touch = e.touches[0];
            const deltaX = touch.clientX - touchStartX;
            const deltaY = touch.clientY - touchStartY;
            if (Math.abs(deltaX) < 20 && Math.abs(deltaY) < 20) return;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                setDirection({ x: deltaX > 0 ? 1 : -1, y: 0 });
            } else {
                setDirection({ x: 0, y: deltaY > 0 ? 1 : -1 });
            }
            touchStartX = touch.clientX;
            touchStartY = touch.clientY;
        }, { passive: false });

        canvas.addEventListener('touchend', (e) => {
            e.preventDefault();
            touchStartX = 0;
            touchStartY = 0;
        }, { passive: false });

        // ========== 按钮绑定 ==========
        document.getElementById('btnUp').addEventListener('click', () => setDirection({ x: 0, y: -1 }));
        document.getElementById('btnDown').addEventListener('click', () => setDirection({ x: 0, y: 1 }));
        document.getElementById('btnLeft').addEventListener('click', () => setDirection({ x: -1, y: 0 }));
        document.getElementById('btnRight').addEventListener('click', () => setDirection({ x: 1, y: 0 }));

        document.getElementById('pauseBtn').addEventListener('click', togglePause);
        document.getElementById('saveBtn').addEventListener('click', saveGame);
        document.getElementById('loadBtn').addEventListener('click', loadGame);
        document.getElementById('restartBtn').addEventListener('click', () => {
            if (isPaused) {
                isPaused = false;
                document.getElementById('pauseBtn').innerText = '⏸️';
            }
            initGame();
        });

        document.querySelectorAll('.role-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                setRole(this.dataset.role);
            });
        });

        document.querySelectorAll('.speed-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                setSpeed(parseInt(this.dataset.speed));
            });
        });

        // ========== 启动 ==========
        initGame();
    </script>
</body>
</html>

Game Source: 角色选择 · 吃吃乐

Creator: MysticLion44

Libraries: none

Complexity: complex (695 lines, 25.1 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-mysticlion44-mr1v0yl6" to link back to the original. Then publish at arcadelab.ai/publish.