文章目录



Hello!大家好!今天我们来写国际象棋棋子的一些走法和吃法,其中主要包括以下几种规则:

  1. 车走直线,象走斜线,马走日字,后走直线和斜线,王也是走直线和斜线但只能走一格。
  2. 兵一开始可以往前走两格或者一格,但之后只能往前走一格,而且兵不能后退。
  3. 兵吃子的时候只能吃斜上方一格的棋子,其他棋子吃法与走法相同。
  4. 两种兵的特殊走法:
  1. 升变
  2. 吃过路兵

在开始写规则之前,我们要引入两个函数,跟斗兽棋一样,我们要判断出棋子的种类和所属一方。具体的方法我在之前写斗兽棋的时候也有介绍过,就是

private static String getname(MyIcon icon) {判断棋子的种类(不分黑白)
  if(icon.getPath()==paths[0]||(icon.getPath()==paths[1])) {
   return "pawn";
  }
  if(icon.getPath()==paths[2]||(icon.getPath()==paths[7])) {
   return "rook";
  }
  if(icon.getPath()==paths[3]||(icon.getPath()==paths[8])) {
   return "knight";
  }
  if(icon.getPath()==paths[4]||(icon.getPath()==paths[9])) {
   return "bishop";
  }
  if(icon.getPath()==paths[5]||(icon.getPath()==paths[10])) {
   return "queen";
  }
  if(icon.getPath()==paths[6]||(icon.getPath()==paths[11])) {
   return "king";
  }
  return "";
 }
private static String getside(MyIcon icon) {
//判断棋子所属一方
  if(icon.getPath()==paths[0]||icon.getPath()==paths[2]||icon.getPath()==paths[3]
   ||icon.getPath()==paths[5]||icon.getPath()==paths[6]||icon.getPath()==paths[4]) {
   return "black";
  }
  if(icon.getPath()==paths[1]||icon.getPath()==paths[7]||icon.getPath()==paths[8]
    ||icon.getPath()==paths[11]||icon.getPath()==paths[10]||icon.getPath()==paths[9]) {
    return "white";
  } 
  return "";
 }

那我们先来写车的规则

private static boolean rooklegal(MyLabel a,MyLabel b) {
  MyIcon a1=(MyIcon) a.getIcon();起始格子上的棋子
  MyIcon b1=(MyIcon) b.getIcon();目标格子上的棋子(有可能是null)

然后我们把两个格子所在的行与列分别取最小值和最大值

int r1=Math.min(a.row, b.row);
  int r2=Math.max(a.row, b.row);
  int c1=Math.min(a.col, b.col);
  int c2=Math.max(a.col, b.col);

  if(a.row==b.row&&a.col==b.col) {//不能原地不动
   return false;
  }
  if(a.row==b.row) {  //这是在同一列的时候
   for(int i=c1;i<=c2;i++) {//查看他们之间的格子(行数从小到大)
    if(labels[a.row][i]!=a) {//不包括起始格子本身
     if(labels[a.row][i]==b&&b1!=null) {//如果循环到了目标格子,而且目标格子上有棋子的话,就要判断该棋子是否与被走棋子属于同一方。如果是同一方的话就违例了,如果是不同方的话就代表吃掉了对方的棋子。
      if(getside(a1)==getside(b1)) {
       return false;
      }
     }
     else if(labels[a.row][i].getIcon()!=null) {//如果没有循环到目标格子,就检查途径的格子上是否有棋子挡住,有的话也违例了。
      return false;
     }
    }
   }
//   如果一切正常就通过了。
   return true;
  }

//接下来是考虑他们在同一行的情况,跟之前的方法完全相同,在这里不做过多的解释了。
  if(a.col==b.col) {  
   for(int i=r1;i<=r2;i++) {
    if(labels[i][a.col]!=a) {
     if(labels[i][a.col]==b&&b1!=null) {
      if(getside(a1)==getside(b1)) {
       return false;
      }
     }
     else if(labels[i][a.col].getIcon()!=null) {
      return false;
     }
    }
   }
   return true;
  }  

//如果两个格子既不在同一行上也不在同一列上就违例了。
  return false;
 }

接下来写马的规则,马的规则是最简单的规则了,只要判断两个格子行的差距与列的差距是2和1或1和2就行。

private static boolean knightlegal(MyLabel a,MyLabel b) {
  int r=Math.abs(a.row-b.row);//列的差距
  int c=Math.abs(a.col-b.col);  //行的差距
  MyIcon a1=(MyIcon) a.getIcon();
  MyIcon b1=(MyIcon) b.getIcon();
  if((r==1&&c==2)||(r==2&&c==1)) {//判断是否是2和1或1和2
   if(b1!=null) {
    return (getside(a1)!=getside(b1));//接下来判断目标格子上的棋子所属势力
   }
   return true;//如果目标格子为空则通过
  }
  return false;
 }

然后是象的规则,我先是写了一个判断两个格子是否在同一条斜线上的函数。

