这期看标题已经能猜到了,主要讲的是成绩排行功能,还有对应的文件读写。那么废话不多说,让我们有请今天的主角...的设计稿:

  那么主角是何方神圣呢?当然是图中的大框框——TableView。关于这个控件的选取没有太多讲究,你也可以用文本域,手动换行来显示。我只是觉得使用表格显示看起来更规范些。接下来考虑数据来源,最直接的来源是每局游戏结束后的用时。不过这还不够,想要有排行一条记录可不行,也就是我们还要保存以往的记录,一般来讲10条即可。至于采用何种方式存取,那就具体情况具体分析了。像这个只是我本人制作分享,采用文件存取能够演示功能就行。有些朋友可能是为了课程设计来学习,需要配合数据库使用也可以,下面来看看文件存取的代码实现。

  首先就是文件和目录的创建问题,都开始写代码了就尽量把这些工作交给程序来完成:

static {
// 每次调用此类都先判断目录和文件是否存在
try {
File directory = new File(PREFIX + "/src/ranks");
if (!directory.exists() || !directory.isDirectory()) {
// 目录不存在, 自动创建
directory.mkdirs();
}
for (String path : RECORD_PATHS) {
path = PREFIX + path;
File file = new File(path);
if (!file.exists()) {
// 文件不存在, 自动创建
if (file.createNewFile()) {
// 创建成功, 写入内置数据
BufferedWriter writer = new BufferedWriter(new FileWriter(path));
for (int i = 0; i < 10; ++i) {
writer.write("未命名 999\n");
}
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
System.out.println("Error on [Class:FileIO, Method:static segment]=>");
e.printStackTrace();
}
}

  需要注意的是使用文件存取的容易引起的问题就是文件相对路径可能并不完全适用,如果你遇到问题还请分析是否由路径错误导致。我对文件路径处理方式如下(路径拼接为绝对路径):

// 完整路径前缀
public static String PREFIX = System.getProperty("user.dir"); // 排行榜文件相对路径
public static final String[] RECORD_PATHS = {
"\\src\\ranks\\easy.txt",
"\\src\\ranks\\medium.txt",
"\\src\\ranks\\hard.txt"
};

  然后就是文件读写,做法如下:

/**
* 读取文件
*
* @param filePath 文件路径
* @return 排行数据集合
*/
public static ObservableList<String[]> readFromFile(String filePath) {
ObservableList<String[]> list = FXCollections.observableArrayList();
try {
// 拼接路径, 创建读取对象
filePath = PREFIX + filePath;
BufferedReader reader = new BufferedReader(new FileReader(filePath));
// 读取数据
String line = null;
while ((line = reader.readLine()) != null) {
list.add(line.split(" "));
}
reader.close();
} catch (Exception e) {
System.out.println("Error on [Class:FileIO, Method:readFromFile]=>");
e.printStackTrace();
}
return list;
} /**
* 向文件内写入数据
*
* @param filePath 文件路径
* @param record 待更新数据项
*/
public static void writeToFile(String filePath, String[] record) {
try {
// 获取已有数据
ObservableList<String[]> list = readFromFile(filePath);
// 将记录插入到合适位置
for (int i = 0; i < 10; ++i) {
if (record[1].compareTo(list.get(i)[1]) <= 1) {
list.add(i, record);
break;
}
}
// 移除多余记录
list.remove(10);
// 重新写入数据
BufferedWriter writer = new BufferedWriter(new FileWriter(PREFIX + filePath));
for (String[] item : list) {
writer.write(item[0] + " " + item[1] + "\n");
}
writer.flush();
writer.close();
} catch (Exception e) {
System.out.println("Error on [Class:FileIO, Method:writeToFile]=>");
e.printStackTrace();
}
}

  这里大家可能对Observation这个接口不太熟悉,使用它是因为TableView指定了它为数据源的类型。关于它的说明,官方文档介绍如下:

A list that allows listeners to track changes when they occur.  Implementations can be created using methods in FXCollections such as observableArrayList, or with a SimpleListProperty.

允许侦听器在发生更改时跟踪更改的列表。实现可以使用FXCollections中的方法来创建,比如observableArrayList,或者使用SimpleListProperty。

  数据处理工作准备完毕后就是展示环节。从本文最开始的设计图可以看出表格由两列内容,一个是玩家昵称,另一个是用时。在代码中对应写法如下:

@FXML  // 排行展示表
private TableView<String[]> table;
@FXML // 表格列
private TableColumn<String[], String> name, time;
// 用于存放数据的列表
private ObservableList<String[]> data; // 设置数据源
table.setItems(data);
// 设置单元格大小
table.setFixedCellSize(36.0);
// 设置每个 TableColumn 的 cellValueFactory
name.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue()[0]));
time.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue()[1]));

  那么有关难度的单选按钮是干嘛用的呢?很明显是区分不同难度下的成绩,下面是相关实现:

