Java五子棋小游戏

五子棋可能大家都玩过或者听说过,规则非常简单:双方分别使用黑白两色的棋子,下在棋盘(15*15)直线与横线的交叉点上,先形成5子连线者获胜。
最近,我用Java写了一个五子棋小游戏,现在和大家分享一下。

首先我创建了四个类,分别为:
Java五子棋小游戏_第1张图片
ChessUI,创建窗口,负责游戏的界面;
ChessPosition,包含两个参数,分别为棋子的x,y坐标;
ChessBoardListener,监听器类,当监听到鼠标的动作时,调用ChessBoard中的方法以做出响应。
ChessBoard,负责管理棋子,包括下棋,悔棋,重新开始,判断输赢等。

首先来实现游戏的界面:

public class ChessUI extends JPanel{
	
	Graphics p;
	BufferedImage bgImage;//棋盘的背景图片
	AlphaComposite ac;  //用以设置透明度
	ChessBoard qz = new ChessBoard(this);

	public void initUI() {
		
		JFrame frame = new JFrame();
		frame.setSize(534,614);//设置窗口大小
		frame.setLocationRelativeTo(null);//设置窗口在屏幕居中
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗口即结束程序
		frame.setTitle("五子棋");//设置
		
		//创建鼠标监听器对象
		ChessBoardListener cblisten = new ChessBoardListener(qz);
 
		//创建南边功能面板并添加到窗体的南面
		JPanel southPanel = new JPanel();
		southPanel.setPreferredSize(new Dimension(45,45));
		frame.add(southPanel,BorderLayout.SOUTH);

//		//创建西边功能面板并添加到窗体的西边
//		JPanel westPanel = new JPanel();
//		westPanel.setPreferredSize(new Dimension(45,500));
//		frame.add(westPanel,BorderLayout.WEST);

		//创建中间绘图区域面板并添加窗体的中间
		frame.add(this,BorderLayout.CENTER);
		this.setBackground(Color.white);
		this.addMouseListener(cblisten);//添加鼠标监听器
		
		//设置一个字符数组来存储图形按钮上的文字
		String[] text = {"重新开始","悔棋","认输","人人对战","人机对战"};
		for(int i=0; i<text.length;i++) {
			//创建按钮
			JButton btn = new JButton(text[i]);
			btn.setPreferredSize(new Dimension(90,35));
			//添加按钮到南面板
			southPanel.add(btn);
			btn.addActionListener(cblisten);//给按钮加上动作监听器
		}
		
		try {
			bgImage = ImageIO.read(new File("C:/Users/background.png"));//给棋盘添加背景图片
		} catch (IOException e) {
			e.printStackTrace();
		}
//		ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f);//设置透明度
		frame.setVisible(true);//设置窗体可见		
		//获取画布
		Graphics2D p = (Graphics2D) this.getGraphics();
		p.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
//		((Graphics2D) p).setComposite(ac);//设置透明度
		System.out.println(p);//
		cblisten.setGraphics(p);	
		qz.init();
	}
	
	public void paint(Graphics g) {//重绘,每次缩小放大窗体时棋盘和棋子能够再次被画出来
		super.paint(g);
		g.drawImage(bgImage, 0, 0, 512, 512, this);
			
		if(qz!=null) {
			qz.drawChessBorad(g);
			qz.drawChess(g);
		}		
	}	
	public static void main (String[] args) {		
		ChessUI pl = new ChessUI();		
		pl.initUI();
		
	}
}

值得注意的是,getGraphics这一步要在setVisible后,要不然会报出空指针异常。
在这里插入图片描述
效果如下图所示:
Java五子棋小游戏_第2张图片
接下来分人人对战,人机对战两部分来讲述。
人人对战的算法相对来说简单一点,所以先来说说人人对战。

分为下棋和判断输赢两部分:
先说下棋:
鼠标在棋盘上点击一下,就在棋盘上画一颗棋子,这里需要对鼠标监听器获取的x,y坐标处理一下,使鼠标点击后棋子画在离获取的坐标最近的格子的正中间,因为我们做不到每一次点击都点在精准的棋盘线交叉点上。

