基于JavaFX的扫雷游戏实现(一)——整体概述

这篇具有很好参考价值的文章主要介绍了基于JavaFX的扫雷游戏实现(一)——整体概述。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 

我在不要更新挑战中坚持了一年🎉🎉🎉,你也来试试吧(咕咕咕)!

  好言归正传,本次更新带来的是经典游戏扫雷,基于JavaFX实现。篇幅有限,文章主要介绍核心操作实现,不会列出所有代码。需要完整源码或是想预览最终效果,可以点击下方链接。后续会逐步更新细节实现方面的内容,将来吧反正(肯定不鸽!

 视频演示:

    https://www.bilibili.com/video/BV1jh4y1u7ad

  源码(项目所使用的JDK为1.8版):

    1. GitHub:https://github.com/xiao-qi-w/MineSweeper
    2. 百度网盘&提取码:https://pan.baidu.com/s/1GqGbfCdluc1yrrniAGh7SQ?pwd=abcd 

  如果您已经看过视频,或是成功运行代码,相信对本项目和扫雷已经有了初步认知。如果您是直接阅读的本篇文章,这里也提供了在线的扫雷入口,方便您快速了解:扫雷游戏网页版 - Minesweeper非本人制作,仅分享

  怎么样,是否找回了那些年在微机课上偷偷玩扫雷的快乐。总之不管您之前有没有玩过,我建议先熟悉下它的规则和操作,本项目主要是围绕这些内容编写。

规则:

  1. 扫雷游戏是在一个方格网格中进行的,其中包含了地雷和数字。
  2. 目标是清除所有非地雷方格而不触发地雷。

操作:

  1. 游戏开始时,你会看到一个方格网格,其中的方格是覆盖的。
  2. 你可以通过鼠标左键点击一个方格来揭开它。如果揭开的方格是地雷,游戏结束,你输了。
  3. 如果揭开的方格是数字,它会显示周围相邻方格中地雷的数量。
  4. 如果揭开的方格是空白方格(数字为0),它会自动揭开相邻的空白方格和数字方格,直到边界或者遇到数字方格为止。
  5. 如果你认为某个方格是地雷,你可以使用鼠标右键进行标记。标记的方格会显示一个旗帜图标,表示你认为该方格是地雷。
  6. 如果你揭开了所有非地雷方格,游戏胜利。

       了解完这些,让我们尝试使用代码来实现它。

       首先是数据来源的问题。每生成一局新游戏,都有对应的地雷数字分布记录,用于指导你推断哪些地方是数字,哪些地方是地雷。考虑到游戏界面行列整齐排放的格子,用二维数组存取对应数据最直观易懂。那么选定数据结构后,如何生成初始数据呢?鉴于每局游戏的数据几乎不会重复,如果只靠我们预输入的数据,没玩几局就腻了。为此可以采用随机生成数据的方式,我的做法如下:

/**
 * 生成新游戏的地图数据
 */
public void init() {
    // 用于记录地雷的位置, 避免重复选择
    HashSet<Integer> set = new HashSet();
    // 确定随机数据范围
    int count = height * width;
    // 开始随机
    for (int rest = bomb; rest > 0; ) {
        int index = rand.nextInt(count);
        // 如果当前位置可以设置为地雷, 标记该位置, 地雷剩余个数减一
        if (!set.contains(index)) {
            set.add(index);
            map[index / width][index % width] = BOOM;
            rest -= 1;
        }
    }
    // 统计地雷分布情况
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            if (map[i][j] != BOOM) {
                map[i][j] = countBomb(i, j);
            }
        }
    }
}

  map是用于存储数据的二维数组;width和height分别表示横向和纵向的格子数,即map每个维度的长

  仅生成地雷位置还不够,我们还需要知道地雷周围对应的数字,上面代码中的countBomb方法负责完成这部分工作,具体实现如下:

/**
 * 统计当前格子周围的地雷个数
 *
 * @param x 横坐标
 * @param y 纵坐标
 * @return count 地雷个数
 */
public int countBomb(int x, int y) {
    int count = 0;
    // 依次判断周围格子是否存在地雷
    for (int i = 0; i < 8; ++i) {
        int newX = x + positions[i][0];
        int newY = y + positions[i][1];
        if (newX > -1 && newX < height && newY > -1 && newY < width && map[newX][newY] == BOMB) {
            count += 1;
        }
    }
    return count;
}

  positions为相对方位坐标数组,用于计算周围八个格子的坐标;BOMB是int常量,值为9,表示地雷

  这样就有了初始游戏数据,仅有这个还不够,我们最终要把它展示在屏幕上。不妨想一下,在绘制界面的过程中,我们可以根据数据的不同来确定某个格子具体显示为地雷,数字或是空白。比如0-8表示周围地雷个数统计,9表示地雷。可是游戏一开始全是未知的格子,难道我们要再设置一个相应的boolean数组记录格子是否被点开吗?这样做虽然可行,但我觉得较为麻烦,所以我是这样设计的:

