学习用Java写简单贪吃蛇第二天

准备开始

贪吃蛇结点的构建

结点是使用双向链表来搭建的,里面定义了x和y分别表示结点所处的位置,用next代表下一个结点,用prev代表前一个结点,而之前的方向,定义在主方法中。

package exp2;

public class SnakeNode {
    private int x;
    private int y;
    protected SnakeNode next;
    protected SnakeNode prev;

    public SnakeNode(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

}

页面的显示搭建

这里写的是关于设置页面标题,设置页面图标,设置页面大小,以及上一篇文章中说的将贪吃蛇画的JPanel进入加载。

package exp2;

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

public class Snake {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setBounds(450,160,900,758);
        frame.setResizable(false);
        //点击关闭能真正关闭
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //添加画布
        frame.add(new exp2.MyPanel());
        //设置标题和icon
        frame.setTitle("贪吃蛇小游戏");
        URL url = frame.getClass().getResource(".\\..\\..\\images\\snakeIcon.png");
        //System.out.println(url);
        Image img = Toolkit.getDefaultToolkit().getImage(url);
        frame.setIconImage(img);
        frame.setVisible(true);
    }
}

大部分功能的实现

贪吃蛇的开始初始化

对于如何在程序运行时,我将贪吃蛇的初始化封装成了一个方法,只要在JPanel刚创建调用构造方法时进行调用。

private void initSnake() {

        SnakeNode snake1 = new SnakeNode(125,135);
        initNode(snake1);
        SnakeNode snake2 = new SnakeNode(100,135);
        initNode(snake2);
        SnakeNode snake3 = new SnakeNode(75,135);
        initNode(snake3);
        setMinFood();
        bigFoodNum = 1;
        direction = "R";
        score = 0;
        setBigFoodXY();
        hasBigFood = false;
        time.start();
    }
private void initNode(SnakeNode node) {
        if (first == null) {
            p = node;
            first = node;
            end = node;
            p.next = null;
            p.prev = null;
        } else {
            p.next = node;
            node.prev = p;
            p = p.next;
            end = p;
        }
        len ++;
    }

里面设置了很多参数,比如初始方向为向右,设置初始食物位置,是否需要设置特殊食物,设置开始分数等等。(具体代码在文末)

放置普通食物

放置食物需要考虑到,食物的位置会不会超出frame框的位置大小,放置的位置会不会和蛇或者特殊食物重叠。

private void setMinFood() {
        Boolean setFood;
        do {
            setFood = true;
            foodx = 25 + 25 * rand.nextInt(34);
            foody = 110 + 25 * rand.nextInt(24);
            p = first;
            // 防止生成的食物在蛇身上
            while (p != null) {
                if (p.getX() == foodx && p.getY() == foody) {
                    setFood = false;
                    break;
                }
                p = p.next;
            }
            if ((foodx == bigFoodx && foody == bigFoody)
                    || (foodx == bigFoodx -25 && foody == bigFoody)
                    || (foodx == bigFoodx && foody == bigFoody - 25)
                    || (foodx == bigFoodx -25 && foody == bigFoody - 25)) {
                setFood = false;
            }
        } while (!setFood);

    }

当然,放置特殊食物也是这样。

判断蛇是否咬到了自己

从头结点以下开始遍历,如果遍历到了身子结点(暂且这么讲),处于的位置相同,说明咬到了自己,返回true代表游戏失败了。在其他方法调用此方法时,就可以判断游戏需要终止了。

private boolean failedCheckOut() {
        p = first.next;
        while (p != null) {
            if (p.getX() == first.getX() && p.getY() == first.getY()) {
                return true;
            }
            p = p.next;
        }
        return false;
    }

在这里插入图片描述

设置吃到食物后的发生

这里就不详细解释了,具体解释请看第一天内容,谢谢大家!

private void getMinFood() {
        toEnd.next = null;
        toEnd.prev = end;
        end.next = toEnd;
        end = toEnd;
        score += 10 * difficulty;
        bigFoodNum++;
        len ++;
    }

设置分数的增加与难度成正比,这里的bigFoodNum是用来判断吃到多少普通食物就会生成一个特殊食物,以及蛇的长度增加。

