相信阅读过上期文章,动手能力强的朋友们已经自己跑出来界面了。所以这期我要讲的是交互部分,也就是对于鼠标点击事件的响应,包括计时计数对点击事件以及一些状态量的影响。

  回忆下第一期介绍的扫雷规则和操作,游戏从开局到结束可能会涉及到哪些情况呢?我认为比较重要的就是明确什么情况下游戏已经结束,结束代表的是胜利还是失败。对此我定义了一个游戏状态量,他有位置、胜利和失败三种可选值,如下:

// 游戏状态相关 [1:获胜, 0:未知, -1:失败]
public static byte WIN = 1;
public static byte UNSURE = 0;
public static byte LOSS = -1;
public static byte STATE = UNSURE;

  很显然游戏只要还未结束,就应该保持在未知状态。那么哪些情况会影响到状态量的取值,就需要我们逐个分析了。

  根据规则,当我们把除地雷以外的所有格子均点开后便取得胜利,所以右键点击并不会对游戏状态造成影响。那我们仅需在每次左键点击处理中进行格子数统计,符合要求就修改游戏状态为胜利,点击到地雷便修改为失败。另外每次点击都需要更新相关格子的显示,所以这两项任务可以放在一起进行,做法如下:

// 更新点击过的数据
mineSweeper.clickCell(row, column);

  执行完后就对游戏状态进行判断,如果没有点击到地雷,执行 STATE == UNSURE 部分:

if (STATE == UNSURE) {
// 统计非雷格子已点开数目
int count = 0;
for (int i = 0; i < GAME.height; ++i) {
for (int j = 0; j < GAME.width; ++j) {
if (map[i][j] > BOUND) {
Button btn = (Button) buttons.get(i * GAME.width + j);
count += 1;
int value = map[i][j] - 100;
if (value != BLANK) {
// 消除空白填充
btn.setPadding(new Insets(0.0));
// 设置粗体和字体颜色
btn.setFont(Font.font("Arial", FontWeight.BOLD, GAME.numSize));
btn.setTextFill(NUMS[value - 1]);
btn.setText(value + "");
}
btn.setStyle("-fx-border-color: #737373; -fx-opacity: 1; -fx-background-color: #ffffff");
btn.setDisable(true);
}
}
}
// 判断全部非雷格子是否全部点开
if (count + GAME.bomb == GAME.width * GAME.height) {
STATE = WIN;
}
}

  否则执行 STATE == LOSS 部分:

if (STATE == LOSS) {
// 游戏失败, 显示所有地雷位置
for (int i = 0; i < GAME.height; ++i) {
for (int j = 0; j < GAME.width; ++j) {
if (map[i][j] == BOMB) {
Button btn = (Button) buttons.get(i * GAME.width + j);
btn.setStyle("-fx-background-color:#ffffff; -fx-background-size: contain; -fx-background-image: url(" + UNEXPLODED_IMG + ")");
}
}
}
button.setStyle("-fx-background-color:#ffffff; -fx-background-size: contain; -fx-background-image: url(" + EXPLODED_IMG + ")");
}

  看上去似乎所有任务都完成了,真的是这样吗?别忘了还有计时功能,时间超出指定范围也可以认为是游戏失败。上期说过计时计数这块有自定义控件,这期它依旧不是主角,但是我会大致说明下它的工作方式。如果你还记得游戏界面那两个黑框框是GridPane布局的话,显示出的数字就是其中的控件外观。我使用的是三位数,也就是说每个布局中都含有三个数字自定义控件,根据数值不同排列组合表示不同整数。

  首先来讲计时,这里JavaFX提供的有时间轴类,直接拿来用非常方便。我们可以设置事件触发的间隔,对应到扫雷里自然是每秒触发一次。事件中要做的就是判断游戏状态和是否超时,下面给出代码以供参考。

  涉及到的量:

// 时间计数和超时范围
public static int TIMER = 0;
public static int OVERTIME = 999;
// 计时器
public static Timeline TIMELINE = null;

  计时事件:

