基于JavaFX的扫雷游戏实现(三)——交互逻辑

打印 上一主题 下一主题

主题 877|帖子 877|积分 2631

  相信阅读过上期文章,动手能力强的朋友们已经自己跑出来界面了。所以这期我要讲的是交互部分,也就是对于鼠标点击事件的响应,包括计时计数对点击事件以及一些状态量的影响。
  回忆下第一期介绍的扫雷规则和操作,游戏从开局到结束可能会涉及到哪些情况呢?我认为比较重要的就是明确什么情况下游戏已经结束,结束代表的是胜利还是失败。对此我定义了一个游戏状态量,他有位置、胜利和失败三种可选值,如下:
  1. // 游戏状态相关 [1:获胜, 0:未知, -1:失败]
  2. public static byte WIN = 1;
  3. public static byte UNSURE = 0;
  4. public static byte LOSS = -1;
  5. public static byte STATE = UNSURE;
复制代码
  很显然游戏只要还未结束,就应该保持在未知状态。那么哪些情况会影响到状态量的取值,就需要我们逐个分析了。
  根据规则,当我们把除地雷以外的所有格子均点开后便取得胜利,所以右键点击并不会对游戏状态造成影响。那我们仅需在每次左键点击处理中进行格子数统计,符合要求就修改游戏状态为胜利,点击到地雷便修改为失败。另外每次点击都需要更新相关格子的显示,所以这两项任务可以放在一起进行,做法如下:
  1. // 更新点击过的数据
  2. mineSweeper.clickCell(row, column);
复制代码
  执行完后就对游戏状态进行判断,如果没有点击到地雷,执行 STATE == UNSURE 部分:
  1. if (STATE == UNSURE) {
  2.     // 统计非雷格子已点开数目
  3.     int count = 0;
  4.     for (int i = 0; i < GAME.height; ++i) {
  5.         for (int j = 0; j < GAME.width; ++j) {
  6.             if (map[i][j] > BOUND) {
  7.                 Button btn = (Button) buttons.get(i * GAME.width + j);
  8.                 count += 1;
  9.                 int value = map[i][j] - 100;
  10.                 if (value != BLANK) {
  11.                     // 消除空白填充
  12.                     btn.setPadding(new Insets(0.0));
  13.                     // 设置粗体和字体颜色
  14.                     btn.setFont(Font.font("Arial", FontWeight.BOLD, GAME.numSize));
  15.                     btn.setTextFill(NUMS[value - 1]);
  16.                     btn.setText(value + "");
  17.                 }
  18.                 btn.setStyle("-fx-border-color: #737373; -fx-opacity: 1; -fx-background-color: #ffffff");
  19.                 btn.setDisable(true);
  20.             }
  21.         }
  22.     }
  23.     // 判断全部非雷格子是否全部点开
  24.     if (count + GAME.bomb == GAME.width * GAME.height) {
  25.         STATE = WIN;
  26.     }
  27. }
复制代码
  否则执行 STATE == LOSS 部分:
  1. if (STATE == LOSS) {
  2.     // 游戏失败, 显示所有地雷位置
  3.     for (int i = 0; i < GAME.height; ++i) {
  4.         for (int j = 0; j < GAME.width; ++j) {
  5.             if (map[i][j] == BOMB) {
  6.                 Button btn = (Button) buttons.get(i * GAME.width + j);
  7.                 btn.setStyle("-fx-background-color:#ffffff; -fx-background-size: contain; -fx-background-image: url(" + UNEXPLODED_IMG + ")");
  8.             }
  9.         }
  10.     }
  11.     button.setStyle("-fx-background-color:#ffffff; -fx-background-size: contain; -fx-background-image: url(" + EXPLODED_IMG + ")");
  12. }
复制代码
  看上去似乎所有任务都完成了,真的是这样吗?别忘了还有计时功能,时间超出指定范围也可以认为是游戏失败。上期说过计时计数这块有自定义控件,这期它依旧不是主角,但是我会大致说明下它的工作方式。如果你还记得游戏界面那两个黑框框是GridPane布局的话,显示出的数字就是其中的控件外观。我使用的是三位数,也就是说每个布局中都含有三个数字自定义控件,根据数值不同排列组合表示不同整数。
  首先来讲计时,这里JavaFX提供的有时间轴类,直接拿来用非常方便。我们可以设置事件触发的间隔,对应到扫雷里自然是每秒触发一次。事件中要做的就是判断游戏状态和是否超时,下面给出代码以供参考。
  涉及到的量:
  1. // 时间计数和超时范围
  2. public static int TIMER = 0;
  3. public static int OVERTIME = 999;
  4. // 计时器
  5. public static Timeline TIMELINE = null;
复制代码
  计时事件:
  1. TIMELINE = new Timeline(
  2.         new KeyFrame(Duration.seconds(1), event -> {
  3.             TIMER += 1;
  4.             // 超时自动判负
  5.             if (TIMER >= OVERTIME) {
  6.                 STATE = LOSS;
  7.             }
  8.             // 游戏胜负已确定
  9.             if (STATE != UNSURE) {
  10.                 String path = WIN_IMG;
  11.                 TIMELINE.stop();
  12.                 if (STATE == LOSS) {
  13.                     path = LOSS_IMG;
  14.                 } else {
  15.                     // 自定义模式不计入成绩
  16.                     if (GAME != GameEnum.CUSTOM) {
  17.                         Platform.runLater(() -> showDialog());
  18.                     }
  19.                 }
  20.                 reset.setStyle("-fx-background-size: contain; -fx-background-image: url(" + path + ")");
  21.             }
  22.             ledTime[0].switchSkin(TIMER / 100);
  23.             ledTime[1].switchSkin(TIMER % 100 / 10);
  24.             ledTime[2].switchSkin(TIMER % 10);
  25.         })
  26. );
复制代码
  接下来是计数功能,数字显示原理同上,主要是交互。这个数字表示的是游戏中剩余可用标记数 REST_FLAG,它的值通过左右键点击改变。它的改变规则具体如下:

  • 该数值初始大小等于地雷数目。
  • 右键点击未知格子时,如果先前没有标记,那么值减去1,标记旗帜;如果已有旗帜标记,值不变,替换为问号标记;如果已有问号标记,值加上1,去除格子上的标记。
  • 左键点击有标记的格子时,不管是哪种标记,值统统加上1,去除标记。
  接下来需要考虑如何监听 REST_FLAG 值的变化,通过查阅资料,我找到了一种方案 ReadOnlyIntegerWrapper。该类提供了一个方便的类来定义只读属性。它创建两个同步的属性。一个属性是只读的,可以传递给外部用户。另一个属性是可读写的,只能在内部使用。最重要的是可以对它设置监听器,在值发生变化时执行一些操作,实现如下:
[code]// 创建具有可观察特性的整数变量rest = new ReadOnlyIntegerWrapper(REST_FLAG);// 添加监听器, 在变量值变化时执行相应的操作, 下同ChangeListener
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表