Java扫雷游戏的设计与实现(论文+源码)

分类号_______________ 密级________________

UDC _______________ 学号_ _

毕业设计(论文)

论文题目Java扫雷游戏的设计与实现
Thesis TopicDesign and Implementation of Mine Game

毕业设计(论文)任务书

毕业设计(论文)题目:Java扫雷游戏的设计与实现毕业设计(论文)要求及原始数据(资料):1.学习和掌握计算机编程相关的基本知识;2.了解和运用JAVA面向对象的特征;3.熟悉JAVA的可移植性,跨平台性等多种特性;4.设计并实现扫雷游戏的基本功能;5.深入分析扫雷游戏的算法实现;6.训练检索文献资料和利用文献资料的能力;7.训练撰写技术文档与学位论文的能力。
毕业设计(论文)主要内容:1.进行可行性分析以及需求分析;2.论述毕业设计所需开发环境以及开发工具;3.实现程序的概要设计; 4.在概要设计的基础上进行详细设计,编写实现各个类;5. 展示Java编程的扫雷游戏源代码;6.进行系统测试。学生应交出的设计文件(论文):1.内容完整、层次清晰、叙述流畅、排版规范的毕业设计论文;2.包括毕业设计论文、源程序等内容在内的毕业设计电子文档及其它相关材料。
主要参考文献(资料):秦亮. 利用java实现扫雷游戏的算法解析. 软件开发与设计(2011年06期)张洪斌. Java2高级程序设计[M]. 中科多媒体出版社,2010孙鑫. Java Web 开发详解[M]. 电子工业出版社, 2006.189-274萨师煊,王珊. 数据库系统概论(第三版)[M]. 北京:高等教育出版社,2009.王家华. 软件工程[M]. 东北大学出版社,2012尹伟民. Java程序设计之网络编程. 北京:中国电力出版社, 2009赵生慧. Java面向对象程序设计. 北京:高等教育出版社, 2010王梅. Java并发程序—设计原则与模式. 北京:中国电力出版社, 2008Jon Titus. ECN Technical Editor: “The Eclipse of stand[J]. Journal of Zhongkai Agrotechnical College” , Vol.19,No.2, 2006.W.Clay,Richardson,Donald,”Avondolio. The Java high class weaves a distance: JDK 5”, Scientific & Technology Book Review,No.3,2006Alice Woudhuysen.China internet:The long march toward e-commerce[J].theeconomist intelligence unit. 2007

Java扫雷游戏的设计与实现

摘 要

扫雷这款游戏有着很长的历史,从扫雷被开发出来到现在进行了无数次的优化,这款游戏变得越来越让人爱不释手了,简单的玩法在加上一个好看的游戏界面,每一处的细节都体现了扫雷的魅力。所以本次的毕业设计我将开发一款扫雷游戏。

本次毕业设计是以JAVA语言作为开发环境,使用Eclipse设计并开发一个类似Windows扫雷的游戏,实现其基本功能。论文首先介绍了课题背景,其次进行了需求分析及可行性分析;然后设计游戏流程,介绍雷区中的雷怎么安放和产生雷的随机算法;最后介绍游戏中可能会触发的各种时间,比如鼠标点击时间和清理掉没有雷的格子,其中鼠标事件包括点击到或没有点击到雷触发的事件和点击到重新开始以及菜单触发的事件,清理掉没有雷的格子就需要使用“递归”的方法来使该功能可以简单的实现。

关键词扫雷;Eclipse;事件;递归

Design and Implementation of Mine Game

Abstract

Mine the game has a long history, from the mine was developed to now numerous optimization, the game is becoming more and more let a person fondle admiringly, simple style with a nice game interface, every detail reflects the charm of mine. So I'm going to develop a minesweeper game.

The graduate design study was designed with the JAVA language as a development environment, using Eclipse to design and develop a game like Windows minesweeper to implement its basic functions. The paper firstly introduces the background of the project and analyzes the requirements and feasibility analysis. Then the game process was designed to introduce the random algorithm of how the thunder in the minefield was placed and produced. Finally introduced the game may trigger a variety of time, such as a mouse click on the clear time and there is no ray of diamonds, mouse events including click to or no click to ray trigger events and click to restart and menu trigger events, clearing out squares no ray will need to use the "recursive" methods to make the function can be simple to implement.

Key words: Mine game; Visual Basic 6.0; Affairs; Recursion

目 录

摘 要i

Abstractii

1绪论1

1.1课题背景及意义1

1.2开发工具的选用及介绍1

1.3选题目的和意义2

1.4本文主要研究的内容2

2需求分析3

2.1可行性分析3

2.2扫雷游戏功能描述3

2.3扫雷游戏用例图4

2.4扫雷游戏功能需求4

2.5扫雷游戏界面需求5

2.6扫雷游戏功能模块6

3游戏的概要分析与设计7

3.1设计构想7

3.2流程规划8

3.3界面规划9

3.4算法思想9

4游戏的详细设计11

4.1游戏初始化11

4.2雷区的布置12

4.3游戏中主要模块的介绍与使用13

4.3.1鼠标事件13

4.3.2地雷及雷区表面探测情况14

4.3.3清除未靠近地雷的格子15

4.3.4游戏难度的选择16

4.3.5菜单栏的功能16

4.4游戏的判断16

4.4.1游戏成功完成16

4.4.2游戏失败16

4.5类设计17

4.5.1MineGame类17

4.5.2Block类18

4.5.3BlockView类19

4.5.4Record类20

4.5.5ShowRecord类21

4.5.6MineArea类22

4.5.7LayMines类23

5游戏实现25

5.1游戏难度自定义25

5.2扫雷28

5.2.1玩家通过右键进行扫雷,并显示小红旗28

5.2.2玩家因触碰到雷而导致游戏结束29

5.2.3玩家扫雷成功29

5.2.4玩家游戏数据显示30

5.3程序打包发布过程32

6游戏测试结果33

6.1游戏难度自定义测试33

6.2扫雷测试35

6.3玩家游戏数据显示测试37

6.4游戏数据显示区测试39

6.5递归算法测试43

结 论44

参考文献45

致 谢47

外文原文48