TIMELINE = new Timeline(
new KeyFrame(Duration.seconds(1), event -> {
TIMER += 1;
// 超时自动判负
if (TIMER >= OVERTIME) {
STATE = LOSS;
}
// 游戏胜负已确定
if (STATE != UNSURE) {
String path = WIN_IMG;
TIMELINE.stop();
if (STATE == LOSS) {
path = LOSS_IMG;
} else {
// 自定义模式不计入成绩
if (GAME != GameEnum.CUSTOM) {
Platform.runLater(() -> showDialog());
}
}
reset.setStyle("-fx-background-size: contain; -fx-background-image: url(" + path + ")");
}
ledTime[0].switchSkin(TIMER / 100);
ledTime[1].switchSkin(TIMER % 100 / 10);
ledTime[2].switchSkin(TIMER % 10);
})
);

  接下来是计数功能,数字显示原理同上,主要是交互。这个数字表示的是游戏中剩余可用标记数 REST_FLAG,它的值通过左右键点击改变。它的改变规则具体如下:

  1. 该数值初始大小等于地雷数目。
  2. 右键点击未知格子时,如果先前没有标记,那么值减去1,标记旗帜;如果已有旗帜标记,值不变,替换为问号标记;如果已有问号标记,值加上1,去除格子上的标记。
  3. 左键点击有标记的格子时,不管是哪种标记,值统统加上1,去除标记。

  接下来需要考虑如何监听 REST_FLAG 值的变化,通过查阅资料,我找到了一种方案 ReadOnlyIntegerWrapper。该类提供了一个方便的类来定义只读属性。它创建两个同步的属性。一个属性是只读的,可以传递给外部用户。另一个属性是可读写的,只能在内部使用。最重要的是可以对它设置监听器,在值发生变化时执行一些操作,实现如下:

// 创建具有可观察特性的整数变量
rest = new ReadOnlyIntegerWrapper(REST_FLAG);
// 添加监听器, 在变量值变化时执行相应的操作, 下同
ChangeListener<? super Number> restListener = (observable, oldValue, newValue) -> {
// 在变量值变化时执行相应的操作
ledMark[0].switchSkin(REST_FLAG / 100);
ledMark[1].switchSkin(REST_FLAG % 100 / 10);
ledMark[2].switchSkin(REST_FLAG % 10);
};
// 将监听器绑定到rest属性
rest.addListener(restListener);

  这些工作完成后,我们再来考虑一个有关计时的问题。什么时机开始计时较为合适呢?是进入游戏界面,还是第一次点击格子?我认为后者更符合要求。当然这个全看个人设计,如果采用后者的方案的话,也需要设置对应的值来监听,比如下面这种:

// 游戏是否开局, 即格子是否被点击过 [1:是, 0:否]
public static int YES = 1;
public static int NO = 0;
public static int CLICKED = NO;

  然后把上边提到的监听事件与之结合:

clicked = new ReadOnlyIntegerWrapper(CLICKED);
ChangeListener<? super Number> clickListener = (observable, oldValue, newValue) -> {
// 已经被点击, 开始计时
TIMER = 0;
// TODO 这里放入计时监听事件
TIMELINE.setCycleCount(Animation.INDEFINITE);
TIMELINE.play();
};
clicked.addListener(clickListener);

  值发生变化后需要手动调用set方法触发监听:

// 判断游戏是否开局
if (CLICKED == NO) {
CLICKED = YES;
clicked.set(CLICKED);
} // 触发监听, 修改剩余地雷数显示
rest.set(REST_FLAG);

  截止到这里,有关游戏部分就只剩下排行榜功能未介绍了。至于鸽了好几期都没说的自定义控件,因为我觉得它的实现并不重要,了解它的作用一样能理解前边的内容,所以就放在最后一期再说吧。

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

  我居然更到第三期了哎,一周之内呀!太勤快了吧!不行,最多再更两期,我要报仇雪恨般地拖更,拖拖拖拖拖拖拖一拖到明年,大好时光怎么能天天用来码文呢?我要打电动去啦,阿伟也拦不住,我说的!