private static boolean samediagonal(MyLabel a,MyLabel b) {
  return (Math.abs(a.row-b.row)==Math.abs(a.col-b.col));
 }

然后再判断象的规则

private static boolean bishoplegal(MyLabel a,MyLabel b) {
  MyIcon a1=(MyIcon) a.getIcon();
  MyIcon b1=(MyIcon) b.getIcon();
  int r1=Math.min(a.row, b.row);
  int r2=Math.max(a.row, b.row);
  int c1=Math.min(a.col, b.col);
  int c2=Math.max(a.col, b.col);


  if(!samediagonal(a,b)) {判断是否在同一条斜线上
   return false;
  }
  if(r1==r2&&c1==c2) {不能原地不动
   return false;
  }
接下来的循环比较简单粗暴,实在一个被框起来的长方形里循环
  for(int i=r1;i<=r2;i++) {
   for(int j=c1;j<=c2;j++) {
    if(labels[i][j]!=a&&samediagonal(labels[i][j],a)) {被循环到的格子不能是起始格子本身,
    也必须和起始格子再同一斜线上
     if(labels[i][j]==b&&b1!=null) {判断吃子是否合法
      if(getside(a1)==getside(b1)) {
       return false;
      }
     }
     else if(labels[i][j].getIcon()!=null) {判断是否有棋子阻隔
      return false;
     }
    }
   }
  }
  return true;
 }

然后是后的规则,在写完象和车的规则之后,后的规则只不过是取一个并集罢了。没有特殊的代码。

然后是王的规则,在这里还牵扯到了一个特殊走法————王车移位,今天先不介绍他的写法。

private static boolean kinglegal(MyLabel a,MyLabel b) {
  MyIcon a1=(MyIcon) a.getIcon();
  MyIcon b1=(MyIcon) b.getIcon();
  if((Math.abs(a.row-b.row)+Math.abs(a.col-b.col)!=1)&&
    !(Math.abs(a.row-b.row)==1&&samediagonal(a,b))) {  判断是否走到了周围的一个格子
   return cancastle(a,b);如果不是的话去判断是否符合王车移位的条件
  }
  else {   判断吃子是否违规
   if(b1!=null) {
    return (getside(a1)!=getside(b1));
   }
   return true;
  }
 }

最后是兵的规则,虽然兵是最小的棋子,但它的规则却是最复杂的。
首先我们来写兵的走法

private static boolean pawnlegal(MyLabel a,MyLabel b) {
  MyIcon icon=(MyIcon) a.getIcon();
  MyIcon icon1=(MyIcon) b.getIcon();
  if(a.col==7&&getside(icon)=="white") {白兵如果刚开始走动
   if(b.row==a.row&&(b.col==5||b.col==6)) {可以向前走一格到两格
    for(int i=b.col;i<a.col;i++) {查看目标格子和途径格子上是否有棋子
     if(labels[a.row][i].getIcon()!=null) {
      return false;
     }
    }
    return true;
   }
   return pawneatlegal(a,b);如果不是往前走,去判断是否符合吃子的条件
  }
  if(a.col==2&&getside(icon)=="black") {黑兵如果刚开始走动,逻辑同上
   if(b.row==a.row&&(b.col==3||b.col==4)) {
    for(int i=b.col;i>a.col;i--) {
     if(labels[a.row][i].getIcon()!=null) {
      return false;
     }
    }
    return true;
   }
   return pawneatlegal(a,b);
  }
  else {兵如果不在起始的行上,那就只能往前走一格了
   if(a.row==b.row) {
    if(getside(icon)=="white") {
     return(a.col-b.col==1&&icon1==null);
    }
    else return(b.col-a.col==1&&icon1==null);
   }
   else {
    return pawneatlegal(a,b);
   }
  }
 }

接下来是兵的吃法

private static boolean pawneatlegal(MyLabel a,MyLabel b) {
  MyIcon a1=(MyIcon) a.getIcon();
  MyIcon b1=(MyIcon) b.getIcon();
  if(getside(a1)=="white") {白兵的情况,白兵向斜上方吃子的时候,所属的行数应该减一,列数应该加或减一。
   if(a.col-b.col==1&&Math.abs(a.row-b.row)==1) {
    if(b1!=null) {
     return (getside(b1)=="black");
    }
    return enpassant(a,b);如果斜上方没有棋子的话,判断是否符合吃过路兵的条件
   }
   return false;
  }
  if(getside(a1)=="black") {黑兵,逻辑同上
   if(a.col-b.col==1&&Math.abs(a.row-b.row)==1) {
    if(b1!=null) {
     return (getside(b1)=="white");
    }
    return enpassant(a,b);
   }
   return false;
  }
  return false;
 }

然后是过路兵的判断,如果有人不清楚过路兵是个什么概念,我在这里跟大家说一下。就是当一方的兵往前走了三格之后,这是对方旁边一列的兵往前走了两格,这时双方的兵就会在同一行上,原先的一方就可以用普通的兵的吃法来吃掉对方的兵,虽然目标格子上并没有棋子。而且过路兵只能当时吃掉,如果过了一步棋才决定吃就为时已晚。由此可见,在过路兵的判断上我们需要知道前一步棋到底走了什么,以此我在原先的move()函数里面加上了记录走棋的方法,大家可以去看一下我上一期发的代码。
接下来给大家看一下吃过路兵的判断。

