1. 程式人生 > >貪吃蛇1.0階段性總結

貪吃蛇1.0階段性總結

跟著視訊做的簡易版貪吃蛇java版

謹記錄一下過程及遇到的問題解決。


程式碼:

/*
蛇類
 */


import java.awt.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class Snake {

    public static final int UP =-1;
    public static final int DOWN =1;
    public static final int LEFT =2;
    public static final int RIGHT =-2;
    //定義一些表示方向的常量

    private Point oldTail;//定義一個Point變數來存放每次移動去掉的蛇尾

    private Set<SnakeListener> listeners = new HashSet<SnakeListener>();
    //定義監聽器組,用於註冊多個監聽器組

    private LinkedList<Point> body=new LinkedList<Point>();
    //定義一個集合欄位來儲存蛇的所有身體座標,用awt中的Point類表示

    //private int direction;//定義一個表示當前方向的欄位

    private boolean life;//定義一個布林變數控制蛇的生命(即執行緒的生命)

    private int oldDirection;//定義一個表示舊的移動方向的變數
    private int newDirection;//定義一個表示新的移動方向的變數

    public Snake()//定義構造方法 呼叫初始化身體方法
    {
        init();
    }

    public void init()//初始化蛇身的方法
    {
        life=true; //初始化蛇的生命

        //找出螢幕正中心的點的座標:
        int x = Global.WIDTH/2;
        int y = Global.HEIGHT/2;

        //依次新增身體節點:(3個節點,蛇頭在右)
        for(int i=0;i<3;i++)
        {
            body.addLast(new Point(x--,y));
        }

        //direction = RIGHT;//因為蛇頭在右邊,所以設定預設移動方向為向右
        oldDirection=newDirection=RIGHT;//因為蛇頭在右邊,所以設定預設移動方向為向右
    }

    public void die()//蛇死亡的方法
    {
        life=false;
    }

    public void drawMe(Graphics g)//蛇顯示方法
    {
        System.out.println("蛇顯示出來了");

        g.setColor(Color.BLUE);
        for(Point p : body) //遍歷集合,填充所有屬於蛇身體的矩形
        {
            g.fill3DRect(p.x*Global.CELL_SIZE,p.y*Global.CELL_SIZE,Global.CELL_SIZE,
                    Global.CELL_SIZE,true);
        }
    }

    public void move()//蛇移動方法
    {
        System.out.println("蛇移動了");

        //我們約定首節點為蛇頭,尾節點為蛇尾
        //去尾(刪除尾節點):
        if(!(oldDirection+newDirection==0))//如果舊的移動方向和新的移動方向不相反,就把新方向賦值給舊方向
            oldDirection=newDirection;
        oldTail=body.removeLast();//在去尾時對oldTail賦值一次
        //先獲得當前蛇頭座標:
        int x=body.getFirst().x;
        int y=body.getFirst().y;

        switch (oldDirection)//根據蛇頭的方向來計算蛇頭的新座標
        {
            case UP:
                y--;
                if (y<0)                    //判斷蛇的位置超出顯示區域邊界,讓他從另一邊進入顯示區
                    y=Global.HEIGHT-1;
                break;  //如果蛇頭向上,y座標-1
            case DOWN:
                y++;
                if (y>=Global.HEIGHT)
                    y=0;
                break;  //如果蛇頭向上,y座標+1
            case LEFT:
                x--;
                if(x<0)
                    x=Global.WIDTH-1;
                break;  //如果蛇頭向上,x座標-1
            case RIGHT:
                x++;
                if (x>=Global.WIDTH)
                    x=0;
                break;  //如果蛇頭向上,x座標+1
        }
        Point newHead = new Point(x,y);//使用新蛇頭座標得到新蛇頭

        //加頭:
        body.addFirst(newHead);//將新蛇頭新增到首節點

    }

    public void changeDirection(int direction)//蛇轉向方法
    {
        System.out.println("蛇改變方向了");

        //if(!(direction+this.direction==0))  //判斷移動方向與按鍵方向是否相反
        newDirection=direction;   //將輸入的方向引數賦值給儲存新的移動方向的變數
    }

    public void eatFood()//蛇吃食物方法
    {
        System.out.println("蛇吃到食物了");

        body.addLast(oldTail);  //吃到食物時把原來去掉的尾巴加上
    }

    public Point getHead()//獲取蛇頭座標的方法
    {
        return body.getFirst();
    }

    public boolean isEatBody()//判斷蛇吃到自己的身體方法
    {
        System.out.println("蛇吃到自己的身體了");

        //遍歷身體所有節點,如果與蛇頭重合,則為吃到了身體
        //PS:因為i=0時表示身體第一個節點,是蛇頭,所以i從1開始迴圈
        for(int i=1;i<body.size();i++)
            if(body.get(i).equals(this.getHead()))
                return true;

        return false;
    }

    private class SnakeDriver implements Runnable   //內部類,建立執行緒
    {
        @Override
        public void run() {

            while(life)     //只要蛇或者,就一直執行這個執行緒
            {
                move();
                for(SnakeListener l:listeners)
                {
                    l.snakeMoved(Snake.this);
                }
                //這裡的for迴圈將會遍歷listeners中所有元素依次呼叫snakeMoved(),觸發事件
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void addSnakeListener(SnakeListener l)   //新增監聽器的方法
    {
        if(l!=null)
            this.listeners.add(l);
    }

    public void start()                             //啟動執行緒的方法
    {
        new Thread(new SnakeDriver()).start();
    }

}

/*
食物類
 */


import java.awt.*;

public class Food extends Point {

    public void drawMe(Graphics g)//食物顯示方法
    {
        System.out.println("食物顯示出來了");

        g.fill3DRect(x*Global.CELL_SIZE,y*Global.CELL_SIZE, //填充食物的矩形
                Global.CELL_SIZE,Global.CELL_SIZE,true);
    }

    public boolean isSnakeEatFood(Snake snake)//判斷食物被蛇吃了
    {
        System.out.println("食物被蛇吃了");

        //食物座標與蛇頭座標重合即為吃到了
        return this.equals(snake.getHead());    // 比較食物座標和蛇頭座標是否重合
    }

    public void newFood(Point p)    //得到一個食物
    {
        this.setLocation(p);
    }

}

/*
牆體類
 */


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

public class Ground {

    //定義一個二維陣列存放多塊牆體的資訊,陣列中置1的便是牆體
    private int[][]rocks = new int[Global.WIDTH][Global.HEIGHT];

    public Ground()//新增一個構造方法來初始化牆體
    {
        for(int x=0;x<Global.WIDTH;x++)
        {
            rocks[x][0]=1;
            rocks[x][Global.HEIGHT-1]=1;
        }
        for (int y=0;y<Global.HEIGHT;y++)
        {
            rocks[0][y]=1;
            rocks[Global.WIDTH-1][y]=1;
        }
    }

    public void drawMe(Graphics g)//牆顯示方法
    {
        System.out.println("牆體顯示出來了");

        g.setColor(Color.GRAY);
        for(int x=0;x<Global.WIDTH;x++)
            for(int y=0;y<Global.HEIGHT;y++)
                if(rocks[x][y]==1)
                    g.fill3DRect(x*Global.CELL_SIZE,y*Global.CELL_SIZE,
                            Global.CELL_SIZE,Global.CELL_SIZE,true);

    }

    public boolean isSnakeEatGround(Snake snake)//判斷蛇吃到牆方法
    {
        System.out.println("蛇吃到牆了");

        for(int x=0;x<Global.WIDTH;x++)
            for(int y=0;y<Global.HEIGHT;y++)
                if(rocks[x][y]==1&&(x==snake.getHead().x&&y==snake.getHead().y))
                    return true;
                return false;
    }

    public Point getPoint()             //獲取隨機座標點的方法
    {
        Random random = new Random();
        int x;
        int y;

        do {                                        //直到生成一個不與牆重疊的座標再結束迴圈
            x = random.nextInt(Global.WIDTH);
            y = random.nextInt(Global.HEIGHT);
        }while (rocks[x][y]==1);

        return new Point(x,y);
    }

}


/*
遊戲面板類
 */


import javax.swing.*;
import java.awt.*;

public class GamePanel extends JPanel {

    private Snake snake;     //定義三個物件的引用,用於呼叫drawMe()顯示函式
    private Food food;
    private Ground ground;

    public void display(Snake snake,Food food,Ground ground)//顯示窗體方法
    {
        System.out.println("窗體顯示了");

        this.snake = snake;             //引數賦值
        this.food = food;
        this.ground = ground;
        repaint();//最終會呼叫paintComponent(Graphics g)用於重新顯示

    }

    @Override
    protected void paintComponent(Graphics g) //用於重繪(重新顯示)
    {
        //蛇移動後要擦除之前的圖形:(通過填充和之前顯示區域相同大小的矩形實現)

        g.setColor(new Color(0xcfcfcf));                        //設定顏色
        g.fillRect(0,0,Global.WIDTH*Global.CELL_SIZE,
                Global.HEIGHT*Global.CELL_SIZE);            //填充


        /*此處paintComponent方法可能在出現窗體時就被呼叫,此時可能snake,food,ground物件還沒產生
            所以可以新增一個判斷:
         */
        if(snake!=null&&food!=null&&ground!=null)
        {
            this.snake.drawMe(g);        //呼叫三個物件的顯示函式
            this.food.drawMe(g);
            this.ground.drawMe(g);
        }
    }
}

/*
用於定義格子寬度、高度及顯示區域大小的類
 */

public class Global {

    public static final int CELL_SIZE = 20;//定義格子寬度、高度

    public static final int WIDTH = 15;     //整個區域大小寬度=15個格子
    public static final int HEIGHT = 15;    //整個區域大小高度=15個格子
}

/*
監聽蛇移動的介面
 */



public interface SnakeListener {
    public void snakeMoved(Snake snake);    //定義蛇移動的方法,蛇是時間源,作為引數傳入
}

/*
遊戲控制類
處理使用者按鍵事件
處理遊戲邏輯
 */


import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;

public class Controller extends KeyAdapter implements SnakeListener{

    private Snake snake;                //定義4個物件的引用
    private Food food;
    private Ground ground;
    private GamePanel gamePanel;

    public Controller(Snake snake,Food food,Ground ground,GamePanel gamePanel)//構造方法,引數賦值
    {
        this.snake=snake;
        this.food=food;
        this.ground=ground;
        this.gamePanel=gamePanel;
    }

    @Override
    public void keyPressed(KeyEvent e) //用於根據使用者按鍵改變蛇的方向
    {
        switch (e.getKeyCode())        //判斷使用者按鍵改變方向具體實現
        {
            case KeyEvent.VK_UP:
                snake.changeDirection(Snake.UP);
                break;
            case KeyEvent.VK_DOWN:
                snake.changeDirection(Snake.DOWN);
                break;
            case KeyEvent.VK_LEFT:
                snake.changeDirection(Snake.LEFT);
                break;
            case KeyEvent.VK_RIGHT:
                snake.changeDirection(Snake.RIGHT);
                break;
        }
    }

    @Override
    public void snakeMoved(Snake snake) //實現蛇移動介面中的蛇移動事件方法
    {
        gamePanel.display(snake,food,ground);   //呼叫GamePanel中的顯示方法,傳入三個物件的引用

        if (food.isSnakeEatFood(snake))     //如果蛇吃到食物,蛇變長,生成新的食物
        {
            snake.eatFood();
            food.newFood(ground.getPoint());
        }

        if(ground.isSnakeEatGround(snake))  //  如果蛇吃到牆,則死掉(執行緒結束)
            snake.die();

        if(snake.isEatBody())               //如果蛇吃到身體,則死掉(縣城結束)
            snake.die();
    }

//    public Point getPoint()
//    {
//        Random random = new Random();
//        int x = random.nextInt(Global.WIDTH);
//        int y = random.nextInt(Global.HEIGHT);
//        return new Point(x,y);
//    }

    public void newGame()           //開始新遊戲的方法
    {
        snake.start();
        food.newFood(ground.getPoint());
    }

}

/*
測試類
 */


import javax.swing.*;
import java.awt.*;
import java.util.Random;

public class Game {

    public static void main(String[] args) {

        Snake snake = new Snake();                  //建立四個關鍵物件
        Food food = new Food();
        Ground ground = new Ground();
        GamePanel gamepanel = new GamePanel();

        Controller controller = new Controller(snake,food,ground,gamepanel);
        //通過Controller構造方法傳入四個物件

        JFrame frame = new JFrame("唐浩牌貪吃蛇1.0");            //新建一個窗體
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//設定窗體可關閉
        frame.setSize(Global.WIDTH*Global.CELL_SIZE+10,
                Global.HEIGHT*Global.CELL_SIZE+35);//設定窗體大小為通過寬高計算出的值
        gamepanel.setSize(Global.WIDTH*Global.CELL_SIZE,
                Global.HEIGHT*Global.CELL_SIZE);    //設定面板大小為通過寬高計算出的值
        frame.add(gamepanel, BorderLayout.CENTER);//將遊戲面板新增到窗體,並設定佈局
        frame.addKeyListener(controller);
        gamepanel.addKeyListener(controller);//新增遊戲面板上的按鍵事件監聽

        snake.addSnakeListener(controller);
        frame.setVisible(true);//設定窗體可見

        controller.newGame(); //呼叫開始新遊戲方法


    }
}


由於是初學練手,因此作了很詳盡的註釋。

以下是編寫過程中的筆記:


基本架構:

蛇類        
蛇顯示            public void drawMe()
蛇移動            public void move()
蛇改變方向        public void changeDirection()
蛇吃到食物        public void eatFood()
蛇吃到自己身體        public boolean isEatBody()

牆類
牆顯示            public void drawMe()
蛇撞到牆        public boolean isSnakeEatGround()

食物類    
食物顯示        public void drawMe()
食物被吃了        public boolean isSnakeEatFood()

面板類
顯示面板        public void display()


邏輯控制類

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
1.蛇每移動一次,面板實時顯示:
蛇移動事件監聽介面    SnakeListener

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
2.蛇隨時間不停移動:
利用執行緒
Snake類中加入內部類SnakeDriver,實現Runnable,重寫run()
run()中迴圈呼叫蛇的move(),並睡眠1S.
    while(true)
    {
        move();
        sleep(1000);
    }

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
3.GamePanel類要起顯示作用,需要繼承一個畫圖的元件JPanel
GamePanel extends JPanel
然後重寫其paintComponent(Graphics g)方法來用於重繪(重新顯示)
並且在窗體類中對外的顯示方法display()中呼叫repaint();
最終repaint()將會呼叫paintComponent()用於重新顯示(蛇,食物,牆)

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
4.重新顯示方法paintComponent()用於顯示的是蛇,食物,牆:
在窗體類的display()中接收三個引數(Snake snake,Food food,Ground,ground)
並在窗體類中定義三個物件的引用
private Snake snake;
private Food food;
private Ground ground;
然後便可以在paintComponent()中呼叫三個物件的顯示函式
this.snake.drawMe();
this.food.drawMe();
this.ground.drawMe();
ps:需要在display()中呼叫repaint()之前進行引數賦值
this.snake = snake;
this.food = food;
this.ground = ground;

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
5.Controller類需要處理使用者的按鍵事件:
Controller類繼承KeyAdapter類,並且重寫keyPressed(KeyEvent e)方法,該方法用於根據使用者的按鍵情況改變蛇的方向。

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
6.如何得知使用者按鍵情況:
在重寫的KeyPressed(KeyEvent e)中呼叫getKeyCode():e.getKeyCode();
將其返回值作為switch常量表達式,用switch語句判斷使用者按的鍵,用於控制蛇的方向
case常量為KeyEvent.VK_UP,KeyEvent.VK_DOWN,KeyEvent.VK_LEFT,KeyEvent.VK_RIGHT

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
7.控制蛇改變方向:
要控制蛇,需要在Controller類中定義引用
private Snake snake;
private Food food;
private Ground ground;
private GamePanel gamepanel;
然後在switch語句中呼叫蛇改變方向的方法changeDirection(): 
snake.changeDirection();
PS:別忘了每個語句後加break;

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
8.蛇每移動之後都要重新顯示,使用事件監聽如何實現:(上接1)
在蛇監聽介面SnakeListener中定義蛇移動的方法:
void snakeMover(Snake snake);(事件源是蛇,接收引數也應該是蛇)
蛇每次移動便觸發一次事件,所以在Snake類中定義監聽器組:
private Set<SnakeListener> listeners =new HashSet<SnakeListener>();
(用於註冊多個監聽器)
有了監聽器,應該在Snake類中提供新增監聽器的方法:
public void addSnakeListener(SnakeListener l)
{if(l!=null)
this.listeners.add(l);
}
在蛇移動後觸發移動事件:
在Snake類中內部類SnakeDiver中呼叫move()之後觸發事件:
加一個for迴圈迴圈所有的監聽器:
for(SnakeListner l: listeners)        (這裡的for迴圈將會遍歷listener中所有元
{l.snakeMoved(Snake.this)        素,依次呼叫snakeMoved(),觸發事件)
}
Controller類中有GamePanel引用,接著讓Controller類實現監聽介面SnakeListener,然後實現其snakeMoved(Snake snake)
內容為呼叫GamePanel類中的顯示方法display,傳入三個物件的引用:
gamepanel.display(snake,food,ground);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
9.Snake類中內部類SnakeDriver中建立的執行緒不停呼叫move(),該執行緒由誰啟動,什麼時候啟動:
在Snake類中提供一個啟動執行緒的方法start():
public void start()
{new Thread(new SnakeDriver()).start();
}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
10.Controller類中定義的四個引用欄位如何進行賦值:
在Controller類中定義一個構造方法接受這四個物件:
public Controller(Snake snake,Food food,Ground ground,GamePanel gamepanel)
{this.snake = snake;
this.food = food;
this.ground = ground;
this.gamepanel = gamepanel;
}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
11.如何開始新遊戲:
在Controller類中新增開始新遊戲的方法newGame():
public void newGame()
{snake.start();
}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
12.編寫測試類:
定義一個帶有main()的Game類,
定義4個關鍵物件:snake,food,ground,gamepanel
通過Controller的構造方法傳入這四個物件:
Controller controller = new Controller(snake,food,ground,gamepanel);
新建一個JFrame物件:
JFrame frame =new JFrame();
設定其可關閉及其大小:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
將gamepanel面板新增到frame上,並設定佈局:
frame.add(gamepanel,Borderlayout.CENTER);
新增遊戲顯示面板的按鍵事件監聽器:
gamepanel.addKeyListener(controller);
由於Controller也是蛇的snakeMoved的事件處理器,所以:
snake.addSnakeListener(controller);
設定frame可顯示:
frame.setVisible(true);
呼叫開始新遊戲方法:
controller.newgame();

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
13.如何表示蛇,食物和牆:
將整個顯示區作為一個表格,一個格子即為一個食物,多個連續的格子為一條蛇,周圍的多個格子連成牆,可用座標來表示這三個物件。

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
14.蛇如何移動:
蛇向前移動,即為蛇頭上增加一個點,蛇尾減少一個點,即去頭加尾。

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
15.用什麼資料結構來存放蛇的身體節點:
Linkedlist(連結串列),因為區分首尾,所以需要有序。

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
16.蛇移動的具體實現:move();
定義一個集合欄位來儲存蛇的身體的所有座標:
用awt包的Point類來表示:
private LinkedList<Point> body=new LinkedList<Point>();
編寫move():
我們約定首節點為蛇頭,尾節點為蛇尾。
去尾: body.removeLast();  //去掉尾節點
加頭之前首先要得到當前蛇頭的座標:
int x=body.getFirst().x;
int y=body.getFirst().y;
然後定義一個表示當前方向的欄位:
private int direction;
接著定義一些表示方向的常量:
public static final int UP =0;
public static final int DOWN =1;
public static final int LEFT =2;
public static final int RIGHT =3;
然後可以使用switch語句,根據蛇頭的方向計算蛇頭的新座標:
switch(direction)
{case UP:y--;break; //向上,即y座標-1
case UP:y++;break;
case UP:x--;break;
case UP:x++;break;
}
Point newHead=new Point(x,y);
加頭:
body.addFirst(newHead);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
17.蛇改變方向的具體實現:changeDirection();
在changeDirection()中加入傳參(int direction)
在方法中引數賦值:
this.direction=direction;
修改Controller類中KeyPressed方法中呼叫changeDirection方法的地方:
在呼叫傳參的地方指定具體改變的方向:
snake.changeDirection(Snake.UP);
snake.changeDirection(Snake.DOWN);
snake.changeDirection(Snake.LEFT);
snake.changeDirection(Snake.RIGHT);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
18.蛇顯示的方法具體實現: drawMe();
通過矩形填充來實現:
(填充矩形函式:fill3DRect()需要傳入四個引數:矩形左上角的點的畫素座標x,y,矩形的寬度,矩形的高度)
矩形左上角的點的畫素座標x=矩形寬*矩形離左邊界的距離
矩形左上角的點的畫素座標y=矩形高*矩形離上邊界的距離
首先新建一個Global類來定義格子的寬度高度:
定義:public static final int CELL_SIZE=20;
定義整個顯示區域的大小:(以格子為單位,15格子寬*15格子高)
public static final int WIDTH=15;
public static final int HEIGHT=15;
然後修改drawMe方法,新增畫布引數(Graphics g)
修改呼叫drawMe方法的地方(傳參)
接著依次把所有屬於蛇身體的座標畫出來:
for(Point p : body)//遍歷集合,填充所有表示蛇身體的矩形
{
g.fill3DRect(p.x*Global.CELL_SIZE,p.Y*Global.CELL_SIZE,Global.CELL_SIZE,Global.CELL_SIZE,true)

初始化蛇身:
定義一個初始化方法init();
public void init()
{找出區域的中心點:
int x=Global.HEIGHT/2;
int y=Global.WIDTH/2;
依次新增身體節點:
for(int i=0;i<3;i++)
{body.addFirst(new Point(x--,y));
}
因為蛇頭在右邊,所以定義其移動方向預設向右:
direction =RIGHT;
}
新增Snake構造方法,在構造方法中呼叫初始化方法init();
public Snake()
{init();}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
19.將窗體(frame)的大小設定為通過寬度高度計算得到的值:
(Global.WIDTH*Global.CELL_SIZE+10,Global.HEIGHT*Global.CELL_SIZE+35)
將面板(gamepanel)的大小設定為:
(Global.WIDTH*Global.CELL_SIZE,Global.HEIGHT*Global.CELL_SIZE)

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
20.蛇移動之後應該要擦除之前的圖形:
可以通過填充和之前的顯示區域相同的大小的矩形來實現:
在GamePanel類中重新顯示的方法pointComponent中應該先擦除之前的圖形:
設定顏色:g.setColor(new Color(0xcfcfcf));
填充:g.fillRect(0,0,Global.WIDTH*Global.CELL_SIZE,Global.HEIGHT*Global.CELL_SIZE);
此時蛇也變成了白色,要在蛇的顯示方法中設定蛇身體的顏色:
g.setColor(Color.BLUE);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
21.此時paintComponent方法可能在出現窗體時就被呼叫,此時可能snake,food,ground物件還沒產生,所以可以新增一個判斷:
if(snake!=null&&food!=null&&ground!=null)
{this.snake.drawMe(g);
this.food.drawMe();
this.ground.drawMe();
}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
22.此時按鍵無法控制蛇:
為frame窗體新增一個事件監聽器,讓他捕獲使用者按鍵輸入:
frame.addKeyListener(controller);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
23.蛇存在跑出顯示區域邊界的情況:
要讓蛇從一邊離開顯示區時,從另一邊進入顯示區:
在Snake的move()方法中得到switch部分進行修改,增加對蛇離開顯示邊界的判斷:
if (y<0)                    
   y=Global.HEIGHT-1;
if (y>=Global.HEIGHT)                    
   y=0;
if (X<0)                    
   y=Global.WIDTH-1;
if (X>=Global.WIDTH)                    
   y=0;

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
24.在按鍵輸入的新方向與原移動方向相反時,會出現穿過身體相反移動的情況:
如何判斷相反方向:
修改Snake類中定義的方向常量的值:
UP=-1,DOWN=1;LEFT=2,RIGHT=-2
在改變方向的方法中加入判斷:
if(!(direction+this.direction==0))

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
25.還有一種按鍵方向與原移動方向相反的情況:即無效方向的影響:
如果在一次移動時間內,連續輸入了多個方向,只有最後一次鍵入為有效方向:
如:在蛇向左行進的過程中,在這一次移動之後,下一次移動之前的一段時間內,連續按出上右兩個方向鍵,就會造成新方向右與之前的移動方向左相反的情況。
解決方法:使用兩個變數存放舊的移動方向和新的移動方向:
oldDirection:存放舊的移動方向
newDirection:新的移動方向
修改改變方向的方法:
把輸入的方向賦值給newDirection
這樣就不用再判斷其相反性,因為只會儲存最後一次鍵入,之間的都會被覆蓋
接著需要在移動方法中判斷舊的移動方向oldDirection與新的移動方向newDirection是否相反:
if(!(oldDirection+newDirection==0))
oldDirection=newDirection;
最後修改switch的條件表示式為oldDirection
修改初始化方法,將oldDirection和newDirection都初始化為RIGHT

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
26.增加食物功能:
用Food類繼承Point類擴充套件其功能
食物顯示方法:接受引數(Graphics g)
填充矩形:g.fill3DRect(x*Global.CELL_SIZE,y*Global.CELL_SIZE,Global.CELL_SIZE,Global.CELL_SIZE,true);
判斷蛇吃到食物方法:接受引數(Sneke snake)
蛇頭與食物座標重合即為吃到了:
這裡需要在Snake類中新增獲取頭節點的方法getHead
public Point getHead()
{return get.body.getFirst}
然後再寫判斷方法:
return this.equals(snake.getHead());
此時gamePoint類中呼叫食物顯示方法的地方需要傳入引數

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
27.在遊戲開始時,初始化一個食物:
在此之前要新增一個獲取隨機座標的方法:
public Point getPoint()
{Random random = new Random();
int x=random.nextInt(Global.WIDTH);
int y=random.nextInt(Global.HEIGHT);
return new Point(x,y);
}
再呼叫newFood(new getPoint());

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
28.在蛇吃到食物後,產生新的食物,蛇變長:
修改Controller類中的snakeMoved方法
判斷蛇是否吃到食物:
if(food.isSnakeEatFood(snake))
{snake.eatFood();
food.newFood(getPoint());
}

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
29.實現蛇吃食物變長的方法:
在蛇頭增加一個節點,蛇尾節點不去掉
在此之前需要定義一個Point變數來儲存去掉的蛇尾
private Point oldTail;
在去尾時對oldTail賦值一次:
oldTail=body.removeLast();
然後實現eatFood方法:
body.addLast(oldTail);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
30.增加牆的功能:
用一個二維陣列儲存多塊牆體的資訊:
private int [][]rocks=new int [Global.WIDTH][Global.HEIGHT];
陣列中置為1的便是牆體矩形
定義一個建構函式來初始化牆體資訊:
public Ground()
{for(int x=0;x<Global.WIDTH;x++)
{rocks[x][0]=1;
rocks[x][Global.HEGHT-1]=1;
}
for(int y=0;y<Global.HEIGHT;y++)
{rocks[0][y]=1;
rocks[Global.WIDTH-1][y]=1;
}
}
實現顯示牆的方法:接收引數(Graphics g)
for(int x=0;x<Global.WIDTH;x++)
for(int y=0;y<Global.HEIGHT;y++)
if(rocks[x][y]==1)
g.fill3DRect(x*Global.CELL_SIZE,y*Global.CELL_SIZE,Global.CELL_SIZE,Global.CELL_SIZE,true);

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
31.實現蛇吃到牆的方法:接收引數(Snake snake)
for(int x=0;x<Global.WIDTH;x++)
for(int y=0;y<Global.HEIGHT;y++)
if(rocks[x][y]==1&&(x==snake.getHead().x&&y==snake.getHead().y))
return true;

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
32.實現蛇吃到牆後死掉的方法:
死掉,即退出執行緒。
可以定義一個布林變數life來控制蛇的生命(即執行緒的生命)
在蛇的初始化方法中將life置為true
新增一個讓蛇死掉的方法:
public void die()
{life=false;
}
在Controller類中的snakeMoved方法中判斷蛇是否吃到了牆,如果是,則呼叫die方法
if(ground.isSnakeEatGround(snake))
snake.die();

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
33.食物和牆出現重疊的情況:
將Controller類中產生隨機座標點的方法getPoint移動到Ground類中,讓其生成一個不會與牆座標重疊的座標點:
加入迴圈判斷:知道生成了一個不與牆重疊的座標,再跳出迴圈
do{
x=radom.nextInt(Global.WIDTH);
y=radom.nextInt(Global.HEIGHT);
}while(rocks[x][y]==1)
PS:這裡的x,y需要定義在do-while迴圈外

-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
34.增加蛇吃到自己身體死亡的功能:
Snake類中isEatBody方法的具體實現:遍歷身體所有節點,如果與蛇頭重合即為吃到身體
for(int i=1;i<body.size();i++)      PS:身體第一個節點為蛇頭,所以i從1開始迴圈
if(body.get(i).equals(this.getHead()))
return true;
最後在snakeMoved方法中呼叫該方法判斷蛇是否吃到身體,如果是,則呼叫die方法讓蛇死亡