Java程序设计
大作业报告
题目: 欢乐五子棋
学院: 软件学院
姓名:
学号:
专业班级:
任课教师:
截止时间: 2022年6月11日
大作业要求:
请根据本学期所学的Java和面向对象的思想实现一个完整的系统。开发环境和工具不限,大作业题目和内容自定,代码总行数不低于800行(含注释)。鼓励围绕这次疫情期间产生的学习、生活、工作的需求来开发相应程序。
具体要求如下:
- 采用面向对象程序设计,要运用继承、多态等机制;
- 代码规范需遵循Java编码规范要求;
- 具备文件读写功能,程序中的数据要以文件方式保存;
- 程序中要求具备异常处理;
- 使用第三方类库或者插件需在报告和程序中注明来源和版权信息,并详细说明使用了该第三方类库或者插件哪部分功能。
- 要求具备菜单功能,根据用户的输入,执行相应的操作。在菜单选项中,必须包含“关于”一项,该菜单项用来显示本作业的基本信息,包括程序名称、版本、完成时间,姓名、学号、班级等信息;
- 程序要求做到界面友好,对于用户的输入操作要有明确的提示信息,并对输入的数据进行合法性和正确性的检查;
- 每个文件、类、方法的前面都必须有规范的注释,注释中要说明作者是谁、主要功能、编写时间等信息。变量、常量、关键语句等都必须有注释说明。
- 所有源程序代码(含注释)都要拷贝到大作业报告的【3.3 程序实现】中。
- 源代码为多文件项目,合理分配好各个源代码文件内容。
- 本报告文档是作业的一个重要内容,文档中的每个内容都必须认真按照要求撰写,否则扣分。
模板格式说明:
- 正文模板中的红色文字部分为说明文字,请仔细阅读后撰写正式的内容。
- 排版要求:
页面设置:上2.54cm,下2.54cm,左3.67cm,右2.67cm,页眉1.5cm,页脚1.75cm,行间距1.35倍。
全文中文字体采用宋体,英文字体采用Times New Roman字体,一级标题使用小三号字体,二级、三级标题均使用四号字体,正文内容采用小四号字体,代码字体大小为五号。
目 录
一、需求分析1
1.1、开发环境1
1.2、功能需求1
二、系统分析与设计1
2.1、本程序需解决的关键技术问题1
2.2、总体功能图1
2.3、类图2
2.4、程序流程图(至少画1个关键性功能的流程图)2
三、程序设计与实现2
3.1、项目文件组织结构2
3.2、程序设计3
3.3、程序实现3
四、系统测试3
五、本程序高内聚低耦合的特点,易扩展、易维护3
六、总结与体会4
七、自评分4
- 需求分析
1.1、开发环境
操作系统及版本:win 10
开发工具及版本:IntelliJ IDEA Educational Edition 2021.3.2
1.2、功能需求
欢乐五子棋
- 设置程序只能在限定的棋盘上落子,开始游戏后,当鼠标在棋盘上点击时,绘制棋子
- 完成五子棋的基本规则——任一方先在棋盘上形成横向、竖向、斜向的连续的相同颜色的五个(含五个以上)棋子的一方为胜,结束游戏。
- 设置倒计时功能,单位为分钟,令游戏更加合理
- 设置悔棋功能,防止因一些客观原因落子失败
- 设置认输功能
- 设置历史对局功能,查看历史对局输赢情况
- 设置退出功能
- 系统分析与设计
2.1、本程序需解决的关键技术问题
1、如何令鼠标在限定的范围内落子而不越界
2、如何实现黑子白子轮流进行落子
3、如何实现任一种颜色棋子在任意方向上练成五个时判定某一方胜利
4、如何令菜单上的功能在规定的点击范围内响应
5、如何令棋子落在棋盘的网格线上且当两个棋子选择下在同一个网格上时,怎样解决上一个棋子被覆盖的问题
6、怎样完成时间的设置,并且当使用者输入正整数和0以外的数字使不产生bug
7、如何将对局输赢情况(包含时间线)存储在文件中不出现bug,以及如何将对局内容呈现在窗口中
2.2、总体功能图
2.3、类图
2.4、程序流程图(至少画1个关键性功能的流程图)
函数:paint函数,MyFrameChess函数,mousePressed函数,judge函数,run函数
- 程序设计与实现
3.1、项目文件组织结构
3.2、程序设计
mainmenu类:用于调用MyFrameChess类
MyFrameChess类:用于构建棋盘,控制落子,判断胜负,设置时间等功能
关键知识点:继承,封装,多态,多线程运行,JFrame和鼠标监听器的相关知识
主要成员函数:
MyFrameChess构造函数:初始化相关变量
paint函数:用于设置界面,包括背景图片的导入,棋盘的绘制,落子方的提示信息。
mousePressed函数:
设计意图:监听鼠标位置,实现棋盘上各项功能
功能:获取x,y的坐标,根据由paint函数记录的数据绘制棋子,判断游戏是否结束。
实现“开始游戏”选项,点击重新开始,清空棋盘,开始游戏
实现“倒计时”选项,以分钟为单位设置时间,输入数为0时,设置时间无限制
实现“游戏说明”选项,点击游戏说明,弹出对话框以介绍游戏规则
实现“关于”选项,点击关于,弹出对话框介绍游戏制作相关内容
实现“悔棋”选项,点击悔棋,撤回上一步,但不可连续重复执行
实现“认输”选项,点击认输,判断当前落子的人败北,游戏结束
实现“历史对局”选项,点击显示历史对局的输赢情况
实现“退出游戏”选项,点击退出,结束程序
judge函数: 用于判断当前落子位置是否存在其余四个相同颜色棋子使当前一方连成五子
run函数: 用于实现多线程运作
3.3、程序实现
paint函数:利用鼠标监听器获取棋盘的范围,再通过for循环划出棋盘,同时使用Graphics类对界面进行相应优化
MyFrameChess构造函数:主要用于初始化各个变量
mousePressed函数:使用if函数进行各类判断确定各类功能作用范围,利用JFrame的弹窗设置各类提示信息,显示时间maxtime本是正整数,运用一些方法设置为时分秒的形式,代码如下:
- if(maxTime > 0){
- bTime = maxTime/3600+":"+
- (maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
- wTime = maxTime/3600+
- ":"+(maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
悔棋选项利用记录最后落子坐标的方法进行撤回,代码如下:
- //设置悔棋选项
- //限定选项范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=280&&e.getY()<=320){
- //判断当前棋盘上是否有棋子
- if(allchess[finalx][finaly]!=0&&Goon==true){
- //弹出选择对话框,根据选项决定是否执行悔棋程序
- int result = JOptionPane.showConfirmDialog(this,
- "请问是否进行悔棋操作?");
- //若选择坚持执行悔棋程序,则进行以下操作
- if(result == 0){
- //将最后一步的棋子设置为无,即清除该点的棋子
- allchess[finalx][finaly] = 0;
- //返回到落子前的棋盘状态
- if(Black == true){
- //若悔棋时是黑子,则将棋子颜色设置为白色
- Black = false;
- //将游戏信息变换为对应的内容
- message = "轮到白方";
- }else{
- //若悔棋时是黑子,则将棋子颜色设置为黑色
- Black = true;
- //将游戏信息变换为对应的内容
- message = "轮到黑方";
- }
- //重绘棋盘
- this.repaint();
- }
- }else{
- //若棋盘上无棋子或连续执行悔棋操作,则弹出该窗口
- JOptionPane.showMessageDialog(this,"无效操作!");
- }
- }
judge函数:设置判断五子是否相连的方法
若存在相同颜色的五个棋子任意方向相连,则返回true值
该类的算法判断依据为:
每落子一次便进行判定一次
当判定当前棋子周围任意一个方向上有相同颜色棋子相连时
则设置改棋子为最后落的棋子,将结束游戏条件设置为true
部分代码如下:
- //初始化为不相连
- boolean flag = false;
- //记录相同颜色棋子相连的数量
- int count = 1;
- //将本轮下的棋子颜色作为参考颜色
- int color = allchess[x][y];
- //用于判断横向是否有五个棋子相连
- int one = 1;
- //首先判断横向右方的棋子情况
- //防止向右越界异常处理
- while(x+one>=0&&x+one<=18&&
- y>=0&&y<=18&&
- color == allchess[x+one][y]){
- //若横向右方有相同颜色的棋子,则计数加一
- count++;
- //判断范围再往右前进一格
- one++;
- }
- //将判断范围初始化
- one = 1;
- //判断横向左方的棋子情况
- //防止向左越界异常处理
- while(x-one>=0&&x-one<=18&&
- y>=0&&y<=18&&
- color == allchess[x-one][y]){
- //若横向左方有相同颜色的棋子,则计数加一
- count++;
- //判断范围再往左前进一格
- one++;
- }
- //若相同连续的棋子计数达到五个,则更改flag的值
- if(count >= 5){
- //使上层判定游戏结束,其中一方获得胜利
- flag = true;
- }
run函数:设置倒计时使黑方与白方时间各独立一个线程,当一方落子时,另一方的线程继续开始,设定每一秒时间某一方时间减一,当某一方时间为0时,停止计时,设置时间为-1,代表游戏结束
代码如下:
- public void run() {
- //当设置时间大于0时,程序才会开始
- if(maxTime>0){
- //设置程序一直运行
- while(true){
- //设置黑方时间减少
- if(Black){
- //当黑方时间大于0时,设置黑方时间每秒减一
- if(BTime > 0){
- BTime--;
- }
- //黑方游戏时间耗尽时,判断游戏结束
- if(BTime == 0){
- JOptionPane.showMessageDialog(this,"黑方超时,游戏结束!");
- Goon = false;
- //当黑方时间等于0时,设置时间为-1,防止倒计时继续运转
- BTime = -1;
- }
- }else{
- if(WTime > 0){
- //设置白方时间每秒减一
- WTime--;
- }
- if(WTime == 0){
- //白方游戏时间耗尽时,判断游戏结束
- JOptionPane.showMessageDialog(this,"白方超时,游戏结束!");
- Goon = false;
- WTime = -1;
- }
- }
- //设置黑方显示时间为时分秒的显示形式
- bTime = BTime/3600+":"+
- (BTime/60-BTime/3600*60)+":"+
- (BTime-BTime/60*60);
- //设置白方显示时间为时分秒的显示形式
- wTime = WTime/3600+":"+
- (WTime/60-WTime/3600*60)+":"+
- (WTime-WTime/60*60);
- //重绘棋盘,刷新时间
- this.repaint();
- try {
- //设置停顿
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //System.out.println(BTime+"---"+WTime);
- }
- }
- }
项目源代码如下:
Mainmenu.java:
- package FiveChessGame;
- //项目名称:欢乐五子棋
- //期望分数:93
- //理由:界面设计合理,功能齐全,具有一定的创新性
- //该类用于调用MyFrameChess函数
- public class mainmenu {
- public static void main(String[] args) {
- //创建MyFrameChess类对象
- MyFrameChess zby = new MyFrameChess();
- }
- }
MyFrameChess.java:
- package FiveChessGame;
- import javax.imageio.ImageIO;
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.MouseEvent;
- import java.awt.event.MouseListener;
- import java.awt.image.BufferedImage;
- import java.io.File;
- import java.io.IOException;
- import java.util.ArrayList;
- //MyFrameChess类
- //主要功能包括绘制棋盘,实现游戏各项功能,设置时间限制等
- //实现游戏主体功能
- public class MyFrameChess extends JFrame implements MouseListener,Runnable {
- //定义图片
- BufferedImage image = null;
- //定义x坐标,y坐标
- int x = 0;
- int y = 0;
- //记录步数
- int step = 0;
- //记录最后下的棋子的横坐标
- int finalx = 0;
- //记录最后下的棋子的纵坐标
- int finaly = 0;
- //创建菜单栏
- private JMenuBar menuBar;
- //添加系统
- private JMenu sysMenu;
- //添加开始选项
- private JMenuItem startMenuItem;
- //添加悔棋选项
- private JMenuItem backMenuItem;
- //添加退出选项
- private JMenuItem exitMenuItem;
- // //添加相关按钮
- // private JPanel toolBar;
- //
- // //添加开始按钮
- // private JButton startButton;
- //
- // //添加悔棋按钮
- // private JButton backButton;
- //
- // //添加退出按钮
- // private JButton exitButton;
- //保存之前下过棋子的全部坐标,0表示没有棋子,1表示黑子,2表示白子
- int[][] allchess = new int[19][19];
- //判断要下的棋子颜色
- boolean Black = true;
- //判断游戏是否需要进行
- boolean Goon = false;
- //提供一些必要的游戏提示信息
- String message = "黑方先行";
- //设置最大时间
- int maxTime = 0;
- //倒计时的线程类
- Thread time = new Thread(this);
- //设置黑方和白方的剩余时间
- int BTime = 0;
- int WTime = 0;
- //设置黑方和白方的剩余显示时间
- String bTime = "无限制";
- String wTime = "无限制";
- ResultManager resultManager = new ResultManager();
- //MyFrameChess类的构造方法
- //用于初始化棋盘,各类按键,设置背景图片,控制落子
- public MyFrameChess(){
- //取得屏幕的宽度
- int width = Toolkit.getDefaultToolkit().getScreenSize().width;
- //取得屏幕的高度
- int height = Toolkit.getDefaultToolkit().getScreenSize().height;
- // toolBar = new JPanel();
- //
- // startButton = new JButton();
- //
- // backButton = new JButton();
- //
- // exitButton = new JButton();
- //初始化菜单栏
- menuBar = new JMenuBar();
- //初始化系统
- sysMenu = new JMenu("系统");
- //初始化开始选项
- startMenuItem = new JMenuItem("开始游戏");
- //初始化悔棋选项
- backMenuItem = new JMenuItem("悔棋");
- //初始化退出选项
- exitMenuItem = new JMenuItem("退出");
- //将菜单栏添加到页面中
- this.setJMenuBar(menuBar);
- //将系统添加到菜单栏中
- menuBar.add(sysMenu);
- //将开始选项添加到系统中
- sysMenu.add(startMenuItem);
- //将悔棋选项添加到系统中
- sysMenu.add(backMenuItem);
- //将退出选项添加到系统中
- sysMenu.add(exitMenuItem);
- //设置标题
- this.setTitle("欢乐五子棋");
- //设置界面大小
- this.setSize(620,700);
- //设置界面出现在屏幕中间
- this.setLocation((width-500)/2,(height-500)/2);
- //设置为大小不可改变,以防止嵌入的图片出现问题
- this.setResizable(false);
- //设置关闭窗口后程序结束
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- //设置窗口可见
- this.setVisible(true);
- //添加鼠标监听器
- this.addMouseListener(this);
- //设置线程设置
- time.start();
- //将线程暂时悬挂
- time.suspend();
- //解决黑屏的情况
- this.repaint();
- try {
- //导入图片
- image = ImageIO.read(new File("f:/Java大作业/background1.jpg"));
- // File background = new File("F:\\Java大作业\\background1.jpg");
- // System.out.println(background.getPath());
- // image = ImageIO.read(background);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- //该类用于设置界面
- public void paint(Graphics show){
- //设置图片出现位置
- show.drawImage(image,0,30,null);
- //设置字体
- show.setFont(new Font("黑体",Font.BOLD,20));
- //在右上角设置落子方提示信息
- show.drawString(message,500,65);
- //重新设置字体,增加悔棋按键
- show.setFont(new Font("黑体",Font.BOLD,25));
- //在棋盘的右方增加悔棋按键
- show.drawString("悔棋",520,300);
- //重新设置字体
- show.setFont(new Font("宋体",0,20));
- //在棋盘的下方设置显示时间
- show.drawString("黑方时间:"+bTime,40,600);
- show.drawString("白方时间:"+wTime,260,600);
- //绘制悔棋按键的边框
- show.drawOval(510,265,70,50);
- //绘制棋盘
- for(int i = 0;i < 20;i++){
- //绘制横线
- show.drawLine(25,82+i*25,499,82+i*25);
- //绘制竖线
- show.drawLine(25+i*25,82,25+i*25,556);
- }
- //标注点位
- show.fillOval(98,155,5,5);
- show.fillOval(98,480,5,5);
- show.fillOval(424,155,5,5);
- show.fillOval(424,480,5,5);
- //标注中心点位
- show.fillOval(274,305,5,5);
- show.fillOval(274,480,5,5);
- show.fillOval(98,305,5,5);
- show.fillOval(424,305,5,5);
- show.fillOval(274,155,5,5);
- //控制棋盘的落子
- for(int i = 0;i < 19;i ++){
- for(int j = 0;j < 19;j ++){
- //读取二维数组中记录的各类棋子的位置
- if(1 == allchess[i][j]){
- //绘制黑子
- int X = i*25+25;
- int Y = j*25+82;
- //使棋子落在网格线的交叉处
- show.fillOval(X-7,Y-7,15,15);
- }
- if(2 == allchess[i][j]){
- int X = i*25+25;
- int Y = j*25+82;
- //将画笔颜色设置为白色
- show.setColor(Color.white);
- //绘制白色棋子
- show.fillOval(X-7,Y-7,15,15);
- show.setColor(Color.black);
- //绘制黑色边框
- show.drawOval(X-7,Y-7,15,15);
- }
- }
- }
- }
- @Override
- public void mouseClicked(MouseEvent e) {
- }
- @Override
- //该类的功能主要通过监听实现,包括获取鼠标位置,绘制棋子,以及实现棋盘上的各类选项
- public void mousePressed(MouseEvent e) {
- //首先判定游戏是否在进行中
- if (Goon == true) {
- //获取鼠标位置信息
- x = e.getX();
- y = e.getY();
- //使棋子落在棋盘上
- if (x >= 25 && x <= 498 && y >= 82 && y <= 556) {
- //得到距离鼠标位置最近的网格交叉点的坐标
- x = (x - 25) / 25;
- y = (y - 82) / 25;
- //将当前所下的棋子的x坐标记录下来
- finalx = x;
- //将当前所下的棋子的y坐标记录下来
- finaly = y;
- //设置步数加一
- step++;
- //设置落子判断条件,防止棋子被覆盖
- if (allchess[x][y] == 0) {
- //若当前回合由黑方落子,则在棋盘相应位置绘制黑子
- if (Black == true) {
- //记录当前黑子的位置信息
- allchess[x][y] = 1;
- //转换棋子颜色
- Black = false;
- //更改回合提示信息
- message = "轮到白方";
- } else {
- //记录当前白子的位置信息
- allchess[x][y] = 2;
- //转换棋子颜色
- Black = true;
- //更改回合提示信息
- message = "轮到黑方";
- }
- //判断胜利条件,设置游戏不可运行
- boolean win = this.judge();
- //判断游戏胜利方
- //若win为true,则执行游戏结束
- //若win为false,则继续游戏
- if (win == true) {
- if (allchess[x][y] == 1) {
- JOptionPane.showMessageDialog(this, "游戏结束,黑方获胜");
- try {
- resultManager.writeResult("游戏结束,黑方获胜");
- } catch (IOException ioException) {
- ioException.printStackTrace();
- }
- //FileOutputStream fos2 = new FileOutputStream( new File(pathname:"1111\\fos.txt"));
- } else {
- JOptionPane.showMessageDialog(this, "游戏结束,白方获胜");
- try {
- resultManager.writeResult("游戏结束,白方获胜");
- } catch (IOException ioException) {
- ioException.printStackTrace();
- }
- }
- //设置游戏结束
- Goon = false;
- }
- //判断当前所下位置是否有棋子,防止棋子被覆盖
- } else {
- JOptionPane.showMessageDialog(this, "当前位置已有棋子,请重下");
- }
- //重绘棋盘
- this.repaint();
- }
- }
- //开始游戏选项
- //限定功能范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=75&&e.getY()<=125){
- int result = JOptionPane.showConfirmDialog(this,"是否重新开始游戏?");
- if(result == 0){
- Goon = true;
- //清空棋盘,回到初始状态
- for(int i = 0;i < 19;i++){
- for(int j = 0;j < 19;j++){
- allchess[i][j] = 0;
- }
- }
- //清空初始信息
- message = "黑方先行";
- Black = true;
- //重新设置游戏时间
- BTime = maxTime;
- WTime = maxTime;
- //将显示时间设置为时、分、秒的形式
- if(maxTime > 0){
- bTime = maxTime/3600+":"+
- (maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
- wTime = maxTime/3600+
- ":"+(maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
- //重启线程
- time.resume();
- //当输入时间为0时设置时间为无限制
- }else if(maxTime == 0){
- bTime = "无限制";
- wTime = "无限制";
- }
- this.repaint();
- }
- }
- //设置游戏设置选项
- //限定选项作用范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=145&&e.getY()<=195){
- //获取最大游戏时长
- String timeSet = JOptionPane.showInputDialog("请输入游戏的最大时间(分钟):");
- try {
- //强制将timeSet转为int形式
- maxTime = Integer.parseInt(timeSet)*60;
- //防止游戏时长输入为负数
- if(maxTime<0){
- JOptionPane.showMessageDialog(this,"请输入正确格式的信息!");
- }
- //设置游戏时长无限制
- if(maxTime == 0) {
- int result = JOptionPane.showConfirmDialog(this, "设置完成,是否重新开始游戏?");
- //游戏时长设置后需重新开始游戏
- if (result == 0) {
- Goon = true;
- for (int i = 0; i < 19; i++) {
- for (int j = 0; j < 19; j++) {
- //清空棋盘
- allchess[i][j] = 0;
- }
- }
- //初始化信息
- message = "黑方先行";
- //重新加载剩余时间
- BTime = maxTime;
- WTime = maxTime;
- //重新加载显示时间
- bTime = "无限制";
- wTime = "无限制";
- //将该回合落子设定为黑子
- Black = true;
- //重绘棋盘
- this.repaint();
- }
- }
- //添加游戏时长大于0时的相关功能
- if(maxTime > 0){
- //重启游戏
- Goon = true;
- time.resume();
- for(int i = 0;i < 19;i++){
- for(int j = 0;j < 19;j++){
- allchess[i][j] = 0;
- }
- }
- message = "黑方先行";
- Black = true;
- //重新加载剩余时间
- BTime = maxTime;
- WTime = maxTime;
- //设置游戏时长显示格式
- bTime = maxTime/3600+
- ":"+(maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
- wTime = maxTime/3600+
- ":"+(maxTime/60-maxTime/3600*60)+
- ":"+(maxTime-maxTime/60*60);
- //重绘棋盘
- this.repaint();
- }
- //异常处理,防止输入错误字符
- } catch (NumberFormatException numberFormatException) {
- JOptionPane.showMessageDialog(this,"请输入正确格式的信息!");
- }
- }
- //设置游戏说明选项
- //限定选项作用范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=215&&e.getY()<=265){
- //介绍游戏的相关信息以及游戏玩法
- JOptionPane.showMessageDialog(this,"五子棋游戏。\n" +
- "游戏规则:\n"+
- "黑白双方依次落子,由黑先下\n"+
- "任一方先在棋盘上形成横向、竖向、斜向的连续的相同颜色的五个(含五个以上)棋子的一方为胜。\n"+
- "若在规定时间内都没有一方能够连成五个(或以上)棋子,则判定先用完时间的一方失败。\n"+
- "开始游戏选项:\n"+
- "点击开始游戏选项以开始游戏,否则无法开始游戏。\n"+
- "倒计时选项:\n"+
- "点击游戏设置选项以完成时间的设置.\n"+
- "时间设置单位为分钟,且仅支持输入0和正整数。\n"+
- "当输入时间为0时,设置双方时间为无限制。\n"+
- "时间设置完毕后若开始游戏,则会清空当前棋盘,请玩家留意。\n"+
- "时间设置完毕后,会在棋盘下方显示剩余时间,双方起始剩余时间相同。\n"+
- "游戏从黑方开始倒计时,若黑方落子,则开始扣除白方的剩余时间,反之亦然。\n"+
- "认输选项:\n"+
- "点击认输选项会直接结束游戏,判定当前落子的一方败北。\n"+
- "使用前还请认真考虑。\n"+
- "关于选项:\n"+
- "点击关于选项会给出制作人员以及该程序的相关信息。\n"+
- "退出选项:\n"+
- "点击退出选项会直接结束掉程序,使用前请认真考虑。\n"+
- "历史对局选项:\n"+
- "点击查看历史对局结果。\n"+
- "悔棋选项:\n"+
- "点击悔棋选项可以撤销上一回合的落子,且有该方重新落子。\n"+
- "点击悔棋后时间不会回复到之前的状态,且悔棋选项不可以连续使用。\n"+
- "若点击悔棋时棋盘上并未有落子,则会提示无效操作。\n"+
- "悔棋选项可能会破坏游戏的公平性,使用前请慎重.\n"
- );
- }
- //设置游戏认输选项
- //限定选项作用范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=335&&e.getY()<=385){
- //设置选择对话框
- int result = JOptionPane.showConfirmDialog(this,"请问您是否确定认输?");
- //根据判断结果决定是否判定认输
- if(result == 0) {
- //判断认输方
- if (Black == true) {
- //若该回合为黑方落子,则判定白方获胜
- JOptionPane.showMessageDialog(this, "黑方认输,白方获得胜利!");
- //FileOutputStream his =new FileOutputStream( name: "黑方认输,白方获胜\\his.txt");
- // File file =new File( pathname:"黑方认输,白方获胜\\his.txt");
- // FileOutputStream fos2 =new FileOutputStream(file);
- try {
- resultManager.writeResult("黑方认输,白方获胜");
- } catch (IOException ioException) {
- ioException.printStackTrace();
- }
- } else {
- //若该回合为白方落子,则判定黑方获胜
- JOptionPane.showMessageDialog(this, "白方认输,黑方获得胜利!");
- try {
- resultManager.writeResult("白方认输,黑方获胜");
- } catch (IOException ioException) {
- ioException.printStackTrace();
- }
- }
- //设置游戏结束
- Goon = false;
- }
- }
- //设置游戏关于选项
- //限定选项范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=410&&e.getY()<=455){
- //介绍作者以及游戏的相关信息
- JOptionPane.showMessageDialog(this,"程序名称:欢乐五子棋\n"+
- "版本信息:欢乐五子棋2.0\n"+
- "完成时间:2022.06.05\n" +
- "姓名:\n" +
- "学号:\n" +
- "班级:班\n");
- }
- //设置历史对局选项
- //限定选项范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=490&&e.getY()<=535){
- // 查看文件中的历史对局记录
- ArrayList resultList;
- String result = "";
- try {
- resultList = resultManager.readResult();
- for(Object obj : resultList){
- result += obj.toString();
- }
- } catch (IOException ioException) {
- ioException.printStackTrace();
- }
- //弹出游戏历史对局对话框
- JOptionPane.showMessageDialog(this,result);
- }
- //设置退出选项
- //限定选项范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=570&&e.getY()<=615){
- //弹出游戏结束对话框
- JOptionPane.showMessageDialog(this,"游戏结束!");
- //退出程序
- System.exit(0);
- }
- //设置悔棋选项
- //限定选项范围
- if(e.getX()>=505&&e.getX()<=598&&e.getY()>=280&&e.getY()<=320){
- //判断当前棋盘上是否有棋子
- if(allchess[finalx][finaly]!=0&&Goon==true){
- //弹出选择对话框,根据选项决定是否执行悔棋程序
- int result = JOptionPane.showConfirmDialog(this,
- "请问是否进行悔棋操作?");
- //若选择坚持执行悔棋程序,则进行以下操作
- if(result == 0){
- //将最后一步的棋子设置为无,即清除该点的棋子
- allchess[finalx][finaly] = 0;
- //返回到落子前的棋盘状态
- if(Black == true){
- //若悔棋时是黑子,则将棋子颜色设置为白色
- Black = false;
- //将游戏信息变换为对应的内容
- message = "轮到白方";
- }else{
- //若悔棋时是白子,则将棋子颜色设置为黑色
- Black = true;
- //将游戏信息变换为对应的内容
- message = "轮到黑方";
- }
- //重绘棋盘
- this.repaint();
- }
- }else{
- //若棋盘上无棋子或连续执行悔棋操作,则弹出该窗口
- JOptionPane.showMessageDialog(this,"无效操作!");
- }
- }
- }
- //设置判断五子是否相连的方法
- //若存在相同颜色的五个棋子任意方向相连,则返回true值
- //该类的算法判断依据为:
- //每落子一次便进行判定一次
- //当判定当前棋子周围任意一个方向上有相同颜色棋子相连时
- //则设置改棋子为最后落的棋子,将结束游戏条件设置为true
- private boolean judge(){
- //初始化为不相连
- boolean flag = false;
- //记录相同颜色棋子相连的数量
- int count = 1;
- //将本轮下的棋子颜色作为参考颜色
- int color = allchess[x][y];
- //用于判断横向是否有五个棋子相连
- int one = 1;
- //首先判断横向右方的棋子情况
- //防止向右越界异常处理
- while(x+one>=0&&x+one<=18&&
- y>=0&&y<=18&&
- color == allchess[x+one][y]){
- //若横向右方有相同颜色的棋子,则计数加一
- count++;
- //判断范围再往右前进一格
- one++;
- }
- //将判断范围初始化
- one = 1;
- //判断横向左方的棋子情况
- //防止向左越界异常处理
- while(x-one>=0&&x-one<=18&&
- y>=0&&y<=18&&
- color == allchess[x-one][y]){
- //若横向左方有相同颜色的棋子,则计数加一
- count++;
- //判断范围再往左前进一格
- one++;
- }
- //若相同连续的棋子计数达到五个,则更改flag的值
- if(count >= 5){
- //使上层判定游戏结束,其中一方获得胜利
- flag = true;
- }
- //初始化计数
- count = 1;
- //纵向判断范围初始化
- int two = 1;
- //判断纵向上方的棋子情况
- //防止向上越界异常处理
- while(x>=0&&x<=18&&y+two>=0&&y+two<=18&&
- color == allchess[x][y+two]){
- //若纵向上方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向上前进一格
- two++;
- }
- //重置判断范围
- two = 1;
- //判断纵向下方的棋子情况
- //防止向下越界异常处理
- while(x>=0&&x<=18&&y-two>=0&&y-two<=18&&
- color == allchess[x][y-two]){
- //若纵向下方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向下前进一格
- two++;
- }
- //若相同连续的棋子计数达到五个,则更改flag的值
- if(count >= 5){
- //判定条件改为true
- flag = true;
- }
- //初始化计数
- count = 1;
- //左上、右下判断初始化
- int three = 1;
- //判断右下方的棋子情况
- //防止右下越界异常处理
- while(x+three>=0&&x+three<=18&&y+three>=0&&y+three<=18&&
- color == allchess[x+three][y+three]){
- //若右下方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向右下前进一格
- three++;
- }
- //重置判断范围
- three = 1;
- //判断左上方的棋子情况
- //防止左上方越界异常处理
- while(x-three>=0&&x-three<=18&&y-three>=0&&y-three<=18&&
- color == allchess[x-three][y-three]){
- //若左上方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向左上方前进一格
- three++;
- }
- //若相同连续的棋子计数达到五个,则更改flag的值
- if(count >= 5){
- flag = true;
- }
- //重置判断范围
- count = 1;
- //右上、左下判断初始化
- int four = 1;
- //判断右上方的棋子情况
- //防止右上方越界异常处理
- while(x+four>=0&&x+four<=18&&y-four>=0&&y-four<=18&&
- color == allchess[x+four][y-four]){
- //若右上方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向右上方前进一格
- four++;
- }
- //重置判断范围
- four = 1;
- //判断左下方的棋子情况
- //防止左下方越界异常处理
- while(x-four>=0&&x-four<=18&&y+four>=0&&y+four<=18&&
- color == allchess[x-four][y+four]){
- //若左下方有相同颜色的棋子,则计数加一
- count++;
- //判断范围向左下方前进一格
- four++;
- }
- //若相同连续的棋子计数达到五个,则更改flag的值
- if(count >= 5){
- flag = true;
- }
- return flag;
- }
- @Override
- public void mouseReleased(MouseEvent e) {
- }
- @Override
- public void mouseEntered(MouseEvent e) {
- }
- @Override
- public void mouseExited(MouseEvent e) {
- }
- @Override
- //倒计时设置
- public void run() {
- //当设置时间大于0时,程序才会开始
- if(maxTime>0){
- //设置程序一直运行
- while(true){
- //设置黑方时间减少
- if(Black){
- //当黑方时间大于0时,设置黑方时间每秒减一
- if(BTime > 0){
- BTime--;
- }
- //黑方游戏时间耗尽时,判断游戏结束
- if(BTime == 0){
- JOptionPane.showMessageDialog(this,"黑方超时,游戏结束!");
- Goon = false;
- //当黑方时间等于0时,设置时间为-1,防止倒计时继续运转
- BTime = -1;
- }
- }else{
- if(WTime > 0){
- //设置白方时间每秒减一
- WTime--;
- }
- if(WTime == 0){
- //白方游戏时间耗尽时,判断游戏结束
- JOptionPane.showMessageDialog(this,"白方超时,游戏结束!");
- Goon = false;
- WTime = -1;
- }
- }
- //设置黑方显示时间为时分秒的显示形式
- bTime = BTime/3600+":"+
- (BTime/60-BTime/3600*60)+":"+
- (BTime-BTime/60*60);
- //设置白方显示时间为时分秒的显示形式
- wTime = WTime/3600+":"+
- (WTime/60-WTime/3600*60)+":"+
- (WTime-WTime/60*60);
- //重绘棋盘,刷新时间
- this.repaint();
- try {
- //设置停顿
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //System.out.println(BTime+"---"+WTime);//测试内容
- }
- }
- }
- }
ResultManager.class
- package FiveChessGame;
- import java.io.*;
- import java.nio.charset.StandardCharsets;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.ArrayList;
- public class ResultManager {
- private String filePath ="result.txt";
- public void writeResult(String resultStr) throws IOException {
- OutputStream outputStream = new FileOutputStream(filePath,true);
- StringBuilder stringBuilder = new StringBuilder();
- LocalDateTime localDateTime = LocalDateTime.now();
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- //格式化
- String dateTime = formatter.format(localDateTime);
- // System.out.println(dateTime);
- stringBuilder.append(dateTime).append(" | " + resultStr).append("\n");
- String data = stringBuilder.toString();
- byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
- try {
- outputStream.write(bytes);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- outputStream.close();
- }
- }
- public ArrayList readResult() throws IOException {
- // InputStream inputStream = new FileInputStream("result.txt");
- InputStream inputStream = new FileInputStream(filePath);
- byte[] buf = new byte[1024];
- int byteRead = -1;
- ArrayList resultList = new ArrayList();
- while ((byteRead = inputStream.read(buf)) != -1) {
- String data = new String(buf, 0, byteRead, StandardCharsets.UTF_8);
- resultList.add(data);
- // System.out.println(data);
- }
- return resultList;
- }
- // @Test
- // public void testWriteResult() throws IOException {
- // writeResult("黑方认输,白方获胜");
- // ArrayList data = readResult();
- // String res = "";
- // for(Object obj : data){
- // res += obj.toString();
- System.out.println(obj);
- // }
- // System.out.println(res);
- // }
- }
- 系统测试
测试目的:减少使用bug,令五子棋程序流畅运行
测试范围:边界问题,图片加载问题,倒计时问题,连子问题等
测试工具:idea
典型错误:1、当落子在棋盘边界时,编译器会不断报错
原因:定义棋盘的数组发生越界行为
2、程序刚运行时,图片会加载不出来
原因:内容混乱,
3、倒计时在为0后仍会往后倒数
原因:run函数if语句判断有纰漏
4、大于五个棋子相连时出现错误
原因:judge判定未设想到该种情况
测试手段:启动程序,任意进行一盘游戏,寻找可能的错误
5.游戏功能测试
游戏说明界面:
关于界面:
倒计时界面:
正确运行:
游戏输赢判断:
认输:
历史对局:
- 本程序高内聚低耦合的特点,易扩展、易维护
- 总结与体会
此次Java大作业我的收获颇深。一开始只是我想着做一个简单的欢乐五子棋小游戏,结果如何实现五子相连困扰了我很久。一开始我的构想是遍历一遍棋盘,查看是否有五个棋子连在一起,但是这个方法后面证明既麻烦,又低效。一遍遍的查找资料后,发现可以以当前落子的位置为参考点,来看周围是否有棋子与该落子的位置实现五子相连,这种方法不仅快速,而且占用内存小,同时又因为以每一次落子点为参考位置,不会出现漏洞。
悔棋的功能我构思了很久,本想放弃,但最后还是决定加上去。一开始我毫无头绪,后面一遍又一遍翻看代码时,重新开始游戏算法中的重绘棋盘给了我启发,只需要利用之前定义的棋盘坐标,短短十几行代码便搞定了我苦思冥想半天的问题。
然后是加上倒计时。多线程对于我来说是有点陌生的,当然,现在依旧称不上太熟悉,而且最困难的一点是,这部分的资料有些繁杂,不同的帖子用的方法和标准都不太一样,我一时间找不到突破口。当然,既然我的程序用上了倒计时,最后我还是把它解决了,但是过程有一些痛苦。首先是给黑白双方各设置一个线程,然后设置时间变量,当然,开始的时候因为变量设置太多把自己给绕进去了。然后是写run函数,设置停顿,一点一点试错,最后拼凑出来。剩下的部分就是倒计时的显示了,解决的方法让我有些出乎意料,只是简单的一个除法,加上计算机语言的特质就令我查阅许久。
最后是历史对局功能,我本来想着使用文件读写在弹窗出输赢的之后写入白方赢了,或者黑方赢了到文件中,然后再用窗口把他调用出来,但是这样会出现问题,也不能很好实现历史对局,后面经过反复观看文件读写相关视频才来了灵感。
上面三个功能,当我在查阅资料时,鲜有程序做到了这些,一开始我只是打算按教程去做项目,但后面随着一行行代码的敲下,我逐渐想要加上自己独属的内容。此次大作业,让我发现,其实一些看似不可思议的操作实际上只是基本方法的熟练使用,比如五子连珠的判断,其实就是利用数组的特点,倒计时显示更是只用了四则运算,没有太多花里胡哨的东西,掌握好基础知识才是最重要的。以及,变量的命名非常重要,好的命名方式可以节省不少时间,我再次在这次大作业中体会到了此点。
追风赶月莫停留,平芜尽处是春山,以后我们想继续深入学习Java或者其他语言,我认为基础内容和一些代码规范是非常重要的,一味追求算法反而容易忽略最本质的东西。当然,定期练习项目,整合所学知识也非常重要。多看,多听,多练,在计算机领域尤其适用。 - 自评分
自评分:93
自评语:本次大作业途中虽然遇到了很多困难,但我最终还是克服了这些困难,并在本次大作业中进行了创新,添加了倒计时,悔棋功能和查看历史对局功能,可以给游戏增加可玩性,不至于因为误触而影响游戏体验。