终于开始第一个小项目了哈哈,今天小编来介绍一个如何用Java来实现一个五子棋。那么,我们先来想一想我们的五子棋需要有些什么功能呢???棋盘界面的实现
人人对战、人机对战
开始新游戏、认输、悔棋的操作
上述的就是我们的五子棋的大致功能了,下面我们来详细介绍一些细节的实现。
ps:由于各个类之间会有交错,所以为了大家有一个较为清晰的思路,下面我就按照我定义的不同类来给大家介绍了。
一、实现棋盘界面类
这个类主要包括了棋盘界面的实现、棋盘和棋子的重绘方法以及我们的监听器的添加。下面先贴出代码:
package goBang;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class ChessBorder extends JPanel implements BorderSize{
public void showBorder(){
JFrame frame = new JFrame();
frame.setTitle("我的五子棋");
frame.setSize(1100, 1000);
//设置窗体不可改变大小frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置棋盘背景图片ImageIcon centerIcon = new ImageIcon("C:\\Users\\sunxiaona\\Desktop\\Java\\蓝杰\\Image\\4.jpg");
JLabel centerLabel = new JLabel(centerIcon);
this.add(centerLabel);
//设置容器为边框布局frame.setLayout(new BorderLayout());
//this.setBackground(Color.LIGHT_GRAY);frame.add(this, BorderLayout.CENTER);
//添加按钮面板并设置大小JPanel eastPanel = new JPanel();
eastPanel.setBackground(Color.DARK_GRAY);
Dimension eastPsize = new Dimension(120,1000);
eastPanel.setPreferredSize(eastPsize);
frame.add(eastPanel, BorderLayout.EAST);
//添加标签,使按钮居中JLabel label = new JLabel();
Dimension labelSize = new Dimension(110,300);
label.setPreferredSize(labelSize);
eastPanel.add(label);
//创建监听器对象BorderListener BL = new BorderListener();
//添加鼠标监听器到棋盘面板//this.addMouseListener(BL);//设置按钮的大小Dimension buttonSize = new Dimension(110,50);
String[] btn ={"开始新游戏","认输","悔棋"};
for(int i=0;i
JButton button = new JButton(btn[i]);
button.setPreferredSize(buttonSize);
button.addActionListener(BL);
eastPanel.add(button);
}
String[] item = {"人人对战","人机对战"};
JComboBox JCB = new JComboBox(item);
JCB.setPreferredSize(buttonSize);
eastPanel.add(JCB);
JCB.addActionListener(BL);
//显示可见frame.setVisible(true);
BL.setPanel(this);
BL.setJCB(JCB);
BL.setCHB(this);
}
public void paint(Graphics g){
super.paint(g);
for(int i=0;i
//画横线g.drawLine(X,Y+SIZE*i,X+(ROW-1)*SIZE,Y+SIZE*i);
g.drawLine(X+SIZE*i, Y, X+SIZE*i,Y+SIZE*(COLUM-1));
}
//画棋子for(int i=0;i
for(int j=0;j
if(chessArray[i][j]==1){
g.setColor(Color.BLACK);
g.fillOval(X+i*SIZE-SIZE/2, Y+j*SIZE-SIZE/2, SIZE, SIZE);
}
if(chessArray[i][j]==2){
g.setColor(Color.WHITE);
g.fillOval(X+i*SIZE-SIZE/2, Y+j*SIZE-SIZE/2, SIZE, SIZE);
}
}
}
}
public static void main(String[] args){
ChessBorder CHB = new ChessBorder();
CHB.showBorder();
}
}
这个类小编是直接继承了JPanel类,所以对棋盘面板的操作也就可以直接使用this关键字了。细心的读者可能已经观察到我们的五子棋的对战模式是通过一个复选框操作的,核心代码就是下面的2句了:
String[] item = {"人人对战","人机对战"};
JComboBox JCB = new JComboBox(item);
因为我们的对战模式是一个字符串,所以复选框也就是一个针对字符串的复选框了,一般来说复选框中的内容参数类型都是相同的,不会出现既有 int、又有 String 的情况,所以这个问题大家不必过于纠结。
二、监听器类的实现
监听器这个类可谓是五子棋中最重要的一个类了,它就像一个枢纽,沟通着我们的其他类。并且在这个类中也会实现很多的功能。你们说它重不重要呢?哈哈......
由于这个类中的关系比较复杂,所以我会把它拆分一下来给大家讲解
首先我们贴出实现人人对战中黑白棋交替下的功能,这一部分的代码
public void mouseReleased(MouseEvent e){
x = e.getX();
y = e.getY();
Result result = new Result();
if(panel!=null){
Graphics g = panel.getGraphics();
//抗锯齿处理添加此行后,棋子边缘光滑Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
int index = 0;
int r,c;
for(int i=X;i<=(ROW-1)*SIZE;i=i+SIZE){
for(int j=Y;j<=(COLUM-1)*SIZE;j=j+SIZE){
if((x-i>-SIZE/2&&x-i-SIZE/2&&y-j
r = (i-X)/SIZE;
c = (j-Y)/SIZE;
//画黑棋if(count==0 && chessArray[r][c]!=1 && chessArray[r][c]!=2){
chessArray[r][c] = 1;
g.setColor(Color.BLACK);
g.fillOval(i-SIZE/2, j-SIZE/2, SIZE, SIZE);
count++;//转换到白棋result.r = r;
result.c = c;
//悔棋的棋子huiX = r;
huiY = c;
}
//画白棋if(count==1 && chessArray[r][c]!=1 && chessArray[r][c]!=2){
chessArray[r][c] = 2;
g.setColor(Color.WHITE);
g.fillOval(i-SIZE/2, j-SIZE/2, SIZE, SIZE);
count--;//转换到黑棋result.r = r;
result.c = c;
//悔棋的棋子huiX = r;
huiY = c;
}
}
}
}
//(人人对战判断黑白棋胜利)和(人机对战判断黑棋部分)if(result.WinOrLoss()){
if(chessArray[result.r][result.c]==1){
JOptionPane.showMessageDialog(null, "黑棋获胜!");
}
if(chessArray[result.r][result.c]==2){
JOptionPane.showMessageDialog(null, "白棋胜利!");
}
//重新开始后黑棋先下count = 0;
//游戏结束后设置下拉列表框为可用JCB.setEnabled(true);
//游戏结束后不可继续下棋 (移除鼠标监听器)panel.removeMouseListener(this);
}
在这一部分我们通过一个变量count来实现黑白棋交替下,黑棋下完count变为1,然后白棋开始下,白棋下完后count又变回0,黑棋在下,这样交替以直下下去,知道分出胜负为止。这一部分代码还涉及到悔棋的操作,是由2个变量来控制的。huiX 记录每一次下棋的X坐标,huiY记录每一次下棋的Y坐标,如果点击悔棋按钮的话,chessArray[huiX][huiY] = 0,重新绘制界面即可。
下面是按钮及复选框操作的代码:
public void actionPerformed(ActionEvent e){
String action = e.getActionCommand();
if("开始新游戏".equals(action)){
//清空棋盘for(int i=0;i
for(int j=0;j
chessArray[i][j] = 0;
}
}
//设置JComboBox不可用JCB.setEnabled(false);
panel.addMouseListener(this);
//画的时候获取画布(游戏重新开始后清空画板)Graphics g = panel.getGraphics();
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
CHB.paint(g);
}else if("认输".equals(action)){
if(count==0){
JOptionPane.showMessageDialog(null, "白棋获胜!!!");
}else {
JOptionPane.showMessageDialog(null, "黑棋获胜!!!");
}
//黑棋先下count = 0;
JCB.setEnabled(true);
panel.removeMouseListener(this);
}else if("悔棋".equals(action)){
chessArray[huiX][huiY] = 0;
Graphics g = panel.getGraphics();
//抗锯齿效果Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
if(count==0){
count = 1;
}else {
count = 0;
}
CHB.paint(g);
}else {
String s =(String)JCB.getSelectedItem();
fightType = s;
}
}
我们的监听器类实现了动作监听器和鼠标监听器的接口,我们按钮以及复选框的操作就在这部分代码实现了。点击开始游戏后,会将上一次的棋局清空,同时复选框会被禁用,在游戏结束时复选框重新变为可用的,此时可以选择对战模式。点击认输按钮之后,棋局结束,同时移除面板上的鼠标监听器,下棋的一方失败。每一次棋局结束时,都会移除面板上的鼠标监听器,在开始新游戏时,重新给面板添加鼠标监听器。
此处有一个小办法可以让我们的五子棋提升一下颜值啦,就是下面这2行代码:
//抗锯齿效果
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
在每次获取画布之后加上这2行代码,然后我们的五子棋就会变得光滑无比了。
三 、接口
这一部分时最简单的,主要是定义我们的棋盘行、列数,以及棋子的大小,储存棋子的数组、人机对战时需要使用的权值数组。代码如下:
package goBang;
public interface BorderSize {
int SIZE = 50;
int ROW = 19;
int COLUM = 19;
int X = 30;
int Y = 30;
int[][] chessArray = new int[ROW][COLUM];
int[][] weightArray = new int[ROW][COLUM];
}
为什么我们通过一个接口设置这些变量呢???主要2个原因:通过接口定义,我们其他的类 只要实现这个接口就可以咯,不需要将变量从一个类传递到另外一个类,简化了代码,还降低了出错的可能性。
通过接口定义参数,方便我们以后对代码的修改,只需修改接口中的数据就可以了,而不需要大幅改动。
四、判断输赢的类
这个类比较简单,每下一颗棋子 ,从该位置横向、纵向、左斜、右斜4个方向判断,如果出现5子相连的情况,即为胜利。否则棋局继续。
package goBang;
public class Result implements BorderSize{
boolean flag = false;
int r,c;
public boolean WinOrLoss(){
if(resultH(r,c)>=5||resultS(r,c)>=5||resultL(r,c)>=5||resultR(r,c)>=5){
return !flag;
}else {
return flag;
}
}
//横向判断public int resultH(int r,int c){
int count=1;
//往左走for(int i=c-1;i>=0;i--){
if(chessArray[r][c]==chessArray[r][i]&&chessArray[r][i]!=0){
count++;
}else {
break;
}
}
//往右走for(int i=c+1;i
if(chessArray[r][c]==chessArray[r][i]&&chessArray[r][i]!=0){
count++;
}else {
break;
}
}
return count;
}
//竖向判断public int resultS(int r,int c){
int count=1;
//往上走for(int i=r-1;i>=0;i--){
if(chessArray[r][c]==chessArray[i][c]&&chessArray[i][c]!=0){
count++;
}else {
break;
}
}
//往下走for(int i=r+1;i
if(chessArray[r][c]==chessArray[i][c]&&chessArray[i][c]!=0){
count++;
}else {
break;
}
}
return count;
}
//斜向判断(左上到右下)public int resultL(int r,int c){
int count=1;
//往左走for(int i=r-1,j=c-1;i>=0&&j>=0;i--,j--){
if(chessArray[i][j]==chessArray[r][c]&&chessArray[i][j]!=0){
count++;
}else{
break;
}
}
//往右走for(int i=r+1,j=c+1;i
if(chessArray[i][j]==chessArray[r][c]&&chessArray[i][j]!=0){
count++;
}else {
break;
}
}
return count;
}
//斜向判断(左下到右上)public int resultR(int r,int c){
int count=1;
//往左走for(int i=r+1,j=c-1;i=0;i++,j--){
if(chessArray[i][j]==chessArray[r][c]&&chessArray[i][j]!=0){
count++;
}else {
break;
}
}
//往右走for(int i=r-1,j=c+1;i>=0&&j
if(chessArray[i][j]==chessArray[r][c]&&chessArray[i][j]!=0){
count++;
}else {
break;
}
}
return count;
}
}
五、电脑下棋
哈哈哈!!终于到了大家最感兴趣的部分了,我们如何让电脑像人一样下棋呢?这里就需要一些算法的实现了。主要有以下几种算法:权值算法、博弈树、极大极小值搜索算法。还有一种比较牛逼的:机器学习。如果小编没有记错的话,阿尔法狗应该是采用了机器学习的。那么,小编用的是一种比较基础的算法:权值算法。同学们可以探索一下更深的、更厉害的算法,那么那你的电脑就会很厉害很厉害啦!!!权值算法即:用权值数组来统计电脑下棋在每一个点的胜率,然后将棋子下在胜率最高的位置。在这里,我们使用了HashMap(哈希表),它可以根据一个字符串找到相对应的数字(在这里就是权值了)。
package goBang;
import java.util.HashMap;
public class AI implements BorderSize{
static HashMap map = new HashMap();
static {
//防守map.put("0", 1);
map.put("010",10);
map.put("021", 15);
map.put("0112",20);
map.put("0221",150);
map.put("02221",400);
map.put("01112",500);
map.put("0110", 800);
map.put("022221", 5000);
map.put("01110", 2000);
map.put("011112", 7000);
map.put("02020", 300);
map.put("02220", 5000);
map.put("020", 25);
map.put("0220",280);
map.put("022220",100000);
map.put("01111", 4000);
map.put("02", 5);
}
//判断每个点的权值public void AIplay(){
for(int i=0;i
for(int j=0;j
weightArray[i][j]=0;
}
}
for(int r=0;r
for(int c=0;c
if(chessArray[r][c]==0){//判断该位是否为空位//水平方向String code = "0";//用来记录棋子相连的情况int chess = 0; //记录第一次出现的棋子int number = 0;//记录空位出现的次数//水平向左统计for(int i=c-1;i>=0;i--){
if(chessArray[r][i]==0){//判断是否为空位if(c==i+1){
break;//如果有两个连续的空位,跳出}else if(number==0){//第一次出现空位code = code +chessArray[r][i];//记录棋子相连的情况number++;
}else if(number==1){//第二次出现空位if(chessArray[r][i]==chessArray[r][i+1]){//判断是否又为2个连续的空位break;
}
code = code +chessArray[r][i];
number++;
}else if(number==2){//第三次出现空位if(chessArray[r][i]==chessArray[r][i+1]){//空位连续break;
}
}
}else{//该位是棋子if(chess==0){//第一次出现棋子chess =chessArray[r][i];
code = code +chessArray[r][i];
}else if(chess==chessArray[r][i]){
code = code +chessArray[r][i];
}else {
code = code +chessArray[r][i];
break;
}
}
}
System.out.print(code+" ");
Integer value0 = map.get(code);//Inter 整数类if(value0!=null){
weightArray[r][c] += value0;
}
code = "0";
chess = 0;
number=0;
//水平向右统计for(int i=c+1;i
if(chessArray[r][i]==0){
if(c==i-1){
break;//相邻2空位,跳出}else if(number==0){//第一次出现空位code = code +chessArray[r][i];
number++;
}else if(number==1){第2次出现空位if(chessArray[r][i]==chessArray[r][i-1]){
break;//连续2空位跳出}else{
code = code + chessArray[r][i];
number++;
}
}else if(number==2){//第3次出现空位if(chessArray[r][i]==chessArray[r][i-1]){
break;
}
}
}else {//该位位棋子if(chess==0){
chess = chessArray[r][i];
code = code +chessArray[r][i];
}else if(chess==chessArray[r][i]){
code = code +chessArray[r][i];
}else {//碰到不同颜色的棋子跳出循环code = code +chessArray[r][i];
break;
}
}
}
System.out.print(code+" ");
Integer value1 = map.get(code);//Inter 整数类if(value1!=null){
weightArray[r][c] += value1;
}
//垂直方向code = "0";
chess = 0;
number=0;
//垂直向上for(int i=r-1;i>=0;i--){
if(chessArray[i][c]==0){
if(r==i+1){
break;//连续2个空位跳出循环}else if(number==0){//第一次出现空位code = code +chessArray[i][c];
number++;
}else if(number==1){//第2次出现空位if(chessArray[i][c]==chessArray[i+1][c]){
break;
}else {
code = code +chessArray[i][c];
number++;
}
}else if(number==2){//第3次出现空位if(chessArray[i][c]==chessArray[i+1][c]){
break;
}
}
}else {
if(chess==0){
chess = chessArray[i][c];
code = code +chessArray[i][c];
}else if(chess==chessArray[i][c]){
code = code +chessArray[i][c];
}else {
code = code +chessArray[i][c];
break;
}
}
}
System.out.print(code+" ");
Integer value2 = map.get(code);//Inter 整数类if(value2!=null){
weightArray[r][c] += value2;
}
code = "0";
chess = 0;
number=0;
//垂直向下for(int i=r+1;i
if(chessArray[i][c]==0){
if(r==i-1){
break;
}else if(number==0){
code = code +chessArray[i][c];
number++;
}else if(number==1){
if(chessArray[i][c]==chessArray[i-1][c]){
break;
}else {
code = code +chessArray[i][c];
}
}else if(number==2){
if(chessArray[i][c]==chessArray[i-1][c]){
break;
}
}
}else {
if(chess==0){
chess = chessArray[i][c];
code = code +chessArray[i][c];
}else if(chess==chessArray[i][c]){
code = code +chessArray[i][c];
}else {
code = code +chessArray[i][c];
break;
}
}
}
Integer value3 = map.get(code);//Inter 整数类if(value3!=null){
weightArray[r][c] += value3;
//}//左斜code = "0";
chess = 0;
number=0;
//往左上int i,j;
for(i=r-1,j=c-1;i>=0&&j>=0;i--,j--){
if(chessArray[i][j]==0){
if(r==i+1&&c==j+1){
break;
}else if(number==0){
code = code +chessArray[i][j];
number++;
}else if(number==1){
if(chessArray[i][j]==chessArray[i+1][j+1]){
break;
}else {
code = code +chessArray[i][j];
number++;
}
}else if(number==2){
if(chessArray[i][j]==chessArray[i+1][j+1]){
break;
}
}
}else {
if(chess==0){
chess=chessArray[i][j];
code = code +chessArray[i][j];
}else if(chess==chessArray[i][j]){
code = code +chessArray[i][j];
}else {
code = code +chessArray[i][j];
break;
}
}
}
System.out.println(code+" ");
Integer value4 = map.get(code);//Inter 整数类if(value4!=null){
weightArray[r][c] += value4;
}
code = "0";
chess = 0;
number = 0;
//往右下for(i=r+1,j=c+1;i
if(chessArray[i][j]==0){
if(r==i-1&&c==j-1){
break;
}else if(number==0){
code = code +chessArray[i][j];
number++;
}else if(number==1){
if(chessArray[i][j]==chessArray[i-1][j-1]){
break;
}else {
code = code +chessArray[i][j];
number++;
}
}else if(number==2){
if(chessArray[i][j]==chessArray[i-1][j-1]){
break;
}
}
}else {
if(chess==0){
chess = chessArray[i][j];
code = code +chessArray[i][j];
}else if(chess==chessArray[i][j]){
code = code +chessArray[i][j];
}else {
code = code +chessArray[i][j];
break;
}
}
}
System.out.println(code+" ");
Integer value5 = map.get(code);//Inter 整数类if(value5!=null){
weightArray[r][c] += value5;
}
//右斜code = "0";
chess = 0;
number = 0;
//往左for(i=r+1,j=c-1;i=0;i++,j--){
if(chessArray[i][j]==0){
if(r==i-1&&c==j+1){
break;
}else if(number==0){
code = code +chessArray[i][j];
number++;
}else if(number==1){
if(chessArray[i][j]==chessArray[i-1][j+1]){
break;
}else {
code = code +chessArray[i][j];
number++;
}
}else if(number==2){
if(chessArray[i][j]==chessArray[i-1][j+1]){
break;
}
}
}else {
if(chess==0){
chess=chessArray[i][j];
code = code +chessArray[i][j];
}else if(chess==chessArray[i][j]){
code = code +chessArray[i][j];
}else {
code = code +chessArray[i][j];
break;
}
}
}
System.out.println(code+" ");
Integer value6 = map.get(code);//Inter 整数类if(value6!=null){
weightArray[r][c] += value6;
}
//往右code = "0";
chess = 0;
number = 0;
for(i=r-1,j=c+1;i>=0&&j
if(chessArray[i][j]==0){
if(r==i+1&&c==j-1){
break;
}else if(number==0){
code = code +chessArray[i][j];
number++;
}else if(number==1){
if(chessArray[i][j]==chessArray[i+1][j-1]){
break;
}else {
code = code +chessArray[i][j];
number++;
}
}else if(number==2){
if(chessArray[i][j]==chessArray[i+1][j-1]){
break;
}
}
}else {
if(chess==0){
chess=chessArray[i][j];
code = code +chessArray[i][j];
}else if(chess==chessArray[i][j]){
code = code +chessArray[i][j];
}else {
code = code +chessArray[i][j];
break;
}
}
}
System.out.println(code+" ");
Integer value7 = map.get(code);//Inter 整数类if(value7!=null){
weightArray[r][c] += value7;
}
}
}
}
}
}
}
我们在一个位置同样分成4个方向统计,让后将权值相加,得到这一点的总权值,然后在监听器类中找出权值最大的点的坐标,在对应位置子下棋即可。需要注意的是,我们的HashMap(哈希表)中的每一种情况的权值一点给要分配合理,只有这样电脑才会合理的下棋。最后小编讲一下我的权值算法的判断方式即思路:首先,判断一个位置是否是一个空位,如果是的话,继续判断它的下一个位置是否为空位,如果出现2个连续的空位,跳出循环,如果不是的话,code = code +chessArray[i][j],即:将坐标为(i,j)的位置对应的字符串相加,如果出现颜色不一样的棋子的话也跳出循环。这部分代码的逻辑性比较强,但是只要理解之后,就是重复工作了,多写几个方向而已。
好了,我们的五子棋项目终于结束了,这个项目的代码量也不是很多,但是一定要注意细节呦,调代码找bug可是很痛苦的哈哈!大神发现错误请多指教呀!!!