中文翻译55

  1. 绪论
    1. 课题背景及意义
      在1964年 有一个叫“方 块”的游戏,这是扫雷最原始的版本。后来,这个游戏被改成了另一种游戏,叫做“Rlogic”。在这个游戏中,玩家扮演了一名军队的军人,接受了一项艰难的任务:为指挥中心探路。当然游戏不会这么简单,这条路上充满了地雷,玩家需要小心翼翼的探索出一条没有地雷的安全的道路,如果被雷炸死游戏就会结束。一年后,汤姆·安德森(Tom Anderson)在之前游戏的基础上又编写了一款新的游戏,这就是扫雷游戏。1979年, 两名工程师在计算机的操作系统上搭载了这款小游戏,从此以后扫雷游戏开始风靡全球。如今,越来越多的人喜爱上了这款简单易上手的小游戏,这个小游戏可以在任何时间在任何平台上进行游戏,让人可以在游戏中享受到乐趣并且还能在一定程度上锻炼玩家的思维能力。
      扫雷这款游戏有着很长的历史,从扫雷被开发出来到现在进行了无数次的优化,这款游戏变得越来越让人爱不释手了,简单的玩法在加上一个好看的游戏界面,每一处的细节都体现了扫雷的魅力。所以本次的毕业设计我将开发一款扫雷游戏。
      扫雷游戏深受大家的喜爱,本次的毕业设计也是迎合大家的喜欢,开发一款扫雷小游戏,同时也可以提高自己编程水平。
    2. 开发工具的选用及介绍
      Eclipse是一款非常好用的编译软件,它也经历了很长时间的发展,并且是由许多公司联合开发的免费软件。2011年5月,公布稳 定版4.2;2012年4月公布代号为Ganymeode的4.3版;2013年5月公布代号为Galieleo的4.4版;2013年7月公布代号为Helomios的4.5版;2013年11月公布代号为Indigon的4.6版;2014年3月公布代号为Jiuno的5.1版;2014年7月公布代号为Keplere的5.2版;2015年3月公布代号为Lunam的5.3版;2015年8月公布代号为Marse的5.4版。
      Eclipse是目前主流的编写代码的软件,可以支持预编译,在写代码的同时就能知道是否书写错误,而不用等到运行的时候才发现。原本这个软件只能编写Java语言,但是经过多年的开发设计出了许多的插件,将这些插件安装进去以后就可以编写其他语言了,这也就是他被大部分人所喜爱的原因。正是由于插件的使用,Eclipse变得很灵活,可以通过安装插件来实现原本没有的功能,让软件更加的便利。
      Eclipse一开始是由两个团队联合设计开发出来的,由于该软件的强大功能使得许多公司纷纷加盟该项目联合开发。而且这个项目是开源的,任何人都能对其进行开发修改,这让Eclipse的名气愈来愈大,后来连甲骨文这种大公司都加入这个项目之中。这种免费的开发软件也很受许多公司的喜爱,通过安装插件可以使用各种语言。
      Java语言是一种深受大家喜爱的语言,因为它是面向对象的,所以在理解了这种语言之后就会在代码的设计上有更深刻的理解。
      如果没有JDK,你编写的Java代码是无法运行的,这是因为我们需要使用他自带的类库来帮助我们设计,由于不同的软件都可以编写代码,语言规范也就显得尤为重要,所以在开始编写代码之前一定要安装好JDK。
      现在在网上是有着许许多多的JDK版本,其中以Sun公司发布的最好,其他公司也有开发并发布出来。有的在性能上甚至超过了Sun公司的JDK,有的则在在执行的效率上高于Sun公司的JDK。但是我们还是选择使用它的原因是因为它是最稳定的JDK,经过多年的开发和完善,是其他JDK所不能相比的。所以我们要熟练的运用它。
    3. 选题目的和意义
      经历了大学四年的学习之后,我已经学习了一些关于设计和编程的理论知识,本次的毕业设计运用学过的知识编写一个扫雷小游戏,是对我平时学习成果的一次检验。在设计当中让我的知识在实践中融会贯通并得到提升,并且能发现许多平常发现不了的问题,期望能通过本次的毕业设计让我的能力再一次提高。这次毕设一定会让我收获许多,在今后的工作生涯中能轻松的胜任任何任务。
    4. 本文主要研究的内容
      扫雷游戏风靡全球,受到很多人的喜爱。我研究了这个扫雷游戏之后,发现有两个关键点:
      第一点是鼠标事件的处理,包括鼠标点击菜单项中按钮的事件,点击雷区中任意一格的事件,点击界面上按钮的事件等。
      第二点是初始化雷区,包括随机生成雷的位置,界面的初始化,计算周围雷数等。
      具体要求如下:
      (1)扫雷游戏分为三种难度,分别是初、中、高级,点击游戏左上角的“游戏”菜单后会出现“初级”、“中级”、“高级”三种选项。除此之外玩家还可以自定义难度。
      (2)在玩家选择难度之后游戏将会改变界面大小,并改变雷的总数。当玩家点开第一个格子时雷区开始布雷同时计时开始。
      (3)若玩家想放弃本局游戏并重新开始的话,可以通过点击扫雷信息统计区中的按钮,来结束当前的游戏并开始新的游戏。
      (4)当玩家认为某个格没有雷,可以直接用鼠标单击这个格或者把鼠标放在该格子上按下空格键。如果玩家的判断是正确的,则会显示周围8个格子中地雷的总数,如果玩家判断错误,该格子下藏着雷的话,游戏就好结束,系统自动弹框提示玩家失败。
      (5)若玩家认为某个格下有雷,在该格子上鼠标右键就可以标记一个玩家认为该格是雷的图标,也就是在格子上标记一个小红旗的图案。玩家每标记一个红旗(不管玩家的判断正确与否),界面上的计数区都会把雷数减一。
      (6)当所有的雷都标记正确后,游戏会自动弹框显示“您真厉害,请输入您的名字,记录上榜!”。
      (7)若玩家在游戏过程中单击了有雷的格子,游戏失败并且记时停止,游戏会将剩余的雷全部显示出来,自动弹框显示“你输了,请继续努力!”,之后玩家可以开始一局新的游戏。
      (8)如果玩家不清楚游戏玩法,可以点击菜单栏上的帮助来获取游戏玩法及游戏窍门。
      (9)对于自定义难度,系统会有一个阈值,行数在9到16之间,列数在9到30之间,地雷数在10到99之间。如果玩家设置的值超过该阈值,系统会自动将超过的数值变成阈值。

  1. 需求分析
    1. 可行性分析
      扫雷游戏是一款玩法简单的小游戏,无论是孩子还是老人,都可以在休息的时候玩上一局从而轻松而有效地舒缓压力。而且真正的扫雷高手还可以通过竞速(在最短的时间内完成扫雷)来感受乐趣。
      (1)投资可行性:扫雷游戏所占内存少,可以搭载在任意平台,有投资的价值。
      (2)财务可行性:从受益者的角度来看,不需太多的经费就能开发这款扫雷小游戏。
      (3)组织可行性:制度可行的项目规划,做好人员的配给,保持组员间的良好沟通,定期开会讨论,确保项目能够准时交付。
      (4) 经济可行性:可以给开发的企业创造效益,给社会增加工作岗位,并且提高人们的生活质量。
      (5) 法律可行性:任何的产品都需要确保它没有触犯法律才能开始设计。但是扫雷游戏并不会触犯任何法律,而且不会和企业之间发生冲突。游戏的开发不会侵犯任何人的利益,也不会违法。
      (6) 技术可行性:扫雷游戏的功能简单,只需稍有经验的开发人员就能轻易开发出来,所以技术方面不是太大的问题,主要需要明白扫雷的玩法才能更好的设计与实现该游戏。
    2. 扫雷游戏功能描述
      游戏界面可以分为三个模块:菜单栏,游戏数据显示区域以及雷区。菜单栏包括游戏难度的选择和排行榜的查询以及游戏帮助。游戏数据显示区域包括地雷计数区、计时区和重新开始按钮。雷区就是玩家的游戏区域,当玩家在游戏过程中用鼠标点击某一模块,系统会自动作出相应的响应,这就是游戏中的鼠标事件。
      当玩家点开第一个格子时雷区开始布雷同时计时开始。然后会在该格子上显示周围8个格子的雷数(如果没有雷则自动点开周围的格子),玩家就需要通过这些数字来判断雷的位置,将是雷的格子标记为小红旗。
      若玩家认为已标记的格子时错误的,可以再次右击该格子来取消标记。当某一格子被标记时,对于该格子的单击操作是无效的(防止玩家误点击导致游戏失败)。
      如果玩家将某一格周围8个格子中的雷标记了出来,双击该格子会自动将周围的格子点击一遍,这样可以简化玩家的操作。
      当玩家将全部的地雷标记出来并且其他的格子点开时,游戏结束。但是如果玩家不小心点到了地雷就会游戏失败,系统自动显示出所有的地雷。如果玩家标识的地雷数量超过了该难度下规定的雷数,计数区会以负数显示超出的雷数并且游戏不会结束。
    3. 扫雷游戏用例图


图2-1 扫雷游戏用例图
从上述扫雷游戏功能描述以及用例图可以分析出,整个扫雷游戏中,玩家所能进行的操作有:选择难度、开始游戏、重新开始游戏和记录查询等。

    1. 扫雷游戏功能需求
      本次扫雷设计需要实现的主要功能有:
      1. 玩家可以选择合适的游戏难度
        点击游戏界面左上角菜单栏中的“游戏”菜单,会出现出现“初级”、“中级”、“高级”、“自定义”四种选项。在玩家选择难度之后游戏将会改变界面尺寸,并改变雷的总数。前三种雷数和尺寸都是固定的,无需玩家设置,而自定义则是玩家自己定义的。
      2. 进行扫雷游戏
        游戏界面包括菜单栏,游戏数据显示区域以及雷区,当玩家点击雷区中的任意一格的时候游戏开始,雷区开始随机布雷,玩家需要在最短的时间内找出雷区中所有的地雷,并加以标识。
        扫雷的基本操作包括鼠标左键单击和右键单击两种。其中左键用来点开玩家认为不是雷的格子,右键标记玩家认为是雷的格子。
        左键单击:玩家在判断出该格子下没有雷时单击该格子,可以将该格子点开。如果该格子周围有雷就会显示周围雷的数目(由于周围最多只有8个格子,所以最多只能显示8);如果格子上什么也不显示(也就是为空),系统就会自动调用递归的方法打开附近的格子。如果点到了有地雷的格子,那么游戏结束,系统显示所有雷的位置。
        右键单击:玩家可以通过使用鼠标右键单击来标记自己认为是雷的格子,通过标记可以有效的提高扫雷游戏的效率。再次右击该格子可以取消标记,且格子被标记的时候鼠标单击无效(防止玩家误操作导致游戏失败)。
      3. 游戏计时
        当点击雷区任意一个格子的时候雷区开始布雷同时计时开始,计时标准是一秒增加1。如果游戏失败时,则计时停止。如果玩家开始了新游戏,计时也会重新开始。
      4. 标记地雷
        当玩家认为格子下有雷时可以右击格子来标记该格子,被标记的格子显示小红旗。再次右击可以取消标记,且格子被标记的时候鼠标单击无效(防止玩家误操作导致游戏失败)。如果玩家标识的地雷的数量超过了该难度下规定的雷数,计数区会以负数来显示超过的雷数并且游戏不会结束。
      5. 统计功能
        当玩家游戏胜利后会弹框提示并可以输入玩家的名字,确认后本次游戏的记录会被保存。玩家可以点击菜单栏上的“游戏”菜单,再点击其中的“扫雷榜”即可查看游戏的最佳记录。
      6. 退出
        点击关闭按钮可以结束游戏。

    1. 扫雷游戏界面需求
      1. 游戏菜单
        玩家有5项可以选择,玩家在点击初级时游戏界面的尺寸会变为初级的大小,雷区重新初始化,变为一共9*9的格子,其中有10颗是地雷;中级一共有16*16个格子,其中有40颗是地雷;高级一共有16*30个格子,其中有99颗是雷。除此之外,玩家还可以自定义难度,在弹框中填写雷数与尺寸。
        玩家通过点击鼠标右键来标记自己认为是雷的格子,通过标记可以有效的提高扫雷游戏的效率,再次点击右键可以取消标记。
        扫雷榜,记录玩家不同难度的最短时间。
      2. 游戏区域
        游戏区域由扫雷信息统计区和雷区组成,其中扫雷信息统计区又分为计数区、计时区、重新开始按钮。
        雷区的雷数,每个难度对应的雷数都不同,初级、中级、高级分别对应10、40、99个雷。
        计数区初始显示的雷数由难度而定,每次标记地雷雷数均减1,如果玩家标记的地雷的数量超过了该难度下规定的雷数,计数区会以负数显示超过的雷数。
        当玩家点开第一个格子雷区开始布雷同时计时开始,一秒加1,直到游戏胜利或者游戏失败的时候停止计时。

    1. 扫雷游戏功能模块
      游戏在功能上分为6个模块:
      1. 游戏界面
      2. 布雷
      3. 鼠标事件
      4. 地雷判断
      5. 游戏胜利(结束)
      6. 游戏失败(结束)

  1. 游戏的概要分析与设计
    1. 设计构想
      如今世界上很多人都在使用Windows操作系统,所以人们也对系统自带的小游戏了如指掌。扫雷游戏的玩法简单,只要玩家进行一定的判断就可以轻松的游戏,所以玩扫雷的时候可以很轻松的玩。除了游戏本身带给人们的乐趣以外,游戏的玩法也在锻炼玩家的思维,如今大部分人都是依赖脑力劳动,这就可以通过在闲暇的时候玩玩扫雷来锻炼一下自己。所以就可以理解,为什么在各种电子产品上都搭载这个小游戏了。
      虽然游戏比较简单,但是还是需要熟悉一下规则。
      玩家需要在最短的时间内找出雷区中所有的地雷,并加以标识,其他没有雷的格子全部点开后游戏胜利。但是如果点到了地雷则游戏失败。
      游戏的操作很简单,当玩家用鼠标左键点击自己认为不是地雷的格子会点开该格子,用鼠标右键点击格子会标记该格子,再次右击可以取消标记。玩家可以通过雷区中被点开的格子上显示的数字来判断该格子周围8个格子所隐藏的地雷,例如:点开的格子显示数字“2”,则表示该格子周边的8个格子里隐藏着2颗地雷。
      如果点开的格子下没有雷且周围8个格子里也没有雷,则系统会自动点开那8个格子,然后递归判断这些格子周围有没有雷。
      本次的扫雷游戏设计,需要编写7个Java类:MineaGamae.java类、MineAra.java类、Block.jaav类、BlockVaiw.java类、LayMinas.java类、Record.java类和ShowRecrd.java类。
      1. MineGame.java
        MineGame类是游戏的入口,用来初始化游戏资源,比如界面尺寸和雷数等。同时也负责难度的转换。
        (2) MineArea.java
        MineArea类是布置雷区的雷,除了初始化雷区以外还可以响应玩家的鼠标操作。
        (3) Block.java
        Block类是一个POJO类,主要记录了雷区一个个格子的属性,比如名字,周围雷的数目等等。
        (4) BlockView.java
        BlockView类用来显示块的属性,并且使用卡片布局来使格子分为了上下两层,当玩家点击格子后,会使下面的属性浮现出来。
        (5) LayMines.java
        LayMines类是计算不是雷的格周围雷个数的类,以及设置点选之后的图片样式。创建的对象lay是MineArea中最重要的成员。
        (6) Record.java
        Record类是通过IO流将游戏记录储存在本地的文件中,主要实现通关后弹窗提示通关的窗口,以及记录成绩。
        (7) ShowRecord.java
        ShowRecord类是显示扫雷记录的类。

    1. 流程规划
      有三个部分,分别是游戏选择难度后、玩家第一次点击格子和为不是地雷的格子自动点开。
      游戏选择难度后,获取该难度设定的雷数与界面尺寸显示界面,但是还没有布雷。当玩家第一次点开格子的时候系统随机布雷并启动定时器,为什么要在点过一次格子之后才布雷呢?这就是要让玩家第一次不会点到地雷,要不然玩家该多郁闷,这样才能提高玩家的游戏欲。之后是最重要的一步,就是如何将格子周围没有雷的格子自动点开,我们可以使用递归的方法来巧妙的判断并点开周围的格子。
      整体规划如图3-2所示:


图3-2 整体规划图

    1. 界面规划
      界面规划如图3-3所示:

       






图3-3 规划样图
说明如下:
①:游戏主界面(Interface)。
②:菜单(Menu)。
③:地雷数显示区(MineNumberArea)
④:重新开始(Restart)。
⑤:扫雷用时显示区(TimeArea)。
⑥:地雷区(MineArea)。

    1. 算法思想
      1. 随机布雷
        扫雷游戏要求在雷区随机布雷,雷数不能太多,这样就没法很好的判断周围是否有雷了;但也不能太少,这样会出现点一下就会点开一大片的空白区域。使用java自带的Math.random()方法产生随机数,经过计算后得到将随机数转换成一个整数,这个整数就是雷的位置的角标。游戏的目标是在将所有的地雷标记出来,并将其它不是雷的格子点开。
      2. 计算方格周围雷数
        当没有雷的格子被点击后,会显示该格子周围8个格子里所有的雷数,玩家通过这个数字就可以判断出雷的位置,所以周围雷数的计算也很重要。

  1. 游戏的详细设计
    1. 游戏初始化
      玩家点开游戏,此时系统自动加载界面,分别是雷区、菜单区、扫雷数据显示区,如图4-1所示:

扫雷数据显示区
地雷区
图4-1 游戏初始界面
游戏界面相对计算机自带的扫雷游戏比较简洁,主要由游戏数据显示区域以及雷区构成。
我们使用ImageIcon对象来存放地雷的图标,可以图标大小最好要小一些,否则会在格子上显示不全。
对地雷区的初始化是很重要的,我们使用一个循环即可实现初始化。玩家点开游戏之后可以点击菜单栏选择扫雷游戏的难度,不同的难度就代表了不同的地雷总数和尺寸,自定义的难度也会设置雷数和尺寸,设置的这些数据全部存放在几个变量之中,当雷区要初始化的时候程序会调用这么变量属性,然后通过一个循环来完成初始化。主要代码如下:


而之前所说的循环初始化其实就是循环的加载组件,雷区就是由一个个组件构成的,通过循环将所有的格子都布置到雷区,然后再随机产生地雷并布置到雷区上。
组件位置的摆放是按照其在数组中的位置来摆放的,所有的格子组件都存放在一个二维数组之中,组件在这个数组中的下标就是他在布局中的位置。
菜单栏是由菜单组件构成的,也就是JMenu组件,这个是菜单组件,用来定义未点击时的菜单样式,这个组件有可以设置JMenauItem组件,这是菜单项的意思,是当玩家点击了菜单后展示出来的子菜单栏。
扫雷游戏的计时区以及计数区又是使用另一种组件完成的,叫做JTextField
,这个组件是文本显示的组件,在设置了不可编辑的属性后,该组件在显示的外观上会有变化,边框会变灰一些,意味着该组件不能够点击或者输入。然后使用一个方法随着时间动态的在时间区以一秒为单位增加,计数区则会随着用户标记雷而减一,可以减到负数。
在设置界面的时候还需要进行一些设置,比如设置属性来让玩家在关闭游戏窗口时,系统会自动释放资源,并关闭窗口。

    1. 雷区的布置
      当玩家第一次点开格子的时候系统随机布雷并启动定时器,但是为什么要在点过一次格子之后才布雷呢?原来这就是要让玩家第一次不会点到地雷,要不然玩家该多郁闷,这样才能提高玩家的游戏欲。具体实现如下所示:
      判断地雷区是否处于可动作状态:


判断为左键且该格子为未探测状态:


布置地雷:


判断地雷应该被布置的位置:

    1. 游戏中主要模块的介绍与使用
      1. 鼠标事件
        我们通过点击鼠标左键或者鼠标右键来完成游戏,使用系统自带的MouseDown和MouseUp事件来响应玩家的操作。
        函数原型如下:
        组件名称:

组件名称:

这两个原型中的参数,Button参数值分别表示玩家用鼠标的左键或者右键进行点击的。其意义如下:
1:左键 2:右键
如果同时按两个按键,那么系统就会传回3,因为点左键是1,点右键是2,一起点就是两个值相加了。
在本次的扫雷设计中,我们会用鼠标的MouseUp事件来响应玩家的操作,其中鼠标左键用来点开玩家认为没有雷的格子,鼠标右键用来标记玩家认为下面藏有雷的格子。使用MouseaUp事件而不用MouseaDown事件的原因是,前者是在当鼠标按键抬起是触发,而后者是当按键按下就触发。假如玩家在点击某一个格子的时候突然发现好像点错了,此时玩家只要继续按下鼠标右键,再抬起的时候就会发现格子被标记成地雷了,现在的游戏一般都有这个功能,这可以有效的提升玩家的游戏体验。
在人机交互的界面上,鼠标的操作是很重要的,但是程序的设计不合理会使鼠标无法发挥应有的功能。在鼠标点击事件中使用MouseUp来响应事件在上面也进行了解释,这样就可以很巧妙的让玩家体会到游戏的贴心之处,可以有效的提升玩家的游戏体验。
当鼠标左键点击格子时:

当鼠标右击标识有雷的格子时:

      1. 地雷及雷区表面探测情况
        在雷区的设计上,我使用循环加载组件的方法让雷区布满格子,如果玩家点击了格子就会让该格子下面隐藏的组件显示出来如图4-2和图4-3所示:


图4-2地雷随机生成


图4-3 地雷被标记
玩家通过单击格子来点开格子,右击格子标记地雷。由于使用了两个数组记录地雷的位置以及被点开的信息,所以可以通过两层嵌套循环来判断该格子周围雷的数目,而且系统会点开该格子周围8个格子中没有雷的格子,这又需要用到递归调用来一直进行这个点开格子并验证的过程,直到不符合条件为止。利用这种嵌套循环以及递归调用,都会使一个耗时耗力的问题变得容易解决起来,使得代码结构也变得清晰明了,并且会提高系统的计算速度。

      1. 清除未靠近地雷的格子
        开始之前需要介绍并学习一下“递归”,递归的狭义解释就是一个方法调用自己本身,通过一个判断语句决定是否结束调用。在代码的设计上也是一种非常重要的代码结构,通过这种方法编写代码,会使得代码结构看起来相当简单明了。但是需要注意的是,如果操作不当会是系统无限循环调用,使得游戏崩溃,所以在编写代码时必须使用判断的语句来控制循环调用的结束,以停止该方法的递归调用。
        当玩家在进行游戏时点击了任意一个格子,系统会检测周围8个格子下是否藏雷,如果雷都被标记或者没有雷就会被点开并循环调用继续判断被点开格子周围的雷数。
        设计的时候需要考虑的判断条件:玩家点击了某一个格子后判断该格子下是否藏雷;如果没有雷则判断周围8个格的雷数并显示;若雷都被标记出来或者本来就没有雷则点开周围的格子递归判断被点开格子周围的雷。
        由于递归是循环调用方法本身,所以需要一个判断语句来结束调用,当被点开的格子周围有雷时在格子上显示雷数并结束递归调用。
        递归方法虽然很好但也有一些的缺点,除了结束条件的选取很重要外,如果在扫雷设计中将雷区的尺寸设置过大,而地雷数却很少的话会使的循环调用变慢。所以在游戏的玩法设计上也要小心谨慎,不可以胡乱定义,以免造成系统负担,使得游戏无法顺利进行下去。
        代码部分如下所示:

      1. 游戏难度的选择
        我设计的扫雷有在难度的选择上有三种难度,分别是初、中、高级,这三种难度定义的雷数和雷区的尺寸是固定的,假如玩家想要自己定义扫雷的难度可以自己设置雷数和尺寸,只要打开“自定义”配置弹窗,在之后的弹框里按提示输入雷数和尺寸,行数最大为24,列数最大为30,如果玩家设置的雷数超过了范围(比如雷数大于格子的总数或小于零),系统会默认雷数为行数减一与列数减一的乘积。
      2. 菜单栏的功能
        初级:设置格子总数为9*9,地雷总数为10颗雷,尺寸为300*380然后重新开始游戏。
        中级:设置格子总数为16*16,地雷总数为40颗雷,尺寸为480*580然后重新开始游戏。
        高级:设置格子总数为16*30,地雷总数为99颗雷,尺寸为800*500然后重新开始游戏。
        自定义:设置格子总数为最大24*30,最多雷数为24*30的难度,然后重新开始游戏。
        扫雷榜: 打开并显示时间最短的扫雷记录,可以进行更新和重新记分。

    1. 游戏的判断
      1. 游戏成功完成
        游戏要求玩家在最短的时间内完成游戏,当玩家将所有的雷标记出来并且其它格子都点开以后游戏胜利,系统会弹出提示框告诉玩家“您真厉害,请输入您的名字,记录上榜!”,系统会记录玩家最短完成游戏的时间。
      2. 游戏失败
        如果玩家不幸点击到了有雷的格子,该格子的地雷会引爆其他的地雷,游戏失败,系统会将剩下的地雷全部显示出来,并弹框提示“你输了,请继续努力!”,玩家可以选择弹框上的重新开始游戏选项来开始一局新的游戏。
        格子类的对象有一个标识该格子是否是雷的属性,通过该属性来判断玩家点到的是不是地雷。在游戏结束时也是通过该属性来判断哪些格子下有雷并显示出来。
        具体代码如下所示:

    1. 类设计
      1. MineGame类
        MineGame类主要负责开始游戏,菜单的设计,以及难度的转换。展示该类的UML图如图4-4所示。