@FXML  // 单选按钮, 难度
private RadioButton easy, medium, hard;
// 单选按钮组
private ToggleGroup degree; // 单选按钮分组
degree = new ToggleGroup();
easy.setToggleGroup(degree);
medium.setToggleGroup(degree);
hard.setToggleGroup(degree); // 默认选中简单, 并加载数据
easy.setSelected(true);
data = FileIO.readFromFile(RECORD_PATHS[0]);
table.setItems(data); // 难度按钮选中事件
degree.selectedToggleProperty().addListener(((observable, oldValue, newValue) -> {
String id = ((RadioButton) newValue).getId();
// 根据不同按钮设置不同文件路径
if (id.equals("easy")) {
data = FileIO.readFromFile(RECORD_PATHS[0]);
} else if (id.equals("medium")) {
data = FileIO.readFromFile(RECORD_PATHS[1]);
} else {
data = FileIO.readFromFile(RECORD_PATHS[2]);
}
table.setItems(data);
}));

  然后让我们回到整个流程开始,获取游戏用时,即游戏胜利时的处理。上期我们讲计时事件中有这样一段代码(使用runLater是因为在动画或布局处理期间不允许使用showAndWait,也就是无法使用提示框):

// 自定义模式不计入成绩
if (GAME != GameEnum.CUSTOM) {
Platform.runLater(() -> showDialog());
}

  这个 showDialog 方法就是关键,它的完整内容如下:

/**
* 用时少于排行版某一项, 输入玩家名称
*/
private void showDialog() {
// 创建带输入的对话框
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("新纪录!");
dialog.setHeaderText("请输入您的昵称:");
dialog.getDialogPane().setGraphic(null); dialog.setOnCloseRequest(event -> {
// 处理取消或关闭事件时输入为空的情况
String userInput = dialog.getEditor().getText();
if (userInput == null || userInput.trim().equals("")) {
event.consume(); // 阻止关闭操作
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setContentText("您必须输入些什么");
alert.showAndWait();
}
});
// 输入事件
Optional<String> result = dialog.showAndWait();
result.ifPresent(name -> {
// 获取输入, 保存到文件
String filePath = null;
if (name == null || name.equals("")) {
name = "player";
}
String[] record = new String[]{name, TIMER + ""};
switch (GAME) {
case HARD:
filePath = RECORD_PATHS[2];
break;
case MEDIUM:
filePath = RECORD_PATHS[1];
break;
case EASY:
filePath = RECORD_PATHS[0];
default:
break;
}
FileIO.writeToFile(filePath, record);
});
}

  看上去似乎充分避免了玩家在游戏结束不输入的情况,事实上并非如此。在带输入的提示框弹出后点击右上角关闭,虽然仍会弹出警告框提醒玩家未输入,但最终还是会在没有输入的情况下关闭。这里我觉得可以使用死循环来改善,即额外定义一个变量初始值为false,只有当获取到输入内容后初始值变为true,才允许结束循环。感兴趣的朋友们可以尝试下,本期内容到此结束,感谢观看。

——————————————我———是———分———割———线—————————————

  天气好热啊,除了呆在空调屋刷手机什么都不想干啦!可是还有好多事要办,必须出门,热死我算了

