FireMonkey3D之中国象棋程序(二)制定规则

这篇具有很好参考价值的文章主要介绍了FireMonkey3D之中国象棋程序(二)制定规则。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。

  本章目标:

  • 实现中国象棋规则

  上一章我们设计了图形界面,可以开始轮流走棋了。但是,由于没有按中国象棋的规则进行限制,所有的棋子都可以在棋盘上随意走动,这章我们开始制定行棋规则。

  2.1 记录局面

  在制定规则之前,我们要先考虑把当前局面记录下来,这样棋子移动后才能知道移动后的局面。棋盘是10×9的格子组成,我们就用二维数组来记录局面变化情况,同时用一个一维数组记录每个棋子的位置:

var
  chessbd:array[0..9,0..8] of Byte; //记录当前棋局,添加到csPieceMove单元的TPieceMove里
  pcPos:array[0..31] of TPoint;     //记录棋子所在位置,声明在csCommon单元 

在startUp函数里,我们将chessbd 初始化,同时记录初始棋局,代码较前章稍作修改,已标记:

procedure TPieceMove.Startup;
var
  i:Integer;
  P:TPoint;
const
  startPos: array[0..31] of Byte =//棋子的初始位置
    ($09, $19, $29, $39, $49, $59, $69, $79, $89, $17, $77, $06, $26, $46, $66, $86,
     $00, $10, $20, $30, $40, $50, $60, $70, $80, $12, $72, $03, $23, $43, $63, $83);
begin
  Player:=0;
  FillChar(Chessbd,SizeOf(chessbd),32);{<--添加代码-->} 
  for I := 0 to 31 do 
  begin
    chess[i].Visible:=False; 
    chess[i].ResetRotationAngle;
    P:=Point(startPos[i] shr 4,startPos[i] and $F);
    chess[i].Position.Point:=Point3D(P.X-4,P.Y-4.5,-0.16);
    chess[i].Visible:=true;
    chessbd[P.Y,P.X]:=i;
    pcPos[i]:=P;{<--添加代码-->} 
   end; 
end;
 

   移动棋子之后,chessbd将发生变化,我们定义function TPieceMove.MovePiece(s,d:TPoint):Byte;这个函数记录移动后的变化:

{搬一步棋}
function TPieceMove.MovePiece(s,d:TPoint):Byte;
var
  sid,did:Byte;
begin
  did:=chessbd[d.y,d.x];
  sid:=chessbd[s.y,s.x];
  chessbd[s.Y,s.X]:=32;
  chessbd[d.Y,d.X]:=sid;
  if did<32 then
  begin
    pcPos[did]:=Point(9,0);
  end;
  pcPos[sid]:=d;
  Result:=did;
end;  

2.2、制定规则

  现在可以制定规则,限制棋子移动的范围。中国象棋走棋规则:车炮走直线,炮打隔山子,马跳日、象飞田,士走斜线,兵有进无退。重要的设计思路:

  根据src源点、dest目标点的纵横坐标差的绝对值判断棋子的移动轨迹是否合理。

  • 兵(卒):有进无退,过河平移,每次一格。伪代码 :

    if  纵坐标绝对差值+横坐标绝对差值<>1 then 返回假;if 未过河 and 横坐标绝对差值=1 then 返回假。

  • 马:跳日字,且马眼无子。伪代码 :if  纵坐标绝对差值*横坐标绝对差值<>2 then 返回假; if 别腿 then 返回假。如何判断别腿?下图讲解: 