基于JavaFX的扫雷游戏实现(三)——交互逻辑的更多相关文章

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

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

  2. 基于HTML5的SLG游戏开发( 三):认识PureMVC

    在游戏开发中,对于一般网络游戏,由于需要多人协同开发,每个人负责不同的模块开发,为了减少耦合,需要用来一些MVC框架,减少模块之间的耦合.我们现在使用的mvc框架是pureMVC.pureMVC的官网 ...

  3. web版扫雷小游戏(三)

    ~~~接上篇,上篇介绍了游戏实现过程中第一个比较繁琐的地方,现在展现在玩家面前的是一个有血有肉的棋盘,从某种意义上说玩家已经可以开始游戏了,但是不够人性化,玩家只能一个一个节点的点开,然后判断,然后标 ...

  4. 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 ...

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

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

  6. wpf版扫雷游戏

    近来觉得wpf做出来的界面很拉风,自己也很喜欢搞些小游戏,感觉这做出来的会很炫,很装逼,(满足自己的一点小小的虚荣心)于是就去自学,发现感觉很不错,可是属性N多,太多了,而且质料也少,很多不会用,只会 ...

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

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

  8. [LeetCode] Minesweeper 扫雷游戏

    Let's play the minesweeper game (Wikipedia, online game)! You are given a 2D char matrix representin ...

  9. 【Android】自己动手做个扫雷游戏

    1. 游戏规则 扫雷是玩法极其简单的小游戏,点击玩家认为不存在雷的区域,标记出全部地雷所在的区域,即可获得胜利.当点击不包含雷的块的时候,可能它底下存在一个数,也可能是一个空白块.当点击中有数字的块时 ...

  10. C#编写扫雷游戏

    翻看了下以前大学学习的一些小项目,突然发现有个项目比较有意思,觉得有必要把它分享出来.当然现在看来,里面有很多的不足之处,但因博主现在已经工作,没有时间再去优化.这个项目就是利用C#编写一个Windo ...

随机推荐

  1. 关于微人事中POI导入文件到数据库的异常以及自己的一些技术心得

    前言 在近四个月的时间里面,我的微人事项目才逐渐接近尾声,在昨天的测试接口中出现了两次数组越界以及一次空指针异常,三处异常我都通过吊事bug根据项目实际情况解决了,但是在空指针异常那里还是带有疑问,起 ...

  2. day115:MoFang:种植园我的背包&种植园道具购买

    目录 1.我的背包 2.道具购买 1.我的背包 1.在种植园点击背包按钮打开我的背包 在种植园打开背包,orchard.html,代码: <!DOCTYPE html> <html& ...

  3. 访问nginx报错502日志:failed (13: Permission denied) while connecting to upstream

    1.错误问题 nginx启动成功,但是访问nginx报错502.检查后台项目,使用IP+端口可以正常访问项目的,这说明项目启动成功了.那就是nginx的问题.检查了nginx.conf文件发现配置的反 ...

  4. Kubernetes(K8S) kubesphere 介绍

    使用 Kubeadm 部署 Kubernetes(K8S) 安装--附K8S架构图 官网地址:https://kubesphere.com.cn/ KubeSphere 是个全栈的Kubernetes ...

  5. 解决ffmpeg源码不能编译ffplay问题

    虽然不是很大问题,还是记录一下,避免以后忘记!!! 总共两个原因影响了源码编译不能生成ffplay可执行文件,如下: 1.系统中没有安装SDL,直接去官网下载SDL源码编译安装http://www.l ...

  6. NOIP 2021 备战计划

    NOIP 2021 备战计划 复习知识点: 加粗表示一定去复习,?表示很可能不需要 线段树.树状数组:无论最近写多少遍都要去好好复习 Dij.SPFA:理由同上 大DP:哪个不重要? 门类:线性DP. ...

  7. 【Linux】Linux 基础入门

    Linux 发行版(发行版之间的联系与区别) 红帽公司开发的RedHat Enterprise Linux,它是全世界内使用最广泛的Linux系统,具有极强的性能与稳定性,并且在全球范围内拥有完善的技 ...

  8. Websocket 60秒断开,连接不稳定

    本地测试都是正常的,线上测试总是过一会就断开... 线上新增了https协议,导致页面中的链接必须也是ssl Websocket链接地址从ws://ws.xxx.com改成了wss://ws.xxx. ...

  9. 自动化部署(Gitlab)

    小程序可持续化自动部署 一.安装gitlab-runner 官方地址:https://docs.gitlab.com/runner/install/ windows安装如下: nodejs的环境变量一 ...

  10. 2022-03-18:arr数组长度为n, magic数组长度为m 比如 arr = { 3, 1, 4, 5, 7 },如果完全不改变arr中的值, 那么收益就是累加和 = 3 + 1 + 4 +

    2022-03-18:arr数组长度为n, magic数组长度为m 比如 arr = { 3, 1, 4, 5, 7 },如果完全不改变arr中的值, 那么收益就是累加和 = 3 + 1 + 4 + ...