// 数字常量 [0:空白格, 9:地雷]
public static final byte BLANK = 0;
public static final byte BOMB = 9;
// [20:旗帜标记判断, 40:问号标记判断]
public static final byte FLAG = 20;
public static final byte GUESS = 40;
// [99:边界标记, 超过这个数字代表当前格子已被点开]
public static final byte BOUND = 99;

  对于可能用于逻辑判断的量,将它们定义为常量,这样在代码中就不会出现 if ( 变量 == 9 ),却不清楚‘9’是什么含义的情况,避免降低可读性。其次是格子是否被点击过的问题,可以设置一个边界值进行区分。因为地雷和周围数字只占用了很少一部分整型数据,所以可以根据数据是否超过某个范围来判断是否被点击过。最后是右键标记问题,我印象里的操作是右键一次采用旗帜标记,两次采用问号标记,所以设置两个对应常量用于判断。下面是点击过程中的逻辑判断代码:

// 获取按钮
Button button = (Button) buttons.get(row * GAME.width + column);
// 根据左右键设置不同响应逻辑
if (event.getButton() == MouseButton.SECONDARY) {
    // 定义图片路径
    String imagePath = null;
    // 右键对应行为
    if (map[row][column] >= GUESS) {
        // 不设置图片, 还原雷的数目
        map[row][column] -= GUESS;
        REST_FLAG += 1;
    } else if (map[row][column] >= FLAG) {
        // 如果已经被标记, 路径更换为问号图片, 表示不确定
        imagePath = GUESS_IMG;
        map[row][column] = map[row][column] - FLAG + GUESS;
    } else {
        // 未被标记过, 判断是否还有可用标记
        if (REST_FLAG > 0) {
            imagePath = FLAG_IMG;
            map[row][column] += FLAG;
            REST_FLAG -= 1;
        }
    }
    button.setStyle("-fx-background-size: contain; -fx-background-image: url(" + imagePath + ")");
} else {
    // 左键对应行为
    if (map[row][column] <= BOUND && map[row][column] >= FLAG) {
        // 如果被标记, 则先清空标记
        map[row][column] -= map[row][column] >= GUESS ? GUESS : FLAG;
        REST_FLAG += 1;
        button.setStyle("-fx-background-size: contain; -fx-background-image: url(" + null + ")");
    } else {
        // 更新点击过的数据
        mineSweeper.clickCell(row, column);

        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;
            }
        } else 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 + ")");
        }
    }
}

  注:阅读时请先忽略掉界面控件相关的操作,仅需关注map数据的变化

  这段代码根据左或右键点击来进行对应的操作,同时引出了新的问题,点击格子后不总是只更新它自身的数据,像操作中说的,如果它是空白格 (数据为0),还需要展开它周围的格子,这个过程是怎么进行的呢?它在上述代码中体现为mineSweeper.clickCell(row, column); 具体实现如下:

/**
 * 展开与当前位置相连的所有空白区域, 包括包裹这层空白区域数字边界
 *
 * @param x 横坐标
 * @param y 纵坐标
 */
public void clickCell(int x, int y) {
    if (map[x][y] == BLANK) {
        map[x][y] += 100;
        // 点击到空白区域, 递归判断周围8个方向
        for (int i = 0; i < 8; i += 1) {
            int newX = x + positions[i][0];
            int newY = y + positions[i][1];
            if (newX > -1 && newX < height && newY > -1 && newY < width
                    && map[newX][newY] != BOMB && map[newX][newY] < FLAG) {
                // 递归展开非雷和未标记区域
                clickCell(newX, newY);
            }
        }
    } else if (map[x][y] == BOMB) {
        // 点击到地雷, 游戏状态设置为失败
        STATE = LOSS;
    } else if (map[x][y] < BOUND) {
        // 点击到数字格, 数值加100用于区分是否已被点开
        map[x][y] += 100;
    }
}

  至此,我们基本完成了扫雷的核心内容,剩余的功能如计时,成绩排行,难度设置,胜负判定等只能说是使这个玩法更像是完整的游戏。因为本文是概述性质的,所以这些功能和界面统一放在后续文章里结合着讲。

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

  隔了这么久再次写博客,都不知道从何写起讲些什么了。如果文章或者演示里有哪些不清楚的地方,还请留意后续更新。另外GitHub的代码我应该还会更新,如果有不足之处欢迎在issue里指出。这次的项目拖拖拉拉大概进行了一个月吧,实际用来写代码的时间也不能算多,拖延症大抵是没救了(悲)希望下次更新不是明年吧😢文章来源地址https://www.toymoban.com/news/detail-515732.html