FireMonkey3D之中国象棋程序(二)制定规则

 从图解中不难看出,马横跳时,马眼的横坐标是(源点横坐标+目标点横坐标)/2,纵坐标与源点相同;竖跳时,马眼的纵坐标是(源点纵坐标+目标点纵坐标)/2,横坐标与源点相同。伪代码:

    if 横跳 and 马眼无棋 then 返回真;if 竖跳 and 马眼无棋 then 返回真。

 

 

 

 

 

 

 

 

 

 

  • 象(相):走田字,象眼的位置简单得多,坐标【(源点横坐标+目标点横坐标)/2,(源点纵坐标+目标点纵坐标)/2)】,与马的判断相同。伪代码:

         if 已过河  or 纵坐标绝对差值<>2 or  横坐标绝对差值<>2  then 返回假;if 象眼无棋 then 返回真。 

  • 士(仕): 走斜线,且不能出宫。伪代码:if 在宫里 and 纵坐标绝对差值*横坐标绝对差值=1 then 返回真。
  • 帅(将):不出宫,每次一格。伪代码:if 在宫里 and 纵坐标绝对差值+横坐标绝对差值=1 then 返回真。以上几种棋判断走法很简单,就不发代码了,文章最后有完整源码。
  • 车炮:走直线。虽说直线容易判断,但是走棋判断就稍复杂些。我们从源点搜索到目标点,看中间有无棋子挡住,如此判断。代码如下: 
var
  H,V,i,j,csPc:integer;
  hasPc:boolean;
begin
  V:=Abs(d.Y-s.Y);
  H:=Abs(d.X-s.X);   
  if csPc in [PIECE_ROOK,PIECE_CANNON] then
  begin     
    if H*V<>0 then Exit(False);//车炮走直线
      hasPc:=chessbd[d.Y,d.X]<32;
      j:=0;
      if H=0 then //纵向行棋,Left相同,判断src与dest之间是否有棋
        for I :=Min(s.Y,d.Y)+1 to Max(s.Y,d.Y)-1 do
          if chessbd[i,s.X]<32 then
            Inc(j);
      if V=0 then//横向行棋,Top相同,同上
        for I :=Min(s.X,d.X)+1 to Max(s.X,d.X)-1 do
          if chessbd[s.Y,i]<32 then
            Inc(j);
      if (j=0)and((csPc=4)or((csPc=5)and(hasPc=False))) then Exit(True);
      if (j=1)and(csPc=5)and(hasPc) then Exit(True); //炮须隔子吃棋
    end;
end;

  2.3 是否将军 

   中国象棋里能将军的棋子也就4种:兵(卒)、马、炮、车,所以我们判断是否将军时,只要判断对方的这4种棋子是否将军即可。这里我们要为兵(卒)、马、帅(将)定义步长,以便判断。所谓步长,就是指兵、马、帅走一步能到的位置,以原点坐标(0,0)为起点,确定以上棋子能走到位置,兵、帅的走法一致,步长也一样;马八个方向都可以走,还得定义马眼的位置;这里把士、相的步长也一并定义了,后面有用 。车炮步长不定,所以不能定义。代码如下:

  KingMV:   array [0..3] of TPoint=((X:1;Y:0),(X:0;Y:-1),(X:-1;Y:0),(X:0;Y:1)); //将(帅)卒(兵)步长
  KnightMV: array [0..7] of TPoint=((X:-1;Y:-2),(X:-2;Y:-1),(X:-2;Y:1),(X:-1;Y:2),(X:1;Y:-2),(X:2;Y:-1),(X:2;Y:1),(X:1;Y:2)); //马步长
  KnightPin:array [0..7] of TPoint=((X:0;Y:-1),(X:-1;Y:0),(X:-1;Y:0),(X:0;Y:1),(X:0;Y:-1),(X:1;Y:0),(X:1;Y:0),(X:0;Y:1));//马眼
  AdvisorMV:array [0..3] of TPoint=((X:-1;Y:-1),(X:-1;Y:1),(X:1;Y:-1),(X:1;Y:1));//士(仕)步长
  BishopMV: array [0..3] of TPoint=((X:-2;Y:-2),(X:-2;Y:2),(X:2;Y:-2),(X:2;Y:2));//相(象)步长  

  定义步长之后,我们就可以根据步长来判断帅(将)周边是否有以上4种有攻击力的棋子(注意:将帅面对面也是被认为是一种被将军!):

function TPieceMove.IsChecked:Boolean;
var
  dest,src,P:TPoint;
  i,j,D,H,V,K:Integer;
