Java五子棋(人机版),昨天买的棋子今天就用不上了_五子棋人机


运行一下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719175418887.png)


### 绘制棋盘


**重写paint方法**



@Override
public void paint(java.awt.Graphics g) {
super.paint(g);
}


**绘制横竖相交接的线**  
 定义15行、15列



public static final int ROWS=15;
public static final int COLS=15;


**绘制网格**



//绘制网格
private void drawGrid(Graphics g) {
Graphics2D g_2d=(Graphics2D)g;
int start=26;
int x1=start;
int y1=20;
int x2=586;
int y2=20;
for (int i = 0; i < ROWS; i++) {
y1 = start + 40*i;
y2 = y1;
g_2d.drawLine(x1, y1, x2, y2);
}

y1=start;
y2=586;
for (int i = 0; i < COLS; i++) {
	x1 = start + 40\*i;
	x2 = x1;
	g_2d.drawLine(x1, y1, x2, y2);		
}

}


**绘制5个圆点**



//绘制5个黑点
private void draw5Point(Graphics g) {
//第1个点
g.fillArc(142, 142, 8, 8, 0, 360);
//第2个点
g.fillArc(462, 142, 8, 8, 0, 360);

//第3个点
g.fillArc(142, 462, 8, 8, 0, 360);
//第4个点
g.fillArc(462, 462, 8, 8, 0, 360);

//中心点
g.fillArc(302, 302, 8, 8, 0, 360);

}


**在paint方法里面调用以上2个方法**



@Override
public void paint(java.awt.Graphics g) {
super.paint(g);
//绘制网格
drawGrid(g);
//绘制5个黑点
draw5Point(g);
}


**棋盘已经绘制完成**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719180312404.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RrbTEyMzQ1Ng==,size_16,color_FFFFFF,t_70)


### 实现落子指示器


1. 创建指示器类



package main;

import java.awt.Color;
import java.awt.Graphics;

//指示器类
public class Pointer {
private int i=0;//二维下标i
private int j=0;//二维下标j
private int x=0;//坐标X
private int y=0;//坐标Y
private GamePanel panel=null;
private Color color=null;
private int h=40;//指示的大小
private boolean isShow=false;//是否展示
private int qizi = 0 ;//棋子类型 0:无 1:白棋 2:黑棋

public Pointer(int x,int y,int i,int j,Color color,GamePanel panel){
	this.x=x;
	this.y=y;
	this.i=i;
	this.j=j;
	this.panel=panel;
	this.color=color;
}
//绘制
void draw(Graphics g){
	Color oColor = g.getColor();
	if(color!=null){
		g.setColor(color);
	}
	if(isShow){
		//绘制指示器
		g.drawRect(x-h/2, y-h/2, h, h);
	}
	if(color!=null){//用完设置回去颜色
		g.setColor(oColor);
	}
}
//判断鼠标是否在指针范围内
boolean isPoint(int x,int y){
	//大于左上角,小于右下角的坐标则肯定在范围内
	if(x>this.x-h/2 && y >this.y-h/2
		&& x<this.x+h/2 && y <this.y+h/2){
		return  true;
	}
	return false;
}
public boolean isShow() {
	return isShow;
}
public void setShow(boolean isShow) {
	this.isShow = isShow;
}
public int getQizi() {
	return qizi;
}
public void setQizi(int qizi) {
	this.qizi = qizi;
}
public int getX() {
	return x;
}
public void setX(int x) {
	this.x = x;
}
public int getY() {
	return y;
}
public void setY(int y) {
	this.y = y;
}
public int getI() {
	return i;
}
public void setI(int i) {
	this.i = i;
}
public int getJ() {
	return j;
}
public void setJ(int j) {
	this.j = j;
}

}


2. 初始化二维数组



public Pointer points[][] = new Pointer[ROWS][COLS];


3. 创建指示器实例对象



//创建二维数组
private void createArr() {
int x=0,y=0;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
y = 26 + 40*i;
x = 26 + 40*j;
Pointer pointer = new Pointer(x, y, i,j,new Color(255,0,0), this);
points[i][j] = pointer;
}
}
}


4. 初始化调用



//初始化相关对象
private void init() {
createArr();
//游戏开始标记
gameFlag=“start”;
}


同时 init方法 在GamePanel 的构造方法调用。


5. paint方法中遍历二维数组并且绘制。



@Override
public void paint(java.awt.Graphics g) {
super.paint(g);
//绘制网格
drawGrid(g);
//绘制5个黑点
draw5Point(g);
//绘制指示器
Pointer pointer = null;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
pointer = points[i][j] ;
if(pointer!=null){
pointer.draw(g);
}
}
}
}


