继上周实现了C++控制台版的五子棋之后,这周开始学习Java,顺便花了两三天时间,做出了一直想做的图形化界面的五子棋小游戏。同时在原来C++控制台程序的基础上对AI的算法进行了一定修正,修复了一些bug,并加入了悔棋的功能
C++五子棋系列传送门:
C++之简单五子棋的设计思路
C++之简单五子棋的语言设计实现
C++简单五子棋的AI设计及实现
五子棋的算法规则和类的设计在C++相关中讲过了,所以现在主要总结一下如何转成图形化界面。由于第一次接触java,顶层设计做的不是很好,还是带有浓厚的面向过程的设计风格,勉强合格吧。
效果如下
界面主要使用swing组件搭建,具体如下:
MyChessBoard :面板类,继承JPanel类。主要用来实现存储棋盘信息,记录下棋过程和显示棋局。考虑到各个组件都要对棋盘进行操作,所以对棋局进行记录操作的相关函数和变量均设为静态类成员。具体包括:
除此之外,在构造函数中对各变量进行初始化,lastChess[][]初始化为全变量-1,有棋子落子时在record函数里对其进行更新,采用队列的数据结构,存储落子的坐标。
重写父类的paintComponent用来完成棋盘和棋子的绘制。
setChess(int,int,int)函数用来接收鼠标点击的坐标和当前所下的棋子颜色,通过点击坐标计算出落子坐标,调用record函数进行记录,调用repaint函数进行重绘。
//面板,用来打印15*15的棋盘和当前在棋盘上的棋子,提供一个15*15的int数组,标记每个节点处的棋子状态
class MyChessBoard extends JPanel{
protected BufferedImage bg = null;
static public int[][] chess ;//0为空,1为白,2为黑
static protected int [][] lastChess = {{-1,-1},{-1,-1}};
static protected boolean isEmpty = true;
static public void record(int x,int y) {
lastChess[1][0] = lastChess[0][0];
lastChess[1][1] = lastChess[0][1];
lastChess[0][0] = x;
lastChess[0][1] = y;
ChessGame.reserve();
MyControlBoard.redo = false;
MyControlBoard.undo.setText("Undo");
isEmpty = false;
}
// default constructor
public MyChessBoard(){
super();
lastChess[0][0] = -1;
lastChess[0][1] = -1;
lastChess[1][0] = -1;
lastChess[1][1] = -1;
chess = new int[15][15];
for(int i =0;i < 15;i++) {
for(int j = 0;j < 15;j++) {
chess[i][j] = 0;
}
}
try {
bg = ImageIO.read(new File("D:\\administrator-\\Documents\\eclipse-workspace\\practice\\src\\Windows\\renju.png"));
}catch(Exception e) {
e.printStackTrace();
}
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(new Color(222,184,135));
Graphics2D g1 = (Graphics2D)g;
//add background
g1.drawImage(bg, 0, 0,getWidth(),getHeight(), null);
//draw chess board
g1.setColor(Color.red);
BasicStroke seg = new BasicStroke(2,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
g1.setStroke(seg);
int width = getWidth();
int height = getHeight();
int vgap = height/16;
int hgap = width/16;
for(int i = 1;i<16;i++) {
g1.drawLine(hgap, i*vgap, 15*hgap, i*vgap);
g1.drawLine(i*hgap,vgap , i*hgap, 15*vgap);
}
//draw chess
int chessWidth = hgap*3/4;
int chessHeight = vgap*3/4;
for(int i = 0;i < 15;i++) {
for(int j = 0;j < 15;j++) {
if(chess[i][j] == 2) {
g1.setColor(Color.BLACK);
g1.fillOval((1+j)*hgap-chessWidth/2,(1+i)*vgap-chessHeight/2,chessWidth, chessHeight);
}
else{
if(chess[i][j] == 1) {
g1.setColor(Color.white);
g1.fillOval((1+j)*hgap-chessWidth/2,(1+i)*vgap-chessHeight/2,chessWidth, chessHeight);
}
}
}
}
}
//清空棋盘并重绘
static public void clearBoard() {
isEmpty = true;
lastChess[0][0] = -1;
lastChess[0][1] = -1;
lastChess[1][0] = -1;
lastChess[1][1] = -1;
for(int i =0;i < 15;i++) {
for(int j = 0;j < 15;j++) {
chess[i][j] = 0;
}
}
}
//悔棋
static public void reDo() {
int count = 0;
for(int i =0;i < 15;i++) {
for(int j = 0;j < 15;j++) {
if(chess[i][j] != 0)
count++;
}
}
if(count == 1) {
chess[lastChess[0][0]][lastChess[0][1]] = 0;
ChessGame.reserve();
lastChess[0][0] = -1;
lastChess[0][1] = -1;
isEmpty = true;
}
else{
if(count == 2) isEmpty = true;
chess[lastChess[0][0]][lastChess[0][1]] = 0;
chess[lastChess[1][0]][lastChess[1][1]] = 0;
lastChess[1][0] = -1;
lastChess[1][1] = -1;
lastChess[0][0] = -1;
lastChess[0][1] = -1;
}
}
//绘制一颗给定颜色和坐标的棋子
public void setChess(int x,int y,int white) {
int width = getWidth();
int height = getHeight();
int vgap = height/16;
int hgap = width/16;
int chessWidth = hgap*3/4;
int chessHeight = vgap*3/4;
for(int i = 0;i < 15;i++) {
for(int j = 0;j < 15;j++) {
if( x >= ((1 + j)*hgap - chessWidth/2) && x <= ((1 + j)*hgap + chessWidth/2)) {
if(y >= ((1 + i)*vgap - chessHeight/2) && y <= ((1 + i)*vgap + chessHeight/2)) {
while(chess[i][j] == 0) {
if(white == 1)
chess[i][j] = 1;
else
chess[i][j] = 2;
record(i, j);
return;
}
}
}
}
}
repaint();
}
}
//Control Board,控制开局,认输和投降,分别对应三个布尔变量started,redo,surrendered,并且提供int型变量whiteC标记先后手
@SuppressWarnings("serial")
class MyControlBoard extends JPanel{
protected static JButton start;
protected JButton giveUp;
protected static JButton undo;
protected JLabel b1,b2;
protected ButtonAct cbut = new ButtonAct();
static boolean started = false;
static boolean redo = false;
static boolean surrendered = false;
static int whiteC = 0; // 为0表示用户执黑先手,为1表示用户执白后走
//constructor
public MyControlBoard(){
super();
this.setBackground(Color.darkGray);
GridLayout bl = new GridLayout(5,1);
this.setLayout(bl);
b1 = new JLabel();
b2 = new JLabel();
start = new JButton("Start");
start.setBackground(Color.yellow);
start.setSize(getWidth(), getHeight()/10);
start.addActionListener(cbut);
giveUp = new JButton("Give up");
giveUp.setBackground(Color.lightGray);
giveUp.setSize(getWidth(), getHeight()/10);
giveUp.addActionListener(cbut);
undo = new JButton("Undo");
undo.setBackground(Color.cyan);
undo.setSize(getWidth(), getHeight()*6/10);
undo.addActionListener(cbut);
add(start);
add(b1);
add(giveUp);
add(b2);
add(undo);
}
//复原为初始状态
static public void initialize() {
started = false;
redo = false;
surrendered = false;
whiteC = 0;
start.setText("Start");
undo.setText("Undo");
}
private class ButtonAct implements ActionListener {
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
if(e.getActionCommand().equals("Start")) {
start.setText("Started");
started = true;
whiteC = JOptionPane.showConfirmDialog(start, "是否执黑先行?(若选择执白单击棋盘后棋局开始)", "先后手确认", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
}
if(e.getActionCommand().equals("Undo")) {
if(started && !MyChessBoard.isEmpty) {
redo = true;
undo.setText("Undone");
MyChessBoard.reDo();
}
}
if(e.getActionCommand().equals("Give up")) {
if(started) {
JOptionPane.showMessageDialog(giveUp, "你已认输!","结果",JOptionPane.INFORMATION_MESSAGE);
surrendered = true;
started = false;
start.setText("Start");
MyChessBoard.clearBoard();
initialize();
ChessGame.takeTurn = 0;
MyChessBoard.lastChess[1][0] = -1;
MyChessBoard.lastChess[1][1] = -1;
MyChessBoard.lastChess[0][0] = -1;
MyChessBoard.lastChess[0][1] = -1;
}
}
}
}
}
其实这里有一些设计上的失误,因为之后的棋盘事件侦听需要综合棋盘点击和控制面板点击进行判定先后手,所以其实将MyControlBoard类设计为MyChessBoad类的内部类比较好,可以通过设置布局管理器为BorderLayout来实现添加子面板。
接下来设计实现ChessGame类,将各个组件组合在一起,实现下棋流程。并在ChessGame类中实现MyChessBoad类的事件侦听程序。
ChessGame类的静态变量主要包括标记当前轮到哪方落子的交替标志量takeTurn和对其进行反转的函数rserve();record函数每一次记录的时候都调用reserve函数反转标志量,从而使得每次会接受一次鼠标点击。
在ChessGame中有用来检查是否有连成五个的CheckFive函数和用来判断局势的judge函数。并定义内部类ClickAct继承MouseAdapter适配器,用来对鼠标点击做出反应。在该类中定义一个初始化为真的布尔变量goAI,用来区分先后手,每次鼠标点击之后,先进行先后手检测,当棋局已开局、棋盘为空且用户棋子颜色与反转标志量不一致,则调用AI进行走棋,同时goAI值置为假,当goAI值为真时,则先接收用户点击,计算出点击坐标对应的棋子坐标,调用setChess函数,再调用judge函数判断局势,如果没有获胜方,再调用AI下棋,再进行局势判断,再根据局势弹出相应对话框或者什么都不做。
代码如下:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class ChessGame {
JFrame frame;
MyChessBoard chessBoard;//棋盘面板
MyControlBoard control;//控制面板
static int takeTurn = 0;//交替标志量,记录当前轮到哪方落子
//反转交替标志量
static public void reserve() {
if(takeTurn == 0)
takeTurn = 1;
else
takeTurn = 0;
}
public ChessGame() {
frame =new JFrame("五子棋");
frame.setSize(850, 710);
control = new MyControlBoard();
frame.getContentPane().add(control,BorderLayout.EAST);
control.setVisible(true);
chessBoard = new MyChessBoard();
}
public void play() {
ClickAct playChess = new ClickAct();
frame.getContentPane().add(chessBoard,BorderLayout.CENTER);
chessBoard.repaint();
chessBoard.setVisible(true);
chessBoard.addMouseListener(playChess);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//判断棋局形式,返回-1继续,0表示平局,1表示白棋获胜,2表示黑棋获胜
private int judge() {
boolean full = true;
if(checkFive() >= 5) {
return MyChessBoard.chess[MyChessBoard.lastChess[0][0]][MyChessBoard.lastChess[0][1]];
}
loop: for(int i = 0;i < 15;i++) {
for(int j = 0;j < 15;j++)
{
if(MyChessBoard.chess[i][j] == 0){
full = false;
break loop;
}
}
}
if(full) {
return 0;
}
else {
return -1;
}
}
//检查连子情况
private int checkFive()
{
int x = MyChessBoard.lastChess[0][0], y = MyChessBoard.lastChess[0][1];
if(x <= 0 || x >= 15)
return 1;
if(y <= 0 || y >= 15)
return 1;
int bd[][] = MyChessBoard.chess;
if(MyChessBoard.isEmpty)
return 0;
int count = 1;//计数器,统计同色个数
int sum[] = {0,0,0,0};
boolean locked = false;//逻辑标记量,用来标记是否遇到了非同色节点
//水平方向检测
for(int i = 1;i < 5 && ((x - i) >= 0) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
{
if(bd[x][y] == bd[x - i][y]) {
count++;
}
else
locked = true;
}
locked = false;
for(int i = 1;i < 5&&((x + i) <= 14) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x + i][y])
count++;
else
locked = true;
sum[0]=count;
if(count >= 5)
return count;
//竖直方向检测
count = 1;
locked = false;
for(int i = 1;i < 5 && ((y - i) >= 0) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x][y - i])
count++;
else
locked = true;
locked = false;
for(int i = 1;i < 5 && ((y + i) <= 14) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x][y + i])
count++;
else
locked = true;
sum[1]=count;
if(count>=5)
return count;
//左上到右下斜向检测
count = 1;
locked = false;
for(int i = 1;i < 5 && ((y - i) >= 0) && ((x - i) >= 0) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x - i][y - i])
count++;
else
locked = true;
locked = false;
for(int i = 1;i < 5 && ((x + i) <= 14) && ((y + i) <= 14) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x + i][y + i])
count++;
else
locked = true;
sum[2] = count;
if(count >= 5)
return count;
//左下到右上斜向检测
count = 1;
locked = false;
for(int i = 1;i < 5 && ((y + i) <= 14) && ((x - i) >= 0) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x - i][y + i])
count++;
else
locked = true;
locked = false;
for(int i = 1;i < 5 && ((x + i) <= 14) && ((y - i) >= 0) && (!locked);i++)//终止循环条件:同色超过4个或触碰到棋盘边界或遇到非同色节点
if(bd[x][y] == bd[x + i][y - i])
count++;
else
locked = true;
sum[3] = count;
if(count >= 5)
return count;
return MAX(sum,4);
}
//求最值
private int MAX(int[] a, int n) {
int max = a[0];
for(int i =1; i < n ;i++)
{
if(a[i] > max)
max = a[i];
}
return max;
}
//棋盘的事件响应类
protected class ClickAct extends MouseAdapter{
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
int state = judge();
AI ai = new AI();
boolean goAI = true;
if(MyControlBoard.started && MyControlBoard.whiteC == 1 && MyChessBoard.isEmpty) {
ai.play();
chessBoard.repaint();
goAI = false;
}
if(MyControlBoard.started && (takeTurn == MyControlBoard.whiteC)&&goAI)
{
chessBoard.setChess(x, y, MyControlBoard.whiteC);
state = judge();
if((state == -1) && (takeTurn != MyControlBoard.whiteC)&&goAI) {
ai.play();
chessBoard.repaint();
state = judge();
// System.out.println("state="+state);
}
switch(state) {
case 1:
if(MyControlBoard.whiteC == 1)
JOptionPane.showMessageDialog(frame, "恭喜:你赢了!","结果",JOptionPane.INFORMATION_MESSAGE);
else
JOptionPane.showMessageDialog(frame, "抱歉:你输了!","结果",JOptionPane.INFORMATION_MESSAGE);
MyControlBoard.surrendered = true;
MyControlBoard.started = false;
MyControlBoard.start.setText("Start");
MyChessBoard.clearBoard();
MyControlBoard.initialize();
MyChessBoard.lastChess[1][0] = -1;
MyChessBoard.lastChess[1][1] = -1;
MyChessBoard.lastChess[0][0] = -1;
MyChessBoard.lastChess[0][1] = -1;
takeTurn = 0;
break;
case 2:
if(MyControlBoard.whiteC == 0)
JOptionPane.showMessageDialog(frame, "恭喜:你赢了!","结果",JOptionPane.INFORMATION_MESSAGE);
else
JOptionPane.showMessageDialog(frame, "抱歉:你输了!","结果",JOptionPane.INFORMATION_MESSAGE);
MyControlBoard.surrendered = true;
MyControlBoard.started = false;
MyControlBoard.start.setText("Start");
MyChessBoard.clearBoard();
MyControlBoard.initialize();
MyChessBoard.lastChess[1][0] = -1;
MyChessBoard.lastChess[1][1] = -1;
MyChessBoard.lastChess[0][0] = -1;
MyChessBoard.lastChess[0][1] = -1;
takeTurn = 0;
break;
case 0:
JOptionPane.showMessageDialog(frame, "平局!","结果",JOptionPane.INFORMATION_MESSAGE);
MyControlBoard.surrendered = true;
MyControlBoard.started = false;
MyControlBoard.start.setText("Start");
MyChessBoard.clearBoard();
MyControlBoard.initialize();
MyChessBoard.lastChess[1][0] = -1;
MyChessBoard.lastChess[1][1] = -1;
MyChessBoard.lastChess[0][0] = -1;
MyChessBoard.lastChess[0][1] = -1;
takeTurn = 0;
break;
default:
break;
}
}
}
}
}
AI类和main类实现下篇继续