begin
  Result:=False;
  D:=(1-Player) shl 4;//乘16,0或16,代表对面的棋子
  dest:=pcPos[4+Player shl 4];//首先要获取将(帅)的位置,以将(帅)为终点,判断是否被将军
  for I := 0 to 3 do //将(帅)四周有没有兵(卒)
  begin
    src:=kingMV[i]+dest;
    if InBoard(src)and(PcCode[chessbd[src.Y,src.X]]=PIECE_PAWN)and((Player shl 1)=(KingMV[i].Y+1)) then
      Exit(True);
  end;
  for I := 0 to 7 do   //将(帅)是否在马口
  begin
    src:=KnightMV[i]+dest;
    if InBoard(src) then
    begin
      P:= dest+AdvisorMV[i shr 1];//马腿的位置是士的步长
      if (chessbd[src.Y,src.X] in [D+1,D+7])and(chessbd[P.Y,P.X]=32) then
        Exit(True);
    end;
  end;
  for I in [D,D+4,D+8,D+9,D+10] do //车炮将(帅)
  begin
    if pcPos[i].X=9 then  Continue;
    src:=pcPos[i];
    H:=Abs(src.X-dest.X);
    V:=Abs(src.Y-dest.Y);
    K:=0;
    if (H*V<>0) then Continue;
    if H=0 then
    for j :=Min(src.Y,dest.Y)+1 to Max(src.Y,dest.Y)-1 do
      if chessbd[j,src.X]<32 then
      begin
        Inc(K);
      end;
    if V=0 then
      for j :=Min(src.X,dest.X)+1 to Max(src.X,dest.X)-1 do
        if chessbd[src.Y,j]<32 then
          Inc(K);
    if (k=0)and(i in [D,D+4,D+8]) then Exit(True);//车将(帅)
    if (k=1)and(PcCode[i]=PIECE_CANNON) then Exit(True);//炮
  end;
end;

  以上代码也不复杂,不再另外讲解。中国象棋里我们要考虑,如果走棋之后,走棋方处于将军的状态,就不能走这步棋,所以得撤回这步走棋:

{撤销搬一步棋}
procedure TPieceMove.UndoMovePiece(s,d:TPoint;id:Byte);
begin
   chessbd[s.Y,s.X]:=chessbd[d.Y,d.X];
   chessbd[d.Y,d.X]:=id;
  if id<32 then
  begin
    pcPos[id]:=d;
  end;
  pcPos[chessbd[s.Y,s.X]]:=s;
end;

  2.4 是否赢棋

  判断是否赢棋,就是某一方被将军后,无法解将,或是某一方子被剃光头。以此来确定赢棋,设计思路:被将军的一方生成所有的走法,逐一尝试这些走法看是否能解将。红黑双方各有16个棋,除去已经被吃掉的棋,逐一生成走法即可,直接上代码(看注释):

function InBoard(P:TPoint):Boolean;//是否在棋盘上
begin
  Result:=(P.X in [0..8])and(P.Y in [0..9]);
end;
function InPalace(id:Integer;P:TPoint):Boolean;//是否在九宫格内
begin
  Result:=(P.X div 3=1)and(((P.Y in [0,1,2])and(id>15))or((P.Y in [7,8,9])and(id<16)));
end;
function SameSide(d,s:TPoint):Boolean;//是否处于同一阵营
begin
  Result:=(pcMove.chessbd[d.Y,d.X] shr 4)=(pcMove.chessbd[s.Y,s.X] shr 4);
end;
{定义走法,即src和dest}
type TMoves=record
  src,dest:TPoint;
end;
{生成所有走法}
function TPieceMove.GenerateMoves:TArray<TMoves>;
var
  i,j,k,D:Integer;
  srcPt,destPt,P:TPoint;
  mvs:TMoves;
procedure AddMV;
begin
   //scr与dest属于不同阵营,就记下这个走法
   if  Sameside(destPt,srcPt)=False then
   begin
     mvs.src:=srcPt;mvs.dest:=destPt;
     Result:=Result+[mvs];
   end;
