数字华容道游戏

在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; // 默认3x3
        let 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>

扩展

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

  • 增加排行榜功能:记录不同尺寸棋盘的最快通关时间和最少步数
  • 自定义主题:支持切换棋盘颜色、方块样式等个性化设置
  • 难度分级:根据棋盘尺寸和打乱程度划分难度等级
  • 移动动画优化:添加更流畅的方块移动过渡效果
package 华容道; import java.awt.*; import java.awt.event.*; //主函数 public class Main { public static void main(String[] args) { new Hua_Rong_Road(); } } //人物按钮颜色 class Person extends Button implements FocusListener{ int number; Color c=new Color(255,245,170); Person(int number,String s) { super(s); setBackground(c);//人物的颜色背景是黄色 this.number=number; c=getBackground(); addFocusListener(this);//好像是焦点监听器 } public void focusGained(FocusEvent e) { setBackground(Color.red);//只要单击该按钮则按钮变颜色 } public void focusLost(FocusEvent e) { setBackground(c);//上一个按钮回复原先的颜色 } } //华容道总类 class Hua_Rong_Road extends Frame implements MouseListener,KeyListener,ActionListener{ Person person[] = new Person[10]; Button left,right,above,below; Button restart = new Button("Start");//重新开始按钮 public Hua_Rong_Road() { init(); setBounds(100,100,320,360); setVisible(true);//设置Frame为可见,默认为不可见 validate(); addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); } public void init() { setLayout(null); add(restart); restart.setBounds(100, 320, 120, 25); restart.addActionListener(this); String name[]={"我","陆逊","姜维","陈宫","许攸","邓艾","周瑜","庞统","诸葛亮","贾诩"}; for(int k=0;k<name.length;k++) { person[k]=new Person(k,name[k]); person[k].addMouseListener(this); person[k].addKeyListener(this); add(person[k]); }//为所有的按钮注册所需的东西 person[0].setBounds(104, 54, 100, 100); person[1].setBounds(104,154, 100, 50); person[2].setBounds(54, 154, 50, 100); person[3].setBounds(204, 154, 50, 100);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BetterInsight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值