Java坦克大战06

8.IO流应用01

坦克大战6.0版

增加功能:

  1. 防止敌人坦克重叠运动
  2. 记录玩家的成绩(累计击毁坦克数),存盘退出
  3. 记录当时的敌人坦克坐标,存盘退出
  4. 玩游戏时,可以选择是开新游戏还是继续上局游戏

8.1防止敌人坦克重叠运动

8.1.1思路分析

按照目标坦克的向右下左四种情况分析,每一种情况又分为两种小情况,一种八种情况。

8.1.2代码实现

8.1.2.1修改处1

在EnemyTank类中:

  1. 增加了增加一个成员属性,EnemyTank 可以得到敌人坦克成员的 Vector,用于循环比较是否重叠
  2. 新增一个方法setEnemyTanks,可以将MyPanel的成员 Vector enemyTanks = new Vector<>() 设置到Enemy 的成员enemyTank
  3. 编写方法isTouchEnemyTank(),判断当前敌人坦克是否和enemyTanks中的其他坦克发生了重叠或碰撞
  4. 在run方法中,在根据坦克的方法来继续移动的判断条件中,调用isTouchEnemyTank方法,如果返回值不为true,则可以继续运行
package li.TankGame.version06;

import java.util.Vector;

public class EnemyTank extends Tank implements Runnable {

