基于JAVA的葫芦娃大战妖精

magipige

发布日期: 2019-03-31 14:49:47 浏览量: 986
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

一、综述

葫芦娃是我国动画界经典IP,与《西游记》一样,经常被人拿来使用。正如六小龄童六老师所说:“戏说不是胡说,改编不是乱编”,葫芦兄弟大战妖精的游戏还是应当遵照动画的原意。

游戏提供简单的GUI界面,帧率高达10帧,提供了简单易用的操作方式,菜单栏选择进行新对局或回放历史对局,当功能选择好后,用户只需按下空格键,程序便会开始进行新对局或播放历史对局。鉴于游戏并不复杂,谁胜谁负一眼就能看出,所以并没有做胜利界面。

二、程序使用手册

1、开始一场新战役

1.1 首先在菜单栏选择该功能

1.2 选择完后,自动摆放阵型,此时按下空格战斗开始

2、回放历史战斗

2.1 首先在菜单栏选择该功能

2.2 在弹出的文件窗口选择要回放的文件,选择完后点击打开并按下空格开始回放。

注意!所有战斗都会被自动保存,保存的文件名根据当前时间设置为年_月_日_时_分_秒.txt(yyyy_MM_dd_HH_mm_ss.txt)

三、架构解析

1、生物逻辑

每个生物都有许多属性,包括位置、攻击力、防御力与血量,这些属性都是根据动画中的相关剧情决定的。生物可以在战场上自由走动,走动方向的确定是根据与该生物之间曼哈顿距离最短的生物所处位置决定的,战场上分为八方:编号为0,1,2,3,4,5,6,7

生物按0~7的顺时针方向探测与自己曼哈顿距离最近的敌对生物,检测到后,会优先沿与该区域相邻的坐标防线前进,例如检测到曼哈顿距离最近的敌对生物在0区域,则首先会判断能否向上走,能则向上走一步,如果上方战场格子上有其他生物,则再判断能否向左走。

这样游戏中的生物们很快就能进行战斗从而分出胜负。战斗时血量在70%以上血条为绿色,40%~60%为黄色,40%以下为红色

  • 葫芦娃🐸

葫芦娃葫芦兄弟阵营的主力输出,具有高攻击力和较高的防御力。

  • 爷爷👴

爷爷年老体衰,攻击力与防御力是所有生物中最低的。

  • 蝎子精🦂

蝎子精作为肉体最强生物,拥有远高(<300%)于其他生物的攻击力、防御力和血量。

  • 蛇精🐍

蛇精攻击力,防御力与血量一般。

  • 小喽啰🕷🦇

攻击力防御力弱,炮灰。

2、战场设计

战场为10*20的矩形,其中左边10*10的矩形是葫芦兄弟阵营(含7个葫芦娃和1个爷爷)的初始摆放处,右侧10*10的矩形是妖精们(含多个小喽啰,1个蝎子精和1个蛇精)的初始摆放处,当用户点击新对局按钮时,双方会在自己的初始摆放处列阵。战场的每个格子都是一个战场格子类引用,如果该格子上有生物,则会使该引用指向一个实例化的战场格子,实例化战场格子中存有该生物的引用,同时该生物自己也存有自己所在战场坐标。

列阵遵循随机原则,葫芦兄弟团结一线,总是摆出一字长蛇阵,共同面对敌人,将葫芦七兄弟按照作业二排序后,随机生成一个葫芦兄弟阵地的坐标,检测从该坐标起向下沿伸(沿x坐标增大的方向),如果能放下,则将葫芦兄弟放置在战场上,否则再次生成随机坐标,重复上述步骤。

爷爷在葫芦兄弟上场后登场,随机生成一个葫芦兄弟阵地的坐标,检测该位置,如果没有葫芦娃,则在此放下爷爷,否则重新生成随机坐标,重复上述操作。

蝎子精带领小喽啰先排阵型。随机选择一个阵型(鹤翼、雁行、衡轭、鱼鳞、方门、偃月和锋矢)后,生成相应数量的蝎子精和小喽啰,按照阵型个体摆放顺序放置在一个向量Vector中,之后随机生成一个妖怪阵营阵地的坐标,检测以该坐标为阵型所占最小矩形的左上角,能否在阵地中放置该阵型,能容纳则摆放该阵型,如果不能则重新生成随即坐标重复上述步骤。

蛇精的放置方式与爷爷相同,不做赘述。

3、并发控制

