本文将介绍如何使用鸿蒙提供的UI组件来绘制一个中国象棋棋盘并放置棋子。通过本教程,您将学会基本的UI构建技巧,以及如何在鸿蒙环境中创建一个简单的象棋游戏界面。

一、定义棋盘线条与棋子位置

首先,我们需要定义几个基础类来帮助我们构造棋盘。ChessLine类用于表示棋盘上的线段,而MyPosition类则用来记录棋盘上每个位置是否需要特殊的标记(如“兵”、“卒”、“炮”的位置)。

class ChessLine {
  startPoint: [number, number] = [0, 0];
  endPoint: [number, number] = [0, 0];
}

class MyPosition {
  x: number = 0;
  y: number = 0;
  topLeft: boolean = true;
  topRight: boolean = true;
  bottomLeft: boolean = true;
  bottomRight: boolean = true;

  constructor(x: number, y: number, topLeft: boolean, topRight: boolean, bottomLeft: boolean, bottomRight: boolean) {
    this.x = x;
    this.y = y;
    this.topLeft = topLeft;
    this.topRight = topRight;
    this.bottomLeft = bottomLeft;
    this.bottomRight = bottomRight;
  }
}

二、创建棋子类

接下来,我们定义ChessPiece类来代表棋盘上的每一个棋子。这个类包括棋子的颜色、类型等属性,并且有一个方法getColor()来获取棋子的颜色值。

@ObservedV2
class ChessPiece {
  @Trace opacity: number = 1;
  @Trace value: string = "";
  @Trace type: number = 0; // 0: 无棋, 1: 红棋,2: 黑棋

  redColor: string = `rgb(144,11,11)`;
  blackColor: string = `rgb(78,56,23)`;

  constructor(value: string, type: number) {
    this.value = value;
    this.type = type;
  }

  setValue(value: string, type: number) {
    this.value = value;
    this.type = type;
  }

  getColor() {
    if (this.type === 1) {
      return this.redColor;
    } else if (this.type === 2) {
      return this.blackColor;
    }
    return "#00000000";
  }
}

三、构建棋盘

使用ChessBoard类来构建整个棋盘,其中包括棋盘的基本尺寸、棋子数组、棋盘线段数组等。在这个类中,我们还定义了初始化游戏的方法initGame(),它会根据规则在棋盘上放置棋子。

@Entry
@Component
struct ChessBoard {
  cellWidth: number = 70;
  borderPieceWidth: number = 12;
  pieceSize: number = 66;
  pieces: ChessPiece[] = [];
  lines: ChessLine[] = [];
  positions: MyPosition[] = [];
  selectedIndex: number = -1; // -1表示未点击任何棋子,非-1表示当前正在点击的棋子

  aboutToAppear(): void {
    for (let i = 0; i < 9 * 10; i++) {
      this.pieces.push(new ChessPiece("", 0));
    }
    this.initGame();
    // 初始化水平线和垂直线...
  }

  initGame() {
    // 设置棋子初始位置...
  }

  build() {
    Column({ space: 10 }) {
      // 构建棋盘框架和线条...
    }
  }
}

四、绘制棋子

最后,我们需要在棋盘上绘制棋子。这里使用了Flex和ForEach等组件来遍历棋子数组,并根据棋子的类型绘制不同的样式。

Flex({ wrap: FlexWrap.Wrap }) {
  ForEach(this.pieces, (piece: ChessPiece, index: number) => {
    Stack() {
      Text(piece.value)
      // 设置棋子文本样式...
    }
    .opacity(piece.opacity)
    .width(`${this.cellWidth}lpx`)
    .height(`${this.cellWidth}lpx`)
    .onClick(() => {
      // 处理点击事件...
    })
  })
}

【完整代码】

class ChessLine {
  startPoint: [number, number] = [0, 0];
  endPoint: [number, number] = [0, 0];
}

class MyPosition {
  x: number = 0;
  y: number = 0;
  topLeft: boolean = true;
  topRight: boolean = true;
  bottomLeft: boolean = true;
  bottomRight: boolean = true;

  constructor(x: number, y: number, topLeft: boolean, topRight: boolean, bottomLeft: boolean, bottomRight: boolean) {
    this.x = x;
    this.y = y;
    this.topLeft = topLeft;
    this.topRight = topRight;
    this.bottomLeft = bottomLeft;
    this.bottomRight = bottomRight;
  }
}

@ObservedV2
class ChessPiece {
  @Trace opacity: number = 1;
  @Trace value: string = "";
  @Trace type: number = 0; // 0: 无棋, 1: 红棋,2: 黑棋
  redColor: string = `rgb(144,11,11)`;
  blackColor: string = `rgb(78,56,23)`;

  constructor(value: string, type: number) {
    this.value = value;
    this.type = type;
  }

  setValue(value: string, type: number) {
    this.value = value;
    this.type = type;
  }

  getColor() {
    if (this.type === 1) {
      return this.redColor;
    } else if (this.type === 2) {
      return this.blackColor;
    }
    return "#00000000";
  }
}

