- 简述
这是本学期上完Java课后老师给出的课程设计题目,目的是:熟悉与掌握GUI编程;实现五子棋棋盘和棋子的绘制;实现游戏AI以及对二维数组的使用。
- 界面效果图
电脑先行,玩家输赢图:
玩家先行,玩家输赢图:
- 整体设计
界面设计部分
这里实现的是框架的主要界面设计(由4366中的在线五子棋修改而来),除棋盘之外的所有部分都在这里完成,即标签,图片,按钮的添加,框架边框的去除,实现框架边框去除后的拖动事件,按钮的点击事件响应等。(这里用到的图片是在GoBang项目中的image文件夹中,后面用到的音乐是在GoBang项目中的music文件夹中,在编写该程序时由程序员自己添加)
代码如下:
/**
* 2017年6月24日GoBang_main.java我和奥巴马
*/
package GoBang;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
*@author我和奥巴马
*@date 2017年6月24日
* @filename GoBang_main.java
* @descriptionTODO
*/
public class GoBang_main extends JFrame{
finalint X_LOCATION=300;
finalint Y_LOCATION=5;
finalint ROWS=16;
finalint COLS=16;
finalint R_SIZE=40;
int xOld,yOld;
static JLabel jl[]=new JLabel[9];
static boolean FLAG=false;
Icon icWhite=new ImageIcon("image/White.png");
static IconicBlack=new ImageIcon("image/Black.png");
Icon icst=new ImageIcon("image/stop.png");
Icon icpl=new ImageIcon("image/play.png");
String strmu="music"+File.separator+"IF YOU-BIGBANG.wav";
public GoBang_main(){
this.setBounds(X_LOCATION,Y_LOCATION, COLS*R_SIZE+170,ROWS*R_SIZE+90);
this.setLayout(null);
this.getContentPane().setBackground(new Color(51,51,51));
//棋谱面板
GoChessgochess=new GoChess();//中央黄色面板
gochess.setBounds(25,70,COLS*R_SIZE,ROWS*R_SIZE);
this.add(gochess);
//五子棋标签
jl[0]=new JLabel("五子棋",JLabel.CENTER);//中央上方标签
jl[0].setFont(new Font("华文行楷",Font.BOLD,25));//字体
jl[0].setForeground(Color.WHITE);//文本颜色
jl[0].setBounds(25, 20,COLS*R_SIZE, 40);//位置大小
jl[0].setOpaque(true);//设置背景透明
jl[0].setBackground(new Color(156,102,31)); //设置背景色
this.add(jl[0]);
//右边功能
jl[1]=new JLabel(new ImageIcon("image/Computer.png"));//电脑图片
jl[1].setBounds(685, 100, 50, 50);
jl[2]=new JLabel(icBlack);//黑棋
jl[2].setBounds(745, 110, 30, 30);
Fontf=new Font("华文楷体",Font.BOLD,15);//右边文本颜色
jl[3]=new JLabel("电脑黑棋,先行",JLabel.LEFT);
jl[3].setBounds(685, 150, 115, 50);
jl[4]=new JLabel(new ImageIcon("image/User.PNG"));//用户图片
jl[4].setBounds(685, 230, 50, 50);
JLabeljlw=new JLabel(icWhite);//白棋
jlw.setBounds(745, 240, 30, 30);
jl[5]=new JLabel("玩家白棋,后行",JLabel.LEFT);
jl[5].setBounds(685, 280, 115, 50);
jl[6]=new JLabel("开始游戏",JLabel.CENTER);
jl[6].setBounds(685, 360, 115, 30);
jl[6].addMouseListener(new MouseAdapter(){ //是电脑先
publicvoid mouseExited(MouseEvente){ //鼠标移开事件监听
jl[6].setBackground(new Color(186,160,10));
}
publicvoid mouseEntered(MouseEvente){ //鼠标进入事件监听
jl[6].setBackground(new Color(214,200,100));
}
publicvoid mouseClicked(MouseEvente){ //鼠标点击事件监听
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
GoChess.blank(i,j); //棋谱置空
}
}
repaint();
FLAG=true;//给标志给JPanel让其响应鼠标点击落点
jlw.setIcon(icWhite);
jl[2].setIcon(icBlack);
jl[3].setText("电脑黑棋,先行");
jl[5].setText("玩家白棋,后行");
GoChess.sureMove(COLS/2,ROWS/2,GoChess.computerColor);//电脑第一颗棋的位置
GoChess.audio(GoChess.strClick);
repaint();
jl[6].setText("重新开始");//点击开始后,换内容
}
});
jl[7]=new JLabel("玩家先行",JLabel.CENTER);
jl[7].setBounds(685, 430, 115, 30);
jl[7].addMouseListener(new MouseAdapter(){ //玩家先
publicvoid mouseExited(MouseEvente){
jl[7].setBackground(new Color(186,160,10));
}
publicvoid mouseEntered(MouseEvente){
jl[7].setBackground(new Color(214,200,100));
}
publicvoid mouseClicked(MouseEvente){
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
GoChess.blank(i,j); //棋谱置空
}
}
repaint();
FLAG=true;//给标志给JPanel让其响应鼠标点击落点
jlw.setIcon(icBlack);//对应的棋色互换
jl[2].setIcon(icWhite);
jl[3].setText("电脑白棋,后行");//标签提示互换
jl[5].setText("玩家黑棋,先行");
}
});
JLabeljlm=new JLabel(new ImageIcon("image/music.PNG"));//音乐图片
jlm.setBounds(685, 550, 50, 50);
JLabeljlp=new JLabel(icst);//暂停图片
jlp.setBounds(745, 558, 40, 40);
jlp.addMouseListener(new MouseAdapter(){
publicvoid mouseClicked(MouseEvente){
if(e.getClickCount()==1){
jlp.setIcon(icpl);//换成暂停图标
GoChess.audio(strmu);
}
}
});
jl[8]=new JLabel("退出",JLabel.CENTER);//退出标签
jl[8].setBounds(685, 500, 115, 30);
jl[8].addMouseListener(new MouseAdapter(){
publicvoid mouseExited(MouseEvente){
jl[8].setBackground(new Color(186,160,10));
}
publicvoid mouseEntered(MouseEvente){
jl[8].setBackground(new Color(214,200,100));
}
publicvoid mouseClicked(MouseEvente){
System.exit(0);
}
});
for(inti=1;i<9;i++){//加组件
jl[i].setFont(f);
if(i==6||i==7||i==8){
jl[i].setOpaque(true);
jl[i].setBackground(new Color(186,160,10));
//设置边框
jl[i].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, new Color(186,160,10)));
}
this.add(jl[i]);
this.add(jlp);
this.add(jlm);
this.add(jlw);
if(i==3||i==5)
jl[i].setForeground(Color.WHITE);//设置字体白色
}
this.setUndecorated(true);
this.setVisible(true);
this.setResizable(false);
this.addMouseListener(new MouseAdapter(){
publicvoid mousePressed(MouseEvente){ //鼠标压监听
xOld=e.getX();//获得x
yOld=e.getY();//获得y
}
});
this.addMouseMotionListener(new MouseMotionAdapter(){ //框架拖动
publicvoid mouseDragged(MouseEvente){
intxOnScreen=e.getXOnScreen();//获得屏幕上的点x坐标
intyOnScreen=e.getYOnScreen();//获得屏幕上的点y坐标
intxNew=xOnScreen-xOld;//得到的新点x
intyNew=yOnScreen-yOld;//得到的新点y
GoBang_main.this.setLocation(xNew,yNew);//实现框架的拖动
}
});
}
publicstaticvoid main(Stringargs[]){
new GoBang_main();
}
}
五子棋绘制部分
这里包括给出棋谱的绘制(在Jpanel 子类中完成),棋子的绘制,评估函数(给每种情况打分,然后可以统计棋盘上的优劣情况),判断输赢等一系列函数,是整个项目中最重要部分。(绘制棋盘和棋子,棋盘分为15行15列,棋子分黑白两种,用颜色去区分,用paint函数去绘制棋盘和棋子,再用一个2维数组去存储棋盘中的棋子的颜色值(在这里空子为0,黑色为1,白色为2))。
代码如下:
/**
* 2017年6月24日.java我和奥巴马
*/
package GoBang;
import java.io.File;
import java.awt.Color;
importjava.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
importjava.net.MalformedURLException;
import javax.swing.JPanel;
importsun.audio.AudioPlayer;
importsun.audio.AudioStream;
/**
*@author我和奥巴马
*@date 2017年6月24日
* @fileROWSame GoChess.java
* @descriptionTODO
*/
publicclassGoChessextends JPanel{
finalstaticint ROWS=16;
finalstaticint COLS=16;
finalint R_SIZE=40;
staticint BLACK=1;
staticint WHITE=2;
staticint EMPTY=0;
staticint userColor=WHITE;
staticint computerColor=BLACK;
staticint table[][]=newint[COLS+1][ROWS+1];
staticint i,j;
booleanFLAG1=false;
booleanFLAG2=false;
static StringstrClick="music"+File.separator+"sale.wav";
String strSuccess="music"+File.separator+"success.wav";
String strFailure="music"+File.separator+"failure.wav";
public GoChess(){
this.addMouseListener(new MouseAdapter(){
publicvoid mouseClicked(MouseEvente){
intx=e.getX();
inty=e.getY();
if(x>=20&&x<COLS*R_SIZE-20&&y>=20&&y<ROWS*R_SIZE-20){//设置鼠标响应范围==左半边和右半边点击时没反应
i=(x-20)/40+1;//映射成相应的x坐标
j=(y-20)/40+1;//映射成相应的y坐标
}
mouseClick();//添加响应函数
}
});
}
publicvoid mouseClick(){
if(GoBang_main.FLAG){//必须点击《开始游戏》或者《玩家先行》才有效
if(isEmpty(i,j)){
sureMove(i,j,userColor);//用户落子
intyn=isEnd(i,j,userColor);//判断用户赢
if(yn!=0){
FLAG1=true;//响应paint里的画字符串
repaint();
GoBang_main.jl[6].setText("开始游戏");//赢了就把重新开始换为开始游戏
return;
}
repaint();
intcomputer[]=Computer.getNext(computerColor);//电脑落子位置
sureMove(computer[0],computer[1],computerColor);//电脑落子
audio(strClick);//提示电脑落子声音 ===海浪
yn=isEnd(computer[0],computer[1],computerColor);
if(yn!=0){
FLAG2=true;
repaint();
GoBang_main.jl[6].setText("开始游戏");
return;
}
repaint();
}
}
}
publicstaticvoid audio(Stringstr){ //添加音乐函数
FileInputStreamfile=null;
try {
file=new FileInputStream(str);
AudioStreamas=new AudioStream(file);
AudioPlayer.player.start(as);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
publicstaticboolean isEmpty(inti,intj){//判断是否为空
returntable[i][j]==EMPTY;
}
publicstaticvoid sureMove(inti,intj,intcolor){//落棋子
table[i][j]=color;
}
publicstaticvoid blank(inti,intj){//撤回棋子
table[i][j]=EMPTY;
}
publicvoid drawPieces(intcolor,intx,inty,Graphicsg){ //画棋子
if(GoBang_main.jl[2].getIcon()==GoBang_main.icBlack){//电脑黑棋,颜色为1
if(color==BLACK){
g.setColor(new Color(0,0,0));
//左右半径19,上下为39/2
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360); //((x-20)/40+1)*RIZE-19
}
}
else{//电脑白棋,颜色为2
if(color==BLACK){
g.setColor(new Color(255,255,255));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360);
}
}
if(GoBang_main.jl[2].getIcon()!=GoBang_main.icBlack){//电脑白棋,白色
if(color==WHITE){
g.setColor(new Color(0,0,0));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360);
}
}
else{//电脑黑棋,颜色黑
if(color==WHITE){
g.setColor(new Color(255,255,255));
g.fillArc(x*R_SIZE-19,y*R_SIZE-19, 39, 39, 0, 360);
}
}
}
publicvoid paint(Graphicsg){
g.setColor(new Color(208,152,69));
g.fillRect(0,0,COLS*R_SIZE,ROWS*R_SIZE);
g.setColor(Color.DARK_GRAY);
for(inti=1;i<COLS;i++){
g.drawLine(R_SIZE*i,R_SIZE,R_SIZE*i, (ROWS-1)*R_SIZE);//画竖线
}
for(intj=1;j<ROWS;j++){
g.drawLine(R_SIZE,R_SIZE*j,(COLS-1)*R_SIZE,R_SIZE*j);//画横线
}
g.setColor(new Color(0,0,0));
//画五个定位点
g.fillArc(8*R_SIZE-5, 8*R_SIZE-5, 10, 10, 0, 360); //中
g.fillArc(4*R_SIZE-5, 4*R_SIZE-5, 10, 10, 0,360); //左上
g.fillArc(12*R_SIZE-5, 4*R_SIZE-5, 10, 10, 0, 360); //右上
g.fillArc(4*R_SIZE-5, 12*R_SIZE-5, 10, 10, 0, 360); //左下
g.fillArc(12*R_SIZE-5, 12*R_SIZE-5, 10, 10, 0, 360); //右下
for(inti=1;i<=COLS;i++){
for(intj=1;j<=ROWS;j++){
if(!isEmpty(i,j))
drawPieces(table[i][j],i,j,g);//画棋子
}
}
if(FLAG1){
g.setColor(Color.RED);
g.setFont(new Font("隶书",Font.BOLD,50));//红色提示赢
g.drawString("你赢了", 1*R_SIZE-5,8*R_SIZE-5);
audio(strSuccess);//赢了的wav
FLAG1=false;
GoBang_main.FLAG=false;
}
if(FLAG2){
g.setColor(Color.LIGHT_GRAY);
g.setFont(new Font("隶书",Font.BOLD,50));
g.drawString("你输了", 1*R_SIZE-5,8*R_SIZE-5);
audio(strFailure);
FLAG2=false;
GoBang_main.FLAG=false;
}
}
publicstaticint reckon(intcolor) {//评估函数
intdx[] = {1, 0, 1, 1};//右,下,右下,右上
intdy[] = {0, 1, 1, -1};
intans = 0;
for(intx=1;x<ROWS;x++) {
for (inty = 1;y <COLS;y++) {
if (table[x][y] != color)
continue;
intnum[][] =newint[2][10];//计数
for (inti = 0;i < 4; i++) {
intsum = 1;
intflag1 = 0,flag2 = 0; //falg1表示一头死,falg2两头活
inttx =x + dx[i];
intty =y + dy[i];
while (tx>0&&tx<ROWS&&ty>0&&ty<COLS&&table[tx][ty]==color) {
tx +=dx[i];
ty +=dy[i];
++sum;
}
if(tx > 0 &&tx <ROWS &&ty > 0 && ty <COLS &&table[tx][ty] ==EMPTY)
flag1 = 1;
tx =x - dx[i];
ty =y - dy[i];
while (tx > 0 && tx <ROWS &&ty > 0 && ty <COLS &&table[tx][ty] ==color) { //回找
tx-=dx[i];
ty-=dy[i];
++sum;
}
if(tx > 0 &&tx <ROWS&&ty > 0 && ty <COLS &&table[tx][ty] ==EMPTY)
flag2 = 1;
if(flag1 +flag2 > 0)
++num[flag1 +flag2- 1][sum];
}
/*成5:即构成五子连珠
活4:即构成两边均不被拦截的四子连珠
死4:一边被拦截的四子连珠
活3:两边均不被拦截的三字连珠
死3:一边被拦截的三字连珠
活2:两边均不被拦截的二子连珠
死2:一边被拦截的二子连珠*/
//成5
if(num[0][5]>0 ||num[1][5]> 0) //num[0][5]+num[1][5]>0
ans = Math.max(ans, 100000);
//活4 |双死四 |死4活3
else if(num[1][4] > 0||num[0][4]> 1||(num[0][4]> 0 &&num[1][3] > 0))
ans = Math.max(ans, 10000);
//双活3
else if(num[1][3] > 1)
ans = Math.max(ans, 5000);
//死4
else if(num[0][4] > 0)
ans = Math.max(ans, 1000);
//死3活3
else if(num[1][3] > 0 &&num[0][3] > 0)
ans = Math.max(ans, 500);
//单活3
else if(num[1][3] > 0)
ans = Math.max(ans, 200);
//双活2
else if(num[1][2] > 1)
ans = Math.max(ans, 100);
//死3
else if(num[0][3] > 0)
ans = Math.max(ans, 50);
//单活2
else if(num[1][2] > 0)
ans = Math.max(ans, 10);
//死2
elseif(num[0][2] > 0)
ans = Math.max(ans, 5);
elseif(num[1][1]>0)
ans=Math.max(ans,1);
}
}
return ans;
}
publicstaticint number(intcolor){//找棋子数
int num=0;
for(int i=1;i<COLS;i++){
for(int j=1;j<ROWS;j++){
if(!isEmpty(i,j)&&table[i][j]==color)
num++;
if(num>0)
return num;
}
}
return num;
}
/*判断局面是否结束 0未结束 1 WHITE赢 2 BLACK赢 */
publicstaticint isEnd(intx,int y,int color) {
intdx[] = {1, 0, 1, 1};
intdy[] = {0, 1, 1, -1};
for (inti = 0;i < 4; i++) {
intsum = 1;
inttx =x + dx[i];
intty =y + dy[i];
while (tx > 0 &&tx <COLS &&ty > 0 && ty <ROWS &&table[tx][ty] ==color) { //当前点去遍历
tx +=dx[i];
ty +=dy[i];
++sum;
}
tx =x - dx[i];
ty =y - dy[i];
while (tx > 0 &&tx <COLS &&ty > 0 && ty <ROWS&&table[tx][ty] ==color) { //返回来遍历
tx -=dx[i];
ty -=dy[i];
++sum;
}
if(sum >= 5)
returncolor;
}
return 0;
}
}
电脑返回走法部分
电脑根据带有alpha-beta剪枝的MinMax搜素算法(递归求解)给出电脑的走法,这里主要是借助评估算法来给出电脑的具体走法。
代码如下:
/**
* 2017年6月25日Computer.java我和奥巴马
*/
package GoBang;
import java.util.Random;
/**
*@author我和奥巴马
*@date 2017年6月25日
* @filename Computer.java
* @descriptionTODO
*/
publicclass Computer {
staticint depth=1;
staticint computerColor=GoChess.BLACK;
/*alpha_beta剪枝搜索,寻找着点
Alpha,即搜索到的最好值,任何比它更小的值就没用了,因为策略就是知道Alpha的值,任何小于或等于Alpha的值都不会有所提高
Beta,即对于对手来说最坏的值。这是对手所能承受的最坏的结果,因为我们知道在对手看来,他总是会找到一个对策不比Beta更坏的。
如果搜索过程中返回Beta或比Beta更好的值,那就够好的了,走棋的一方就没有机会使用这种策略了*/
publicstaticint alpha_betaFind(intdepth,intalpha,intbeta,intcolor,intx,inty){
if(depth>Computer.depth||GoChess.isEnd(x,y,color%2+1)!=0){
intans =GoChess.reckon(computerColor)-GoChess.reckon(computerColor%2+1);
if(depth%2==0)
ans=-ans;
return ans;
}
for(inti=1;i<GoChess.COLS;i++){
for(intj=1;j<GoChess.ROWS;j++){
if(!GoChess.isEmpty(i,j))
continue;
GoChess.sureMove(i,j,color);
intval=-alpha_betaFind(depth+1,-beta ,-alpha,color%2+1,i,j);//ans的值给val
GoChess.blank(i,j);
if(val>=beta)
returnbeta;//返回比beta好的值val=(-ans)>=-beta====beta<=-val //所以加个 -号
if(val>alpha)
alpha=-val;//返回比alpha更坏的值val=(-ans)<-alpha====val>alpha
}
}
return alpha;
}
publicstaticint[] getNext(intcolor){
intrel[]=newint[2];
int ans=-100000000;
Randomrandom=new Random(47);
if(GoChess.number(GoChess.BLACK)<1){
if(GoChess.table[GoChess.COLS/2][GoChess.ROWS/2]!=computerColor){//电脑后手需定位
if(GoChess.isEmpty(GoChess.COLS/2,GoChess.ROWS/2)){//中点
rel[0]=GoChess.COLS/2;
rel[1]=GoChess.ROWS/2;
}
else{
rel[0]=GoChess.COLS/2+1;//向右占位
rel[1]=GoChess.ROWS/2;
}
}
}else{
for(intx=1;x<GoChess.COLS;x++){
for(inty=1;y<GoChess.ROWS;y++){
if(!GoChess.isEmpty(x,y))
continue;
GoChess.sureMove(x,y, color); //黑棋落子
intval=-alpha_betaFind(0,-100000000,100000000,color%2+1,x,y);//判断白棋局面
intran=random.nextInt(100);//100是不包含在内的,只产生0~100之间的数
if(val>ans||val==ans&&ran>50){//val(-递归返回值)<-ans=====val>ans ||ans一直被刷新
ans=val;
rel[0]=x;
rel[1]=y;
}
GoChess.blank(x,y);
}
}
}
return rel;
}
}
想要看源码的小伙伴点这里
注:
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
报错解决办法如下:
Windows->preference->java->complier->errors/warning->deprecated and restricted API把 Forbidden reference 的Error改成warning 即可
- 总结
其实这次是对自己的一个很大的挑战,因为以前只是随便了解了一下AI,现在自己有在没有经验的情况下自己编写代码来实现人机对弈,实在是一次很大的进步。由于能力有限,只能实现简单模式的人机对弈,在默认情况下,电脑的第一个落子是固定的,玩家多次对战后能基本掌握电脑的走法,所以这是本程序的一个漏洞,电脑没有每次随机出招,没有记忆功能和自学习功能,导致这只是一个初级的AI游戏。后续的话,我打算自己再次去探索AI编程,以及寻找更好的算法来支撑我的程序,让玩家体验一个更智能的五子棋游戏。