由于每个生物都是一个独立的个体,而且在现实世界中,每个个体的行为方式也都是不一样的,自然就需要将每个生物视为一个单独的线程,既然有了线程,就涉及到了并发,现在就来详细分析一下本程序中涉及到的并发问题。

3.1 javaFX线程与Canvas线程

javaFX线程与Canvas线程分属两个线程,Canvas对于UI的所有操作都应当在写在匿名内部类中。

  1. Platform.runLater(()->{
  2. ...
  3. });

3.2 同步问题

同步问题主要发生在生物在战场中移动时。当生物想要移动时,它首先会检测要移动方向上有没有其他生物阻挡,如果没有,则移动,如果有,则不移动,考虑以下两种情况:1、当生物A检测到临近某格子空,想要移动到该格子,此时另一生物B恰好在A检测完但还没来得及移动时移动到了A想要去的格子上,但A并不知道此事,于是A也移动到了该格子上了,于是含有B的实例化战场格子类便被含有A的实例化战场格子类覆盖掉了。2、当生物A检测到临近格子空,想要移动到改格子,此时另一生物B恰好在A检测完但是还没来得及移动时,想要移动到A的格子上,此时由于A还未移动,所以B认为不能移动,然而实际上A马上就要移开,B是可以移动到A的原位置的。为了解决这两个问题,将检测格子为空和移动到改格子这两个动作整合在一个方法中,并且该方法只允许一个进程进行调用。

  1. //生物直接调用该方法,如果移动成功则返回true,不成功则返回false
  2. public boolean SetBFPosition(int x, int y, Creature t) {
  3. synchronized(BattleFields.class){
  4. if(!isEmpty(x,y))return false;
  5. else {
  6. BFs[x][y]=new BattleField<Creature>(t);
  7. return true;
  8. }
  9. }
  10. }

注意:本文第三部分2中并未写出synchronized关键字

类似的问题还有:1、例如生物A想要移动到某个格子,就在A检测完还未移动时,B对该格子进行攻击,但发现格子为空,攻击落空,但实际上A移动过来被B攻击了。2、生物想要移动到某个格子,就在A检测完还未移动时,B对A进行攻击,A掉血,但是实际上A离开了,B的本次攻击落空。这个问题可以将取得战场某格子生物引用的方法设置为同时只能由一个线程访问解决。

还有一些其他的类似问题,解决方法和原理大都类似,不过多进行赘述。

四、源码

1、依赖关系

凑合看看,实线代表继承自,虚线代表聚集

2、类与方法

  • package java2018.CalabashBrother.application

Main.java

  1. package java2018.CalabashBrother.application;
  2. public class Main extends Application {
  3. //用以指示用户选择的功能
  4. private int play;
  5. //菜单栏的高度,显示战场的时候要整体下移菜单栏高度
  6. static int menuBarHeight = 35;
  7. //功能选择和MainCanvas类的实例化
  8. @Override
  9. public void start(Stage primaryStage);
  10. //整个程序的入口
  11. public static void main(String[] args) {
  12. launch(args);
  13. }
  14. }

