首页 > 技术文章 > 用java实现贪吃蛇小游戏

susexuexi011 2021-01-20 10:41 原文

在本类中目的是做一个背景和画板,实现贪吃蛇游戏的一个动态效果

package Snake;

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

public class Background {
    //容器对象
    private JFrame sanke_jf = new JFrame("贪吃蛇小游戏");
    private JPanel jPanel = new JPanel();
    private SnakeClass snake;//
    private GetFood.Food food;//食物
    public Background( SnakeClass snake, GetFood.Food food) {
        //构造器
        this.food = food;
        this.snake = snake;
    }
    //背景的大小
    public static final int BACK_WIDTH = 1210;
    public static final int BACK_HEIGHT = 858;
    //游戏相关信息的画布和画笔
    private class DrawArea extends Canvas {
        @Override
        public void paint(Graphics g) {
            if (Is.isOver) {
                g.setColor(Color.black);
                g.setFont(new Font("Times", Font.BOLD, 45));
                g.drawString("游戏结束!", 600, 350);
                g.drawString("你的分数:"+food.count, 600, 400);
                g.drawString("历史最高分:"+SaveMaxCount.read(), 600, 450);
            } else {
                if (food.color==5||food.color==10||food.color==7){
                    g.setColor(Color.orange);
                    g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA);
                }else if(food.color==4||food.color==8){
                    g.setColor(Color.black);
                    g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA);
                }else {
                    g.setColor(Color.GREEN);
                    g.fillOval(food.foodx, food.foody, Node.NODE_DIA, Node.NODE_DIA);
                }
                //蛇头(黄色)
                g.setColor(Color.orange);
                g.fillOval(snake.head.nodex, snake.head.nodey, Node.NODE_DIA, Node.NODE_DIA);

                //蛇的身体(蓝色)
                g.setColor(Color.BLUE);
                Node temp = snake.head;
                while (true) {
                    if (temp.next == null) {
                        g.fillOval(temp.nodex, temp.nodey, Node.NODE_DIA, Node.NODE_DIA);
                        break;
                    }
                    temp = temp.next;
                    g.fillOval(temp.nodex, temp.nodey, Node.NODE_DIA, Node.NODE_DIA);

                }
            }
        }
    }

    private Timer timer;//多久自动执行一次相关代码的对象

    //创建绘画区域对象
    DrawArea drawArea = new DrawArea();
    //组装显示界面的方法
    public void draw_back() {
        drawArea.setPreferredSize(new Dimension(BACK_WIDTH, BACK_HEIGHT));//为绘画区域设置大小
        //键盘监听器,用键盘控制蛇的移动
       KeyListener listener = new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                snake.move(keyCode);
            }
        };

       //为相关区域添加监听器
       jPanel.addKeyListener(listener);
        sanke_jf.addKeyListener(listener);
        drawArea.addKeyListener(listener);
        //设置JFrame容器的大小
        sanke_jf.setSize(BACK_WIDTH, BACK_HEIGHT);
        //每过指定时间自动执行的任务代码
        ActionListener tack = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                drawArea.repaint();//重画,实现视觉效果
                snake.autmove();
                if (Is.isOver) timer.stop();
            }
        };
        timer = new Timer(230, tack);
        timer.start();
        //将绘画区域添加到容器中
        jPanel.add(drawArea);
        sanke_jf.add(jPanel);
        sanke_jf.setResizable(false);//设置JFrame的大小不可变
        sanke_jf.setVisible(true);//设置窗口可见
        //点击右上角“x”号结束程序
        sanke_jf.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                System.exit(0);
            }
        });
    }
//该类是食物相关类,实现同一时间有且仅有一个食物存在,主要利用多线程的通信机制

