主頁 > 後端開發 > Java五子棋(人機版),昨天買的棋子今天就用不上了

Java五子棋(人機版),昨天買的棋子今天就用不上了

2021-07-23 07:54:32 後端開發

<iframe id="nKaQimkf-1626741607279" src="https://live.csdn.net/v/embed/171268" allowfullscreen="true" data-mediaembed="csdn"></iframe>

Java五子棋,老程式員也花了3天

作者簡介

作者名:編程界明世隱
簡介:CSDN博客專家,從事軟體開發多年,精通Java、JavaScript,博主也是從零開始一步步把學習成長、深知學習和積累的重要性,喜歡跟廣大ADC一起打野升級,歡迎您關注,期待與您一起學習、成長、起飛!

系列目錄

1. Java俄羅斯方塊
2. 老Java程式員花2天寫了個連連看
3. 老Java程式員花一天時間寫了個飛機大戰
4. Java植物大戰僵尸
5. Java消消樂(天天愛消除)
6. Java貪吃蛇小游戲
7. Java掃雷小游戲
8. Java坦克大戰

效果圖

在這里插入圖片描述

實作思路

1.創建運行視窗并添加背景色,
2.繪制棋盤,
3.用二維陣列來控制起碼落子位置、繪制指示器,
4.滑鼠在落子位置處點擊可落子,
5.落子后檢查是否獲得勝利,
6.機器判斷下一步,并落子,
7.機器判斷是否獲得勝利,

代碼實作

創建視窗

首先創建一個游戲表單類GameFrame,繼承至JFrame,用來顯示在螢屏上(window的物件),每個游戲都有一個視窗,設定好視窗標題、尺寸、布局等就可以,

/*
 * 游戲表單類
 */
public class GameFrame extends JFrame {
	
	public GameFrame() {
		setTitle("五子棋");//設定標題
		setSize(620, 670);//設定尺寸
		getContentPane().setBackground(new Color(209,146,17));//添加背景色
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//點擊關閉按鈕是關閉程式
        setLocationRelativeTo(null);   //設定居中
    	setResizable(false); //不允許修改界面大小
	}
}

創建面板容器GamePanel繼承至JPanel


import javax.swing.JFrame;
import javax.swing.JPanel;

/*
 * 畫布類
 */
public class GamePanel extends JPanel {
	private static final long serialVersionUID = 1L;
	GamePanel gamePanel=this;
	private JFrame mainFrame=null;
	
	//構造里面初始化相關引數
	public GamePanel(JFrame frame){
		this.setLayout(null);
		this.setOpaque(false);
		mainFrame = frame;
		
		mainFrame.requestFocus();
		mainFrame.setVisible(true);
	}
	
}

再創建一個Main類,來啟動這個視窗,

public class Main {
	//主類
	public static void main(String[] args) {
		GameFrame frame = new GameFrame();
		GamePanel gamePanel = new GamePanel(frame);
		frame.add(gamePanel);
		frame.setVisible(true);//設定顯示
	}
}

右鍵執行這個Main類,視窗建出來了
在這里插入圖片描述

創建選單及選單選項

創建選單

private void  initMenu(){
	// 創建選單及選單選項
	jmb = new JMenuBar();
	JMenu jm1 = new JMenu("游戲");
	jm1.setFont(new Font("思源宋體", Font.BOLD, 18));// 設定選單顯示的字體
	JMenu jm2 = new JMenu("幫助");
	jm2.setFont(new Font("思源宋體", Font.BOLD, 18));// 設定選單顯示的字體
	
	JMenuItem jmi1 = new JMenuItem("開始新游戲");
	JMenuItem jmi2 = new JMenuItem("退出");
	jmi1.setFont(new Font("思源宋體", Font.BOLD, 18));
	jmi2.setFont(new Font("思源宋體", Font.BOLD, 18));
	
	JMenuItem jmi3 = new JMenuItem("操作說明");
	jmi3.setFont(new Font("思源宋體", Font.BOLD, 18));
	JMenuItem jmi4 = new JMenuItem("成功/失敗判定");
	jmi4.setFont(new Font("思源宋體", Font.BOLD, 18));
	
	jm1.add(jmi1);
	jm1.add(jmi2);
	
	jm2.add(jmi3);
	jm2.add(jmi4);
	
	jmb.add(jm1);
	jmb.add(jm2);
	mainFrame.setJMenuBar(jmb);// 選單Bar放到JFrame上
	jmi1.addActionListener(this);
	jmi1.setActionCommand("Restart");
	jmi2.addActionListener(this);
	jmi2.setActionCommand("Exit");
	
	jmi3.addActionListener(this);
	jmi3.setActionCommand("help");
	jmi4.addActionListener(this);
	jmi4.setActionCommand("lost");
}

