当前位置: 首页 > news >正文

开发vue小游戏:数字华龙道

一、游戏介绍

1、历史背景
        数字华容道脱胎于传统华容道,后者源自三国时期“曹操败走华容道”的故事。传统玩法是通过移动不同形状的木块,帮助“曹操”从出口逃脱。而数字华容道将棋子替换为数字,目标是通过滑动方块,将乱序的数字按1至N的顺序排列整齐(例如4×4棋盘需排列1-15,留一空格作为移动空间)。

2、基本规则

  • 棋盘分为3×3、4×4、5×5等不同难度等级,数字范围随棋盘大小递增。
  • 每次只能移动与空格相邻的数字方块,通过滑动调整顺序,最终达成从左到右、从上到下的数字连贯性

二、代码生成游戏盘面

1、实现思路:

1.按游戏难度生成一个目标数字盘面;

2.随机打乱整个盘面的数字;

3.判断盘面可解性,无解则重新生成直到有解为止。

2、实现代码:

/** 难度:3x3、4x4、5x5 */
const level = ref(4);
/** 数字盘面 */
const list = ref([]);

/**
 * 随机打乱数组
 * @param {number[]} array 要打乱的数组
 */
function shuffleArray(array: number[]) {
	for (let i = array.length - 1; i > 0; i--) {
		const j = Math.floor(Math.random() * (i + 1));
		[array[i], array[j]] = [array[j], array[i]];
	}
	return array;
}

/**
 * 判断数字华容道的可解性
 * @param {number[]} array 数字盘面
 * @param {number} row 盘面行数
 */
function isSolvable(array: number[], row: number = 4) {
	// 去掉空格(0)
	const puzzles = array.filter(item => item > 0);
	// 逆序数:例如,在序列 [2, 3, 1] 中,2 和 1 构成一个逆序对,3 和 1 也构成一个逆序对。
	let n = 0;
	for (var i = 0; i < puzzles.length; i++) {
		for (var j = i + 1; j < puzzles.length; j++) {
			if (puzzles[i] > puzzles[j]) {
				n++;
			}
		}
	}
	/**
	 * 若格子列数为奇数,则逆序数必须为偶数;
	 * 若格子列数为偶数,且逆序数为偶数,则当前空格所在行数与初始空格所在行数的差为偶数;
	 * 若格子列数为偶数,且逆序数为奇数,则当前空格所在行数与初始空格所在行数的差为奇数
	 */
	if (level.value % 2 !== 0) return n % 2 === 0;
	// 空格所在的行
	let emptyRow = Math.ceil((array.findIndex(item => item === 0) + 1) / row);
	return emptyRow % 2 === 0 ? n % 2 === 0 : n % 2 !== 0;
}

/**
 * 创建游戏
 * @param {number} n 游戏难度
 */
function createGame(n?: number) {
	level.value = n || level.value;
	const ls = [];
	for (var i = 0; i < level.value * level.value; i++) {
		ls.push(i);
	}
	const array = shuffleArray(ls);
	if (isSolvable(array, level.value)) { // 是否可解
		list.value = array;
	} else {
		createGame();
	}
}

createGame();

 三、简单开发游戏界面

1.呈现游戏盘面;

2.增加计步、计时、难度选择、暂停/继续、重新开始。

<template>
	<div class="page-box">
		<div class="game-box" :style="{
			gridTemplateColumns: `repeat(${level}, 1fr)`,
			width: `calc(${level}00px + ${level - 1}0px)`,
		}">
			<template v-for="(n, i) in list" :key="n">
				<div v-if="n === 0"  :class="{ 'is-active': i === first }" @click="onItemClick(i)"></div>
				<div v-else class="game-item" :class="{ 'is-active': i === first }" @click="onItemClick(i)">{{ n }}</div>
			</template>
		</div>
		<div style="display: flex;justify-content: space-between;width: 50%;">
			<div>步数:{{ step }}</div>
			<div>耗时:{{ timeFormat }}</div>
		</div>
		<div style="display: flex;justify-content: space-between;width: 50%;">
			<div>难度:</div>
			<div @click="createGame(3)">3x3</div>
			<div @click="createGame(4)">4x4</div>
			<div @click="createGame(5)">5x5</div>
		</div>
		<div style="display: flex;justify-content: space-between;width: 50%;">
			<button @click="createGame()">重新开始</button>
			<button @click="changeStatus">{{isStop ? '继续' : '暂停' }}</button>
		</div>
	</div>
</template>

<style lang="scss">
.page-box {
	display: flex;
	flex-direction: column;
	align-items: center;
	zoom: 20%;
	gap: 20px;
}
.game-box {
	display: grid;
	gap: 5px;
	padding: 10px;
	background: #d7d8db;
	border-radius: 10px;
	.game-item {
		border: 1px solid #000;
		border-radius: 10px;
		height: 100px;
		background-color: #fff;
		font-size: 40px;
		display: flex;
		align-items: center;
		justify-content: center;
	}
	.is-active {
		box-shadow: 0 0 8px 8px rgba(255, 0, 0, 0.5) inset;
	}
}
</style>

四、实现交互数字换位操作

1、点击空格和相邻数字交换位置;

2、统计步数;

3、验证是否完成游戏。


/** 步数 */
const step = ref(0);
/** 首次点击位置 */
const first = ref(-1);
/** 游戏是否暂停 */
const isStop = ref(false);

/** 是否完成游戏 */
function isSuccess(array: number[]) {
	return array.every((n, m) => (n === 0 && m === level.value * level.value - 1) || n === m + 1)
}