到了这里,关于基于JavaFX的扫雷游戏实现(一)——整体概述的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 小游戏扫雷实现教学(详解)

    目录  【前言】 一、模块化程序设计(多文件编程)介绍 1.概述 2.传统编程的方式 3.模块化程序设计的方法 二、扫雷代码设计思路 三、扫雷代码设计 1.创建菜单函数  2.实现9x9扫雷 3.初始化棋盘  4.打印棋盘  5.随机布置雷的位置 6.排查雷的信息  7.回到步骤1,重新选择进入

    2024年02月12日
    浏览(36)
  • 扫雷游戏的实现(C语言)

    对于扫雷游戏,大家应该都很熟悉吧,下面让我们来看看它是如何实现的。 目录 一、游戏规则及设计思想 二、各功能的代码实现 1.创建菜单 2、主函数的实现  3、创建棋盘并初始化 4、打印棋盘  5、布置雷的位置 (埋雷) 6、排查雷   三、代码汇总 1、game.h文件 2、game.c文

    2024年02月03日
    浏览(38)
  • “纯C”实现——扫雷游戏(递归实现展开功能)

    📺游戏动画演示 🚀扫雷实现思路 🚀棋盘实现 🚀布置雷实现 🚀玩家扫雷实现 🚀带展开功能的扫雷 🚀小结语 🚗text.c文件 🚗game.h文件 🚗game.c文件 游戏实现完成的模样: 实现扫雷游戏的前提是要知道: 扫雷游戏的玩法 (会玩的跳过这步) 扫雷游戏也就是排雷,让玩家点

    2024年02月03日
    浏览(47)
  • 探秘C语言扫雷游戏实现技巧

    本篇博客会讲解,如何使用C语言实现扫雷小游戏。 使用2个二维数组mine和show,分别来存储雷的位置信息和排查出来的雷的信息,前者隐藏,后者展示给玩家。假设盘面大小是9×9,这2个二维数组都要开大一圈,也就是大小是11×11,这是为了更加方便的数边角上雷的个数,防止

    2024年02月10日
    浏览(47)
  • 【C语言】扫雷游戏完整代码实现

    目录 1.game.h 2.game.c 3.progress.c 4.运行结果

    2024年02月21日
    浏览(45)
  • C语言实现简单的扫雷游戏

    目录 1 - test.c 2 - game.c 3 - game.h 代码里的注释感觉已经很清楚啦,就不多讲解啦 感谢各位大佬的支持!!!

    2024年01月22日
    浏览(49)
  • C语言之扫雷游戏实现篇

    目录 主函数test.c 菜单函数 选择循环 扫雷游戏实现分析 整体思路  问题1 问题2  问题3 问题4  游戏函数(函数调用)  创建游戏盘数组mine 创建游戏盘数组show 初始化游戏盘数组InitBoard 展示游戏盘DisplayBoard 游戏盘置雷SetMine 游戏盘排雷FindMine test.c总代码 头文件函数声明game

    2024年02月11日
    浏览(38)
  • 详解Java实现2048小游戏(JavaFX,完整源码+注释说明)

    刚刚完成的一个小游戏,写文章记录一下,如果有任何错误或者可以改进的代码请提出 另一方面也是方便自己几个月或几年后忘记时,来这里翻一翻回顾思路 目录 基本界面: 类的组织: _CardPane: _CardMatrixPane: _CardColor: _GameMenuBar: _2048Demo: 基本思路: 卡片: 卡片矩阵:

    2024年02月03日
    浏览(55)
  • 带你一步步实现低代码开发平台——概述、实现模式、整体框架

    低代码开发平台是一种开发工具,它允许用户使用图形界面和少量编码来创建应用程序。这种平台的目的是加快应用程序开发速度,减少开发成本和技能门槛。目前,市场上有许多低代码开发平台可供选择,包括Microsoft Power Apps、OutSystems、Mendix等等。这些平台提供了各种各样

    2024年02月09日
    浏览(59)
  • C语言:轻松实现扫雷小游戏

    目录 一、前言 二、扫雷步骤 1.创建项目 2.设计整体框架 1.定义数组长度和雷的个数 2.game函数功能 三、头文件game.h代码实现  四、测试文件test.c代码实现 五、game函数功能实现 1.初始化棋盘 2.打印棋盘  3.布置雷 4. 获得雷的个数  5.排查雷 6.game函数实现代码 六、总结 大家好

    2024年02月03日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包