图4-4 MineGame类的类图
(1)成员变量
bar 是窗体的菜单栏变量。
fileMenu1 是扫雷游戏的菜单,叫做“游戏”。
fileMenu2 是扫雷的另一个菜单,叫做“帮助”。
初级 是扫雷游戏的难度变量。
中级 是扫雷游戏的难度变量。
高级 是扫雷游戏的难度变量。
扫雷榜 是扫雷的统计信息变量。
mineArea 是MineArea雷的对象,是设计雷区的变量。
file 是文件变量,用来读取扫雷记录。
hashtable 是用来临时存放扫雷记录。
showHeroRecord 是用来显示扫雷记录的变量。
(2)成员方法
MiwneGaame()通过该方法设置了游戏整体的位置和布局,并实现菜单栏功能,初始化统计信息。
actionPerformed(ActionEvent e)是响应点击菜单项的方法,通过该方法可以实现选择不同难度调整游戏的雷数和尺寸。玩家点选不同的菜单项会让该方法执行不同的操作。
main()方法是游戏开始的方法,通过new了一个MineGame的对象来实现游戏的开始。

      1. Block类
        Block类是一个POJO类,主要记录了雷区一个个格子的属性,比如名字,周围雷的数目等等。展示该类的UML图如图4-5所示。


图4-5 Block类的类图

        1. 成员变量
          Name是记录格子名字的变量。
          isMine是标记格子下有无地雷的变量。
          isMark是标记格子被标记状态的变量。
          mineIcon记录了块的图标的变量。
          isOpen是标记格子的点开状态的变量。
          arounfdMinqeNmber是记录格子周围8个格子中雷的数量的变量。
        2. 成员方法。
          Block()无参构造方法,以防止创建错误参数的对象。
          Block(String name,int aroundMineNumber,ImageIcon mineIcon,boolean isMine,boolean isMark,boolean isOpen)全参构造方法,可以为变量赋值。
          setNamqe()方法可以设置块的名字。
          getName()获取块的名字。
          setAounMineNuber()方法可以设置块周围雷的数目。
          setMineIicon()方法可以设置块的图标。
          getMineIcon()获取块的图标。
          setIsMinae()方法可以设置块是否是雷。
          getAroundMineNumber()获取块周围雷的数目。
          getIsMine()获取块是否是雷。
          setIsMarek()方法可以设置块是否被标记。
          getIsMark()获取块是否被标记。
          setIsOpen(boolean p)设置块是否被挖开。
          getIsOpen()获取块是否被挖开。
          toString()以字符串的格式输出该对象。

      1. BlockView类
        BlockView类继承了JPanel类,主要布置每一个格子的布局,用来定义每个格子的属性。显示该类的UML图如图4-6所示。


图4-6 BlockView类的类图

        1. 成员变量
          blockeNameOrIcona 该变量用来显示格子的名字和图标属性。
          blockeCoverq 是一个标记变量。
          card 卡片式布局,显示第一次添加的组件。
        2. 成员方法
          BlockView()构造方法,初始化变量,设置块上属性显示的位置为居中,并添加组件。
          giveView()方法,给每一个格子提供视图,如果该格子下有雷,会调用方法赋予该格子“地雷”的图标,但被覆盖在下面。如果下面没有雷,会显示该格子周围地雷的数目,同样会被覆盖住。该方法被调用时显示雷或者数字。
          seeBlouckNamerOrIcon()方法是用来让块的属性显示出来。
          seeBlouckCovear()方法是让cover遮盖块的属性。
          getBlouckCovear()方法是得到用来遮盖的按钮。

      1. Record类
        Record类主要实现通关后弹窗提示通关的窗口,以及记录成绩。当玩家扫雷成功时,该对象提供了保存成绩到文件的界面。展示该类的UML图如图4-7所示。


图4-7 Record类的类图

        1. 成员变量
          time 用来给游戏计时。
          grade 记录难度的变量。
          key 表示一个判断的变量。
          message 记录成绩的数据变量。
          textName 一个文本显示组件。
          label 一个可输入文本输入框组件。
          确定,取消 两个按钮组件,一个代表确定,一个代表取消。
        2. 成员方法
          Record() 构造方法,初始化变量,设置窗口是否可以调整大小。
          setGrade() 方法可以设置成绩。
          writeRecord() 方法可以读写记录,如果没有记录会先创建一个新的文档来保存记录,下次直接修改覆盖记录。
          setTime() 方法是用来设置时间的。
          actionPerformed(ActionEvent e) 方法是响应鼠标的点击事件的方法,当玩家点击弹窗上的任意位置都会触发,但是只有点到正确的位置才会执行操作。比如点击确认,就可以把玩家的游戏记录存放到本地的文件中。

      1. ShowRecord类

ShowRecord类是显示扫雷记录的类。展示该类的UML图如图4-8所示。

图4-8 ShowRecord类的类图

  1. 成员变量
    file 记录成绩的文档。
    name 名字。
    hashtable 是记录成绩的变量。
    show 是一个显示成绩的按钮组件变量。
    Rescore 是一个初始分数的按钮组件变量。
    Label1[] 显示成绩的组件。
    Label2[] 显示成绩的组件。
    Label3[] 显示成绩的组件。
  2. 成员方法

ShowRecord() 方法是该类的构造方法,初始化成员变量。

readAndShow() 方法会把在文档中记录的玩家通关信息筛选出来(按游戏完成是花费的时间最短为条件筛选),并显示到弹出的对话框中。

actionPerformed(ActionEvent e)方法是响应玩家操作的方法,当玩家想要显示游戏记录的时候系统会响应玩家的要求并调用显示成绩的方法,在弹框中刷新成绩。

  1. MineArea类

MineArea类主要用于雷区的初始化以及鼠标事件的响应。展示该类的UML图如图4-9所示。

图4-9 MineArea类的类图

  1. 成员变量
    reStart 是用来显示一个重新开始的按钮。
    block 是一个二维数组,定义了所有雷区的格子。
    blockView 是一个二维数组,是给每一个格子提供显示视图的。
    lay 是负责定义每一个格子下有雷或者没有雷。
    row, colum 是负责记录雷区行列数的变量。
    mineCount 是记录地雷总数的变量。
    markMount 是记录玩家标记的地雷数的变量。
    mark 是ImageIcon类型的变量,用来存放地雷图标。
    time 是记录时间的变量。
    grade 是记录难度的变量,分别有“初级”、“中级”、“高级”三种难度。
    pCenter 是JPanel类型的变量,定义布局中部的控件
    pNorth 是JPanel类型的变量,分别位于布局的北部的控件。
    showTime 是显示扫雷时间的变量。
    showMareedCoint 是显示玩家标记后剩余的雷数的变量。
    record 是一个对话框变量,用来显示玩家的最好记录,当玩家点击菜单中的英雄榜时会显示该对话框。
    lose 是游戏失败时显示的对话框变量。
    spendTime 是一个标记变量。
    panel 一个轻量级容器变量,嵌套在对话框中。
    str 显示失败的提示语的一个文本框变量。
    reStart1 是一个按钮对象,位于失败对话框里,玩家点击后会触发与reStart一样的操作。
  2. 成员方法

MineArea()方法是该类的构造方法,初始化组件和一下对象,设置字体颜色、边框颜色等,并调用初始化雷区的方法来初始化雷区。

initMineArea()方法用来设置雷数和游戏界面所有格子的数量,初始化雷区。

initMine()方法是真正执行初始化的方法。

setRow(int row)方法用来设置行数。

setColum(int colum)方法用来设置列数。

setMineCount(int mineCount)方法用来设置地雷总数。

setGrade(int grade)方法用来设置难度。

actionPerformed(ActionEvent)方法是响应鼠标点击格子的事件的方法,当点击的格子下面没有雷,该方法会显示该格子周围雷的数目,有雷则显示雷的图标。

show()方法是显示格子内容,并且使用递归调用来显示周围格子的内容,直到这个格子周围有雷。

mousePressed(MouseEvent)方法响应玩家对格子进行的鼠标左键单击事件和鼠标右键点击事件。

inquireWin()方法是在玩家在游戏过程中满足获胜条件是触发的方法,同时会调用其他方法弹框提示玩家游戏结束。

  1. LayMines类

LayMines是计算不是雷的格周围雷个数的类,以及设置点选之后的图片样式。标明该类的UML图如图4-10所示。

图4-10 LayMines类的类图

  1. 成员变量
    mineIcon 图片资源变量。
  2. 成员方法

LayMines() 构造方法,定义图片资源。

layMinesForBlock(Block block[][], int mineCount) 方法会判断是否是第一次点击格子,如果玩家是第一次点击格子,开始随机产生雷,但是雷不会布置到第一次点的格子上。如果不是第一次点击且该格子没有雷时会计算周围8个格子中有雷的数目并显示出来。

  1. 游戏实现
    1. 游戏难度自定义


图5-1 选择难度以及游戏自定义


图5-2 初级难度


图5-3 中级难度


图5-4 高级难度


图5-5 自定义难度
如图5-1所示,玩家可以自己选择游戏难度,其中有初级,中级,高级以及自定义四种难度,所对应的游戏界面的尺寸也不相同,以及地雷的总数也不相同,雷数越多则难度越大,同时玩家可以自定义游戏难度,通过输入行列数以及地雷数设置游戏界面的大小以及难度。
如图5-2、图5-3、图5-4、图5-5所示,分别展示了初级、中级、高级以及自定义难度的游戏界面尺寸和雷数。

    1. 扫雷
      1. 玩家通过右键进行扫雷,并显示小红旗


