Java实现五子棋效果
要利用Java制作一个简单的五子棋程序,大致分为如下几步
1.绘制棋盘。
2.添加一些如按钮类的组件。
3.让棋子落在网格线的交叉点上。
4.棋子重绘。
5.实现悔棋功能。
6.判断输赢机制。
7.实现人机对战。
接下来我们就按照这个思路来一步步实现五子棋。
一. 绘制棋盘
由于每一次运行程序系统会自动调用paint函数,我们只需要直接把棋盘的绘制代码写在重写后的paint函数内即可。如下
public void paint(Graphics g){
super.paint(g);
//绘制棋盘
for(int i=0;i<LINE;i++){
g.drawLine(X1,Y1+i*SIZE,X1+(LINE-1)*SIZE,Y1+i*SIZE);
g.drawLine(X1+i*SIZE,Y1,X1+i*SIZE,Y1+(LINE-1)*SIZE);
}
这其中的SIZE等都是常量,并且将它们设计成接口方便改变棋盘的尺寸等
如下:
public interface wuzi {
public static final int X1=160; //棋子左上角顶点的横纵坐标
public static final int Y1=120;
public static final int SIZE=40; //网格边长
public static final int LINE=18; //所画线条数量
public static final int CHESS=40; //棋子直径
}
二.添加组件
下面这段代码是展示五子棋界面的函数:
public void showUI(){
ChessUI jf=new ChessUI();
jf.setSize(1000,1000);
jf.setTitle("五子棋"); //设置标题
jf.setLocationRelativeTo(null); //设置居中
jf.setDefaultCloseOperation(3); //设置程序可关闭
FlowLayout flow=new FlowLayout(); //采用流式布局
jf.setLayout(flow);
ChessMouse mouse =new ChessMouse();
//添加按钮组件
String[] name={"黑棋先手","白棋先手","重新开始","悔棋","人机对战","游戏说明"};
for(int i=0;i<6;i++){
JButton jbu=new JButton(name[i]);
jf.add(jbu);
jbu.addActionListener(mouse); //给按钮添加动作监听器
}
jf.setVisible(true); //设置可见
Graphics g=jf.getGraphics(); //获取画笔
jf.addMouseListener(mouse); //添加鼠标监听器
mouse.g=g;
jf.mouse= mouse; //传数组数据
mouse.ui=jf; //传棋盘对象
}
三.让棋子落在网格的交叉点上
在这方面我们的思路是一个格子分为四等份,当然由于需要横竖两次划分,所以需要写两次判断,如下:
if((x1-X1)%SIZE>SIZE/2){
xx=(x1-X1)/SIZE+1;
}else{
xx=(x1-X1)/SIZE;
}
if((y1-Y1)%SIZE>SIZE/2){
yy=(y1-Y1)/SIZE+1;
}else{
yy=(y1-Y1)/SIZE;
}
其中 xx,yy为需要落子的位置坐标。
四.实现棋子重绘
我们的思路是定义一个二维数组chessArray来记录棋局情况,然后由于网格点的坐标实际上是以界面框的左上顶点为坐标原点,以一个像素点为单位的,这样网格点的坐标就会比较大,在上面也可以看到我们将坐标除以一个系数(网格边长),如此一来棋盘的左上顶点的坐标为(0,0),其左边相邻的网格点坐标即为(1,0),以此类推。如此便可以将二维数组的下标作为网格点坐标,然后二位数组所存数据代表棋子,这里我们定义0表示该处没有棋子,1表示该处为黑棋,2表示该处为白棋。每一次下子都更新二维数组中的数据,然后再调用重写之后的paint方法实现重绘。代码如下:
public void paint(Graphics g){
super.paint(g);
//添加背景图片
ImageIcon icon =new ImageIcon("C:\\Users\\Pictures\\Camera Roll\\3.png");
g.drawImage( icon.getImage(),160,120,null);
//重新绘制棋盘
for(int i=0;i<LINE;i++){
g.drawLine(X1,Y1+i*SIZE,X1+(LINE-1)*SIZE,Y1+i*SIZE);
g.drawLine(X1+i*SIZE,Y1,X1+i*SIZE,Y1+(LINE-1)*SIZE);
}
int[][] chessArray=mouse.chessArray;//传入chessArray数组
//重绘棋子
for(int i=0;i<chessArray.length;i++){
for(int j=0;j<chessArray[0].length;j++){
if(chessArray[i][j]==1){
g.setColor(Color.BLACK);
g.fillOval(i*SIZE+X1-CHESS/2,j*SIZE+Y1-CHESS/2,CHESS,CHESS);
}else if(chessArray[i][j]==2){
g.setColor(Color.WHITE);
g.fillOval(i*SIZE+X1-CHESS/2,j*SIZE+Y1-CHESS/2,CHESS,CHESS);
}
}
}
}
其中有一段操作是给棋盘加上背景图片,当然为了让界面更加美观,也可以选择不用fillOval函数而同样采用drawImage函数画出棋子的图片。
五.实现悔棋功能。
有了上面的基础实现悔棋就很方便了。为了记录下棋的顺序,我们需要定义一个一维数组,然后该一维数组中每一个单元必须记录所落棋子的横坐标,纵坐标这两个数据。由此我们可以考虑将该一维数组定义为一个chess类:
public class chess {
public int x,y;
public chess(int x,int y){
this.x=x;
this.y=y;
}
}
该类中包含横纵坐标,然后定义一维数组chessarray ,其大小为LINE*LINE,然后同样每一次落子都要更新其中的数据。悔棋函数如下:
public void huiqi(){
if(start==0){
if(index>0){
chessArray[chessarray[index-1].x][chessarray[index-1].y]=0;
index--; //chessarray数组下标
num--; //棋子数量
ui.repaint(); //重绘棋盘组件让最后下的棋子消失
}
}else if(start==1){
if(index>0){
chessArray[chessarray[index-1].x][chessarray[index-1].y]=0;
chessArray[chessarray[index-2].x][chessarray[index-2].y]=0;
index-=2;
ui.repaint();
}
}
}
值得一提的是在这里写了两个判断,我们定义整型量start来区分当前进行的是人人对战还是人机对战,0表示人人对战,1表示人机对战。然后若为人机对战,则应该一次悔两颗棋(人下的棋和机器人下的棋)。
6.判断输赢机制
这个可以称之为五子棋的核心,我们的思路是每一次下棋都应该进行一次输赢的判断,定义整型变量count记录某一方向上连续与该棋子颜色相同的棋子数量,由于对于单个棋子而言我们需要考虑横竖两种,斜两种共四个方向的棋局情况,这四个方向又以棋子为间断点分为八段。所以应该有四组一共八个循环。如果其中一组遍历后count值为5,则返回true,否则将count初始化为1。
//判断输赢函数
public boolean win(int x,int y){
int count=1;
//横
for(int i=x+1;i<chessArray.length;i++){
if(chessArray[i][y]==chessArray[x][y]){
count++;
}else break;
}
for(int i=x-1;i>=0;i--){
if(chessArray[x][y]==chessArray[i][y]){
count++;
}else break;
}
if(count>=5){
return true;
}else{
count=1;
}
//竖
for(int i=y+1;i<chessArray.length;i++){
if(chessArray[x][y]==chessArray[x][i]){
count++;
}else break;
}
for(int i=y-1;i>=0;i--){
if(chessArray[x][y]==chessArray[x][i]){
count++;
}else break;
}
if(count>=5){
return true;
}else{
count=1;
}
//斜
for(int i=x-1,j=y-1;j>=0&&i>=0;i--,j--){
if(chessArray[x][y]==chessArray[i][j]){
count++;
}else break;
}
for(int i=x+1,j=y+1;i<chessArray.length&&j<chessArray.length;i++,j++){
if(chessArray[x][y]==chessArray[i][j]){
count++;
}else break;
}
if(count>=5){
return true;
}else {
count=1;
}
for(int i=x-1,j=y+1;i>=0&&j<chessArray.length;i--,j++){
if(chessArray[x][y]==chessArray[i][j]){
count++;
}else break;
}
for(int i=x+1,j=y-1;i<chessArray.length&&j>=0;i++,j--){
if(chessArray[x][y]==chessArray[i][j]){
count++;
}else break;
}
if(count>=5){
return true;
}else{
count=1;
}
return false;
}
七.人机对战
为了实现人机对战,人每一次下棋我们都得分析整个棋局的情况,确定AI最应该下棋的位置。在这里介绍一下权值算法,就是首先制定自己的权值表,权值由棋局情况而定,当然这里我们只需要考虑未下过棋子的位置,比如对于某个空位,它的左边有三个连续黑棋,将这种情况定义为2000,上面有两个白棋,将这种情况定义为200,诸如此类,然后将该空位八个方向棋局对应的权值相加,作为此处的权值,通过循环遍历棋盘上每一个空位,将权值赋值给二维数组,最后找出二维数组中权值最大的那个单元的下标,就是AI应该下子的位置。
在这里再介绍一个工具:HaspMap <K,V>, 其中的K,V其实是一对键值对,可以通过K值来获得对应的V值,它最大的特点就是没有顺序。其中的<>表示泛型,泛型即引用类型,包括类,数组和接口。在这里使用时为方便定义K为String类型,V为Integer类型。如在某次搜索竖直向上方向棋局情况时,遇到一个黑子一个白子,则将白子对应的数字2接到黑子对应的1后,每个方向搜索完成后通过字符串获取键值,即权值。
代码如下:
public void AI(){
String code="";
int color=0;
HashMap<String,Integer> hm=new HashMap<>();
//制定权值表
hm.put("1", 20);
hm.put("11", 200);
hm.put("111", 2000);
hm.put("1111", 3000);
hm.put("12", 20);
hm.put("112", 200);
hm.put("1112", 2000);
hm.put("11112", 3000);
hm.put("2", 20);
hm.put("22", 200);
hm.put("222", 2000);
hm.put("2222", 4000);
hm.put("21", 20);
hm.put("221", 200);
hm.put("2221", 1500);
hm.put("22221", 4000);
for(int i=0;i<chessValue.length;i++){
for(int j=0;j<chessValue[0].length;j++){
if(chessArray[i][j]==0){
//遍历横向棋局情况
for(int k=i+1;k<chessArray.length;k++){
if(chessArray[k][j]==0){
break;
}else{
if(color==0){
color=chessArray[k][j];
code+=chessArray[k][j];
}else if(chessArray[k][j]==color){
code+=chessArray[k][j];
}else{
code+=chessArray[k][j];
break;
}
}
}
Integer value=hm.get(code);
if(value!=null){
chessValue[i][j]+=value;
}
code="";
color=0;
for(int k=i-1;k>=0;k--){
if(chessArray[k][j]==0){
break;
}else{
if(color==0){
color=chessArray[k][j];
code+=chessArray[k][j];
}else if(chessArray[k][j]==color){
code+=chessArray[k][j];
}else{
code+=chessArray[k][j];
break;
}
}
}
Integer value1=hm.get(code);
if(value1!=null){
chessValue[i][j]+=value1;
}
code="";
color=0;
//遍历纵向棋局情况
for(int k=j-1;k>=0;k--){
if(chessArray[i][k]==0){
break;
}else{
if(color==0){
color=chessArray[i][k];
code+=chessArray[i][k];
}else if(chessArray[i][k]==color){
code+=chessArray[i][k];
}else{
code+=chessArray[i][k];
break;
}
}
}
Integer value2=hm.get(code);
if(value2!=null){
chessValue[i][j]+=value2;
}
code="";
color=0;
for(int k=j+1;k<chessArray.length;k++){
if(chessArray[i][k]==0){
break;
}else{
if(color==0){
color=chessArray[i][k];
code+=chessArray[i][k];
}else if(chessArray[i][k]==color){
code+=chessArray[i][k];
}else{
code+=chessArray[i][k];
break;
}
}
}
Integer value3=hm.get(code);
if(value3!=null){
chessValue[i][j]+=value3;
}
code="";
color=0;
for(int k=i+1,z=j+1;k<chessArray.length&&z<chessArray.length;k++,z++){
if(chessArray[k][z]==0){
break;
}else{
if(color==0){
color=chessArray[k][z];
code+=chessArray[k][z];
}else if(chessArray[k][z]==color){
code+=chessArray[k][z];
}else{
code+=chessArray[k][z];
break;
}
}
}
Integer value4=hm.get(code);
if(value4!=null){
chessValue[i][j]+=value4;
}
code="";
color=0;
for(int k=i-1,z=j-1;k>=0&&z>=0;k--,z--){
if(chessArray[k][z]==0){
break;
}else{
if(color==0){
color=chessArray[k][z];
code+=chessArray[k][z];
}else if(chessArray[k][z]==color){
code+=chessArray[k][z];
}else {
code+=chessArray[k][z];
break;
}
}
}
Integer value5=hm.get(code);
if(value5!=null){
chessValue[i][j]+=value5;
}
code="";
color=0;
//遍历斜方向棋局情况
for(int k=i-1,z=j+1;k>=0&&z<chessArray.length;k--,z++){
if(chessArray[k][z]==0){
break;
}else{
if(color==0){
color=chessArray[k][z];
code+=chessArray[k][z];
}else if(chessArray[k][z]==color){
code+=chessArray[k][z];
}else {
code+=chessArray[k][z];
break;
}
}
}
Integer value6=hm.get(code);
if(value6!=null){
chessValue[i][j]+=value6;
}
code="";
color=0;
for(int k=i+1,z=j-1;k<chessArray.length&&z>=0;k++,z--){
if(chessArray[k][z]==0){
break;
}else{
if(color==0){
color=chessArray[k][z];
code+=chessArray[k][z];
}else if(chessArray[k][z]==color){
code+=chessArray[k][z];
}else {
code+=chessArray[k][z];
break;
}
}
}
Integer value7=hm.get(code);
if(value7!=null){
chessValue[i][j]+=value7;
}
code="";
color=0;
}
}
}
//搜索chessValue 数组中最大值,记录该值的位置,用来电脑下棋;
int max=chessValue[0][0];
for(int i=0;i<chessValue.length;i++){
for(int j=0;j<chessValue[0].length;j++){
if(chessValue[i][j]>max){
max=chessValue[i][j];
x2=i;
y2=j;
}
}
}
if(chessArray[x2][y2]==0){
if(num%2==0){
g.setColor(Color.WHITE);//定义AI执白子后下
chessArray[x2][y2]=2;
g.fillOval(x2*SIZE+X1-CHESS/2,y2*SIZE+Y1-CHESS/2,CHESS,CHESS);
num++;
chessarray[index++]=new chess(x2,y2);
if(win(x2,y2)==true){ //调用win函数判断输赢
JOptionPane.showMessageDialog(ui, "白棋胜利");
reset(); //重新开始
}
}
}
for(int i=0;i<chessValue.length;i++){
for(int j=0;j<chessValue[0].length;j++){
chessValue[i][j]=0;
}
}
}
虽然这个函数比较长,但是重复的代码部分颇多。至于如何改进,一方面可以丰富完善权值表,调整权值大小;另一方面可以增加对一些特殊情况的判断,比如某时某空位左右两边各有两个黑子,那么就AI就最应该下这个位置了。
以上就是笔者和大家分享的如何用Java实现五子棋了,如有不当,欢迎大家批评指正。