设置方向键的限定

为什么要限定方向键?您想,如果蛇是往右走的,如果您的方向键按了一下左,下一秒可能直接就咬到自己了,像我这样手比较滑的人,轻轻按一下整个游戏就失败了,所以我设置了方向键的限定。当然,下面的内容也包含了如何设置空格键为游戏的开始与暂停,以及重新开始。

@Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();//获取键盘背后对应的数字
        if (keyCode == KeyEvent.VK_SPACE) {
            if (isFailed) {
                isFailed = false;
                toRestart();
            } else {
                isStarted = !isStarted;
            }
            if (isStarted) {
                playBGM();
            } else {
                stopBGM();
            }
            repaint();
        } else if (keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) {
            tempDir = "L";
            direction = tempDir.equals(direction ) ? "L" : "R";
        } else if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) {
            tempDir = "R";
            direction = tempDir.equals(direction) ? "R" : "L";
        } else if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) {
            tempDir = "D";
            direction = tempDir.equals(direction) ? "D" : "U";
        } else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) {
            tempDir = "U";
            direction = tempDir.equals(direction) ? "U" : "D";
        }
    }

时间时钟到了执行的方法

Java中有一个Timer类,创建的对象可以每隔一段时间调用下面的方法。下面这个方法可以改变蛇的位置,判断是否吃到了食物。里面也有许多下面一部分要讲的完善内容,具体请看下面一部分。

//时钟时间到了就会调用这个移动方法
    @Override
    public void actionPerformed(ActionEvent e) {
        if (isStarted && !isFailed) {
            //蛇进行移动
            //防止end结点改变,新建时比较简单
            toEnd = new SnakeNode(end.getX(),end.getY());
            p = end.prev;
            if (direction == "R") {
                end.setX(first.getX() + speed);
                end.setY(first.getY());
                if (end.getX() >= 875 && model == 1) {
                    end.setX(25);
                } else if (end.getX() >= 875 && model == 2) {
                    isFailed = true;
                }
            } else if (direction == "L") {
                end.setX(first.getX() - speed);
                end.setY(first.getY());
                if (end.getX() <= 0 && model == 1) {
                    end.setX(850);
                } else if (end.getX() <= 0 && model == 2) {
                    isFailed = true;
                }
            } else if (direction == "U") {
                end.setX(first.getX());
                end.setY(first.getY() - speed);
                if (end.getY() <= 85 && model == 1) {
                    end.setY(685);
                } else if (end.getY() <= 85 && model == 2) {
                    isFailed = true;
                }
            } else if (direction == "D") {
                end.setX(first.getX());
                end.setY(first.getY() + speed);
                if (end.getY() >= 710 && model == 1) {
                    end.setY(110);
                } else if (end.getY() >= 710 && model == 2) {
                    isFailed = true;
                }
            }
            p.next = null;
            end.next = first;
            first.prev = end;
            first = end;
            end = p;

            //蛇头与食物是否重合
            if (first.getX() == foodx && first.getY() == foody) {
                getMinFood();
                setMinFood();
            }
            if ((first.getX() >= bigFoodx)
                    && (first.getX() <= bigFoodx + 25)
                    && (first.getY() >= bigFoody)
                    && (first.getY() <= bigFoody + 25)) {
                getBigFood();
                slowTimes += 20;
            }
            if (failedCheckOut()) {
                isFailed = true;
                stopBGM();
            }
            repaint();
        }
        //time.setDelay(timeDely);
        if (slowTimes != 0) {
            time.setDelay(200);
            setBackground(Color.BLUE);
            slowTimes --;
        } else if (difficulty == easy) {
            time.setDelay(150);
        } else if (difficulty == medium) {
            time.setDelay(100);
        } else if (difficulty == hard) {
            time.setDelay(60);
        }
        time.start();
    }

绘图

将上面讲的总结起来,在绘图功能中实现,每隔很短的时间执行绘图操作,对于我们就感觉在动一样啦。

