Flutter for OpenHarmony游戏集合App实战之黑白棋落子翻转

通过网盘分享的文件:game_flutter_openharmony.zip

链接: https://pan.baidu.com/s/1ryUS1A0zcvXGrDaStu530w 提取码: tqip

前言

黑白棋(又叫翻转棋、奥赛罗)是一个策略游戏。落子后,夹住的对方棋子会翻转成自己的颜色。

翻转是黑白棋的核心机制,这篇来聊聊怎么实现。这个机制看起来简单,但实现起来需要考虑八个方向的搜索、夹住的判断、以及各种边界情况。

我在实现这个功能的时候,最开始只考虑了水平和垂直方向,忘了对角线,结果游戏玩起来很奇怪。后来加上了八个方向的搜索,游戏才正常了。

棋盘数据

dart 复制代码
static const int size = 8;
late List<List<int>> board; // 0:empty, 1:black, 2:white

8x8的棋盘,每个格子存储:

  • 0: 空
  • 1: 黑棋
  • 2: 白棋

用数字而不是枚举,是因为数字可以做简单的数学运算(比如3-player得到对手),代码更简洁。

初始布局

dart 复制代码
void _initGame() {
  board = List.generate(size, (_) => List.filled(size, 0));
  board[3][3] = board[4][4] = 2;
  board[3][4] = board[4][3] = 1;

中间四格交叉放置黑白棋,这是标准开局。

复制代码
   3 4
3  ○ ●
4  ● ○

List.generate创建8行,每行用List.filled创建8个0。然后在中间四格放置初始棋子。

这个初始布局是黑白棋的标准规则,所有正式比赛都是这样开局的。

获取可翻转的棋子

dart 复制代码
List<List<int>> _getFlips(int row, int col, int player) {
  if (board[row][col] != 0) return [];
  List<List<int>> allFlips = [];
  int opponent = 3 - player;

这是黑白棋最核心的方法,计算在某个位置落子后能翻转哪些棋子。返回一个坐标列表,每个元素是[row, col]。

前置检查

dart 复制代码
if (board[row][col] != 0) return [];

只能落在空格子上。如果格子已经有棋子,返回空列表,表示不能落子。

对手计算

dart 复制代码
int opponent = 3 - player;

巧妙的计算:player是1时opponent是2,player是2时opponent是1。

这个技巧利用了1+2=3的特性。比用if-else判断更简洁,而且不容易出错。

八个方向

dart 复制代码
  for (int dr = -1; dr <= 1; dr++) {
    for (int dc = -1; dc <= 1; dc++) {
      if (dr == 0 && dc == 0) continue;

遍历8个方向:上、下、左、右、四个对角线。

drdc是方向向量,取值-1、0、1的组合,排除(0,0)。

具体来说:

  • (-1, -1): 左上
  • (-1, 0): 上
  • (-1, 1): 右上
  • (0, -1): 左
  • (0, 1): 右
  • (1, -1): 左下
  • (1, 0): 下
  • (1, 1): 右下

用两层循环生成这8个方向,比手动列出来更简洁。

沿方向搜索

dart 复制代码
      List<List<int>> flips = [];
      int r = row + dr, c = col + dc;
      while (r >= 0 && r < size && c >= 0 && c < size && board[r][c] == opponent) {
        flips.add([r, c]);
        r += dr; c += dc;
      }

从落子位置出发,沿着方向走,遇到对手的棋子就记录下来。

while循环的条件:

  1. r和c在棋盘范围内
  2. 当前位置是对手的棋子

每次循环把当前位置加入flips列表,然后继续沿方向走(r += dr, c += dc)。

验证夹住

dart 复制代码
      if (flips.isNotEmpty && r >= 0 && r < size && c >= 0 && c < size && board[r][c] == player) {
        allFlips.addAll(flips);
      }
    }
  }
  return allFlips;
}

如果:

  1. 记录了对手的棋子(flips不为空)
  2. 最后遇到的是自己的棋子(在棋盘范围内且是player)

说明这些对手棋子被夹住了,可以翻转。把flips加入allFlips。

如果flips为空,说明这个方向没有对手棋子,不能翻转。

如果最后遇到的是空格或出界,说明没有形成夹击,也不能翻转。

落子逻辑

dart 复制代码
void _placePiece(int row, int col) {
  if (gameOver) return;
  List<List<int>> flips = _getFlips(row, col, currentPlayer);
  if (flips.isEmpty) return;

  setState(() {
    board[row][col] = currentPlayer;
    for (var f in flips) board[f[0]][f[1]] = currentPlayer;

这个方法处理玩家的落子操作,是游戏交互的核心。

游戏结束检查

dart 复制代码
if (gameOver) return;

游戏已经结束,不能再落子。

检查合法性

dart 复制代码
List<List<int>> flips = _getFlips(row, col, currentPlayer);
if (flips.isEmpty) return;

如果没有可翻转的棋子,这步棋不合法,不能落子。

黑白棋的规则是:每一步必须翻转至少一个对手的棋子。如果某个位置不能翻转任何棋子,就不能在那里落子。

放置棋子

dart 复制代码
board[row][col] = currentPlayer;

在落子位置放上自己的棋子。currentPlayer是1(黑)或2(白)。

翻转棋子

dart 复制代码
for (var f in flips) board[f[0]][f[1]] = currentPlayer;

把所有被夹住的棋子翻转成自己的颜色。

flips是一个坐标列表,每个元素是[row, col]。遍历这个列表,把每个位置的棋子改成currentPlayer。

这就是"翻转"的实现------把对手的棋子变成自己的。

切换玩家

dart 复制代码
    int next = 3 - currentPlayer;
    if (_hasValidMove(next)) {
      currentPlayer = next;
    } else if (!_hasValidMove(currentPlayer)) {
      gameOver = true;
      _showResult();
    }
  });
}

落子后需要切换玩家,但黑白棋有特殊规则:如果对手没有合法落子位置,就跳过对手,当前玩家继续下。

计算下一个玩家

dart 复制代码
int next = 3 - currentPlayer;

同样用3-player的技巧计算对手。

正常切换

dart 复制代码
if (_hasValidMove(next)) {
  currentPlayer = next;
}

如果对手有合法落子位置,切换到对手。

跳过

如果对手没有合法位置(_hasValidMove返回false),不切换,当前玩家继续下。

这是黑白棋的重要规则。有时候一方会连续下好几步,因为另一方一直没有合法位置。

游戏结束

dart 复制代码
else if (!_hasValidMove(currentPlayer)) {
  gameOver = true;

如果对手没有合法位置,而且当前玩家也没有合法位置,游戏结束。

这意味着双方都不能再下了,通常是棋盘满了或者剩余的空位都不能形成夹击。

检查是否有合法落子

dart 复制代码
bool _hasValidMove(int player) {
  for (int r = 0; r < size; r++) {
    for (int c = 0; c < size; c++) {
      if (_getFlips(r, c, player).isNotEmpty) return true;
    }
  }
  return false;
}

遍历所有格子,只要有一个位置能翻转棋子,就有合法落子。

这个方法用于判断是否需要跳过玩家,以及游戏是否结束。

遍历64个格子,每个格子调用_getFlips检查。虽然看起来效率不高,但实际上很快,因为大部分格子要么已经有棋子,要么很快就能判断出不能翻转。

可落子位置提示

dart 复制代码
bool canPlace = _getFlips(r, c, currentPlayer).isNotEmpty;
...
Widget _buildPiece(int value, bool canPlace) {
  if (value == 0) {
    return canPlace ? Container(width: 12, height: 12, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white30)) : const SizedBox();
  }

空格子如果可以落子,显示一个半透明的小圆点提示。

这个提示很重要,黑白棋规则复杂,玩家不容易看出哪里能下。有了提示,玩家可以专注于策略,而不是花时间找合法位置。

提示点的样式

dart 复制代码
Container(width: 12, height: 12, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white30))

12x12像素的小圆点,白色30%透明度。足够明显让玩家看到,又不会太抢眼影响棋盘的整体观感。

棋子渲染

dart 复制代码
return Container(
  width: 32, height: 32,
  decoration: BoxDecoration(shape: BoxShape.circle, color: value == 1 ? Colors.black : Colors.white,
    boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2, offset: const Offset(1, 1))]),
);

