Hello!大家好!我们前两次已经完成了对棋子基本走法与吃法的约束,今天我们要加入将军的概念,它牵扯到如下几个规则:
1.己方的王不能走(或吃)到对方棋子威胁到的格子
2.当己方被将军时,必须应对将军,不能走其他的棋
3.当己方一枚棋子挡在对方棋子与己方的王之间时,该棋子不能走开,以至于把王暴露给对方。
我本来想按照一般的思路来写出合法走棋的逻辑,但是后来我意识到还有第三条规则,它成为压倒我代码的最后一根稻草,如果按照普通逻辑来写的话将会非常麻烦,因此我用了另一种方法,就是在走棋的时候不考虑将军的问题,在走完棋之后再检查一下己方是否仍然被将军,如果仍然被将军说明走的棋是违例的,这是我们就要加入悔棋的功能,自动悔棋,返回上一步棋。
首先我们要写两个判断是否被将军的函数。
private static boolean ischecking(MyLabel[][] a) {for(int i=1;i<rows+1;i++) {for(int j=0;j<cols+1;j++) {对每一个格子进行循环MyIcon icon=(MyIcon) a[i][j].getIcon();if(icon!=null&&getname(icon)=="king") {找到有王的那个格子for(int x=1;x<rows+1;x++) {再次对每个格子进行循环for(int y=1;y<cols+1;y++) {MyIcon icon1=(MyIcon) a[x][y].getIcon(); if(icon1!=null&&getside(icon1)!=getside(icon)判断该格子上有棋子且棋子与王不是同一势力&&getside(icon1)==sidetomove&&islegalmove(a[x][y],a[i][j])) { 判断走棋的合法性return true; } }} }} }return false;}
private static boolean ischecking2(MyLabel[][] a) {该函数逻辑同上for(int i=1;i<rows+1;i++) {for(int j=0;j<cols+1;j++) {MyIcon icon=(MyIcon) a[i][j].getIcon();if(icon!=null&&getname(icon)=="king") {for(int x=1;x<rows+1;x++) {for(int y=1;y<cols+1;y++) {MyIcon icon1=(MyIcon) a[x][y].getIcon(); if(icon1!=null&&getside(icon1)!=getside(icon)&&getside(icon1)!=sidetomove&&islegalmove(a[x][y],a[i][j])) { return true;} }} }} }return false;}
大家在看完这两个函数之后可能看不出这两个函数的区别,我在这里给大家解释一下。
第一个函数,是用来判断走完一步棋之后是否还被将军;而第二个函数是用来判断在走棋之前是否被将军。
这两个函数代码上的区别就在于在最里面的一连串判断中第一个函数的条件是getside(icon1)==sidetomove,第二个函数的条件是getside(icon1)!=sidetomove。
大家还记得我们最初设定的move()函数吗?在点击第一下(选中棋子)的时候我们就已经换了sidetomove,打个比方,如果轮到白方走,在选中一枚白方的棋子后,sidetomove就会变成黑方,然后在点击第二下之后白方就完成了走子,这时候我们判断白方是否仍然被将军,就要判断是否有黑方的棋子直接威胁到王,所以getside(icon1)和sidetomove两者之间应该相等。反之,如果要在走棋之前就判断是否被将军的话,两者之间自然是不相等的。
在判断完将军之后,如果仍然被将军,我们就要自动悔棋。因此我们需要设计一个悔棋的函数。悔棋函数大概的逻辑就是,找出前一步棋的起始点和终止点,然后起始点上重新设置上movingpiece,即被移动的棋子。并在终止点上设置上capturedpiece,这样一来如果上一步棋吃了对方的子,现在也可以把对方的子还原回来,即使没有吃子(capturedpiece==null)程序也不会报错。
private static void undo() {
我们前两行是找出之前一步棋的起始点和终止点。这在写到吃过路兵的时候也有用到过,所以在这里不做过多解释。MyLabel a=recordedmove.get(recordedmove.size()-1)[1];终止点MyLabel c=recordedmove.get(recordedmove.size()-1)[0];起始点for(int i=1;i<rows+1;i++) {for(int j=1;j<cols+1;j++) {if(!(i==0&&j==0)) {if(i==c.row&&j==c.col) {labels[i][j].setIcon(movingpiece);把起始点设置上被移动的棋子movingpiece.hasmoved=false;棋子是否移动过设为否定} if(i==a.row&&j==a.col) {labels[i][j].setIcon(capturedpiece);把终止点设置上被吃掉的棋子 }}}}由于之前走棋的时候已经改变了sidetomove,那么在悔棋之后自然要换回来。if(sidetomove=="white") {sidetomove="black";}if(sidetomove=="black") {sidetomove="white";} }
其实这个悔棋的函数已经可以涵盖绝大多数的情况,包括兵升变,都可以用这个函数来进行悔棋。可是他却没有包括吃过路兵的悔棋功能。因为吃过路兵时,对方的兵原先并不在终止的格子上,如果我们依然用这个悔棋的方法,就不能还原对方的兵原来的位置。为了解决这个问题,我们还要设计一个新的函数advancepawn(MyLabel a),目的是在进行基本的悔棋操作时候,自动把对方的兵往前推进一格,这样就可以百分之百还原之前的棋局了。
private static void advancepawn(MyLabel a) {MyIcon icon=(MyIcon) a.getIcon();if(icon!=null) {if(getside(icon)=="white"&&a.col==6) {这是把在第六行的白兵推进到第五行a.setIcon(null);labels[a.row][5].setIcon(icon);}if(getside(icon)=="black"&&a.col==3) {这是把在第三行的黑兵推进到第四行a.setIcon(null);labels[a.row][4].setIcon(icon);}} }
写完advancepawn(MyLabel a)之后我们要对原来的悔棋函数进行改动。
if(i==a.row&&j==a.col) {找到这一行,labels[i][j].setIcon(capturedpiece);把终止点设置上被吃掉的棋子,然后我们要判断上一步棋是否是吃过路兵,具体逻辑如下if(getname(movingpiece)=="pawn"&&Math.abs(c.row-a.row)==1&&getname(capturedpiece)=="pawn") {上一步棋是用己方的兵,吃掉了对方的兵MyLabel b=recordedmove.get(recordedmove.size()-2)[1];找到上上步对方走的棋的终止点if((getside(movingpiece)=="white"&&c.col==4&&b.col==4)||(getside(movingpiece)=="black"&&c.col==5&&b.col==5)) {判断对方是把兵冲到了第二格,黑方白方分别判断advancepawn(labels[i][j]); 是的话,就把兵推进一格}} }
这里大家不用担心对方可能只把兵往前走了一格的情况,因为在这种情况下根本就不会通过吃过路兵的判断,也就不会有随后的悔棋等一系列问题了。
现在我们需要对原先的move()函数作出改动。
找到如下分支
else{ if(islegalmove(movinglabel,jl)) {现在走棋的时候要判断是否合法了if(jl.getIcon()!=null) {capturedpiece=(MyIcon) jl.getIcon();}if(getname(movingpiece)=="pawn"&&enpassant(movinglabel,jl)) {如果满足吃过路兵的条件,就执行吃过路兵的函数eatenpassant(movinglabel,jl);}if(getname(movingpiece)=="pawn"&&(jl.col==1||jl.col==8)) {如果满足兵升变的条件,就执行兵升变的函数promote(jl); }else{这是正常走法jl.setIcon(movingpiece); for(int i=1;i<rows+1;i++) {for(int j=1;j<cols+1;j++) {if((labels[i][j].getBackground()).equals(Color.YELLOW)) { labels[i][j].setIcon(null); labels[i][j].setBackground(colors[(i+j)%2]); }}} if(getname(movingpiece)=="king"||(getname(movingpiece)=="rook")){如果被走动的棋子是王或车,就要给他们标记上已走动,之后判断王车移位的时候要用到movingpiece.hasmoved=true;}} MyLabel[] record=new MyLabel[2];record[0]=movinglabel;record[1]=jl;recordedmove.add(record);ischecking=ischecking2(labels);这一步是用来判断对方是否被将军if(ischecking(labels)) {这一步是判断己方是否仍然被将军,是的话就要自动悔棋undo();System.out.println("illegal move");} } else {如果是非法走棋的话,要提醒玩家,并且把sidetomove和hasmoved两个函数都还原System.out.println("illegal move"); movinglabel.setBackground(colors[(movinglabel.row+movinglabel.col)%2]);if(sidetomove=="white") {sidetomove="black";movingpiece.hasmoved=false;}else if(sidetomove=="black") {sidetomove="white";movingpiece.hasmoved=false;}} }}
这种悔棋的函数,也可以应用到手动悔棋中。只要在菜单栏中加入悔棋这一项就可以了。但是这个函数有一个缺点就是只能悔一步棋。我曾经试图把整个棋盘都深拷贝一遍,但是失败了,在这一点上我还是希望能够得到大神的指导的。
接下来我们就要写最常见的特殊走法————王车移位了,在这里简单地跟大家说一下有关王车移位的规则。
在进行王车移位时,王和车之前都不能动过,经过的格子上不能有其他棋子挡住,王经过的格子以及目标格子不能被对方的棋子威胁到(车无所谓),被将军时也不能移位。
因此我们还需要一个新的函数isthreatened(MyLabel a),用来判断一个格子是否被对方的棋子威胁到
private static boolean isthreatened(MyLabel a) {for(int i=1;i<rows+1;i++) {for(int j=1;j<cols+1;j++) {MyIcon icon=(MyIcon) labels[i][j].getIcon();if(icon!=null) {if(getside(icon)==sidetomove) {注意,在进行了第一次点击(选中棋子后)sidetomove就会变成对方,所以这里要用==来判断if(islegalmove(labels[i][j],a)) {return true;}}} }}return false;}
然后就开始判断王车移位
private static boolean cancastle(MyLabel a,MyLabel b) {MyIcon icon=(MyIcon) a.getIcon();if(ischecking) {被将军时不能移位return false;}if(a.col==8&&getside(icon)=="white"&&!icon.hasmoved) {白棋的情况if(a.row==5&&b.row==7) {白王从原来的位置向右移位MyIcon icon1=(MyIcon) labels[8][8].getIcon();if(getname(icon1)=="rook"&&getside(icon1)=="white"&&!icon1.hasmoved) {判断车是否在它该在的位置if(isthreatened(labels[6][8])) {检查中间的格子是否被威胁到return false;}} return (labels[6][8].getIcon()==null&&labels[7][8].getIcon()==null);检查中间是否有棋子}if(a.row==5&&b.row==3) {这是白王向左边换位,逻辑同上MyIcon icon1=(MyIcon) labels[8][8].getIcon();if(getname(icon1)=="rook"&&getside(icon1)=="white"&&!icon1.hasmoved) {if(isthreatened(labels[6][8])) {return false;}} return (labels[6][8].getIcon()==null&&labels[7][8].getIcon()==null);}return false;}if(a.col==1&&getside(icon)=="black"&&!icon.hasmoved) {黑方的情况,逻辑同上if(a.row==5&&b.row==7) {MyIcon icon1=(MyIcon) labels[8][1].getIcon();if(getname(icon1)=="rook"&&getside(icon1)=="white"&&!icon1.hasmoved) {if(isthreatened(labels[6][1])) {return false;}} return (labels[6][1].getIcon()==null&&labels[7][1].getIcon()==null);}if(a.row==5&&b.row==3) {MyIcon icon1=(MyIcon) labels[1][1].getIcon();if(getname(icon1)=="rook"&&getside(icon1)=="white"&&!icon1.hasmoved) {if(isthreatened(labels[4][1])) {return false;}} return (labels[4][1].getIcon()==null&&labels[3][1].getIcon()==null);}return false;}return false;}
通过了判断之后,我们还需要写一个专门的函数来完成这个过程,就像兵升变和吃过路兵一样
这个函数有点无脑,就是分了白棋黑棋向左向右一共四种情况,挨个进行操作
private static void castle(MyLabel a,MyLabel b) {MyIcon icon=(MyIcon) a.getIcon();if(a.col==8&&b.row==7) {MyIcon icon1=(MyIcon) labels[8][8].getIcon();labels[6][8].setIcon(icon1);第一步,设置好王的图标labels[7][8].setIcon(icon);第二部,设置好车的图标a.setIcon(null);第三步,原来王的位置取消图标labels[8][8].setIcon(null); 原来车的位置取消图标}if(a.col==8&&b.row==3) {第四步MyIcon icon2=(MyIcon) labels[1][8].getIcon();labels[3][8].setIcon(icon);labels[4][8].setIcon(icon2);a.setIcon(null);labels[1][8].setIcon(null); }if(a.col==1&&b.row==7) {MyIcon icon3=(MyIcon) labels[8][1].getIcon();labels[6][1].setIcon(icon3);labels[7][1].setIcon(icon);a.setIcon(null);labels[8][1].setIcon(null);}if(a.col==1&&b.row==3) {MyIcon icon4=(MyIcon) labels[1][1].getIcon();labels[4][1].setIcon(icon4);labels[3][1].setIcon(icon);a.setIcon(null);labels[1][1].setIcon(null);}
justcastled=true;if(ischecking(labels)) {判断是否被将军,是的话自动悔棋uncastle(b);justcastled=false;}for(int i=1;i<rows+1;i++) {把黄色的格子设置成原来的颜色for(int j=1;j<cols+1;j++) {if((labels[i][j].getBackground()).equals(Color.YELLOW)) { labels[i][j].setBackground(colors[(i+j)%2]); }}}
然后是王车移位的特有悔棋函数uncastle(MyLabel a),也是分了四种情况,没有什么技术含量
private static void uncastle(MyLabel a) {MyIcon icon=new MyIcon(paths[7]);MyIcon icon1=new MyIcon(paths[2]);if(a.row==7&&a.col==8) {labels[5][8].setIcon(movingpiece);第一步,还原王的位置labels[7][8].setIcon(null);第二步,取消原来王的位置labels[8][8].setIcon(icon);第三步,还原车的位置labels[6][8].setIcon(null);第四步,取消原来车的位置sidetomove="white";这里多出来了一步,就是还原sidetomove}if(a.row==3&&a.col==8) {labels[5][8].setIcon(movingpiece);labels[3][8].setIcon(null);labels[1][8].setIcon(icon);labels[4][8].setIcon(null);sidetomove="white";}if(a.row==7&&a.col==1) {labels[5][1].setIcon(movingpiece);labels[7][1].setIcon(null);labels[8][1].setIcon(icon1);labels[6][1].setIcon(null);sidetomove="black";}if(a.row==3&&a.col==1) {labels[5][1].setIcon(movingpiece);labels[3][1].setIcon(null);labels[1][1].setIcon(icon1);labels[4][1].setIcon(null);sidetomove="black";}}
然后我们在move()里面加上王车移位的操作
找到这里
if(islegalmove(movinglabel,jl)) {if(jl.getIcon()!=null) {capturedpiece=(MyIcon) jl.getIcon();}if(getname(movingpiece)=="pawn"&&enpassant(movinglabel,jl)) {eatenpassant(movinglabel,jl);}if(getname(movingpiece)=="pawn"&&(jl.col==1||jl.col==8)) {promote(jl); }if(getname(movingpiece)=="king"&&cancastle(movinglabel,jl)) {在这里加上了王车移位的判断及操作castle(movinglabel,jl); }else{——————————————
今天的讲解就到这里了,下次我们会将赢棋与和棋的判断,还会加入自定义棋盘的功能。