MainCanvas.java

  1. package java2018.CalabashBrother.application;
  2. public class MainCanvas extends Canvas{
  3. //葫芦娃阵营人数,归0时战斗结束
  4. private int goodboyCount = 8;
  5. //妖怪阵营人数,归0战斗结束。两阵营人数在新战役开始时都会被初始化,此处-1仅作为判断方便
  6. private int badboyCount = -1;
  7. //指示战斗是否开始,用以忽略战斗过程中用户的空格输入
  8. private boolean begin = false;
  9. //战场
  10. private BattleFields BFs;
  11. //战场的上帝视角
  12. private Director director;
  13. private GraphicsContext gc;
  14. //保存文件的路径
  15. private String fileName;
  16. //指示战役是否结束
  17. private boolean battleOver;
  18. //指示当前功能是新战役还是回放
  19. int play;
  20. //写文件器,在接收到保存文件路径时初始化
  21. private FileWriter writer;
  22. //读文件器,在接收到读入文件路径时初始化
  23. private BufferedReader reader;
  24. //线程池
  25. ExecutorService exec = Executors.newCachedThreadPool();
  26. //菜单栏高度
  27. static int menuBarHeight = 35;
  28. //存有各类图片资源。背景及各类人物图像
  29. static Image BG;
  30. static Image CB1 = null;
  31. static Image CB2 = null;
  32. static Image CB3 = null;
  33. static Image CB4 = null;
  34. static Image CB5 = null;
  35. static Image CB6 = null;
  36. static Image CB7 = null;
  37. static Image GP = null;
  38. static Image LL = null;
  39. static Image SC = null;
  40. static Image SN = null;
  41. static {//加载图片
  42. try {
  43. BG = new Image(new File("resource/background.jpg").toURI().toURL().toString());
  44. CB1 = new Image(new File("resource/1.jpg").toURI().toURL().toString());
  45. CB2 = new Image(new File("resource/2.jpg").toURI().toURL().toString());
  46. CB3 = new Image(new File("resource/3.jpg").toURI().toURL().toString());
  47. CB4 = new Image(new File("resource/4.jpg").toURI().toURL().toString());
  48. CB5 = new Image(new File("resource/5.jpg").toURI().toURL().toString());
  49. CB6 = new Image(new File("resource/6.jpg").toURI().toURL().toString());
  50. CB7 = new Image(new File("resource/7.jpg").toURI().toURL().toString());
  51. GP = new Image(new File("resource/Grandpa.jpg").toURI().toURL().toString());
  52. LL = new Image(new File("resource/LouLuo.jpg").toURI().toURL().toString());
  53. SC = new Image(new File("resource/Scopion.jpg").toURI().toURL().toString());
  54. SN = new Image(new File("resource/Snake.jpg").toURI().toURL().toString());
  55. }catch(Exception e) {
  56. e.printStackTrace();
  57. }
  58. }
  59. //MainCanvas类本身也是一个线程,不断刷新UI
  60. private Thread thread = new Thread(new Runnable() {
  61. @Override
  62. public void run();
  63. });
  64. //构造函数,初始化
  65. public MainCanvas();
  66. //用户按下空格后,开始进行新战役或回放
  67. public void flashBegin();
  68. //绘制背景及格子
  69. public void drawBackground();
  70. //新战役,用以重新初始化各种属性和准备新线程
  71. public void newWar();
  72. //新战役中,画出当前战场情况
  73. public void draw();
  74. //战斗是否结束
  75. public boolean isBattleOver();
  76. //用户选择新战役并按下空格后会调用该方法,用以开始所有线程
  77. public void creatureThreadRun();
  78. //用户选择功能时会调用此方法设置功能
  79. public void setPlay(int play);
  80. //用户选择回放时会传入文件路径,此方法用以接收文件路径
  81. public void setFileName(String fileName);
  82. //回放一张画面
  83. public void replay();
  84. //将此时的战场情况写入文件
  85. public void saveBattleField();
  86. }
  • package Beings

Beings.java

  1. package java2018.CalabashBrother.Beings;
  2. //生物的父类
  3. public class Beings {
  4. //坐标
  5. protected int x, y;
  6. //移动
  7. public void MoveToPos(int x, int y)
  8. }

Creature.java

  1. //本类是葫芦娃、爷爷、蝎子精、蛇精、小喽啰的父类,是Beings的派生类
  2. package java2018.CalabashBrother.Beings;
  3. public class Creature extends Beings implements Runnable{
  4. //攻击力Combat Effectiveness
  5. protected int CE;
  6. //防御力Defence
  7. protected int DEF;
  8. //血量Health Point
  9. protected int HP;
  10. //初始血量
  11. protected int fullHP;
  12. //存活状态
  13. protected boolean livingStatus;
  14. //取攻击力
  15. public int getCE(int radio);
  16. //取防御力
  17. public int getDEF();
  18. //取血量
  19. public int getHP();
  20. //取初始血量
  21. public int getFullHP();
  22. //取存活状态
  23. public boolean isLiving();
  24. //攻击
  25. public void Attack(Creature attackedBeing,int radio);
  26. //while循环,循环条件是该生物存活,循环体中生物不断移动并对周围敌对生物发起攻击。线程每执行一个循环都要随机sleep一到两秒
  27. public void run();
  28. }

Goodboy.java

  1. //葫芦娃和爷爷的父类,用以敌对阵营的鉴别
  2. package java2018.CalabashBrother.Beings;
  3. public class Goodboy extends Creature{
  4. Goodboy(int CE, int DEF, int HP){
  5. super(CE,DEF,HP);
  6. }
  7. Goodboy(int CE, int DEF, int HP, boolean Motivated){
  8. super(CE,DEF,HP,Motivated);
  9. }
  10. Goodboy(int CE, int DEF, int HP, boolean Motivated, BattleFields BFs){
  11. super(CE,DEF,HP,Motivated,BFs);
  12. }
  13. }