public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //添加标题
        title.paintIcon(this,g,25,11);
        //绘制游戏区
        g.fillRect(25,110,850,600);
        //打印分数和长度
        g.setColor(Color.WHITE);
        g.setFont(new Font("arial", Font.PLAIN, 30));
        g.drawString("Length:" + len,730,50);
        g.drawString("Score:" + score,730,80);

        //打印蛇
        if (direction.equals("R")) {
            right.paintIcon(this, g, first.getX(), first.getY());
        } else if (direction.equals("L")) {
            left.paintIcon(this, g, first.getX(), first.getY());
        } else if (direction.equals("U")) {
            up.paintIcon(this, g, first.getX(), first.getY());
        } else if (direction.equals("D")) {
            down.paintIcon(this, g, first.getX(), first.getY());
        }
        p = first.next;
        while (p != null) {
            body.paintIcon(this, g, p.getX(), p.getY());
            p = p.next;
        }
        //打印食物
        food.paintIcon(this,g,foodx,foody);
        if (len < 150) {
            if (bigFoodNum % 6 == 0) {
                hasBigFood = true;
            }
            if (hasBigFood) {
                bigFood.paintIcon(this,g,bigFoodx,bigFoody);
            }
        }
        if (len == 10) {
            g.setColor(Color.YELLOW);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("So nice!!!",100,200);
        }
        //显示背景
        if (slowTimes != 0) {
            setBackground(Color.GREEN);
            g.setColor(Color.WHITE);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("Slow down!",600,600);
        } else {
            setBackground(Color.WHITE);
        }
        //打印开始
        if (!isStarted && !isFailed) {
            g.setColor(Color.YELLOW);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("Press Space to Start",300,300);
        }
        //重新开始
        if (isFailed) {
            g.setColor(Color.RED);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("Failed! Press Space to Restart",200,300);
        }

    }

完善

非常感谢您能读到这里,谢谢您!相信如果您仔细看的话,上面的内容里有许多我已经完善好的东西。就比如说:

吃完特殊食物会怎么样

(很抱歉不能再复制一次上面的代码,影响您的阅读,因为会比较长,您可以直接在文末下载代码来阅读,谢谢)在paintComponent方法中,如果吃下特殊食物,slowTimes会增加一定的数,当它不为0时,我设置了背景颜色变为绿色,以及显示Slow down和速度变为200的时间内刷新,为0背景是白色的。

此处在paintComponent方法中

//显示背景
        if (slowTimes != 0) {
            setBackground(Color.GREEN);
            g.setColor(Color.WHITE);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("Slow down!",600,600);
        } else {
            setBackground(Color.WHITE);
        }
private void getBigFood() {
        toEnd.next = null;
        toEnd.prev = end;
        end.next = toEnd;
        end = toEnd;
        score += 20 * difficulty;
        len ++;
        bigFoodNum ++;
        hasBigFood = false;
        slowTimes += 20;
        setBigFoodXY();
    }

此处在actionPerformed方法中

//time.setDelay(timeDely);
        if (slowTimes != 0 && isStarted) {
            time.setDelay(200);
            setBackground(Color.BLUE);
            slowTimes --;
        } else if (difficulty == easy) {
            time.setDelay(150);
        } else if (difficulty == medium) {
            time.setDelay(100);
        } else if (difficulty == hard) {
            time.setDelay(60);
        }
        time.start();

当蛇的长度到达一定长度会怎么样

我这里只写了会在界面打印“So nice”,比较菜,见谅。(比如可以速度加快什么的)

if (len == 10) {
            g.setColor(Color.YELLOW);
            g.setFont(new Font("arial", Font.BOLD, 40));
            g.drawString("So nice!!!",100,200);
        }

游戏音乐的开启