end;
begin
   D:=Player shl 4; //找到本方的棋
   for i := D to D+15 do
   begin
     if pcPos[i].X=9 then  Continue;
     srcPt:=pcPos[i];
     case PcCode[i] of
       PIECE_KING: //将(帅)
         for j := 0 to 3 do
         begin
           destPt:=KingMV[j]+srcPt;
           if InPalace(D+4,destPt) then
              AddMV;
         end;
       PIECE_ADVISOR: //士仕
         for P in AdvisorMV do
         begin
           destPt:=P+srcPt;
           if InPalace(I,destPt) then
            AddMV;
         end;
       PIECE_BISHOP: //象相
         for j:=0 to 3 do
         begin
           P:=AdvisorMV[j]+srcPt;//象眼是士的步长
           destPt:=BishopMV[j]+srcPt;
           if InBoard(destPt)and(Cross_River(i,destPt.Y)=False)and(chessbd[P.Y,P.X]=32) then
               AddMV;
         end;
       PIECE_KNIGHT:  //马
         for j := 0 to 7 do
         begin
           destPt:=KnightMV[j]+srcPt;
           if InBoard(destPt)  then
           begin
             P:=KnightPin[j]+srcPt;
             if chessbd[P.Y,P.X]=32 then
               AddMV;
           end;
         end;
       PIECE_ROOK,PIECE_CANNON: //车炮
         for j:= 0 to 3 do
         begin
            P:=KingMV[j];//KingMV的步长为1,以KingMV为起点,向四个方向搜索
            destPt:=srcPt+P;
            k:=0;
            while(InBoard(destPt))do
            begin
               if (chessbd[destPt.Y,destPt.X]=32)and(k=0) then  AddMV
               else
               begin
                 if i in [D,D+8] then//车找到棋子终止搜索
                 begin
                   AddMV;
                   Break;
                 end;
                 if chessbd[destPt.Y,destPt.x]<32 then //计数,炮搜索到棋子后继续向前
                    Inc(k);
                 if k=2 then //找到炮的隔山子终止搜索
                 begin
                   AddMV;
                   Break;
                 end;
               end;
              destPt.Offset(P);
            end;
         end;
       PIECE_PAWN://兵卒
         for j := 0 to 3 do
         begin
           P:=KingMV[j];
           destPt:=P+srcPt;
           if (InBoard(destPt))and((Cross_River(i,destPt.Y)and(P.Y=0))or(P.Y+1=(i shr 4 shl 1))) then
             AddMV;
         end;
     end;
   end;
end;  

  剩下的工作就是逐一走这些走,判断是否仍处于将军状态,再撤销这些走法(为什么用IsMate?纯粹是与象棋巫师一致,实在不明白为什么这样的函数名,我最初用的GameOver):

{是否赢棋}
function TPieceMove.IsMate:Boolean;
var
  MVS:TArray<TMoves>;
  i,id:Integer;
  src,dest:TPoint;
begin
  MVS:=GenerateMoves;
  for I := 0 to High(MVS) do
  begin
    id:=MovePiece(src,dest);
    if not IsChecked then
    begin
      UndoMovePiece(src,dest,id);
      Exit(False);
    end
    else
    UndoMovePiece(src,dest,id);
  end;
  Result:=True;
end;

  2.5  响应规则

  在csBoard事件里添加canMove、MovePiece、IsChecked,IsMate等规则函数即可,见源码。

下一章将开始AI算法。

本章节源码百度云盘:

链接:中国象棋程序设计(二)制定规则

提取码:1234

 文章来源地址https://www.toymoban.com/news/detail-807408.html

