深海大战


文章目录

  • 深海大战
  • 游戏规则
  • 所参与的角色:
  • 角色间的关系:
  • 常量类 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();
    }
}