JAVA实现围棋提子
围棋中,有一个概念叫做“提子”。要实现提子的功能,就必须要知道提子是什么。根据围棋的规则,普通的19路棋盘有19横19纵的线,线与线交叉叫做点,点上可以落子,一个棋盘共361个点。所以我们可以把抽象成为一个又一个的点,每个点都有三种状态:有白子、有黑子、无子。对于棋盘上的点,我们都可以找到由线连接、并且相邻的两个(角落)、三个(边)或四个点(其它的全部)。当一个点有一个棋子落下时,与这个棋子所在的点所有相邻没有子的点就成为了这个棋子的“气”。如果两枚同色棋子相邻,则它们的气是共享的。当一片相邻的同色棋子的气全部被另一色棋子占据时,则这一片棋子应该被从棋盘上移除,称做“提子”。
要实现提子功能,则一个点必需能够获得相邻点的状态,每一个点都需要四个指向相邻点的指针。在JAVA中,由于对象通过地址传值,即你把一个实例化的对象赋给一个变量时,实际上保存的是对象的地址。那么我们在用JAVA实现Dot类,也就是棋盘的点时,只需要为它添加四个Dot类型的属性,表示相邻的点。然后我们还需要为它添加一个表示状态的属性,具体如下:
public class Dot extends JButton{
public Dot leftDot;
public Dot rightDot;
public Dot upDot;
public Dot downDot;
private int now;//当前状态:-1黑棋0无子1白子
// 点的类型,主要用于界面而非功能实现。
// 这不是这篇文章的重点
private int type;
//这个是是否被检查过的标记,用于提子功能实现,后面会讲到
public boolean checked=false;
}
在创建棋盘时,我们只需要声明一个19*19、Dot类型的二维数组,然后再手动为它们添加上相邻点的指针就可以了~~如果是角落或者靠边的点,直接把不存在的相邻点设置成null即可。
public class GameWindow extends JFrame implements ComponentListener{
Dot[][] dot=new Dot[19][19];
//论如何用JAVA实现窗体不是本文的重点
ControlPanel controlPanel=new ControlPanel();
// 中间省略几十行代码
for(int i=0;i<19;i++){
for(int j=0;j<19;j++){
//this.setTitle("小青棋棋by王贺青(正在绘制棋盘: "+i+","+j+")");
//this.dot[i][j]=new Dot();
this.dot[i][j].leftDot=(i>0)?this.dot[i-1][j]:null;
this.dot[i][j].upDot=(j>0)?this.dot[i][j-1]:null;
this.dot[i][j].rightDot =(i<18)?this.dot[i+1][j]:null;
this.dot[i][j].downDot=(j<18)?this.dot[i][j+1]:null;
//论如何用JAVA实现窗体不是计算机导论的重点
this.dot[i][j].setSize(dotsWidth/20, dotsWidth/20);
this.dot[i][j].setLocation(i*dotsWidth/20, j*dotsWidth/20);
}
}
// 后面省略几十行代码
}
棋盘实现了,接下来就要实现提子功能了。在具体进行提子功能时,我写的程序需要执行的步骤大致有三步:1.检索出没气的子并对相应点做标记2.对被标记的点的状态进行修改,把它们改成无子。3.将所有被标记的点的标记取消。 上述三个步骤都用到了深度优先搜索遍历。深度优先搜索遍历的原理是:从当前结点开始,先检查第一个相邻结点是否符合条件,若不符合条件则检查第二个相邻结点,若符合条件则做一个标记,并对其相邻结点重复上述步骤。若遇到被标记的结点则忽略。 在实现时,深度优先搜索遍历是通过递归函数实现的。这个函数的返回类型为一个boolean类型。符合条件则返回true,不符合条件则返回false。我本来想把这些步骤写出来以便更好地凑够字数,但我发现语言描述不是我的强项。以后我有时间会慢慢完善这篇文章,我先把代码贴出来:
//检查本子与其相连的子是否有气。被检查的子的checked属性通通会被设置为true
public boolean hasArea() {
int checkColor=this.now;
if(this.checked==true){
return false;
}
this.checked=true;
if((this.leftDot!=null&&this.leftDot.getNow()==0)||(this.rightDot !=null&&this.rightDot.getNow()==0)||
(this.upDot!=null&&this.upDot.getNow()==0)||(this.downDot!=null&&this.downDot.getNow()==0)){
return true;
}else if((this.leftDot!=null&&this.leftDot.getNow()!=checkColor)&&(this.rightDot !=null&&this.rightDot.getNow()!=checkColor)&&
(this.upDot!=null&&this.upDot.getNow()!=checkColor)&&(this.downDot!=null&&this.downDot.getNow()!=checkColor)){
return false;
}else {
if(this.leftDot!=null&&this.leftDot.now==checkColor){
if(this.leftDot.hasArea()){
return true;
}
}
if(this.rightDot !=null&&this.rightDot.now==checkColor){
if(this.rightDot.hasArea()){
return true;
}
}
if(this.upDot!=null&&this.upDot.now==checkColor){
if(this.upDot.hasArea()){
return true;
}
}
if(this.downDot!=null&&this.downDot.now==checkColor){
if(this.downDot.hasArea()){
return true;
}
}
return false;
}
}
public void move() {
int checkColor=this.now;
if(this.checked==true){
this.setChess(0);
this.checked=false;
if(this.leftDot!=null&&this.leftDot.getNow()==checkColor){
this.leftDot.move();
}
if(this.rightDot !=null&&this.rightDot.getNow()==checkColor){
this.rightDot.move();
}
if(this.upDot!=null&&this.upDot.getNow()==checkColor){
this.upDot.move();
}
if(this.downDot!=null&&this.downDot.getNow()==checkColor){
this.downDot.move();
}
}
}
public void clearChackedValue() {
int checkColor=this.now;
if(this.checked){
this.checked=false;
if(this.leftDot!=null&&this.leftDot.getNow()==checkColor){
this.leftDot.clearChackedValue();
}
if(this.rightDot !=null&&this.rightDot.getNow()==checkColor){
this.rightDot.clearChackedValue();
}
if(this.upDot!=null&&this.upDot.getNow()==checkColor){
this.upDot.clearChackedValue();
}
if(this.downDot!=null&&this.downDot.getNow()==checkColor){
this.downDot.clearChackedValue();
}
}
}
这里面还有一些坑要注意,比如要检测相邻点是否为null,否则在具体实现时会引发 空指针异常。 最后,这个提子功能在每次落子时都要用到。因为根据围棋规则,子不能落在没有气的点上,除非能提起对手的子。所以在每次落子前,要对落子点或落子点相邻被对手占据的点做气的检测,至于落子功能本身则很简单,只是将点改了个状态。最后我做出来的效果是这样的:
其实还有一些功能,比如循环劫检测、计算有效占据点数(用来计算胜负)等功能,受到本人水平所限(编程与围棋水平都有所不足),未能实现。本人将在有空的时候实现它。