图5-6 扫雷图
玩家在游戏过程中会对某一格周围是否有雷进行判断,当玩家认为格子下有雷时就可以通过使用鼠标右键点击该格子,将该格子标识为小红旗如图5-6所示。

      1. 玩家因触碰到雷而导致游戏结束


图5-7 扫雷失败图
如图5-7所示,当玩家在点开了有雷的格子时,游戏失败,系统会自动将所有的地雷显示出来,并且会弹出一个窗口提示玩家:“你输了,请继续努力!”。除此以外还有两个选项,当玩家选择重新开始按钮时,游戏界面初始化,雷区中的雷全部取消,等待玩家第一次点击格子后开始布雷;如果用户点的是取消的话,就可以关掉这个提示的弹框。

      1. 玩家扫雷成功


图5-8 扫雷成功图
如图5-8所示,当所有的地雷用都被玩家用小红旗标识出来,并且非雷的格子也被点开时游戏结束,计时停止,系统弹框提醒玩家“您真厉害,请输入您的名字,记录上榜!”,玩家可以在这个界面输入自己的名字来保存扫雷成绩。

      1. 玩家游戏数据显示


图5-9扫雷榜图
如图5-9所示,每当玩家成功扫完所有的雷后,系统会自动的记录本次的成绩,保存在本地的txt文档中,内容是三种难度,玩家姓名以及对应难度玩家所用的最少游戏时间。当玩家点击查看英雄榜的时候系统默认只显示三种难度和匿名。


图5-10 最佳成绩显示图


图5-11 重新记分显示图
如图5-10所示,玩家点击关闭时此窗口会关掉这个弹窗,当点击重新记分时,统计信息会清空,点击显示成绩就好刷新并将历史最好的成绩显示出来。界面也会发生变化,可以显示出游戏用时。

    1. 程序打包发布过程
      至此扫雷游戏已经编写好了,我们可以将这个游戏打包发布,这就需要利用jar.exe命令来将编写好的扫雷游戏的class文件打包发布。
      1. 编写清单文件。首先需要使用“记事本”,根据要求自己编写一个清单文件,格式和内容要和这里给的样子一样,具体内容如下:


将清单文件保存到E:\workspace\扫雷\bin\com\zx\mine中,也就是和编写的扫雷游戏程序生成的字节码存放的位置相同,保证这些文件放在一个目录下。
需要注意的是,清单文件中的所有内容均为键值对的形式存放,键值直接必须空一个空格。

      1. 生成JAR文件。E:\workspace\扫雷\jarcfmGame.jarMymoon.mf*.class,这条命令中有一些参数需要明白:
        ①参数c意味着要让系统生成一个文件,这个文件叫做JAR。
        ②参数f意味着要让系统产生JAR文件的名字。
        ③参数m意味着显示清单文件的名字。
        最后就可以将这个生成的JAR文件复制到任何一个安装了Java运行环境(电脑上的jdk版本不能太低)的电脑上,玩家就可直接用鼠标双击图标来运行自己编写的扫雷小游戏了。

  1. 游戏测试结果
    1. 游戏难度自定义测试
      游戏难度自定义测试用例:
      表6-1 自定义难度测试用例表
预期结果选择不同的难度后可以正确的初始化雷区
输入条件分别点击初级、中级、高级和自定义
测试结果点选不同难度后系统会自动改变雷区大小和地雷数


图6-1 游戏难度以及自定义测试


图6-2 初级难度测试


图6-3中级难度测试


图6-4 高级难度测试


图6-5 自定义难度测试
如图6-1所示,本次扫雷游戏设计将游戏的难度设定为初、中、高以及自定义四种难度,前三种难度对应的游戏界面的尺寸以及地雷总数是不同的,难度越大雷数越多,尺寸也越大,玩家扫雷时就需要更加的动脑思考才能通关。除了固定的难度以外还可以自定义难度,玩家需要自己输入尺寸与雷数,系统根据玩家的要求生产新的游戏,难度可以更加的灵活,这样可以让玩家更满意。
图6-2、图6-3、图6-4、图6-5测试了四种难度的选择,由图可以看出难度的选择功能正常,可以自由变换尺寸和地雷总数。

    1. 扫雷测试
      扫雷胜利测试用例:
      表6-2 扫雷胜利测试用例表
预期结果成功的将所有地雷标记出来后,游戏可以立马结束且会弹框提示输入玩家名字
输入条件将所有地雷标记并将非雷格子点开
测试结果游戏结束并且显示了要求玩家输入名字的弹框


扫雷失败测试用例:
表6-3 扫雷失败测试用例表

预期结果点击了有雷的格子后游戏可以立马结束且弹框提示玩家游戏失败
输入条件点击有雷的格子
测试结果游戏正确的结束且弹框提示了游戏失败的提示


图6-6 游戏完成并且扫雷成功测试
如图6-6所示,玩家可以通过左键单击格子点开格子,右键单击标识地雷的方式完成游戏。当玩家将所有的地雷都标识出来且其他的格子都被点开的情况下,游戏胜利。系统会弹框提示玩家成功完成了游戏任务“您真厉害,请输入您的名字,记录上榜!”。玩家可以在弹出的弹框中输入自己的名字,若不输入则默认为匿名,然后系统会保存玩家的游戏记录。当玩家点击扫雷榜的时候可以查看自己用时最短的游戏记录。


图6-7 游戏完成但是扫雷失败测试
如图6-7所示,玩家可以通过左键单击格子点开格子,右键单击标识地雷的方式完成游戏。当玩家在进行扫雷游戏的时候,不小心点击了有雷的格子,这时候地雷被引爆了,所有未被标记的和已被标记的都被引爆,游戏结束。此时系统会自动将所有的地雷显示出来,方便玩家查看之前标记的正确与否,可以总结经验。然后系统弹框提示“你输了,请继续努力!”,玩家可以点击重新开始的按钮重开游戏,继续挑战扫雷游戏,也可以选择结束游戏。

    1. 玩家游戏数据显示测试
      最佳成绩测试用例:
      表6-4 最佳成绩测试用例表
预期结果点击“最佳成绩”按钮时会显示玩家最佳的游戏记录
输入条件打开游戏的扫雷榜,点击“最佳成绩”按钮
测试结果游戏可以正确的显示玩家的游戏记录


重新记分测试用例:
表6-5 重新记分测试用例表

预期结果点击“重新记分”按钮时会清除玩家以往的游戏记录
输入条件打开游戏的扫雷榜,点击“重新记分”按钮
测试结果点击按钮后游戏记录消失,再点击“最佳成绩”按钮只会显示默认的成绩


图6-8 扫雷榜测试


图6-9 最佳成绩测试


图6-10 重新记分测试
如图6-8和图6-9所示,玩家在进行游戏时,如果成功的扫完雷,系统会提示并保存游戏记录。在扫雷榜上玩家可以进行查看。但是当玩家反复通关后,系统会自动的将用时最短的扫雷记录显示出来,所以玩家在查看记录的时候只能看到最快一次通关的记录。
通过图6-10可以看出,当玩家点击了“重新记分”按钮后,之前游戏记录都会被清除掉,只会显示默认的数据:时间为999秒,名字是匿名。

    1. 游戏数据显示区测试
      计时区显示测试用例:
      表6-6 计时区显示测试用例表
预期结果第一次点击雷区的格子时,计时区开始计时,游戏结束时计时停止,重新开始游戏时,计时归零
输入条件点击雷区的任意一格,然后结束游戏,最后点击“重新开始”按钮
测试结果当点击雷区任意一格后计时开始,计时正常。然后点击有雷的格子结束游戏发现计时停止。点击“重新开始”后计时区的计时正确归零


图6-11 开始计时测试


图6-12 计时停止测试


图6-13 重新计时测试
如图6-11,图6-12,图6-13所示,当点击雷区中的任意一格后,计时区开始正常计时。当游戏结束时计时区的计时就会停止,最后点击了“重新开始”后计时区的计时归零。
地雷数显示区测试用例:
表6-7 地雷数显示区测试用例表

预期结果标记地雷后计数区的数字会减1,如果标记数超过地雷数,计数区会用负数显示
输入条件先标记一颗地雷,然后将所有的地雷标记出来
测试结果标记一颗地雷后,计数区可以正确的减少1个,然后当所有的地雷全被标记后,如果继续标记,计数区可以正确的以负数表示


图6-14 计数区测试


图6-15 计数区负数显示测试
如图6-14,图6-15所示,当标记了一颗地雷后计数区中的数字会相应的减少一个,表示有一颗雷被找到了。但是如果标记数超过了地雷的数目,计数区会以负数显示,表示有的地雷标记错了

    1. 递归算法测试

递归调用测试用例:

表6-8 递归调用测试用例表

预期结果当点击一个周围八个格子中都没有雷的格子时,系统会将这8个格子也点开
输入条件点开一个周围没有雷的格子
测试结果系统正确的将周围的八个格子点开,如果这些格子中周围的格子没有雷的话还会自动点开

图6-16 递归算法测试

如图6-16所示,当点击一个周围八个格子中都没有雷的格子时,系统会将周围的格子点开,被点开的格子也会显示周围的地雷数。如果被点开的格子周围还是没有地雷,系统就会递归调用再把周围的格子点开。

扫雷游戏的代码编写完成,且基本功能也实现了,虽然与电脑上自带的扫雷游戏相比在界面上还是有着一些差距,一些小细节也没有完善,但已经让我很满意了,我会在以后继续完善这些细节,让这个扫雷游戏变得愈来愈好玩。在简单的测试后,该游戏也达到了我的预期,所以本次的设计就到此结束了。

结 论

毕业设计对于我们来说并不是简单的一个毕业考核而已,这是我们的一个机会,一个将大学四年所学到的知识结合起来,通过自己的构思完成一个系统的机会。在完成这个系统的过程中,一定会遇到许许多多的问题,这些问题就犹如大山挡在我们的面前,如何去解决这些问题也是我们要思考的。而当我们把困难都解决掉,成功完成毕业设计的时候,就说明我们成功的摆脱了只会理论知识,不会解决实际问题的状态,锻炼了我们学习新事物并加以运用以及发现问题并找到方法去解决的能力。

虽然这次的扫雷游戏看似简单,但是其中却涉及了GUI设计,这一部分在学校是没有深讲的,需要我自己去学习。其中有各种组件的调用,各种控件的使用等,我通过本次的毕业设计又学到了许多的新知识。

