记忆翻牌游戏开发:实现卡牌匹配检测、删除与点击控制功能
卡牌匹配游戏核心功能实现
需求说明
需要实现以下功能:
- 点击卡牌时,最多同时翻开两张
- 两张卡牌翻开后,禁用所有卡牌点击直到匹配判断完成
- 若两张卡牌图案匹配,删除对应的卡牌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) // 初始化后开启点击
关键逻辑说明
状态控制:
isProcessing:标记是否正在进行匹配判断,避免用户在判断期间重复点击flippedCards:数组存储当前翻开的两张卡牌,用于后续对比
点击事件过滤:
点击卡牌时先判断三种无效情况:正在处理匹配、卡牌已经翻开、已经选了两张,直接返回不执行任何操作匹配判断:
- 对比两张卡牌背面的背景图URL,判断是否匹配
- 匹配成功:延迟删除卡牌,给用户足够的时间确认匹配结果
- 匹配失败:延迟翻回卡牌,让用户记住两张卡牌的图案后再隐藏
点击禁用优化:
修改了原toggleClick函数,针对所有.cards元素设置pointer-events,而不是仅控制第一个div,确保所有卡牌的点击状态统一
内容的提问来源于stack exchange,提问作者LeeIsII
相关产品推荐
相关产品推荐

