贪吃蛇小游戏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() {@Overridepublic 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;}@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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}
说明
源码是很久以前下载的,因此不能提供源码链接,但是,在此,对源码提供者表示衷心的感谢,谢谢原作者的给力分享。