运行如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719183021556.gif#pic_center)


但是这个指示器方块不是我们想要的,需要改一下


6. 修改指示器类的绘图代码



> 
> 在Pointer方法中新增方法,并在指示器绘制的时候调用此方法,而不是之前的drawRect。
> 
> 
> 



private void drawPointer(Graphics g) {

Graphics2D g2 = (Graphics2D)g;  //g是Graphics对象
g2.setStroke(new BasicStroke(2.0f));

/\*

* 1.先计算4个顶点
* 2.依次从每个顶点绘制横竖两条线
*/

//左上角
int x1 = x-h/2;
int y1 = y-h/2;
//向右画线
int x2 = x1+1\*h/4;
int y2 = y1;
g2.drawLine(x1, y1, x2, y2);

//向下画线
x2 = x1;
y2 = y1+1\*h/4;
g2.drawLine(x1, y1, x2, y2);

//右上角
x1 = x+h/2;
y1 = y-h/2;
//向左画线
x2 = x1-1\*h/4;
y2 = y1;
g2.drawLine(x1, y1, x2, y2);

//向下画线
x2 = x1;
y2 = y1+1\*h/4;
g2.drawLine(x1, y1, x2, y2);

//右下角
x1 = x+h/2;
y1 = y+h/2;
//向左画线
x2 = x1-1\*h/4;
y2 = y1;
g2.drawLine(x1, y1, x2, y2);

//向上画线
x2 = x1;
y2 = y1-1\*h/4;
g2.drawLine(x1, y1, x2, y2);

//左下角
x1 = x-h/2;
y1 = y+h/2;
//向右画线
x2 = x1+1\*h/4;
y2 = y1;
g2.drawLine(x1, y1, x2, y2);

//向上画线
x2 = x1;
y2 = y1-1\*h/4;
g2.drawLine(x1, y1, x2, y2);

}


再运行


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719183351832.gif#pic_center)


### 落子


1. 创建ImageValue加载类



import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ImageValue {
public static BufferedImage whiteQiziImage ;

public static BufferedImage blackQiziImage  ;
//路径
public static String ImagePath = "/images/";
//将图片初始化
public static void init(){
	String whitePath = ImagePath +"white.png";
	String blackPath = ImagePath +"black.png";
	try {
		whiteQiziImage = ImageIO.read(ImageValue.class.getResource(whitePath));
		blackQiziImage = ImageIO.read(ImageValue.class.getResource(blackPath));
	} catch (IOException e) {
		e.printStackTrace();
	}
}

}


2. 创建落子类



import java.awt.Color;
import java.awt.Graphics;
import common.ImageValue;

public class Qizi {

private int x = 0;
private int y = 0;
private int r = 36;
private GamePanel panel = null;
private Color color = null;
private int type = 1;// 棋子类型 1:白棋 2:黑棋

public Qizi(int x, int y, int type, GamePanel panel) {
	this.x = x;
	this.y = y;
	this.panel = panel;
	this.type=type;
}

// 绘制
void draw(Graphics g) {
	Color oColor = g.getColor();
	if (type == 1) {// 白色
		g.drawImage(ImageValue.whiteQiziImage, x - r / 2, y - r / 2,r,r, null);
	} else {// 黑色
		g.drawImage(ImageValue.blackQiziImage, x - r / 2, y - r / 2,r,r, null);
	}

	if (color != null) {// 用完设置回去颜色
		g.setColor(oColor);
	}
}

public int getType() {
	return type;
}

public void setType(int type) {
	this.type = type;
}

}


3. 在createMouseListener方法中重写mouseClicked,创建棋子



@Override
public void mouseClicked(MouseEvent e) {
//在合适的位置点击则进行落子操作
if(!“start”.equals(gameFlag)) return ;

int x = e.getX();
int y = e.getY();

Pointer pointer;
for (int i = 0; i <ROWS; i++) {
	for (int j = 0; j < COLS; j++) {
		pointer = points[i][j];
		if(pointer==null)continue;
		//被点击,且没有棋子,则可以落子
		if(pointer.isPoint(x, y) && pointer.getQizi()==0){
			Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
			pointer.setQizi(2);
			qizis.add(qizi);
		    //重绘画布
			repaint();
			return ;
		}
	}
}

}


4. 在paint 方法中绘制棋子