/** 交换两个数组元素 */
function exchange(array: number[], i: number, j: number) {
	[array[i], array[j]] = [array[j], array[i]];
	return array;
}

function onItemClick(i: number) {
	if (first.value === -1) {
		first.value = i;
	} else if (first.value !== i) {
		if ([list.value[first.value], list.value[i]].includes(0) && (Math.abs(first.value - i) === 1 || (Math.abs(first.value - i) <= level.value && first.value % level.value === i % level.value))) {
			exchange(list.value, i, first.value);
			step.value++;
			if (isSuccess(list.value)) {
				uni.showToast({
					title: '挑战成功',
					icon: 'success'
				});
				isStop.value = true;
			}
		}
		first.value = -1;
	}
}

五、实现游戏计时和暂停/继续功能

/** 耗时(s) */
const time = ref(0);
let timer = null;

const timeFormat = computed(() => {
	let res = '';
	if (time.value >= 3600) res += `${Math.floor(time.value / 3600)}时`;
	if (time.value >= 60) res += `${Math.floor(time.value % 3600 / 60)}分`;
	return res + `${Math.floor(time.value % 60)}秒`
});

/** 计时和暂停 */
function changeStatus() {
	if (isStop.value) {
		isStop.value = false;
		timeDown();
	} else {
		clearTimeout(timer);
		timer = null;
		isStop.value = true;
	}
}

/** 倒计时 */
function timeDown() {
	timer = setTimeout(() => {
		if (isStop.value) return;
		time.value++;
		timeDown();
	}, 1000);
}

还需在创建游戏时重置数据

/**
 * 创建游戏
 * @param {number} n 游戏难度
 */
function createGame(n?: number) {
	// console.log('createGame', n, level.value)
	level.value = n || level.value;
	const ls = [];
	for (var i = 0; i < level.value * level.value; i++) {
		ls.push(i);
	}
	const array = shuffleArray(ls);
	if (isSolvable(array, level.value)) { // 是否可解
        //
		first.value = -1;
		step.value = 0;
		if (timer) {
			clearTimeout(timer);
			timer = null;
		}
		time.value = 0;
		isStop.value = false;
		timeDown();
        //
		list.value = array;
	} else {
		createGame();
	}
}

六、用鼠标玩太累了,支持下键盘操作

1、上下左右键控制空格先指定方位的数字进行位置交换;

2、空格键控制暂停/继续;

3、数字键控制对应数字进行位置交换。


window.addEventListener('keydown', (e) => {
	switch(e.key) {
		case 'ArrowUp':
			first.value === -1 && onItemClick(list.value.findIndex(item => item === 0));
			first.value >= level.value && onItemClick(first.value - level.value);
			break;
		case 'ArrowDown':
			first.value === -1 && onItemClick(list.value.findIndex(item => item === 0));
			first.value < level.value * (level.value - 1) && onItemClick(first.value + level.value);
			break;
		case 'ArrowLeft':
			first.value === -1 && onItemClick(list.value.findIndex(item => item === 0));
			first.value % level.value > 0 && onItemClick(first.value - 1);
			break;
		case 'ArrowRight':
			first.value === -1 && onItemClick(list.value.findIndex(item => item === 0));
			(first.value + 1) % level.value > 0 && onItemClick(first.value + 1);
			break;
		case 'Backspace':
			break;
		case ' ':
			changeStatus();
			break;
		default :
			const i = list.value.findIndex(item => item.toString() === e.key)
			if(i !== -1) onItemClick(i);
			break;
	}
});

七、玩法与技巧

1、分阶段排列法

  • 逐行推进:优先排列第一行(如1-3),再依次完成后续行。例如,在4×4棋盘中,先固定1-4,再处理5-8,以此类推210。
  • 处理底层数字:若目标数字位于底层,需通过“绕行”策略,将上层数字暂时移开,腾出空间调整1012。

2、高阶技巧

  • 循环移动法:利用空格形成循环路径,将目标数字逐步推至目标位置(如将4从底层移至第一行时,需反复调整1-3的位置)。
  • 预留通道:在排列后几行时,需为后续调整保留移动通道,避免死锁

相关文章:

  • 2025 docker安装TiDB数据库
  • 嵌入式人工智能应用-第6章 人脸检测
  • 华为鸿蒙系统全景解读:从内核设计到生态落地的技术革命
  • Java中的栈的实现
  • css 文本属性-第五章
  • 利用Deepseek+即梦,3分钟做出疗愈风禅意小院视频(含sop)
  • 网站应用-电脑PC微信快速登录-微信开发平台
  • 在Uniapp中实现特殊字符弹出框并插入输入框
  • 第本章:go 切片
  • win11 Visual Studio 17 2022源码编译 opencv4.11.0 + cuda12.6.3 启用GPU加速
  • Java链接redis
  • react 和 react-dom
  • VUE3项目的文档结构分析
  • JVM、JDK、JRE三者的关系
  • 【linux网络编程】字节序
  • 第七章 二叉树
  • 生成式AI系列(二) LLM生成质量改善的方法——RAG检索增强生成
  • Python评估网络脆弱性
  • Redis常问八股(一)
  • Java网络编程,多线程,IO流综合项目一一ChatBoxes
  • 网上做任务赚钱网站有哪些/seo服务外包报价
  • 做王境泽表情的网站/个人怎么做百度竞价
  • wordpress 素材网站模版/推广策划方案怎么做
  • 带注册登录的网站模板/免费优化
  • 网站中的二级菜单怎么做23/网店推广培训
  • 衡水网站建设哪家好/营销策略是什么