前言
数字华容道是传统华容道游戏的改良版,整体为正方形棋盘,里面有一系列数字,要求玩家用最少的步数、最短的时间,将棋盘上的数字方块按照从左到右、从上到下的顺序排列。 整体来说游戏不难,打乱数字顺序,移动数字块等操作。但是其中值得需要注意的是,打乱数字之后,最后一行出现8、7或者13、15、14等的可能,这个是成不了的。
1. 游戏基本布局
这里我没用方块移动动画,感兴趣的可以自己做成方块移动效果,只要把方块设成position: absolute,再刚开始计算每一块的top、left即可。
<view class="klotski z-height-vh flexColumn">
<!-- 当前等级 -->
<view class="klotski-status flexCenter">
<view class="klotski-status-item">
<view class="klotski-status-rope"></view>
<view class="klotski-status-level">
<view class="klotski-status-level-box">3X3</view>
</view>
</view>
</view>
<!-- 时间、步数、等级选择 -->
<view class="klotski-btns box">
<!-- <view class="klotski-btns-title z-t-c" v-if="!is_start">目标关卡</view> -->
<view class="flexBetween" style="height: 100%;">
<view class="klotski-btns-time flexCenter">时间 {{time}}</view>
<view class="klotski-btns-step flexCenter">步数 <text class="u-m-l-20">{{step}}</text></view>
<view class="klotski-btns-level flexCenter" @click="bindLevel">等级</view>
</view>
</view>
<!-- 游戏区 -->
<view class="klotski-play">
<view class="klotski-play-box">
<view class="klotski-play-tr" v-for="(item1, index1) in newList" :key="index1">
<view class="klotski-play-td" v-for="(item2, index2) in item1" :key="index2" @click="bindBlock(index1, index2)">
<view class="klotski-play-td-box" v-if="item2 !== ''">
<view class="klotski-play-td-block flexCenter">{{item2}}</view>
</view>
</view>
</view>
</view>
</view>
<!-- 开始游戏按钮-带动画 -->
<view class="flex1 flexCenter" v-if="!is_start">
<view class="klotski-start" @click="playStart">开始游戏</view>
</view>
</view>
<style lang="scss" scoped>
@mixin three-dimensional{
border-top: 8rpx solid rgb(248,232,210);
border-right: 8rpx solid rgb(156,116,79);
border-bottom: 8rpx solid rgb(136,99,65);
border-left: 8rpx solid rgb(156,116,79);
}
.klotski-status{
padding: 40rpx 30rpx;
.klotski-status-level{
position: relative;border-radius: 28rpx;background-color: rgb(180, 102, 61);padding: 10rpx;box-shadow: 10rpx 14rpx 10rpx 0rpx rgba(0,0,0,.6);
.klotski-status-level-box{position: relative;width: 100%;height: 100%;padding: 10rpx 50rpx;background-color: rgb(241,241,241);border-radius: 20rpx;box-shadow: inset 2rpx 4rpx 10rpx 4rpx rgba(0,0,0,.6);font-weight: bold;font-size: 50rpx;}
}
.klotski-status-rope{
position: relative;width: 100%;
&::after{content: "";position: absolute;width: 10rpx;height: 80rpx;background-color: rgb(180, 102, 61);top: -60rpx;left: 50rpx;box-shadow: 10rpx 14rpx 10rpx 0rpx rgba(0,0,0,.6);}
&::before{content: "";position: absolute;width: 10rpx;height: 80rpx;background-color: rgb(180, 102, 61);top: -60rpx;right: 50rpx;box-shadow: 10rpx 14rpx 10rpx 0rpx rgba(0,0,0,.6);}
}
}
.klotski-btns{
height: 134rpx; padding: 0 30rpx 40rpx;
.klotski-btns-title{font-size: 70rpx;font-weight: bold;-webkit-text-stroke: 2rpx rgba(180, 102, 61,.5); background-image: linear-gradient(to bottom, rgb(254,243,201), rgb(254,235,38));-webkit-background-clip: text;color: transparent;}
.klotski-btns-time{width: 210rpx;height: 60rpx; background-color: rgb(95,94,93);color: #fff;border-radius: 100rpx;box-shadow: inset 4rpx 4rpx 10rpx rgba(0,0,0,1);font-weight: bold;font-size: 30rpx;}
.klotski-btns-step{width: 210rpx;height: 60rpx;background-color: #fff;font-weight: bold;font-size: 30rpx;border-radius: 100rpx;box-shadow: inset 4rpx 4rpx 10rpx rgba(0,0,0,.5);}
.klotski-btns-level{width: 210rpx;height: 60rpx;background-color: rgb(69, 28, 13);color: rgb(254,228,58);font-weight: bold;font-size: 30rpx;border-radius: 20rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 0, 0, 1);}
}
.klotski-play{
width: 100%;height: 750rpx;border-radius: 20rpx;background-color: rgb(69, 28, 13);border: 20rpx solid rgb(180, 102, 61);box-shadow: 0rpx 12rpx 15rpx 0rpx rgba(0,0,0,.6);
.klotski-play-box{width: 100%;height: 100%;display: flex;flex-direction: column;border: 4rpx solid rgb(2,0,2);}
.klotski-play-tr{display: flex;width: 100%;flex: 1;}
.klotski-play-td{flex: 1;}
.klotski-play-td-box{width: 100%;height: 100%;color: #fff;font-size: 80rpx;border: 4rpx solid rgb(2,0,2);}
.klotski-play-td-block{width: 100%;height: 100%;background-color: rgb(232,189,131);color: rgb(78,28,9);font-weight: bold;@include three-dimensional;}
}
.klotski-start{padding: 40rpx 120rpx;background-color: rgb(69, 28, 13);color: #fff;font-weight: bold;font-size: 40rpx;border-radius: 100rpx;box-shadow: 0rpx 10rpx 15rpx 0rpx rgba(0,0,0,.4);animation: btnScale 1s infinite linear;}
@keyframes btnScale{
0%{transform: scale(1);}
50%{transform: scale(1.1);}
100%{transform: scale(1);}
}
</style>
2. 定义我们需要用到的参数
level: ['3X3', '4X4', '5X5'], // 等级
is_start: false, // 是否开始
is_over: false, // 是否结束
rowcol: 3, // 等级对应的行列数
list: [], // 每个等级需要的数字集合 (为了结束时判断输赢)
newList: [], // 二维数组集合
step: 0, // 一共走了多少步
time: 0, // 所用的时间
timer: null // 定时器
3. 初始化需要的数据
每次游戏都要重新根据级别获取相关的数据,比如切换级别的时候
init(){
const allNum = this.rowcol * this.rowcol;
for(let i = 0; i < allNum; i++){
this.list[i] = i < allNum - 1 ? i + 1 : ""
this.newList[i] = i < allNum - 1 ? i + 1 : ""
}
// 这里是一个数组转换,一维数组转二维数组,可自行查看其他js文章或者游戏,里面都有写到
this.newList = this.changeDimen(this.newList)
}
4. 用到的方法
之前游戏文章中说到,我们写游戏,最多的就是打乱数组,转换数组等,所以时常要用到,
// 一维数组转成二维数组
changeDimen(arr){
let newArr = []
while(arr.length > 0){
newArr.push(arr.splice(0, this.rowcol))
}
return newArr
}
// 打乱数组 —— 查看我js专栏文章
// 深拷贝数组 —— 同上
5. 开始游戏
开始游戏的时候,我们需要克隆一个一维数组list,然后把一维数组打乱,再转成二维数组即可
playStart(){
this.is_over = false;
this.is_start = true;
this.newList = this.changeDimen(this.$u.randomArray(this.$u.deepClone(this.list)));
this.timer = setInterval(()=>{
this.time++;
},1000)
}
6. 点击数字方块
点击方块的时候,(没开始 已结束 空方块)都不能触发逻辑
if(!this.is_start || this.is_over || this.newList[x][y] == ''){
return;
}
// 判断上下左右有没有空格,有则移动
if(this.newList[x - 1] && this.newList[x - 1][y] == ''){
this.newList[x - 1][y] = this.newList[x][y]
this.newList[x][y] = ""
this.step++
}
if(x + 1 < this.rowcol && this.newList[x + 1][y] == ''){
this.newList[x + 1][y] = this.newList[x][y]
this.newList[x][y] = ""
this.step++
}
if(y - 1 >= 0 && this.newList[x][y - 1] == ''){
this.newList[x][y - 1] = this.newList[x][y]
this.newList[x][y] = ""
this.step++
}
if(y + 1 < this.rowcol && this.newList[x][y + 1] == ''){
this.newList[x][y + 1] = this.newList[x][y]
this.newList[x][y] = ""
this.step++
}
this.$forceUpdate()
// 移动完,判断是否赢了
this.win()
7. 判断输赢
判断是否赢了,就是把newlist数组变成一维数组,再去跟list比较是否相同
win(){
let newArr = this.newList.reduce((a, b) => { return a.concat(b) })
if(this.list.toString() == newArr.toString()){
clearInterval(this.timer)
uni.showModal({
title: "恭喜你赢了",
content: `用时:${this.time}s, 步数:${this.step}`,
showCancel: false,
success: res => {
if (res.confirm) {
this.initGame()
}
}
})
}
}
8. 重置游戏
initGame(){
clearInterval(this.timer)
this.is_over = false
this.is_start = false
this.step = 0
this.time = 0
this.timer = null
this.list = []
this.newList = []
this.init()
}
9. 选择等级
这里自己可以随便规定等级,游戏原理都是一样的。
bindLevel(){
uni.showActionSheet({
itemList: this.level,
success: res => {
this.rowcol = Number(this.level[res.tapIndex][0]) // 获取3x3字符串中第一个3
this.initGame()
}
})
}
10. 最后
游戏整体实现不难,其中可优化的地方还是不少的,其中我之前说的,如果规避末尾数字的bug,因为游戏是以前写的了,此处没做处理,也就是说有几率会出现无解的bug。感兴趣的可自行解决。还比如方块移动的效果,也没做,也可自行解决。
最后附上完整代码: 【数字华容道】完整代码 —— gitee