@Entry
@Component
struct ChessBoard {
  cellWidth: number = 70;
  borderPieceWidth: number = 12;
  pieceSize: number = 66;
  pieces: ChessPiece[] = [];
  lines: ChessLine[] = [];
  positions: MyPosition[] = [];
  selectedIndex: number = -1; // -1表示未点击任何棋子,非-1表示当前正在点击的棋子

  aboutToAppear(): void {
    for (let i = 0; i < 9 * 10; i++) {
      this.pieces.push(new ChessPiece("", 0));
    }
    this.initGame();

    // 初始化水平线和垂直线
    for (let i = 0; i < 10; i++) {
      this.lines.push({
        startPoint: [0, this.cellWidth * i],
        endPoint: [this.cellWidth * 8, this.cellWidth * i]
      });
      this.lines.push({
        startPoint: [this.cellWidth * i, 0],
        endPoint: [this.cellWidth * i, this.cellWidth * (i === 0 || i === 8 ? 9 : 4)]
      });
      this.lines.push({
        startPoint: [this.cellWidth * i, this.cellWidth * 5],
        endPoint: [this.cellWidth * i, this.cellWidth * 9]
      });
    }


    // 初始化九宫格内的斜线
    this.lines.push({
      startPoint: [3 * this.cellWidth, 0],
      endPoint: [5 * this.cellWidth, 2 * this.cellWidth],
    });
    this.lines.push({
      startPoint: [5 * this.cellWidth, 0],
      endPoint: [3 * this.cellWidth, 2 * this.cellWidth],
    });
    this.lines.push({
      startPoint: [3 * this.cellWidth, 7 * this.cellWidth],
      endPoint: [5 * this.cellWidth, 9 * this.cellWidth],
    });
    this.lines.push({
      startPoint: [5 * this.cellWidth, 7 * this.cellWidth],
      endPoint: [3 * this.cellWidth, 9 * this.cellWidth],
    });

    // 兵卒炮位置标
    this.positions.push(new MyPosition(1, 2, true, true, true, true))
    this.positions.push(new MyPosition(7, 2, true, true, true, true))
    this.positions.push(new MyPosition(0, 3, false, true, false, true))
    this.positions.push(new MyPosition(2, 3, true, true, true, true))
    this.positions.push(new MyPosition(4, 3, true, true, true, true))
    this.positions.push(new MyPosition(6, 3, true, true, true, true))
    this.positions.push(new MyPosition(8, 3, true, false, true, false))
    this.positions.push(new MyPosition(1, 7, true, true, true, true))
    this.positions.push(new MyPosition(7, 7, true, true, true, true))
    this.positions.push(new MyPosition(0, 6, false, true, false, true))
    this.positions.push(new MyPosition(2, 6, true, true, true, true))
    this.positions.push(new MyPosition(4, 6, true, true, true, true))
    this.positions.push(new MyPosition(6, 6, true, true, true, true))
    this.positions.push(new MyPosition(8, 6, true, false, true, false))
  }

  initGame() {
    for (let i = 0; i < 9 * 10; i++) {
      this.pieces[i].setValue("", 0);
    }
    this.pieces[0].setValue("车", 2)
    this.pieces[1].setValue("马", 2)
    this.pieces[2].setValue("象", 2)
    this.pieces[3].setValue("士", 2)
    this.pieces[4].setValue("将", 2)
    this.pieces[5].setValue("士", 2)
    this.pieces[6].setValue("象", 2)
    this.pieces[7].setValue("马", 2)
    this.pieces[8].setValue("车", 2)
    this.pieces[19].setValue("炮", 2)
    this.pieces[25].setValue("炮", 2)
    this.pieces[27].setValue("卒", 2)
    this.pieces[29].setValue("卒", 2)
    this.pieces[31].setValue("卒", 2)
    this.pieces[33].setValue("卒", 2)
    this.pieces[35].setValue("卒", 2)

    this.pieces[54].setValue("兵", 1)
    this.pieces[56].setValue("兵", 1)
    this.pieces[58].setValue("兵", 1)
    this.pieces[60].setValue("兵", 1)
    this.pieces[62].setValue("兵", 1)
    this.pieces[64].setValue("炮", 1)
    this.pieces[70].setValue("炮", 1)
    this.pieces[81].setValue("车", 1)
    this.pieces[82].setValue("马", 1)
    this.pieces[83].setValue("相", 1)
    this.pieces[84].setValue("仕", 1)
    this.pieces[85].setValue("帅", 1)
    this.pieces[86].setValue("仕", 1)
    this.pieces[87].setValue("相", 1)
    this.pieces[88].setValue("马", 1)
    this.pieces[89].setValue("车", 1)
  }

