贪吃蛇小游戏java实现代码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010412719/article/details/46746343

贪吃蛇小游戏java实现代码分析

贪吃蛇的小游戏,网上的代码比较多,今天周五,在教研室没啥事做,在电脑中发现了一个贪吃蛇的小游戏,于是就看了下实现的源码,发现别人写的代码确实挺好的,自己也是边加注释边进行理解的去看别人实现的游戏源码,发现还是挺有意思的。自己花了一个下午的时间看了源码,也加了一点小小的功能,于是,不写篇博客觉得对不起自己也,哈哈哈。

此游戏代码的思路非常的清晰,也相当好理解,没有太多难的地方,不过有很多值得学习的地方,因为,这份源码中,对java.awt包中的很多类的很多方法都进行了应用,值得我们去学习,去深刻的理解

注释写的非常非常详细,相信只要学了一点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 {
    /*
     * 画图的线程
     * */
    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();
    }

    public static void main(String[] args) {
        Yard y=new Yard();
        y.beginTime=System.currentTimeMillis();
        y.launch();
    }
    /*
     * 将变量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;
        }
//      node.next = head;
//      head.prev = node;
//      head = node;
        /*
         * 上面这种写法与下面这种写法一致,不过下面这种写法我更容易理解
         * */
        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);
        /*
         * public abstract void fillOval(int x,
                              int y,
                              int width,
                              int height)使用当前颜色填充外接指定矩形框的椭圆。
         * */
        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
}

说明

源码是很久以前下载的,因此不能提供源码链接,但是,在此,对源码提供者表示衷心的感谢,谢谢原作者的给力分享。

猜你喜欢

转载自blog.csdn.net/u010412719/article/details/46746343