You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

记忆翻牌游戏开发:实现卡牌匹配检测、删除与点击控制功能

卡牌匹配游戏核心功能实现

需求说明

需要实现以下功能:

  • 点击卡牌时,最多同时翻开两张
  • 两张卡牌翻开后,禁用所有卡牌点击直到匹配判断完成
  • 若两张卡牌图案匹配,删除对应的卡牌div元素
  • 若不匹配,将两张卡牌翻回背面

完整代码实现

HTML(保持原结构不变)

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]>      <html class="no-js"> <!--<![endif]-->
<html>

  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="style/index.css">
    <script src='js/jquery.js'></script>
    <script src="js/app.js" async defer></script>
  </head>

  <body>
    <header><h1 id='title'>Halloween Match:</h1></header>
    <div class='card_box'>
        <div class='cards g1'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t0'>
            
          </div>
        </div>
        <div class='cards g1'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t1'>
            
          </div>
        </div>
        <div class='cards g1'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t2'>
            
          </div>
        </div>
        <div class='cards g1'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t3'>
            
          </div>
        </div>
        <div class='cards g2'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t4'>
            
          </div>
        </div>
        <div class='cards g2'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t5'>
            
          </div>
        </div>
        <div class='cards g2'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t6'>
            
          </div>
        </div>
        <div class='cards g2'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t7'>
            
          </div>
        </div>
        <div class='cards g3'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t8'>
            
          </div>
        </div>
        <div class='cards g3'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t9'>
            
          </div>
        </div>
        <div class='cards g3'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t10'>
            
          </div>
        </div>
        <div class='cards g3'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t11'>
            
          </div>
        </div>
        <div class='cards g4'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t12'>
            
          </div>
        </div>
        <div class='cards g4'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t13'>
            
          </div>
        </div>
        <div class='cards g4'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t14'>
            
          </div>
        </div>
        <div class='cards g4'>
          <div class='card-face front'>
            
          </div>
          <div class='card-face back' id='t15'>
            
          </div>
        </div>
      </div>
      
  </body>

</html>

CSS(保持原样式不变)

@import url('https://fonts.googleapis.com/css2?family=Creepster&display=swap');

*{
    background-color: #FF6A00;
    margin: auto;
    padding: 5px;
    font-family: Creepster;
}

#title{
  position: absolute;
  left: 42vw;
  font-size: 40px;
  color: gainsboro;
  text-shadow: 1px 1px black, -1px -1px black;
}

.card_box {
    width: 100px;
    height: 100px;
    display: inline-block;
    perspective: 1000px;
  }
  
  .cards {
    position: relative;
    width: 100%;
    height: 100%;
    transform-style: preserve-3d;
    transform-origin: center right;
    transition: transform 500ms;
    margin-top: 5px;
    margin-left: 32vw;
  }
  
  .flipped {
    transform: rotateY(-180deg) translateX(100%);
  }
  
  .card-face {
    position: absolute;
    width: 100%;
    height: 100%;
    color: white;
    backface-visibility: hidden;
    line-height: 100px;
    text-align: center;
    font-weight: bolder;
    font-size: 25px;
  }
  .card-face:hover{
    cursor: pointer;
    box-shadow: 2px 2px 20px 5px white,-2px -2px 20px 5px white;
  }
  .front {
    background: url('../imgs/back.png');
    background-size: 100% 100%;
    color: black;
  }
  
  .back {
    background: black;
    color: tan;
    background-size: 100% 100%;
    transform: rotateY(180deg);
  }
  .g1{
    left: 0px;
    top: 0px;
  }
  .g2{
    left: 150px;
    top: -400px;
  }
  .g3{  
    left: 300px;
    top: -861px;
  }
  .g4{
    left: 450px;
    top: -1379px;
  }

修改后的JavaScript/JQuery代码

document.addEventListener('contextmenu', event => event.preventDefault());

const SCOUNT = 100000
const cards = document.querySelectorAll('.cards')
const IMGS = {
    back: "./imgs/back.png",
    bat: "./imgs/bat.png",
    blank: "./imgs/blank.png",
    ghost: "./imgs/ghost.png",
    jol: "./imgs/jol.png",
    jack: "./imgs/js.png",
    vamp: "./imgs/vamp.png",
    witch: "./imgs/witch.png",
    wolf: "./imgs/ww.png",
    zomb: "./imgs/zomb.png",
}
const PICS = [
    IMGS.bat,
    IMGS.ghost,
    IMGS.jol,
    IMGS.jack,
    IMGS.vamp,
    IMGS.witch,
    IMGS.wolf,
    IMGS.zomb,
    IMGS.bat,
    IMGS.ghost,
    IMGS.jol,
    IMGS.jack,
    IMGS.vamp,
    IMGS.witch,
    IMGS.wolf,
    IMGS.zomb
]

