贪吃蛇

来源:互联网 发布:网络歌曲视频下载 编辑:程序博客网 时间:2024/04/30 14:06

介绍

相信大家小时候都玩过贪吃蛇,对这很熟悉,所以不多说废话,直接来分析思路。
游戏中主要有这几个类,院子(Yard)、蛇(Snake)、给蛇吃的蛋(Egg)、构成蛇的结点类(Node)。当然,构成蛇结点的类最好作为蛇的内部类,但为了好具体操作,我将构成蛇的结点类没有设置成蛇的内部类。
接下来便是对游戏的功能进行分析。首先,我们得有一个院子,这是最基础的部分,接着得画出一条蛇,让它能够在键盘控制下移动。最后,蛇挂掉之后我们得让游戏画面停止刷新,并提示游戏结束,按相关键后又可以重新游戏。


核心

设计这个游戏的难点就是实现蛇的移动。
这是贪吃蛇向***右***移动,我们可以发现,蛇变动的只有头结点和尾结点,进一步看,蛇的移动相当于将尾结点直接放到了头结点
这是贪吃蛇向***上***移动,我们可以发现,蛇变动的只有头结点和尾结点,进一步看,蛇的移动相当于将尾结点直接放到了头结点
其余情况不一一列举了,综上我们可以发现,蛇的移动相当于将尾结点直接放到了头结点,所以我们可以用一个双向链表来存储蛇的结点,每当蛇移动一格时,我们就先将双向链表的尾结点加到头结点前,成为新的头结点,再删除尾结点,此时的尾结点相当于原来尾结点的前一个结点。又由于蛇吃蛋时需要插入结点到头部或者尾部,移动时必须头插法。所以,我最终选择头插法,当然也可以加上尾插法。


具体过程

  • 第一步:绘制出院子(Yard),主要绘制出有多少行多少列格子,并定义枚举类型变量Direction,分别用来指示四个方向U, D, L, R(上下左右)

  • 第二步:设计结点Node类,写出move方法, draw方法,绘制一个Node结点到屏幕上,并能通过键盘控制一个Node结点在屏幕上移动,其中Node的move方法仅做测试用,第二部完成即可删除。

  • 第三步:设计蛇(Snake)类,最重要的便是头插法建立一个带头结点和带尾结点的双向链表,蛇的移动概括为先将尾结点加到头结点(此时还没删除尾结点),再来删除尾结点,新生成的双向链表的尾结点为原来尾部结点的前一个结点。最后,让蛇在键盘控制下移动。

  • 第四步:设计蛋(Egg)类,让Egg被Snake吃掉后能够随机出现在屏幕上另外一个地方。当然,蛇吃掉蛋后通过头插法增加自己的长度

  • 第五步:设计检测蛇是否死了的方法,一是越界,二是咬到自己。当蛇死了的时候,我们就将线程中repaint的方法挂起,当按F2时,又启用repaint方法,使得游戏重新开始。

  • 第六步:当然是愉快的玩耍,测试bug罗。

是否觉得废话太多,那就直接看代码吧