private void LoadBGM() {
        try {
            //如果bgm文件比较大,使用SourseDataLine
            bgm = AudioSystem.getClip();
            InputStream is = this.getClass().getResourceAsStream("..\\music\\bgm.wav");
            AudioInputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(is));
            bgm.open(ais);
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

音乐的循环播放以及按下space键的停止播放

private void playBGM() {
        bgm.loop(Clip.LOOP_CONTINUOUSLY);
    }

    private void stopBGM() {
        bgm.stop();
    }

游戏上方按钮的绘制

按钮的创建

private final JMenuBar jMenuBar = new JMenuBar();
    private final JMenu choose = new JMenu("选项");
    private final JMenuItem stop = new JMenuItem("暂停");
    private final JMenuItem start = new JMenuItem("开始");
    private final JMenu difChoose = new JMenu("难度选择");
    private final JMenuItem dif1 = new JMenuItem("难度1");
    private final JMenuItem dif2 = new JMenuItem("难度2");
    private final JMenuItem dif3 = new JMenuItem("难度3");
    private final JMenu modelChoose = new JMenu("模式选择");
    private final JMenuItem model1 = new JMenuItem("不碰壁模式");
    private final JMenuItem model2 = new JMenuItem("碰壁模式");
    private final JMenuItem exit = new JMenuItem("退出");

按钮的初始化

private void initItem() {

        difChoose.add(dif1);
        difChoose.add(dif2);
        difChoose.add(dif3);
        modelChoose.add(model1);
        modelChoose.add(model2);
        jMenuBar.add(start);
        jMenuBar.add(stop);
        jMenuBar.add(difChoose);
        jMenuBar.add(modelChoose);
        jMenuBar.add(exit);
        //panel.add(popMenu);
        //设置字体
        choose.setFont(new Font("宋体",Font.BOLD,20));
        difChoose.setFont(new Font("宋体",Font.BOLD,20));
        modelChoose.setFont(new Font("宋体",Font.BOLD,20));
        setJMenuItemFont(dif1);
        setJMenuItemFont(dif2);
        setJMenuItemFont(dif3);
        setJMenuItemFont(model1);
        setJMenuItemFont(model2);
        setJMenuItemFont(start);
        setJMenuItemFont(stop);
        setJMenuItemFont(exit);
        this.add(jMenuBar);
        start.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (isFailed) {
                    isFailed = false;
                    toRestart();
                } else {
                    isStarted = true;
                }
                playBGM();
                repaint();
            }
        });
        stop.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                isStarted = false;
                stopBGM();
                repaint();
            }
        });
        dif1.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                difficulty = easy;
            }
        });
        dif2.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                difficulty = medium;
            }
        });
        dif3.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                difficulty = hard;
            }
        });
        model1.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                model = 1;
            }
        });
        model2.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                model = 2;
            }
        });
        exit.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                System.exit(0);
            }
        });
    }

根据难度以及模式判断

模式为1表明为穿墙模式,模式为2表明为不可穿墙模式
此处的1和2没有见名知意,不好意思。

