JAVA:扫雷设计

1.窗口绘制:创建完成后要继承JFrame类,这样才有监听鼠标键盘时间的功能。写一个launch方法,用来创建窗口
首先,设置窗口是否可见,第二,设置窗口大小setSize(width:500,height:500),设置窗口位置,局中显示,设置窗口标题,最后添加一个关闭窗口的方法this.setDefaultCloseOperation(EXIT_ON_CLOSE);。
方法设置完毕,new一个GameWin类,先添加一个入口函数,调用其中launch方法
将窗口大小要进行修改,使其不为固定
2.雷区绘制:雷区由网格构成,可以通过划线来绘制雷区,创建一个paint方法来绘制组件,其中g.drawLine是画线的方法,需要传入4个参数(起点坐标,终点坐标,横坐标相等是竖线,纵坐标相等是横线)g.drawLine(x1:100,y1:100,x2:100,y2:400)
画两条线g.drawLine(100,100,400,100);同时给线设置一个颜色g.setColor(red);【看看效果】
网格的许多线可以通过循环来实现

创建一个MapBottom类,(底层地图,这个类主要负责绘制游戏相关的组件)
Game类中new一个MaspBottom类,在Game的paint方法中调用MapBottom类中方法,传入画笔
类中创建一个绘制方法paintSelf(Graphics g)绘制自己,传入画笔,写入一个循环绘制的方法
循环方法分开,是因为后期窗口可以是矩形,而并非全为正方形

3.界面规划:窗体顶端需要有两个数字(左边是剩余雷数,右边是点击次数),一张图片,窗口大小代表游戏难度
首先,新建一个工具类GameUtil(存放静态参数,工具方法),设置四个参数,分别是地图的宽(11),高(11)、雷区偏移量(45)、格子边长(50)

4.底层地图:绘制雷区,两个字符串和一个状态图,还有最重要的就是雷区中的元素,雷区是网格,其中每个格子都需要存放对应的元素,每个格代表一个区域,可以根据格的顶点坐标来获取对应的区域,所以雷区可以抽象成一个二维数组,两个维度分别表示横坐标和纵坐标,数组中存放对应的元素。
定义一个静态的int数组static int[][] MAP_BOTTOM = new int[MAP_W+2][MAP_H+2](数组当中存放底层的元素,其中-1代表雷,零代表空,1-8代表对应数字);

获取图片,载入,新建文件夹imgs,
在GameUtil类中,载入,Image类型,传入图片路径,然后在MapBottom中添加相应的绘制方法,因为是二维数组,所以需要循环两轮
g.drawImage是画图的方法,先传入图片路径,其次起点坐标在Image中跟着写入