为了使黑白子轮流下棋,我用了一个标志位flag来标志,当flag为1时下的棋为黑色,flag为2时下的棋为白色。一开始初始化flag为1,规定了黑子先下。下完一颗棋子后就反转标志位,就可实现黑白子轮流下棋。

创建一个大小为15*15的二维数组,用来保存棋盘上棋子的值,初始化为0后,下了一颗黑棋就把数组对应的位置的值置为1,下白棋则置为2,同时每下一颗棋子就将其放入ArrayList中,方便后续的悔棋。

public void humanAndhuman(Graphics g, int rowh, int rowl){
		
		if(this.allchess[rowh][rowl]==0){
			if(this.flag==1) {
				g.setColor(Color.black);
				g.fillOval((rowh+1)*gap-radius, (rowl+1)*gap-radius, 2*radius, 2*radius);
				this.list.add(new ChessPosition(rowh,rowl));
				this.allchess[rowh][rowl] = 1;			
				this.flag = 2;
			}
			else if(this.flag==2){
				g.setColor(Color.white);
				g.fillOval((rowh+1)*gap-radius, (rowl+1)*gap-radius, 2*radius, 2*radius);
				this.list.add(new ChessPosition(rowh,rowl));
				this.allchess[rowh][rowl] = 2;
				this.flag = 1;
			}
		}
		checkWin(rowh,rowl);
	}

判断输赢:
每下完一步棋,就要判断一下是否分出胜负。判断胜负的规则非常简单,就是看棋盘上有没有相同颜色的棋子连成5颗或以上,先连成5颗的一方胜利。

所以判断输赢的算法这样来写:
从当前棋子的位置开始,分为四个方向:横向、竖向、左斜、右斜,每个方向都要判断是否有5颗或以上相同颜色的棋子连成一线。举个例子,定义一个变量count来计数,初始化为1(即当前棋子自身),检查横向时,先向左开始检查,遇到相同颜色的棋子时count自加一,直到遇到颜色不同的棋子或者到了棋盘边界,接着从当前棋子位置的右边开始检查,遇到相同颜色的棋子时count自加一,直到遇到颜色不同的棋子或者到了棋盘边界,最后判断count是否大于等于5,若是,获取当前棋子的颜色,若是黑色则弹出“黑方胜利,白方失败”这样的弹窗。

另外,设置一个标志位gameover,初始化为1,意思是允许下棋,当分出胜负后,设置gameover为0,就不能再下棋或者悔棋了。

public void checkWin(int rowh, int rowl){
		
		boolean winflag = false;
		int count = 1;
		int color = this.allchess[rowh][rowl];
		
		count = checkCount(rowh,rowl,1,0,color);//检查横向
		if(count>=5){
			winflag = true;
		}else{
			count = checkCount(rowh,rowl,0,1,color);//检查纵向
			if(count>=5){
				winflag = true;
			}else{
				count = checkCount(rowh,rowl,1,1,color);//检查左下右上
				if(count>=5){
					winflag = true;
				}else{
					count = checkCount(rowh,rowl,1,-1,color);//检查左上右下
					if(count>=5){
						winflag = true;
					}
				}
			}
		}
		if(winflag == true){
			if(color == 1){//黑子胜
				JOptionPane.showMessageDialog(null, "游戏结束,黑方获胜!");				
			}else if(color == 2){
				JOptionPane.showMessageDialog(null, "游戏结束,白方获胜!");				
			}
			this.gameover = 0;	
		}
	}
	public int checkCount(int rowh, int rowl, int xChange, int yChange ,int color){
		
		int count = 1;
		int tempX = xChange;
		int tempY = yChange;
		while((rowh+xChange>=0)&&(rowh+xChange<=14)&&(rowl+yChange>=0)&&(rowl+yChange<=14)&&(color==this.allchess[rowh+xChange][rowl+yChange])){
			
			count++;			
			if(xChange != 0){
				xChange++;
			}
			if(yChange != 0){
				if(yChange>0){
					yChange++;
				}else{
					yChange--;
				}
			}
		}
		xChange = tempX;
		yChange = tempY;
		while((rowh-xChange>=0)&&(rowh-xChange<=14)&&(rowl-yChange>=0)&&(rowl-yChange<=14)&&(color==this.allchess[rowh-xChange][rowl-yChange])){
			
			count++;			
			if(xChange != 0){
				xChange++;
			}
			if(yChange != 0){
				if(yChange>0){
					yChange++;
				}else{
					yChange--;
				}
			}
		}
		return count;
	}

