Java-五子棋1.0
目录:
一.项目介绍
本次五子棋项目用 Java Swing 做图形界面,实现了双人对战、判胜负和悔棋功能。我按功能拆分了类,用数组保存棋盘状态并实现界面刷新,熟练使用事件监听的用法。
主要功能:
黑白两子双人对战,判断胜负,悔棋
主要构成:
-GoBangUI:主界面。负责创建窗口、添加面板、绑定监听器、把监听器 画笔 AI 绑在一起。是程
序执行的入口。
-GoBangListener: 监听器类。负责接收鼠标点击、下棋、切换黑白、调用 AI。
-GoPanel 面板类。负责绘制网格、棋子,实现刷新重绘功能。
-GoData 接口。负责统一共享数据。
-GoWin 只负责判断是否连成五子。
public class GoBangUI {
GoBangListener gbl=new GoBangListener();//创建监听器对象
public void GoUI(){
`````
`````
//传递画笔,绑定监听器
Graphics g = gp.getGraphics(); // 获取棋盘面板的画笔对象
gbl.g = g; // 把画笔交给监听器(监听器用它画棋子)
gp.addMouseListener(gbl); // 给棋盘面板绑定鼠标监听器
}
public static void main(String[] args) {
GoBangUI ui=new GoBangUI();
ui.GoUI();
}
}
public class GoBangListener implements MouseListener, ActionListener,GoData {
`````````
}
public class GoPanel extends JPanel implements GoData{
public void paint (Graphics g){
``````
}
}
public interface GoData {
`````
`````
}
项目实现步骤:
1.绘制网格,添加面板
2.绘制五子棋
3.实现黑白棋子交替
4.实现重绘功能,添加弹窗
5.添加按钮-->按钮“开始游戏 到 结束游戏”的转换。然后点击“开始游戏”按钮,棋盘清空
6.实现判断输赢的功能
7.实现悔棋功能
二.具体实现
1.棋盘的绘制:
(1).在GoBangUI类的GoUI方法中 设置窗口,添加面板和按钮。
public void GoUI(){
JFrame jf=new JFrame();
jf.setTitle("五子棋");
jf.setSize(1050, 800);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//添加面板
GoPanel gp=new GoPanel();
jf.add(gp);
//添加右边面板
JPanel right=new JPanel();
right.setPreferredSize(new Dimension(260,0));
right.setBackground(Color.GRAY);
jf.add(right,BorderLayout.EAST);
String[] text={"开始游戏","悔棋","AI对战"};
for(int i=0;i<text.length;i++){
JButton btn=new JButton(text[i]);
right.add(btn);
//给按钮加点击监听(点按钮会触发动作)。按钮被点击时,交给 gol 处理
btn.addActionListener(gbl);
if(i==0) {
btn.setBackground(Color.GREEN);
}else{
btn.setBackground(Color.WHITE);
}
}
jf.setVisible(true);
(2)在GoPanel类中绘制 棋盘背景和网格
//棋盘背景
g.setColor(new Color(218, 131, 60));
g.fillRect(X - SIZE / 2, Y - SIZE / 2, COL * SIZE + SIZE, ROW * SIZE + SIZE);
g.setColor(Color.BLACK);
//画棋盘线
for(int i=0;i<=ROW;i++){
g.drawLine(X,Y+i*SIZE,X+ROW*SIZE,Y+i*SIZE);
g.drawLine(X+i*SIZE,Y,X+i*SIZE,Y+COL*SIZE);
}
(3).GoPanel为什么要继承JPanel?
1.继承 JPanel 后,拥有画布绘图能力,可以重写 paint 方法,绘制棋盘背景、线条和黑白棋子。
2.成为标准界面组件,能添加到 JFrame 窗口中显示。
3.属于可视组件,可以绑定鼠标监听器,获取鼠标点击位置实现落子。
4.自带 Swing 重绘机制,支持 repaint (),窗口缩放、遮挡、悔棋后能自动刷新重绘画面。
2.五子棋的绘制:
(1)关键一步: 传递画笔,绑定监听器
在GoBangUI类的GoUI方法中:
//传递画笔,绑定监听器
Graphics g = gp.getGraphics(); // 获取棋盘面板的画笔对象
gbl.g = g; // 把画笔交给监听器(监听器用它画棋子)
gp.addMouseListener(gbl); // 给棋盘面板绑定鼠标监听器
(2)确定棋子绘制位置
在GoBangListening的mousePressed方法中:
//获取鼠标点击处的坐标
int x=e.getX();
int y=e.getY();
//进行坐标换算,来确定最终落子的位置
int r=(y - Y + SIZE / 2) / SIZE;
int c=(x - X + SIZE / 2) / SIZE;
(3)绘制棋子
在GoBangListening的mousePressed方法中:
int cx=c * SIZE + X - SIZE / 2;
int cy=r * SIZE + Y - SIZE / 2;
g.fillOval(cx,cy,SIZE,SIZE);

fillRect(x, y, 宽, 高) 画实心矩形。
fillOval(x, y, 宽, 高)画实心椭圆 (当宽 = 高时就是圆)
两个方法的 x 和 y 都是外接矩形的左上角坐标,不是圆心
3.黑白棋子交替功能:
定义 int CHESS_FLAG=0;(初始化CHESS_FLAG=0的原因:当点击“开始游戏”按钮后,CHESS_FLAG被置为1,可以下棋。未点击按钮,为0,不可以下棋)
在GoBangListening的mousePressed方法中:
//1表示黑棋,2表示白棋
if(CHESS_FLAG==1){
g.setColor(Color.BLACK);
CHESS_FLAG=2;//黑棋下完后,CHESS_FLAG被置为2
}else if(CHESS_FLAG==2){
g.setColor(Color.WHITE);
CHESS_FLAG=1;
}
这段代码要在下面这段代码之前,先判断给画笔颜色,再绘制第一颗棋子
g.fillOval(cx,cy,SIZE,SIZE);
4.实现重绘功能
重绘的分两种情况:
1.系统自动重绘:拉动窗口、放大缩小窗口、窗口被遮挡再显示、窗口最小化还原时,Swing 系统会自动调用 paint () 方法,读取最新 chessArr 数组数据,重新绘制棋盘与所有棋子。
2.手动重绘:悔棋、AI 自动下棋、重新开始游戏时,界面不会自动刷新,需要手动调用repaint()方法,主动触发 paint () 执行,根据更新后的数组刷新画面。
原理:
窗口发生界面变化时,Swing 自动调用 paint 方法;
悔棋、AI 下棋等逻辑变化时,手动调用 repaint 触发 paint 方法;
最终都是读取最新 chessArr 数组,重新绘制全部棋盘和棋子。
步骤:
(1).用二维数组保存所有棋子状态(例如chessArr[r][c]记录 0 空 / 1 黑子 / 2 白子)。
-定义chessArr,此时数组中所有元素都为零。(在GoBangListener中:

-在每次绘制棋子之前将 CHESS_FLAG 存入数组中,此时1表示黑棋,2表示白棋,0表示没有棋。(在GoBangListener的mousePressed方法中:
if(chessArr[r][c]==0){
chessArr[r][c]=CHESS_FLAG;//将棋子颜色存入数组中
}
else {
showMessage("请不要重复下棋");
return;
}
(2).在GoPanel的 paint 方法中,根据数组绘制全部棋子。
//遍历数组,将数组中的棋子重绘
for(int i=0;i<chessArr.length;i++){
for(int j=0;j<chessArr.length;j++){
if(chessArr[i][j]!=0) {
if (chessArr[i][j] == 1) {
g.setColor(Color.BLACK);
} else if (chessArr[i][j] == 2) {
g.setColor(Color.WHITE);
}
int cx, cy;
cx = j * SIZE + X - SIZE / 2;
cy = i * SIZE + Y - SIZE / 2;
g.fillOval(cx, cy, SIZE, SIZE);
}
}
}
注意:
1.鼠标点击时,只更新数组,然后调用 repaint() 触发重绘
2.鼠标mousePressed里画的棋子是临时的,只有将棋子信息保存到数组里才能永久重绘(每次刷新窗口,系统自动调用GoPanel里的paint()方法 )
3.重绘分两种:系统自动重绘、代码手动重绘
5.判断输赢
1.在GoWin类中 判断“是否赢了”:
每次落下一颗棋子,就检查这颗棋子的 "上下、左右、两个斜方向" 有没有连成 5 个同色棋子。只
要任意一个方向>=5 个,就判定赢棋。
以上下方向为例:
向最后一颗棋子的 上下两端延伸统计 连续同色棋子数量。若遇到不同色的,就停止循环。最终返回count的值。
//纵向
public int col(int[][] chessArr,int r,int c){
int count=1;
//向上查找
for(int i=r-1;i>=0;i--){
if(chessArr[i][c]==chessArr[r][c]) count++;
else {break;}
}
//向下查找
for(int i=r+1;i<chessArr.length;i++){
if(chessArr[i][c]==chessArr[r][c]) count++;
else {break;}
}
return count;
}
2.在GoBanglistener类中 再判断是“什么颜色的棋子赢了”:
mousePressed方法中:
若最后下的一颗棋是黑棋,则黑棋赢。反之,白棋赢。
//判断输赢
GoWin win=new GoWin();
if(win.checkWin(chessArr,r,c)){
if(chessArr[r][c]==1){
showMessage("黑棋赢");
}else if(chessArr[r][c]==2){
showMessage("白棋赢");
}
CHESS_FLAG = 0; // 禁止继续下棋
}
6.悔棋
关键步骤:
-记住上一步下在哪
-点悔棋时把数组里那颗棋子删掉
-通知面板刷新画面
(1).在GoBanglistener中加上:
//初始化为-1,表示没有棋子可以悔棋
int lastR = -1;
int lastC = -1;
// 拿到棋盘面板,用来刷新画面
GoPanel panel;
GoPanel panel; 的目的和作用 :
把棋盘面板拿进监听器里,悔棋、AI 下棋时方便调用 repaint () 刷新画面
(2).在 chessArr[r][c]=CHESS_FLAG; 下面加两行:
// 把当前落子位置记下来,给悔棋用
lastR = r;
lastC = c;
你每点一次鼠标下棋,就自动把行 r、列 c存起来,后面点悔棋,就知道删哪个位置的棋子。
注意:
不可以加在
int r=(y - Y + SIZE / 2) / SIZE;
int c=(x - X + SIZE / 2) / SIZE;
这个后面
原因:可能会记录 超出棋盘、重复下棋 的坐标,导致无法功能混乱
(3).写悔棋方法:
// 悔棋功能方法
public void huiQi(){
// 如果没点开始游戏,不让悔棋
if(CHESS_FLAG == 0){
showMessage("请先开始游戏");
return;
}
// 还没下过棋,不能悔棋
if(lastR == -1 || lastC == -1){
showMessage("还没有棋子可以悔棋");
return;
}
// 1. 把上一步棋子从数组清空(相当于拿走棋子)
chessArr[lastR][lastC] = 0;
// 2. 回合倒回去:刚才黑下的,变回黑下;白下的变回白下
if(CHESS_FLAG == 1){
CHESS_FLAG = 2;
}else{
CHESS_FLAG = 1;
}
// 3. 清空记录,防止重复悔棋
lastR = -1;
lastC = -1;
// 4. 刷新棋盘画面,自动重新画所有棋子
panel.repaint();
showMessage("悔棋成功");
}
repaint与paint的关系:
repaint是Swing自带的刷新方法,paint是自己写的方法
你不能直接调用 paint (),你只能 喊 repaint (),系统自动帮你调用 paint ()
repaint 是发通知,paint 是真正画图;你调用 repaint,系统自动帮你跑 paint。

自动执行paint的时候(此时不用写repaint):窗口的 放大、缩小、被别的窗口挡住再移开、最小化再还原
不会自动执行(要写repaint):悔棋、AI 自动下棋、清空棋盘
(4).绑定按钮
else if (a.equals("悔棋")) {
huiQi();
}
(5).把棋盘面板传给监听器
在GoBangUI中加上:
gbl.panel = gp;
解释:GoBangUI里创建了棋盘GoPanel gp ,把棋盘 gp 交给监听器里的 panel 变量,这样悔棋时才能调用 panel.repaint() 刷新画面。
三.完整代码
GoBangUI:
package 五子棋3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class GoBangUI {
GoBangListener gbl=new GoBangListener();
public void GoUI(){
JFrame jf=new JFrame();
jf.setTitle("五子棋");
jf.setSize(1050, 800);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//添加面板
GoPanel gp=new GoPanel();
jf.add(gp);
//添加右边面板
JPanel right=new JPanel();
right.setPreferredSize(new Dimension(260,0));
right.setBackground(Color.GRAY);
jf.add(right,BorderLayout.EAST);
String[] text={"开始游戏","悔棋","AI对战"};
for(int i=0;i<text.length;i++){
JButton btn=new JButton(text[i]);
right.add(btn);
//给按钮加点击监听(点按钮会触发动作)。按钮被点击时,交给 gol 处理
btn.addActionListener(gbl);
if(i==0) {
btn.setBackground(Color.GREEN);
}else{
btn.setBackground(Color.WHITE);
}
}
jf.setVisible(true);
//传递画笔,绑定监听器
Graphics g = gp.getGraphics(); // 获取棋盘面板的画笔对象
gbl.g = g; // 把画笔交给监听器(监听器用它画棋子)
gp.addMouseListener(gbl); // 给棋盘面板绑定鼠标监听器
gp.setChessArr(gbl.chessArr); // 把监听器里的真实棋盘 交给面板画画
gbl.panel = gp;
}
public static void main(String[] args) {
GoBangUI ui=new GoBangUI();
ui.GoUI();
}
}
GoBangListener:
package 五子棋3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class GoBangListener implements MouseListener, ActionListener,GoData {
Graphics g;
int CHESS_FLAG=0;
int[][] chessArr=new int[ROW+1][COL+1];
// 记住上一步棋子的行、列
int lastR = -1;
int lastC = -1;
// 拿到棋盘面板,用来刷新画面
GoPanel panel;
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
if(CHESS_FLAG==0){
showMessage("请点开始游戏");
return;
}
int x=e.getX();
int y=e.getY();
System.out.println("按下~");
System.out.println("x=" + x + " y=" + y);
//坐标换算,目的是确定落子位置
int r=(y - Y + SIZE / 2) / SIZE;
int c=(x - X + SIZE / 2) / SIZE;
System.out.println(r+" "+c);
if (r > ROW || c > COL) {
showMessage("请在棋盘范围内下棋");
return;
}
//绘制棋子
if(chessArr[r][c]==0){
chessArr[r][c]=CHESS_FLAG;//将棋子颜色存入数组中
// 把当前落子位置记下来,给悔棋用
lastR = r;
lastC = c;
}
else {
showMessage("请不要重复下棋");
return;
}
if(CHESS_FLAG==1){
g.setColor(Color.BLACK);
CHESS_FLAG=2;
}else if(CHESS_FLAG==2){
g.setColor(Color.WHITE);
CHESS_FLAG=1;
}
int cx=c * SIZE + X - SIZE / 2;
int cy=r * SIZE + Y - SIZE / 2;
g.fillOval(cx,cy,SIZE,SIZE);
//判断输赢
GoWin win=new GoWin();
if(win.checkWin(chessArr,r,c)){
if(chessArr[r][c]==1){
showMessage("黑棋赢");
}else if(chessArr[r][c]==2){
showMessage("白棋赢");
}
CHESS_FLAG = 0; // 禁止继续下棋
}
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void actionPerformed(ActionEvent e) {
String a = e.getActionCommand();
JButton btn=(JButton) e.getSource();
if(a.equals("开始游戏")){
for(int i=0;i<=ROW;i++){
for(int j=0;j<=COL;j++){
if(chessArr[i][j]!=0){
chessArr[i][j]=0;
}
}
}
//重新绘制棋盘,覆盖之前的棋盘,实现重绘
g.setColor(new Color(218, 131, 60));
g.fillRect(X - SIZE / 2, Y - SIZE / 2, COL * SIZE + SIZE, ROW * SIZE + SIZE);
g.setColor(Color.BLACK);
for (int i = 0; i <= ROW; i++) {
g.drawLine(X, Y + i * SIZE, X + COL * SIZE, Y + i * SIZE);
g.drawLine(X + i * SIZE, Y, X + i * SIZE, Y + ROW * SIZE);
}
btn.setText("结束对局");
CHESS_FLAG=1;
btn.setBackground(Color.RED);
}
else if (a.equals("结束对局")) {
btn.setText("开始游戏");
btn.setBackground(Color.GREEN);
CHESS_FLAG=0;
showMessage("游戏已结束");
} else if (a.equals("悔棋")) {
huiQi();
} else if (a.equals("AI对战")) {
}
}
// 悔棋功能方法
public void huiQi(){
// 如果没点开始游戏,不让悔棋
if(CHESS_FLAG == 0){
showMessage("请先开始游戏");
return;
}
// 还没下过棋,不能悔棋
if(lastR == -1 || lastC == -1){
showMessage("还没有棋子可以悔棋");
return;
}
// 1. 把上一步棋子从数组清空(相当于拿走棋子)
chessArr[lastR][lastC] = 0;
// 2. 回合倒回去:刚才黑下的,变回黑下;白下的变回白下
if(CHESS_FLAG == 1){
CHESS_FLAG = 2;
}else{
CHESS_FLAG = 1;
}
// 3. 清空记录,防止重复悔棋
lastR = -1;
lastC = -1;
// 4. 刷新棋盘画面,自动重新画所有棋子
panel.repaint();//强制调用GoPanel里的paint()方法,让GoPanel重新画一遍
showMessage("悔棋成功");
}
}
GoPanel:
package 五子棋3;
import javax.swing.*;
import java.awt.*;
public class GoPanel extends JPanel implements GoData{
// 在这里加一个数组,用来接收
int[][] chessArr;
// 加一个方法,让外面把数组传进来
public void setChessArr(int[][] chessArr) {
this.chessArr = chessArr;
}
public void paint (Graphics g){
super.paint(g);
//棋盘背景
g.setColor(new Color(218, 131, 60));
g.fillRect(X - SIZE / 2, Y - SIZE / 2, COL * SIZE + SIZE, ROW * SIZE + SIZE);
g.setColor(Color.BLACK);
//画棋盘线
for(int i=0;i<=ROW;i++){
g.drawLine(X,Y+i*SIZE,X+ROW*SIZE,Y+i*SIZE);
g.drawLine(X+i*SIZE,Y,X+i*SIZE,Y+COL*SIZE);
}
//遍历数组,将数组中的棋子重绘
for(int i=0;i<chessArr.length;i++){
for(int j=0;j<chessArr.length;j++){
if(chessArr[i][j]!=0) {
if (chessArr[i][j] == 1) {
g.setColor(Color.BLACK);
} else if (chessArr[i][j] == 2) {
g.setColor(Color.WHITE);
}
int cx, cy;
cx = j * SIZE + X - SIZE / 2;
cy = i * SIZE + Y - SIZE / 2;
g.fillOval(cx, cy, SIZE, SIZE);
}
}
}
}
}
GoData:
package 五子棋3;
import javax.swing.*;
public interface GoData {
//棋盘左上角坐标
int X=50;
int Y=50;
//每个格子的大小
int SIZE=40;
//棋盘15*15格
int ROW=15;//15行
int COL=15;//15列
public default void showMessage(String msg){
JOptionPane.showMessageDialog(null,msg);
}
}
GoWin:
package 五子棋3;
public class GoWin {
public boolean checkWin(int[][] chessArr,int r,int c) {//注意:这里的chessArr是形参,和前面的chessArr不是一个
if (col(chessArr, r, c) >= 5 || row(chessArr, r, c) >= 5 ||
leftUp(chessArr, r, c) >= 5 || rightUp(chessArr, r, c) >= 5) {
return true;
} else {
return false;
}
}
//纵向
public int col(int[][] chessArr,int r,int c){
int count=1;
//向上查找
for(int i=r-1;i>=0;i--){
if(chessArr[i][c]==chessArr[r][c]) count++;
else {break;}
}
//向下查找
for(int i=r+1;i<chessArr.length;i++){
if(chessArr[i][c]==chessArr[r][c]) count++;
else {break;}
}
return count;
}
//横向
public int row(int[][] chessArr,int r,int c){
int count=1;
//向左查找
for(int i=c-1;i>=0;i--){
if(chessArr[r][i]==chessArr[r][c]) count++;
else {break;}
}
//向右查找
for(int i=c+1;i<chessArr[0].length;i++){
if(chessArr[r][i]==chessArr[r][c]) count++;
else {break;}
}
return count;
}
//左上-右下
public int leftUp(int[][] chessArr,int r,int c) {
int count=1;
//左上找
for(int i=r-1,j=c-1;i>=0&&j>=0;i--,j--){
if(chessArr[i][j]==chessArr[r][c]) {
count++;
}
else{break;}
}
//右下找
for(int i=r+1,j=c+1;i<chessArr.length&&j<chessArr[0].length;i++,j++){
if(chessArr[i][j]==chessArr[r][c]) {
count++;
}else{break;}
}
return count;
}
//右上-左下
public int rightUp(int[][] chessArr,int r,int c) {
int count=1;
//右上找
for(int i=r-1,j=c+1;i>=0&&j<chessArr[0].length;i--,j++){
if(chessArr[i][j]==chessArr[r][c]) {
count++;
}
else{break;}
}
//左下找
for(int i=r+1,j=c-1;i<chessArr.length&&j>=0;i++,j--){
if(chessArr[i][j]==chessArr[r][c]) {
count++;
}else{break;}
}
return count;
}
}
四.思考:
关于GoData接口
错误做法:
在GoData接口中定义数组chessArr:
int[][] chessArr=new int[ROW+1][COL+1];
注意:不能在GoData接口里定义数组,这是错误的
因为:接口里的变量 → 默认是 public static final(常量)常量数组 = 一旦创建,永远不能真正清空、永远不能重新初始化所以你点 “开始游戏” 清空棋盘是无效的!
接口只放常量和抽象规则,不放数组。接口更像是一份“契约”或“菜单”,它应该用来定义 “能做什么” (比如 showMessage 弹窗),而不是去实现 “怎么做” 的具体业务逻辑(比如怎么用 for 循环去遍历清空)。
正确的做法:
1. 在 GoBangListener 里创建数组chessArr
2. 给 GoPanel 加一个 “接收数组” 的方法

3.关键一步:GoBangUI 里把数组传给面板

总结:
接口只放常量,不放数组
数组在监听器里定义,面板需要时,从监听器拿过去
更多推荐


所有评论(0)