贪吃蛇
来源:互联网 发布:网络歌曲视频下载 编辑:程序博客网 时间: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); }}
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- 贪吃蛇
- x86中断编程
- 【Linux学习笔记七】Linux编程-Vim编辑器
- 大牛的《背包九讲》
- 记录自己的html与css之路:
- windows下安装theano
- 贪吃蛇
- HDU 1021 Fibonacci Again(找规律)
- hdu 3033 I love sneakers! 分组背包
- RxJava使用(四)变换
- ISLR第三章线性回归应用练习题答案(下)
- 【VS开发】CTimeSpan类
- 入门新手如何系统地学习数据挖掘?
- JAVA+CKEditor+CKFinder 配置异常解决方案
- 用view绘制六边形能力值自定义控件