基于JavaFX的扫雷游戏实现(四)——排行榜的更多相关文章

  1. 基于jQuery经典扫雷游戏源码

    分享一款基于jQuery经典扫雷游戏源码.这是一款网页版扫雷小游戏特效代码下载.效果图如下: 在线预览   源码下载 实现的代码. html代码: <center> <h1>j ...

  2. C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式

    C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...

  3. 基于JavaFX图形界面演示的迷宫创建与路径寻找

    事情的起因是收到了一位网友的请求,他的java课设需要设计实现迷宫相关的程序--如标题概括. 我这边不方便透露相关信息,就只把任务要求写出来. 演示视频指路: 视频过审后就更新链接 完整代码链接: 网 ...

  4. [置顶] 使用红孩儿工具箱完成基于Cocos2d-x的简单游戏动画界面

    [Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ3群:205100149,47 ...

  5. (转载)WinformGDI+入门级实例——扫雷游戏(附源码)

    本文将作为一个入门级的.结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解.游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中. 整体思路: 扫雷的游戏界面让我从 ...

  6. JavaSwing 版本的简单扫雷游戏

    JavaSwing 版本的简单扫雷游戏 一.扫雷游戏的基本规则 1.扫雷游戏分为初级.中级.高级和自定义四个级别. 单击游戏模式可以选择"初级"."中级".&q ...

  7. 扫雷游戏 NOIP(入门)

    题目描述: 扫雷游戏是一款十分经典的单机小游戏.它的精髓在于,通过已翻开格子所提示的周围格地雷数,来判断未翻开格子里是否是地雷. 现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格的周围格地雷 ...

  8. 使用vue写扫雷游戏

    上班闲来没事做,,心血来潮.想用刚学的vue,写一个扫雷游戏..好了,直入正题. 第一步,先制作一个10x10的格子图..这个divcss就不说了..大家都会. 第二步,制造一个数组,用来生成随机雷区 ...

  9. WinformGDI+入门级实例——扫雷游戏(附源码)

    写在前面: 本文将作为一个入门级的.结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解.游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中. 整体思路: 扫雷的游 ...

  10. Pomelo:网易开源基于 Node.js 的游戏服务端框架

    Pomelo:网易开源基于 Node.js 的游戏服务端框架 https://github.com/NetEase/pomelo/wiki/Home-in-Chinese

随机推荐

  1. mysql中innodb_open_files限制导致数据库异常重启

    问题描述:收到监控软件告警,提示数据库发生重启,进去查看,截止到6/27 10:00 之前,作为主节点的orch1先重启,然后故障转移到orch2和orch3节点上.在持续到6/27 9:00 左右, ...

  2. PyInstaller打包的文件闪退

    问题描述:使用PyInstaller打包的pycharm写的python程序,打包好后从windows上打开一直闪退 一.双击exe文件闪退,从cmd命令行中与加载程序,可以看到具体的报错 D:\di ...

  3. RDIFramework.NET代码生成器全新V5.0版本发布

    RDIFramework.NET代码生成器介绍 RDIFramework.NET代码生成器,代码.文档一键生成. RDIFramework.NET代码生成器集代码生成.各数据库对象文档生成.数据库常用 ...

  4. 如何玩转国产神器:接口一体化协作平台Apifox!

    前言:Apifox是什么? 简介: 简单来说,Apifox = swagger + mock + postman+Jmeter,是API 文档.API 调试.API Mock.API 自动化测试一体化 ...

  5. Go语言基础: JSON序列化

    JSON 是一种轻量级的数据交换格式.简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言. 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率. 首先得熟悉结构体: pack ...

  6. RCE-Tricks

    这篇文章介绍RCE的一些tricks 0x01 无回显的RCE 在ctf中,有时会遇到无回显rce,就是说虽然可以进行命令执行,但却看不到命令执行的结果,也不知道命令是否被执行,借着这次总结rce的机 ...

  7. 小知识:使用errorstack定位特定问题

    有客户遇到ORA-2289的报错,同事协助去现场排查,我帮着远程共同check下. 客户只是应用端报出的错误,为了进一步定位,服务端需要开errorstack协助定位具体问题. 下面就以这个ORA-2 ...

  8. Centos7.x 更换Jenkins构建目录

    原由:最近因为原来的Jenkins构建目录,已经要满了,想着更换下构建目录,此篇文件简单介绍下更换过程. 注:此文章可能仅适用于我个人,仅供参考.如有其他办法,欢迎评论指教. 查了几种方法,最终选为使 ...

  9. 2021牛客OI赛前集训营-提高组(第三场) 第二题 交替 题解与结论证明

    题目描述 一个长度为 \(n\) 的数组\(A\),每秒都会变成一个长度为 \(n − 1\) 新数组 \(A'\),其变化规 则如下: 若当前数组 \(A\) 的长度 \(n\) 为偶数,则对于新数 ...

  10. C++ Primer 5th Edition, Chapter 2, Solutions

    Exercise 2.1 Questions What are the differences between int, long, long long, and short? Between an ...