實作ActionListener并重寫方法actionPerformed
在這里插入圖片描述
此時GamePanel是報錯的,重寫actionPerformed方法,

actionPerformed方法的實作

@Override
public void actionPerformed(ActionEvent e) {
	String command = e.getActionCommand();
	System.out.println(command);
	UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
	UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
	if ("Exit".equals(command)) {
		Object[] options = { "確定", "取消" };
		int response = JOptionPane.showOptionDialog(this, "您確認要退出嗎", "",
				JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				options, options[0]);
		if (response == 0) {
			System.exit(0);
		} 
	}else if("Restart".equals(command)){
		if(!"end".equals(gamePanel.gameFlag)){
			JOptionPane.showMessageDialog(null, "正在游戲中無法重新開始!",
					"提示!", JOptionPane.INFORMATION_MESSAGE); 
		}else{
			if(gamePanel!=null) {
				gamePanel.restart();
			}
		}
	}else if("help".equals(command)){
		JOptionPane.showMessageDialog(null, "滑鼠在指示器位置點下,則落子!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}else if("lost".equals(command)){
		JOptionPane.showMessageDialog(null, "五子連珠方獲得勝利!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}
}

此時的GamePanel代碼如下:

package main;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;

/*
 * 畫布類
 */
public class GamePanel extends JPanel implements ActionListener{
	private static final long serialVersionUID = 1L;
	GamePanel gamePanel=this;
	private JFrame mainFrame=null;
	JMenuBar jmb=null;
	public String gameFlag="";
	
	//構造里面初始化相關引數
	public GamePanel(JFrame frame){
		this.setLayout(null);
		this.setOpaque(false);
		mainFrame = frame;
		
		//創建按鈕
		initMenu();
		
		mainFrame.requestFocus();
		mainFrame.setVisible(true);
	}
	
	private void  initMenu(){
		// 創建選單及選單選項
		jmb = new JMenuBar();
		JMenu jm1 = new JMenu("游戲");
		jm1.setFont(new Font("思源宋體", Font.BOLD, 18));// 設定選單顯示的字體
		JMenu jm2 = new JMenu("幫助");
		jm2.setFont(new Font("思源宋體", Font.BOLD, 18));// 設定選單顯示的字體
		
		JMenuItem jmi1 = new JMenuItem("開始新游戲");
		JMenuItem jmi2 = new JMenuItem("退出");
		jmi1.setFont(new Font("思源宋體", Font.BOLD, 18));
		jmi2.setFont(new Font("思源宋體", Font.BOLD, 18));
		
		JMenuItem jmi3 = new JMenuItem("操作說明");
		jmi3.setFont(new Font("思源宋體", Font.BOLD, 18));
		JMenuItem jmi4 = new JMenuItem("成功/失敗判定");
		jmi4.setFont(new Font("思源宋體", Font.BOLD, 18));
		
		jm1.add(jmi1);
		jm1.add(jmi2);
		
		jm2.add(jmi3);
		jm2.add(jmi4);
		
		jmb.add(jm1);
		jmb.add(jm2);
		mainFrame.setJMenuBar(jmb);// 選單Bar放到JFrame上
		jmi1.addActionListener(this);
		jmi1.setActionCommand("Restart");
		jmi2.addActionListener(this);
		jmi2.setActionCommand("Exit");
		
		jmi3.addActionListener(this);
		jmi3.setActionCommand("help");
		jmi4.addActionListener(this);
		jmi4.setActionCommand("lost");
	}
	
	//重新開始
	public void restart() {
		//游戲開始標記
		gameFlag="start";
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();
		System.out.println(command);
		UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
		UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋體", Font.ITALIC, 18)));
		if ("Exit".equals(command)) {
			Object[] options = { "確定", "取消" };
			int response = JOptionPane.showOptionDialog(this, "您確認要退出嗎", "",
					JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
					options, options[0]);
			if (response == 0) {
				System.exit(0);
			} 
		}else if("Restart".equals(command)){
			if(!"end".equals(gamePanel.gameFlag)){
				JOptionPane.showMessageDialog(null, "正在游戲中無法重新開始!",
						"提示!", JOptionPane.INFORMATION_MESSAGE); 
			}else{
				if(gamePanel!=null) {
					gamePanel.restart();
				}
			}
		}else if("help".equals(command)){
			JOptionPane.showMessageDialog(null, "滑鼠在指示器位置點下,則落子!",
					"提示!", JOptionPane.INFORMATION_MESSAGE);
		}else if("lost".equals(command)){
			JOptionPane.showMessageDialog(null, "五子連珠方獲得勝利!",
					"提示!", JOptionPane.INFORMATION_MESSAGE);
		}
	}
}

運行一下
在這里插入圖片描述

繪制棋盤

重寫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);
}

棋盤已經繪制完成
在這里插入圖片描述

實作落子指示器

  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;
	}
}
  1. 初始化二維陣列