黑棋黑色,白棋白色,加阴影增加立体感。

尺寸

dart 复制代码
width: 32, height: 32,

32x32像素的棋子,在8x8的棋盘上大小适中。

形状

dart 复制代码
shape: BoxShape.circle,

圆形棋子,这是黑白棋的标准样式。

颜色

dart 复制代码
color: value == 1 ? Colors.black : Colors.white,

value是1显示黑色,value是2显示白色。

阴影

dart 复制代码
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2, offset: const Offset(1, 1))]

阴影让棋子看起来像是浮在棋盘上,有立体感。blurRadius是模糊半径,offset是阴影偏移。

棋盘背景

dart 复制代码
Container(
  margin: const EdgeInsets.all(8),
  decoration: BoxDecoration(color: Colors.green[700], border: Border.all(color: Colors.black, width: 2)),

绿色背景,模拟经典黑白棋棋盘。

颜色选择

dart 复制代码
color: Colors.green[700],

深绿色,这是黑白棋棋盘的经典颜色。和黑白棋子形成鲜明对比,看起来很舒服。

边框

dart 复制代码
border: Border.all(color: Colors.black, width: 2),

2像素的黑色边框,让棋盘更有质感。

边距

dart 复制代码
margin: const EdgeInsets.all(8),

8像素的外边距,让棋盘不会贴着屏幕边缘。

格子渲染

dart 复制代码
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 8),
  itemCount: 64,
  itemBuilder: (_, index) {
    int r = index ~/ 8, c = index % 8;
    bool canPlace = _getFlips(r, c, currentPlayer).isNotEmpty;
    return GestureDetector(
      onTap: () => _placePiece(r, c),
      child: Container(
        decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
        child: Center(child: _buildPiece(board[r][c], canPlace)),
      ),
    );
  },
)

