******************贪吃蛇***************
1 图形界面基础
  如何显示和控制窗口,swing API的使用
2 图形界面的绘图技术,用于绘制舞台,蛇和食物
  如何进行自定义绘制,swing API 的使用
3 图形界面的事件响应,用于控制蛇的运行方向
  如何处理键盘事件,swing API 的使用
  (1 2 3 是舞台的功能)
4 蛇的运行算法,是蛇的功能
  爬行
  创建两个方法:
  1,爬行creep()
  2,创建新头节点createHead()
  吃食物
  1,创建新头节点
  2,检查是否吃到食物
  3,如果吃到食物,就复制数组(扩容,丢弃原数组)
  4,向后移动点
  5,插入到新节点[0]
  碰撞检测(边界,自身碰撞)
5 在舞台上定时驱动蛇的运行
**************单元测试工具****************
测试要新建个类,在这个新的类中测试
如果是测试主函数中的代码,则要在主函数外建一个test()方法,把主函数中的代码移到test()中,如果是在类中的方法可以直接调用,如果类中没有new的话,则需要在测试这个类中new下.
在项目点击右键选择properties---->左边框内选择Java Build Path选项--->右边选择Libraries--->Add Library--->Junit 4
导入包:import org.junit.Test; 单元测试工具注释
在方法前@Test
在一个测试类中有多个测试,所以会输出多个测试结果,如果只想看一个测试结果,可以在你想看到结果之外的其他测试上//@Test
**************爬行**********
创建两个方法:
1,爬行creep()
2,创建新头节点createHead()
        /** 按照默认方向爬行一步 */
    public void creep(){
        for(int i=this.cells.length-1; i>=1;i-- ){
            cells[i] = cells[i-1];
        }
        cells[0] = createHead(currentDirection);
    }
    /** 根据方向,和当前(this)的头节点,创建新的头节点 */
    private Cell createHead(int direction){
        Cell head = this.cells[0];//临时创建头节点
        int x = head.getX();
        int y = head.getY();
        switch(direction){
        case DOWN:
            y++;
            break;
        case UP:
            y--;
            break;
        case RIGHT:
            x++;
            break;
        case LEFT:
            x--;
            break;
        }
        return new Cell(x,y);//返回新的节点
    }
在测试类中调用着两个方法并测试
    @Test//爬行测试 creep 爬行
    public void creepTest(){
        Worm worm = new Worm();
        System.out.println(worm);
        //[[0,0], [1,0], [2,0], [3,0], [4,0], [5,0],
        //[6,0], [7,0], [8,0], [9,0], [10,0], [11,0]]
        worm.creep();
        System.out.println(worm);
        //[[0,1], [0,0], [1,0], [2,0], [3,0], [4,0],
        //  [5,0], [6,0], [7,0], [8,0], [9,0], [10,0]]
        /** 第二次输出的结果与第一次的比较表示蛇的爬行 */
    }
**************吃食物****************
爬行的时候检查是否能够吃到food,food是参数
如吃到食物,蛇的长度会增加
1,创建新头节点
2,检查是否吃到食物
3,如果吃到食物,就复制数组(扩容,丢弃原数组)
4,向后移动点
5,插入到新节点[0]
public boolean creep(Cell food){
        Cell head = createHead(currentDirection);
        boolean eat = head.getX()==food.getX()//检查是否吃到食物
                && head.getY()==food.getY();
        if(eat){//如果吃到食物就复制数组
            Cell[] ary = Arrays.copyOf(cells, cells.length+1);
            cells = ary;//丢弃源数组
        }//向后移动节点
        for(int i=cells.length-1;i>=1;i--){
            cells[i] = cells[i-1];
        }
        cells[0] = head;//插入头节点
        return eat;
    }
在测试类中调用方法并测试
    @Test
    public void creepWithFoodTest(){
        Worm worm = new Worm();
        Cell food = new Cell(0,2);
        System.out.println(worm);
        //蛇的默认长度,
        //[[0,0], [1,0], [2,0], [3,0], [4,0], [5,0],
        //[6,0], [7,0], [8,0], [9,0], [10,0], [11,0]]
        System.out.println(worm.creep(food));
        //false,表示蛇爬行一步,但没有吃到食物
        System.out.println(worm);
        //蛇爬行后的长度,
        //[[0,1], [0,0], [1,0], [2,0], [3,0], [4,0],
        //[5,0], [6,0], [7,0], [8,0], [9,0], [10,0]]
        System.out.println(worm.creep(food));
        //true,表示蛇再次爬行后吃到了食物
        System.out.println(worm);
        //蛇吃到食物后的长度,蛇吃到食物后蛇的长度增加(数组的扩容)
        //[[0,2], [0,1], [0,0], [1,0], [2,0], [3,0],
        //[4,0], [5,0], [6,0], [7,0], [8,0], [9,0], [10,0]]
    }
