HarmonyOS 小游戏《对战五子棋》开发第25篇 - 最后一手标记

小细节大体验------棋盘上那些容易被忽略的视觉元素

标记截图:

星位:棋盘的"坐标参照物"

什么是星位

星位是棋盘上的特殊标记点,传统五子棋/围棋棋盘上有5个星位:

复制代码
    0  1  2  3  4 ... 7 ... 11 ... 14
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
3 │  │  │  ★  │  │  │  │  ★  │  │
  ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
7 │  │  │  │  │  ★  │  │  │  │  │  ← 天元
  ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤
11│  │  │  ★  │  │  │  │  ★  │  │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

代码实现

typescript 复制代码
const stars: number[][] = [[3, 3], [3, 11], [7, 7], [11, 3], [11, 11]];
ctx.fillStyle = STAR_COLOR;
for (const star of stars) {
  const sx = halfCell + star[1] * cellSize;
  const sy = halfCell + star[0] * cellSize;
  ctx.beginPath();
  ctx.arc(sx, sy, 3, 0, Math.PI * 2);
  ctx.fill();
}

星位的作用

  1. 视觉参照:帮助玩家快速定位棋盘位置
  2. 美观性:增加棋盘的传统感
  3. 开局参考:天元(7,7)是常见的开局位置

星位坐标解析

  • [3, 3]:左上星位
  • [3, 11]:右上星位
  • [7, 7]:天元(中心)
  • [11, 3]:左下星位
  • [11, 11]:右下星位

这5个位置在15x15棋盘上呈对称分布,符合传统棋盘的美学。

星位半径

typescript 复制代码
ctx.arc(sx, sy, 3, 0, Math.PI * 2);

固定3像素半径------不随cellSize变化。这是一个设计选择:星位保持固定大小,在不同屏幕上视觉一致。

最后一手标记

代码实现

typescript 复制代码
if (this.lastMoveRow >= 0 && this.lastMoveCol >= 0) {
  const px = halfCell + this.lastMoveCol * cellSize;
  const py = halfCell + this.lastMoveRow * cellSize;
  ctx.strokeStyle = LAST_MOVE_COLOR;  // #E63946 红色
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(px, py, cellSize * 0.16, 0, Math.PI * 2);
  ctx.stroke();
}

设计要点

  1. 红色空心圆stroke()而非fill(),不遮挡棋子
  2. 半径cellSize * 0.16:比棋子(0.42)小得多,是一个小标记
  3. 线宽2像素:足够醒目但不粗糙
  4. 条件绘制lastMoveRow >= 0时才绘制

为什么用空心圆而非实心

复制代码
实心圆(不推荐):        空心圆(本项目):
      ◯                       ◯
    ◉  ← 实心遮挡棋子        ◉  ← 空心不遮挡
      ◯                       ◯

空心圆画在棋子上面,但不遮挡棋子的渐变效果。

用户体验价值

在快节奏的对局中,玩家很容易忘记对手刚才下在哪里。最后一手标记解决了这个问题:

  • 双人对战:双方都能看到最后一步
  • AI对战:玩家能看清楚AI的落子位置
  • 悔棋后:标记移动到上一步位置

标记的更新机制

typescript 复制代码
// ChessBoardView通过@Prop接收
@Prop lastMoveRow: number = -1;
@Prop lastMoveCol: number = -1;

// 父组件更新
private refreshBoardData(): void {
  this.lastMoveRow = this.engine.lastMove ? this.engine.lastMove.row : -1;
  this.lastMoveCol = this.engine.lastMove ? this.engine.lastMove.col : -1;
}

当引擎的lastMove变化时,父组件更新@Prop@Watch触发drawBoard()重绘,新的最后一手标记自动绘制。

悔棋后的标记

typescript 复制代码
// GomokuEngine.undo()
if (this.history.length > 0) {
  const prev = this.history[this.history.length - 1];
  this.lastMove = new Move(prev.row, prev.col);
} else {
  this.lastMove = null;  // 没有历史时清除标记
}

悔棋后lastMove指向倒数第二步,标记自动移动。如果悔到空棋盘,lastMove为null,标记消失(lastMoveRow = -1)。

drawBoard中的绘制顺序

typescript 复制代码
private drawBoard(): void {
  // 1. 清空画布
  ctx.clearRect(0, 0, size, size);
  // 2. 绘制背景
  ctx.fillRect(0, 0, size, size);
  // 3. 绘制网格线
  // 4. 绘制星位
  // 5. 绘制棋子
  // 6. 绘制最后一手标记 ← 最后绘制,在最上层
}

绘制顺序决定图层:后绘制的在上方。最后一手标记最后绘制,确保它在棋子之上。

总结

星位和最后一手标记看似是小细节,但对用户体验有重要影响:

  1. 星位:传统棋盘的视觉参照,增加专业感
  2. 最后一手标记:红色空心圆,醒目且不遮挡
  3. 绘制顺序:标记在棋子之上,确保可见
  4. 自动更新:通过@Prop数据驱动,无需手动管理
  5. 悔棋适配:标记自动跟随历史记录移动

这些细节让游戏从"能玩"变成"好玩"。

相关推荐
解局易否结局2 小时前
鸿蒙PC应用开发:窗口管理 + 鼠标键盘交互 + 文件对话框
笔记·华为·计算机外设·交互·harmonyos
心中有国也有家2 小时前
AtomGit Flutter 鸿蒙客户端:呼吸练习的三种科学模式
学习·flutter·华为·harmonyos
FrameNotWork3 小时前
HarmonyOS 6.1 Tabs组件深度定制 — 底部导航栏从入门到踩坑
华为·交互·harmonyos
不才难以繁此生3 小时前
HarmonyOS ArkUI NavPathStack 实战:中式美食详情页多入口返回栈怎么设计
harmonyos·arkts·arkui·navigation·中式美食·navpathstack·路由栈
不才难以繁此生3 小时前
HarmonyOS 实战|中式美食系列目录:从页面体验到本地数据闭环
harmonyos·arkts·中式美食·arkui v2·系列目录
不才难以繁此生4 小时前
ArkTS 本地数据库实战|中式美食购物清单:按菜谱来源合并食材数量
harmonyos·arkts·repository·rdb·中式美食·购物清单·本地数据库
ZZZMMM.zip4 小时前
基于鸿蒙HarmonyOS NEXT开发AI书籍推荐应用:智能阅读新体验与鸿蒙Flutter框架跨端实践
人工智能·flutter·华为·harmonyos·鸿蒙
不才难以繁此生4 小时前
HarmonyOS ResourceStr 实战:中式美食菜谱封面怎么统一注册、兜底和缓存
harmonyos·arkts·image·arkui·中式美食·resourcestr·图片缓存
Helen_cai4 小时前
OpenHarmony http 网络请求封装与全局拦截实战(API Version23 + 适配版)
网络·网络协议·http·华为·harmonyos