const TILES = [
    $('#t0'), $('#t1'), $('#t2'), $('#t3'),
    $('#t4'), $('#t5'), $('#t6'), $('#t7'),
    $('#t8'), $('#t9'), $('#t10'), $('#t11'),
    $('#t12'), $('#t13'), $('#t14'), $('#t15')
]

// 状态变量:存储当前翻开的卡牌、是否正在处理匹配判断
let flippedCards = [];
let isProcessing = false;

function shuffle(){
    for (let shuf = 0; shuf < SCOUNT; shuf++){
        let x = Math.floor(Math.random()*PICS.length)
        let y = Math.floor(Math.random()*PICS.length)
        let z = PICS[x]
        PICS[x] = PICS[y]
        PICS[y] = z; 
    }
}

function implementBacks(){
    for(let i = 0; i < 16; i++){
        TILES[i].css('background',`url(${PICS[i]})`)
        TILES[i].css('background-size','100% 100%')
    }
}

// 控制所有卡牌的点击可用性
function toggleClick(isEnabled){
    cards.forEach(card => {
        card.style.pointerEvents = isEnabled ? 'all' : 'none';
    });
}

// 匹配判断核心函数
function checkMatch() {
    isProcessing = true;
    toggleClick(false); // 禁用所有点击

    const card1 = flippedCards[0];
    const card2 = flippedCards[1];
    // 获取两张卡牌的背景图URL(去掉url()包裹)
    const img1 = card1.querySelector('.back').style.background.replace(/url\(['"]?(.*?)['"]?\)/, '$1');
    const img2 = card2.querySelector('.back').style.background.replace(/url\(['"]?(.*?)['"]?\)/, '$1');

    if (img1 === img2) {
        // 匹配成功:延迟删除卡牌,给用户视觉反馈
        setTimeout(() => {
            card1.remove();
            card2.remove();
            flippedCards = [];
            isProcessing = false;
            toggleClick(true); // 恢复点击
        }, 800);
    } else {
        // 匹配失败:延迟翻回卡牌
        setTimeout(() => {
            card1.classList.remove('flipped');
            card2.classList.remove('flipped');
            flippedCards = [];
            isProcessing = false;
            toggleClick(true); // 恢复点击
        }, 1200);
    }
}

// 初始化卡牌点击事件
cards.forEach((card) => {
    // 初始翻转动画
    card.classList.toggle('flipped')
    setTimeout(() => {
        card.classList.toggle('flipped')
    }, 500)

    card.onclick = () => {
        // 过滤无效点击:正在处理中、卡牌已翻开、已经选了两张
        if (isProcessing || card.classList.contains('flipped') || flippedCards.length >= 2) {
            return;
        }

        // 翻开当前卡牌
        card.classList.add('flipped');
        flippedCards.push(card);

        // 选够两张后开始判断匹配
        if (flippedCards.length === 2) {
            checkMatch();
        }
    } 
})

// 初始化游戏
shuffle()
implementBacks()
toggleClick(true) // 初始化后开启点击

关键逻辑说明

  1. 状态控制:

    • isProcessing:标记是否正在进行匹配判断,避免用户在判断期间重复点击
    • flippedCards:数组存储当前翻开的两张卡牌,用于后续对比
  2. 点击事件过滤:
    点击卡牌时先判断三种无效情况:正在处理匹配、卡牌已经翻开、已经选了两张,直接返回不执行任何操作

  3. 匹配判断:

    • 对比两张卡牌背面的背景图URL,判断是否匹配
    • 匹配成功:延迟删除卡牌,给用户足够的时间确认匹配结果
    • 匹配失败:延迟翻回卡牌,让用户记住两张卡牌的图案后再隐藏
  4. 点击禁用优化:
    修改了原toggleClick函数,针对所有.cards元素设置pointer-events,而不是仅控制第一个div,确保所有卡牌的点击状态统一

内容的提问来源于stack exchange,提问作者LeeIsII

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 20:10:27