Java学习总结之贪吃蛇项目程序编写(完结)

接上篇

V0.4:从这个版本开始考虑吃的问题,补充:由于边界被窗口上面那一行挡住了,所以我给程序动了一个小手术,加上了一个panel,但是随之带来了一个问题,用pack()设置大小的话窗口只是一条线,没有办法让窗口刚刚好将Panel包住,如果有大牛知道怎么弄麻烦说一声,谢谢

突然发现在贪吃蛇这种小格子的游戏中,碰撞检测不需要弄一个getRectangle方法,直接判断蛇头和豆的行列数是否相等就可以了,所以我取消了getRectangle方法,直接用row和column来比较是否吃到豆。这时候需要在蛇类中调用豆的坐标,所以需要添加一个主窗口的引用。

细心观察,可以发现吃豆eat 和移动move 两个动作的区别只在于移动move 去掉尾巴。移动是加一个头,去一个尾,而吃则是只加一个头,不去尾,所以通过观察,可以将eat 方法简化为判断下一步是否为吃,如果为吃,则在move 方法中,不执行去尾的操作。代码如下:

SnakeWindow.java

import java.awt.*;
import java.awt.event.*;

/**
 * 窗口类
 * @author hzp
 */
public class SnakeWindow extends Frame {
 public static void main(String[] args){
  new SnakeWindow("贪吃蛇").launch();
 }

 /**
  * 初始化窗口
  */
 private void launch() {
  setLocation(400,130);
  setSize(520,540);
  setLayout(null);
  setBackground(Color.WHITE);
  addWindowListener(new WindowAdapter(){
   public void windowClosing (WindowEvent e){
    System.exit(0);
   }
  });
  SnakePanel sp = new SnakePanel();
  sp.launch();
  add(sp);
  setVisible(true);
  
 }
 
 /**
  * 构造方法
  * @param s 标题栏名称
  */
 public SnakeWindow(String s){
  super(s);
 }
}

/**
 * 界面类
 * @author hzp
 */
class SnakePanel extends Panel {
 /**
  * 窗口宽度
  */
 public static final int WIDTH = 500;
 /**
  * 窗口高度
  */
 public static final int HEIGHT = 500;
 /**
  * 单元格边长
  */
 public static final int UNIT = 20;
 /**
  * 行数
  */
 public static final int ROWS = HEIGHT/UNIT;
 /**
  * 列数
  */
 public static final int COLUMNS = WIDTH/UNIT;
 /**
  * 刷新速度
  */
 public static final int UPDATES = 100;
 
 
 /**
  * 蛇对象
  */
 private Snake snake = new Snake(this);
 /**
  * 豆对象
  */
 public Bean bean = Bean.newBean();
 
 /**
  * 界面初始化
  */
 public void launch(){
  setLocation(10,30);
  setSize(WIDTH,HEIGHT);
  setBackground(Color.GRAY);
  this.setFocusable(true);
  addKeyListener(new KeyMonitor());
  new Thread(new PaintThread()).start();
 }
 
 /**
  * 绘制界面
  */
 public void paint(Graphics g){
  Color c = g.getColor();
  g.setColor(Color.DARK_GRAY);
  for(int i = 1; i < HEIGHT/UNIT ; i++) {
   g.drawLine(0,UNIT * i,WIDTH ,UNIT * i);
  }
  for (int i = 1; i < WIDTH/UNIT ; i++ )
  {
   g.drawLine(UNIT * i, 0, UNIT * i, HEIGHT);
  }
  snake.draw(g);
  bean.draw(g);
  g.setColor(c);
 }
 
 /**
  * 键盘监听器
  * @author hzp
  */
 class KeyMonitor extends KeyAdapter
 {
  /**
   * 键盘按下时调用该方法
   * @param e:键盘事件
   */
  public void keyPressed (KeyEvent e){
   snake.keyPressed(e);
  }
 }
 
 /**
  * 界面刷新线程
  * @author hzp
  */
 class PaintThread implements Runnable
 {
  public void run() {
   while(true){
    repaint();
    try{
     Thread.sleep(UPDATES);
    }catch (InterruptedException e){
     e.printStackTrace();
    }
   }
  }
 }
}