if (direction == "R") {
                end.setX(first.getX() + speed);
                end.setY(first.getY());
                if (end.getX() >= 875 && model == 1) {
                    end.setX(25);
                } else if (end.getX() >= 875 && model == 2) {
                    isFailed = true;
                }

难度的设定:简单时间延迟设定为150,中等难度设定为100,困难为60(如果迟到特殊食物设定为200)

//time.setDelay(timeDely);
        if (slowTimes != 0 && isStarted) {
            time.setDelay(200);
            setBackground(Color.BLUE);
            slowTimes --;
        } else if (difficulty == easy) {
            time.setDelay(150);
        } else if (difficulty == medium) {
            time.setDelay(100);
        } else if (difficulty == hard) {
            time.setDelay(60);
        }
        time.start();

具体展示

在这里插入图片描述在这里插入图片描述

总结

从贪吃蛇的制作学习到了很多数据结构以及算法上的内容,相关的资源请看链接链接:https://pan.baidu.com/s/1UKitV8ccNaEi1ccNm9TKbg
提取码:oefj
GitHub链接:https://github.com/Braden-hey/Snake

好的,以下是一个简单Java贪吃蛇游戏示例代码: ```java import java.awt.Color; import java.awt.Graphics; import java.awt.Point; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.LinkedList; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; public class SnakeGame extends JPanel implements Runnable, KeyListener { private static final long serialVersionUID = 1L; public static final int WIDTH = 500, HEIGHT = 500; private Thread thread; private boolean running; private Snake snake; private LinkedList<Point> food; private int direction; private boolean keyLock; private Random rand; public SnakeGame() { setPreferredSize(new Dimension(WIDTH, HEIGHT)); setFocusable(true); addKeyListener(this); snake = new Snake(); food = new LinkedList<>(); rand = new Random(); direction = Snake.RIGHT; keyLock = false; start(); } public void start() { running = true; thread = new Thread(this); thread.start(); } public void stop() { running = false; try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } public void run() { while (running) { update(); repaint(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public void update() { if (snake.isDead()) { stop(); return; } if (!food.isEmpty() && snake.head().equals(food.get(0))) { snake.eat(); food.remove(0); } if (rand.nextInt(50) == 0 && food.size() < 3) { int x = rand.nextInt(WIDTH / Snake.BLOCK_SIZE) * Snake.BLOCK_SIZE; int y = rand.nextInt(HEIGHT / Snake.BLOCK_SIZE) * Snake.BLOCK_SIZE; food.add(new Point(x, y)); } switch (direction) { case Snake.UP: snake.moveUp(); break; case Snake.DOWN: snake.moveDown(); break; case Snake.LEFT: snake.moveLeft(); break; case Snake.RIGHT: snake.moveRight(); break; } keyLock = false; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLACK); g.fillRect(0, 0, WIDTH, HEIGHT); snake.draw(g); g.setColor(Color.RED); for (Point p : food) { g.fillRect(p.x, p.y, Snake.BLOCK_SIZE, Snake.BLOCK_SIZE); } } public void keyPressed(KeyEvent e) { if (keyLock) return; switch (e.getKeyCode()) { case KeyEvent.VK_UP: if (direction != Snake.DOWN) direction = Snake.UP; break; case KeyEvent.VK_DOWN: if (direction != Snake.UP) direction = Snake.DOWN; break; case KeyEvent.VK_LEFT: if (direction != Snake.RIGHT) direction = Snake.LEFT; break; case KeyEvent.VK_RIGHT: if (direction != Snake.LEFT) direction = Snake.RIGHT; break; } keyLock = true; } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } public static void main(String[] args) { JFrame frame = new JFrame("Snake Game"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setResizable(false); frame.add(new SnakeGame()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } } class Snake { public static final int BLOCK_SIZE = 10; public static final int UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3; private LinkedList<Point> body; private boolean dead; public Snake() { body = new LinkedList<>(); body.add(new Point(200, 200)); body.add(new Point(200, 210)); body.add(new Point(200, 220)); dead = false; } public void moveUp() { Point head = head(); Point newHead = new Point(head.x, head.y - BLOCK_SIZE); if (checkCollision(newHead)) dead = true; else { body.addFirst(newHead); body.removeLast(); } } public void moveDown() { Point head = head(); Point newHead = new Point(head.x, head.y + BLOCK_SIZE); if (checkCollision(newHead)) dead = true; else { body.addFirst(newHead); body.removeLast(); } } public void moveLeft() { Point head = head(); Point newHead = new Point(head.x - BLOCK_SIZE, head.y); if (checkCollision(newHead)) dead = true; else { body.addFirst(newHead); body.removeLast(); } } public void moveRight() { Point head = head(); Point newHead = new Point(head.x + BLOCK_SIZE, head.y); if (checkCollision(newHead)) dead = true; else { body.addFirst(newHead); body.removeLast(); } } public void eat() { Point tail = tail(); Point newTail = new Point(tail.x, tail.y); body.addLast(newTail); } public Point head() { return body.getFirst(); } public Point tail() { return body.getLast(); } public boolean isDead() { return dead; } public boolean checkCollision(Point p) { if (p.x < 0 || p.x >= SnakeGame.WIDTH || p.y < 0 || p.y >= SnakeGame.HEIGHT) return true; for (Point bodyPart : body) { if (p.equals(bodyPart)) return true; } return false; } public void draw(Graphics g) { g.setColor(Color.WHITE); for (Point bodyPart : body) { g.fillRect(bodyPart.x, bodyPart.y, BLOCK_SIZE, BLOCK_SIZE); } } } ``` 这是一个基本的贪吃蛇游戏,可以通过方向键来控制蛇的移动。在游戏中,蛇会一直自动向前移动,当蛇头碰到边界或自己的身体时,游戏结束。游戏中会随机生成食物,蛇可以吃掉食物并增长身体长度。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值