package
Snake; import java.util.Random; public class GetFood { //食物消失与生成的类 //每一个食物也是一个节点 Random rx = new Random();//随机生成食物的x坐标 Random ry = new Random();//随机生成食物的y坐标 Random rc = new Random(); //将背景按节点大小划分为一定的格子,食物只能产生在格子中以保证蛇一定可以吃到 int rowy = Background.BACK_WIDTH/Node.NODE_DIA; int rowx = Background.BACK_HEIGHT/Node.NODE_DIA; //食物类 class Food{ //坐标 public int foodx =-1; public int foody =-1; public int color = 1; //是否需要产生食物 public boolean isGetFood = true; //计算分数 int count=0; } //用多线程实现食物的消亡与产生,利用等待唤醒机制保证食物的同步 class CreateFood extends Thread { //提供锁对象 private Food food; public CreateFood(Food food){ this.food=food; } @Override public void run() { while(true){ synchronized (food){ if(!food.isGetFood){ try { food.wait();//如果有食物进入等待 } catch (InterruptedException e) { e.printStackTrace(); } }else{ //如果没有食物随机生成一个食物(坐标) if(Is.is_Strength){ food.color=(int)(Math.random()*10); } food.foodx=Node.NODE_DIA*(rx.nextInt(rowy-2)+1); food.foody=Node.NODE_DIA*(ry.nextInt(rowx-3));//设置一定范围,增强游戏体验 food.isGetFood=false;//产生食物以后修改食物的状态 food.notify();//唤醒吃食物的线程 } } } } } class EatFood extends Thread{ private Food food;//提供锁对象 private SnakeClass snake;//蛇(与食物发生碰撞) public EatFood(Food food,SnakeClass snake){ //构造器 this.food=food; this.snake=snake; } @Override public void run() { while(true){ synchronized (food){ if(food.isGetFood){ try { food.wait();//如果没有食物,进入等待 } catch (InterruptedException e) { e.printStackTrace(); } }else{ //如果有食物进 if (snake.head.nodex==food.foodx&&snake.head.nodey==food.foody){ //如果蛇头食物发生碰撞 if(food.color==5||food.color==10||food.color==7){ snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); food.foodx=-1; food.foody=-1; food.count+=2; }else if(food.color==4||food.color==8){ snake.deleteTail(); food.foodx=-1; food.foody=-1; food.count--; try { Thread.sleep(3500); } catch (InterruptedException e) { e.printStackTrace(); }if(food.count>=SaveMaxCount.read()){ SaveMaxCount.save(food.count); } food.isGetFood=true;//修改食物状态 food.notify();//唤醒产生食物的线程 } else{ snake.add_Tail(new Node(snake.get_Tail().nodex,snake.get_Tail().nodey)); food.foodx=-1; food.foody=-1; food.count++; } // System.out.println(food.color);(在测试时查看随机数) //如果当前分数大于历史最大分数,则将当前分数存入文件 if(!food.isGetFood){ food.isGetFood=true;//修改食物状态 food.notify();//唤醒产生食物的线程 } } } } } } } }
//游戏入口
public
class Inner { public static void main(String[] args) { new StartFrame().start_window(); } }
//游戏模式和游戏是否结束的开关
package
Snake; public class Is { public static boolean isOver = false;//表示游戏是否结束 public static boolean is_nodie=false; public static boolean is_Strength=false; }
package Snake;

//蛇的没一个节点的基本单位,因为在本游戏中不断地进行删除与增加,所以选择使用链表
public class Node {

    //表示蛇的每一个节点(链表结构)

    public  Node next;//指向下一个
    public  int nodex;//节点的x坐标
    public  int  nodey;//节点的y坐标
    public  static final int NODE_DIA = 22;//节点的大小

    public Node(int nodex,int nodey){
        //构造器
        this.nodex=nodex;
        this.nodey=nodey;
    }
}
package Snake;
//游戏的初始化信息和模式设置
public class RunGameMethod {

    public static void method1(){//标准模式
        Is.is_nodie=false;
        Is.is_Strength=false;
        original();
    }
    public static void method2(){//无尽模式
        Is.is_Strength=false;
        Is.is_nodie=true;
        original();
    }
    public static void method3(){//无尽模式
        Is.is_nodie=false;
        Is.is_Strength=true;
        original();
    }

    private static  void original(){
        SaveMaxCount filecount  = new SaveMaxCount();//创建保存最高分数的对象

        //初始化一条小蛇
        Node head = new Node(2 * Node.NODE_DIA, 0);
        Node body1 = new Node(Node.NODE_DIA, 0);
        Node body2 = new Node(0, 0);
        SnakeClass snake = new SnakeClass(head);
        snake.add_Tail(body1);
        snake.add_Tail(body2);

        //创建食物的对象
        GetFood.Food food = new GetFood().new Food();
        //显示游戏运行界面
        Background back = new Background(snake, food);
        back.draw_back();
        //开启产生食物和吃食物的两个线程,并将食物对象作为锁对象
        new GetFood().new EatFood(food, snake).start();
        new GetFood().new CreateFood(food).start();
    }
}
package Snake;
//利用文件保存游戏的最高分
import java.io.*; public class SaveMaxCount { //保存最高分数的文件位置 private static File file_normal = new File("C:\\Users\\lenovo\\IdeaProjects\\MyJava\\src\\Snake\\savecount.txt"); private static File file_Strength= new File("C:\\Users\\lenovo\\IdeaProjects\\MyJava\\src\\Snake\\save_strength.txt"); private static File file = file_normal; private static File getFile() { if(Is.is_Strength){ return file_Strength; } return file; } //存储分数的方法 public static void save(int count){ FileOutputStream fos = null; try{ fos = new FileOutputStream(SaveMaxCount.getFile()); fos.write(count); }catch(IOException e){ e.getMessage(); }finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } //得到历史最高分数的方法 public static int read(){ FileInputStream fis = null; int count = 0; try { fis=new FileInputStream(SaveMaxCount.getFile()); count=fis.read(); } catch (IOException e) { e.printStackTrace(); }finally{ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } return count; } }
package Snake;
//实现蛇的构造、移动、游戏结束逻辑的控制。
public class SnakeClass {

    public SnakeClass(){

    }
    //蛇的构造器,重点是头节点  IS是为了在整个程序中保证判断结束语句的对象的一致性
    public SnakeClass( Node head) {
        this.head = head;
    }
    public Node head;//头结点
    public Node tail;//尾节点
    //增加蛇的长度(向尾部添加一个节点)
    public void add_Tail(Node node) {
        Node temp = head;
        while (true) {
            if (temp.next == null) {
                //此时temp为当前的尾节点
                break;
            }
            temp = temp.next;
        }
        temp.next = node;
    }

    //在头结点的后面和原头结点的下一个节点中间插入一个节点(移动的第一步)
    public void add_head(Node node) {
        Node temp = head.next;
        head.next = node;
        node.next = temp;
    }

    //得到当前的尾节点(在得分和移动时都需要用到)
    public Node get_Tail() {
        Node temp = head;
        while (true) {
            if (temp.next == null) {
                tail = temp;
                return tail;
            }
            temp = temp.next;
        }
    }

    //删除尾节点(移动的第二步)
    public void deleteTail() {
        Node temp = head;
        Node temp2 = temp;
        while (true) {
            if (temp.next == null) {
                temp2.next = null;
                return;
            }
            temp2 = temp;
            temp = temp.next;
        }
    }

    //蛇头撞到了自身,游戏结束逻辑的一方面
    private boolean isOver_Self(){
        boolean b = false;
        Node temp = head.next;
        while(true){
            if(head.nodey==temp.nodey&&head.nodex==temp.nodex){
                b=true;
                break;
            }else if(temp.next==null){
                break;
            }
            temp=temp.next;
        }
        return b;
    }

    //游戏结束的逻辑
    public   boolean isOver(){
        return  head.nodex < Node.NODE_DIA || head.nodex > (Background.BACK_WIDTH - 2 * Node.NODE_DIA) ||
                head.nodey < -0 || head.nodey > (Background.BACK_HEIGHT - 4 * Node.NODE_DIA)||isOver_Self();
    }

    //移动的方法(改变方向)(将头结点按指定方法移动一个单位,在其后新增加一个单位,删除尾节点
    //判断蛇是否可以向右移动,正在向左方向移动
    //条件成立不能该方向移动
    private boolean can_moveRight(){
        return head.nodey==head.next.nodey&&head.nodex<head.next.nodex;
    }
    //判断蛇是否可以向左移动,正在向右方向移动
    //条件成立不能该方向移动
    private boolean can_moveLeft(){
        return head.nodey==head.next.nodey&&head.nodex>head.next.nodex;
    }
    //判断蛇是否可以向上移动,正在向下方向移动
    //条件成立不能该方向移动
    private boolean can_moveUp(){
        return head.nodex==head.next.nodex&&head.nodey<head.next.nodey;
    }
    //判断蛇是否可以向下移动,正在向上方向移动
    //条件成立不能该方向移动
    private boolean can_moveDown(){
        return head.nodex==head.next.nodex&&head.nodey>head.next.nodey;
    }

    public void move(int ch) {
        boolean b=isOver();
        //撞到边界的判断逻辑,满足条件游戏结束
        int tempx = head.nodex;
        int tempy = head.nodey;
        //新增节点的x和y坐标
        switch (ch) {
            case 37:
                if(can_moveLeft()) break;
                if(!Is.is_nodie){
                    if (b) Is.isOver = true;
                }
                head.nodex -= Node.NODE_DIA;
                add_head(new Node(tempx, tempy));
                deleteTail();
                break;
            case 40:
                if(can_moveUp()) break;
                if(!Is.is_nodie){
                    if (b) Is.isOver = true;
                }
                head.nodey += Node.NODE_DIA;
                add_head(new Node(tempx, tempy));
                deleteTail();
                break;
            case 39:
                if(can_moveRight()) break;
                if(!Is.is_nodie){
                    if (b) Is.isOver = true;
                }
                head.nodex += Node.NODE_DIA;
                add_head(new Node(tempx, tempy));
                deleteTail();
                break;
            case 38:
                if(can_moveDown()) break;
                if(!Is.is_nodie){
                    if (b) Is.isOver = true;
                }
                head.nodey -= Node.NODE_DIA;
                add_head(new Node(tempx, tempy));
                deleteTail();
                break;
            default:
                break;
        }
    }

    //实现蛇的自动移动(与第一种移动方式有相似的逻辑,但不会改变方向)
    public void autmove(){
        int tempx = head.nodex;
        int tempy = head.nodey;
        boolean b=isOver();
        if(can_moveRight()){
            if(!Is.is_nodie){
                if (b) Is.isOver = true;
            }
            head.nodex -= Node.NODE_DIA;
            add_head(new Node(tempx, tempy));
            deleteTail();
        }else if(can_moveLeft()){
            if(!Is.is_nodie){
                if (b) Is.isOver = true;
            }
            head.nodex += Node.NODE_DIA;
            add_head(new Node(tempx, tempy));
            deleteTail();
        }else if (can_moveUp()){
            if(!Is.is_nodie){
                if (b) Is.isOver = true;
            }
            head.nodey -= Node.NODE_DIA;
            add_head(new Node(tempx, tempy));
            deleteTail();
        }else{
            if(!Is.is_nodie){
                if (b) Is.isOver = true;
            }
            head.nodey += Node.NODE_DIA;
            add_head(new Node(tempx, tempy));
            deleteTail();
        }
    }
}
package Snake;
//游戏开始界面
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class StartFrame implements ActionListener{

    private  JFrame start_trame = new JFrame("游戏模式选择");
    private  JPanel start_panle = new JPanel();
    private   JButton but1 = new JButton("标准模式");
    private  JButton but2 = new JButton("无尽模式");
    private   JButton but3 = new JButton("强化模式");
    private   JButton but4 = new JButton("游戏信息");
    private   String str;
    public  void start_window(){
        but1.addActionListener(this);
        but2.addActionListener(this);
        but3.addActionListener(this);
        but4.addActionListener(this);
        start_panle.add(but1);
        start_panle.add(but2);
        start_panle.add(but3);
        start_panle.add(but4);
        start_panle.setLayout(new GridLayout(4,1));
        start_trame.add(start_panle);
        start_trame.setSize(400,500);
        start_trame.setVisible(true);
        start_trame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Is.isOver=false;
        if(e.getSource()==but1){
            RunGameMethod.method1();
        }else if(e.getSource()==but2){
            RunGameMethod.method2();
        }else if(e.getSource()==but3){
            RunGameMethod.method3();
        }else{
            str="你可以通过“↑”“↓”“←”“→”控制一条小蛇去吃食物,注意不要撞到边界和自己的身体!\n" +
                    "在无尽模式中你可以任意移动而不担心死亡,在强化模式中有三种不同的食物各有不同的效果!\n" +
                    "快点开始你的游戏之旅吧!\n" +
                    "\n\n" +
            JOptionPane.showMessageDialog(null,str);
        }
    }
}

 

推荐阅读