鸿蒙开发之ArkTS并发技术中国象棋

仿佛云烟
发布于 2025-6-28 16:54
浏览
0收藏

一、工具


鸿蒙开发之ArkTS并发技术中国象棋-鸿蒙开发者社区

DevEco Studio


二、开发过程


创建ChessView.ets文件


添加一个布局用来显示棋盘:


[
              [5, 4, 3, 2, 1, 2, 3, 4, 5],
              [0, 0, 0, 0, 0, 0, 0, 0, 0],
              [0, 6, 0, 0, 0, 0, 0, 6, 0],
              [7, 0, 7, 0, 7, 0, 7, 0, 7],
              [0, 0, 0, 0, 0, 0, 0, 0, 0],

              [0, 0, 0, 0, 0, 0, 0, 0, 0],
              [14, 0, 14, 0, 14, 0, 14, 0, 14],
              [0, 13, 0, 0, 0, 0, 0, 13, 0],
              [0, 0, 0, 0, 0, 0, 0, 0, 0],
              [12, 11, 10, 9, 8, 9, 10, 11, 12],
            ]


通过二维数组来定义棋盘位置


1、2、3、4、5、6、7、8、9、10、10、11、12、13、14分别代表


蓝:帅、士、象、马、車、兵、炮


红:帅、士、象、马、車、兵、炮



棋盘布局:


用ForEach+Column嵌套ForEach+Row


