1. 概述
有一天突然挺无聊的, 就想着去熟悉下JAVA的界面程序开发. 平常虽然都是做WEB开发, 但想来思路都是差不多的. 于是就查看了下Swing的介绍以及一些基本的组件. 但由于工作原因, 想起的时候就写一点, 想起的时候就写一点, 直到今天才做完. 其实整个游戏是很简单的, 如果从头开始做(指的是无Swing基础), 我觉得差不多要用3天时间去完成, 主要耗时间的点在对组建的尝试, 毕竟和WEB的布局还是有差异的. 源代码大家可以去我的资源里面下载.
2. 开发环境
操作系统: Ubuntu 18.04
JDK: openjdk version "17.0.7" 2023-04-18
Eclipse: Version: 2018-12 (4.10.0) (比较古老了, 但一直懒的换)
Maven: 3.3.9 (一样古老的版本, 大家自己可以用最新的版本)
3. 效果展示
3.1 棋盘布局
3.1.1 重新开始按钮
点击该按钮会清空棋盘上的棋子, 并把参数初始化到默认值.
// 按钮添加点击事件
gameStartBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GameUI.clearPieces();
}
});
...
// 遍历棋盘上的棋子进行清除
public static void clearPieces() {
ChessPieceDropArea.chessPieceColor = 0;
for(int i = 0; i < 19; i++) {
for(int j = 0; j < 19; j++) {
ChessPieceDropArea chessPieceDropAreaObj = chessPieceDropArea[i][j];
chessPieceDropAreaObj.isDroped = false;
chessPieceDropAreaObj.isWhite = false;
chessPieceDropAreaObj.repaint();
showInfoOfDropBlackPiece();
}
}
}
3.1.2 信息Label
显示该下什么颜色的棋子, 默认黑棋先下. 每次落子后会修改该信息.
3.1.3 下棋区域
19*19的围棋棋盘, 并在对应的位置画上了星(黑色点点). 这里主要用到JPanel的paintComponent方法, 重写该方法, 在对应的位置画圆.
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = this.getWidth();
int height = this.getHeight();
for(int i = 1; i <= 19; i++) {
g.drawLine(chessPieceDiameter, chessPieceDiameter * i, width - chessPieceDiameter, chessPieceDiameter * i);
}
for(int i = 1; i <= 19; i++) {
g.drawLine(chessPieceDiameter * i, chessPieceDiameter, chessPieceDiameter * i, height - chessPieceDiameter);
}
//
int startRadio = 5;
int startWidth = 2 * startRadio;
// 左上角星
g.fillOval(chessPieceDiameter * 4 - startRadio, chessPieceDiameter * 4 - startRadio, startWidth, startWidth);
// 上中星
g.fillOval(chessPieceDiameter * 10 - startRadio, chessPieceDiameter * 4 - startRadio, startWidth, startWidth);
// 右上角星
g.fillOval(chessPieceDiameter * 16 - startRadio, chessPieceDiameter * 4 - startRadio, startWidth, startWidth);
// 中左星
g.fillOval(chessPieceDiameter * 4 - startRadio, chessPieceDiameter * 10 - startRadio, startWidth, startWidth);
// 在天元处画一个点
g.fillOval(chessPieceDiameter * 10 - startRadio, chessPieceDiameter * 10 - startRadio, startWidth, startWidth);
// 中右星
g.fillOval(chessPieceDiameter * 16 - startRadio, chessPieceDiameter * 10 - startRadio, startWidth, startWidth);
// 下左星
g.fillOval(chessPieceDiameter * 4 - startRadio, chessPieceDiameter * 16 - startRadio, startWidth, startWidth);
// 下中星
g.fillOval(chessPieceDiameter * 10 - startRadio, chessPieceDiameter * 16 - startRadio, startWidth, startWidth);
// 下右星
g.fillOval(chessPieceDiameter * 16 - startRadio, chessPieceDiameter * 16 - startRadio, startWidth, startWidth);
}
3.2 下棋
3.2.1 落子区
在棋盘之上添加了19*19个落子的JPanel区域, 每个区域默认设定是透明的. 当鼠标点击该区域后, 应用会根据当前落子的颜色绘制相应的棋子. 这里默认设置的棋子的直径为40.
// 画落子区
chessPieceDropArea = new ChessPieceDropArea[19][19];
for(int i = 0; i < 19; i++) {
for(int j = 0; j < 19; j++) {
ChessPieceDropArea chessPieceDropAreaObj = new ChessPieceDropArea(i, j, CHESS_PIECE_DIAMETER);
chessPieceDropArea[i][j] = chessPieceDropAreaObj;
chessPieceDropAreaObj.setBounds(CHESS_PIECE_DIAMETER/2+i*CHESS_PIECE_DIAMETER
, CHESS_PIECE_DIAMETER/2+j*CHESS_PIECE_DIAMETER
, CHESS_PIECE_DIAMETER
, CHESS_PIECE_DIAMETER);
chessPieceDropAreaObj.setOpaque(false);
// chessPieceDropAreaObj.setBorder(new LineBorder(Color.black, 1));
chessBoard.add(chessPieceDropAreaObj);
}
3.2.2 棋子
当时的设计是, 落子区触发点击事件后进行重绘, 重绘会触发paintComponent, 然后我们就可以去根据条件进行绘制棋子了.
@Override
public void mouseClicked(MouseEvent e) {
if(isDroped) {
// 已经有棋子不可以再点击
return;
}
isDroped = true;
if(chessPieceColor == 0) {
isWhite = false;
chessPieceColor = 1;
GameUI.showInfoOfDropWhitePiece();
}else {
isWhite = true;
chessPieceColor = 0;
GameUI.showInfoOfDropBlackPiece();
}
this.repaint();
// 判断结果
if(GameUI.judge(this.isWhite)) {
//游戏结束
if(this.isWhite) {
JOptionPane.showMessageDialog(null, "白棋赢", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
}else {
JOptionPane.showMessageDialog(null, "黑棋赢", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
}
GameUI.clearPieces();
}
}
...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(!isDroped) {
return;
}
// 画一个黑色边框的透明棋子
g.setColor(Color.black);
g.drawOval(0, 0, chessPieceDiameter, chessPieceDiameter);
// 根据落子的颜色进行填充
if (isWhite) {
g.setColor(Color.white);
g.fillOval(0, 0, chessPieceDiameter, chessPieceDiameter);
} else {
g.setColor(Color.black);
g.fillOval(0, 0, chessPieceDiameter, chessPieceDiameter);
}
}
3.3 判定输赢
3.3.1 输赢判断
每当落子后, 都会触发输赢判断. 判断逻辑主要是:
1. 遍历每一行, 确认是否有5子相连
2. 遍历每一列, 确认是否有5子相连
3. 遍历每一条斜线, 确认是否有5子相连
4. 遍历每一条反斜线, 确认是否有5子相连
// true表示已经判断出结果, 对奕结束
public static boolean judge(boolean isWhite) {
System.out.println("isWhite="+isWhite);
// 检查横线上是否有五个相同的棋子
int count = 0;
for(int i = 0; i < 19; i++) {
for(int j = 0; j < 19; j++) {
ChessPieceDropArea chessPieceDropAreaObj = chessPieceDropArea[i][j];
if(!chessPieceDropAreaObj.isDroped || chessPieceDropAreaObj.isWhite != isWhite) {
// 没有落子, 或者已经断开, 重新从0计数
if(count >=5) {
return true;
}
count = 0;
}else {
count++;
if(count >=5) {
return true;
}
}
}
// 新的一行
count = 0;
}
// 检查每一列是否有五个相同的棋子
count = 0;
for(int i = 0; i < 19; i++) {
for(int j = 0; j < 19; j++) {
ChessPieceDropArea chessPieceDropAreaObj = chessPieceDropArea[j][i];
if(!chessPieceDropAreaObj.isDroped || chessPieceDropAreaObj.isWhite != isWhite) {
// 没有落子, 或者已经断开, 重新从0计数
if(count >=5) {
return true;
}
count = 0;
}else {
count++;
if(count >=5) {
return true;
}
}
}
// 新的一行
count = 0;
}
// 检查斜线上是否有五个相同的棋子
count = 0;
for(int i = 18; i > 0; i--) {
int tmp = i;
for(int j = 0; j < 19 && tmp < 19; j++) {
ChessPieceDropArea chessPieceDropAreaObj = chessPieceDropArea[tmp][j];
if(!chessPieceDropAreaObj.isDroped || chessPieceDropAreaObj.isWhite != isWhite) {
// 没有落子, 或者已经断开, 重新从0计数
if(count >=5) {
return true;
}
count = 0;
}else {
count++;
if(count >=5) {
return true;
}
}
tmp++;
}
}
// 检查反斜线上是否有五个相同的棋子
count = 0;
for(int i = 0; i < 19; i++) {
int tmp = i;
for(int j = 0; j < 19 && tmp >= 0; j++) {
ChessPieceDropArea chessPieceDropAreaObj = chessPieceDropArea[tmp][j];
if(!chessPieceDropAreaObj.isDroped || chessPieceDropAreaObj.isWhite != isWhite) {
// 没有落子, 或者已经断开, 重新从0计数
if(count >=5) {
return true;
}
count = 0;
}else {
count++;
if(count >=5) {
return true;
}
}
tmp--;
}
}
return false;
}
3.3.2 展示结果
一旦确认输赢, 弹出信息框展示结果. 退出信息框后重新初始化棋盘.
// 判断结果
if(GameUI.judge(this.isWhite)) {
//游戏结束
if(this.isWhite) {
JOptionPane.showMessageDialog(null, "白棋赢", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
}else {
JOptionPane.showMessageDialog(null, "黑棋赢", "游戏结束", JOptionPane.INFORMATION_MESSAGE);
}
GameUI.clearPieces();
}
4. 总结
这只是一个测试项目, 实现了基本功能, 但还有很多优化的项. 尤其设计这块. 如果大家喜欢的话, 一键三连哦.