Snake.java

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class Snake
{
 /**
  * 盛放组成蛇的各个节点
  */
 public LinkedList nodeList = new LinkedList();
 /**
  * 蛇头行进的方向
  */
 private Direction direction = Direction.Left;
 private SnakePanel sw = null;

 /**
  * 方向枚举类型
  */
 public enum Direction {Up,Down,Left,Right}

 /**
  * 构造方法
  */
 public Snake(SnakePanel sw){
  nodeList.addFirst(new Node(19,14));
  this.sw = sw;
 }

 /**
  * 自身绘制
  */
 public void draw(Graphics g){
  move();

  Color c = g.getColor();
  g.setColor(Color.GREEN);
  for(int i = 0; i < nodeList.size(); i++){
   Node n = nodeList.get(i);
   n.draw(g);
  }
  g.setColor(c);
 }

 /**
  * 移动,如果不为吃则去尾
  */
 public void move() {
  addNode();
  if(!isEat()) nodeList.removeLast();
  else sw.bean = Bean.newBean();
 }

 /**
  * 根据蛇头和豆的位置判断是否为吃
  */
 public boolean isEat() {
  if(nodeList.getFirst().getColumn() == sw.bean.getColumn() && nodeList.getFirst().getRow() == sw.bean.getRow()) return true;
  return false;
 }

 /**
  * 根据方向新增一个节点
  */
 public void addNode() {
  switch(direction){
  case Up :
   nodeList.addFirst(new Node(nodeList.getFirst().getColumn(), nodeList.getFirst().getRow() - 1));
   break;
  case Down :
   nodeList.addFirst(new Node(nodeList.getFirst().getColumn(), nodeList.getFirst().getRow() + 1));
   break;
  case Left :
   nodeList.addFirst(new Node(nodeList.getFirst().getColumn() - 1, nodeList.getFirst().getRow()));
   break;
  case Right :
   nodeList.addFirst(new Node(nodeList.getFirst().getColumn() + 1, nodeList.getFirst().getRow()));
   break;
  }
 }

 public void collide () {
  //
 }

 /**
  * 方向键按键事件的处理,改变移动方向
  */
 public void keyPressed (KeyEvent e) {
  int keyCode = e.getKeyCode();
  switch(keyCode){
  case KeyEvent.VK_UP :
   direction = Direction.Up;
   break;
  case KeyEvent.VK_DOWN :
   direction = Direction.Down;
   break;
  case KeyEvent.VK_LEFT :
   direction = Direction.Left;
   break;
  case KeyEvent.VK_RIGHT :
   direction = Direction.Right;
   break;
  }
 }

 /**
  * 节点类
  */
 class Node
 {
  /**
   * 豆子列坐标
   */
  private int row = 0;
  /**
   * 豆子行坐标
   */
  private int column = 0;
  /**
   * 豆子行坐标
   */
  public static final int SIZE = SnakePanel.UNIT;

  /**
   * 私有的构造方法,不许别的类调用
   */
  public Node(int col, int row){
   this.row = row;
   this.column = col;
  }

  public int getRow(){
   return row;
  }

  public int getColumn() {
   return column;
  }

  /**
   * 节点的绘制方法
   */
  public void draw(Graphics g){
   Color c = g.getColor();
   g.setColor(Color.BLACK);
   g.fillRect(column * SnakePanel.UNIT, row * SnakePanel.UNIT, SIZE, SIZE);
   g.setColor(c);
  }
 }
}

Bean.java

import java.awt.*;
import java.util.*;

/**
 * 豆类
 */
public class Bean
{
 /**
  * 豆子的直径
  */
 public static final int SIZE = SnakePanel.UNIT;
 /**
  * 豆子行坐标
  */
 private int row = 0;
 /**
  * 豆子列坐标
  */
 private int column = 0;
 /**
  * 随机数产生器
  */
 static Random r = new Random();

 /**
  * 私有的构造方法,不许别的类调用
  */
 private Bean(int col, int row){
  this.row = row;
  this.column = col;
 }

 /**
  * 豆的绘制方法
  */
 public void draw(Graphics g){
  Color c = g.getColor();
  g.setColor(Color.GREEN);
  g.fillOval(column * SnakePanel.UNIT, row * SnakePanel.UNIT, SIZE, SIZE);
  g.setColor(c);
 }
 
 public int getRow(){
  return row;
 }

 public int getColumn() {
  return column;
 }
 
 /**
  * 静态方法,用来产生一个新豆
  */
 public static Bean newBean(){
  Bean b = new Bean(r.nextInt(SnakePanel.COLUMNS), r.nextInt(SnakePanel.ROWS));
  
  return b;
 }
}

 

V0.5:在这一个版本里我们重点解决各种边界问题,碰撞问题

首先是豆类的newBean方法,在创建之前,需要判断新位置是否已经被占据了,若被占据,则需要重新选取坐标创建。因为要获取贪吃蛇每个节点的坐标,所以要传一个LinkedList的引用。

然后是贪吃蛇的出界问题和追尾问题,出界只需要判断坐标即可,追尾可以分析一下,只要头没有撞上任何一个身体节点,其他节点必然不可能互相撞上。所以只要判断头节点有没有和其他节点重合即可。

V0.6:在这个版本中解决剩余的一些杂七杂八的问题,比如碰撞之后的界面问题,我是让界面消失,然后输出两行提示“GAME OVER!”“按F2重新开始。”具体做法就是添加一个布尔型变量live表示活着,在paint方法开始时进行判断,若是为true才进行正常绘制,否则就输出这两行字符。

然后我又添加了一个对F2键的响应和一个reset方法,按F2调用reset重新初始化snake、bean和live变量,实现重新开始的功能。

还有双缓冲机制用来消除闪烁。

还加入了暂停功能,暂停时就让PaintThread 线程不调用repaint 方法,一直空转。

我现在正在研究关于道具和关卡的问题。欢迎有问题的童鞋提问。关于贪吃蛇暂时就先写到这。

你可能感兴趣的:(Java学习总结)