到了这里,关于FireMonkey3D之中国象棋程序(二)制定规则的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FireMonkey3D之中国象棋程序设计(四)水平效应、检查重复局面

    声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上版本)。 上一章我们的程序终于会走棋了,不过很多时候它很低能。由于水平线效应,任何变化都只搜索固定的深度。还有,有时它会长将。我们能做哪些改进呢? 本章的目标: 用Zobrist校验码技术

    2024年01月20日
    浏览(38)
  • Qt CMake 中国象棋程序实现

    C++自学精简实践教程 目录(必读) C++数据结构与算法实现(目录) Qt 入门实战教程(目录) 为学习 Qt 的人提供一个合适的有一定难度的综合型练习项目。 在学会写代码之前,先看别人怎么写的代码。深入其中,扩展完善。 最大限度的模拟企业开发的真实场景。 运行效果 中

    2024年02月09日
    浏览(40)
  • 从0开始写中国象棋-创建棋盘与棋子

    考虑到象棋程序,其实就是数据结构与算法实现。 所以和界面相关的QT部分我们先放一放。 我们从控制台版本开始。这样大家更容易接受,也不影响开发。 后面我们会把控制台嫁接到QT上完成完整的游戏,那时候自然就水到渠成了。 中国象棋的棋盘是一个宽9列,长 5+5 = 10

    2024年02月07日
    浏览(37)
  • 中国象棋AI库AlphaZero_ChineseChess

    AlphaZero_ChineseChess是一个基于AlphaZero算法的中国象棋AI库,它是开源的,使用Python语言编写,托管在GitHub上。以下是对AlphaZero_ChineseChess库的详细介绍: 算法原理 AlphaZero_ChineseChess基于AlphaZero算法,这是一种基于自我对弈的强化学习算法,能够让AI自主学习棋局的优劣、评估策略

    2024年02月14日
    浏览(122)
  • C++实现双人中国象棋(一)——算法篇(附完整代码)

    最近突发奇想,要使用C++做一个双人象棋的程序,昨天肝了一天,终于把算法部分完成了,下面把开发过程中的经验分享一下。 开发环境:Visual Studio 2019 语言标准:C++11及以上 纠错:暂无 知识要求: 熟练掌握C++语言面向对象编程的知识(继承,多态) 掌握STL的基本操作 了

    2024年02月04日
    浏览(61)
  • C++900行代码实现中国象棋游戏规则以及相关功能

    本文章通过C++中的900行代码实现中国象棋游戏以及相关功能,主要的内容如下: 1.设置未进入游戏前的主页面; 2.绘制棋盘(如果有刚好尺寸的图片也可直接加载),包括棋盘网格,炮与兵的特殊标记绘制; 3.绘制和创建棋子,并令其初始化在棋盘的相应位置; 4.游戏开始,

    2024年02月09日
    浏览(49)
  • AI人工智能(调包侠)速成之路十五(中国象棋AI网络机器人:AI模型部署)

    神经网络模型动态加解密的技术这个以后再写吧  书接上文: AI人工智能(调包侠)速成之路十四(中国象棋AI网络机器人:AI技术综合应用实现) 神经网络模型的存储格式         我们训练的神经网络就是一堆模拟神经元的参数集合,给(他/她/它)一个输入信息就会得到

    2024年01月23日
    浏览(50)
  • 从0实现基于Alpha zero的中国象棋AI(会分为多个博客,此处讲解蒙特卡洛树搜索)

    ​ 题主对于阿尔法狗的实现原理好奇,加上毕业在即,因此选择中国象棋版的阿尔法zero,阿尔法zero是阿尔法狗的升级版。在完成代码编写的历程中,深刻感受到深度学习环境的恶劣,网络上固然资料繁多,但要么水平不行,不知所云,要么国外课程,门槛过高。因而碰壁良

    2024年02月06日
    浏览(52)
  • html Antv L7 + mapbox 实现3D地图 3D中国地图 不限于中国地图

    echarts的3D地图实在太丑了,还一堆bug 使用阿里的Antv可视化库L7,实现3D地图,底图是mapbox 参考示例:https://l7.antv.antgroup.com/zh/examples/polygon/3d#floatMap 如果不需要底图样式,可把Scene的style设置为blank 直接上代码了,vue的就不说了,项目是html的 mapbox依赖 L7依赖 body元素 实现

    2024年02月14日
    浏览(52)
  • echarts 5.0——3d中国地图和飞线

    echarts 5.0的版本样式语法与4.0及以下的语法有个别差异,使用旧语法浏览器会警告提示。 3d地图常用的实现方法有两种。一种是使用GL来实现,一种是使用叠层和阴影来实现,本文将使用叠层和阴影来实现。先看效果图: 1. 一定要使用 echarts 5.0及以上的版本; 2. echarts 5.0没有

    2024年04月13日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包