@Override
public void paint(java.awt.Graphics g) {
super.paint(g);
//绘制网格
drawGrid(g);
//绘制5个黑点
draw5Point(g);
//绘制指示器
Pointer pointer = null;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
pointer = points[i][j] ;
if(pointer!=null){
pointer.draw(g);
}
}
}
//绘制棋子
Qizi qizi=null;
for (int i = 0; i < qizis.size(); i++) {
qizi = (Qizi)qizis.get(i);
qizi.draw(g);
}
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719212508612.gif#pic_center)


### 加入电脑AI


1. 创建AI类
2. 创建静态方法next(下一步)
3. 创建静态方法has5(连成5子)



public class AI {
//AI进行下一步
static void next(GamePanel gamePanel){

}
//判断五子连珠
static boolean has5(Pointer pointer1,GamePanel gamePanel){
	
	return false;
}

}


4. 在你落子后,会先执行has5方法,根据返回来决定走向
5. has5返回true则表示你获得胜利,否则AI将会走一步棋  
 **在刚才的mouseClicked修改代码**



@Override
public void mouseClicked(MouseEvent e) {
//在合适的位置点击则进行落子操作
if(!“start”.equals(gameFlag)) return ;

int x = e.getX();
int y = e.getY();

Pointer pointer;
for (int i = 0; i <ROWS; i++) {
	for (int j = 0; j < COLS; j++) {
		pointer = points[i][j];
		if(pointer==null)continue;
		//被点击,且没有棋子,则可以落子
		if(pointer.isPoint(x, y) && pointer.getQizi()==0){
			Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
			pointer.setQizi(2);
			qizis.add(qizi);
			//重绘画布
			repaint();
			//判断有没有五子连珠的情况
			if(AI.has5(pointer, gamePanel)){
				gamePanel.gameWin();
			}else{
				AI.next(gamePanel);
			}
			return ;
		}
	}
}

}


6. 在AI类中随机落子(创建方法)



> 
> - 随机获取下标 i 和 j。  
>  - 通过下标从二维数组取到指示器对象。  
>  - 如果此指示器被占用则再次随机(递归),直到正确获取到指示器对象。  
>  - 在此指示器位置,创建棋子对象并更新指示器的棋子信息。  
>  - Qizi类中加入last属性,表示AI下的最后一个棋子。  
>  - 根据last在对应的位置创建一个小红方块标示AI的最后落子。
> 
> 
> 



//随机落子
static boolean luoziRandom(GamePanel gamePanel){

Pointer pointer = getRandomPointer(gamePanel);

luozi(pointer, gamePanel,1);

return true;

}

//获取随机下的棋子
static Pointer getRandomPointer(GamePanel gamePanel){
Random random = new Random();
int i = random.nextInt(gamePanel.ROWS);
int j = random.nextInt(gamePanel.COLS);
//取得随机到的格子
Pointer pointer = gamePanel.points[i][j];
if(pointer.getQizi()!=0){//如果当前格子已经下了棋子,则递归重新取
pointer = getRandomPointer(gamePanel);
}
return pointer;
}

//AI落子操作
static void luozi(Pointer pointer,GamePanel gamePanel,int type){
if(pointer.getQizi()==0){//如果没有棋子,则落子
Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), type, gamePanel);
qizi.setLast(true);
pointer.setQizi(type);
gamePanel.qizis.add(qizi);

	//重绘画布
	gamePanel.repaint();
	
	//判断电脑有没有五子连珠的情况
	if(AI.has5(pointer, gamePanel)){
		gamePanel.gameOver();
	}
}

}


7. AI的next方法调用随机落子



//AI进行下一步
static void next(GamePanel gamePanel){
luoziRandom(gamePanel);
}


运行效果:


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215047199.gif#pic_center)


7. 小红方块一直在,修改代码  
 **仅需在落子前将其他小红方块清除即可**



//清除电脑棋子的最后一个棋子指示器
private void clearAILast() {
Qizi qizi;
for (int i = 0; i < qizis.size(); i++) {
qizi = (Qizi)qizis.get(i);
if(qizi!=null && qizi.getType()==1){
qizi.setLast(false);
}
}
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719214941603.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RrbTEyMzQ1Ng==,size_16,color_FFFFFF,t_70)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215122866.gif#pic_center)


### AI算法


#### 棋子的4个方向


* 横向  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215544160.png)
* 竖向  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215617772.png)
* 右捺  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215639565.png)
* 左撇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719215704979.png)


#### 权重分



> 
> 描述:计算出分数,最高的分数来决定下一步的落子。  
>  左开:就是说左边可落子  
>  右开:右边可落子
> 
> 
> 


**3子的相关定义(4、5子类似)**



> 
> 什么是3子左开,就是目前有2个子,下一个子可以落子左边
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719220446379.png)



