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

技术FireMonkey3D之中国象棋程序,二)制定规则 FireMonkey3D之中国象棋程序(二)制定规则声明:本程序设计参考象棋巫师源码(开发工具dephi 11,建议用delphi 10.3以上

火猴3D中国象棋程序(二)规则制定

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

上一章设计了图形界面,可以轮流下棋。然而,由于中国象棋的规则没有限制,所有棋子都可以在棋盘上自由移动。在这一章,我们开始制定下棋的规则。

1.1、记录局面

在制定规则之前,首先要考虑记录当前的情况,这样才能知道棋子移动后的情况。棋盘由109个格子组成,所以我们用一个二维数组记录形势变化,同时用一个一维数组记录每个棋子的位置:

定义变量

chesbd : array[0.9,0.字节的8];//记录当前游戏,并将其添加到csPieceMove单元的TPieceMove中。

pcPos:array数组[0.31]的TPoint//记录棋子的位置,并以csCommon单位声明。

在startUp函数中,我们初始化chessbd,同时记录初始游戏。与前一章相比,代码略有修改,标记如下:

程序TPieceMove。启动;

定义变量

i:Integer

p :点;

常数

Startpos:array [0.字节=//棋子初始位置的31]

($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);

开始

player :=0;

FillChar(Chessbd,SizeOf(chessbd),32);{-添加代码-}

对于I :=0到31 do

开始

象棋[I]. visible :=False;

国际象棋。ResetRotationAngle

P:=点(startPos[i] shr 4、startPos[i]和$ F);

国际象棋[I]. position . point :=point 3d(P . X-4,P.Y-4.5,-0.16);

国际象棋[I]. visible :=true;

chessbd[P.Y,P . X]:=I;

pcPos[I]:=P;{-添加代码-}

结束;

结束;

移动棋盘后Chessbd会改变。我们定义函数tpiecemove.move piece (s,d:t点):字节;此功能记录移动后的变化:

{棋步棋步}

函数TPieceMove。移动件(s,d :点):字节;

定义变量

sid,did:Byte字节;

开始

did:=chessbd[d.y,d . x];

sid:=chessbd[s.y,s . x];

chessbd[s.Y,s . X]:=32;

chessbd[d.Y,d . X]:=sid;

如果did32,那么

开始

pcPos[did]:=点(9,0);

结束;

pcPos[sid]:=d;

结果:=did;

结束;

1.2、制定规则

现在可以制定规则来限制棋子的移动范围。中国象棋规则:车炮走直线,炮打山,马跳太阳,象飞田,书生斜行,兵进不退。重要的设计理念:

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

兵(卒):不退不进,过河平移,一格一格。伪代码:

如果纵坐标绝对差和横坐标绝对差为1,则返回假;如果不过河,横坐标绝对差=1,则返回假。

马:跳日语单词,马没有孩子。伪码:如果纵坐标绝对差*横坐标绝对差2则返回假;如果没有腿,那就返回离开。如何判断别人的腿?在下图中解释:

从图中不难看出,马水平跳跃时,马眼横坐标为(源点横坐标和目标点横坐标)/2,纵坐标与源点相同;垂直跳跃时,马眼纵坐标为(源点纵坐标和目标点纵坐标)/2,横坐标与源点相同。伪代码:

如果横跳和马眼不动那还真;如果垂直跳跃和马的眼睛不动,那么返回真。

>象(相):走田字,象眼的位置简单得多,坐标【(源点横坐标+目标点横坐标)/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*V0 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;

1.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((KingMV[i].Y+Player shl 2)=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*V0) 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 id32 then
  begin
    pcPos[id]:=d;
  end;
  pcPos[chessbd[s.Y,s.X]]:=s;
end;

1.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(id15))or((P.Y in [7,8,9])and(id16)));
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:TArrayTMoves;
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(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:TArrayTMoves;
  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;

1.5、响应规则

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

下一章将开始AI算法。

本章节源码百度云盘:

中国象棋程序设计(二)制定规则,提取码:1234。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/143788.html

(0)

相关推荐

  • 如何用Python分析热门夺冠球队

    技术如何用Python分析热门夺冠球队如何用Python分析热门夺冠球队,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。2018年,火热的世界杯即将拉开序

    攻略 2021年10月29日
  • Windows激活破解以及office安装破解的示例分析

    技术Windows激活破解以及office安装破解的示例分析Windows激活破解以及office安装破解的示例分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个

    攻略 2021年10月23日
  • 花想容,“云想衣裳花想容”出自哪首诗

    技术花想容,“云想衣裳花想容”出自哪首诗云想衣裳花想容出处:《清平调·其一》  原文:  云想衣裳花想容,春风拂槛露华浓。  若非群玉山头见,会向瑶台月下逢。  译文  云霞是她的衣裳,花儿是她的颜容;春风吹拂栏杆,露珠

    生活 2021年10月29日
  • 40岁女人高贵优雅网名,四十岁的女人最好听的微信名字

    技术40岁女人高贵优雅网名,四十岁的女人最好听的微信名字四十岁的女人一般都比较的成熟,很多事情都会特别成熟的思考,事情处理方式也都比较成熟。那么四十岁的女人有哪些好听的微信昵称,有哪些比较合适的微信名字呢?接下来就和小编

    生活 2021年10月28日
  • 分析js对象的读取速度

    技术分析js对象的读取速度本篇内容介绍了“分析js对象的读取速度”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、访问字

    攻略 2021年11月8日
  • 火花塞清洗,汽车火花塞多久清理一次

    技术火花塞清洗,汽车火花塞多久清理一次质信车服——保障品质火花塞清洗,坚守诚信。本文观点:火花塞不需要清洗,只需要更换。首先,火花塞属于常用保养件,是有使用寿命的,到了时限就要更换,平时只要车辆感觉没什么异常,比如油耗增

    生活 2021年10月23日