上一篇讲到人人下棋功能,下面讲解其他功能的实现思路。
人机对战
首先使用布尔变量isAI标记当前是人人对战还是人机对战。在点击人机对战按钮后,设置isAI为true。
人机对战的思路:在玩家落子后,遍历整个棋盘,判断每个可落子的位置,以当前位置为原点,分别向八个方向探索,是否有活连或眠连(分一连二连三连四连,眠连表示同色连子后是异色棋子或边界)
如图所示以红圈为原点向八个方向探索,发现有四个方向达成活连,顺时针看分别达成了一连、二连、三连和四连。很显然如果达成了四连,在该点落子就能胜利,因此如果为这四种情况分配权重,应该是依次增加的,且三连四连的权重要比一连二连权重大的多。眠连因为有一头被堵死,所以权重是要比活连整体都小。
我们用字符串表示活连和眠连的几种情况,活连有010,0110,01110,011110,眠连有01,011,0111,01111,同时也考虑到为了不让玩家赢,防止玩家一连二连三连四连,因此要为020,0220,02220,022220,02,022,0222,02222也分配权重,这里用到HashMap存储字符串,并为每个字符串分配合适的分数。
import java.util.HashMap;
public class AIplay {
// 活连:
// 从这个点朝某个方向出发,连到几个同色棋子之后是空棋。代码为010,0110,01110,011110等等。
// 活棋有一、二、三、四连。
// 死连:
// 从这个点朝某个方向出发,连到几个同色棋子之后是空另一方的棋或者边界。代码为01,011,0111,01111等等。
// 死棋有一、二、三、四连。
HashMap<String,Integer> CodeWeight = new HashMap<String,Integer>();
public void addCode(){
//活连
CodeWeight.put("010",10);
CodeWeight.put("0110",50);
CodeWeight.put("01110",2000);
CodeWeight.put("011110",5000);
CodeWeight.put("020",10);
CodeWeight.put("0220",50);
CodeWeight.put("02220",2000);
CodeWeight.put("022220",5000);
//死连
CodeWeight.put("01",5);
CodeWeight.put("011",25);
CodeWeight.put("0111",1000);
CodeWeight.put("01111",2500);
CodeWeight.put("02",5);
CodeWeight.put("022",25);
CodeWeight.put("0222",1000);
CodeWeight.put("02222",2500);
}
public int getCodeWeight(String code){
return CodeWeight.get(code);
}
}
最终是否落子要通过对该点四周八个方向的查找,如果找到匹配我们存入的字符串(符合活连或眠连或玩家达成活连眠连)则加分。
//算法开始遍历棋盘
for(int i=0;i<=ROW;i++){
for(int j=0;j<=COL;j++){
if(chessArr[i][j]==0){ //这个位置首先必须是个空位子才有可能放棋子
int score=0; //分数随棋盘算的位置才更新一次
for(int l=0;l<8;l++) { //8个方向循环
if(i+this.changeLocation[l][0]>ROW || j+this.changeLocation[l][1]>COL || i+this.changeLocation[l][0]<0 || j+this.changeLocation[l][1]<0){
continue;//如果下一颗棋子落在边界外,直接进入下一次循环
}
String strCode="0"; //没出就开始记录
int chessColor = chessArr[i+changeLocation[l][0]][j+changeLocation[l][1]];
if(chessColor!=0) {
for(int k=1;k<=4;k++) {
int R=i+k * changeLocation[l][0];
int C=j + k * changeLocation[l][1];
if (R > ROW || C > COL || R < 0 || C < 0) { //边界==死棋 break
break;
}
if (chessArr[R][C] == chessColor) { //同色,记录
strCode += chessColor;
}
else if(chessArr[R][C] == 0){ //空棋 记录,break
strCode += "0";
break;
}
else
break;
}
}
if(!strCode.equals("0")) {
score += chessHashMap.getCodeWeight(strCode);
}
}
chessWeight[i][j]=score;
}
}
}
评估整个棋盘的空位后,最后分数最大的点被认为是最值得落的点。
int maxWeight=0,maxI=0,maxJ=0;
for (int i=0;i<=ROW;i++){
for(int j=0;j<=COL;j++){
if(chessWeight[i][j]>maxWeight){
maxI=i;
maxJ=j;
maxWeight=chessWeight[i][j];
}
}
}
int[] resArr = new int[2];
resArr[0]=maxI;
resArr[1]=maxJ;
System.out.println("最佳坐标:"+maxI+","+maxJ);
System.out.println("最佳分数:"+maxWeight);
return resArr;
那么如何实现在玩家下棋后自动计算落点落子呢?与人人对战不同的是,人机对战是监听到鼠标按下的动作后做两个事情,一个是在鼠标点击的地方画玩家的棋子,然后调用计算落点的方法,返回最适合落点的坐标,画机器的棋子,相当于点一下画两个棋子。
g.setColor(Color.BLACK);
// 根据行列值 还原 标准坐标
int cx = c * SIZE + X - SIZE / 2;
int cy = r * SIZE + Y - SIZE / 2;
// 绘制圆形
g.fillOval (cx, cy, SIZE, SIZE);
// 绘制完棋子后判断输赢
if(IsWin.isWin (chessArr,r,c)){
msgFrame ( "玩家胜利!");
chessFlag = 0;
return;
}
//根据黑子落点计算白子落点
int[] resArr=fillChesses();
int r1=resArr[0];
int c1=resArr[1];
g.setColor (Color.WHITE);
// 根据行列值 还原 标准坐标
int cx1 = c1 * SIZE + X - SIZE / 2;
int cy1 = r1 * SIZE + Y - SIZE / 2;
// 绘制圆形
g.fillOval (cx1, cy1, SIZE, SIZE);
// 绘制完棋子后判断输赢
if(IsWin.isWin (chessArr,r1,c1)){
msgFrame ("人机胜利!");
chessFlag = 0;
return
}
悔棋
悔棋的实现主要靠一个chess类型的数组,chess是一个具有r,c,chessFlag,index属性的方法,也就是用chess类型的数组记录每个棋子的坐标,棋子颜色,棋子顺序。
public class Chess {
int r, c;
int chessFlag;
int index;
public Chess(int r, int c, int chessFlag, int index){
this.r = r;
this.c = c;
this.chessFlag = chessFlag;
this.index = index;
}
}
Chess[] chessIndexArr = new Chess[(ROW+1)*(COL+1)];
int chessIndex=0;
每下一个棋子都创建一个chess对象存到这个数组中,这样如果悔棋,只要把数组最后一个棋子取出来,根据坐标把chessArr棋盘数组中该点赋值为0表示这个点为空棋,再重新刷新棋盘。注意在人机对战模式下,要一下取出两颗棋子,并且因为在下棋时会将当前落点的权重分数设置为0,所以悔棋后要重新统计并填上该点的权值分数(使用reFillWeight方法实现)。
if(btnstr.equals("悔棋")) {
if(!isAI) {
if(chessIndex<=0||chessFlag==0) {
msgFrame("不能再悔棋了");
return;
}
Chess chess=chessIndexArr[chessIndex-1];//把最后一个棋子取出来
chessArr[chess.r][chess.c]=0;
chessFlag=chess.chessFlag;
chessIndex--;
}else {
if(chessIndex<=0||chessFlag==0) {
msgFrame("不能再悔棋了");
return;
}
Chess chess1=chessIndexArr[chessIndex-1];//把最后两颗棋子取出来
chessArr[chess1.r][chess1.c]=0;
reFillWeight(chess1.r,chess1.c);
chessIndex--;
Chess chess2=chessIndexArr[chessIndex-1];
chessArr[chess2.r][chess2.c]=0;
reFillWeight(chess2.r,chess2.c);
chessFlag=chess2.chessFlag;
chessIndex--;
}
// 刷新
ui.drawBack (g);
ui.drawChess (g);
ui.rfBtn ();
}
回放
棋局回放实现思路:首先把棋盘清空,这一步主要依靠将chessArr棋盘数组清空,然后刷新界面实现。遍历chessIndexArr数组,按顺序取出每一个chess,根据chess的坐标和棋子颜色画出棋子,每画一个停顿1s,直到chess为null说明遍历完,退出循环。
if(btnstr.equals("回放")) {
for(int i=0;i<chessArr.length;i++) {
for(int j=0;j<chessArr[i].length;j++) {
chessArr[i][j]=0;
}
}
// 刷新界面
ui.drawBack (g);
ui.rfBtn ();
for(int i=0;i<chessIndexArr.length;i++) {
Chess chess=chessIndexArr[i];
if(chess==null) {
break;
//chess数组包括棋盘上所有棋子的信息,包括还没下的,如果棋盘没下满,数组就会有空值,遇到空值说明棋盘已经遍历完
}
g.setColor(chess.chessFlag==1?Color.BLACK:Color.WHITE);
//根据行列,还原坐标
int cx = chess.c * SIZE + X - SIZE / 2;
int cy = chess.r * SIZE + Y - SIZE / 2;
g.fillOval(cx, cy, SIZE, SIZE);
try {
Thread.sleep (1000);
} catch (InterruptedException ex) {
throw new RuntimeException (ex);
}
}
}
历史棋局
历史棋局的实现思路:当某一方胜利代表棋局的终结,创建fileInput方法用来将这一局的信息写入本地文件中。
public void fileInput() {
try {
if(!file.exists())
file.createNewFile();
FileWriter out=new FileWriter (file);
BufferedWriter bw= new BufferedWriter(out);
str+="第"+times+"局"+":"+"\r\n";
for(int i=0;i<chessIndexArr.length;i++) {
Chess chess=chessIndexArr[i];
if(chess==null) {
break;
}
str +=chess.toString();
}
str+="\r\n";
bw.write(str);
bw.flush();
bw.close();
times++;
}catch (IOException e1) {
e1.printStackTrace();
}
}
if(IsWin.isWin (chessArr,r,c)){
msgFrame (chessFlag == 1 ? "白棋胜利!!" : "黑棋胜利!!");
chessFlag = 0;
fileInput();
}
当点击历史棋局时读取文件并输出
if(btnstr.equals("历史棋局")) {
if(!file.exists()) {
msgFrame("还没有历史棋局!");
}else {
try {
BufferedReader in=new BufferedReader(new FileReader(file));
line=in.readLine();//一次读取文件中的一行数据
while (line!=null) {
str1+=line+"\r\n";
line=in.readLine();
}
System.out.println(str1);
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
结束游戏
结束游戏主要实现思路:点击结束游戏会清屏,将chessArr数组清空,重新创建chessIndex对象,chessIndex赋值为0,重新刷新界面。
if(btnstr.equals("结束游戏")) {
isAI=false;
chessFlag=0;
//清屏
for(int i=0;i<chessArr.length;i++) {
for(int j=0;j<chessArr[i].length;j++) {
chessArr[i][j]=0;
}
}
chessIndexArr = new Chess[(ROW+1)*(COL+1)];//每次开始一局必须清空上一局的棋盘
chessIndex=0;
//刷新界面,只要刷新就会重新执行paint函数重置棋盘
ui.repaint ();
}
完整代码在下载资源里~