人机对战:
我设置了人先下棋(黑子),然后到机器下棋。
人机中,人下棋与上述人人对战时一样,在此不再赘述。
难点在于机器下棋,当人下完一步后,机器应该下在哪里呢?
我用了权值法去解决这个问题。
我们把每一种行棋的情况用枚举法尽可能地列举出来:
1、若当前位置四周为空,这个位置落子的意义不大,相应的给这个位置的权值就要低很多;
2、若该位置的四周出线一条连线,此线上的棋子越多,说明该位置越重要,该位置的权值就越高;
3、当一条线的一端被堵住,称为眠连;当连线的两端没有被堵住,称为活连。眠连意味着受到对方的牵制,所以活连价值比眠连高。
根据以上规则,把棋局的状态用类似“0110”、“01111”这样的字符串描述出来,每一种状态一一对应一个权值,用哈希表存储。
对于每一个空格,从四个方向来考察其得分,最后把四个得分相加起来,得到总的分数,取得分最高的空格为机器落子点。
值得注意的是,攻的同时要注意守,当对方出现活三连或者眠四连的情况时,要去截住对方。对对方来说很重要的位置也就意味着对自己来说也是很重要的位置,所以列举情况时,要把对方的情况也考虑进来。
哈希表部分内容如下:

		map.put("01012",65);
		map.put("02021",60);
		map.put("01102",80);
		map.put("02201",76);
		map.put("01120",80);
		map.put("02210",76);
		map.put("00112",65);
		map.put("00221",60);