public Pointer points[][] =  new Pointer[ROWS][COLS];
  1. 創建指示器實體物件
//創建二維陣列
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;
		}
	}		
}
  1. 初始化呼叫
//初始化相關物件
private void init() {
	createArr();
	//游戲開始標記
	gameFlag="start";
}

同時 init方法 在GamePanel 的構造方法呼叫,

  1. 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);
			}
		}
	}
}

運行如下
在這里插入圖片描述

但是這個指示器方塊不是我們想要的,需要改一下

  1. 修改指示器類的繪圖代碼

在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);
}

再運行

在這里插入圖片描述

落子

  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();
		}
    }
}
  1. 創建落子類

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;
	}
}
  1. 在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 ;
			}
		}
	}
}
  1. 在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);
	}
}

在這里插入圖片描述

加入電腦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;
	}
}
  1. 在你落子后,會先執行has5方法,根據回傳來決定走向
  2. 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 ;
			}
		}
	}
}
  1. 在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();
		}
	}
}
  1. AI的next方法呼叫隨機落子
//AI進行下一步
	static void next(GamePanel gamePanel){
		luoziRandom(gamePanel);
	}

運行效果:

在這里插入圖片描述

  1. 小紅方塊一直在,修改代碼
    僅需在落子前將其他小紅方塊清除即可
//清除電腦棋子的最后一個棋子指示器
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);
		}
	}
}

在這里插入圖片描述
在這里插入圖片描述

AI演算法

棋子的4個方向

  • 橫向
    在這里插入圖片描述

  • 豎向
    在這里插入圖片描述

  • 右捺
    在這里插入圖片描述

  • 左撇
    在這里插入圖片描述

權重分

描述:計算出分數,最高的分數來決定下一步的落子,
左開:就是說左邊可落子
右開:右邊可落子

3子的相關定義(4、5子類似)

什么是3子左開,就是目前有2個子,下一個子可以落子左邊

在這里插入圖片描述

3子只能落子在中間,圖示:

在這里插入圖片描述

3子右開就是落子在右邊

在這里插入圖片描述

只計算3個子以上的分數

型別3子左開3子3子右開
得分323031
型別4子左開4子4子右開
得分424041
型別5子左開5子5子右開
得分525051

通過上述表可以看到,落子權重分順序:5>4>3,同時:左>右>中,

計算橫向權重分

  1. 從左往右判斷

* 橫向下標 i 是一樣的,回圈從當前位置 j 加1開始,
* 當碰到和當前子一樣的就計數器 +1,
* 當碰到不一樣的就退出回圈,表示堵住 ,
* 如果碰到空子,是第一次計數器 +1,第二次退出回圈,
* 判斷左開和右開的狀態,
* 根據計數器和左右開的狀態,計算出分數

  1. 從右往左判斷

* 橫向下標 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;
			}else{
				tempPointer = points[i][j+1];
				if(tempPointer.getQizi()!=0){//如果當前子的右邊有子,則右關閉
					rClosed = true;
				}
			}
		}
	}
	setCount(resData, i, j, dir, type, num,num2, breakNum, lClosed, rClosed);
	
	return resData;
}