//(i-1)和(j-1)是因为开始定义的初始值为1,而我们需要的是从0开始;而后面的+1就是为了让图片往下挪一个像素值,将网格的绿线露出来
for (int i = 1; i <= GameUtil.MAP_W; i++) {
for (int j = 1; j <= GameUtil.MAP_H; i++) {
g.drawImage(GameUtil.lei,
x: GameUtil.OFFSET + (i-1) * GameUtil.SQUARE_LENGTH + 1,//这里的x就是左边的偏移量(OFFSET)加上每个方格对应的长度
y: GameUtil.OFFSET * 3 + (j-1) * GameUtil.SQUARE_LENGTH + 1,//y就是竖着的偏移量加上竖着的这些宽度
width: GameUtil.SQUARE_LENGTH - 2,// }这是定义长和高,让图片的宽度和这个网络接近,变成4848,这样上下左右的线都可以露出来
height: GameUtil.SQUARE_LENGTH - 2,//}
observe: null);
5.地雷生成:首先定义地雷数目
在GameUtil中定义
再新建一个类BottomRay底层地雷类,初始化地雷;雷区是网格,可以通过二维数组来表示位置,两个维度分别表示横、纵坐标(均为整数),可以通过单独定义一个一维数组来存放坐标,该数组当中相邻两个数代表地雷坐标,所以数组长度是地雷数的两倍。
同时,类中设置两个参数,用来表示横纵坐标
再设置一个代码块,用来生成地雷坐标,每次生成一个,需要循环 i 从0开始到数组结束;每次需要随机生成x和y的坐标;max函数random ,x是乘以地图的宽度,然后加1(需要强制转化数据类型);//加1是让随机数从0到11变为1到12,y同理
然后,将生成的坐标复制到数组当中,赋值为-1,-1代表地雷
然后再MapBottom当中,new一个地雷类,绘制方法修改,在生成雷的方法当中添加一个判断
6.地雷重合:在雷生成时,由于没有检验该坐标已生成过地雷,所以可能重复于同一坐标生成雷。
需要在雷生成的时候添加判断,判断新生成的两个坐标是否已经存在,循环,如果新生成的坐标已经存在,那么就会回退,其次不可放置(需要再设置一个参数不可放置,boolean类型,初始值为true,true表示可以放置,false表示不可放置)
判断完毕过后,如果仍然是可以放置的,再将两个坐标放到数组当中;最后不论是否可以放置,都需要释放状态
7.数字生成:遍历DATA_BOTTOM当中-1的位置,再次遍历,以-1为中心的3
3区域,只要不是雷,那么加1,
边界上的雷仍可以循环+1,但要添加一个判断来放止数组越界,角落也是循环加1,判断两次是否越界,就可以分开来遍历,很麻烦,所以就可以让二维数组比雷区大上一圈,这也是在设置DATA_BOTTOM横纵坐标都加2的原因。表面雷区大小为1-11,但实际上数组大小为0-12.这样循环遍历雷区3*3时就不会出现数组越界的情况。

新建一个类,底层数字类BottomNum;先循环整个数组,从1开始,到宽为止,在是高,判断这个位置是否是雷;
在MapBottom中new一个底层数字类;再添加绘制方法
在GameUtil中新建一个数组,数字大小为9,用来存放这8张图片,静待代码块,循环1-8,
在MapBottom中添加绘制的方法
8. 顶层绘制:游戏顶部包含两个内容,绘制和逻辑
绘制,包含4种情况,有覆盖,无覆盖,插旗,还有插错旗;
逻辑则包含鼠标左键点开和右键点击插旗,判断胜利或失败等等
首先,创建一个装载顶部数据的二维数组DATA_TOP,-1是无覆盖,也就是翻开之后,0是覆盖,也就是初始状态,1表示插旗,2表示插错旗
top后缀为gif

创建一个顶层地图类MapTop,并在此中写入覆盖(图片)、插旗(图片)、插错旗()图片的循环,top等于零时是覆盖,然后在GameUtil中载入图片。
在Game中new一个MapTop,并在画笔中调用, 完成后发现窗口会有频闪
9.双缓存技术:用双缓存技术来解决频闪的问题
在Game中创建一个组件(Image类型),初始值为空,然后再绘制当中初始化这个组件,宽高和窗口大小一样,在其中设置一个画笔,调用,将底层元素和顶层元素都绘制在这个画布中;最后再添加一个绘制方法,将画布绘制到窗口当中,
10.鼠标事件:点击鼠标左键,覆盖处变为无覆盖,点击右键,覆盖处插旗。顶层的元素都是基于鼠标的操作来变化的
在Game当中设置一个接收鼠标的方法,(launch方法下);addmouse,选择鼠标点击事件,使系统可以接收鼠标信号,点击左键,信号给定为“1",右键,信号给定为”2“。
然后再Gamutil中设置参数,在逻辑中需要给定初始值,否则会一直指向鼠标最后点击的这个区域,给定坐标X,Y。静态boolean LEFT、RIGHT,默认为false
信号返回Game,再写一个判断,就收信号后,读取此时鼠标的坐标,然后像鼠标相应键位状态修改为true;
再在MapTop当中设置一个接收,编写一个逻辑,成功捕捉鼠标信号,释放相应键位状态
在paintSelf当中调用这个逻辑
11.左键翻开:首先要在MapTop当中接收到鼠标坐标,并且将坐标值规范为雷区中对应格子的位置,定义两个参数来存放,然后在逻辑当中将鼠标的坐标转化为这个格子的位置(横坐标长度减去偏移量,再除以格子的边长,纵坐标同理)
添加一个if判断,鼠标只有在雷区才有左右键的判断
12.递归翻开:点击到空白区触发的事件,在MapTop当中添加一个翻开空格的方法,spaceOpen(),传入两个参数。如果底层为空,则直接打开周围3*3的区域
递归,结束条件一定要明确,无法停止的递归会造成溢出现象
添加一个内存的判断(格子没有被打开),那么再打开这个格子并且在调用自己,这样被打开的空格就不会再次被递归打开了 *由于雷区未显示的那一圈都是顶层覆盖,并且底层为空,那一圈的格子在满足递归条件后再次遍历33后,就会出现数组越界的情况, 为了防止数组越界需要再添加一个设定:必须在雷区当中,>=1
双重for循环,,当点击空白格子时,系统自动翻开周围3
3的格子,但还不够,当自动翻开的格子中还含有空格时,就需要对该空格处也调用改方法,直到周围全是数字,就需要用到递归
13.右键插旗:如果目标是覆盖区域则插旗,插旗的意义是玩家认为该格子的底层是雷,如果目标是插旗区域则目标变为无覆盖,目标是无覆盖区域,则判断周围旗子数量是否和底层数字相同,如果相同,则翻开所有非旗格子
方法与左键翻开无异。
14.右键翻开:首先创建一个方法numOpen,传入两个参数,横纵坐标,首先需要保证点击的格子是数字,然后需要统计该格子周围旗子的数量(双重for循环:),需要做一个统计,设置一个局部变量来记录旗子数,退出循环:如果旗子数量等于数字,那么就证明该格子周围的雷已经全部被判断出来了,然后翻开周围未插旗的格子
还是双重for循环,if没有插旗,那么翻开,如果翻开的位置是空格,那么还需要进行递归打开周围的格子,还得保证在雷区内
15.失败判定:设置一个失败判定的方法,boolean类型,首先遍历整个雷区,如果底层为雷,顶层无覆盖,则失败。返回状态“true”(表示失败),其他条件是“false”(表示未失败),在logic当中调用
再创建方法seeBoom,即再失败后显示雷区当中所有的雷,遍历整个雷区,寻找雷的位置,如果顶层状态为覆盖,则将其顶层状态设为未覆盖,如果顶层状态是旗子,则不用理,继续循环寻找下一个雷。 如果底层不是雷,顶层是旗,则显示插错旗。
16.胜利判断:添加一个胜利判断的方法,设置一个局部变量(初始值是零)用来统计未打开格子数。遍历雷区,如果顶层不是被打开,变量加一,如果被打开格子数、等于雷数,那么游戏胜利。
然后将未翻开的区域全部变为旗,同时返回一个true,其他情况是false,在logic当中调用方法。
17.游戏状态:在GameUtil中添加一个游戏状态,初始值设置为0,1表示胜利,2表示失败,不同的界面有不同的显示
载入代表不同状态的图片
然后是绘制,有三种状态,所以用switch语句,加载不同的图片,然后再MapTop当中修改相应的数据
18.游戏重置:任何状态下只要点击笑脸就能重置游戏,游戏重置就是重置游戏的各个元素
添加一个方法用来重置游戏,首先是重置雷区中的元素,将DATA_BOTTOM中元素全部重置为零,然后需要重新生成雷和数字
再设置鼠标事件,首先对应不同的状态,鼠标有不同的事件,添加switch语句,分别对应三种情况。
19.数字添加:在界面左上角和右上角添加剩余雷数和所用时间。
剩余雷数:setColor设计颜色、setFout字体样式(仿宋,加粗,长度30),打印字符串(剩余雷数:雷的总数减去插旗数量;坐标:横坐标一个偏移量;纵坐标两个偏移量),
还要在GameUtil中设置一个插旗数量的整形变量(初始值设置为零),

drawWord(){
g.setColor(Color.red);
g.setFont(new Font(“仿宋”,Font.BOLD,30));//字体样式
g.drawString(“剩余雷数” + (GameUtil.RAY_MAX - GameUtil.FLAG_NUM), GameUtil.OFFSET, 2 * GameUtil.OFFSET);//打印字符串
}

倒计时:需要设置两个参数,开始时间,结束时间,具体方法和剩余雷数相同,只需改动数据

20.难度选择:首先要绘制一个新界面来完成该功能,不同难度会得到不同的雷区参数:雷数,雷区宽,雷区高。
先设计一个参数,代表难度

新建一个类,绘制窗口
再写一个方法,判断是否点击到这个区域
再设置一个重置参数的方法
在Game中new一个选择类,再单独绘制这个窗口,当start=3时,开始游戏
再是鼠标事件,先设置一个参数,用来传递鼠标状态信息(初始值未false),在状态三的时候,如果点击鼠标左键,并判断是否开始游戏
再设置一个方法,如果变量返回值是true,则载入游戏:首先释放状态,再载入相对应的参数,接下来重新加载Game类,用来新建一个窗口,重新加载时间,重新开始游戏,调用launch方法(该方法再重绘之后时时刻刻都在调用)。
最后是绘制方法,如果游戏状态时3,那么绘制选择的东西,给窗口设置一个颜色并填充,
由于数组大小再定义后不可更改,所以在选择难度时会发生数组越界问题,为解决数组越界问题,调整一下参数GameUtil,将雷的初始值定为100,宽设置为36,高为17,

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值