通过编写这次的扫雷游戏,我深刻的认识到了细节问题的重要性,这些问题看似无关紧要但是如果忽视会养成不好的编程习惯,所以我们要着眼于细节,善于去发现身边的一些小事情,让自己的水平不断提高。而且我的编程能力在不断的编写代码的过程中也有了显著的提高。很多时候人们都认为知识学过了就行,但是当实际去用的时候就会发现这些知识是多么的陌生。所以我认为学过的知识就要多用,用实际操作来让知识真正变成自己的。

参考文献ᄃ

[1] 耿祥义.Java大学实用教程[M].北京:清华大学出版社,2009.

[2] 冯锋,王运坚.Visual Basic 程序设计基础教程[M].西安:电子工业出版社,1999。

[3] 王鹏.JavaSwing图形界面开发与案例详解[M].北京:清华大学出版社,2008。

[4] 李怀明.Visual Basic 6.0 中文版 参考详解[M].北京:清华大学出版社,1999。

[5] 丁振凡.Java语言实验教程[M].北京:北京邮电大学出版社,2005。

[6] 郑莉.Java语言程序设计[M].北京:清华大学出版社,2006。

[7] 伍俊良.VB课程设计与系统开发案例[M].北京:清华大学出版社,2002。

[8] 孙全党,王吴迪,赵枫朝.Java程序设计应用教程[M].北京:电子工业出版社,2006。

[9] 雷之宇.Java项目开发实践-网络篇[M].北京:中国铁道出版社,2005。

[10] 赵玉阳.Java从入门到精通[M].北京:清华大学出版社,2006。

[11] 何斌,刘醒.Visual Basic 6.0 应用指南[M].成都:四川大学出版社,1998。

[12] 毕广吉.Java程序设计实例教程[M].北京:冶金工业出版社,2007年。

[13] 李善茂.Visual Basic 6.0 高级编程技巧[M].西安:电子工业出版社,1999。

[14] 王保罗.Java面向对象程序设计[M].北京:清华大学出版社,2003年.人民邮电出版社,2004

[15] 刘腾红,孙细明.信息系统分析与设计[M].北京:科学出版社,2003年。

[16] 林邦杰,彻底研究java[M].北京:电子工业出版社,2002年。

[17] 刘京华. Java Web整合开发王者归来[M].北京:清华大学出版社,2010。

[18] 唐任仲.工程应用软件开发技术[M].北京:化学工业出版社,1999。

[19] 谭浩强.Visual Basic 6.0 中文版 提高与应用[M].西安:电子工业出版社,1999。

[20] (美)阿诺德,Ken Arnold,等. Java程序设计语言[M].北京:人民邮电出版社,2006.

[21] Bruce Eckel. Thinking in Java[M]. Upper Saddle River, New Jersey, USA: Prentice Hall, 2006.

[22] Holger Eichelberger,Klaus Schhmid.Flexible resource monitoring ofJava programs.[J].The Journal of Systems & Software,2014 Elsevier.

[23] Joshua Bloch. Effective Java[M]. Piscataway, N.J: IEEE Press, 2009.

[24] oug Twilleager,Jeff Kesselman,Athomas Goldberger,Daniel Petersen,Juan Carlos,Soto,Chris Melissinos.Java Technologies for games.[J].Computers in Entertainment(CIE),2004,Vol.2(2),pp.18-18 ACM.

致 谢

感谢杰普基地的老师以及校内指导老师,在这次我的毕业设计过程中,老师们对我的悉心的指导,从一开始的开题报告与选择到后来的系统设计,从程序设计到论文的编写,他们纠正了我许多的不成熟的想法和考虑,给我提出了许多宝贵的建议,而且在程序设计与开发过程中遇到的最困难的时候给予我继续努力完成设计的信心。老师们严谨的作风和对我的严格要求,使我克服了懒惰和投机取巧的毛病,能过不断战胜自己,取得进步。

通过这次毕业设计的整个开发过程,我系统开发过程从需求分析到具体功能实现,再到最终测试和维护的理解有了很大的进步,让我对系统开发有了更深层次的认识。现在我的动手能力和独立解决问题的能力也得到了很大的锻炼和提高,这是这次毕业设计最好的收获。

最后,在整个系统开发过程中,我身边的同学和朋友给了我很多的建议,让我很快的确定了系统的业务逻辑。在次,我衷心的向他们表示感谢。

在论文完成过程中,本人还得到了其他老师和许多同学的热心帮助,本人向他们表示深深的谢意!

最后向在百忙之中评审本文的各位专家、老师表示衷心的感谢!

外文原文

Thinking in Java

Learning Java

At about the same time that my first book, Using C++ (Osborne/McGraw-Hill, 1989), came out, I began teaching that language. Teaching programming ideas has become my profession; I’ve seen nodding heads, blank faces, and puzzled expressions in audiences all over the world

since 1987. As I began giving in-house training with smaller groups of people, I discovered something during the exercises. Even those people who were smiling and nodding were confused about many issues. I found out, by creating and chairing the C++ track at the Software Development Conference for a number of years (and later creating and chairing the Java track), that I and other speakers tended to give the typical audience too many topics too quickly. So eventually, through both variety in the audience level and the way that I presented the material, I would end up losing some portion of the audience. Maybe it’s asking too much, but because I am one of those people resistant to traditional lecturing (and for most people, I believe, such resistance results from boredom), I wanted to try to keep everyone up to speed. For a time, I was creating a number of different presentations in fairly short order. Thus, I ended up learning by experiment and iteration (a technique that also works well in program design). Eventually, I developed a course using everything I had learned from my teaching experience. My company, MindView, Inc., now gives this as the public and in-house Thinking in Java seminar; this is our main introductory seminar that provides the foundation for our more advanced seminars. You can find details at www.MindView.net. (The introductory seminar is also available as the Hands-On Java CD ROM. Information is available at the same Web site.)

The feedback that I get from each seminar helps me change and refocus the material until I think it works well as a teaching medium. But this book isn’t just seminar notes; I tried to pack as much information as I could within these pages, and structured it to draw you through into the next subject. More than anything, the book is designed to serve the solitary reader who is struggling with a new programming language.

The singly rooted hierarchy

One of the issues in OOP that has become especially prominent since the introduction of C++ is whether all classes should ultimately be inherited from a single base class. In Java (as with virtually all other OOP languages except for C++) the answer is yes, and the name of this ultimate base class is simply Object. It turns out that the benefits of the singly rooted hierarchy are many.

All objects in a singly rooted hierarchy have an interface in common, so they are all ultimately the same fundamental type. The alternative (provided by C++) is that you don’t know that everything is the same basic type. From a backward-compatibility standpoint this fits the model of C better and can be thought of as less restrictive, but when you want to do full-on objectoriented programming you must then build your own hierarchy to provide the same convenience that’s built into other OOP languages. And in any new class library you acquire, some other incompatible interface will be used. It requires effort (and possibly multiple inheritance) to work the new interface into your design. Is the extra “flexibility” of C++ worth it? If you need it—if you have a large investment in C—it’s quite valuable. If

you’re starting from scratch, other alternatives such as Java can often be more productive.

All objects in a singly rooted hierarchy can be guaranteed to have certain functionality. You know you can perform certain basic operations on every object in your system. All objects can easily be created on the heap, and argument passing is greatly simplified. A singly rooted hierarchy makes it much easier to implement a garbage collector, which is one of the fundamental improvements of Java over C++. And since information about the type of an object is guaranteed to be in all objects, you’ll never end up with an object whose type you cannot determine. This is especially important with system-level operations, such as exception handling, and to allow greater flexibility in programming.

Containers

In general, you don’t know how many objects you’re going to need to solve a particular problem, or how long they will last. You also don’t know how to store those objects. How can

you know how much space to create if that information isn’t known until run time?

The solution to most problems in object-oriented design seems flippant: You create another type of object. The new type of object that solves this particular problem holds references to other objects. Of course, you can do the same thing with an array, which is available in most

languages. But this new object, generally called a container (also called a collection, but the Java library uses that term in a different sense so this book will use “container”), will expand itself whenever necessary to accommodate everything you place inside it. So you don’t need to know how many objects you’re going to hold in a container. Just create a container object

and let it take care of the details.

Fortunately, a good OOP language comes with a set of containers as part of the package. In C++, it’s part of the Standard C++ Library and is often called the Standard Template Library(STL). Smalltalk has a very complete set of containers. Java also has numerous containers inits standard library. In some libraries, one or two generic containers is considered good enough for all needs, and in others (Java, for example) the library has different types of containers for different needs: several different kinds of List classes (to hold sequences), Maps (also known as associative arrays, to associate objects with other objects), Sets (to hold one of each type of object), and more components such as queues, trees, stacks, etc.

From a design standpoint, all you really want is a container that can be manipulated to solve your problem. If a single type of container satisfied all of your needs, there’d be no reason to have different kinds. There are two reasons that you need a choice of containers. First, containers provide different types of interfaces and external behavior. A stack has a different interface and behavior than a queue, which is different from a set or a list. One of these might provide a more flexible solution to your problem than the other. Second, different containers have different efficiencies for certain operations. For example, there are two basic types of List: ArrayList and LinkedList. Both are simple sequences that can have identical interfaces and external behaviors. But certain operations can have significantly different costs. Randomly accessing elements in an ArrayList is a constant-time operation; it takes the same amount of time regardless of the element you select. However, in a LinkedList it is expensive to move through the list to randomly select an element, and it takes longer to find an element that is farther down the list. On the other hand, if you want to insert an element in the middle of a sequence, it’s cheaper in a LinkedList than in an ArrayList. These and other

operations have different efficiencies depending on the underlying structure of the sequence. You might start building your program with a LinkedList and, when tuning for performance,change to an ArrayList. Because of the abstraction via the interface List, you can change from one to the other with minimal impact on your code.

Object creation & lifetime

One critical issue when working with objects is the way they are created and destroyed. Each object requires resources, most notably memory, in order to exist. When an object is no longer needed it must be cleaned up so that these resources are released for reuse. In simple programming situations the question of how an object is cleaned up doesn’t seem too challenging: You create the object, use it for as long as it’s needed, and then it should be destroyed. However, it’s not hard to encounter situations that are more complex.

