尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Java实现华容道:移动判定、数据结构与BFS求解全解析
简介面向Java初学者的华容道小游戏源码包适合通过实际项目巩固编程基础以经典棋盘谜题为载体覆盖语法基础、GUI设计与事件驱动开发同时涉及类与对象、数组遍历等知识点适合课堂练习或自学。代码使用Swing组件构建窗口、棋盘面板与按钮借助ActionListener处理点击事件棋盘用二维数组表示移动时包含合法性判定、边界与阻挡判断能给出非法操作提示并带有定时动画便于理解异常处理与Timer用法。压缩包共3个Java源文件分别承担主控制、界面布局与棋子对象逻辑大小仅2KB结构精简可直接导入IDE阅读已有414人浏览学习。通过运行代码可获得完整的华容道程序理解棋盘初始化、移动判定、胜利判断等思路并学会拆分界面与逻辑类、使用调试工具定位异常体会多文件协作与面向对象设计为独立开发小型游戏打基础。读者还可扩展难度、计分等功能进一步掌握事件驱动设计。1. 华容道小游戏用Java做移动判定这一步最容易劝退新手很多人第一次动手写Java小游戏会在贪吃蛇、扫雷、华容道里选一个。华容道看起来是规则最简单的一个4乘5棋盘曹操带着几员大将和四个小卒通过上下左右滑动把曹操送到出口就算赢。可真用Java从零写一遍踩坑最多的不是界面而是移动判定——棋子尺寸不一样横着的关羽占两格竖着的张飞占两格曹操占四格一次移动要判断整个目标区域有没有越界、有没有被其他棋子占着写不好就会“能滑出去但棋盘一片乱”。这篇笔记就沿着这个方向把实现链路完整拆开先立数据结构再做移动和胜负逻辑然后接到Swing界面上最后补上BFS求解和几个已经替你踩过的坑。2. 棋盘与棋子的类结构坐标宽高把滑块抽象清楚华容道虽然叫“小游戏”但本质是一个滑块拼图核心在于“棋子占格”。棋盘固定4列5行棋子只分四类1x1的小卒、2x1横将、1x2竖将、2x2曹操。第一次写这个游戏的人最容易想当然地用(x1,y1,x2,y2)四个坐标表示一个棋子结果移动判定变成一堆矩形相交判断代码越写越长。更简洁的建模是每个棋子记录左上角坐标(row,col)和宽高(width,height)用“左上角尺寸”表达它占用的所有格子。2.1 用Piece类把棋子抽象成带宽高的滑块在设计棋子类之前先明确棋子的尺寸表后面所有移动判定都会围绕这张表展开。棋子宽高占用格子数曹操224关羽212张飞/赵云/马超/黄忠122兵111有了这个表棋子类就不需要为每一种棋子写不同的逻辑统一用宽度和高度两个字段表达。public class Piece { public final String name; // 界面显示的名字曹操、关羽、兵等 public final int width; // 横向占几个格子只能是 1 或 2 public final int height; // 纵向占几个格子只能是 1 或 2 public Color color; // 画棋子用的颜色 public int row; // 左上角所在行从 0 开始 public int col; // 左上角所在列从 0 开始 public Piece(String name, int width, int height, Color color) { this.name name; this.width width; this.height height; this.color color; } }这里row和col存的是左上角而不是中心点。为什么这样设计因为“占格子”的语义最直接以一个宽2高1的关羽为例只要知道(row,col)它占的格子就是(row,col)和(row,col1)计算目标区域时不需要额外换算。如果存中心点移动和碰撞判断时反而要反复把中心转成左上角边界计算很容易出错。需要提醒的是name只用于显示不要把它当成唯一标识。一盘游戏里会有四个“兵”它们的name相同但状态独立。如果后面做存档或者BFS求解我给棋子的建议是额外加一个id字段或者直接用pieces列表下标作为身份。否则通过字符串去对比棋子会在“四个兵分不清”这个问题上吃大亏。2.2 占位矩阵棋盘状态怎么记录才不容易错棋子的位置信息都存在Piece里了但棋盘还需要一个数据结构回答两个问题某个格子现在被谁占着某个区域能不能放一个新的棋子很多初学会选择boolean[][] occupied但boolean只能表示“有没有棋子”不能表示“是哪一颗”一旦棋盘状态异常排查起来非常痛苦。我一般直接用一个Piece[][] grid每个格子存放指向棋子的引用空着就是null。public class Board { public static final int ROWS 5; public static final int COLS 4; private final Piece[][] grid new Piece[ROWS][COLS]; public boolean inBoard(int row, int col) { return row 0 row ROWS col 0 col COLS; } public boolean canPlaceAt(int row, int col, int width, int height) { for (int r row; r row height; r) { for (int c col; c col width; c) { if (!inBoard(r, c) || grid[r][c] ! null) { return false; } } } return true; } }canPlaceAt是后面移动判定的核心也是初始化布局时校验配置合法性的工具。它把“越界”和“重叠”放在同一个循环里检查目标矩形里任何一个格子超出棋盘或被其他棋子占用就返回false。这里有一个初学者很容易踩的数组问题棋盘只有5行一个height2的棋子放在row4循环会访问grid[5][c]所以必须先经过inBoard判断否则直接抛数组越界异常。这个判断不能省也不能靠try-catch掩盖因为游戏运行时需要频繁调用它异常开销完全没有必要。为什么用Piece[][]而不是boolean[][]调试时如果发现两个棋子重叠从grid里能直接看到“这个格子指向哪一个棋子”定位是哪一个棋子没清干净如果只有boolean还得把所有棋子坐标拉出来逐一比对效率低很多。后面做BFS求解时把Piece[][]编码成字符串状态也顺理成章每个格子的字符就是对应棋子的符号这个优势到第6章会体现出来。2.3 开局布局把坐标从代码里拆出来开局布局有两个要求一是棋子的位置不能随意写死二是最好让一份布局配置能复用。直接在main里new十个Piece再挨个set坐标也能跑但每增加一个关卡就要改代码重新编译不灵活。我建议把开局配置做成一个列表每一项表示“哪个名字的棋子、放在第几行第几列”初始化时统一创建。下面这个“横刀立马”开局是华容道最经典的配置曹操在中间上方关羽横在曹操正下方挡住出口四员竖将守在两侧四个小卒分列下方左右。public static ListInitialPiece createInitialLayout() { // 开局名称横刀立马 // 格式名字, 左上角行, 左上角列, 宽, 高 return List.of( new InitialPiece(曹操, 1, 1, 2, 2), new InitialPiece(关羽, 3, 1, 2, 1), new InitialPiece(张飞, 1, 0, 1, 2), new InitialPiece(赵云, 1, 3, 1, 2), new InitialPiece(马超, 3, 0, 1, 2), new InitialPiece(黄忠, 3, 3, 1, 2), new InitialPiece(兵, 2, 0, 1, 1), new InitialPiece(兵, 2, 3, 1, 1), new InitialPiece(兵, 4, 0, 1, 1), new InitialPiece(兵, 4, 3, 1, 1) ); }InitialPiece可以理解为一个只保存配置数据的简单对象启动时遍历这个列表创建真正的Piece再逐个调用canPlaceAt校验。如果某个棋子摆到了已经占用的格子上直接抛出带位置的异常避免“带病开局”。用这个方式扩展新关卡时只需要往游戏里添加一个静态方法返回不同的List 不用动任何游戏逻辑。这里的坐标是以左上角为基准所以曹操放在(1,1)宽2高2实际占第1、2行和第1、2列棋盘4列5行正好对称视觉上是在棋盘正中偏上的位置。3. 移动判定与胜负逻辑不越界、不重叠才能动得干净这一章是整个游戏的核心。很多人把华容道写成了“看起来能玩但状态很乱”多半是移动判定偷懒了。先定义方向再讲目标矩形扫描最后落到移动、悔棋和胜利判定。3.1 移动判定目标矩形不越界、不重叠才算合法华容道的移动是一次滑动一格所以移动一个棋子本质就是把它左上角的(row,col)按方向加上一个偏移量。向上row-1向下row1向左col-1向右col1。先把方向枚举写出来后面到处都要用。public enum Direction { UP(-1, 0), DOWN(1, 0), LEFT(0, -1), RIGHT(0, 1); public final int dr; public final int dc; Direction(int dr, int dc) { this.dr dr; this.dc dc; } }有了方向之后移动判定不要用“目标格子是否为空”来判断。曹操这种2x2棋子向上挪一格时新位置和旧位置有一行重叠重叠区域里还是它自己如果不考虑这一点棋子一动就被自己挡住。正确做法是先算出完整的目标矩形再逐个格子检查“越界”和“非自身占用”。public boolean canMove(Piece p, Direction dir) { // 目标位置左上角移动到新坐标 int nRow p.row dir.dr; int nCol p.col dir.dc; // 目标矩形右下角 int endRow nRow p.height - 1; int endCol nCol p.width - 1; // 左上角和右下角都必须落在棋盘内 if (!inBoard(nRow, nCol) || !inBoard(endRow, endCol)) { return false; } // 目标矩形内不能有别的棋子但可以包含自己 for (int r nRow; r endRow; r) { for (int c nCol; c endCol; c) { Piece occupied grid[r][c]; if (occupied ! null occupied ! p) { return false; } } } return true; }关键就在 occupied ! p 这一句。2x2棋子向上移动新矩形是第0行到第1行旧矩形是第1行到第2行第1行的两个格子同时属于新旧矩形如果grid里存着p却又不排除自身曹操就被自己挡住了。这个细节是最容易让人翻车的地方很多人写着写着发现大棋子完全动不了基本都是这个问题。另外边界检查要检查左上角和右下角两个点。只查nRow和nCol的话一个1x2竖将向下移动时nRow可能还在棋盘里endRow却已经等于5了同样越界。把这条规则统一成“目标矩形扫描”后四种尺寸的棋子共用同一段逻辑不用按棋子类型写特例后面BFS求解时生成合法移动也省心。3.2 执行移动与悔棋先清旧格子再写新格子判定通过后执行移动。顺序有讲究先remove清掉旧占位更新坐标再place写入新占位。如果先改坐标再removeremove时用的就是新坐标旧格子里的引用永远清不掉。public void movePiece(Piece p, Direction dir) { if (!canMove(p, dir)) { return; } removePiece(p); // 先清旧位置 p.row dir.dr; p.col dir.dc; placePiece(p); // 再写新位置 steps; // 有效移动步数加一 } public void removePiece(Piece p) { for (int r p.row; r p.row p.height; r) { for (int c p.col; c p.col p.width; c) { grid[r][c] null; } } } public void placePiece(Piece p) { for (int r p.row; r p.row p.height; r) { for (int c p.col; c p.col p.width; c) { grid[r][c] p; } } }removePiece必须遍历棋子当前占用的所有格子。一个2x2曹操占用4个格子如果只写grid[p.row][p.col] null另外三个格子会残留引用棋盘上就会出现“一个棋子占两片区域”的诡异效果。placePiece同理遍历整个矩形把格子都写入引用这一步不能省。悔棋是华容道游戏标配的“后悔药”。我建议移动时顺便记录一个MoveRecord而不是在悔棋时把方向反过来执行一次。反向移动可能因为目标位置被其他棋子占住而不合法而记录移动前后坐标后撤销时可以强制把棋子放回旧位置完全不做合法性判断逻辑简单可靠。public void undo() { if (undoStack.isEmpty()) return; MoveRecord record undoStack.pop(); removePiece(record.piece); record.piece.row record.fromRow; // 直接回到移动前的位置 record.piece.col record.fromCol; placePiece(record.piece); steps--; }MoveRecord里至少要有piece、fromRow、fromCol、toRow、toCol五个信息。这里的撤销只在最后一次移动后调用而旧区域是刚空出来的所以不会被其他棋子占用直接设置坐标是安全的。3.3 胜利判定与步数统计标准华容道的出口在棋盘底部中间曹操2x2要占据第3行第1列到第4行第2列也就是左上角坐标为(3,1)时游戏胜利。public boolean isWin() { return caoCao.row 3 caoCao.col 1; }如果你的设计把出口放在顶部把3改成0就行其他逻辑完全不用动。为了界面上能明确看到出口我会在paintComponent里把目标矩形画成一个浅色底框曹操滑到那里时触发胜利流程比如弹窗显示用时和步数。步数统计的口径要提前想好一种是只统计有效移动次数movePiece执行一次就加一另一种是“从开始到当前状态经过的步数”悔棋时要减一。上面的undo里已经同步做了steps--两种口径保持一致。有一个细节新手容易忽略胜利判定不能写成p.row 4。曹操高2如果左上角row等于4它就已经超出棋盘下边界了这局不可能赢。所以目标坐标应该用“左上角到达出口起始格”来判断而不是“棋子的中心点”或“右下角到达边界”。如果只判曹操的左上角位置当row3且col1时它已经占住出口的四格判定成立这也符合玩家认知。4. Swing界面画棋盘、接鼠标键盘把游戏跑起来数据结构做好之后界面部分反而简单核心就是把棋子的逻辑坐标翻译成像素坐标。Swing方案我建议直接用JFrame JPanel重写paintComponent绘制所有元素不要拖一堆JButton堆界面。4.1 paintComponent绘制棋盘绘制思路很直接按固定的CELL_SIZE计算格子大小先把棋盘底色画出来再画出口区域最后遍历pieces逐个绘制棋子。public class GamePanel extends JPanel { private final Board board; private static final int CELL_SIZE 90; Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 棋盘底色 g.setColor(new Color(222, 184, 135)); g.fillRect(0, 0, Board.COLS * CELL_SIZE, Board.ROWS * CELL_SIZE); // 画出口区域底部中间两格 g.setColor(new Color(255, 215, 0, 80)); g.fillRect(1 * CELL_SIZE, 3 * CELL_SIZE, 2 * CELL_SIZE, 2 * CELL_SIZE); // 绘制棋子 for (Piece p : board.getPieces()) { g.setColor(p.color); g.fillRect(p.col * CELL_SIZE, p.row * CELL_SIZE, p.width * CELL_SIZE, p.height * CELL_SIZE); g.setColor(Color.WHITE); // 让名字尽量居中 g.drawString(p.name, p.col * CELL_SIZE (p.width * CELL_SIZE - g.getFontMetrics().stringWidth(p.name)) / 2, p.row * CELL_SIZE (p.height * CELL_SIZE g.getFontMetrics().getAscent()) / 2); } } }这段代码里p.col乘以CELL_SIZE就是棋子的左边距p.row乘以CELL_SIZE就是上边距宽度和高度也各乘CELL_SIZE。逻辑坐标和像素坐标分离后GamePanel里不需要知道棋子具体是曹操还是小卒界面代码就稳定了。CELL_SIZE决定了窗口大小如果要自适应缩放可以把CELL_SIZE改成根据getWidth()和getHeight()动态计算的变量但要注意字体大小也要跟着缩放否则窗口拉大后文字不会同步变大。对华容道这种4x5的小棋盘固定尺寸更省心直接setPreferredSize(new Dimension(Board.COLS * CELL_SIZE, Board.ROWS * CELL_SIZE))即可。出口区域我用半透明黄色标出来玩家一眼就能看到要把曹操送到哪里。Color的第四个参数是Alpha透明度JDK的Swing是原生支持的。如果你改用JavaFX绘制API完全不同但整体思路一致先画棋盘再画棋子最后画装饰。4.2 鼠标选中和键盘操作的接入交互建议做成鼠标点选棋子、方向键移动的组合。这样玩家一只手指就能完成大部分操作也方便后续增加“自动走一步”的辅助功能。panel.addMouseListener(new MouseAdapter() { Override public void mousePressed(MouseEvent e) { int row e.getY() / CELL_SIZE; int col e.getX() / CELL_SIZE; selectedPiece board.findPieceAt(row, col); panel.repaint(); } });findPieceAt返回grid[row][col]上的棋子引用可能是null。点击空白就取消选中点击棋子就选中它。被选中后可以在paintComponent里给棋子加一个高亮外框否则玩家不知道当前操作的是谁。键盘方面我强烈建议不要用KeyListener而是用InputMap/ActionMap。panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) .put(KeyStroke.getKeyStroke(LEFT), moveLeft); panel.getActionMap().put(moveLeft, new AbstractAction() { Override public void actionPerformed(ActionEvent e) { moveSelected(Direction.LEFT); } });很多人写Swing键盘交互时用KeyListener结果发现鼠标点了一下按钮后方向键全无反应原因是焦点跑到了按钮上KeyEvent根本没有派发给面板。而WHEN_IN_FOCUSED_WINDOW模式下只要事件发生在当前窗口内不管焦点在哪个组件上都能触发省去到处调requestFocus的麻烦。moveSelected里还可以做一个很实用的细节如果当前没有选中棋子就自动找第一个“能往该方向移动”的棋子并移动它。private void moveSelected(Direction dir) { if (selectedPiece null) { for (Piece p : board.getPieces()) { if (board.canMove(p, dir)) { board.movePiece(p, dir); break; } } } else { board.movePiece(selectedPiece, dir); } panel.repaint(); }这个交互习惯在手机上玩过华容道的人会很熟悉按方向键不一定是移动当前选中的棋子而是移动那个方向能走的棋子。桌面版本保留这个习惯新手随便按方向键也能走第一步不会觉得“怎么没反应”。4.3 repaint与后台任务的基本礼仪Swing是单线程模型所有UI操作都要放在事件分发线程EDT里。paintComponent结束后如果有耗时的逻辑在循环里频繁调用repaint界面看起来会“忙”但真正的卡顿往往是逻辑阻塞了EDT而不是绘制本身。一个值得养成的习惯凡是计算可能超过一两秒的都别直接写在事件回调里。第6章的BFS求解就是典型例子如果直接在按钮点击事件里跑BFS窗口会立刻变成“未响应”直到求解完成才恢复。正确做法是用SwingWorker把重计算放到后台线程在done()里更新结果。移动完棋子我通常只调用panel.repaint()不做局部刷新。4x5的棋盘上整板重绘开销很小代码也简单。如果以后扩展到更大的滑块拼图再考虑局部绘制或双缓冲优化对华容道这个规模来说优化得太早反而增加复杂度。5. 华容道Java小游戏避坑越界、焦点、残留与无解开局这一章写几个实际调试中很容易遇到的现象每一条都按“现象、原因、解决”的顺序记录方便你对照排查。5.1 移动和状态类的三个典型坑越界、残留、撤销错乱坑一棋子飞出了棋盘边界。现象是棋子可以移动到棋盘外一半身体在外面甚至直接消失不见。原因基本只有一个移动判定只检查了左上角的新位置没有检查右下角。解决方法是把canMove里的边界检查写成同时检查nRow/nCol和endRow/endCol两端任何一个越界都返回false。尤其要注意往下和往右移动时右下角最容易越界。坑二旧位置残留。现象是曹操从中间移走后原位置还留着一块颜色棋盘上出现了两个曹操一样的重影。原因是removePiece只清了一个格子比如只写了grid[p.row][p.col] null2x2棋子还有三个格子保留着引用。解决方法是removePiece和placePiece都写成双重循环把整个矩形全部置null或写入引用移动顺序必须是remove、改坐标、place三步顺序不能换。坑三撤销后步数和状态对不上。现象是点悔棋棋子确实回去了但步数不减或者连续撤销两次后棋子叠在一起。原因是撤销栈里只存了方向执行时用反向移动去恢复一旦目标位置被其他棋子挡住撤销就失败或者步数没有同步减。解决方法是MoveRecord里记录移动前后坐标undo直接设置坐标并重放占位不再判断合法性同时同步执行steps--。这三条如果按顺序排查能省下一个星期的调试时间。我自己的习惯是每次改动移动逻辑后先跑一遍针对2x2曹操的上下左右移动再跑一遍四个小卒的移动确认没有越界和残留后再接UI。5.2 交互和关卡的两个典型坑键盘无响应、自定义布局无解坑四键盘方向键完全没反应。现象是鼠标点击能够选中棋子但按上下左右键游戏毫无反应。原因大多是用了KeyListener且焦点不在GamePanel上。解决方法是改用InputMap/ActionMap并设置WHEN_IN_FOCUSED_WINDOW不要依赖焦点。还有一个容易被忽略的点如果界面上有按钮按下方向键时焦点可能会跑到按钮上这时即便用KeyListener也收不到事件。换成ActionMap后这个坑就基本消失了。坑五自定义关卡跑半天也赢不了。现象是新增关卡时随手摆了一些棋子玩家走了很多步还是到不了终点程序也不报错。原因是滑块拼图不是随便摆就一定可解的有些布局从初始状态到目标状态根本不可达。解决方法是把BFS求解器集成到关卡编辑器里启动关卡前跑一次求解无解就提示“当前布局无解”。这比让玩家试错要友好得多。要注意的是用canPlaceAt校验初始位置不重叠、不越界只能排除明显非法的配置不代表一定有解。真正可靠的可解性判定还是第6章的BFS。如果一个自定义关卡的棋子数量或出口位置设置得很怪异BFS会搜索完整状态空间然后告诉你这个布局不可达省去玩家反复折腾的挫败感。6. 进阶玩法用BFS算最少步数顺手把存档加上到了这一步基础游戏已经能玩了但还可以再加一个真正体现价值的功能最少步求解。华容道每走一步代价都是1BFS天然适合找最短路径第一次到达目标状态的路径就是最优解。6.1 BFS求解的核心状态压缩与去重BFS去重不能直接拿棋盘对象做HashMap key必须先把状态压缩成字符串。做法是按行扫描4x5的grid每个格子用对应棋子的符号表示空格用点号。public String encode() { char[][] map new char[Board.ROWS][Board.COLS]; for (char[] row : map) Arrays.fill(row, .); for (Piece p : pieces) { char symbol symbolOf(p); // 曹操C、张飞Z、兵B等 for (int r p.row; r p.row p.height; r) { for (int c p.col; c p.col p.width; c) { map[r][c] symbol; } } } StringBuilder sb new StringBuilder(); for (char[] row : map) sb.append(row); return sb.toString(); }BFS主循环用HashMap记录前驱状态等于同时完成去重和路径回溯。expand过程就是还原棋盘、尝试每个棋子四个方向、生成新编码具体逻辑可以直接复用第3章的canMove和movePiece。public ListString bfs(String start, String goal) { DequeString queue new ArrayDeque(); MapString, String prev new HashMap(); queue.offer(start); prev.put(start, null); while (!queue.isEmpty()) { String cur queue.poll(); if (cur.equals(goal)) break; for (String next : expand(cur)) { if (!prev.containsKey(next)) { prev.put(next, cur); queue.offer(next); } } } if (!prev.containsKey(goal)) return null; // 无解 LinkedListString path new LinkedList(); for (String s goal; s ! null; s prev.get(s)) { path.addFirst(s); } return path; }经典开局的状态量其实并不大普通内存完全能扛住。等到开发关卡编辑器时这个BFS就能顺手用来做无解校验给玩家一个有意义的提示。6.2 把求解做成提示按钮并放在后台线程求解器直接放在按钮回调里会把Swing界面卡死这个问题我在自己项目里踩过。正确做法是用SwingWorker在后台跑BFS完成后回到EDT更新界面再把第一步动作高亮显示玩家就能照着提示走。6.3 存档和关卡编辑器的扩展方向存档可以把每个棋子的名称、row、col、宽高写成一行文本读取时按行恢复并调用canPlaceAt自检。关卡编辑器则是一块能拖拽棋子的画布拖完之后先做初始化合法性校验再用BFS判断是否可解做到这一步这个游戏就不只是“小游戏”而是一个完整的Java桌面应用练习了。一通操作下来你会发现华容道最难的并不是画面而是状态表达和对移动规则的敬畏。我自己的教训是先花半天把Board和move逻辑想清楚界面很快就出来了反过来先做UI再补逻辑大多会把时间浪费在反复调试上。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

华为eNSP中VRRP高可用实战:秒级切换配置与故障验证

华为eNSP中VRRP高可用实战:秒级切换配置与故障验证

1. 项目概述:这不是“配个VRRP就完事”的演示,而是一次真实网络故障场景下的冗余设计实战你手头有一台华为eNSP模拟器,刚拖出两台AR系列路由器,准备配置VRRP——但别急着敲vrrp vrid 1 virtual-ip 192.168.1.254。真正决定项目成败…

📅 2026/10/13 20:58:52
C语言第10周在线编程测试:指针字符串与OJ避坑指南

C语言第10周在线编程测试:指针字符串与OJ避坑指南

每年到这个节点,我的消息列表里总会冒出几类一模一样的问题:“第10周的在线测试,我本地跑得好好的,怎么提交就是不过?”“这个字符串题到底用指针还是用数组?”“程序填空只给我几个空,我根本不…

📅 2026/10/13 20:58:52
团队聚会问题解析:时间区间合并与边界处理实战

团队聚会问题解析:时间区间合并与边界处理实战

这周的限时大模拟碰到一道非常经典的“团队聚会”题,题面很短,但信息密度很高:一群人分别给出自己的若干忙碌时间段,要在给定时间范围内找出所有能让全员一起开会、并且持续时间至少 h 小时的连续空闲时间。第一次看到这种题&…

📅 2026/10/13 20:58:52
MORE NEWS

更多资讯

📰

ASP.NET C# SqlServer打造数据库驱动轮播图,动态加载告别改代码

做后端开发的人应该都有这种感觉:轮播图看着是个小功能,但运营提需求时从来不会只说一次。今天换图、明天加图、后天调顺序,你要是把图片写在HTML里写死,每次都得改代码重新发布。这篇文章要分享的,就是用ASP.NET C# …

📰

多物理场仿真实践:工业机箱自然散热与热应力耦合分析笔记

我接到一个项目,要评估某台工业控制机箱在满负载工况下的内部温升和壳体热应力。搁在以前,我的第一反应是赶紧打开几个不同的软件,一边建几何、一边剖网格、一边调求解器,数据格式来回转,网格模型在软件之间还得重新生…

📰

CMS建站系统自定义表单硬核横评:从字段设计到数据管理

做建站的人基本都遇到过这种场景:需求沟通阶段客户一口气讲了三四十个页面想要什么风格,可等到真正动工时才发现,所有复杂需求最后都浓缩成一个不起眼的“表单”——留资、预约、报名、申请、反馈,表单才是访客和网站发生深度交互…

📰

UE多人游戏网络同步:RPC与属性同步的选型与踩坑指南

做了几年多人在线游戏项目,我发现自己团队里讨论最多的不是玩法、不是美术,而是“这个数据到底怎么同步过去”。UE 的 RPC 和属性同步这两套机制,几乎是每个联机功能都绕不开的基础设施。但恰恰是这两套东西,我见过太多人把它们混…

📰

自适应悬浮导航模板:fixed与sticky定位思路及避坑实践

简介:面向网页设计初学者和前端新手,这份自适应固定悬浮导航条HTML源码模板以HTML5语义结构、CSS定位技术和JavaScript事件机制为核心,实现页面滚动时导航菜单始终固定在屏幕顶部,并借助媒体查询兼顾手机、平板与桌面端的响应式显…

📰

jQuery DateTimePicker实战:日期时间选择与表单校验全指南

简介:jQuery日期与时间选择器插件DateTimePicker,面向需要为网页快速集成日期输入组件的Web前端开发者,提供了一套轻量易用的日期时间交互解决方案。插件内置多语言、自定义显示格式、日周月年四种视图、时分秒选择、禁用指定日期、事件回调、…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