Row() {
          ForEach(pieceX, (icon1:number,index1:number ) => {
            Column() {
              ForEach(pieceY, (icon2:number,index2:number ) => {
                Row() {
                  Image(this.bg[AiPlayData.chessInfo.piece[icon2][icon1]])
                    .width(40)
                    .height(40)
                    .onClick(()=>{

                      this.setPlay(index1,index2)

                    })
                }
                .justifyContent(FlexAlign.Center)

              })
            }
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)



红蓝方下棋方法:


首先判断下棋方红/蓝,然后根据点击的位置,确定車、马、炮、兵、象、士、帅棋子
 


机器人下棋算法:


因为机器人算法耗时,所以我们来创建一个线程执行,防止卡UI。


async function AiAsync(piece:number[][],isRedGo:boolean,depth:number,startZobristKey:number,startZobristKeyCheck:number,ZobristInfo:Map<number, number>): Promise {
  try {
    let task: taskpool.Task = new taskpool.Task(startAI,piece,isRedGo,depth,startZobristKey,startZobristKeyCheck,ZobristInfo);
    let move = await taskpool.execute(task)

    return move as Move
  } catch (e) {
    console.error("taskpool execute error is: " + e);
  }

  return new Move(new Pos(-1, -1), new Pos(-1, -1))

}


这里我们选择开启taskpool线程:


taskpool的线程开启方法简单,适合短时间使用。他和Worker线程不同,Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。


创建线程函数


@Concurrent
async function startAI(piece:number[][],isRedGo:boolean,depth:number,startZobristKey:number,startZobristKeyCheck:number,ZobristInfo:Map<number, number>): Promise{
  // 添加生产相关逻辑
  let move:Move=AI.getBestMove(piece,isRedGo,depth,startZobristKey,startZobristKeyCheck,ZobristInfo)
  return move;
}


启动线程:



try {
let task: taskpool.Task = new taskpool.Task(startAI,piece,isRedGo,depth,startZobristKey,startZobristKeyCheck,ZobristInfo);
let move = await taskpool.execute(task)

return move as Move
} catch (e) {
console.error("taskpool execute error is: " + e);
}


完成后返回Move对象:


return move as Move


下面是线程执行函数的getBestMove(),主要通过循环遍历AI算法得到最终会结果。


public static getBestMove( piece:number[][],  isRedGo:boolean,  depth:number,  startZobristKey:number,  startZobristKeyCheck:number,  ZobristInfo:Map<number, number>):Move {

    let pieceClone:number[][] = [
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0],
      [0,0,0,0,0,0,0,0,0]
    ];

    for (let  i = 0; i <10; i++) {
      pieceClone[i] = piece[i];
    }

    let searchCnt = 0;
    let goodMove:Move = new Move(new Pos(-1, -1), new Pos(-1, -1));
    let bestMove:Move = new Move(new Pos(-1, -1), new Pos(-1, -1));
    AI.clearHistory();
    for (let  i = 2; i <= depth; i += 2) {
      goodMove = AI.getGoodMove(pieceClone, isRedGo, i, startZobristKey, startZobristKeyCheck, ZobristInfo);
      if (AI.goodValue >= -AI.Win / 2) {
        bestMove = goodMove;
      }
    }

    if (bestMove.fromPos == (new Pos(-1, -1)) == true) {
      bestMove = AI.getLegalMove(pieceClone, isRedGo);
    }
    //Log.i("博弈树搜索算法比对","搜索结点数:"+String.valueOf(searchCnt));
    return bestMove;
  }



然后就是AI算法了:


首先定义象棋数据Info


鸿蒙开发之ArkTS并发技术中国象棋-鸿蒙开发者社区


setting          //棋子行走过中的状态变化
infoSet          //通过知识库AI算法遍历完之后的数据修改方法
knowledgeBase    //知识库象棋棋子的行走的方法封装。
chessInfo        //整局象棋的所有数据,每走一步,这个数据会实时更新,UI显示更具这个数据刷新的。


export class AiPlayData {
  static setting:Setting = new Setting(false,true,true,2)
  static infoSet:InfoSet =new InfoSet()
  static knowledgeBase:KnowledgeBase = new KnowledgeBase()
  static chessInfo:ChessInfo = new ChessInfo
}



有了Info之后就可以对Info进行 循环遍历


下面是AICore的封装


鸿蒙开发之ArkTS并发技术中国象棋-鸿蒙开发者社区


AI               //人机思考下棋循环遍历算法封装
AiPlay           //人机开始下棋的方法
BestMove         //人机棋子移动的最佳位置
KnowledgeBase    //人机棋子移动的知识库
Move             //人机棋子移动的方法
Rule             //人机棋子移动的规则
TransformInfo    //人机棋子信息转换
TransformTable   //人机棋子转换数据


export async function AiPlay(): Promise {
  
  if (AiPlayData.chessInfo.status != 1) return;
  let  depth = AiPlayData.setting.mLevel * 2;
  if (AiPlayData.chessInfo.IsRedGo == true) {
    let  move = AiPlayData.knowledgeBase.readBestMoves(AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, depth);
    if (move.fromPos.y == -1) {
      //move = AI.getBestMove(AiPlayData.chessInfo.piece, true, depth, AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, AiPlayData.infoSet.ZobristInfo);
      move =await AiAsync(AiPlayData.chessInfo.piece, true, depth, AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, AiPlayData.infoSet.ZobristInfo)
      AiPlayData.knowledgeBase.saveBestMove(AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, depth, move);
      blueAfter(move)

    }else {
      blueAfter(move)
    }

  } else {
    let move = AiPlayData.knowledgeBase.readBestMoves(AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, depth);
    if (move.fromPos.y == -1) {
      //move = AI.getBestMove(AiPlayData.chessInfo.piece, false, depth, AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, AiPlayData.infoSet.ZobristInfo);
      move =await AiAsync(AiPlayData.chessInfo.piece, false, depth, AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, AiPlayData.infoSet.ZobristInfo)
      AiPlayData.knowledgeBase.saveBestMove(AiPlayData.chessInfo.ZobristKey, AiPlayData.chessInfo.ZobristKeyCheck, depth, move);
      redAfter(move)

    }else {
      redAfter(move)
    }

  }

}


因为下棋的规则确定了,所以我们每次在获取到棋子时就可以对棋子的下一步进循环遍历,获取最佳走法。


效果图:


鸿蒙开发之ArkTS并发技术中国象棋-鸿蒙开发者社区


import { Rule } from '../AICore/Rule';
import { Pos } from '../Info/Pos';
import { promptAction } from '@kit.ArkUI'
import { AI } from '../AICore/AI';
import { Zobrist } from '../Info/Zobrist';
import { TransformTable } from '../AICore/TransformTable';
import { AiPlay, AiPlayData } from '../AICore/AiPlay';


let  pieceX:number[] = [0,1,2,3,4,5,6,7,8]

let pieceY:number[] = [0,1,2,3,4,5,6,7,8,9]


@Entry
@Component
struct ChessComponent {

  @State bg: Resource[] = [
    $r("app.media.null"), $r("app.media.1"), $r("app.media.2"), r("app.media.4"),r("app.media.6"),r("app.media.8"),$r("app.media.9"),
    $r("app.media.10"), $r("app.media.11"), r("app.media.13"),r("app.media.board"))
          .width(360)
          .width(360)

        Row() {
          ForEach(pieceX, (icon1:number,index1:number ) => {
            Column() {
              ForEach(pieceY, (icon2:number,index2:number ) => {
                Row() {
                  Image(this.bg[AiPlayData.chessInfo.piece[icon2][icon1]])
                    .width(40)
                    .height(40)
                    .onClick(()=>{

                      this.setPlay(index1,index2)

                    })
                }
                .justifyContent(FlexAlign.Center)

              })
            }
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)

      }.width('100%')
      .height("60%")

      Row(){
        Button('悔棋', { type: ButtonType.Normal, stateEffect: true })
          .borderRadius(8)
          .backgroundColor(0x317aff)
          .width(90)
          .height(40)
          .onClick(()=>{

            if (AiPlayData.chessInfo.isMachine == true && AiPlayData.chessInfo.status == 1) {
              promptAction.showToast({
                message: '请等待电脑思考',
                duration: 1000
              });
            }else {
              let cnt = 0;
              let total = 2;
              if (AiPlayData.chessInfo.status == 2 && AiPlayData.chessInfo.isMachine == true) {
                total = 1;
              }
              if (AiPlayData.infoSet.preInfo.length >= total) {
                while (!AiPlayData.infoSet.preInfo.isEmpty()) {
                  let tmp = AiPlayData.infoSet.preInfo.pop();
                  cnt++;
                  try {
                    AiPlayData.infoSet.recallZobristInfo(AiPlayData.chessInfo.ZobristKeyCheck);
                    AiPlayData.chessInfo.setInfo(tmp);
                    AiPlayData.infoSet.curInfo.setInfo(tmp);
                  } catch (error) {
                    console.log('error'+error)
                  }
                  if (cnt >= total) {
                    break;
                  }
                }
              }
            }
            this.bg[0] = r("app.media.chuhan4"))
    .backgroundImageSize(1)
    .justifyContent(FlexAlign.Center)
  }

  setPlay(x: number,y:number){

    if (AiPlayData.chessInfo.status == 1) {

      AiPlayData.chessInfo.Select = [x, y];

      let  realPos:Pos = Rule.reversePos(new Pos(AiPlayData.chessInfo.Select[0], AiPlayData.chessInfo.Select[1]), AiPlayData.chessInfo.IsReverse);

      let  i = realPos.x, j = realPos.y;

      if (i >= 0 && i <= 8 && j >= 0 && j <= 9) {
        if (AiPlayData.chessInfo.IsRedGo == true && AiPlayData.setting.isPlayerRed == true) {
          if (AiPlayData.chessInfo.IsChecked == false) {
            if (AiPlayData.chessInfo.piece[j][i] >= 8 && AiPlayData.chessInfo.piece[j][i] <= 14) {
              AiPlayData.chessInfo.prePos = new Pos(i, j);
              AiPlayData.chessInfo.IsChecked = true;
              AiPlayData.chessInfo.ret = Rule.PossibleMoves(AiPlayData.chessInfo.piece, i, j, AiPlayData.chessInfo.piece[j][i]);
              //console.log("x+y:"+AiPlayData.chessInfo.ret[0]+AiPlayData.chessInfo.ret[1]+AiPlayData.chessInfo.ret[2]+AiPlayData.chessInfo.ret[3]+AiPlayData.chessInfo.ret[4]+AiPlayData.chessInfo.ret[5])
              //playEffect(selectMusic);
            }
          } else {

            if (AiPlayData.chessInfo.piece[j][i] >= 8 && AiPlayData.chessInfo.piece[j][i] <= 14) {
              AiPlayData.chessInfo.prePos = new Pos(i, j);
              AiPlayData.chessInfo.ret = Rule.PossibleMoves(AiPlayData.chessInfo.piece, i, j, AiPlayData.chessInfo.piece[j][i]);
              //playEffect(selectMusic);
            } else if (AiPlayData.chessInfo.ret.some(item => item.x === new Pos(i, j).x && item.y ===  new Pos(i, j).y)) {

              let tmp = AiPlayData.chessInfo.piece[j][i];
              AiPlayData.chessInfo.piece[j][i] = AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x];
              AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x] = 0;

              if (Rule.isKingDanger(AiPlayData.chessInfo.piece, true)) {
                AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x] = AiPlayData.chessInfo.piece[j][i];
                AiPlayData.chessInfo.piece[j][i] = tmp;

                promptAction.showToast({
                  message: '帅被将军',
                  duration: 1000
                });

              } else {

                AiPlayData.chessInfo.IsChecked = false;
                AiPlayData.chessInfo.IsRedGo = false;
                AiPlayData.chessInfo.curPos = new Pos(i, j);
                AiPlayData.chessInfo.updateAllInfo(AiPlayData.chessInfo.prePos, AiPlayData.chessInfo.curPos, AiPlayData.chessInfo.piece[j][i], tmp);

                try {
                  AiPlayData.infoSet.pushInfo(AiPlayData.chessInfo);

                } catch (error) {
                  console.error(error+error);
                }
                //playEffect(clickMusic);
                let  key = 0;
                if (Rule.isKingDanger(AiPlayData.chessInfo.piece, false)) {
                  key = 1;
                }
                if (Rule.isDead(AiPlayData.chessInfo.piece, false)) {
                  key = 2;
                }
                if (key == 1) {

                  promptAction.showToast({
                    message: '将军',
                    duration: 1000
                  });

                } else if (key == 2) {
                  //playEffect(winMusic);
                  AiPlayData.chessInfo.status = 2;

                  promptAction.showToast({
                    message: '红方获得胜利',
                    duration: 1000
                  });

                }

                if (AiPlayData.chessInfo.status == 1) {
                  if (AiPlayData.chessInfo.peaceRound >= 60) {
                    AiPlayData.chessInfo.status = 2;
                    promptAction.showToast({
                      message: '双方60回合内未吃子,此乃和棋',
                      duration: 1000
                    });

                  } else if (AiPlayData.chessInfo.attackNum_B == 0 && AiPlayData.chessInfo.attackNum_R == 0) {
                    AiPlayData.chessInfo.status = 2;

                    promptAction.showToast({
                      message: '双方都无攻击性棋子,此乃和棋',
                      duration: 1000
                    });

                  } else if (AiPlayData.infoSet.ZobristInfo.get(AiPlayData.chessInfo.ZobristKeyCheck) as number >= 4) {
                    AiPlayData.chessInfo.status = 2;

                    promptAction.showToast({
                      message: '重复局面出现4次,此乃和棋',
                      duration: 1000
                    });
                  }
                }

                AiPlayData.chessInfo.isMachine = true;
                this.bg[0] = $r("app.media.null")
                AiPlay().then(()=>{
                  this.bg[0] = $r("app.media.null")
                })
              }
            }
          }
        }else if (AiPlayData.chessInfo.IsRedGo == false && AiPlayData.setting.isPlayerRed == false) {
          if (AiPlayData.chessInfo.IsChecked == false) {
            if (AiPlayData.chessInfo.piece[j][i] >= 1 && AiPlayData.chessInfo.piece[j][i] <= 7) {
              AiPlayData.chessInfo.prePos = new Pos(i, j);
              AiPlayData.chessInfo.IsChecked = true;
              AiPlayData.chessInfo.ret = Rule.PossibleMoves(AiPlayData.chessInfo.piece, i, j, AiPlayData.chessInfo.piece[j][i]);
            }
          } else {
            if (AiPlayData.chessInfo.piece[j][i] >= 1 && AiPlayData.chessInfo.piece[j][i] <= 7) {
              AiPlayData.chessInfo.prePos = new Pos(i, j);
              AiPlayData.chessInfo.ret = Rule.PossibleMoves(AiPlayData.chessInfo.piece, i, j, AiPlayData.chessInfo.piece[j][i]);
              //playEffect(selectMusic);
            } else if (AiPlayData.chessInfo.ret.some(item => item.x === new Pos(i, j).x && item.y ===  new Pos(i, j).y)) {
              let  tmp = AiPlayData.chessInfo.piece[j][i];
              AiPlayData.chessInfo.piece[j][i] = AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x];
              //piece[j][i] = AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x];
              AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x] = 0;
              if (Rule.isKingDanger(AiPlayData.chessInfo.piece, false)) {
                AiPlayData.chessInfo.piece[AiPlayData.chessInfo.prePos.y][AiPlayData.chessInfo.prePos.x] = AiPlayData.chessInfo.piece[j][i];
                AiPlayData.chessInfo.piece[j][i] = tmp;
                //piece[j][i] = tmp;

                promptAction.showToast({
                  message: '将被将军',
                  duration: 1000
                });

              } else {

                AiPlayData.chessInfo.IsChecked = false;
                AiPlayData.chessInfo.IsRedGo = true;
                AiPlayData.chessInfo.curPos = new Pos(i, j);

                AiPlayData.chessInfo.updateAllInfo(AiPlayData.chessInfo.prePos, AiPlayData.chessInfo.curPos, AiPlayData.chessInfo.piece[j][i], tmp);

                try {
                  AiPlayData.infoSet.pushInfo(AiPlayData.chessInfo);
                } catch (error) {
                  console.error(error+error);
                }
                //playEffect(clickMusic);
                let  key = 0;
                if (Rule.isKingDanger(AiPlayData.chessInfo.piece, true)) {
                  key = 1;
                }
                if (Rule.isDead(AiPlayData.chessInfo.piece, true)) {
                  key = 2;
                }
                if (key == 1) {
                  //playEffect(checkMusic);

                  promptAction.showToast({
                    message: '将军',
                    duration: 1000
                  });


                } else if (key == 2) {
                  //playEffect(winMusic);
                  AiPlayData.chessInfo.status = 2;

                  promptAction.showToast({
                    message: '黑方获得胜利',
                    duration: 1000
                  });

                }

                if (AiPlayData.chessInfo.status == 1) {
                  if (AiPlayData.chessInfo.peaceRound >= 60) {
                    AiPlayData.chessInfo.status = 2;

                    promptAction.showToast({
                      message: '双方60回合内未吃子,此乃和棋',
                      duration: 1000
                    });

                  } else if (AiPlayData.chessInfo.attackNum_B == 0 && AiPlayData.chessInfo.attackNum_R == 0) {
                    AiPlayData.chessInfo.status = 2;

                    promptAction.showToast({
                      message: '双方都无攻击性棋子,此乃和棋',
                      duration: 1000
                    });

                  } else if (AiPlayData.infoSet.ZobristInfo.get(AiPlayData.chessInfo.ZobristKeyCheck) as number >= 4) {
                    AiPlayData.chessInfo.status = 2;
                    promptAction.showToast({
                      message: '重复局面出现4次,此乃和棋',
                      duration: 1000
                    });
                  }
                }

                AiPlayData.chessInfo.isMachine = true;

                this.bg[0] = $r("app.media.null")
                AiPlay().then(()=>{
                  this.bg[0] = $r("app.media.null")
                })


              }
            }
          }
        }
      }
    }
  }


}


最后根据棋子行走移动后的信息判断是否将军。


完整项目代码:​​https://gitee.com/stevenllv/MyApplication​


分类
收藏
回复
举报
回复
    相关推荐