Badboy.java

  1. //小喽啰、蛇精和蝎子精的父类,用以敌对阵营的鉴别
  2. package java2018.CalabashBrother.Beings;
  3. public class Badboy extends Creature{
  4. Badboy(int CE, int DEF, int HP){
  5. super(CE,DEF,HP);
  6. }
  7. Badboy(int CE, int DEF, int HP, boolean Motivated){
  8. super(CE,DEF,HP,Motivated);
  9. }
  10. Badboy(int CE, int DEF, int HP, boolean Motivated, BattleFields BFs){
  11. super(CE,DEF,HP,Motivated,BFs);
  12. }
  13. }

NameAndColor.java

  1. //用enum类型将葫芦娃排行与颜色绑定
  2. package java2018.CalabashBrother.Beings;
  3. public enum NameAndColor {//
  4. RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE;
  5. String[] NAME = {"老大", "老二", "老三", "老四", "老五", "老六", "老七"};
  6. String[] COLOR = {"赤", "橙", "黄", "绿", "青", "蓝", "紫"};
  7. //取名字
  8. String getName() {
  9. return this.NAME[ordinal()];
  10. }
  11. //取颜色
  12. String getColor() {
  13. return this.COLOR[ordinal()];
  14. }
  15. }

CalaBashBrother.java

  1. package java2018.CalabashBrother.Beings;
  2. public class CalabashBrother extends Creature{
  3. private NameAndColor nc;
  4. //默认构造函数
  5. CalabashBrother();
  6. //带参构造函数
  7. CalabashBrother(NameAndColor NC);
  8. CalabashBrother(int x, int y);
  9. CalabashBrother(int x, int y, NameAndColor NC);
  10. //取名字
  11. public String getName();
  12. //取颜色
  13. public String getColor();
  14. //设置颜色和名字,对应上面的enum类型
  15. public void setNameAndColor(int index);
  16. //比较函数,用于作业二排序
  17. public int compareTo(CalabashBrother brother);
  18. }

CalabashBrothers.java

  1. package Beings;
  2. public class CalabashBrothers {
  3. //七个葫芦兄弟
  4. private CalabashBrother[] calabashbrothers;
  5. //默认构造函数
  6. public CalabashBrothers();//设置位置
  7. public void SetCBPostion(int index, int x, int y);
  8. //作业二排序交换位置
  9. public void SwapBrother(int index1, int index2);
  10. //取index位置的葫芦娃
  11. public CalabashBrother getBrother(int index);
  12. //取index位置的葫芦娃的名字
  13. public String getName(int index);
  14. //取index位置的葫芦娃名字
  15. public String getColor(int index);
  16. //作业二输出当前葫芦兄弟队的情况
  17. public void QueueNameStatus();
  18. public void QueueColorStatus();
  19. //作业二打乱葫芦兄弟队伍
  20. public void Disorder();
  21. }

GrandPa.java

  1. package Beings;
  2. public class GrandPa extends Creature{
  3. //构造函数
  4. public GrandPa();
  5. }

Scorpion.java

  1. package Beings;
  2. //蝎子精
  3. public class Scorpion extends Creature{
  4. //构造函数
  5. public Scorpion();
  6. }

Snake.java

  1. package Beings;
  2. public class Snake extends Creature{
  3. //构造函数
  4. public Snake();
  5. }

LouLuo.java

  1. package Beings;
  2. public class LouLuo extends Creature{
  3. //构造函数
  4. public LouLuo();
  5. }
  • package java2018.CalabashBrother.BattleField

BattleField.java

  1. //泛型,可存放所有物体
  2. package java2018.CalabashBrother.BattleField;
  3. import Beings.*;
  4. public class BattleField<T extends Beings> {
  5. //本块场地中的物体
  6. private T Being;
  7. //默认构造函数
  8. BattleField();
  9. //带参构造函数
  10. BattleField(T t);
  11. //查看格子是否为空
  12. public boolean isEmpty();
  13. //取得本格子上的物体
  14. public T getBeing();
  15. //把格子上的物体移走
  16. public T removeBeing();
  17. }

