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

数字华容道游戏

在AI工具辅助下完善了网页版数字华容道游戏,可直接下载,离线使用,祝使用愉快!
界面如下:
在这里插入图片描述
成功界面:
在这里插入图片描述

自制数字华容道游戏 | 支持多规格+自定义序列+无解判断

作为经典的益智游戏,数字华容道不仅能锻炼逻辑思维,还能打发碎片时间。这次我基于HTML/CSS/JavaScript自制了一款功能完备的数字华容道,支持多尺寸棋盘、自定义序列、无解判断等实用功能,完全适配PC和移动端,分享给大家一起交流学习~

核心功能

  • 多规格棋盘自由切换:支持3×3到10×10共8种尺寸,从新手到大神难度全覆盖
  • 实时数据统计:自动记录游戏步数和耗时,清晰展示通关效率
  • 自定义序列功能:可手动输入数字序列,满足个性化游戏需求
  • 逆序数无解判断:通过算法自动识别无解布局,避免无效尝试
  • 响应式设计:PC端、手机端自适应显示,布局美观不凌乱
  • 流畅交互体验:方块移动有过渡动画,按钮hover反馈明显,操作手感舒适

核心实现解析

1. 棋盘生成与自适应布局

通过动态创建表格实现不同尺寸棋盘,根据棋盘大小自动调整单元格尺寸,保证显示效果一致。

// 创建棋盘核心代码
function createBoard() {let tableHTML = '<table>';// 循环生成行和列for (let i = 0; i < boardSize; i++) {tableHTML += '<tr>';for (let j = 0; j < boardSize; j++) {tableHTML += '<td></td>';}tableHTML += '</tr>';}tableHTML += '</table>';boardContainer.innerHTML = tableHTML;// 自适应单元格大小const cellSize = Math.min(400 / boardSize, 80);document.querySelectorAll('td').forEach(td => {td.style.width = `${cellSize}px`;td.style.height = `${cellSize}px`;if (boardSize > 6) td.style.fontSize = `${cellSize / 4}px`; // 大棋盘缩小字体});
}

2. 逆序数无解判断算法

这是游戏的核心亮点之一,通过计算数字序列的逆序数判断布局是否有解,避免玩家陷入无解困境。

// 计算逆序数总和
const countInversions = (arr) => {let filteredArr = arr.filter(item => item !== null); // 忽略空位let inversions = 0;// 双重循环统计逆序对for (let i = 0; i < filteredArr.length - 1; i++) {for (let j = i + 1; j < filteredArr.length; j++) {if (filteredArr[i] > filteredArr[j]) inversions++;}}return inversions;
};// 布局有效性判断(逆序数为偶数则有解)
const total = countInversions(series);
if (total % 2 !== 0) {desc.style.visibility = 'visible'; // 显示无解提示
}

3. 方块移动逻辑与通关检测

通过监听单元格点击事件,判断点击方块是否与空位相邻,仅允许上下左右相邻移动,同时实时检测是否通关。

// 移动逻辑核心代码
boardContainer.addEventListener('click', (event) => {const target = event.target;if (target.nodeName !== 'TD') return;const current = document.querySelector('.current'); // 空位元素const currentIndex = Array.from(document.querySelectorAll('td')).indexOf(current);const targetIndex = Array.from(document.querySelectorAll('td')).indexOf(target);// 计算行列位置,判断是否相邻const currentRow = Math.floor(currentIndex / boardSize);const currentCol = currentIndex % boardSize;const targetRow = Math.floor(targetIndex / boardSize);const targetCol = targetIndex % boardSize;const isAdjacent = (Math.abs(currentRow - targetRow) === 1 && currentCol === targetCol) ||(Math.abs(currentCol - targetCol) === 1 && currentRow === targetRow);if (isAdjacent) {// 交换方块与空位内容[target.innerText, current.innerText] = [current.innerText, target.innerText];[target.className, current.className] = [current.className, target.className];// 更新步数step.innerText = parseInt(step.innerText) + 1;startTimer(); // 启动计时器(首次移动触发)check(); // 检测是否通关}
});

4. 计时器与数据统计

实现精准计时功能,记录游戏开始到通关的耗时,配合步数统计,让玩家清晰了解自己的游戏表现。