用GridView.builder构建8x8的网格。

坐标计算

dart 复制代码
int r = index ~/ 8, c = index % 8;

index是0-63的一维索引,转换成二维坐标。~/是整除,%是取余。

点击处理

dart 复制代码
onTap: () => _placePiece(r, c),

点击格子时调用_placePiece落子。

小结

这篇讲了黑白棋的落子翻转,核心知识点:

  • 八方向搜索: dr和dc的组合遍历8个方向,覆盖所有可能的夹击方向
  • 夹住判断: 连续对手棋子后遇到自己的棋子,形成夹击
  • 翻转操作: 把被夹住的棋子改成自己的颜色
  • 合法性检查: 必须能翻转至少一个棋子才能落子
  • 跳过规则: 没有合法位置时跳过,对手继续
  • 游戏结束: 双方都没有合法位置时结束
  • 落子提示: 显示可落子位置,帮助玩家找到合法位置
  • 3-player技巧: 用3减去当前玩家得到对手

翻转是黑白棋的核心,理解了这个,游戏的主要逻辑就清楚了。黑白棋的策略很深,角落和边缘的位置特别重要,因为它们不容易被翻转。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
Word码4 小时前
[C++语法]-string类(用法详解及实现)
开发语言·c++
Web极客码4 小时前
为什么建议使用WordPress WP Mail SMTP来替代PHP Mail
开发语言·php·wordpress
熹乐互动4 小时前
亲测有效!马年互动赛马游戏实践复盘分享
游戏
zhangphil4 小时前
Android adb shell抓取trace(二)
android
kaikaile19954 小时前
基于MATLAB的视频行人检测与跟踪系统实现
开发语言·matlab·音视频
hqwest4 小时前
码上通QT实战37--项目总结
开发语言·qt·软件开发·系统集成·设备选型
咕噜企业分发小米4 小时前
腾讯云多云管理工具如何与第三方合规工具集成以支持持续合规?
运维·服务器·游戏
猛扇赵四那边好嘴.5 小时前
Flutter 框架跨平台鸿蒙开发 - 全国公厕查询:智能定位附近公厕
flutter·华为·harmonyos
血色橄榄枝5 小时前
01 Flutter for OpenHarmony
flutter·开源·鸿蒙