携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情 >>
前言
最近迷上了拿Js复刻各类经典小游戏,今天就是尝试复刻一下13点游戏,要实现的功能就是两张牌加起来等于13点就消除,然后还要加上一些消牌动效,切牌动效,初始化动效之类的
卡牌桌面
桌面的话,很简单就是一个700*800的长方形 , moveL和moveR是换牌用的。一个是从左往右换 一个是从右往左换
<div class="table">
<div class="moveL"> < </div>
<div class="moveR"> > </div>
</div>
//css
.table{
width: 700px;
height: 800px;
margin:20px auto;
position: relative;
background-image: url("https://gimg2.baidu.com/image_search/src=http%3A%2F%2F2e.zol-img.com.cn%2Fproduct%2F102%2F840%2FceX1m2pmkRSs.jpg&refer=http%3A%2F%2F2e.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1663638867&t=5dde1bdd205db8ec75ab2a52446dcdbf");
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
color: #ffffff;
}
生成卡牌
接下来就是在页面上动态创建卡牌,并随机赋予对应的卡牌,首先,一副牌除了大小王是52张,黑桃(Spade)、红桃(Heart)、方块(Diamond)、梅花(Club)每种13张,所以我们需要一副牌的img,并按数字+花色首字母进行命名
接下来就是通过代码来把牌放在页面上
首先先把牌进行初始化,花色4 数字13 不能重复
// 52 4 13
// hcsd 黑桃(Spade)、红桃(Heart)、方块(Diamond)、梅花(Club)
let poke=[];
let color=['h','s','c','d'];
let exist={};
// 这里就是循环产生52张牌的花色及下标,而且为了保证不重复,通过exist来保存已经生成的牌
for(let i=0;i<52;i++){
let huase=color[Math.floor(Math.random()*4)];
let shuzi=Math.floor(Math.random()*13+1);
while (exist[`${huase}_${shuzi}`]){
huase=color[Math.floor(Math.random()*4)];
shuzi=Math.floor(Math.random()*13+1);
}
exist[`${huase}_${shuzi}`]=true;
poke.push({huase,shuzi});
}
然后把牌以金字塔的形式放在页面上,这里因为上面卡牌生成是随机的,所以poke里52张牌的顺序也是随机的,相当于是进行了乱序洗牌的操作,想形成金字塔,第一时间我想到的就是双层for循环,第一行一张,第二行两张,依次类推,那就是相当于把第一层for循环的循环游标作为第二层for循环的结束条件
for(let i=0;i<7;i++){
for(let j=0;j<=i;j++){
let item=poke[index];
//item={huase:shuzi}
// $("<img>").appendTo(document.body).attr({src:`img/${item.shuzi}${item.huase}.png`});
let imgSrc=`//pic.qy566.com/${item.shuzi}${item.huase}.png`;
// $("img").remove();
$('<div>').addClass('poke').css({
backgroundImage:`url(${imgSrc})`
}).delay(30*index)
.prop({id:`${i}_${j}`}).attr({pokeid:`${item.shuzi}`})
.data("num",item.shuzi)
.animate({
left:300-i*50+100*j,
top:70*i,
opacity:1
})
.appendTo('.table');
}
}
形成金子塔还不算完,我们还需要把剩下的牌放在底部,供消除使用,这里上面两层for循环相当于等差数列求和,当然我们跑一次就会知道结果了,为了方便直接使用,我们声明一个下标,放在第二层for循环内让他自增,这样,循环完成后的下标就是poke未使用的第一个下标
let index= 0
...
for(let j=0;j<=i;j++){
...
index++;
...
把剩下的牌放进页面内,通过定位的方式放置在页面下方
for(;index<poke.length;index++){
let item=poke[index];
let imgSrc=`//pic.qy566.com/${item.shuzi}${item.huase}.png`;
$('<div>').addClass('poke zuo').attr({pokeid:`${item.shuzi}`})
.data("num",item.shuzi)
.css({
backgroundImage:`url(${imgSrc})`
}).delay(30*index)
.animate({
left:100,
top:600,
opacity:1
})
.appendTo('.table');
}
然后。就是给牌添加点击事件,然后判断选中的两张牌和是否为13,他下面还有没有牌压着他,这里你们也注意到了,我给每张牌都添加了自定义属性叫做pokeid 值是对应的数字,这里任意花色都可组合,所以我们不考虑花色的问题,在初始化上面的卡牌的时候,我们把id定义为了i_j,这样,我们通过i+1 j+1存不存在,就知道下面有没有牌压着他了
$('.poke').click(function () {
// 拿到当前的id,并拿到他下面的左面那张和右面那张的id
let coords = $(this).prop('id').split('_')
let ele = $(`#${parseInt(coords[0]) + 1}_${parseInt(coords[1])}`)
let ele1 = $(`#${parseInt(coords[0]) + 1}_${parseInt(coords[1]) + 1}`)
// 判断假如被压着,那么就return 不做任何操作,没有被压着的时候,需要给一个动画效果,浮起
if (!(ele.length || ele1.length)) {
$(this).toggleClass('active')
if ($(this).hasClass('active')) {
$(this).finish().animate({ top: '-=20' })
} else {
$(this).finish().animate({ top: '+=20' })
}
} else {
return
}
// 判断点的牌超过两张但是仍然没有达成条件,那就都移除浮起的效果
if ($('.active').length > 2) {
$('.active').animate({ top: '+=20' })
$('.active').removeClass('active')
// $(".active").eq(1).removeClass("active");
}
// 判断是否和为13 是的话,给一个飞出动画,并且移除对应dom元素
$('.active').map(function (value, index) {
for (let i = 0; i < $('.active').length; i++) {
// console.log($(index).attr("pokeid"));
if (parseInt($(index).attr('pokeid')) + parseInt($('.active').eq(i).attr('pokeid')) == 13 || $(index).attr('pokeid') == 13) {
$('.active').animate({ top: 0, left: 600 }, function () {
$(this).remove()
})
}
}
})
})
接下来就是下面的两个左右箭头,可以操纵底下的备选卡牌进行左右移动
这里也是通过添加css样式+animation的方式实现的,所以就不粘贴代码了,具体代码都在代码片段内了
关键技术点
$.finish 这是为了保证能够流畅的应用动画,避免多次点击同一元素,不断执行添加移除样式导致的无限动画。
停止当前正在运行的动画,删除所有排队的动画,并完成匹配元素所有的动画。
当.finish()在一个元素上被调用,立即停止当前正在运行的动画和所有排队的动画(如果有的话),并且他们的CSS属性设置为它们的目标值(所有动画的目标值)。所有排队的动画将被删除。
如果第一个参数提供,该字符串表示的队列中的动画将被停止。
.finish()方法和.stop(true, true)很相似,.stop(true, true)将清除队列,并且目前的动画跳转到其最终值。但是,不同的是,.finish() 会导致所有排队的动画的CSS属性跳转到他们的最终值。