Suppose, for example, you are designing a system to manage air traffic for an airport. (The same model might also work for managing crates in a warehouse, or a video rental system, or a kennel for boarding pets.) At first it seems simple: Make a container to hold airplanes, then create a new airplane and place it in the container for each airplane that enters the air-trafficcontrol zone. For cleanup, simply clean up the appropriate airplane object when a plane leaves the zone.

But perhaps you have some other system to record data about the planes; perhaps data that doesn’t require such immediate attention as the main controller function. Maybe it’s a record of the flight plans of all the small planes that leave the airport. So you have a second container of small planes, and whenever you create a plane object you also put it in this second container if it’s a small plane. Then some background process performs operations on the objects in this container during idle moments.

Now the problem is more difficult: How can you possibly know when to destroy the objects? When you’re done with the object, some other part of the system might not be. This same

problem can arise in a number of other situations, and in programming systems (such as C++) in which you must explicitly delete an object when you’re done with it this can become quite complex.

Where is the data for an object and how is the lifetime of the object controlled? C++ takes the approach that control of efficiency is the most important issue, so it gives the programmer a choice. For maximum runtime speed, the storage and lifetime can be determined while the program is being written, by placing the objects on the stack (these are sometimes called

automatic or scoped variables) or in the static storage area. This places a priority on the speed of storage allocation and release, and this control can be very valuable in some situations. However, you sacrifice flexibility because you must know the exact quantity, lifetime, and type of objects while you’re writing the program. If you are trying to solve a more general problem such as computer-aided design, warehouse management, or air-traffic control, this is too restrictive.

The second approach is to create objects dynamically in a pool of memory called the heap. In this approach, you don’t know until run time how many objects you need, what their lifetime is, or what their exact type is. Those are determined at the spur of the moment while the program is running. If you need a new object, you simply make it on the heap at the point that you need it. Because the storage is managed dynamically, at run time, the amount of time required to allocate storage on the heap can be noticeably longer than the time to create storage on the stack. Creating storage on the stack is often a single assembly instruction to move the stack pointer down and another to move it back up. The time to create heap storage depends on the design of the storage mechanism.

The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object. In addition, the greater flexibility is essential to solve the general programming problem.

Java uses dynamic memory allocation, exclusively. Every time you want to create an object, you use the new operator to build a dynamic instance of that object. There’s another issue, however, and that’s the lifetime of an object. With languages that allow objects to be created on the stack, the compiler determines how long the object lasts and can automatically destroy it. However, if you create it on the heap the compiler has no knowledge of its lifetime. In a language like C++, you must determine programmatically when to destroy the object, which can lead to memory leaks if you don’t do it correctly (and this is a common problem in C++

programs). Java provides a feature called a garbage collector that

automatically discovers when an object is no longer in use and destroys it. A garbage collector is much more convenient because it reduces the number of issues that you must track and the code you must write. More importantly, the garbage collector provides a much

higher level of insurance against the insidious problem of memory leaks, which has brought many a C++ project to its knees.

With Java, the garbage collector is designed to take care of the problem of releasing the memory (although this doesn’t include other aspects of cleaning up an object). The garbage collector “knows” when an object is no longer in use, and it then automatically releases the memory for that object. This, combined with the fact that all objects are inherited from the single root class Object and that you can create objects only one way—on the heap—makes

the process of programming in Java much simpler than programming in C++. You have far fewer decisions to make and hurdles to overcome.

中文翻译

Java编程思想

Java的学习

在我第一本书《Using C++》面市的几乎同一时间(Osborne/McGraw-Hill 于 1989 年出版),我开始教授那种语言。程序设计语言的教授已成为我的专业。自 1989 年以来,我便在世界各地见过许多昏昏欲睡、满脸茫然以及困惑不解的面容。开始在室内面向较少的一组人授课以后,我从作业中发现了一些特别的问题。即使

那些上课面带会心的微笑或者频频点头的学生,对许多问题也存在认识上的混淆。在过去几年间的“软件开发会议”上,由我主持 C++分组讨论会(现在变成了 Java 讨论会)。有的演讲人试图在很短的时间内向听众灌输过多的主题。所以到最后,尽管听众的水平都还可以,而且提供的材料也很充足,但仍然损失了一部分

听众。这可能是由于问得太多了,但由于我是那些采取传统授课方式的人之一,所以很想使每个人都能跟上讲课进度。

有段时间,我编制了大量教学简报。经过不断的试验和修订(或称“反复”,这是在 Java 程序设计中非常有用的一项技术),最后成功地在一门课程中集成了从我的教学经验中总结出来的所有东西——我在很长一段时间里都在使用。其中由一系列离散的、易于消化的小步骤组成,而且每个小课程结束后都有一些适当的练习。我目前已在 Java 公开研讨会上公布了这一课程,大家可到 http://www.BruceEckel.com 了解详情(对研讨会的介绍也以 CD-ROM 的形式提供,具体信息可在同样的 Web 站点找到)。 从每一次研讨会收到的反馈都帮助我修改及重新制订学习材料的重心,直到我最后认为它成为一个完善的教学载体为止。但本书并非仅仅是一本教科书——我尝试在其中装入尽可能多的信息,并按照主题进行了有序的分类。无论如何,这本书的主要宗旨是为那些独立学习的人士服务,他们正准备深入一门新的程序设计语言,而没有太大的可能参加此类专业研讨会。

单根结构

在面向对象的程序设计中,由于 C++的引入而显得尤为突出的一个问题是:所有类最终是否都应从单独一个基础类继承。在 Java 中(与其他几乎所有 OOP 语言一样),对这个问题的答案都是肯定的,而且这个终级基础类的名字很简单,就是一个“Object”。这种“单根结构”具有许多方面的优点。

单根结构中的所有对象都有一个通用接口,所以它们最终都属于相同的类型。另一种方案(就象 C++那样)是我们不能保证所有东西都属于相同的基本类型。从向后兼容的角度看,这一方案可与 C 模型更好地配合,而且可以认为它的限制更少一些。但假期我们想进行纯粹的面向对象编程,那么必须构建自己的结构,以期获得与内建到其他 OOP 语言里的同样的便利。需添加我们要用到的各种新类库,还要使用另一些不兼容的接口。理所当然地,这也需要付出额外的精力使新接口与自己的设计方案配合(可能还需要多重继承)。为得到 C++额外的“灵活性”,付出这样的代价值得吗,当然,如果真的需要——如果早已是 C 专家,如果对 C有难舍的情结——那么就真的很值得。但假如你是一名新手,首次接触这类设计,象 Java 那样的替换方案也许会更省事一些。 单根结构中的所有对象(比如所有 Java 对象)都可以保证拥有一些特定的功能。在自己的系统中,我们知道对每个对象都能进行一些基本操作。一个单根结构,加上所有对象都在内存堆中创建,可以极大简化参数的传递(这在 C++里是一个复杂的概念)。 利用单根结构,我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中,而垃圾收集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构,而且系统通过一个句柄来操纵对象,那么实现垃圾收集器的途径会有很大的不同,而且会面临许多障碍。

由于运行期的类型信息肯定存在于所有对象中,所以永远不会遇到判断不出一个对象的类型的情况。这对系统级的操作来说显得特别重要,比如违例控制;而且也能在程序设计时获得更大的灵活性。但大家也可能产生疑问,既然你把好处说得这么天花乱坠,为什么 C++没有采用单根结构呢,事实上,这是早期在效率与控制上权衡的一种结果。单根结构会带来程序设计上的一些限制。而且更重要的是,它加大了新程序与原有代C码兼容的难度。尽管这些限制仅在特定的场合会真的造成问题,但为了获得最大的灵活程度,C++最终决定放弃采用单根结构这一做法。而 Java 不存在上述的问题,它是全新设计的一种语言,不必

与现有的语言保持所谓的“向后兼容”。所以很自然地,与其他大多数面向对象的程序设计语言一样,单根结构在 Java 的设计方案中很快就落实下来。

集合与继承器

针对一个特定问题的解决,如果事先不知道需要多少个对象,或者它们的持续时间有多长,那么也不知道如何保存那些对象。既然如此,怎样才能知道那些对象要求多少空间呢,事先上根本无法提前知道,除非进入运行期。