这个权值需要根据实际情况逐渐调整,打分的合理性决定了AI的智能性,越合理AI表现就会越好。

	//人机对战下棋
	public void human(Graphics g, int rowh, int rowl){
		
		int scoremax = 0;
		int x = 0,y = 0;
		if(this.allchess[rowh][rowl]==0){			
				g.setColor(Color.black);
				g.fillOval((rowh+1)*gap-radius, (rowl+1)*gap-radius, 2*radius, 2*radius);
				this.list.add(new ChessPosition(rowh,rowl));
				this.allchess[rowh][rowl] = 1;			
				this.flag = 2;
		}
		checkWin(rowh,rowl);
	}
	
	public void machine(Graphics g){

			int weightmax=0;
			for(int i=0;i<15;i++) {
				for(int j=0;j<15;j++) {
					if(weightmax<this.chessvalue[i][j]) {
						weightmax=this.chessvalue[i][j];
						x2=i;
						y2=j;
					}
				}
			}
			g.setColor(Color.white);
			g.fillOval((x2+1)*gap-radius, (y2+1)*gap-radius, 2*radius, 2*radius);
			this.list.add(new ChessPosition(x2,y2));
			this.allchess[x2][y2] = 2;			
			this.flag = 1;			
			for(i=0;i<15;i++){
				for(j=0;j<15;j++){
					this.chessvalue[i][j]=0;
				}
			}		
		checkWin(x2,y2);
	}

	public void countvalue(){
		 
		for(int i=0;i<15;i++) {
			for(int j=0;j<15;j++) {
				  //首先判断当前位置是否为空
				  if(this.allchess[i][j]==0) {
					  //往左延伸
					  String ConnectType="0";
					  int jmin=Math.max(0, j-4);
					  for(int positionj=j-1;positionj>=jmin;positionj--) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+this.allchess[i][positionj];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueleft = map.get(ConnectType);
					  if(valueleft!=null) this.chessvalue[i][j]+=valueleft;
					  
					  //往右延伸
					  ConnectType="0";
					  int jmax=Math.min(14, j+4);
					  for(int positionj=j+1;positionj<=jmax;positionj++) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+this.allchess[i][positionj];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueright = map.get(ConnectType);
					  if(valueright!=null) this.chessvalue[i][j]+=valueright;
					  
					  //联合判断,判断行
					  this.chessvalue[i][j]+=unionvalue(valueleft,valueright);
					  
					  //往上延伸
					  ConnectType="0";
					  int imin=Math.max(0, i-4);
					  for(int positioni=i-1;positioni>=imin;positioni--) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+this.allchess[positioni][j];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueup=map.get(ConnectType);
					  if(valueup!=null) this.chessvalue[i][j]+=valueup;
					  
					  //往下延伸
					  ConnectType="0";
					  int imax=Math.min(14, i+4);
					  for(int positioni=i+1;positioni<=imax;positioni++) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+this.allchess[positioni][j];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valuedown=map.get(ConnectType);
					  if(valuedown!=null) this.chessvalue[i][j]+=valuedown;
					  
					  //联合判断,判断列
					  this.chessvalue[i][j]+=unionvalue(valueup,valuedown);
					  
					  //往左上方延伸,i,j,都减去相同的数
					  ConnectType="0";
					  for(int position=-1;position>=-4;position--) {
						  if((i+position>=0)&&(i+position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+this.allchess[i+position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueLeftUp=map.get(ConnectType);
					  if(valueLeftUp!=null) this.chessvalue[i][j]+=valueLeftUp;
					  
					 //往右下方延伸,i,j,都加上相同的数
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i+position>=0)&&(i+position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+this.allchess[i+position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueRightDown=map.get(ConnectType);
					  if(valueRightDown!=null) this.chessvalue[i][j]+=valueRightDown;
					  
					  //联合判断,判断行
					  this.chessvalue[i][j]+=unionvalue(valueLeftUp,valueRightDown);
					  
					  //往左下方延伸,i加,j减
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i+position>=0)&&(i+position<=14)&&(j-position>=0)&&(j-position<=14))
						  ConnectType=ConnectType+this.allchess[i+position][j-position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueLeftDown=map.get(ConnectType);
					  if(valueLeftDown!=null) this.chessvalue[i][j]+=valueLeftDown;
					  
					  //往右上方延伸,i减,j加
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i-position>=0)&&(i-position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+this.allchess[i-position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueRightUp=map.get(ConnectType);
					  if(valueRightUp!=null) this.chessvalue[i][j]+=valueRightUp;
					  
					  //联合判断,判断行
					  this.chessvalue[i][j]+=unionvalue(valueLeftDown,valueRightUp);
				  }
			  }
		  }	  
		  //取出最大的权值		  
	}
	public Integer unionvalue(Integer a,Integer b){
			if((a==null)||(b==null)) return 0;
			//一一:101/202
		    else if((a>=22)&&(a<=25)&&(b>=22)&&(b<=25)) return 60;
			//一二、二一:1011/2022
			else if(((a>=22)&&(a<=25)&&(b>=76)&&(b<=80))||((a>=76)&&(a<=80)&&(b>=22)&&(b<=25))) return 800;
			//一三、三一、二二:10111/20222
			else if(((a>=10)&&(a<=25)&&(b>=1050)&&(b<=1100))||((a>=1050)&&(a<=1100)&&(b>=10)&&(b<=25))||((a>=76)&&(a<=80)&&(b>=76)&&(b<=80)))
				return 3000;
			//眠三连和眠一连。一三、三一
			else if(((a>=22)&&(a<=25)&&(b>=140)&&(b<=150))||((a>=140)&&(a<=150)&&(b>=22)&&(b<=25))) return 3000;
			//二三、三二:110111
			else if(((a>=76)&&(a<=80)&&(b>=1050)&&(b<=1100))||((a>=1050)&&(a<=1100)&&(b>=76)&&(b<=80))) return 3000;
			else return 0;
		}

监听器类的代码如下:

import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JOptionPane;

import com.sun.glass.ui.Robot;

public class ChessBoardListener implements MouseListener,ActionListener {
	
	public ChessBoard chess;
	Graphics p;
	int x1,y1;
	int rowh,rowl;
	int color;
	int num = 15;
	int gap = 32;
	int radius = 12;
	
	public ChessBoardListener(ChessBoard chess){
		this.chess = chess;
	}
	public void setGraphics(Graphics g) {
		this.p = g;
	}
	
	public void actionPerformed(ActionEvent e) {
		if(e.getActionCommand().equals("重新开始")){			
			chess.restart();		
		}
		else if(e.getActionCommand().equals("悔棋")){
			if(chess.gameover==0) JOptionPane.showMessageDialog(null, "游戏已结束,不能悔棋");
			chess.reback();
		}
		else if(e.getActionCommand().equals("人人对战")){
			chess.humanormachine = 1;
		}
		else if(e.getActionCommand().equals("人机对战")){
			chess.humanormachine = 2;
		}
		else if(e.getActionCommand().equals("认输")){
			chess.surrender();
		}
	}

	public void mouseClicked(MouseEvent e) {
		
		x1 = e.getX();
		y1 = e.getY();
		rowh = ((x1-gap+num)/gap)%gap;
		rowl = ((y1-gap+num)/gap)%gap;
		
		if((x1<495&&x1>0&&y1<495&&y1>0) && (chess.gameover == 1)){
			if(chess.humanormachine != 0){
				if(chess.humanormachine == 1){
					chess.humanAndhuman(p, rowh, rowl);
				}							
				else if(chess.humanormachine == 2){					
					chess.human(p, rowh, rowl);
					chess.countvalue();
					chess.machine(p);									
				}
			}
			
		}
	}
	
    public void mousePressed(MouseEvent e) {}
    
    public void mouseReleased(MouseEvent e) {}

    public void mouseEntered(MouseEvent e) {}
    
    public void mouseExited(MouseEvent e) {}
}

悔棋功能:
之前每下一步棋就把棋子存进ArrayList中,当鼠标监听器监听到鼠标点击悔棋按钮时,获取ArrayList中最上面那个节点之后删除它,此元素的数据类型我们定义为棋子的x,y坐标,所以取出来的就是最新下的棋子的x,y坐标,然后把二维数组中对应的位置置为0。这时候还要获取当前ArrayList中最上面的棋子的颜色,若获得的flag为2(白色),则要把flag赋值为1(黑色),因为白色的棋子下完之后就到黑色的棋子下了。最后调用repaint方法后刷新棋盘。

public ArrayList<ChessPosition> list=new ArrayList<ChessPosition>();
	public void reback(){
		if(list.size()>0&&this.gameover==1){
			ChessPosition cp = new ChessPosition();
			cp = list.remove(list.size()-1);
			this.allchess[cp.i][cp.j]=0;
			if(flag==1) flag=2;
			if(flag==2) flag=1;
			pl.repaint();
		}
	}

重新开始功能:
就是把二维数组重新全部置为0,再调用repaint方法刷新棋盘。然后把flag置为1,因为我始终使黑棋先下。使gameover标志位为1,表示可以下棋。

public void restart(){
		if(this.list.size()>0){
			for(i=0;i<15;i++) {
				for(j=0;j<15;j++) {
					this.allchess[i][j]=0;
				}
			}
			flag = 1;
			this.gameover = 1;
			pl.repaint();
		}
	}

认输功能:
监听器监听到鼠标点击认输按钮时,使gameover标志位为0,终止游戏,弹出胜负弹窗。

	public void surrender(){
		if(this.flag==2){
			JOptionPane.showMessageDialog(null, "白方投降,黑方获胜!");			
		}
		else if(this.flag==1){
			JOptionPane.showMessageDialog(null, "黑方投降,白方获胜!");
		}
		this.gameover = 0;
	}

实现效果:
Java五子棋小游戏_第3张图片
以上就是我做的五子棋的大概内容,写得不好的地方还请大家指教。(声明一下,人机中部分算法借鉴了网上的一些大佬,在此致谢。)

你可能感兴趣的:(java,游戏)