/** * 用来指示方向的枚举变量,分别为上、下、左、右 * @author lu * */public enum Direction {    U, D, L, R}

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;public class Yard extends Frame {    /**     * 屏幕行数     */    public static final int ROWS = 40;    /**     * 屏幕列数     */    public static final int COLS = 40;    /**     * 屏幕方块宽度     */    public static final int BLOCK_SIZE = 15;    //flag,start用来控制线程中rePaint方法执行    private boolean flag = true;    private boolean start = true;    //记录得分    private int score = 0;    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    //用4个结点初始化蛇    Node node1 = new Node(25, 25, Direction.R);    Node node2 = new Node(30, 30, Direction.U);    Node node3 = new Node(40, 40, Direction.R);    Node node4 = new Node(50, 50, Direction.L);    Snake snake = new Snake(this);    Egg egg = new Egg();    //用于双缓冲,可以跳过不看    Image offScreenImage = null;    /**     * 双缓冲,可以不用看,对最终结果影响不大     */    @Override    public void update(Graphics g) {        if (offScreenImage == null)            offScreenImage = this.createImage(Yard.COLS * Yard.BLOCK_SIZE,                    Yard.ROWS * Yard.BLOCK_SIZE);        Graphics gOffScreen = offScreenImage.getGraphics();        gOffScreen.setColor(Color.DARK_GRAY);        gOffScreen.fillRect(0, 0, Yard.COLS * Yard.BLOCK_SIZE, Yard.ROWS                * Yard.BLOCK_SIZE);        paint(gOffScreen);        g.drawImage(offScreenImage, 0, 0, null);    }    public static void main(String[] args) {        new Yard().launchFrame();    }    public void launchFrame() {        this.setBackground(Color.DARK_GRAY);        this.setBounds(200, 50, COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);        this.setResizable(false);        this.addKeyListener(new KeyMonitor());        this.addWindowListener(new WindowAdapter() {            @Override            public void windowClosing(WindowEvent arg0) {                System.exit(0);            }        });        this.setVisible(true);        //构建4个结点的蛇        snake.addToHead(node1);        snake.addToHead(node2);        snake.addToHead(node3);        snake.addToHead(node4);        //启动重画的线程        new Thread(new myPaint()).start();    }    /**     * 将所有对象画在院子里面画出来     */    @Override    public void paint(Graphics g) {        Color c = g.getColor();        //画线画笔颜色为黑色        g.setColor(Color.black);        // 画横线        for (int i = 1; i < ROWS; i++) {            g.drawLine(0, i * BLOCK_SIZE, COLS * BLOCK_SIZE, i * BLOCK_SIZE);        }        // 画竖线        for (int i = 1; i < COLS; i++) {            g.drawLine(i * BLOCK_SIZE, 0, i * BLOCK_SIZE, ROWS * BLOCK_SIZE);        }        //画得到的分数,画笔颜色设置为红色        g.setColor(Color.red);        g.drawString("Score:" + score, 30, 50);        //将画笔颜色设置为默认颜色        g.setColor(c);        //当吃掉一个蛋的时候,分数加5        if (snake.eat(egg))            score += 5;        //将蛋和蛇画到屏幕上        egg.draw(g);        snake.draw(g);        //flag默认值为true,当蛇死掉时候,flag = false,此时,执行下面语句,使得结束提示在屏幕上出现        if (!flag) {            Font f = g.getFont();            g.setFont(new Font("宋体", Font.BOLD, 50));            g.setColor(Color.red);            g.drawString("Game Over", 150, 200);            g.setFont(new Font("宋体",Font.BOLD, 30));            g.drawString("请按F2重新开始", 150, 250);            start = false;        }        g.setColor(c);    }    /**     * 当蛇死掉时候,将flag设置为false的函数     */    public void stop() {        this.setFlag(false);    }    /**     * 重新开始游戏的函数,当按F2时,执行这个方法,将snake指向一只新构建的蛇对象,再将该对象设置为与一开始蛇一样大小,一样的位置     */    public void reStart() {        start = true;        flag = true;        snake = new Snake(this);        snake.addToHead(node1);        snake.addToHead(node2);        snake.addToHead(node3);        snake.addToHead(node4);    }    /**     * 用来监听键盘上的按键,主要为上下左右, F2     * @author Lu     *     */    class KeyMonitor extends KeyAdapter {        /**         * 重写KeyPressed方法,监听蛇上下左右,重新开始F2         */        @Override        public void keyPressed(KeyEvent e) {            snake.keyPressed(e);            int keyCode = e.getKeyCode();            if(keyCode == KeyEvent.VK_F2) {                reStart();            }        }    }    /**     * 线程类,对屏幕上的图不停进行重画     * @author lu     *     */    class myPaint implements Runnable {        public void run() {            while (true) {                if (start) {                    repaint();                }                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

import java.awt.Color;import java.awt.Graphics;import java.awt.Rectangle;import java.awt.event.KeyEvent;/** * 构成蛇的结点类 * @author lu * */public class Node {    /**     * 结点宽度     */    int w = Yard.BLOCK_SIZE;    /**     * 结点高度     */    int h = Yard.BLOCK_SIZE;    /**     * 结点方向     */    Direction dir = Direction.D;    /**     * 结点行数     */    int row;    /**     * 结点列数     */    int col;    /**     * 指向后一个结点的指针     */    Node next = null;    /**     * 指向前一个结点的指针     */    Node pre = null;    /**     * 因为结点长度和宽度都已经确定,故构造方法中给出结点所在行数、列数和方向即可     * @param row 行数     * @param col 列数     * @param dir 方向     */    public Node(int row, int col, Direction dir) {        this.row = row;        this.col = col;        this.dir = dir;    }    /**     * 画出结点     * @param g 画结点的画笔     */    public void draw(Graphics g) {        Color c = g.getColor();        g.setColor(Color.red);        g.fillRect(col * Yard.BLOCK_SIZE, row * Yard.BLOCK_SIZE, w, h);        g.setColor(c);    }    /**     * 为了进行碰撞检验的函数     * @return 返回包围结点的矩形     */    public Rectangle getRect() {        return new Rectangle(this.col * Yard.BLOCK_SIZE, this.row * Yard.BLOCK_SIZE, Yard.BLOCK_SIZE, Yard.BLOCK_SIZE);    }}

import java.awt.Color;import java.awt.Graphics;import java.awt.Rectangle;import java.awt.event.KeyEvent;/** * 蛇是由一个一个结点构成,为了处理方便,结点单独作为一个类,注意蛇的头结点存放数据,并不是头结点的下一个结点存放数据 * @author Lu * */public class Snake {    //头结点    private Node head = null;    //尾结点    private Node tail = null;    //得到Yard的一个引用,方便调用Yard里面的方法    private Yard y = null;    /**     * 将Yard对象引用传进来     * @param y Yard对象的引用     */    public Snake(Yard y) {        this.y = y;    }    //记录蛇是否还活着    private boolean live = true;    /**     * 头插法构建双向链表,将新生成的结点插入到蛇头     * @param node 待插入的结点     */    public void addToHead(Node node) {        //当蛇还是空的时候,插入第一个结点的处理        if(head == null) {             head = tail = node;            head.next = null;            tail.next = null;            head.pre = null;            tail.pre = null;            return;        }         /**          * 下面的为处理的具体过程          * 当头结点向下时,此时加入的结点应加在头结点的正下方          * 当头结点向上时,此时加入的结点位置为头结点的正上方          * 当头结点向左时,此时加入的结点位置为头结点的正左方          * 当头结点向右时,此时加入的结点位置为头结点的正右方          */        Node nTemp = null;        if(head.dir == Direction.D)            nTemp = new Node(head.row + 1, head.col, Direction.D);        else            if(head.dir == Direction.U)                nTemp = new Node(head.row - 1, head.col, Direction.U);            else                if(head.dir == Direction.L)                    nTemp = new Node(head.row, head.col - 1, Direction.L);                else                    if(head.dir == Direction.R)                        nTemp = new Node(head.row, head.col + 1, Direction.R);        //插入结点,注意是双向链表        nTemp.next = head;        head.pre = nTemp;        head = nTemp;    }    /**     * 蛇移动时改变方向     * 蛇移动的核心就是将蛇尾结点加入到蛇头,再将蛇头结点删除     */    public void changeDiretion() {        //将蛇尾结点插入到蛇头        this.addToHead(tail);        //将蛇尾结点删除        tail = tail.pre;        tail.next = null;    }    /**     * 将蛇画出来     * @param g 画蛇的画笔     */    public void draw(Graphics g) {        //如果蛇死掉了,直接返回        if(!this.live) return;        //当蛇没有一个结点时,直接返回        if(head == null) return;        //检查蛇是否越过Yard的四边,如果越过,会调用y.stop方法,将线程中rePaint方法跳过        this.check();        //进行移动        this.changeDiretion();        //依次画出蛇的每一个结点        Node n = head;        while(n != null) {            n.draw(g);            n = n.next;        }    }    /**     * 此方法用来监听键盘按键,改变蛇头方向,在Yard方法中调用     * @param e 监听键盘的类     */    public void keyPressed(KeyEvent e) {        int keyCode = e.getKeyCode();        switch(keyCode) {        case KeyEvent.VK_UP:            if(head.dir != Direction.D)                head.dir = Direction.U;            break;        case KeyEvent.VK_DOWN:            if(head.dir != Direction.U)                head.dir = Direction.D;            break;        case KeyEvent.VK_LEFT:            if(head.dir != Direction.R)                head.dir = Direction.L;            break;        case KeyEvent.VK_RIGHT:            if(head.dir != Direction.L)                head.dir = Direction.R;            break;        }    }    /**     * 得到蛇头结点的矩形位置和大小,用于碰撞检测,     * @return 返回头结点的矩形具体位置和大小     */    public Rectangle getRect() {        return new Rectangle(head.col * Yard.BLOCK_SIZE, head.row * Yard.BLOCK_SIZE, Yard.BLOCK_SIZE, Yard.BLOCK_SIZE);    }    /**     * 检测蛋是否被吃掉     * @param egg 蛇要吃的蛋     * @return 如果吃掉,就     */    public boolean eat(Egg egg) {        //判断蛋是否被吃用碰撞检测        if(this.live && egg.isLive() && this.getRect().intersects(egg.getRect())) {            egg.setLive(false);            this.addToHead(new Node(egg.row * Yard.BLOCK_SIZE, egg.col * Yard.BLOCK_SIZE, Direction.D));            return true;        }        return false;    }    /**     * 检测蛇头是否出界,蛇是否咬到自己     *      */    public void check() {        //出界检测        if(this.head.col < 1 || this.head.row < 3 || this.head.col > Yard.COLS - 2 || this.head.row > Yard.ROWS - 2) {            y.stop();        }        //是否咬到自己检测        Node temp = head.next;        while(temp != null) {            if(temp.col == head.col && temp.row == head.row)                y.stop();            temp = temp.next;        }    }}

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 boolean live = true;    public boolean isLive() {        return live;    }    public void setLive(boolean live) {        this.live = live;    }    /**     * 蛋的构造方法     * @param row 蛋所在的行数     * @param col 蛋所在的列数     */    public Egg(int row, int col) {        this.row = row;        this.col = col;    }    /**     * 蛋的另外一种构造方法,当蛋被吃掉时,调用此构造方法,使蛋出现在另外一个地方     */    public Egg() {        this(r.nextInt(Yard.ROWS - 4) + 3, r.nextInt(Yard.COLS - 4) + 3);    }    /**     * 将蛋画出来     * @param g 画蛋的画笔     */    public void draw(Graphics g) {        this.reAppear();        Color c = g.getColor();        g.setColor(Color.blue);        g.fillOval(col * Yard.BLOCK_SIZE, row * Yard.BLOCK_SIZE, w, h);        g.setColor(c);    }    /**     * 当蛋被蛇吃掉时,将蛋随机画到屏幕上另外一个地方     * @return 如果被蛇吃掉,重复出现在屏幕上另外一处的时候返回true, 否则返回false     */    public boolean reAppear() {        if(!this.live ) {            this.row = r.nextInt(Yard.ROWS - 4) + 3;            this.col = r.nextInt(Yard.COLS - 4) + 3;            this.live = true;            return true;        }        return false;    }    /**     * 得到包围蛋的矩形,用于碰撞检测     * @return 返回包围蛋的矩形     */    public Rectangle getRect() {        return new Rectangle(col * Yard.BLOCK_SIZE, row * Yard.BLOCK_SIZE, w, h);    }}
0 0
原创粉丝点击