在面向对象的设计中,大多数问题的解决办法似乎都有些轻率——只是简单地创建另一种类型的对象。用于解决特定问题的新型对象容纳了指向其他对象的句柄。当然,也可以用数组来做同样的事情,那是大多数语言都具有的一种功能。但不能只看到这一点。这种新对象通常叫作“集合”(亦叫作一个“容器”,但 AWT

在不同的场合应用了这个术语,所以本书将一直沿用“集合”的称呼。在需要的时候,集合会自动扩充自己,以便适应我们在其中置入的任何东西。所以我们事先不必知道要在一个集合里容下多少东西。只需创建一个集合,以后的工作让它自己负责好了。 幸运的是,设计优良的 OOP 语言都配套提供了一系列集合。在 C++中,它们是以“标准模板库”(STL)的形式提供的。 Object Pascal 用自己的“可视组件库”(VCL)提供集合。 Smalltalk 提供了一套非常完整的集合。而 Java 也用自己的标准库提供了集合。在某些库中,一个常规集合便可满足人们的大多数要求;而在另一些库中(特别是C++的库),则面向不同的需求提供了不同类型的集合。例如,可以用一个矢量统一对所有元素的访问方式;一个链接列表则用于保证所有元素的插入统一。所以我们能根据自己的需要选择适当的类型。其中包括集、队列、散列表、树、堆栈等等。 所有集合都提供了相应的读写功能。将某样东西置入集合时,采用的方式是十分明显的。有一个叫作“推”(Push)、“添加”(Add)或其他类似名字的函数用于做这件事情。但将数据从集合中取出的时候,方式却并不总是那么明显。如果是一个数组形式的实体,比如一个矢量(Vector),那么也许能用索引运算符或函

数。但在许多情况下,这样做往往会无功而返。此外,单选定函数的功能是非常有限的。如果想对集合中的一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢,

办法就是使用一个“继续器”(Iterator),它属于一种对象,负责选择集合内元素,并把它们提供给继承器的用户。作为一个类,它也提供了一级抽象。利用这一级抽象,可将集合细节与用于访问那个集合的代码隔离开。通过继承器的作用,集合被抽象成一个简单的序列。继承器允许我们遍历那个序列,同时毋需关心基础结构是什么——换言之,不管它是一个矢量、一个链接列表、一个堆栈,还是其他什么东西。这样一来,我们就可以灵活地改变基础数据,不会对程序里的代码造成干扰。 Java 最开始(在1.0和 1.1版中)提供的是一个标准继承器,名为 Enumeration(枚举),为它的所有集合类提供服务。 Java 1.2 新增一个更复杂的集合库,其中包含了一个名为 Iterator 的继承器,可以做比老式的 Enumeration 更多的事情。

从设计角度出发,我们需要的是一个全功能的序列。通过对它的操纵,应该能解决自己的问题。如果一种类型的序列即可满足我们的所有要求,那么完全没有必要再换用不同的类型。有两方面的原因促使我们需要对集合作出选择。首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列的接口与行为又与一个集(Set)或列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。 其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量( Vector)和列表( List )的区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么,需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重”的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在序列中部插入一个元素,用列表就比用矢量划算得多。这些以及其他操作都有不同的执行效率,具体取决于序列的基础结构是什么。在设计阶段,我们可以先从一个列表开始。最后调整性能的时候,再根据情况把它换成矢量。由于抽象是通过继承器进行的,所以能在两者方便地切换,对代码的影响则显得微不足道。最后,记住集合只是一个用来放置对象的储藏所。如果那个储藏所能满足我们的所有需要,就完全没必要关心它具体是如何实现的(这是大多数类型对象的一个基本概念)。如果在一个编程环境中工作,它由于其他因素(比如在 Windows 下运行,或者由垃圾收集器带来了开销)产生了内在的开销,那么矢量和链接列表之间在系统开销上的差异就或许不是一个大问题。我们可能只需要一种类型的序列。甚至可以想象有一个“完美”的集合抽象,它能根据自己的使用方式自动改变基层的实现方式。

对象的创建和存在时间

每个对象都要求资源才能“生存”,其中最令人注目的资源是内存。如果不再需要使用一个对象,就必须将其清除,以便释放这些资源,以便其他对象使用。如果要解决的是非常简单的问题,如何清除对象这个问题并不显得很突出:我们创建对象,在需要的时候调用它,然后将其清除或者“破坏”。但在另一方面,我们平时遇到的问题往往要比这复杂得多。

举个例子来说,假设我们要设计一套系统,用它管理一个机场的空中交通(同样的模型也可能适于管理一个仓库的货柜、或者一套影带出租系统、或者宠物店的宠物房。这初看似乎十分简单:构造一个集合用来容纳飞机,然后创建一架新飞机,将其置入集合。

对进入空中交通管制区的所有飞机都如此处理。至于清除,在一架飞机离开这个区域的时候把它简单地删去即可。

但事情并没有这么简单,可能还需要另一套系统来记录与飞机有关的数据。当然,和控制器的主要功能不同,这些数据的重要性可能一开始并不显露出来。例如,这条记录反映的可能是离开机场的所有小飞机的飞行计划。所以我们得到了由小飞机组成的另一个集合。一旦创建了一个飞机对象,如果它是一架小飞机,那

么也必须把它置入这个集合。然后在系统空闲时期,需对这个集合中的对象进行一些后台处理。

问题现在显得更复杂了:如何才能知道什么时间删除对象呢,用完对象后,系统的其他某些部分可能仍然要发挥作用。同样的问题也会在其他大量场合出现,而且在程序设计系统中(如 C++),在用完一个对象之后必须明确地将其删除,所以问题会变得异常复杂。

最重要的问题之一是对象的创建及破坏方式。对象需要的数据位于哪儿,如何控制对象的“存在时间”呢,针对这个问题,解决的方案是各异其趣的。 C++认为程序的执行效率是最重要的一个问题,所以它允许程序员作出选择。为获得最快的运行速度,存储以及存在时间可在编写程序时决定,只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可。这样便为存储空间的分配和释放提供了一个优先级。某些情况下,这种优先级的控制是非常有价值的。然而,我们同时也牺牲了灵活性,因为在编写程序时,必须知道对象的准确的数量、存在时间、以及类型。如果要解决的是一个较常规的问题,如计算机辅助设计、仓储管理或者空中交通控制,这一方法就显得太局限了。 第二个方法是在一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”。若采用这种方式,除非进入运行期,否则根本不知道到底需要多少个对象,也不知道它们的存在时间有多长,以及准确的类型是什么。这些参数都在程序正式运行时才决定的。若需一个新对象,只需在需要它的时候在内存堆里简单地创建

它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。

动态方法使该对象对于的一般逻辑的假设往往是复杂的,所以找到存储和释放该内存的额外空间不会对创建对象产生重要的影响。此外,更多的灵活性对于解决一般的规划问题是至关重要的。

Java 对象不具备与主类型一样的存在时间。对于用 new 创建的对象,只要我们愿意,它们就会一直保留下去。这个编程问题在 C和 C++里特别突出。看来在 C++里遇到的麻烦最大:由于不能从语言获得任何帮助,所以在需要对象的时候,根本无法确定它们是否可用。而且更麻烦的是,在 C++里,一旦工作完成,必须保证将对象清除。这样便带来了一个有趣的问题。假如 Java 让对象依然故我,怎样才能防止它们大量充斥内存,并最终造成程序的“凝固”呢。在 C++里,这个问题最令程序员头痛。但 Java 以后,情况却发生了改观。 Java 有一个特别的“垃圾收集器”,它会查找用 new 创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。只需简单地创建对象,一旦不再需要它们,它们就会自动离去。这样做可防止在 C++里很常见的一个编程问题:由于程序员忘记释放内存造成的“内存溢出”。

在 Java 中,垃圾收集器在设计时已考虑到了内存的释放问题(尽管这并不包括清除一个对象涉及到的其他方面)。垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。采用这种方式,另外加上所有对象都从单个根类 Object 继承的事实,而且由于我们只能在内存堆中以一种方式创建对象,所以 Java 的编程要比 C++的编程简单得多。我们只需要作出少量的抉择,即可克服原先存在的大量障碍。

  • 2
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
戏界面可以分为三个模块:菜单栏,戏数据显示区域以及雷区。 (1)菜单栏: 菜单栏包括戏难度的选择和排行榜的查询以及戏帮助。 (2)戏数据显示区域: 戏数据显示区域包括地雷计数区、计时区和重新开始按钮。 (3)雷区: 雷区就是玩家的戏区域,当玩家在戏过程中用鼠标点击某一模块,系统会自动作出相应的响应,这就是戏中的鼠标事件。 简单玩法介绍: 当玩家点开第一个格子时雷区开始布雷同时计时开始。然后会在该格子上显示周围8个格子的雷数(如果没有雷则自动点开周围的格子), 玩家就需要通过这些数字来判断雷的位置,将是雷的格子标记为小红旗。若玩家认为已标记的格子时错误的,可以再次右击该格子来取消标记。 当某一格子被标记时,对于该格子的单击操作是无效的(防止玩家误点击导致戏失败)。如果玩家将某一格周围8个格子中的雷标记了出来, 双击该格子会自动将周围的格子点击一遍,这样可以简化玩家的操作。当玩家将全部的地雷标记出来并且其他的格子点开时,戏结束。 但是如果玩家不小心点到了地雷就会戏失败,系统自动显示出所有的地雷。如果玩家标识的地雷数量超过了该难度下规定的雷数, 计数区会以负数显示超出的雷数并且戏不会结束。
好的,下面是Java实现扫雷游戏的完整源码: ```java import java.util.Random; import java.util.Scanner; public class MineSweeper { private int[][] board; // 扫雷游戏面板 private boolean[][] revealed; // 记录位置是否被揭示 private int mines; // 雷的个数 private int size; // 面板大小 private boolean gameOver; // 戏是否结束 public MineSweeper(int size, int mines) { this.size = size; this.mines = mines; this.board = new int[size][size]; this.revealed = new boolean[size][size]; this.gameOver = false; initBoard(); } // 初始化面板,随机放置雷 private void initBoard() { Random random = new Random(); int count = 0; while (count < mines) { int x = random.nextInt(size); int y = random.nextInt(size); if (board[x][y] != -1) { // 如果该位置不是雷,就放置雷 board[x][y] = -1; count++; } } } // 显示戏面板 private void displayBoard() { System.out.println(); System.out.print(" "); for (int i = 0; i < size; i++) { System.out.print(i + " "); } System.out.println(); for (int i = 0; i < size; i++) { System.out.print(i + " "); for (int j = 0; j < size; j++) { if (revealed[i][j]) { // 如果该位置已经被揭示 if (board[i][j] == -1) { // 如果该位置是雷 System.out.print("* "); } else { // 如果该位置不是雷 System.out.print(board[i][j] + " "); } } else { // 如果该位置还没有被揭示 System.out.print(". "); } } System.out.println(); } } // 获取某个位置周围雷的数量 private int getSurroundingMines(int x, int y) { int count = 0; for (int i = x - 1; i <= x + 1; i++) { for (int j = y - 1; j <= y + 1; j++) { if (i >= 0 && i < size && j >= 0 && j < size && board[i][j] == -1) { count++; } } } return count; } // 揭示某个位置 private void reveal(int x, int y) { if (!revealed[x][y]) { // 如果该位置还没有被揭示 revealed[x][y] = true; if (board[x][y] == -1) { // 如果该位置是雷,戏结束 gameOver = true; return; } if (getSurroundingMines(x, y) == 0) { // 如果该位置周围没有雷,递归揭示周围的位置 for (int i = x - 1; i <= x + 1; i++) { for (int j = y - 1; j <= y + 1; j++) { if (i >= 0 && i < size && j >= 0 && j < size) { reveal(i, j); } } } } } } // 运行戏 public void run() { Scanner scanner = new Scanner(System.in); while (!gameOver) { displayBoard(); System.out.print("请输入要揭示的位置(x y):"); int x = scanner.nextInt(); int y = scanner.nextInt(); if (x >= 0 && x < size && y >= 0 && y < size) { // 如果位置合法 reveal(x, y); } } displayBoard(); System.out.println("戏结束!"); } public static void main(String[] args) { MineSweeper game = new MineSweeper(10, 10); // 创建一个大小为10x10,雷数为10的扫雷游戏 game.run(); } } ``` 这个程序实现了一个简单的扫雷游戏,可以自定义面板大小和雷的数量。玩家每次输入要揭示的位置,程序会判断该位置是否是雷,如果是雷则戏结束,否则会递归揭示周围的位置直到所有不是雷的位置都被揭示出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值