java课程设计
1. 团队课程设计博客链接
https://www.cnblogs.com/choco1ate/p/12172223.html
2.本组课题及本人任务
本组课题:泡泡堂(炸弹人)游戏
本人任务:Box类(游戏地图中的每个方格)
Bomb类(游戏过程中的)
游戏玩家输赢信息的文件储存
3.需求分析
- Box类:该类为地图Map中的每一个格子,利用该Box设置该位置是空地或者障碍,设置该位置是否有道具和道具类型。
- Bomb类:该类为炸弹类,实现在地图中的某一个Box中放置炸弹、炸弹爆炸及清除、设置爆炸后产生的道具或地雷、地雷爆炸及清除、判断玩家是否被炸弹炸伤、设置爆炸音效等功能。
- 游戏用户输赢信息的储存即排行榜:开始游戏前,从文件读入文件内的用户信息,若无当前用户,则创建一个新的用户,当一个玩家生命为0时,当前用户的失败场次-1,另一用户胜利场次+1,计算胜率,写入文件。排行榜中同样文件读入文件内的用户信息,同过重写compareTo方法对用户的胜率进行排序。
4.总体设计(概要设计)
5.本人负责的主要功能展示与代码分析
(1)Box类
public Box(int x, int y, int n, int index, boolean canDestroy)// *障碍存在* { this.canDestroy = canDestroy; // 设置该box大小、位置 this.setBounds(80 * x, 80 * y, 80, 80); // 根据传入的参数设置图片 setIcon("images/" + n + "/" + index + ".png", this); // 如果该box可以破坏 if (canDestroy) { // 设置该box内藏的宝物 setTreasure(); } } /**不存在*/ public Box(int x, int y) { // 设置该box障碍不存在 isExist = false; // 设置该box大小、位置 this.setBounds(80 * x, 80 * y, 80, 80); // 设置该box处图片为默认的图片(透明) setIcon("images/default.png", this); }
分析:当该Box为障碍物时,根据传入的障碍物编号设置相应图片,当该Box为道路时,设置为透明,显示背景地图。
public void setTreasure() { Random r = new Random(); int isTreasure = r.nextInt(10) + 1; // 是宝物的几率为40% int noTreasure = 6; if (isTreasure > noTreasure) { // 读取一个1-5的随机数 int treasureNum = r.nextInt(6) + 1; switch (treasureNum) { // 速度+1 case 1: treasureIndex = 1; treasurePath = "images/speed+.gif"; break; // 泡泡+1 case 2: treasureIndex = 2; treasurePath = "images/bombnum+.gif"; break; // 威力 case 3: treasureIndex = 3; treasurePath = "images/power+.gif"; break; //无敌 case 4: treasureIndex = 4; treasurePath = "images/invincible.png"; break; //生命值 case 5: treasureIndex = 5; treasurePath = "images/live+.png"; break; //地雷 case 6: treasureIndex = 6; break; default: break; } } }
分析:在每一个存在障碍物的Box中,都有一定几率出现道具,随机生成一个1~10的整数,设置当该随机数>6时(即40%的可能性出现道具)再随机生成一个1~6的随机数,根据已设定的道具编号把当前的Box图片地址设置为相应的道具图片。
(2)Bomb类
玩家扔下炸弹:
public synchronized void dropBoom(Player pl) { // 如果当前位置没有炸弹并且玩家所放炸弹数也没有达到上限,便放置一颗炸弹 if (!box.isExistBomb && player.getBombexist() < player.bombnum) { // 将box处定义炸弹的参数赋值true box.isExistBomb = true; box.needDetonate = true; box.isExistPlayer = true; player.thisbomb = box; // 设置玩家当前炸弹为当前所在box box.boom = this; // 设置当前所在box处的boom值 player.setBombexist(player.getBombexist() + 1); // 玩家当前存在的炸弹数+1 setIcon("images/bomb.gif", box); // 将当前box图片设置为炸弹 MyThread1 ph1 = new MyThread1(); ph1.start(); } }
private class MyThread1 extends Thread { @Override public void run() { try { Thread.sleep(2000); explodeSound(); } catch (InterruptedException e) { e.printStackTrace(); } } }
分析:如果当前位置没有炸弹并且玩家所放炸弹数也没有达到上限,便放置一颗炸弹,将当前Box设为炸弹图片,并开启一个炸弹爆炸线程。
地雷:
public synchronized void hideBoom(Box box1) { box1.isExistBomb = true; box1.needDetonate = true; box1.isExistPlayer = true; box1.boom = this; // 设置当前所在box处的boom值 // setIcon("images/bomb.gif", box1);// 将当前box图片设置为炸弹 MyThread2 ph2 = new MyThread2(box1); ph2.start(); }
private class MyThread2 extends Thread { private Box box1; private MyThread2(Box box1) { this.box1 = box1; } @Override public void run() { try { // 将被清除的路径上的爆炸块恢复成透明块 setIcon("images/default.png", box1); Thread.sleep(2700); // 将当前box图片设置为炸弹 setIcon("images/bomb.gif", box1); Thread.sleep(250); box1.isExistBomb = false; box1.needDetonate = false; // 设置炸弹中心的图片 setIcon("images/UDLR.png", box1); // 判断是否炸到了玩家 explodePlayer(box1); Thread.sleep(300); // 将被清除的路径上的爆炸块恢复成透明块 setIcon("images/default.png", box1); } catch (InterruptedException e) { e.printStackTrace(); } } }
分析:当有障碍的Box爆炸后,有一定几率出现地雷,开启一个地雷爆炸线程,地雷的爆炸时间和威力都比正常的炸弹小。
设置爆炸方法:
public synchronized void explode() throws InterruptedException // 爆炸 { // 将box处定义炸弹的参数赋值false box.isExistBomb = false; box.needDetonate = false; player.setBombexist(player.getBombexist() - 1); // 玩家当前炸弹数-1 setIcon("images/UDLR.png", box); // 设置炸弹中心的图片 explodePlayer(box); // 判断是否炸到了玩家 for (int n = 1; n <= power; n++) // 向上爆炸 { boolean flag = true; int x = this.x; int y = this.y - n; if (y >= 0) { Box temp = GameFrame.thismap.getBoxbyLocation(x, y); flag = explode1(temp); if(flag == false) { break; } } } // 向下爆炸 for (int s = 1; s <= power; s++) { boolean flag = true; int x = this.x; int y = this.y + s; if (y < 12) { Box temp = GameFrame.thismap.getBoxbyLocation(x, y); flag = explode1(temp); if(flag == false) { break; } } } // 向左爆炸 for (int w = 1; w <= power; w++) { boolean flag = true; int x = this.x - w; int y = this.y; if (x >= 0) { Box temp = GameFrame.thismap.getBoxbyLocation(x, y); flag = explode1(temp); if(flag == false) { break; } } } // 向右爆炸 for (int e = 1; e <= power; e++) { boolean flag = true; int x = this.x + e; int y = this.y; if (x < 15) { Box temp = GameFrame.thismap.getBoxbyLocation(x, y); flag = explode1(temp); if(flag == false) { break; } } } } public boolean explode1(Box temp) { if (temp.isExist) // 如果爆炸的路线上遇到方块了 { if (temp.isdestroyshowT) // 如果是宝物 { temp.isExist = false; return true; // 宝物消失 } else // 如果是障碍,则终止爆炸 { showTreasure(temp); // 显示该box处隐藏的宝物 return false; } } else if (temp.isExistBomb) // 如果该爆炸处有炸弹 { MyThread3 ph3 = new MyThread3(temp); ph3.start(); continuousBomb.add(temp); // 将该炸弹添加到被引爆的列表里 } explodePlayer(temp); // 判断是否炸到了玩家 bombCache.add(temp); // 将该处添加到爆炸缓存中 setIcon("images/UD.png", temp); // 设置该处的爆炸图片 return true; }
分析:根据当前玩家的威力,从当前Box分别向上下左右威力内的Box遍历,若遇到不可炸毁的障碍,则结束当前方向的遍历,否则,将遍历的Box设置爆炸。
判断是否炸到玩家:
/** 判断是否炸到玩家 */ public synchronized void explodePlayer(Box b) { if (b.getRect().intersects(GameFrame.player1.getRect()) && !GameFrame.player1.isInvincible() && GameFrame.player1.isalive) // 判断玩家一是否和爆炸块重叠,不处于无敌状态,并且存活 { GameFrame.player1.beInjured();// 玩家一受到1点伤害 } if (b.getRect().intersects(GameFrame.player2.getRect()) && !GameFrame.player2.isInvincible() && GameFrame.player2.isalive) // 判断玩家二是否和爆炸块重叠,不处于无敌状态,并且存活 { GameFrame.player2.beInjured(); // 玩家二受到1点伤害 } }
分析:判断玩家是否和爆炸块重叠,不处于无敌状态,并且存活,若符合,玩家受到1点伤害。
(3)游戏前输入玩家名称以进行胜率排行
分析:创建一个用户User类,包括用户姓名,胜利的场次、失败的场次和胜率,开始游戏前,使用ArrayList从文件读入文件内的用户信息,若无当前用户,则创建一个新的User,创建一个线程监听当前游戏,当一个玩家生命为0时,当前用户的失败场次-1,另一用户胜利场次+1,加入ArrayList,结束时将ArrayList写入文件。排行榜中同样使用ArrayList从文件读入文件内的用户信息,同过重写compareTo方法对用户的胜率进行排序。
测试、改进与感想
测试与改进
(1)在扔炸弹、炸弹爆炸、地雷爆炸方法中改进使用synchronized,当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。 所以若玩家1和玩家2同时使用丢炸弹方法,也不会产生混乱。
(2)读取用户信息时实现实现serializabel接口接口,serializabel接口的作用是就是可以把对象存到字节流,然后可以恢复,方便进行User对象的直接储存和读取。
(3)
测试用户场次信息的储存和读取,读取文件用户信息,游戏前a的胜率为87%,b为25%,令a胜,则a的胜率场次+1,b的sb场次+1,a的胜率增加,b的胜率减少,正确。
感想
通过这次面向对象课程设计,我对于多线程和Serializable 接口的理解和掌握更加深刻,特别是掌握了synchronized关键字的用法,使用synchronized关键字让两个玩家同时丢炸弹和炸弹爆炸时不会产生混乱,而虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。通过实现 java.io.Serializable 接口以启用序列化功能,未实现此接口的类将无法使其任何状态序列化或反序列化。此次课程设计,我们付出了很多的辛苦和精力,但是也学到了很多东西,不仅巩固了以前学过的知识,还学到了很多书本上没有的知识,同时我也懂得了理论与实际相结合的重要性,只有把学的理论知识与实践相结合,才能提高自己的实际动手能力和思考的能力,也深刻体会到了团队间合作的重要性。