//計算并設定分數
static void setCount(Data data,int i,int j,int dir,int type,
		int num,int num2,int breakNum,boolean lClosed,boolean rClosed){
	int count=0;
	if(num>2){//連續3個子以上
		if(num==3){//設定默認分
			count=30;
		}else if(num==4){
			count=40;
		}else if(num==5){
			count=50;
		}
		if(num2>=5&&breakNum==0){//用來判斷是否五子或五子以上
			count=100;
			//設定好權重分
			data.setCount(count);
			return ;
		}
		if(breakNum==0){//如果不是中斷的那種
			if(lClosed&&rClosed){//如果沒有中斷,并且左右都關閉了,則分數為-1,-1表示落子的時候要過濾掉
				count = -1;
			}else if(!lClosed){//如果是中斷的那種,左邊未關閉
				count+=2;//加2分
				if(dir==1){
					if(type==1){
						data.setI(i);
						data.setJ(j-1);
					}else{
						data.setI(i);
						data.setJ(j-num+1);
					}
				}else if(dir==2){
					if(type==1){
						data.setI(i-1);
						data.setJ(j);
					}else{
						data.setI(i-num+1);
						data.setJ(j);
					}
				}else if(dir==3){
					if(type==1){
						data.setI(i-1);
						data.setJ(j-1);
					}else{
						data.setI(i-num+1);
						data.setJ(j-num+1);
					}
				}else if(dir==4){
					if(type==1){
						data.setI(i+1);
						data.setJ(j-1);
					}else{
						data.setI(i+num-1);
						data.setJ(j-num+1);
					}
				}
			}else if(!rClosed){//如果是中斷的那種,右邊未關閉
				count+=1;//加1分
				if(dir==1){
					if(type==1){
						data.setI(i);
						data.setJ(j+num-1);
					}else{
						data.setI(i);
						data.setJ(j+1);
					}
				}else if(dir==2){
					if(type==1){
						data.setI(i+num-1);
						data.setJ(j);
					}else{
						data.setI(i+1);
						data.setJ(j);
					}
				}else if(dir==3){
					if(type==1){
						data.setI(i+num-1);
						data.setJ(j+num-1);
					}else{
						data.setI(i+1);
						data.setJ(j+1);
					}
				}else if(dir==4){
					if(type==1){
						data.setI(i-num+1);
						data.setJ(j+num-1);
					}else{
						data.setI(i-1);
						data.setJ(j+1);
					}
				}
			}
		}else{//如果中斷,
			if(num!=5){//num不是5, 并且左右都關閉,也要過濾
				if(lClosed&&rClosed){
					count = -1;
				}
			}
		}
		//設定好權重分
		data.setCount(count);
	}
}

AI落子處理

- 回圈取橫向、豎向、右捺、左撇 4種分數,放到List集合中
- 對集合進行排序(分數從高到底)
- 第一個分數值作為下一步落子的位置
- 落子操作(如果集合沒有值,則進行隨機落子)

//進行下一步
static boolean go(GamePanel gamePanel){
	
	List<Data> datas=new ArrayList<Data>();
	//回圈找出黑棋,判斷此棋子的1橫向  2縱向  3右捺  4左撇 是否有4子的情況,
	Pointer pointer;
	for (int i = 0; i <gamePanel.ROWS; i++) {
		for (int j = 0; j < gamePanel.COLS; j++) {
			pointer = gamePanel.points[i][j];
			if(pointer==null)continue;
			if(pointer.getQizi()==0){//沒有棋子則跳過
				continue;
			}
			//回圈4個方向
			int dir=1;
			for (int k = 1; k <= 4; k++) {
				dir = k;
				Data data = getData(pointer, dir,1, gamePanel);
				if(data.getCount()!=-1&&data.getCount()!=0){//0和-1 的過濾掉
					datas.add(data);
				}
				data = getData(pointer, dir, 2,gamePanel);
				if(data.getCount()!=-1&&data.getCount()!=0){//0和-1 的過濾掉
					datas.add(data);
				}
			}
		}
	}
	//按權重分排序處理,從大到小
	Collections.sort(datas, new DataCount());
	
	/*for (int i = 0; i < datas.size(); i++) {
		System.out.println("----------"+datas.get(i).getCount());
	}*/
	if(datas.size()>0){//取第一個位置落子
		Data data = datas.get(0);
		Pointer p = gamePanel.points[data.getI()][data.getJ()];
		luozi(p, gamePanel, 1);
		return true;
	}
	
	return false;
}

五子或者以上的判斷就很簡單了,當棋子是連續的并且計數器大于5就成功了!

這里只介紹了橫向的,另外3個情況也差不多,就是注意下標的處理即可,

在這里插入圖片描述

最后

1. AI還不是特別智能,應該算簡單版吧,贏的難度不大.
2. 可能會有我沒發現的bug吧,望理解!

看到這里的大佬,動動發財的小手 點贊 + 回復 + 收藏,能【 關注 】一波就更好了,

相關閱讀

1. Java俄羅斯方塊
2. 老Java程式員花2天寫了個連連看
3. 老Java程式員花一天時間寫了個飛機大戰
4. JavaWeb圖書管理系統
5. JavaWeb學生宿舍管理系統
6. JavaWeb在線考試系統

為了幫助更多小白從零進階 Java 工程師,從CSDN官方那邊搞來了一套 《Java 工程師學習成長知識圖譜》,尺寸 870mm x 560mm,展開后有一張辦公桌大小,也可以折疊成一本書的尺寸,原件129元現價 29 元先到先得,有興趣的小伙伴可以了解一下

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/289576.html

標籤:java

上一篇:奉勸各位準大一的學弟學妹們,這個暑假只要作對一件事,大學的時候你就是王者!

下一篇:? Python入門 ? 1?? 環境配置 ?? Hello World

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more