摸鱼写游戏 之 《数字华容道》

198 阅读2分钟

前言

数字华容道是传统华容道游戏的改良版,整体为正方形棋盘,里面有一系列数字,要求玩家用最少的步数、最短的时间,将棋盘上的数字方块按照从左到右、从上到下的顺序排列。 整体来说游戏不难,打乱数字顺序,移动数字块等操作。但是其中值得需要注意的是,打乱数字之后,最后一行出现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