🎮ArcadeLab

卡牌记忆配对游戏

by NovaFalcon96
456 lines14.3 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>卡牌记忆配对游戏</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            min-height: 100vh;
            background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            color: #fff;
            overflow: hidden;
        }

        /* 浮动粒子背景 */
        .particles {
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            pointer-events: none;
            z-index: 0;
        }
        .particle {
            position: absolute;
            border-radius: 50%;
            background: rgba(255,255,255,0.08);
            animation: floatUp linear infinite;
        }
        @keyframes floatUp {
            0% { transform: translateY(100vh) scale(0); opacity: 0; }
            10% { opacity: 1; }
            90% { opacity: 1; }
            100% { transform: translateY(-10vh) scale(1); opacity: 0; }
        }

        .game-container {
            position: relative;
            z-index: 1;
            text-align: center;
        }

        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            background: linear-gradient(90deg, #f7971e, #ffd200, #f7971e);
            background-size: 200% auto;
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            animation: shimmer 3s linear infinite;
        }
        @keyframes shimmer {
            to { background-position: 200% center; }
        }

        .stats {
            display: flex;
            justify-content: center;
            gap: 30px;
            margin-bottom: 20px;
            font-size: 1.1rem;
        }
        .stats span {
            background: rgba(255,255,255,0.1);
            padding: 8px 20px;
            border-radius: 25px;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255,255,255,0.15);
        }
        .stats .value {
            color: #ffd200;
            font-weight: 700;
        }

        .grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 14px;
            perspective: 1000px;
            margin-bottom: 25px;
        }

        .card {
            width: 100px;
            height: 120px;
            cursor: pointer;
            position: relative;
            transform-style: preserve-3d;
            transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .card.flipped {
            transform: rotateY(180deg);
        }
        .card.matched {
            transform: rotateY(180deg);
            animation: matchPop 0.5s ease;
        }
        @keyframes matchPop {
            0% { transform: rotateY(180deg) scale(1); }
            50% { transform: rotateY(180deg) scale(1.15); }
            100% { transform: rotateY(180deg) scale(1); }
        }

        .card-face {
            position: absolute;
            width: 100%;
            height: 100%;
            border-radius: 14px;
            backface-visibility: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 2.8rem;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
        }

        .card-back {
            background: linear-gradient(145deg, #6a11cb, #2575fc);
            border: 2px solid rgba(255,255,255,0.2);
        }
        .card-back::after {
            content: '\2726';
            font-size: 2rem;
            color: rgba(255,255,255,0.5);
        }

        .card-front {
            background: linear-gradient(145deg, #1a1a2e, #16213e);
            border: 2px solid rgba(255,255,255,0.15);
            transform: rotateY(180deg);
        }

        .card.matched .card-front {
            background: linear-gradient(145deg, #0f3460, #1a1a2e);
            border-color: #ffd200;
            box-shadow: 0 0 20px rgba(255,210,0,0.3);
        }

        .btn-restart {
            padding: 12px 36px;
            font-size: 1.1rem;
            border: none;
            border-radius: 30px;
            cursor: pointer;
            background: linear-gradient(90deg, #f7971e, #ffd200);
            color: #1a1a2e;
            font-weight: 700;
            letter-spacing: 1px;
            transition: all 0.3s ease;
            box-shadow: 0 4px 20px rgba(247,151,30,0.4);
        }
        .btn-restart:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 25px rgba(247,151,30,0.6);
        }
        .btn-restart:active {
            transform: translateY(0);
        }

        /* 胜利弹窗 */
        .overlay {
            display: none;
            position: fixed;
            top: 0; left: 0;
            width: 100%; height: 100%;
            background: rgba(0,0,0,0.7);
            z-index: 100;
            align-items: center;
            justify-content: center;
            backdrop-filter: blur(5px);
        }
        .overlay.active {
            display: flex;
            animation: fadeIn 0.5s ease;
        }
        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        .win-modal {
            background: linear-gradient(145deg, #1a1a2e, #16213e);
            padding: 40px 50px;
            border-radius: 20px;
            text-align: center;
            border: 2px solid rgba(255,210,0,0.3);
            box-shadow: 0 0 40px rgba(255,210,0,0.2);
            animation: bounceIn 0.6s ease;
        }
        @keyframes bounceIn {
            0% { transform: scale(0.5); opacity: 0; }
            60% { transform: scale(1.05); }
            100% { transform: scale(1); opacity: 1; }
        }
        .win-modal h2 {
            font-size: 2rem;
            margin-bottom: 15px;
            background: linear-gradient(90deg, #f7971e, #ffd200);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }
        .win-modal p {
            font-size: 1.2rem;
            margin-bottom: 8px;
            color: #ccc;
        }
        .win-modal .highlight {
            color: #ffd200;
            font-weight: 700;
        }
        .win-modal .btn-restart {
            margin-top: 20px;
        }

        /* 烟花效果 */
        .firework {
            position: fixed;
            pointer-events: none;
            z-index: 99;
        }
        .firework-particle {
            position: absolute;
            width: 5px;
            height: 5px;
            border-radius: 50%;
            animation: fireworkBurst 1s ease-out forwards;
        }
        @keyframes fireworkBurst {
            0% { transform: translate(0,0) scale(1); opacity: 1; }
            100% { transform: translate(var(--tx), var(--ty)) scale(0); opacity: 0; }
        }

        @media (max-width: 500px) {
            .card { width: 75px; height: 95px; }
            .card-face { font-size: 2rem; }
            .grid { gap: 10px; }
            h1 { font-size: 1.8rem; }
        }
    </style>
</head>
<body>

<div class="particles" id="particles"></div>

<div class="game-container">
    <h1>🃏 卡牌记忆配对</h1>
    <div class="stats">
        <span>步数: <span class="value" id="moves">0</span></span>
        <span>时间: <span class="value" id="timer">0</span>s</span>
        <span>配对: <span class="value" id="pairs">0</span>/8</span>
    </div>
    <div class="grid" id="grid"></div>
    <button class="btn-restart" onclick="restartGame()">🔄 重新开始</button>
</div>

<div class="overlay" id="overlay">
    <div class="win-modal">
        <h2>🎉 恭喜通关!</h2>
        <p>总步数: <span class="highlight" id="finalMoves">0</span></p>
        <p>用时: <span class="highlight" id="finalTime">0</span> 秒</p>
        <button class="btn-restart" onclick="restartGame()">再来一局 🚀</button>
    </div>
</div>

<script>
    // 卡牌图标池(8对)
    const ICONS = ['🐱', '🐶', '🦊', '🐼', '🐸', '🦁', '🐨', '🐰'];

    let cards = [];
    let flippedCards = [];
    let matchedPairs = 0;
    let moves = 0;
    let timerInterval = null;
    let seconds = 0;
    let lockBoard = false;

    // 创建背景粒子
    function createParticles() {
        const container = document.getElementById('particles');
        for (let i = 0; i < 30; i++) {
            const p = document.createElement('div');
            p.className = 'particle';
            const size = Math.random() * 8 + 3;
            p.style.width = size + 'px';
            p.style.height = size + 'px';
            p.style.left = Math.random() * 100 + '%';
            p.style.animationDuration = (Math.random() * 8 + 6) + 's';
            p.style.animationDelay = (Math.random() * 10) + 's';
            container.appendChild(p);
        }
    }

    // 洗牌算法
    function shuffle(arr) {
        const a = [...arr];
        for (let i = a.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [a[i], a[j]] = [a[j], a[i]];
        }
        return a;
    }

    // 更新状态显示
    function updateStats() {
        document.getElementById('moves').textContent = moves;
        document.getElementById('timer').textContent = seconds;
        document.getElementById('pairs').textContent = matchedPairs;
    }

    // 初始化游戏
    function initGame() {
        const grid = document.getElementById('grid');
        grid.innerHTML = '';
        cards = [];
        flippedCards = [];
        matchedPairs = 0;
        moves = 0;
        seconds = 0;
        lockBoard = false;
        clearInterval(timerInterval);
        timerInterval = null;
        updateStats();

        // 生成卡牌数据:每个图标出现两次
        const cardData = shuffle([...ICONS, ...ICONS]);

        cardData.forEach((icon, index) => {
            const card = document.createElement('div');
            card.className = 'card';
            card.dataset.icon = icon;
            card.dataset.index = index;

            card.innerHTML =
                '<div class="card-face card-back"></div>' +
                '<div class="card-face card-front">' + icon + '</div>';

            card.addEventListener('click', function() { flipCard(card); });
            grid.appendChild(card);
            cards.push(card);
        });

        document.getElementById('overlay').classList.remove('active');
    }

    // 翻牌逻辑
    function flipCard(card) {
        if (lockBoard) return;
        if (card.classList.contains('flipped')) return;
        if (card.classList.contains('matched')) return;
        if (flippedCards.length >= 2) return;

        // 首次点击开始计时
        if (!timerInterval) {
            timerInterval = setInterval(function() {
                seconds++;
                document.getElementById('timer').textContent = seconds;
            }, 1000);
        }

        card.classList.add('flipped');
        flippedCards.push(card);

        if (flippedCards.length === 2) {
            moves++;
            document.getElementById('moves').textContent = moves;
            checkMatch();
        }
    }

    // 检查是否匹配
    function checkMatch() {
        var card1 = flippedCards[0];
        var card2 = flippedCards[1];
        var isMatch = card1.dataset.icon === card2.dataset.icon;

        if (isMatch) {
            card1.classList.add('matched');
            card2.classList.add('matched');
            matchedPairs++;
            document.getElementById('pairs').textContent = matchedPairs;
            flippedCards = [];

            if (matchedPairs === 8) {
                clearInterval(timerInterval);
                setTimeout(function() {
                    showWinScreen();
                    createFireworks();
                }, 600);
            }
        } else {
            lockBoard = true;
            setTimeout(function() {
                card1.classList.remove('flipped');
                card2.classList.remove('flipped');
                flippedCards = [];
                lockBoard = false;
            }, 900);
        }
    }

    // 显示胜利画面
    function showWinScreen() {
        document.getElementById('finalMoves').textContent = moves;
        document.getElementById('finalTime').textContent = seconds;
        document.getElementById('overlay').classList.add('active');
    }

    // 烟花效果
    function createFireworks() {
        var colors = ['#ff0', '#f7971e', '#ff6b6b', '#48dbfb', '#0abde3', '#ffd200', '#ff9ff3', '#54a0ff'];
        for (var f = 0; f < 6; f++) {
            setTimeout(function() {
                var fw = document.createElement('div');
                fw.className = 'firework';
                fw.style.left = (Math.random() * 80 + 10) + '%';
                fw.style.top = (Math.random() * 60 + 10) + '%';

                for (var i = 0; i < 20; i++) {
                    var p = document.createElement('div');
                    p.className = 'firework-particle';
                    var angle = (Math.PI * 2 / 20) * i;
                    var dist = Math.random() * 80 + 40;
                    p.style.setProperty('--tx', Math.cos(angle) * dist + 'px');
                    p.style.setProperty('--ty', Math.sin(angle) * dist + 'px');
                    p.style.background = colors[Math.floor(Math.random() * colors.length)];
                    fw.appendChild(p);
                }

                document.body.appendChild(fw);
                setTimeout(function() { fw.remove(); }, 1200);
            }, f * 400);
        }
    }

    // 重新开始
    function restartGame() {
        initGame();
    }

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

Game Source: 卡牌记忆配对游戏

Creator: NovaFalcon96

Libraries: none

Complexity: complex (456 lines, 14.3 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-novafalcon96" to link back to the original. Then publish at arcadelab.ai/publish.