// 计时器核心逻辑
function startTimer() {if (!isGameActive) {isGameActive = true;timerInterval = setInterval(() => {elapsedSeconds++;updateTimerDisplay(); // 更新时间显示}, 1000);}
}// 时间格式化显示(00:00格式)
function updateTimerDisplay() {const minutes = Math.floor(elapsedSeconds / 60);const seconds = elapsedSeconds % 60;timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}

游戏使用说明

  1. 选择棋盘尺寸:通过下拉框选择3×3到10×10的棋盘规格,默认3×3
  2. 开始游戏:点击"重开"按钮生成随机布局,点击相邻方块移动到空位
  3. 自定义序列:在输入框中按格式输入数字(如1,2,3,4,5,6,7,8),点击"确定"生成自定义布局
  4. 通关条件:将所有数字按1、2、3…的顺序排列,空位在最后一格即可通关
  5. 规则限制:只能移动与空位相邻的方块,不可对角线移动或跳格

完整代码

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>数字华容道</title><style>h1 {text-align: center;color: #2c3e50;}.box {border: 1px solid #cfcfcf;margin: 0 auto;max-width: 900px;padding: 20px;border-radius: 20px;display: flex;flex-wrap: wrap;box-shadow: 0 4px 8px rgba(0,0,0,0.1);background-color: #f9f9f9;}.game-container {flex: 1;min-width: 300px;padding: 10px;}.info-container {flex: 1;min-width: 300px;padding: 10px;}.fun {display: flex;justify-content: space-between;margin: 10px 0;flex-wrap: wrap;}.fun-item {margin: 5px 10px;}table {border-collapse: collapse;margin: 20px auto;box-shadow: 0 4px 8px rgba(0,0,0,0.1);}td {width: 60px;height: 60px;text-align: center;background-color: #f1c385;user-select: none;border: 1px solid #ccc;font-size: 18px;font-weight: bold;transition: all 0.3s;cursor: pointer;border-radius: 5px;}.current {background-color: #fff !important;}#error {color: #e74c3c;font-weight: bold;}#timer {font-size: 18px;font-weight: bold;color: #2980b9;}#size-selector {margin: 10px 0;padding: 5px;border-radius: 5px;border: 1px solid #ccc;}.size-label {font-weight: bold;margin-right: 10px;}button {padding: 5px 10px;border-radius: 5px;border: 1px solid #3498db;background-color: #3498db;color: white;cursor: pointer;transition: background-color 0.3s;}button:hover {background-color: #2980b9;}input {padding: 5px;border-radius: 5px;border: 1px solid #ccc;}.stats {display: flex;justify-content: space-around;background-color: #ecf0f1;padding: 10px;border-radius: 10px;margin: 10px 0;}.stat-item {text-align: center;}.stat-value {font-size: 20px;font-weight: bold;color: #2c3e50;}.stat-label {font-size: 14px;color: #7f8c8d;}@media (max-width: 768px) {.box {flex-direction: column;}.fun {flex-direction: column;align-items: center;}.fun-item {margin: 5px 0;}}</style>
</head>
<body><div class="box"><div class="game-container"><h1>数字华容道</h1><p><strong>规则:</strong>移动方块使数字按顺序排列即可通关!不能对角线移动,不能跳格子移动,只能相邻上下或左右移动。</p><hr /><div class="stats"><div class="stat-item"><div class="stat-value" id="num">0</div><div class="stat-label">步数</div></div><div class="stat-item"><div class="stat-value" id="timer">00:00</div><div class="stat-label">时间</div></div></div><div class="fun"><div class="fun-item"><span class="size-label">棋盘大小:</span><select id="size-selector"><option value="3">3×3</option><option value="4">4×4</option><option value="5">5×5</option><option value="6">6×6</option><option value="7">7×7</option><option value="8">8×8</option><option value="9">9×9</option><option value="10">10×10</option></select></div><div class="fun-item"><button id="reset">重开</button></div></div><hr /><div class="fun"><div class="fun-item"><label>指定序列:</label><input type="text" id="custom-sequence" placeholder="例如:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" /></div><div class="fun-item"><button id="confirm">确定</button></div></div><hr /><div id="board-container"></div><div id="error" style="text-align: center; margin-top: 10px;"></div></div><div class="info-container"><p><strong>逆序数:</strong>是指,在一个数列中,任取两个数字,如果前面的数字大于后面的数字,则这两个数字形成一个逆序。在数字华容道中,忽略空位,将盘面上的数字从上到下、从左到右排列成一个序列,然后计算这个序列的逆序数总和。如果逆序数的总和是偶数,那么这个布局是有解的;如果是奇数,则这个布局是无解的。</p><p><strong>例如:</strong>如果一个布局的数字序列(空格忽略不计)是12345678,那么其逆序数为0(因为它已经是顺序排列),这是一个有解的布局。如果布局是12345687,其逆序数为1(因为只有数字8和7是逆序的),所以这个布局是无解的。</p><div id="desc" style="color: red; visibility: hidden;">逆序数为"奇数",此局无解,建议重开<br>[1,2,3]<br>[4,5,6]<br>[8,7, ]<br></div></div></div><script>// 获取DOM元素const step = document.getElementById('num');const error = document.getElementById('error');const desc = document.getElementById('desc');const input = document.getElementById('custom-sequence');const boardContainer = document.getElementById('board-container');const sizeSelector = document.getElementById('size-selector');const timerDisplay = document.getElementById('timer');// 游戏状态let boardSize = 3; // 默认3x3let seed = []; // 数字序列let custom_seed = []; // 自定义序列let timerInterval = null;let elapsedSeconds = 0;let isGameActive = false;// 初始化游戏function initGame() {boardSize = parseInt(sizeSelector.value);createBoard();initSeed();updateBoard();resetTimer();}// 创建棋盘function createBoard() {let tableHTML = '<table>';for (let i = 0; i < boardSize; i++) {tableHTML += '<tr>';for (let j = 0; j < boardSize; j++) {tableHTML += '<td></td>';}tableHTML += '</tr>';}tableHTML += '</table>';boardContainer.innerHTML = tableHTML;// 设置单元格大小const cellSize = Math.min(400 / boardSize, 80);document.querySelectorAll('td').forEach(td => {td.style.width = `${cellSize}px`;td.style.height = `${cellSize}px`;if (boardSize > 6) {td.style.fontSize = `${cellSize / 4}px`;}});}// 初始化数字序列function initSeed() {seed = [];const totalCells = boardSize * boardSize;for (let i = 1; i < totalCells; i++) {seed.push(i);}seed.push(null); // 最后一个为空}// 随机数组const shuffle = (array) => {// 先过滤掉null值let filteredArray = array.filter(item => item !== null);// 打乱非空部分for (let i = filteredArray.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1));[filteredArray[i], filteredArray[j]] = [filteredArray[j], filteredArray[i]];}// 将打乱后的数组和null重新组合let result = [...filteredArray, null];return result;}// 计算逆序数总和const countInversions = (arr) => {// 过滤掉空值let filteredArr = arr.filter(item => item !== null);let inversions = 0;for (let i = 0; i < filteredArr.length - 1; i++) {for (let j = i + 1; j < filteredArr.length; j++) {if (filteredArr[i] > filteredArr[j]) {inversions++;}}}return inversions;}// 检查结果const check = () => {const tds = document.querySelectorAll('td');let flag = true;for (let i = 0; i < tds.length - 1; i++) {const expectedValue = i + 1;const actualValue = parseInt(tds[i].innerText);if (actualValue !== expectedValue) {flag = false;break;}}// 检查最后一个是否为空if (tds[tds.length - 1].innerText !== '') {flag = false;}if (flag) {error.innerText = '恭喜你通关啦!👌';stopTimer();}return flag;}// 计时器函数function startTimer() {if (!isGameActive) {isGameActive = true;timerInterval = setInterval(() => {elapsedSeconds++;updateTimerDisplay();}, 1000);}}function stopTimer() {isGameActive = false;if (timerInterval) {clearInterval(timerInterval);timerInterval = null;}}function resetTimer() {stopTimer();elapsedSeconds = 0;updateTimerDisplay();}function updateTimerDisplay() {const minutes = Math.floor(elapsedSeconds / 60);const seconds = elapsedSeconds % 60;timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;}// 更新棋盘数据const updateBoard = () => {desc.style.visibility = 'hidden';const data = custom_seed.length ? custom_seed : shuffle([...seed]);const tds = document.querySelectorAll('td');// 将数据填充到表格for (let i = 0; i < tds.length; i++) {if (data[i] === null) {tds[i].className = 'current';tds[i].innerText = '';} else {tds[i].className = '';tds[i].innerText = data[i];}}error.innerText = '';step.innerText = 0;resetTimer();// 计算逆序数,检查是否有解const series = data.filter(item => item !== null);const total = countInversions(series);if (total % 2 !== 0) {desc.style.visibility = 'visible';}custom_seed = []; // 清空自定义序列}// 初始化游戏initGame();// 重开游戏document.getElementById('reset').addEventListener('click', () => {input.value = '';updateBoard();});// 自定义序列document.getElementById('confirm').addEventListener('click', () => {const value = input.value;if (value) {const inputArray = value.split(',').map(item => {const num = parseInt(item.trim());return isNaN(num) ? null : num;});// 验证输入const totalCells = boardSize * boardSize;const expectedNumbers = Array.from({length: totalCells - 1}, (_, i) => i + 1);const inputNumbers = inputArray.filter(item => item !== null);const sortedInput = [...inputNumbers].sort((a, b) => a - b);// 检查是否包含所有必要的数字if (JSON.stringify(sortedInput) !== JSON.stringify(expectedNumbers) || inputArray.length !== totalCells) {alert(`指定数列错误,请输入${totalCells-1}个不重复的数字(1-${totalCells-1}),用英文逗号分隔`);return;}custom_seed = inputArray;updateBoard();}});// 监听棋盘大小变化sizeSelector.addEventListener('change', () => {input.value = '';initGame();});// 监听点击事件,移动方块处理boardContainer.addEventListener('click', (event) => {const target = event.target;if (target.nodeName !== 'TD') return;const current = document.querySelector('.current');if (target === current) return;// 获取当前空白格和点击格的位置const currentIndex = Array.from(document.querySelectorAll('td')).indexOf(current);const targetIndex = Array.from(document.querySelectorAll('td')).indexOf(target);const currentRow = Math.floor(currentIndex / boardSize);const currentCol = currentIndex % boardSize;const targetRow = Math.floor(targetIndex / boardSize);const targetCol = targetIndex % boardSize;// 检查是否相邻const isAdjacent = (Math.abs(currentRow - targetRow) === 1 && currentCol === targetCol) ||(Math.abs(currentCol - targetCol) === 1 && currentRow === targetRow);if (isAdjacent) {// 启动计时器(如果尚未启动)startTimer();// 交换内容const targetText = target.innerText;target.innerText = '';target.className = 'current';current.innerText = targetText;current.className = '';// 更新步骤let num = parseInt(step.innerText) || 0;num++;step.innerText = num;error.innerText = '';check();} else {error.innerText = '只能移动相邻的方块!';}});</script>
</body>
</html>

扩展

这款数字华容道已经实现了核心玩法和实用功能,代码结构清晰,易于理解和修改。后续可以考虑这些扩展方向:

  • 增加排行榜功能:记录不同尺寸棋盘的最快通关时间和最少步数
  • 自定义主题:支持切换棋盘颜色、方块样式等个性化设置
  • 难度分级:根据棋盘尺寸和打乱程度划分难度等级
  • 移动动画优化:添加更流畅的方块移动过渡效果
http://www.dtcms.com/a/592940.html

相关文章:

  • M4-R1 开源鸿蒙(OpenHarmory)开发板丨串口调试助手实战案例
  • 建材做网站好吗破解插件有后门wordpress
  • 旅游网站建设流程步骤怎么自己做礼品网站
  • C语言--文件读写函数的使用,对文件读写知识有了更深的了解。C语言--文件读写函数的使用,对文件读写知识有了更深的了解。
  • 数据结构示例代码
  • 数字化工厂:基于层级模型的智能制造新范式
  • C语言--变量(全局变量、局部变量、初始化)
  • 羊驼送洗后因毛发未吹干致失温死亡,物联网技术助力防范宠物洗澡失温事故
  • Raylib 基本绘图操作
  • (Arxiv-2025)BINDWEAVE:通过跨模态整合实现主体一致性的视频生成
  • 怎么做会员积分网站建网站商城有哪些公司
  • 网站如何验证登陆状态广州专业做网页的公司
  • MySQL的增删改查功能合集
  • Oracle数据块编辑工具( Oracle Block Editor Tool)-obet
  • 什么是量子纠缠?大白话
  • 用服务器自建 RustDesk 远程控制平台
  • 新手做网站如何被百度快速收录教程
  • 基于java技术的田径俱乐部网站的设计与实现
  • 第二十四篇:C++模板元编程入门:constexpr与type_traits的编译期魔法
  • C语言数组作为函数参数(3种写法,附带实例)
  • SPARQL文档导读
  • JavaEE初阶——JUC的工具类和死锁
  • 如何将自己做的网站发布到网上ui展示 网站
  • 上门家政小程序用户激励机制分析:用 “利益 + 情感” 双驱动,解锁高复购增长密码
  • 内网横向靶场——记录一次横向渗透(二)
  • Mysql作业四
  • 枣庄住房和城乡建设厅网站教育网站制作开发
  • 万象EXCEL应用(十六)便利店进销存毛利 ——东方仙盟炼气期
  • 单片机和C语言中的一些通用知识:(二)大端和小端
  • 【疑难解答】MySQL 报错 Public Key Retrieval is not allowed