Java实现贪吃蛇小游戏

运行效果如下:
在这里插入图片描述
新建Yard类

import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
 * 这个类代表贪吃蛇的活动场所
 *
 * @author wuranghao
 * @version 1.0
 */
public class Yard extends Frame {
    public static void main(String[] args) {
        Yard y = new Yard();
        y.beginTime = System.currentTimeMillis();
        y.launch();
    }
    /*
     * 画图的线程
     * */
    PaintThread paintThread = new PaintThread();
    private boolean gameOver = false; //游戏是否结束
    /*
     * 行数
     */
    public static final int ROWS = 30;
    /*
     * 列数
     * */
    public static final int COLS = 30;
    /*
     * 活动区域大小
     * */
    public static final int BLOCK_SIZE = 15;
    //设置显示字属性
    private Font fontGameOver = new Font("宋体", Font.BOLD, 50);
    //分数
    private int score = 0;
    /*
     * 记录开始时候的时间
     * */
    private long beginTime = 0;
    /*
     *实例化Snake和Egg的对象
     * */
    Snake s = new Snake(this);
    Egg e = new Egg();
    /*
     * 抽象类 Image 是表示图形图像的所有类的超类。
     * 必须以特定于平台的方式获取图像。
     * */
    Image offScreenImage = null;
    /*
     * 此函数的功能是:设置窗口的大小、位置、可见,以及点击事件和键盘事件,最后开启了绘图线程
     * */
    public void launch() {
        /*
         * 指定窗口的位置,窗口的左上角的位置为(90,10).是相对父窗口的相对位置
         * */
        this.setLocation(90, 10);
        /*
         * 设定窗口的大小
         * 宽度width:COLS*BLOCK_SIZE
         * 高度hight:ROWS*BLOCK_SIZE
         * */
        this.setSize(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        /*
         * 为窗口添加监听器
         * */
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        /*
         * 将窗口设置可见
         * */
        this.setVisible(true);
        /*
         * 为窗口添加键盘事件
         * */
        this.addKeyListener(new KeyMonitor());
        new Thread(paintThread).start();
    }
    /*
     * 将变量gameOver设置为true,使得在paint()函数中将使得线程停止
     * */
    public void stop() {
        gameOver = true;
    }
    @Override
    public void paint(Graphics g) {
        /*
         * 获取此图形上下文的颜色
         * */
        Color c = g.getColor();
        /*
         * 指定图形上下文的颜色
         * */
        g.setColor(Color.GRAY);
        /*
         * 用当前的颜色来填充指定的区域
         * */
        g.fillRect(0, 0, COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        /*
         * 再一次的指定颜色为:黑灰色???????为什么要再一次的设定???
         * 原因在于:我们想将绘图的颜色与文字显示的不一样
         * */
        g.setColor(Color.DARK_GRAY);
        //画出横线
        /*
         * drawLine(int x1, int y1, int x2, int y2)
         *  函数的功能为:
         * 在此图形上下文的坐标系中,使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。
         * 通过下面的两个for循环就会在图形化对象上画出表格
         * */
        for (int i = 1; i < ROWS; i++) {
            g.drawLine(0, BLOCK_SIZE * i, COLS * BLOCK_SIZE, BLOCK_SIZE * i);
        }
        for (int i = 1; i < COLS; i++) {
            g.drawLine(BLOCK_SIZE * i, 0, BLOCK_SIZE * i, BLOCK_SIZE * ROWS);
        }
        g.setColor(Color.YELLOW);//设定颜色,为下面显示文字信息做准备
        /*
         * drawString(String str, int x, int y)
         *使用此图形上下文的当前字体和颜色绘制由指定 string 给定的文本。
         * */
        g.drawString("使用说明:使用方向键控制方向,F1--停止,F2--停止后恢复,F5--重新开始", 10, 40);
        g.drawString("目前分数:" + score, 10, 60);
        g.drawString("加分规则:每吃一个加5分,加油!", 10, 80);
        g.drawString("已经使用的时间:" + (System.currentTimeMillis() - beginTime) / 1000, 10, 100);
        /*
         * 检测游戏是否结束,当游戏结束时,则提示游戏“game over”,而且将界面恢复到初始界面的状态,且终止绘图线程
         * */
        if (gameOver) {
            g.setFont(fontGameOver);
            g.drawString("game over!", 90, 170);
            g.drawString("在玩一次,请按F5", 10, 250);
            g.drawString(" ", 200, 230);//???这个用意何在??

            paintThread.pause();
        }
        if (score > 100) {
            g.drawString("好棒!!!", 90, 170);
            g.drawString("你已经超过" + score + ",继续加油", 10, 230);
        }
        /*
         * 将图形界面设置为刚开始的颜色
         * */
        g.setColor(c);
        s.eat(e);
        e.draw(g);
        s.draw(g);
    }
    @Override
    public void update(Graphics g) {
        if (offScreenImage == null) {
            /*
             * public Image createImage(int width,
             *           int height)
             * 创建一幅用于双缓冲的、可在屏幕外绘制的图像
             * */
            offScreenImage = this.createImage(COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
        }
        /*
         * getGraphics()
         *创建供绘制闭屏图像(off-screen image)使用的图形上下文。
         * */
        Graphics gOff = offScreenImage.getGraphics();
        paint(gOff);
        /*
         * drawImage(Image img, int x, int y, ImageObserver observer)
         *      绘制指定图像中当前可用的图像。
         * */
        g.drawImage(offScreenImage, 0, 0, null);
    }
    private class PaintThread implements Runnable {
        private boolean running = true;
        private boolean pause = false;
        public void run() {
            while (running) {//线程将一直处于运行当中,只有在游戏结束的时候
                if (pause) continue;
                else repaint();//如果组件是轻量级组件,则会尽快调用paint()方法或者是调用update()

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public void pause() {
            this.pause = true;
        }
        public void reStart() {
            this.pause = false;
            s = new Snake(Yard.this);
            gameOver = false;
            score = 0;
        }
        public void gameOver() {
            running = false;
        }
    }
    /*
     * 此函数的功能为:检测我们是否按下F2,若按下,则重新启动线程,即重新开始游戏
     * */
    private class KeyMonitor extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            int key = e.getKeyCode();
            if (key == KeyEvent.VK_F5) {
                paintThread.reStart();//重新开始游戏
            } else if (key == KeyEvent.VK_F1) {
                paintThread.pause = true;//暂停
            } else if (key == KeyEvent.VK_F2) {
                paintThread.pause = false;//从暂停中恢复
            }
            s.keyPressed(e);
        }
    }
    /**
     * 拿到所得的分数
     *
     * @return 分数
     */
    public int getScore() {
        return score;
    }
    /**
     * 设置所得的分数
     *
     * @param score 分数
     */
    public void setScore(int score) {
        this.score = score;
    }
}

新建Snake类

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

/**
 * 代表蛇
 * @author wuranghao
 *
 */
public class Snake {
    /*
     * 头结点
     * */
    private Node head = null;
    /*
     * 尾结点
     * */
    private Node tail = null;
    /*
     * 大小
     * */
    private int size = 0;
    /*
     * 开始游戏时:
     * 初始位置:(20,30)
     * 初始运动方向:Dir.L
     * */
    private Node n = new Node(20, 30, Dir.L);
    /*
     * Yard的对象属性;
     * */
    private Yard y;
    /*
     * 构造函数
     * */
    public Snake(Yard y) {
        /*
         * 将初始结点给头结点和尾结点,size初始化为 1,
         * */
        head = n;
        tail = n;
        size = 1;
        this.y = y;
    }
    /*
     * 节点类
     * */
    private class Node {
        int w = Yard.BLOCK_SIZE;
        int h = Yard.BLOCK_SIZE;
        int row , col;
        Dir dir = Dir.L;
        Node next = null;
        Node prev = null;
        Node(int row, int col, Dir dir) {
            this.row = row;
            this.col = col;
            this.dir = dir;
        }
        void draw(Graphics g) {
            Color c = g.getColor();
            g.setColor(Color.BLACK);
            g.fillRect(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h);
            g.setColor(c);
        }
    }
    // 从尾加,代码与下面一个函数的代码功能相似,这里不再分析
    public void addToTail() {
        Node node = null;
        switch(tail.dir) {
            case L :
                node = new Node(tail.row, tail.col + 1, tail.dir);
                break;
            case U :
                node = new Node(tail.row + 1, tail.col, tail.dir);
                break;
            case R :
                node = new Node(tail.row, tail.col - 1, tail.dir);
                break;
            case D :
                node = new Node(tail.row - 1, tail.col, tail.dir);
                break;
        }
        tail.next = node;
        node.prev = tail;
        tail = node;
        size ++;
    }
    // 从头加,下面的代码比较简单,相信大家应该都能理解
    public void addToHead() {
        Node node = null;
        switch(head.dir) {
            case L :
                node = new Node(head.row, head.col - 1, head.dir);
                break;
            case U :
                node = new Node(head.row - 1, head.col, head.dir);
                break;
            case R :
                node = new Node(head.row, head.col + 1, head.dir);
                break;
            case D :
                node = new Node(head.row + 1, head.col, head.dir);
                break;
        }
        head.prev=node;
        node.next=head;
        head=node;
        size ++;
    }
    public void draw(Graphics g) {
        if(size <= 0) return;
        move();
        for(Node n = head; n != null; n = n.next) {
            n.draw(g);
        }
    }
    /*
     * 移动过程所要做的操作:在运动方向增加一个节点,在尾部减去一个节点,并且检测是否已经死亡
     * */
    private void move() {
        addToHead();
        deleteFromTail();
        checkDead();
    }
    private void checkDead() {
        if(head.row < 2 || head.col < 0 || head.row > Yard.ROWS || head.col > Yard.COLS)  {
            y.stop();
        }
        /*
         * 头节点与身体的某一个节点相撞,也标志着结束
         * */
        for(Node n = head.next; n != null; n = n.next) {
            if(head.row == n.row && head.col == n.col) {
                y.stop();
            }
        }
    }
    /*
     * 删除尾节点
     * */
    private void deleteFromTail() {
        if(size == 0) return;
        tail = tail.prev;
        tail.next = null;

    }
    public void eat(Egg e) {
        /*
         * boolean intersects(Rectangle r)
         *  确定此 Rectangle 是否与指定的 Rectangle 相交。
         * 若相交,表示我们吃到了一个点 ,则导致蛇的长度变长并且在出现一个点,并且加5分,否则什么也不做
         * */
        if(this.getRect().intersects(e.getRect())) {
            e.reAppear();
            this.addToHead();
            //吃了加5分
            y.setScore(y.getScore() + 5);
        }
    }
    private Rectangle getRect() {
        /*
         * 构造了一个格子大小的区域
         * */
        return new Rectangle(Yard.BLOCK_SIZE * head.col, Yard.BLOCK_SIZE * head.row, head.w, head.h);
    }
    /*
     * 接收从键盘的按键事件,然后采取相应的解决方案
     * */
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        switch(key) {
            case KeyEvent.VK_LEFT  :
                /*
                 * 当按键为左的时候,只要前进方向不是右,即可转向
                 * */
                if(head.dir != Dir.R)
                    head.dir = Dir.L;
                break;
            case KeyEvent.VK_UP  :
                /*
                 * 当按键为"上",只要前进方向不是"下",就可以转向
                 * */
                if(head.dir != Dir.D)
                    head.dir = Dir.U;
                break;
            case KeyEvent.VK_RIGHT  :
                /*
                 * 当按键为"右"的时候,只要前进方向不是"左",就可以转向
                 * */
                if(head.dir != Dir.L)
                    head.dir = Dir.R;
                break;
            case KeyEvent.VK_DOWN :
                /*
                 * 当按键为"下"的时候,只要前进方向不是"上",就可以转向
                 * */
                if(head.dir != Dir.U)
                    head.dir = Dir.D;
                break;
        }
    }
}

新建Egg类

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;

//鸡蛋

public class Egg {
    int row, col;
    int w = Yard.BLOCK_SIZE;
    int h = Yard.BLOCK_SIZE;
    //初始位置产生随机数
    private static Random r = new Random();
    //颜色
    private Color color = Color.PINK;
    //运动时的位置
    public Egg(int row, int col) {
        this.row = row;
        this.col = col;
    }
    //初始时在位置
    public Egg() {
        this(r.nextInt(Yard.ROWS-2) + 2, r.nextInt(Yard.COLS));
    }
    public void reAppear() {
        this.row = r.nextInt(Yard.ROWS-2) + 2;
        this.col = r.nextInt(Yard.COLS);
    }
    public Rectangle getRect() {
        return new Rectangle(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h);
    }
    public void draw(Graphics g) {
        Color c = g.getColor();
        g.setColor(color);
        g.fillOval(Yard.BLOCK_SIZE * col, Yard.BLOCK_SIZE * row, w, h);
        g.setColor(c);
        if(color == Color.GREEN) color = Color.RED;
        else color = Color.GREEN;
    }
    public int getCol() {
        return col;
    }
    public void setCol(int col) {
        this.col = col;
    }
    public int getRow() {
        return row;
    }
    public void setRow(int row) {
        this.row = row;
    }
}

新建Dir类

/**
 * 代表蛇的运行方向
 * @author wuranghao
 *
 */
public enum Dir {
    L, U, R, D
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

500警告

打赏两块钱,帮我买包咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值