  build() {
    Column({ space: 10 }) {
      Column() {
        Stack() {
          // 棋盘矩形边框
          Rect()
            .margin({
              top: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`,
              left: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`
            })
            .width(`${this.cellWidth * 8 + this.borderPieceWidth}lpx`)
            .height(`${this.cellWidth * 9 + this.borderPieceWidth}lpx`)
            .fillOpacity(0)
            .stroke(Color.Black)
            .strokeWidth(`${this.borderPieceWidth / 3}lpx`);

          // 绘制线条
          ForEach(this.lines, (line: ChessLine, _index: number) => {
            Line()
              .margin({ left: `${this.cellWidth / 2}lpx`, top: `${this.cellWidth / 2}lpx` })
              .startPoint([`${line.startPoint[0]}lpx`, `${line.startPoint[1]}lpx`])
              .endPoint([`${line.endPoint[0]}lpx`, `${line.endPoint[1]}lpx`])
              .stroke(Color.Black);
          });
          // 添加"兵卒炮"标记
          ForEach(this.positions, (position: MyPosition, _index: number) => {
            if (position.topLeft) {
              Polyline()
                .margin({
                  left: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`,
                  top: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`
                })
                .points([
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y - this.borderPieceWidth}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x - this.borderPieceWidth}lpx`, `${this.cellWidth * position.y}lpx`],
                ])
                .width(1)
                .height(1)
                .fillOpacity(0)
                .stroke(Color.Black);
            }
            if (position.topRight) {
              Polyline()
                .margin({
                  left: `${this.cellWidth / 2 + this.borderPieceWidth / 2}lpx`,
                  top: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`
                })
                .points([
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y - this.borderPieceWidth}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x + this.borderPieceWidth}lpx`, `${this.cellWidth * position.y}lpx`],
                ])
                .width(1)
                .height(1)
                .fillOpacity(0)
                .stroke(Color.Black)
            }
            if (position.bottomLeft) {
              Polyline()
                .margin({
                  left: `${this.cellWidth / 2 - this.borderPieceWidth / 2}lpx`,
                  top: `${this.cellWidth / 2 + this.borderPieceWidth / 2}lpx`
                })
                .points([
                  [`${this.cellWidth * position.x - this.borderPieceWidth}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y + this.borderPieceWidth}lpx`],
                ])
                .width(1)
                .height(1)
                .fillOpacity(0)
                .stroke(Color.Black)
            }
            if (position.bottomRight) {
              Polyline()
                .margin({
                  left: `${this.cellWidth / 2 + this.borderPieceWidth / 2}lpx`,
                  top: `${this.cellWidth / 2 + this.borderPieceWidth / 2}lpx`
                })
                .points([
                  [`${this.cellWidth * position.x + this.borderPieceWidth}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y}lpx`],
                  [`${this.cellWidth * position.x}lpx`, `${this.cellWidth * position.y + this.borderPieceWidth}lpx`],
                ])
                .width(1)
                .height(1)
                .fillOpacity(0)
                .stroke(Color.Black)
            }
          });

          // 绘制棋子
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.pieces, (piece: ChessPiece, index: number) => {
              Stack() {
                Text(piece.value)
                  .width(`${this.pieceSize}lpx`)
                  .height(`${this.pieceSize}lpx`)
                  .backgroundColor(piece.type !== 0 ? `rgb(192,149,106)` : Color.Transparent)
                  .textAlign(TextAlign.Center)
                  .fontSize(`${this.pieceSize / 2}lpx`)
                  .fontColor(piece.getColor())
                  .borderColor(piece.getColor())
                  .borderRadius(`50%`)
                  .borderWidth(`2lpx`)
                  .textShadow({
                    radius: 2,
                    color: Color.White,
                    offsetX: 2,
                    offsetY: 2
                  });
                Circle()
                  .width(`${this.pieceSize - 15}lpx`)
                  .height(`${this.pieceSize - 15}lpx`)
                  .fillOpacity(0)
                  .strokeWidth(2)
                  .stroke(piece.getColor())
                  .strokeDashArray([0.2, 1]);
              }
              .opacity(piece.opacity)
              .width(`${this.cellWidth}lpx`)
              .height(`${this.cellWidth}lpx`)
              .onClick(() => {
                if (this.selectedIndex === -1) {
                  this.selectedIndex = index;
                  animateToImmediately({
                    iterations: 3,
                    duration: 300,
                    onFinish: () => {
                      animateToImmediately({
                        iterations: 1,
                        duration: 0
                      }, () => {
                        piece.opacity = 1;
                      });
                    }
                  }, () => {
                    piece.opacity = 0.5;
                  });
                } else {
                  piece.value = this.pieces[this.selectedIndex].value;
                  piece.type = this.pieces[this.selectedIndex].type;
                  this.pieces[this.selectedIndex].value = '';
                  this.pieces[this.selectedIndex].type = 0;
                  this.selectedIndex = -1;
                }
              });
            });
          }.width('100%').height('100%');
        }
        .align(Alignment.TopStart)
        .width(`${this.cellWidth * 9}lpx`)
        .height(`${this.cellWidth * 10}lpx`);
      }
      .padding(10)
      .backgroundColor(Color.Orange)
      .borderRadius(10);

      Button('重新开始').onClick(() => {
        this.initGame();
      });
    }.width('100%');
  }
}

zhongcx
1 声望3 粉丝