***********更换方向************
public boolean creep(int direction,Cell food){
        if(currentDirection+direction==0){//反向
            return false;//如果反向,就不进行任何动作
        }
        this.currentDirection = direction;//换方向
        Cell head = createHead(currentDirection);
        boolean eat = head.getX()==food.getX()//检查是否吃到食物
                && head.getY()==food.getY();
        if(eat){//如果吃到食物就复制数组
            Cell[] ary = Arrays.copyOf(cells, cells.length+1);
            cells = ary;//丢弃源数组
        }//向后移动节点
        for(int i=cells.length-1;i>=1;i--){
            cells[i] = cells[i-1];
        }
        cells[0] = head;
        return eat;
    }
在测试类中调用方法并测试   
    @Test//更换方向爬行测试
    public void creepWithDirFoodTest(){
        System.out.println("更换方向爬行测试");
        Worm worm = new Worm();
        Cell food = new Cell(1,2);
        System.out.println(worm);
        //[[0,0], [1,0], [2,0], [3,0], [4,0],
        //[5,0], [6,0], [7,0], [8,0], [9,0], [10,0], [11,0]]
        System.out.println(worm.creep(food));//false
        System.out.println(worm);
        //[[0,1], [0,0], [1,0], [2,0], [3,0], [4,0],
        //[5,0], [6,0], [7,0], [8,0], [9,0], [10,0]]
        System.out.println(worm.creep(food));//flase
        System.out.println(worm);
        //[[0,2], [0,1], [0,0], [1,0], [2,0], [3,0],
        //[4,0], [5,0], [6,0], [7,0], [8,0], [9,0]]
        System.out.println(worm.creep(Worm.UP,food));
        //false,调用方向向上
        System.out.println(worm);
        //反向无动作.蛇的默认方向是向下,而调用的方向向上,与默认方向相反,       所以没有任何动作
        //[[0,2], [0,1], [0,0], [1,0], [2,0], [3,0],
        //[4,0], [5,0], [6,0], [7,0], [8,0], [9,0]]
        System.out.println(worm.creep(Worm.RIGHT,food));
        //true,调用的方向向右,吃到食物
        System.out.println(worm);//吃到食物
        //[[1,2], [0,2], [0,1], [0,0], [1,0], [2,0], [3,0],
        //[4,0], [5,0], [6,0], [7,0], [8,0], [9,0]]
    }
**************碰撞检测(边界,自身碰撞)**********
检查在新的运行方向上是否能够碰撞到边界和自己this
0 反向不检查返回false
1 生成下个新头节点位置
2 如果新头节点出界,返回true,表示碰撞边界
3 如果新头节点包含在蛇的前n-1节点范围,表示吃到自己返回true
4 否则返回false
public boolean hit(int direction){
        if(currentDirection + direction==0){//反向
            return false;//如果反向,就不进行任何动作
        }
        Cell head = createHead(direction);//创建新的头节点
        //判断新的头节点是否出界
        if(head.getX()<0 || head.getX()>=WormStage.COLS ||
                head.getY()<0 ||head.getY()>=WormStage.ROWS){
            return true;
        }//判断新头节点是否包含在自己体内,表示吃到自己
        for(int i=0;i<cells.length-1;i++){
            if(cells[i].getX()==head.getX() &&
                    cells[i].getY()==head.getY()){
                return true;
            }
        }
        return false;
    }
在测试类中调用方法并测试
    @Test
    public void hitTest(){
        System.out.println("碰撞方法的检测");
        Worm worm = new Worm();
        System.out.println(worm);
        worm.creep();
        System.out.println(worm);
        System.out.println(worm.hit(Worm.LEFT));
     //true,因为蛇的默认方向是向下的,调用向左的方向,则会碰撞边界,返回true
        System.out.println(worm.hit(Worm.RIGHT));
//false,因为蛇的默认方向是向下的,调用向右的方向,则不会碰撞边界,返回false
        Cell food = new Cell(4,5);
        worm.creep(Worm.RIGHT,food);
        //改变默认方向,使蛇的方向由默认的向下改为向右
        System.out.println(worm);
        System.out.println(worm.hit(Worm.UP));
    //true,因为蛇的默认方向是向右的,调用向上的方向,则会碰撞边界,返回true
        System.out.println(worm.hit(Worm.DOWN));
//false,因为蛇的默认方向是向右的,调用向下的方向,则不会碰撞边界,返回false
    }
