深海大战
文章目录
- 深海大战
- 游戏规则
- 所参与的角色:
- 角色间的关系:
- 常量类 Constant
- 超类 BaseSeaObject
- 接口类
- 战舰 Battleship
- 深水炸弹 Bomb
- 侦察潜艇 ObserveSubmarine
- 鱼雷潜艇 TorpedoSubmarine
- 水雷潜艇 MineSubmarine
- 水雷 Mine
- 绘图类 MyPanel
游戏规则
所参与的角色:
战舰、深水炸弹、侦察潜艇、鱼雷潜艇、水雷潜艇、水雷
角色间的关系:
- 战舰发射深水炸弹
- 深水炸弹击打潜艇,打中则对应潜艇,深水炸弹消失,获得相应奖励
- 侦察潜艇:10分
- 鱼雷潜艇:20分
- 水雷潜艇:1条命
- 水雷潜艇可发射水雷
- 水雷击打战舰,打中则战舰减1条命(命为0游戏结束),水雷消失
常量类 Constant
package com.liner.constant;
/**
* @author 一条淡水鱼 ゆ
* @Classname Constant
* @Description 常量存放类
* @Date 2023-03-09
*/
public class Constant {
/**
* 游戏界面宽度
*/
public static final int WINDOW_WIDTH = 641;
/**
* 游戏界面高度
*/
public static final int WINDOW_HEIGHT = 479;
/**
* 游戏状态
*/
public static final int GAME_RUNNING = 1;
public static final int GAME_FAIL = 0;
/**
* 存活状态
*/
public static final int LIVE = 0;
public static final int DEAD = 1;
}
超类 BaseSeaObject
package com.liner.submarine;
import com.liner.constant.Constant;
import javax.swing.*;
import java.awt.*;
import java.util.Random;
/**
* @author 一条淡水鱼 ゆ
* @Classname SeaObject
* @Description 海洋对象超类
* @Date 2023-03-09
*/
public abstract class BaseSeaObject {
/**
* 自身状态
*/
private int state = Constant.LIVE;
/**
* 尺寸、位置、速度
*/
private int width, height;
private int x, y;
private int speed;
/**
* 敌舰构造器
*
* @param width
* @param height
*/
public BaseSeaObject(int width, int height) {
this.width = width;
this.height = height;
x = -width;
Random random = new Random();
y = random.nextInt(Constant.WINDOW_HEIGHT - 150 - height + 1) + 150;
speed = random.nextInt(3) + 1;
}
/**
* 战舰、水雷、炸弹构造器
*
* @param width
* @param height
* @param x
* @param y
* @param speed
*/
public BaseSeaObject(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;
}
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();
/**
* 获取图片 抽象方法
* @return
*/
public abstract ImageIcon getImage();
/**
* 判断对象死活
* @return
*/
public boolean isLive(){
return state == Constant.LIVE;
}
/**
* 对象存活状态设置死亡
*/
public void goDead() {
state = Constant.DEAD;
}
/**
* 对象存活则画出相应图片
* @param g
*/
public void paint(Graphics g){
if (isLive()) {
this.getImage().paintIcon(null,g,x,y);
}
}
/**
* 判断是否越界
* @return
*/
public boolean isOutOfBounds(){
return this.x >= Constant.WINDOW_WIDTH;
}
/**
* 判断是否击中
* @param other 表示水雷、深海炸弹 this 表示舰艇
* @return
*/
public boolean isHit(BaseSeaObject 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);
}
}
接口类
- 获取生命值
package com.liner.gameinterface;
/**
* @author 一条淡水鱼 ゆ
* 获取生命
*/
public interface EnemyLife {
int getLife();
}
- 获取得分
package com.liner.gameinterface;
/**
* @author 一条淡水鱼 ゆ
* 获取得分
*/
public interface EnemyScore {
int getScores();
}
战舰 Battleship
package com.liner.submarine;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname Battleship
* @Description 战舰
* @Date 2023-03-09
*/
public class Battleship extends BaseSeaObject {
/**
* 私有化生命、分数
*/
private int life;
private int score;
/**
* 构造器
*/
public Battleship() {
super(66, 26, 270, 124, 40);
life = 5;
}
/**
* 由于战舰的移动需要侦听器去检测键盘
* 就在这里重写一下,去除继承来的抽象方法,防止报错
* 然后在下面重载move方法
*/
@Override
public void move() {
}
public void move(boolean flag) {
if (flag) {
//右移
setX(getX() + getSpeed());
} else {
//左移
setX(getX() - getSpeed());
}
}
@Override
public ImageIcon getImage() {
return Images.BATTLESHIP;
}
/**
* 战舰发射炸弹:炸弹初始化的位置为战舰所在位置
*
* @return
*/
public Bomb shotBomb() {
//战舰的x 加上自身的宽度的二分之一,战舰的y加上自身的高度,就是炸弹出现的位置
return new Bomb(this.getX() + this.getWidth() / 2, this.getY() + this.getHeight());
}
/**
* 战舰状态
*
* @return
*/
@Override
public boolean isLive() {
return life > 0;
}
/**
* 返回战舰的生命,显示在游戏界面上
*
* @return
*/
public int getLife() {
return life;
}
/**
* 战舰击中了水雷潜艇获得生命,或者被水雷击中减命
* life形参可以为负值
*
* @param life
*/
public void setLife(int life) {
this.life += life;
}
/**
* 返回战舰的分数,显示在游戏界面
*
* @return
*/
public int getScore() {
return score;
}
/**
* 打掉其他潜艇获得分数
*
* @param score
*/
public void setScore(int score) {
this.score += score;
}
}
深水炸弹 Bomb
package com.liner.submarine;
import com.liner.constant.Constant;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname Bomb
* @Description 深水炸弹
* @Date 2023-03-09
*/
public class Bomb extends BaseSeaObject{
public Bomb(int x, int y) {
super(9, 12, x, y, 12);
}
/**
* 深水炸弹向着y增加的方向移动 向下移动
*/
@Override
public void move() {
setY(getY() + getSpeed());
}
@Override
public ImageIcon getImage() {
return Images.BOMB;
}
/**
* 判断是否越界
* 返回boolean类型,炸弹不能超过海底
* @return
*/
@Override
public boolean isOutOfBounds() {
return getY() >= Constant.WINDOW_HEIGHT;
}
}
侦察潜艇 ObserveSubmarine
package com.liner.submarine;
import com.liner.gameinterface.EnemyScore;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname ObserveSubmarine
* @Description 侦察潜艇
* @Date 2023-03-09
*/
public class ObserveSubmarine extends BaseSeaObject implements EnemyScore {
public ObserveSubmarine() {
super(63, 19);
}
/**
* 侦察潜艇x向右移动
*/
@Override
public void move() {
setX(getX() + getSpeed());
}
/**
* 返回侦察潜艇图片
*
* @return
*/
@Override
public ImageIcon getImage() {
return Images.OBSERSUBM;
}
/**
* 击中获得10分
*
* @return
*/
@Override
public int getScores() {
return 10;
}
}
鱼雷潜艇 TorpedoSubmarine
package com.liner.submarine;
import com.liner.gameinterface.EnemyScore;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname TorpedoSubmarine
* @Description 鱼雷潜艇
* @Date 2023-03-09
*/
public class TorpedoSubmarine extends BaseSeaObject implements EnemyScore {
public TorpedoSubmarine() {
super(63, 19);
}
/**
* 鱼雷潜艇x向右移动
*/
@Override
public void move() {
setX(getX() + getSpeed());
}
/**
* 返回鱼雷潜艇图片
*
* @return
*/
@Override
public ImageIcon getImage() {
return Images.TORPESUBM;
}
/**
* 击中获得20分
*
* @return
*/
@Override
public int getScores() {
return 20;
}
}
水雷潜艇 MineSubmarine
package com.liner.submarine;
import com.liner.gameinterface.EnemyLife;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname MineSubmarine
* @Description 水雷潜艇
* @Date 2023-03-09
*/
public class MineSubmarine extends BaseSeaObject implements EnemyLife {
public MineSubmarine() {
super(63, 19);
}
/**
* 水雷潜艇只能向x递增方向前进
*/
@Override
public void move() {
setX(getX() + getSpeed());
}
/**
* 返回水雷潜艇图片
*
* @return
*/
@Override
public ImageIcon getImage() {
return Images.MINESUBM;
}
/**
* 发射水雷
*
* @return
*/
public Mine shotMine() {
return new Mine(getX(), getY());
}
/**
* 击中获得1条生命
*
* @return
*/
@Override
public int getLife() {
//若水雷潜艇被击中则返回1,赋予战舰
return 1;
}
}
水雷 Mine
package com.liner.submarine;
import com.liner.window.Images;
import javax.swing.*;
/**
* @author 一条淡水鱼 ゆ
* @Classname Mine
* @Description 水雷
* @Date 2023-03-09
*/
public class Mine extends BaseSeaObject {
public Mine( int x, int y) {
super(11, 11, x, y, 1);
}
/**
* 水雷向着y递减的方向移动 向上移动
*/
@Override
public void move() {
setY(getY() - getSpeed());
}
@Override
public ImageIcon getImage() {
return Images.MINE;
}
/**
* 判断是否越界
* 返回boolean类型,当水雷超出了海平面,150是海平面的y
* @return
*/
@Override
public boolean isOutOfBounds() {
return getY() <= 150 -getHeight();
}
}
绘图类 MyPanel
package com.liner.window;
import com.liner.constant.Constant;
import com.liner.gameInterface.EnemyLife;
import com.liner.gameInterface.EnemyScore;
import com.liner.submarine.*;
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;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author 一条淡水鱼 ゆ
* @Classname MyPanel
* @Description 绘图类
* @Date 2023-03-09
*/
public class MyPanel extends JPanel {
private static ScheduledExecutorService scheduledExecutor;
/**
* 私有化游戏状态
*/
private int state = Constant.GAME_RUNNING;
/**
* 创建私有化对象
*/
private final Battleship ship = new Battleship();
/**
* 这里进行数组初始化,这时数组为空,啥也没有,一个元素都不存在!!!
*/
private BaseSeaObject[] subms = {};
private Bomb[] bombs = {};
private Mine[] mines = {};
/**
* 随机生成潜艇,私有化仅自己使用
*/
private BaseSeaObject getSubms() {
Random random = new Random();
int num = random.nextInt(20);
if (num < 10) {
return new MineSubmarine();
} else if (num < 16) {
return new ObserveSubmarine();
} else {
return new TorpedoSubmarine();
}
}
/**
* 潜艇数组元素赋值
*/
private int submsIndex = 0;
private void submsAction() {
submsIndex++;
if (submsIndex % 50 == 0) {
//这里生成潜艇然后放进数组中
BaseSeaObject 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类的shotMine方法去发射水雷
使用向上类型转换赋值的变量说到底还是,只能调用超类中的属性、方法,但是当这个方法被new出来的对象重写的话
就会调用被重写之后的方法
*/
MineSubmarine obj = (MineSubmarine) subms[i];
mines = Arrays.copyOf(mines, mines.length + 1);
mines[mines.length - 1] = obj.shotMine();
}
}
}
}
//将死亡或者越界的角色从数组中删除,其余的正常移动
//这里不可以使用传入BaseSeaObject数组类的方式(别想省代码了)!!!!!
//形参与实参不是一个东西,
private void moveAction() {
//每10毫秒走一次
//遍历所有潜艇
for (int i = 0; i < subms.length; i++) {
//若越界了,或者,是DEAD状态的
if (subms[i].isOutOfBounds() || !subms[i].isLive()) {
//将越界元素替换为最后一个元素
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++) {
//若越界了,或者,是DEAD状态的
if (mines[i].isOutOfBounds() || !mines[i].isLive()) {
//将越界元素替换为最后一个元素
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++) {
//若越界了,或者,是DEAD状态的
if (bombs[i].isOutOfBounds() || !bombs[i].isLive()) {
//将越界元素替换为最后一个元素
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++) {
BaseSeaObject 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.getLife());
} 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传入的是BaseSeaObject类,直接传入Mine类,有个向上造型的过程
//也可以这样
//BaseSeaObject obj = mines[i];
if (mine.isLive() && ship.isLive() && ship.isHit(mine)) {
mine.goDead();
//被击中之后战舰的setLife方法直接传入-1
ship.setLife(-1);
}
}
}
/**
* 检测游戏是否应该结束
*/
private void checkGameOver() {
if (ship.getLife() == 0) {
state = Constant.GAME_FAIL;
}
}
/**
* 启动程序
*/
private void action() {
//侦听器
//这里声明了一个匿名内部类
KeyAdapter k = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//按键的存储在Java中是16进制 按下空格发射
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
Bomb bomb = ship.shotBomb();
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);
scheduledExecutor = Executors.newScheduledThreadPool(5);
scheduledExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
moveAction();
submsAction();
minesAction();
bombBangAction();
mineBangAction();
checkGameOver();
repaint();
}
}, 10, 10, TimeUnit.MILLISECONDS);
}
/**
* 重写paint,这个方法是一层一层的画上去的,最先画的在最底层,一层一层覆盖 g是画笔
*
* @param g
*/
@Override
public void paint(Graphics g) {
//根据游戏状态进行画面绘制
switch (state) {
case Constant.GAME_FAIL:
Images.GAME_OVER.paintIcon(null, g, 0, 0);
Images.SKY.paintIcon(null, g, 0, 0);
//这里放置一张图片用来将之前的窗口显示生命覆盖,当游戏结束时可以显示生命为0
g.drawString("SCORE: " + ship.getScore(), 200, 50);
g.drawString("LIFE: " + ship.getLife(), 400, 50);
break;
case Constant.GAME_RUNNING:
//Images.SEA是静态成员的调用方式
//不过我为了节省代码行,将这些图片变成了常量,声明加初始化放在一起
//这里面先画的在底下,后画的将其覆盖
Images.SEA.paintIcon(null, g, 0, 0);
//这里不可以这样画潜艇哦
//因为ImageIcon.paintIcon要和上面一样传入四个参数哦
//ship.getImage().paintIcon(g);
//这里去调用Battleship的超类BaseSeaObject中的方法
ship.paint(g);
for (int i = 0; i < subms.length; i++) {
//这里的操作同ship一致哦
subms[i].paint(g);
}
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);
break;
default:
break;
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
MyPanel myPanel = new MyPanel();
//监听键盘
myPanel.setFocusable(true);
frame.add(myPanel);
frame.setSize(Constant.WINDOW_WIDTH + 6, Constant.WINDOW_HEIGHT + 28);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//将窗口显示在屏幕中心
frame.setLocationRelativeTo(null);
//窗口创建之后不可以再次改变尺寸
frame.setResizable(false);
//将窗口进行显示,这里会自动调用paint
frame.setVisible(true);
myPanel.action();
}
}