贪吃蛇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方法让蛇死亡

猜你喜欢

转载自blog.csdn.net/QiuBika_061/article/details/84326190