*************显示和控制窗口***************
显示窗口需要导入包:
import javax.swing.JFrame;
导入包后就可以调用此包中的方法
1.frame.setSize(width,height);
调整组件的大小,使其宽度为 width,高度为 height。
2.frame.setLocation(x,y);
将组件移到新位置。通过此组件父级坐标空间中的 x 和 y 参数来指定新位置的左上角
3.frame.setLocationRelativeTo(null);
则此窗口将置于屏幕的中央。
4.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
设置默认的关闭操作为在关闭时候离开软件,单窗口软件这样设置最简单,多窗口软件另有其他设法
5.frame.setLayout(null)
关闭默认布局管理,避免面板充满窗口
6.frame.setVisible(true)
设置可见的窗口
************自定义绘制***************
/**重写JPanal绘制方法*/
先导入包:import java.awt.Graphics;
允许应用程序在组件以及在闭屏图像上进行绘制
public void paint(Graphics g){
        //添加自定义绘制
        //先绘制背景
        g.setColor(Color.darkGray);//把背景涂成深灰色
        g.fillRect(0, 0, getWidth(), getHeight());//填充指定矩形
        g.setColor(Color.RED);
        //draw 绘制 Rect矩形,绘制指定矩形的边框
        g.drawRect(0, 0, this.getWidth()-1, this.getHeight()-1);
        //绘制食物
        g.setColor(food.getColor());
        //fill 填充 3D 3维 Rect矩形 突起的立体按钮形状
        g.fill3DRect(food.getX()*CELL_SIZE,
                food.getY()*CELL_SIZE, CELL_SIZE, CELL_SIZE, true);
        //绘制蛇
        worm.paint(g);//让蛇自己利用画笔绘制//调用worm类中paint()方法
    }
在Worm类中的paint()方法,表示为蛇添加绘制方法,也就是利用来自舞台面板的画笔(Graphics)绘制蛇到舞台上
public void paint(Graphics g){
        g.setColor(this.color);//设置当前蛇的颜色
        for(int i= 0;i<cells.length;i++){
            Cell cell = cells[i];//临时创建一个格子
            //绘制一个填充当前颜色的矩形
            g.fill3DRect(cell.getX()*WormStage.CELL_SIZE,
                    cell.getY()*WormStage.CELL_SIZE,
               WormStage.CELL_SIZE, WormStage.CELL_SIZE, true);
        }
    }
***********在舞台上定时驱动蛇的运行************
启动定时器驱动蛇的运行
1 检查碰撞是否将要发生
2 如果发生碰撞:创建新的蛇和食物,重写开始
3 如果没有碰撞就爬行,并检查是否能够吃到食物
4 如果吃到食物:重新创建新的食物
5 启动重新绘制界面功能 repaint() 更新界面显示效果!
  repaint() 方法会尽快调用 paint() 更新界面!
private Timer timer;
private void go(){
        if(timer==null){
            timer = new Timer();
            timer.schedule(new TimerTask(){
                public void run(){
                    if(worm.hit()){//如果蛇碰到边界或自己
                        worm = new Worm();//创建新蛇
                        food = createFood();//创建新食物
                    }else{//如果没有碰到自己
                      boolean eat = worm.creep(food);
                     //蛇向前(当前方向)爬行,返回结果表示是否吃到食物
                      if(eat){//如果吃到食物,就生成新食物
                          food = createFood();
                        //不能new一个,要把蛇躲过去
                      }
                    }
                    repaint();
                    //不加repaint()会造成只有在拉画框是才会动
                }
            }, 0, 1000/10);
        }
    }
      
*********paint方法************
package com.tarena.worm;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
/**
 * 如何绘图
 */
public class PaintDemo {
    public static void main(String[] args) {
        JFrame frame = new JFrame("绘图演示");
        frame.setSize(300, 200);
        frame.setLayout(null);//取消默认布局管理
        //窗口中的组件,需要自定义位置和大小
        MyPane panel = new MyPane();
        panel.setSize(280, 150);
        panel.setLocation(20, 20);//设置面板的位置
        panel.setBorder(new LineBorder(Color.BLACK));
        frame.add(panel);
        frame.setVisible(true);
        panel.go();
    }
}
class MyPane extends JPanel{
    String[] s = {".o0o....","..o0o...","...o0o..",
            "....o0o.",".....o0o","o.....0o","0o.....o"};
    int i = 0;
    //方法重写:子类修改父类的功能
    //paint是swing 默认调用绘制面板的方法
    public void paint(Graphics g){
        super.paint(g);//调用父类的paint方法,包含绘制边框功能
        g.drawString(s[i++%s.length], 20, 20);//自定义绘制
    }
    public void go(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            public void run(){
                repaint();//在JPanel中定义,尽快的启动界面绘制功能,
                //界面绘制功能会调用paint方法
            }
        }, 0,1000/10);
    }
}