private static boolean enpassant(MyLabel a,MyLabel b) {
  if(clickcount>2) {因为第一步棋不可能出现吃过路兵的情况,
  所以要默认为第二步棋以后,这样可以避免下面第一行出现空指针的情况
  
   MyLabel[] record=recordedmove.get(recordedmove.size()-1);
   //这一步是用来找到上一步走棋的记录
  
   MyIcon a1=(MyIcon) a.getIcon();
   MyIcon a2=(MyIcon) record[1].getIcon();
   if(getside(a1)=="white"&&a.col==4) {当轮到白棋走的时候,并且兵在第四行
    if(record[0].col==2&&record[1].col==4&&getname(a2)=="pawn"
     &&record[1].row==b.row&&getside(a2)=="black") {在这里我们要判断
     上一步棋是对方把兵往前走了两格,并且对方兵所处的列与走棋的目标格子
     所处的列相同
     
     return true;
    }
   }
   if(getside(a1)=="black"&&a.col==5) {黑方的判断逻辑同上
    if(record[0].col==7&&record[1].col==5&&getname(a2)=="pawn"
     &&record[1].row==b.row&&getside(a2)=="white") {
     return true;
    }
   }
   return false; 
  }
  return false;
 }

既然吃过路兵的情况特殊,那么他的吃法的编写也是特殊的,我们不能用普遍的方法来完成吃子的过程,我们也需要一个专门的函数来完成这个过程

private static void eatenpassant(MyLabel a,MyLabel b){
  int r=b.row;
  MyIcon a1=(MyIcon) a.getIcon(); 
  if(a.col==4) {这是白棋的情况
   MyIcon b1=(MyIcon) labels[r][4].getIcon();
   b.setIcon(a1);把目标格子设置上要走的棋子
   labels[r][4].setIcon(null);把兵旁边的那个格子上的图片(这里是黑兵)去掉
   capturedpiece=b1;记录被吃掉的子(之后写到悔棋的功能时要用到)
  }
  if(a.col==5) {黑棋的情况,同上
   MyIcon b1=(MyIcon) labels[r][4].getIcon();
   b.setIcon(a1);
   labels[r][5].setIcon(null);
   capturedpiece=b1;
  } 
 }

最后的最后,我们只剩下兵升变了,国际象棋的兵在到达对方的底线后可以变成马,象,车或后。
这个函数跟之前所有函数的区别在于它的逻辑十分简单,可是编写起来却非常复杂,因为我们要引入新的JButton和JFrame

private static void promote(MyLabel jl) {这里的参数是兵升变的那个格子
  JFrame f=new JFrame();设置一个JFrame
  f.setLayout(new FlowLayout());
  f.setSize(250, 250);
  Dimension a=new Dimension(100,100);这是每个棋子代表的JButton的大小
  JButton queen=new JButton();————后
  JButton bishop=new JButton();————象
  JButton knight=new JButton();————马
  JButton rook=new JButton();————车

然后根据兵的颜色给JButton设置上相应的图片
  if(getside(movingpiece)=="white") {
   queen.setIcon(new MyIcon(paths[10]));
   bishop.setIcon(new MyIcon(paths[9]));
   knight.setIcon(new MyIcon(paths[8]));
   rook.setIcon(new MyIcon(paths[7]));
  } 
  if(getside(movingpiece)=="black") {
   queen.setIcon(new MyIcon(paths[5]));
   bishop.setIcon(new MyIcon(paths[4]));
   knight.setIcon(new MyIcon(paths[3]));
   rook.setIcon(new MyIcon(paths[2]));
  }

这里为了方便起见,我把四个JButton放在了一个ArrayList里面,然后进行相同的操作。

ArrayList<JButton> buttons=new ArrayList<>();
  buttons.add(queen);
  buttons.add(bishop);
  buttons.add(knight);
  buttons.add(rook);
  for(int i=0;i<4;i++) {
   int x=i;
   buttons.get(x).setPreferredSize(a);
   buttons.get(x).setBorder(BorderFactory.createLineBorder(Color.BLACK));
   buttons.get(x).addActionListener(new ActionListener() {加上监听器
    @Override
    public void actionPerformed(ActionEvent e) {
     jl.setIcon(buttons.get(x).getIcon());在该格子上设置上相应的图片
   f.dispose();在完成升变之后,这个JFrame就可以关掉了。
    }   
   });
  } 
  f.add(queen);
  f.add(bishop);
  f.add(knight);
  f.add(rook);
  f.setVisible(true);
 }

今天就给大家讲到这里,其实在今天设计走棋的规则中我们还没有引入将军的概念,仅仅是限制了棋子基本的走法。下次我们将引入这一概念,同时也会加入另一大特殊走法————王车移位,以及加入悔棋的功能。