潜艇大战
一、超类SeaObject(派生类有,战舰、敌舰、水雷、炸弹)
我这里面将一些属性进行了私有化处理,并且提供了get及set方法进行访问,
提高了程序安全性,还将一些代码继续进行简化处理
package javabean;
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public abstract class SeaObject {
//声明常量生死
public static final int LIVE = 0;
public static final int DEAD = 1;
//自身状态
private int state = LIVE;
//尺寸、位置、速度
private int width;
private int height;
private int x;
private int y;
private int speed;
//敌舰构造器
public SeaObject(int width, int height) {
this.width = width;
this.height = height;
x = -width;
Random random = new Random();
y = random.nextInt(World.HEIGHT - 150 - height + 1) + 150;
speed = random.nextInt(3) + 1;
}
//战舰、水雷、炸弹构造器
public SeaObject(int width, int height, int x, int y, int speed) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speed = speed;
}
//方法
//get及set方法
public int getWidth() {return width;}
public int getHeight() {return height;}
public int getX() {return x;}
public void setX(int x) {this.x = x;}
public int getY() {return y;}
public void setY(int y) {this.y = y;}
public int getSpeed() {return speed;}
//移动 抽象方法
public abstract void move();
//获取图片 抽象方法
public abstract ImageIcon getImage();
//判断死活
public boolean isLive() {
return state == LIVE;
}
//让对象去死
public void goDead() {
state = DEAD;
}
//画对象 g画笔,如果对象还活着则将其图片画出来
public void paint(Graphics g) {
if (isLive()) {
this.getImage().paintIcon(null, g, x, y);
}
}
//越界检测 抽象方法
public boolean isOutOfBounds() {
return this.x >= World.WIDTH;
}
//击中检测
public boolean isHit(SeaObject other) {
//this是潜艇战舰,other是水雷、炸弹
int x1 = this.x - other.width;
int x2 = this.x + this.width;
int y1 = this.y - other.height;
int y2 = this.y + this.height;
int x = other.x;
int y = other.y;
return (x >= x1 && x <= x2) && (y >= y1 && y <= y2);
}
}
二、战舰BattleShip
package javabean;
import javax.swing.*;
public class Battleship extends SeaObject {
//私有化生命、分数
private int life;
private int score;
//构造器
public Battleship() {
super(66, 26, 270, 124, 40);
life = 5;
}
//私有属性life及score的get及set方法
//返回战舰的生命,显示在游戏界面上
public int getLife() {return life;}
//战舰击中了水雷潜艇获得生命,或者被水雷击中减命
//这里的life形参可以为负值
public void setLife(int life) {this.life += life;}
//返回战舰的分数,显示在游戏界面
public int getScore() {return score;}
//打掉其他潜艇获得分数
public void setScore(int score) {this.score += score;}
//重写move,由于战舰的移动需要侦听器去检测键盘
//所以就在这里重写一下,去除继承来的抽象方法,防止报错
//然后在下面重载move方法
@Override
public void move(){};
//左右移动
//这里是对move的重载
public void move(boolean flag) {
if (flag) {
setX(getX() + getSpeed());//右移
} else {
setX(getX() - getSpeed());//左移
}
}
@Override//重写getImage,返回战舰图片
public ImageIcon getImage() {
return Images.battleship;
}
//战舰发射炸弹:炸弹初始化的位置为战舰所在位置
public Bomb shootBomb() {
//战舰的x 加上自身的宽度的二分之一,战舰的y加上自身的高度,就是炸弹出现的位置
return new Bomb(this.getX() + this.getWidth() / 2, this.getY() + this.getHeight());
}
//战舰状态
public boolean isLive() {
return life > 0;
}
}
三、敌舰
1.水雷潜艇Minesubmarine
package javabean;
import javax.swing.*;
public class MineSubmarine extends SeaObject implements EnemyLife{
//构造器
public MineSubmarine() {
super(63, 19);
}
@Override//重写move
//潜艇只能向着x递增的方向前进
public void move() {
//setX这些方法可以看SeaObject类的定义
setX(getX() + getSpeed());
}
@Override//重写getImage
//返回水雷潜艇的图片
public ImageIcon getImage() {
return Images.minesubm;
}
//发射水雷
public Mine shootMine() {
return new Mine(getX(), getY());
}
@Override//获得生命
public int getLifes() {
//若水雷潜艇被击中则返回1,赋予战舰
return 1;
}
}
2.鱼雷潜艇Totpesubmarine
package javabean;
import javax.swing.*;
public class TorpeSubmarine extends SeaObject implements EnemyScore{
//构造器
public TorpeSubmarine() {
super(64, 20);
}
@Override//重写move
public void move() {
setX(getX() + getSpeed());
}
@Override//重写getImage
public ImageIcon getImage() {
return Images.torpesubm;
}
@Override//重写接口的方法战舰获得分数
public int getScores() {
return 200;
}
}
3.侦察潜艇Obsersubmarine
package javabean;
import javax.swing.*;
public class ObserSubmarine extends SeaObject implements EnemyScore{
//构造器
public ObserSubmarine() {
super(63, 19);
}
@Override//重写move
//潜艇向着x递增的方向移动
public void move() {
setX(getX() + getSpeed());
}
@Override//重写getImage
public ImageIcon getImage() {
return Images.obsersubm;
}
@Override
public int getScores() {
return 100;
}
}
四、水雷Mine
package javabean;
import javax.swing.*;
public class Mine extends SeaObject{
//构造器
public Mine(int x, int y) {
super(11, 11, x, y, 1);
}
@Override//重写move
//水雷向着y递减的方向移动
public void move() {
setY(getY() - getSpeed());
}
@Override//重写getImage
public ImageIcon getImage() {
return Images.mine;
}
@Override//重写isOutOfBounds
//返回boolean类型,当水雷的超出了海平面,150是海平面的y
public boolean isOutOfBounds() {
return getY() <= 150 - getHeight();
}
}
五、炸弹Bomb
package javabean;
import javax.swing.*;
public class Bomb extends SeaObject{
//构造体
public Bomb(int x, int y) {
super(9, 12, x, y, 12);
}
@Override//重写move
//炸弹只能向着y增加的方向移动
public void move() {
setY(getY() + getSpeed());
}
@Override//重写getImage
public ImageIcon getImage() {
return Images.bomb;
}
@Override//重写越界检测
public boolean isOutOfBounds() {
//同一个包不需要import导包
return getY() >= World.HEIGHT;
}
}
六、图片类Images
package javabean;
import javax.swing.*;
import java.awt.*;
/**
* @Description: 照片类,专门存放图片
* @Author: addict
* @Date: 2022-10-17 17:00
* @Version: 1.8
*/
public class Images {
//方法一
//这里使用常量声明加赋值,注意常量声明的同时一定要赋值否则报错
public static final ImageIcon sea = new ImageIcon("img/sea.png");
public static final ImageIcon battleship = new ImageIcon("img/battleship.png");
public static final ImageIcon bomb = new ImageIcon("img/bomb.png");
public static final ImageIcon gameover = new ImageIcon("img/gameover.png");
public static final ImageIcon mine = new ImageIcon("img/mine.png");
public static final ImageIcon minesubm = new ImageIcon("img/minesubm.png");
public static final ImageIcon obsersubm = new ImageIcon("img/obsersubm.png");
public static final ImageIcon torpesubm = new ImageIcon("img/torpesubm.png");
public static final ImageIcon sea1 = new ImageIcon("img/sea1.png");
//main用来检测图片的读取是否有误,输出8无误,其余数字有误
public static void main(String[] args) {
System.out.println(sea.getImageLoadStatus());
System.out.println(battleship.getImageLoadStatus());
System.out.println(bomb.getImageLoadStatus());
System.out.println(minesubm.getImageLoadStatus());
System.out.println(mine.getImageLoadStatus());
System.out.println(torpesubm.getImageLoadStatus());
System.out.println(obsersubm.getImageLoadStatus());
System.out.println(gameover.getImageLoadStatus());
}
}
七、运行类World
package javabean;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class World extends JPanel {
//由于常量经常被调用,所以声明为public
//常量定义标准:1.经常使用
// 2.不再变化
public static final int WIDTH = 641;
public static final int HEIGHT = 479;
//创建游戏状态常量
public static final int RUNNING = 1;
public static final int GAME_OVER = 0;
//私有化游戏状态
private int state = RUNNING;
//创建私有化对象
private Battleship ship = new Battleship();
private SeaObject[] subms = {};//这样进行数组初始化,这时数组为空,啥也没有,一个元素都不存在!!!
private Bomb[] bombs = {};
private Mine[] mines = {};
//随机生成潜艇,私有化仅自己使用
private SeaObject getSubms() {
Random random = new Random();
int num = random.nextInt(20);
if (num < 10) {
return new MineSubmarine();
} else if (num < 16) {
return new ObserSubmarine();
} else {
return new TorpeSubmarine();
}
}
//潜艇数组元素赋值
private int submsIndex = 0;
private void submsAction() {
submsIndex++;
if (submsIndex % 50 == 0) {
//这里生成潜艇然后放进数组中
SeaObject obj = getSubms();
subms = Arrays.copyOf(subms, subms.length + 1);//扩容
subms[subms.length - 1] = obj;
}
}
//水雷数组元素赋值
private int minesIndex = 0;
private void minesAction() {
minesIndex++;
if (minesIndex % 100 == 0) {
for (int i = 0; i < subms.length; i++) {
//这里检测subms[i]是否是由MineSubmrine类向上造型得到
if (subms[i] instanceof MineSubmarine) {
//true的话,就强制将subm[i]转换成MineSubmrine(这里不进行判断就强转的话,编译不报错,运行报错)
//强转是为了调用Minesubmrine类的shootMine方法去发射水雷
//使用向上造型赋值的变量说到底还是,只能调用超类中的属性、方法,但是当这个方法被new出来的对象重写的话
// 就会调用被重写之后的方法
MineSubmarine obj = (MineSubmarine)subms[i];
mines = Arrays.copyOf(mines, mines.length + 1);
mines[mines.length - 1] = obj.shootMine();
}
}
}
}
//将死亡或者越界的角色从数组中删除,其余的正常移动
//这里不可以使用传入SeaObject数组类的方式(别想省代码了)!!!!!
//形参与实参不是一个东西,
private void moveAction(){ //每10毫秒走一次
for(int i=0;i<subms.length;i++){ //遍历所有潜艇
if(subms[i].isOutOfBounds() || !subms[i].isLive()){ //若越界了,或者,是DEAD状态的
subms[i] = subms[subms.length-1]; //将越界元素替换为最后一个元素
subms = Arrays.copyOf(subms,subms.length-1); //缩容
} else {
subms[i].move(); //潜艇移动
}
}
for(int i=0;i<mines.length;i++){ //遍历所有水雷
if(mines[i].isOutOfBounds() || !mines[i].isLive()){ //若越界了,或者,是DEAD状态的
mines[i] = mines[mines.length-1]; //将越界元素替换为最后一个元素
mines = Arrays.copyOf(mines,mines.length-1); //缩容
} else {
mines[i].move(); //水雷移动
}
}
for(int i=0;i<bombs.length;i++){ //遍历所有炸弹
if(bombs[i].isOutOfBounds() || !bombs[i].isLive()){ //若越界了,或者,是DEAD状态的
bombs[i] = bombs[bombs.length-1]; //将越界元素替换为最后一个元素
bombs = Arrays.copyOf(bombs,bombs.length-1); //缩容
} else {
bombs[i].move(); //炸弹移动
}
}
}
//炸弹击中潜艇
private void bombBangAction() {
for (int i = 0; i < bombs.length; i++) {
Bomb bomb = bombs[i];
//这里先检测炸弹是否还活着,false的话就不再向下执行浪费时间
for (int j = 0; j < subms.length; j++) {
SeaObject obj = subms[j];
//这里检测潜艇是否还活着,且被炸弹击中
if (obj.isLive() && obj.isHit(bomb)) {
//true的话让他们去死
bomb.goDead();
obj.goDead();
if (obj instanceof EnemyLife) {//被击中潜艇是命
EnemyLife el = (EnemyLife)obj;//强转为命的接口
ship.setLife(el.getLifes());//玩家获得一条狗命
} else {
EnemyScore es = (EnemyScore)obj;
ship.setScore(es.getScores());
}
}
}
}
}
//水雷击中战舰
private void mineBangAction() {
for (int i = 0; i < mines.length; i++) {
Mine mine = mines[i];
//这里的isHit传入的是SeaObject类,直接传入Mine类,有个向上造型的过程
//也可以这样
//SeaObject obj = mines[i];
if (mine.isLive() && ship.isLive() && ship.isHit(mine)) {
mine.goDead();
ship.setLife(-1);//被击中之后战舰的setLife方法直接传入-1
}
}
}
//检测游戏是否应该结束
private void checkGameOver() {
if (ship.getLife() == 0) {
state = GAME_OVER;
}
}
//启动程序
private void action() {
//侦听器
//这里声明了一个World的匿名内部类
KeyAdapter k = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {//按键的存储在Java中是16进制
Bomb bomb = ship.shootBomb();
bombs = Arrays.copyOf(bombs, bombs.length + 1);
bombs[bombs.length - 1] = bomb;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
ship.move(false);
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
ship.move(true);
}
}
};
this.addKeyListener(k);
Timer timer = new Timer();
//方法内不允许权限修饰符哦
//private int interval = 10;
int interval = 1;
//这里也同样定义了一个匿名内部类哦
timer.schedule(new TimerTask() {
@Override
public void run() {
moveAction();
submsAction();
minesAction();
bombBangAction();
mineBangAction();
checkGameOver();
repaint();
}
}, interval, interval);
}
@Override//重写paint,这个方法是一层一层的画上去的,最先画的在最底层,一层一层覆盖 g是画笔
public void paint(Graphics g) {
//根据游戏状态进行画面绘制
switch (state) {
case GAME_OVER:
Images.gameover.paintIcon(null, g, 0, 0);
Images.sea1.paintIcon(null, g, 0, 0);
//这里放置一张图片用来将之前的窗口显示生命覆盖,当游戏结束时可以显示生命为0
g.drawString("SCORE: " + ship.getScore(), 200, 50);
g.drawString("LIFE: " + ship.getLife(), 400, 50);
break;
case RUNNING:
//Images.sea是静态成员的调用方式
// 不过我为了节省代码行,将这些图片变成了常量,声明加初始化放在一起
//这里面先画的在底下,后画的将其覆盖
Images.sea.paintIcon(null, g, 0, 0);
//这里不可以这样画潜艇哦
//因为ImageIcon.paintIcon要和上面一样传入四个参数哦
//ship.getImage().paintIcon(g);
ship.paint(g);//这里去调用Battleship的超类SeaObject中的方法
for (int i = 0; i < subms.length; i++) {
subms[i].paint(g);//这里的操作同ship一致哦
}
for (int i = 0; i < bombs.length; i++) {
bombs[i].paint(g);
}
for (int i = 0; i < mines.length; i++) {
mines[i].paint(g);
}
g.drawString("SCORE: " + ship.getScore(), 200, 50);
g.drawString("LIFE: " + ship.getLife(), 400, 50);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
World world = new World();
world.setFocusable(true);//这里控制着游戏界面是否侦听键盘
frame.add(world);
frame.setSize(World.WIDTH + 6, World.HEIGHT + 28);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);//将窗口显示在屏幕中心
frame.setResizable(false);//窗口创建之后不可以再次改变chicun
//将窗口进行显示,这里会自动调用paint
// World没有重写paint的话,会自动调用java自身的paint方法,
//重写之后的话就会调用重写后的paint
frame.setVisible(true);
world.action();
}
}
八、接口Interface
1.EnemyScore
package javabean;
public interface EnemyScore {
int getScores();
}
2.EnemyLife
package javabean;
public interface EnemyLife {
int getLifes();
}
九、附上百度网盘链接