游戏逻辑与 Windows 经典扫雷一致:左键翻开、右键插旗/取消插旗,自动递归展开空白区域,实时显示剩余雷数。
(界面基于 uifigure
+ uigridlayout
,MATLAB R2019b 及以上版本可直接运行;旧版可用 figure
+uicontrol
的兼容版本见文末)
一、运行方式
minesweeper(9,9,10); % 9×9、10颗雷
二、完整源码
function minesweeper(m,n,k)
% m 行、n 列、共 k 颗雷
%
% 用法示例:
% minesweeper(9,9,10) % 初级
% minesweeper(16,16,40) % 中级
% minesweeper(16,30,99) % 高级
% ---------- 1. 参数初始化 ----------
if nargin<3, k=floor(m*n*0.15); end % 默认15%雷
if k>m*n-9, error('雷数太多!'); end
rng('shuffle');
% 逻辑矩阵: -1 表示雷,>=0 表示周围雷数
board = zeros(m,n);
mineIdx = randperm(m*n,k);
board(mineIdx) = -1;
% 计算每个非雷格子的周围雷数
for i = 1:m
for j = 1:n
if board(i,j) ~= -1
ii = max(i-1,1):min(i+1,m);
jj = max(j-1,1):min(j+1,n);
board(i,j) = sum(board(ii,jj)==-1);
end
end
end
% 状态矩阵:0=未翻开,1=已翻开,2=插旗
state = zeros(m,n);
gameOver = false;
win = false;
% ---------- 2. GUI ----------
fig = uifigure('Name','MATLAB 扫雷','Resize','off', ...
'Position',[500 300 35*n+25 35*m+80]);
uigrid = uigridlayout(fig,[m+1 n],'RowSpacing',1,'ColumnSpacing',1);
uigrid.RowHeight = repmat({'1x'},m,1);
uigrid.ColumnWidth = repmat({'1x'},n,1);
% 顶部信息
lbFlag = uilabel(fig,'Position',[10 35*m+30 150 25],'FontSize',12);
lbFlag.Text = sprintf('剩余雷数: %d',k);
% 按钮网格
btn = gobjects(m,n);
for i = 1:m
for j = 1:n
btn(i,j) = uibutton(uigrid,'Text',' ','FontWeight','bold', ...
'BackgroundColor',[.9 .9 .9], ...
'FontSize',10,'Tag',[num2str(i),'-',num2str(j)]);
btn(i,j).ButtonPushedFcn = @(src,evt) leftClick(src);
btn(i,j).ContextMenu = contextMenuCreate(btn(i,j));
end
end
% ---------- 3. 事件函数 ----------
function leftClick(src)
if gameOver || win, return; end
[i,j] = strtok(src.Tag,'-');
i = str2double(i); j = str2double(j(2:end));
if state(i,j)==2, return; end % 插旗格不响应左键
reveal(i,j);
checkWin;
end
function rightClick(src,~)
if gameOver || win, return; end
[i,j] = strtok(src.Tag,'-');
i = str2double(i); j = str2double(j(2:end));
if state(i,j)==1, return; end % 已翻开不能插旗
if state(i,j)==0 % 未翻开→插旗
state(i,j)=2;
src.Text = '⚑';
src.BackgroundColor = [1 .6 .6];
elseif state(i,j)==2 % 插旗→取消
state(i,j)=0;
src.Text = ' ';
src.BackgroundColor = [.9 .9 .9];
end
lbFlag.Text = sprintf('剩余雷数: %d',k-sum(state==2,'all'));
end
function reveal(i,j)
if i<1 || i>m || j<1 || j>n || state(i,j)==1, return; end
state(i,j)=1;
if board(i,j)==-1 % 踩到雷
showAllMines;
uialert(fig,'BOOM! 你踩到雷了','游戏结束');
gameOver = true;
return;
end
% 更新按钮文字和颜色
btn(i,j).Text = num2str(board(i,j));
btn(i,j).BackgroundColor = [1 1 1];
if board(i,j)==0
btn(i,j).Text = ' ';
% 递归展开空白
for di = -1:1, for dj = -1:1
reveal(i+di,j+dj);
end, end
end
end
function showAllMines
for i = 1:m
for j = 1:n
if board(i,j)==-1
btn(i,j).Text = '💣';
btn(i,j).BackgroundColor = [1 0 0];
end
end
end
end
function checkWin
if gameOver, return; end
if all(state(board~=-1)==1) % 所有非雷已翻开
win = true;
uialert(fig,'恭喜你,扫雷成功!','胜利');
end
end
function cMenu = contextMenuCreate(btnObj)
cMenu = uicontextmenu(fig);
uimenu(cMenu,'Text','插旗/取消','Separator','on', ...
'MenuSelectedFcn', @(src,evt) rightClick(btnObj,src));
end
end
三、旧版 MATLAB 兼容版(无 uifigure
)
如果 MATLAB 版本 < R2019b,用传统 figure
+ uicontrol
实现,可替换“GUI”段为(其余逻辑不变):
fig = figure('Name','MATLAB 扫雷','NumberTitle','off','MenuBar','none', ...
'Resize','off','Position',[500 300 35*n+25 35*m+45]);
btn = zeros(m,n);
for i = 1:m
for j = 1:n
h = uicontrol('Style','pushbutton','String',' ','Units','pixels', ...
'Position',[35*(j-1)+5 35*(m-i)+5 30 30], ...
'FontWeight','bold','FontSize',10, ...
'Callback',{@leftClick,i,j});
btn(i,j) = h;
end
end
% 其余回调函数需对应改为处理句柄,逻辑完全一致,这里略。
参考代码 matlab扫雷小游戏 www.youwenfan.com/contentcsf/22784.html
四、扩展玩法
- 计时器:使用
timer
对象记录用时并在标题栏实时刷新。 - 难度选择:在
uifigure
顶部加uidropdown
供用户选初级/中级/高级。 - 自定义雷区:加两个
uieditfield
输入行列与雷数,点击“开始”后动态创建按钮。 - AI 自动扫雷:把已翻开格子的数字与周围未知格关系建模为约束满足问题,用 MATLAB 的
intlinprog
或逻辑求解器推断必开/必旗格。