> 
> 3子只能落子在中间,图示:
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/202107192205206.png)



> 
> 3子右开就是落子在右边
> 
> 
> 


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210719220608974.png)


只计算3个子以上的分数




| 类型 | 3子左开 | 3子 | 3子右开 |
| --- | --- | --- | --- |
| 得分 | 32 | 30 | 31 |




| 类型 | 4子左开 | 4子 | 4子右开 |
| --- | --- | --- | --- |
| 得分 | 42 | 40 | 41 |




| 类型 | 5子左开 | 5子 | 5子右开 |
| --- | --- | --- | --- |
| 得分 | 52 | 50 | 51 |


通过上述表可以看到,落子权重分顺序:5>4>3,同时:左>右>中。


#### 计算横向权重分


1. 从左往右判断



> 
> \* 横向下标 i 是一样的,循环从当前位置 j 加1开始。  
>  \* 当碰到和当前子一样的就计数器 +1。  
>  \* 当碰到不一样的就退出循环,表示堵住 。  
>  \* 如果碰到空子,是第一次计数器 +1,第二次退出循环。  
>  \* 判断左开和右开的状态。  
>  \* 根据计数器和左右开的状态,计算出分数
> 
> 
> 


2. 从右往左判断



> 
> \* 横向下标 i 是一样的,循环从当前位置 j 减1开始。  
>  \* 当碰到和当前子一样的就计数器 +1。  
>  \* 当碰到不一样的就退出循环,表示堵住 。  
>  \* 如果碰到空子,是第一次计数器 +1,第二次退出循环。  
>  \* 判断左开和右开的状态。  
>  \* 根据计数器、左右开的状态,计算出分数和落子的位置。
> 
> 
> 


其实左右还是很相似的



static Data getData(Pointer pointer,int dir,int type,GamePanel gamePanel){
Pointer[][] points = gamePanel.points;
int i = pointer.getI();
int j = pointer.getJ();

Data resData = new Data();
Pointer tempPointer;
int num=1;//默认是1,因为其中自己就是一个子。
int num2=1;//默认是1,用来累加连续的棋子数
int breakNum=0;//默认是0,有一个则不能通过了。

boolean lClosed=false;//左边是否关闭
boolean rClosed=false;//右边是否关闭
if(dir==1){//横向
	//往右循环,判断能与当前pointer 相同的棋子连续多少个。
	if(type==1){
		for (int k = j+1; k < gamePanel.COLS; k++) {
			tempPointer = points[i][k];
			if(tempPointer.getQizi()==pointer.getQizi()){//连续
				num++;
				num2++;
				if(k == gamePanel.COLS-1){//如果最后一个子也是连续的,则也是右关闭的
					rClosed = true;
				}
			}else if(tempPointer.getQizi()==0){//空白子
				if(breakNum==1){//有一个则不能通过了
					if(points[i][k-1].getQizi()==0){//如果前一个是空子,要设置成不是中断的
						breakNum=0;
					}else{
						breakNum=2;
					}
					break;
				}
				breakNum=1;
				num++;
				//是中断的那种,这里设定好落子位置
				resData.setI(i);
				resData.setJ(k);
			}else{//对立子,右关闭
				rClosed = true;
				break;
			}
		}
		//判断是否左关闭
		if(j==0){//当前子就是最左边的子
			lClosed = true;
		}else{
			tempPointer = points[i][j-1];
			if(tempPointer.getQizi()!=0){//如果当前子的左边有子,则左关闭
				lClosed = true;
			}
		}
	}else{//从右往左
		for (int k = j-1; k >=0; k--) {
			tempPointer = points[i][k];
			if(tempPointer.getQizi()==pointer.getQizi()){//连续
				num++;
				num2++;
				if(k == 0){//如果最后一个子也是连续的,则也是左关闭的
					lClosed = true;
				}
			}else if(tempPointer.getQizi()==0){//空白子
				if(breakNum==1){//有一个则不能通过了。
					if(points[i][k+1].getQizi()==0){//如果前一个是空子,要设置成不是中断的
						breakNum=0;
					}else{
						breakNum=2;
					}
					break;
				}
				breakNum=1;
				num++;
				//是中断的那种,这里设定好落子位置
				resData.setI(i);
				resData.setJ(k);
			}else{//对立子,左关闭
				lClosed = true;
				break;
			}
		}
		//判断是否右关闭
		if(j==gamePanel.COLS-1){//当前子就是最右边的子
			rClosed = true;

给大家的福利

零基础入门

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:

在这里插入图片描述

因篇幅有限,仅展示部分资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值