BattleFields.java
注意:本类中大部分方法不需要检测某位置是否为空就可以直接调用,为空或者越界时返回null

  1. package java2018.CalabashBrother.BattleField;
  2. import java.util.*;
  3. import Beings.*;
  4. public class BattleFields {
  5. //战场为M*N的矩形
  6. //场地长
  7. private int M;
  8. //场地宽
  9. private int N;
  10. //场地
  11. private BattleField BFs[][];
  12. //默认构造函数
  13. public BattleFields();
  14. //带参构造函数
  15. BattleFields(int m, int n);
  16. //初始化场地。实际上就是将所有格子都赋空指针
  17. public void
  18. initializeBattleField();
  19. //作业三,场地能否容纳阵型
  20. public boolean Containable(int x, int y, int length, int height);
  21. //将某位置清空
  22. public void remove(int x, int y);
  23. //生物要移动时,调用该方法寻找方向
  24. public int findDirection(Creature c,int x, int y);
  25. //战场(x,y)位置是否空。本方法在下一个方法中被调用
  26. public boolean isEmpty(int x, int y);
  27. //在战场(x,y)位置放置物体t
  28. public boolean SetBFPosition(int x, int y, Beings t);
  29. //取得战场(x,y)位置生物阵营
  30. public String creatureType(int x, int y);
  31. //取得战场(x,y)位置生物具体名字
  32. public String CBName(int x, int y);
  33. //取得战场(x,y)位置生物引用。位置空返回null
  34. public Creature getCreature(int x, int y);
  35. //命令行输出战场情况
  36. public void BFOutput();
  37. }
  • package java2018.CalabashBrother.Director

Director.java

  1. package java2018.CalabashBrother.Director;
  2. public class Director {
  3. //战场
  4. private BattleFields BFs;
  5. //葫芦兄弟们。这个对象实际上已经没有用了,是作业二的遗产代码
  6. private CalabashBrothers CBs;
  7. //默认构造函数
  8. Director();
  9. //带参构造函数
  10. Director(BattleFields bfs);
  11. //带参构造函数
  12. Director(BattleFields bfs, CalabashBrothers cbs);
  13. //在某位置摆放。两个重载,第一个是摆放爷爷和蛇精,第二个是摆放葫芦兄弟和蝎子精与小喽啰们,他们储存在Vector向量中
  14. public boolean setFormation(int x, int y, int biasY, Creature t);
  15. public boolean setFormation(int x, int y, int biasY, String formationName,Vector<Creature>list);
  16. //输出名字状态。两个重载
  17. public void QueueNameStatus();
  18. public void QueueNameStatus(CalabashBrothers calabashbrothers);
  19. //输出颜色状态。两个重载
  20. public void QueueColorStatus();
  21. public void QueueColorStatus(CalabashBrothers calabashbrothers);
  22. //冒泡排序。两个重载
  23. public void BubbleSort();
  24. public static void BubbleSort(CalabashBrothers calabashbrothers);
  25. //快速排序。两个重载
  26. private int Partition(int p, int r);
  27. public void QuickSort(int p, int r);
  28. private static int Partition(CalabashBrothers calabashbrothers, int p, int r);
  29. public static void QuickSort(CalabashBrothers calabashbrothers, int p, int r);
  30. //将葫芦娃的顺序打乱
  31. public void Disorder();
  32. public CalabashBrother getBrother(int index);
  33. //列阵
  34. public void setPos();
  35. //显示战场情况
  36. public void showBF();
  37. //清空战场
  38. public void clearBattleField();
  39. }
  • package java2018.CalabashBrother.Randomnum
  1. package java2018.CalabashBrother.Randomnum;
  2. public class Randomnum {
  3. //Random类。只在第一次初始化
  4. static Random r = new Random();
  5. //产生一个在[0,upline)范围内的随机整数
  6. public static int getRandom(int upLine);
  7. }

五、其他

我在这门课里面,主要的收获是学习并实践了并发,并且在另一门课里面使用到了并发排序。

写大作业的过程很痛苦,但是总算是做了一个勉强能运行的程序。我发现一个程序的完成,不仅难在核心算法(本程序中是并发),更难在整体设计和细节完善上,很多我预想的功能最终都没有实现。很多小功能尽管乍眼一看不难,但是把这个功能添加进去却要经过编码和调试的两个过程,还要考虑怎么与已有数据结构和代码结构进行兼容。
路漫漫其修远兮,吾将上下而求索。

上传的附件 cloud_download 基于java的葫芦娃大战妖精.zip ( 64.00mb, 3次下载 )
error_outline 下载需要11点积分

发送私信

生活不会因为你是女孩子就善待你

11
文章数
15
评论数
最近文章
eject