目录
一、团队成员及任务
队长:
高晔川 计科(智能)22-1 202203200035
负责模块:Java Swing界面搭建,Graphic 2D图形绘制,Panel面板搭建,五子棋过程的实现构思
面向对象程序设计(Java) 课程设计——三少五子棋 Part1_Aimless.的博客-CSDN博客
队员:
戚彦良 计科(智能)22-1 202203200029
负责模块: 排行榜功能的实现 负责函数算法的编写,使用函数方法体判断哪方获胜
用score()
方法来评估当前棋局的分数的,以便计算计算机下一步的最佳落子点。
队员:
吴沅峰 计科(智能)22-1 202203200020
负责模块:鼠标监听器,动作监听器,自动下棋算法
面对程序设计(Java)课程设计————三少五子棋_a1067793829的博客-CSDN博客
团队博客:
面向对象程序设计(Java) 课程设计——三少五子棋(Final)_Aimless.的博客-CSDN博客
参考博客:
五元组评价算法实现简易五子棋【人工智能】_五子棋五元组算法_YouthUpward的博客-CSDN博客
二、项目简介
功能描述:由三人合作运用Java编写五子棋小游戏,具有双人对战,人机对战,悔棋,Replay,GameOver,投降,排行榜功能。可从排行榜中刷新,查看战绩,并且在About us 中加入了我们制作人的信息。
三、.功能架构图
四、运行结果截图
五、项目git地址
六、个人部分实现过程
LeaderboardFrame.java
这是一个Java Swing程序,用于实现五子棋游戏的排行榜功能。通过JFrame
和一些Swing组件创建了一个简单的排行榜窗口。该窗口中包括一个JTextArea
和一个JButton
,JTextArea
用于显示排行榜内容,JButton
用于刷新排行榜内容。
当用户点击“刷新”按钮时,程序将读取之前保存在文件中的五子棋游戏数据,并将其显示在JTextArea
中。具体来说,程序通过FileReader
读取文件中的数据,并使用JTextArea
的append()
方法将这些数据添加到JTextArea
中显示出来。
package MyFrame;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
/*Java Swing的窗口类,包含一个文本区域和一个按钮。
当用户点击按钮时,窗口会读取文件中的数据并在文本区域中显示,以此来刷新排行榜。具
体来说,文本区域被添加在窗口左上角的位置,按钮被添加在窗口中间下方的位置。
在构造函数中,设置了窗口的标题、大小、位置等属性,并将文本区域和按钮添加到窗口中。
在按钮的监听器中,当用户点击按钮时,先判断按钮的文本是否为“刷新”,
如果是则清空文本区域并尝试读取文件。如果文件不存在,则创建一个新文件,然
后将文件中的内容读取到字符数组中,并将数组转换为字符串后添加到文本区域中。
如果读取文件过程中发生任何异常,则在控制台中打印出异常的堆栈跟踪信息。
*/
public class LeaderboardFrame extends JFrame {
//JTextArea 是 Swing 类库中的一个组件,用于显示多行文本。
//JButton 也是 Swing 类库中的一个组件,用于显示按钮。
JTextArea textArea = new JTextArea();//创建了一个文本区域 textArea
JButton button = new JButton("刷新");//创建了一个按钮 button,并将按钮的文本设置为 “刷新”
LeaderboardFrame(){
setTitle("排行榜");//设置标题为“排行榜”
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置点击窗口关闭按钮时退出程序
setSize(300, 400);//设置窗口大小为宽度 300 像素,高度 400 像素
setLocationRelativeTo(null); // 设置窗口居中显示
setVisible(true);//JFrame 类的方法,用于将窗口设置为可见状态,即在屏幕上显示窗口。
setLayout(null);//设置布局为空布局(null layout)
//使用了 setBounds() 方法,该方法将组件的位置和大小设置为给定的 x 坐标、y 坐标、宽度和高度。
textArea.setBounds(20,20,260,260);//文本区域的左上角坐标为 (20,20),宽度为 260 像素,高度为 260 像素;
button.setBounds(90,300,100,30);//按钮的左上角坐标为 (90,300),宽度为 100 像素,高度为 30 像素。
//注册按钮的 ActionListener,以便在按钮被点击时执行对应的操作,这里是执行 actionListener 中定义的逻辑。
button.addActionListener(actionListener);
//将文本区域设置为不可编辑,这意味着用户不能修改文本区域的内容,仅能查看。
textArea.setEditable(false);
//将文本区域和按钮添加到窗口中
add(textArea);
add(button);
}
//当用户点击了名为 “刷新” 的按钮时,它将读取文件 “a.txt” 中的内容,并将其显示在窗口中的文本区域中。这样,用户就可以查看最新的排行榜信息了
ActionListener actionListener = new ActionListener() {//定义了一个 ActionListener 对象 actionListener,用于监听按钮的 ActionEvent 事件。
@Override
public void actionPerformed(ActionEvent e) {//当按钮被点击时,actionPerformed() 方法会被调用,执行对应的逻辑
JButton button1 = (JButton) e.getSource();//通过 e.getSource() 方法获取触发事件的源组件(即被点击的按钮),并将其转换成 JButton 类型的对象 button1。
String text = button1.getText();//通过 button1.getText() 方法获取按钮的显示文本,即按钮上显示的文字内容,并将其赋值给字符串 text。
if ("刷新".equals(text)){//如果 text 的值是 “刷新”,则执行后面的代码。
textArea.setText("");//先使用 textArea.setText("") 将文本区域的内容清空。
try{
File file = new File("a.txt");//使用 File 类打开名为 “a.txt” 的文件。
if (!file.exists()){//如果文件不存在,则创建一个新文件。
file.createNewFile();
}
FileReader fr = new FileReader(file);//使用 FileReader 类创建一个新的文件读取器 fr,将其与打开的文件关联。
int line,len = 0;
char[] ch = new char[10000];//创建一个字符数组 ch,用于保存文件中的内容。
while ((line = fr.read()) != -1){//fr.read() 方法每次读取一个字符,并将其赋值给 line 变量,同时将 line 的值与 -1 进行比较。如果读取到了文件的末尾,则 line 的值为 -1,循环终止;
ch[len] = (char)line;//通过 fr.read() 方法一次从文件中读取一个字符,如果读取成功,将读取到的字符转换成字符类型,并将其添加到字符数组 ch 的尾部。并且让数组的长度 len 加 1。
len ++ ;
}//在循环结束后,变量 len 的值就等于读取的字符总数,变量 ch 中的前 len 个字符就是从文件中读取到的内容。
String result = new String(ch);//将字符数组转换成字符串 result,便于将其添加到窗口中的文本区域中。
textArea.append(result);//通过 textArea.append(result) 方法将其添加到文本区域中。
fr.close();//关闭文件流。
}catch (IOException f){//定义了一个捕获文件读取过程中可能出现的 IOException 异常的 catch 语句块。
f.printStackTrace();//如果读取文件时出现错误,就将错误信息打印到控制台或日志文件中(调用方法 printStackTrace()),以便查找和解决问题。
}//IOException 表示捕获的异常类型,f 是该异常对象的引用,f.printStackTrace() 用于将异常信息输出到标准错误流(即控制台或日志文件),以便程序员进行调试和错误排查
}
}
};
}
函数算法的编写,使用函数方法体判断哪方获胜
判断某种棋子是否已经在当前局面中胜利。它通过遍历八个方向来判断该种棋子的连子情况,如果存在一个方向中有四个或以上的同色棋子,则将win
标记置为true
,表示该种棋子获胜。
具体来说,该段代码中的directions
数组存储了八个方向的偏移量,其中{ -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }
分别表示横向、纵向、左斜和右斜四个方向,{ -1, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 }
分别表示左上到右下、右下到左上、左下到右上和右上到左下四个方向。然后对于每个方向,程序将棋子逐个向一个方向延伸,直到遇到一个空位置或者边界,如果在此过程中遇到了相同颜色的棋子,则将 sum
计数器加1,表示已经扩展了一个棋子,直到 sum
累计数值等于或者大于4,则表示该种棋子已经获胜。
当某种棋子获胜时,将win
标记置为true
,同时记录下获胜时的棋局情况,包括步数和胜利方。这段代码将数据写入到一个文件中,文件路径为a.txt
。如果文件不存在,则会创建一个空文件。
//判断谁赢,扫描整个棋盘,来判断是否练成五个
private void judgement(int type, int x, int y) {
// 传入参数 type 用于判断黑(2)或白(1)方,x 和 y 用于定位当前下的棋子在棋盘中的位置。
int[][] directions = { {-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}, {1, -1} };
//定义了一个二维数组 directions,其中记录了八个方向的偏移量,方便之后的遍历。
for (int[] direction : directions) {//通过一个以 direction 这个变量名来遍历这八个方向
int sum = 0;//定义变量 sum 记录相邻颜色相同的棋子数
int dx = direction[0];//变量 dx 和 dy 记录当前方向的横纵坐标偏移值
int dy = direction[1];
int i = x + dx;//初始化变量 i 和 j,定位到当前正在被判断的棋子的上下左右或斜方向上的一个棋子。
int j = y + dy;
while (i >= 0 && i < num && j >= 0 && j < num && table[i][j] == type) {//循环的条件为 i 和 j 均在棋盘范围内,且 table[i][j] 的值与要检查的棋子的颜色相同
sum++;//在循环体中,先将 sum 的值加一
i += dx;//然后将 i 和 j 分别增加一个偏移值 dx 和 dy
j += dy;//就可以在当前方向上继续检查相邻的棋子,直到遇到颜色不同的棋子或到达棋盘边界。
}//循环结束时,sum 记录的就是当前方向上相邻颜色相同的棋子个数。
i = x - dx;
j = y - dy;
//反方向检查当前方向上相邻颜色相同的棋子个数。循环首先是由前往后检查棋子的个数,然后在由后往前验证是否达到了胜负条件。
while (i >= 0 && i < num && j >= 0 && j < num && table[i][j] == type) {
sum++;
i -= dx;
j -= dy;
}//循环体中判断棋子是否相邻,并累计相邻棋子的个数,最终得到当前方向和相反方向上相邻颜色相同的棋子的个数。
//在 judgment 函数检查棋盘上的每个棋子后,如果此时某一玩家连成四个相邻的棋子,将游戏状态 win 设为 true,并将棋局信息记录到文件中。
if (sum >= 4) {//使用 if 判断变量 sum 是否大于等于 4,如果成立,表示当前玩家连成了四个相邻的棋子。
win = true;//将游戏状态 win 设为 true,表示当前玩家已经取得了胜利
if (win){//强调在赢得游戏之后记录棋局信息。
try{//在 try 块中通过创建 File 和 FileWriter 对象,将信息记录到文件中
java.io.File file = new java.io.File("a.txt");//定义一个 java.io.File 对象,其指向名为 “a.txt” 的文件。
if (!file.exists()){//如果该文件不存在,使用 file.createNewFile() 创建一个新文件
file.createNewFile();
}
if (oval_type == 1){//通过 if 判断变量 oval_type,如果其等于 1,则当前玩家为黑色方,否则表示其为白色方。
String result = "步数:" + step + "黑方赢" + '\n';//设置 String result 为对应颜色方赢得比赛的信息,即记录形式为 “步数:X 黑方/白方赢”。
java.io.FileWriter fileWriter = new java.io.FileWriter(file,true);//创建一个 java.io.FileWriter 对象
fileWriter.write(result);//向文件中写入文本信息 result
fileWriter.close();//关闭文件流,回收资源。
} else {
String result = "步数:" + step + "白方赢通过 printStackTrace() 打印出异常的堆栈跟踪信息。" + '\n';
java.io.FileWriter fileWriter = new java.io.FileWriter(file,true);
fileWriter.write(result);FileWriter 的第二个参数为 true,表示以追加的方式向文件中写入数据。这样,以后每次记录游戏胜负信息时,会在该文件中追加记录。
fileWriter.close();
}
}catch (java.io.IOException e){//如果在文件写入操作过程中发生了 I/O 异常(如文件不存在或其他错误),则会由 catch 块中的代码块捕获异常
e.printStackTrace();//通过 printStackTrace() 打印出异常的堆栈跟踪信息。
}
}
return;//继续执行后续程序,并通过 return 退出函数,避免重复记录胜负信息。
}
}
}
score()
方法
用来评估当前棋局的分数的,以便计算计算机下一步的最佳落子点。该方法的输入参数w
和b
分别表示白色和黑色棋子在当前局面中连续的棋子个数,其中w
和b
至少有一个为0。如果两个参数都为0,则返回7,表示此处可以下子。如果w
和b
都大于0,则返回0,表示此处不能下子。如果只有其中一种连续的棋子,则根据对应的分数返回结果。分数越高,表明这个位置越有利。
具体来说,当白子连续1颗时返回15分,连续2颗时返回400分,连续3颗时返回1800分,连续4颗时返回100000分;当黑子连续1颗时返回35分,连续2颗时返回800分,连续3颗时返回15000分,连续4颗时返回800000分。
//用于根据给定形式的棋盘数据计算出当前评分。这个评分可以用于评估当前棋局的优劣,方便 AI 程序作出下一步棋的决策。
private int score(int w, int b) {//方法的输入参数是两个整形数 w 和 b,分别表示黑方和白方在棋盘上的棋子数量。
if (w > 0 && b > 0) {//使用 if 语句,判断棋盘中是否同时出现了两个玩家的棋子。如果出现了,游戏处于平局状态,返回 0 分。
return 0;
}
if (w == 0 && b == 0) {//判断棋盘上是否有一方的棋子都没有,这种情况也是平局,此时返回 7 分。
return 7;
}
//接下来根据白方或黑方棋子的数量来计算得分。
if (w == 1) {//如果黑方有 1 枚棋子,返回 35 分
return 35;
}
if (w == 2) {//有两枚返回 800 分
return 800;
}
if (w == 3) {//有三枚返回 15000 分,
return 15000;
}
if (w == 4) {//有四枚返回 800000 分。
return 800000;
}
if (b == 1) {//如果白方有一枚棋子,返回 15 分,
return 15;
}
if (b == 2) {//有两枚返回 400 分
return 400;
}
if (b == 3) {//有三枚返回 1800 分
return 1800;
}
if (b == 4) {//有四枚返回 100000 分
return 100000;
}
return -1;//如果输入参数 w 和 b 不符合以上任何条件,返回 -1 分。
}