    //在敌人坦克类使用Vector保存多个shot
Vector<Shot> shots = new Vector<>();
boolean isLive = true;
//增加一个成员,EnemyTank 可以得到敌人坦克成员的Vector,用于循环比较是否重叠
Vector<EnemyTank> enemyTanks = new Vector<>(); public EnemyTank(int x, int y) {
super(x, y);
} //这里提供一个方法,可以将MyPanel的成员 Vector<EnemyTank> enemyTanks = new Vector<>();
// 设置到Enemy 的成员enemyTank
public void setEnemyTanks(Vector<EnemyTank> enemyTanks) {
this.enemyTanks = enemyTanks;
} /**
* 编写方法,判断当前敌人坦克是否和enemyTanks中的其他坦克发生了重叠或碰撞
* 思路:读取一个坦克的坐标,在前进之前先将该坦克坐标与当前所有坦克的坐标依次比较,
* 如果到达某个坦克的边缘则不能再前进,则更改方向前进
*/
public boolean isTouchEnemyTank() {
//判断当前敌人坦克(this)方向
switch (this.getDirect()) {
case 0://向上
//让当前的this敌人坦克和其他所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//Vector 中取出一个敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下方向
/**
* 情况1.this坦克向上,如果敌人的坦克是上/下方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+40]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+60]
* */
if (enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2) {
//this坦克的左上角的坐标(this.getX(),this.getY())
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;//如果进入左上角了该范围,说明发生了碰撞,返回true
}
//this坦克的右上角的坐标(this.getX()+40,this.getY())
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;//如果右上角进入了该范围,说明发生了碰撞,返回true
} } //如果敌人坦克是右/左方向
/**
* 情况2.this坦克向上,如果敌人的坦克是右/左方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+60]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+40]
* */
if (enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3) {
//this坦克的左上角的坐标(this.getX(),this.getY())
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;//如果进入左上角了该范围,说明发生了碰撞,返回true
} //this坦克的右上角的坐标(this.getX()+40,this.getY())
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;//如果右上角进入了该范围,说明发生了碰撞,返回true
} }
}
}
break;
case 1://向右
//让当前的this敌人坦克和其他所有的敌人坦克比较
for (int i = 0; i < enemyTanks.size(); i++) {
//Vector 中取出一个敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下方向
/**
* 情况3.this坦克向右,如果敌人的坦克是上/下方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+40]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+60]
* */
if (enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2) {
//this坦克的右上角的坐标(this.getX()+60,this.getY())
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;//如果进入右上角了该范围,说明发生了碰撞,返回true
}
//this坦克的右下角的坐标(this.getX()+60,this.getY()+40)
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;//如果右下角进入了该范围,说明发生了碰撞,返回true
} } //如果敌人坦克是右/左方向
/**
* 情况4.this坦克向右,如果敌人的坦克是右/左方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+60]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+40]
* */
if (enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3) {
//this坦克的右上角的坐标(this.getX()+60,this.getY())
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;//如果进入右上角了该范围,说明发生了碰撞,返回true
} //this坦克的右下角的坐标(this.getX()+60,this.getY()+40)
if (this.getX() + 60 >= enemyTank.getX()
&& this.getX() + 60 <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;//如果右下角进入了该范围,说明发生了碰撞,返回true
}
}
}
}
break;
case 2://向下
for (int i = 0; i < enemyTanks.size(); i++) {
//Vector 中取出一个敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下方向
/**
* 情况5.this坦克向下,如果敌人的坦克是上/下方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+40]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+60]
* */
if (enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2) {
//this坦克的左下角的坐标(this.getX(),this.getY()+60)
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;//如果进入左下角了该范围,说明发生了碰撞,返回true
}
//this坦克的右下角的坐标(this.getX()+40,this.getY()+60)
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 40
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 60) {
return true;//如果右下角进入了该范围,说明发生了碰撞,返回true
} } //如果敌人坦克是右/左方向
/**
* 情况6.this坦克向下,如果敌人的坦克是右/左方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+60]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+40]
* */
if (enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3) {
//this坦克的左下角的坐标(this.getX(),this.getY()+60)
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;//如果进入左下角了该范围,说明发生了碰撞,返回true
} //this坦克的右下角的坐标(this.getX()+40,this.getY()+60)
if (this.getX() + 40 >= enemyTank.getX()
&& this.getX() + 40 <= enemyTank.getX() + 60
&& this.getY() + 60 >= enemyTank.getY()
&& this.getY() + 60 <= enemyTank.getY() + 40) {
return true;//如果右下角进入了该范围,说明发生了碰撞,返回true
}
}
}
}
break;
case 3://向左
for (int i = 0; i < enemyTanks.size(); i++) {
//Vector 中取出一个敌人坦克
EnemyTank enemyTank = enemyTanks.get(i);
//不和自己比较
if (enemyTank != this) {
//如果敌人坦克是上/下方向
/**
* 情况7.this坦克向左,如果敌人的坦克是上/下方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+40]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+60]
* */
if (enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2) {
//this坦克的左上角的坐标(this.getX(),this.getY())
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 60) {
return true;//如果进入左上角了该范围,说明发生了碰撞,返回true
}
//this坦克的左下角的坐标(this.getX(),this.getY()+40)
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 40
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 60) {
return true;//如果左下角进入了该范围,说明发生了碰撞,返回true
} } //如果敌人坦克是右/左方向
/**
* 情况8.this坦克向左,如果敌人的坦克是右/左方向
* 那么敌人坦克x的范围是[enemyTank.getX(),enemyTank.getX()+60]
* 敌人坦克y的范围是[enemyTank.getY(),enemyTank.getY()+40]
* */
if (enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3) {
//this坦克的左上角的坐标(this.getX(),this.getY())
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() >= enemyTank.getY()
&& this.getY() <= enemyTank.getY() + 40) {
return true;//如果进入左下角了该范围,说明发生了碰撞,返回true
} //this坦克的左下角的坐标(this.getX(),this.getY()+40)
if (this.getX() >= enemyTank.getX()
&& this.getX() <= enemyTank.getX() + 60
&& this.getY() + 40 >= enemyTank.getY()
&& this.getY() + 40 <= enemyTank.getY() + 40) {
return true;//如果右下角进入了该范围,说明发生了碰撞,返回true
}
}
}
}
break;
}
return false;
} @Override
public void run() {
while (true) { //这我们先判断当前的坦克是否存活
// 在判断shots.size<3是否真,为真,说明当前的3颗子弹已经消亡了,
// 就创建一颗子弹,放到shots集合中,并启动线程
if (isLive && (shots.size() < 3)) {//可以通过控制数字来修改敌人坦克一次发射几颗子弹
Shot s = null;
//判断坦克的方创建对应的子弹
switch (getDirect()) {
case 0://向上
s = new Shot(getX() + 20, getY(), 0);
break;
case 1://向右
s = new Shot(getX() + 60, getY() + 20, 1);
break;
case 2://向下
s = new Shot(getX() + 20, getY() + 60, 2);
break;
case 3://向左
s = new Shot(getX(), getY() + 20, 3);
break;
}
shots.add(s);
new Thread(s).start();
} //根据坦克的方法来继续移动
switch (getDirect()) {
case 0://上
//让坦克保持一个方向走50步
for (int i = 0; i < 50; i++) {
if (getY() > 0 && !isTouchEnemyTank()) {
moveUp();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 1://右
//让坦克保持一个方向走50步
for (int i = 0; i < 50; i++) {
if (getX() + 60 < 700 && !isTouchEnemyTank()) {//700为面板宽度
moveRight();//走一步
}
try {
Thread.sleep(50);//每走一步就休眠50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 2://下
for (int i = 0; i < 50; i++) {
if (getY() + 60 < 550 && !isTouchEnemyTank()) {//550为面板宽度
moveDown();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case 3://左
for (int i = 0; i < 50; i++) {
if (getX() > 0 && !isTouchEnemyTank()) {
moveLeft();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
//随机地改变坦克的方向 0-3
setDirect((int) (Math.random() * 4));//[0,4)的取整
//如果被击中了,就退出线程
if (!isLive) {
break;//退出线程
}
}
}
}
8.1.2.2修改处2

在MyPanel类中的MyPanel()方法,初始化敌人坦克时,将enemyTanks集合设置给 enemyTank

//将enemyTanks集合设置给 enemyTank
enemyTank.setEnemyTanks(enemyTanks);

可以看到,两个坦克移动到相邻的时候不再发生碰撞/重叠:

8.2记录玩家的成绩,存盘退出

8.2.1思路

创建Recorder类,记录我方击毁敌方坦克的数量。

8.2.2代码实现

8.2.2.1修改处1

在MyPanel类中的paint方法前增加一个方法,用来显示信息

/**
* 编写方法,显示我方击毁敌方坦克的信息
*/
public void showInfo(Graphics g) {
//画出 玩家的总成绩
g.setColor(Color.BLACK);//设置画笔颜色
Font font = new Font("宋体", Font.BOLD, 20);
g.setFont(font);
g.drawString("您累计击毁敌方坦克", 730, 30);//画出“您累计击毁敌方坦克”的字样
drawTank(760, 55, g, 0, 0);//画一个敌方坦克图案
g.setColor(Color.BLACK);//重新设置画笔颜色
g.drawString(Recorder.getAllEnemyTankNum()+"", 850, 95); }
8.2.2.2修改处2

在paint方法中调用showInfo方法:

8.2.2.3修改处3

新增Recorder类:

package li.TankGame.version06;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException; /**
* @author 李
* @version 6.0
* 该类用于记录相关信息,和文件交互
*/
public class Recorder {
//定义变量,记录我方击毁敌人坦克数
private static int allEnemyTankNum = 0;
//定义IO对象,用于写入到文件中
private static BufferedWriter bw = null;//处理流
private static String recordFile = "d:\\myRecord.txt";//记录文件的路径 public static int getAllEnemyTankNum() {
return allEnemyTankNum;
} public static void setAllEnemyTankNum(int allEnemyTankNum) {
Recorder.allEnemyTankNum = allEnemyTankNum;
} //当我方击毁一辆敌人坦克时,就应该allEnemyTankNum++
public static void addAllEnemyTankNum() {
Recorder.allEnemyTankNum++;
} //增加一个方法,当游戏退出时,将allEnemyTankNum保存到myRecord.txt文件中
public static void keepRecord() {
try {
bw = new BufferedWriter(new FileWriter(recordFile));
bw.write(allEnemyTankNum + "\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
8.2.2.4修改处4

在TankGame06类中的构造器TankGame06方法中,增加相应关闭窗口的处理(在关闭窗口时进行数据存储)

public TankGame06(){
mp = new MyPanel();
//将mp放入到Thread,并启动
Thread thread = new Thread(mp);
thread.start();
this.add(mp);//把面板(就是游戏的绘图区域)添加进来
this.setSize(950,600);//设置大小
this.addKeyListener(mp);//让JFrame监听mp的键盘事件
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击窗口的叉时停止运行
this.setVisible(true);//设置显示 //在JFrame中增加相应关闭窗口的处理
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
Recorder.keepRecord();
System.exit(0);
}
}); }


PS:这里在MyPanel类中的hitTank方法中,发现之前子弹击中敌坦克的判断条件有误,产生了坦克向左边打其他坦克会瞬间爆炸的bug

修正250行、268行后:

day06-IO流应用01的更多相关文章

  1. Java基础知识强化之IO流笔记01:异常的概述和分类

     IO流操作的时候会出现很多问题,java中叫作异常,所以我们先介绍一下异常: 1. 程序的异常:Throwable(Throwable类是java中所有异常或错误的超类) (1)严重问题:Error ...

  2. java——IO流01

    移动文件有一种简单方法,不需要复制文件再删除文件. package com.unir.test01; import java.io.File; import java.io.IOException; ...

  3. IO流(字节流复制)01

    package ioDemo; import java.io.*; /** * IO流(字节流复制) * Created by lcj on 2017/11/2. */ public class bu ...

  4. 01 语言基础+高级:1-8 File类与IO流_day10【缓冲流、转换流、序列化流】

    day10[缓冲流.转换流.序列化流] 主要内容 缓冲流 转换流 序列化流 打印流 教学目标 能够使用字节缓冲流读取数据到程序 能够使用字节缓冲流写出数据到文件 能够明确字符缓冲流的作用和基本用法 能 ...

  5. 01 语言基础+高级:1-8 File类与IO流_day09【字节流、字符流】

    day09[字节流.字符流] 主要内容 IO流 字节流 字符流 异常处理 Properties 教学目标 能够说出IO流的分类和功能 能够使用字节输出流写出数据到文件 能够使用字节输入流读取数据到程序 ...

  6. java IO流详解

    流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...

  7. IO流01--毕向东JAVA基础教程视频学习笔记

    提要 01 IO流(BufferedWriter)02 IO流(BufferedReader)03 IO流(通过缓冲区复制文本文件)04 IO流(readLine的原理)05 IO流(MyBuffer ...

  8. Java IO流详尽解析

    流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输 ...

  9. 【转载】JAVA IO 流的总结

    来自http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html,写的很详细 Java流操作有关的类或接口: Java流类图结构: 流的概念和 ...

  10. JavaSE_ IO流 总目录(19~22)

    JavaSE学习总结第19天_IO流119.01 集合的特点和数据结构总结19.02 如何选择使用哪种集合19.03 集合常见功能和遍历方式总结19.04 异常的概述和分类19.05 JVM默认处理异 ...

随机推荐

  1. 【C语言深度解剖】一篇解决程序的环境【编译+链接详解】让面试官给我们竖起大拇指

    文章目录 程序的翻译环境 翻译环境详解 编译 预编译 编译 汇编 关于形成符号表 链接 运行环境 尾声 [C语言深度解剖][Linux操作系统]程序的环境[编译+链接详解] 那么这里博主先安利一下一些 ...

  2. js 实现call和apply方法,超详细思路分析

    壹 ❀ 引 我在 五种绑定策略彻底弄懂this 一文中,我们提到call,apply,bind属于显示绑定,这三个方法都能直接修改this指向.其中call与apply比较特殊,它们在修改this的同 ...

  3. OCR 02: Tesseract-OCR

    Catalog OCR 01: EasyOCR OCR 02: Tesseract-OCR OCR 03: PaddleOCR Project Host And Brief Official Site ...

  4. 【Unity3D】UGUI之布局组件

    1 概述 ​ 布局组件主要有:水平布局(HorizontalLayoutGroup).垂直布局(VerticalLayoutGroup).网格布局(GridLayoutGroup),用于约束子控件的布 ...

  5. 【Unity3D】UGUI概述

    1 UGUI 与 GUI 区别 ​ GUI控件 在编译时不能可视化,并且界面不太美观,在实际应用中使用的较少.UGUI 在编译时可视化,界面美观,实际应用较广泛. 2 Canvas 渲染模式(Rend ...

  6. Laravel入坑指南(7)——中间件Middleware

    Laravel框架中引入了"中间件"这个概念,笔者觉得不是太合适.这里的Middleware和Java Servlet中的过滤器(Filter)就是一个东西,但是想比之下Filte ...

  7. Git实战系列教程

    介绍 本文详细记录了Git一系列核心概念和工作中常用的操作命令,通篇以实际出发拒绝过度理论,值得典藏:). 概念 版本管理系统 版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的 ...

  8. spring boot整合dubbo

    本项目通过模拟卖票和买票模块来讲解spring boot如何整合dubbo. 1.搭建zookeeper 使用docker方式: docker pull registry.docker-cn.com/ ...

  9. crontab采坑总结

    目录 crontab环境变量 脚本缺少执行权限 crontab是Linux平台实现定时任务的服务工具,通常情况下该服务会预装在发行版中,直接使用即可. 关于crontab的详细用法参考:https:/ ...

  10. 从零开始写 Docker(二)---优化:使用匿名管道传递参数

    本文为从零开始写 Docker 系列第二篇,主要在 mydocker run 命令基础上优化参数传递方式,改为使用 runC 同款的匿名管道传递参数. 如果你对云原生技术充满好奇,想要深入了解更多相关 ...