贪 吃 蛇

news2024/12/23 20:25:17

简介

简易贪吃蛇,使用 javax.swing 组件构建游戏界面,通过监听键盘按键实现游戏操纵。

功能设计

  • 按1 - 开始游戏
  • 按2 - 重新开始
  • 按3 - 暂停/继续
  • 按Esc-退出游戏
  • 统计吃到的苹果个数(得分)
  • 难度控制,得分超过阈值时难度增加(蛇身移动速度加快)

实现

定义 SnakeGame 类:
继承 JPanel 类, 重写其由 JComponent 类中继承的 paintComponent 方法,在此方法中进行图像的绘制。
实现 ActionListener 类, 实现其 actionPerformed 方法, 通过监听在 SnakeGame 类中的Timer 计时器步长内按键的输入完成对图像的操作。

public class SnakeGame extends JPanel implements ActionListener {

	//(timer = new Timer(delay, this)).start();

	public void paintComponent(Graphics g) {
	    //在这里操作图像的绘制,蛇身和苹果等
	}
	
	public void actionPerformed(ActionEvent e) {
	    //在这里通过对定时器的监听完成对图像的操作
	}
	
}

自定义按键适配器, 将键盘输入转换为程序识别的方向值,同时记录键盘输入。


/**
 * 定义方向
 */
public interface Direction {
        char LEFT = 'L';
        char RIGHT = 'R';
        char UP = 'U';
        char DOWN = 'D';
    }

/**
 * 按键适配器,用于监听输入按键
 */
public class MyKeyAdapter extends KeyAdapter {
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        eventKeyCode = e.getKeyCode();
        if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) && direction != Direction.RIGHT) {
            direction = Direction.LEFT;
        } else if ((keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) && direction != Direction.LEFT) {
            direction = Direction.RIGHT;
        } else if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) && direction != Direction.DOWN) {
            direction = Direction.UP;
        } else if ((keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) && direction != Direction.UP) {
            direction = Direction.DOWN;
        }
    }
}

在 paintComponent 和 actionPerformed 方法中实现游戏逻辑。

注意:

  • 对可用像素点的处理:可用像素点个数 = 屏幕长 * 屏幕宽 / 单位大小(苹果大小)。
  • 对蛇身初始坐标的处理: 存储蛇身的初始坐标须在可用像素点中。
  • 对苹果坐标的处理:需判断新的苹果坐标是否在蛇身内。
  • 对游戏是否存活的处理:蛇头撞到蛇身或者蛇头撞到边缘都应视为结束。
  • 对接收到的按键值的处理:除方向按键外,其余按键值使用完之后需做清除。

完整实现代码如下

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.Random;

public class SnakeGame extends JPanel implements ActionListener {

    public interface Direction {
        char LEFT = 'L';
        char RIGHT = 'R';
        char UP = 'U';
        char DOWN = 'D';
    }

    public interface RunStatus {
        char READY = '0'; // 就绪
        char RUN = '1'; // 运行
        char OVER = '2'; // 失败
        char PAUSE = '3'; // 暂停/继续
    }

    final Random random = new Random();
    volatile int eventKeyCode;

    //记时步长
    volatile int delay = 150;
    Timer timer = null;

    //屏幕大小
    static final int SCREEN_WIDTH = 600;
    static final int SCREEN_HEIGHT = 600;

    //可用像素点
    static final int UNIT_SIZE = 25;
    static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;

    //蛇身
    volatile int bodyParts;
    int snakeBodyX[];
    int snakeBodyY[];

    //目标
    int appleX;
    int appleY;

    //得分
    volatile int applesEaten;
    volatile char direction;
    volatile char runStatus;

    SnakeGame() {
        this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        (timer = new Timer(delay, this)).start();
        init();
    }

    public void init() {
        bodyParts = 1;
        snakeBodyX = new int[GAME_UNITS];
        Arrays.fill(snakeBodyX, -1);
        snakeBodyX[0] = 0;
        snakeBodyY = new int[GAME_UNITS];
        Arrays.fill(snakeBodyY, -1);
        snakeBodyY[0] = 0;
        appleX = nextCoordinate(SCREEN_WIDTH, snakeBodyX);
        appleY = nextCoordinate(SCREEN_HEIGHT, snakeBodyY);
        applesEaten = 0;
        direction = Direction.RIGHT;
        runStatus = RunStatus.READY;
        timer.setDelay(delay);
    }

    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        String tip = "" + applesEaten;
        int y = SCREEN_HEIGHT / 2 - 120;
        switch (runStatus) {
            case RunStatus.READY:
                drawing(g);
                g.setColor(Color.BLUE);
                g.setFont(new Font(null, Font.ITALIC, 20));
                g.drawString("按1-开始游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                g.drawString("按2-重新开始", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                g.drawString("按3-暂停/继续", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                break;
            case RunStatus.PAUSE:
                drawing(g);
                g.setColor(Color.BLUE);
                g.setFont(new Font(null, Font.ITALIC, 20));
                y = y + 80;
                g.drawString("按3-继续", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                break;
            case RunStatus.RUN:
                drawing(g);
                g.setColor(Color.blue);
                g.setFont(new Font(null, Font.BOLD, 20));
                g.drawString(tip, (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, g.getFont().getSize());
                break;
            case RunStatus.OVER:
                g.setColor(Color.RED);
                g.setFont(new Font("Ink Free", Font.BOLD, 40));
                g.drawString("Game Over", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, y - 120);
                g.setFont(new Font(null, Font.ITALIC, 20));
                g.drawString("得分:" + applesEaten, (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, y - 60);
                g.setColor(Color.BLUE);
                g.drawString("按2-重新开始", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
                break;
            default:
                break;
        }
    }

    /**
     * 绘制蛇身和苹果
     *
     * @param g
     */
    public void drawing(Graphics g) {
        //苹果颜色
        g.setColor(Color.PINK);
        //画苹果
        g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
        //蛇身颜色
        g.setColor(Color.RED);
        //填充蛇身
        for (int i = 0; i < bodyParts; i++) {
            g.fillRect(snakeBodyX[i], snakeBodyY[i], UNIT_SIZE, UNIT_SIZE);
        }
    }

    /**
     * 执行的动作
     * 会定时执行此方法
     *
     * @param e
     */
    public void actionPerformed(ActionEvent e) {

        //前置事件
        doSomeThing();

        if (runStatus == RunStatus.RUN) {
            move();

            //撞到边缘
            if (snakeBodyX[0] < 0 || snakeBodyX[0] > SCREEN_WIDTH || snakeBodyY[0] < 0 || snakeBodyY[0] > SCREEN_HEIGHT) {
                runStatus = RunStatus.OVER;
            }
            //蛇身相撞
            for (int i = bodyParts; i > 0; i--) {
                if ((snakeBodyX[0] == snakeBodyX[i]) && (snakeBodyY[0] == snakeBodyY[i])) {
                    runStatus = RunStatus.OVER;
                }
            }

            //得分
            if ((snakeBodyX[0] == appleX) && (snakeBodyY[0] == appleY)) {
                applesEaten++;
                bodyParts++;

                //重新生成苹果
                appleX = nextCoordinate(SCREEN_WIDTH, snakeBodyX);
                appleY = nextCoordinate(SCREEN_HEIGHT, snakeBodyY);

                //预留难度设置的方法
                difficultySettings();
            }
        }

        //重新绘图, 执行 paintComponent 方法
        repaint();
    }

    /**
     * 蛇身移动
     */
    private void move() {
        for (int i = bodyParts; i > 0; i--) {
            snakeBodyX[i] = snakeBodyX[(i - 1)];
            snakeBodyY[i] = snakeBodyY[(i - 1)];
        }
        switch (direction) {
            case Direction.UP:
                snakeBodyY[0] -= UNIT_SIZE;
                break;
            case Direction.DOWN:
                snakeBodyY[0] += UNIT_SIZE;
                break;
            case Direction.LEFT:
                snakeBodyX[0] -= UNIT_SIZE;
                break;
            case Direction.RIGHT:
                snakeBodyX[0] += UNIT_SIZE;
                break;
            default:
                break;
        }
    }

    private void doSomeThing() {
        if (KeyEvent.VK_ESCAPE == eventKeyCode) {
            System.out.println("退出程序");
            System.exit(0);
            return;
        }

        if (KeyEvent.VK_1 == eventKeyCode) {
            System.out.println("开始游戏");
            runStatus = RunStatus.RUN;
        }

        if (KeyEvent.VK_2 == eventKeyCode) {
            if (runStatus != RunStatus.RUN) {
                System.out.println("重新开始");
                init();
            }
            runStatus = RunStatus.RUN;
        }
        if (KeyEvent.VK_3 == eventKeyCode) {
            if (runStatus == RunStatus.RUN) {
                System.out.println("暂停");
                runStatus = RunStatus.PAUSE;
                eventKeyCode = -1;
                return;
            }
            if (runStatus == RunStatus.PAUSE) {
                System.out.println("继续");
                runStatus = RunStatus.RUN;
                eventKeyCode = -1;
                return;
            }
        }
        eventKeyCode = -1;

    }

    /**
     * 难度设置, 默认得分超过16的倍数时速度提升1/4
     */
    private void difficultySettings() {
        //难度增加, 速度加快
        if (applesEaten % 16 == 0 && applesEaten != 0) {
            timer.setDelay(timer.getDelay() - timer.getDelay() / 4);
        }
    }

    /**
     * 下一个苹果坐标
     *
     * @param randomFactorint
     * @param arr
     * @return
     */
    synchronized private int nextCoordinate(int randomFactorint, int[] arr) {
        int coordinate = random.nextInt(randomFactorint / UNIT_SIZE) * UNIT_SIZE;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == -1) {
                break;
            }
            if (coordinate == arr[i]) {
                nextCoordinate(randomFactorint, arr);
            }
        }
        return coordinate;
    }

    /**
     * 按键适配器,用于监听输入按键
     */
    public class MyKeyAdapter extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            int keyCode = e.getKeyCode();
            eventKeyCode = e.getKeyCode();
            if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) && direction != Direction.RIGHT) {
                direction = Direction.LEFT;
            } else if ((keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) && direction != Direction.LEFT) {
                direction = Direction.RIGHT;
            } else if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) && direction != Direction.DOWN) {
                direction = Direction.UP;
            } else if ((keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) && direction != Direction.UP) {
                direction = Direction.DOWN;
            }
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setTitle("贪吃蛇");
        SnakeGame snake = new SnakeGame();
        frame.add(snake);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

效果展示

游戏启动

在这里插入图片描述

游戏暂停

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1812244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机内存分类

1&#xff0c;非易失性存储器。 断电时&#xff0c;存储器的内容不会丢失&#xff0c;并且 加电时再次可用。这对于处理器启动或重启时使用的引导代码是必需的。 o ROM&#xff1a;只读存储器&#xff1b;此存储器的内容在制造过程中定义 设备。一旦发生错误&#xff0c;芯片…

推荐系统学习笔记(五)-----双塔模型

目录 双塔模型 训练 pointwise训练 pairwise训练 listwise训练 双塔模型 矩阵补充模型只用到了用户id和物品id&#xff0c;其余属性没有用上 用户属性也可以这样处理 用户塔和物品塔各输出一个向量&#xff0c;两个向量的余弦相似度作为兴趣的预估值 训练 第一种&#x…

VFP发送邮件有哪些配置要求?如何使用VFP?

VFP发送邮件支持的邮件服务器&#xff1f;邮件发送功能怎么优化&#xff1f; 在如今的互联网时代&#xff0c;应用程序发送邮件功能几乎是不可或缺的&#xff0c;VFP也不例外。然而&#xff0c;要实现VFP发送邮件&#xff0c;需要了解并配置相关的要求和步骤。AokSend将详细介…

如何查看个人大数据信用报告?查询报告哪家好呢?

大数据信用报告是现代社会中非常重要的信用评估工具&#xff0c;对于个人来说也具有非常重要的意义。那么&#xff0c;如何查看个人大数据信用报告?查询报告哪家好呢?本文将为您介绍。 首先&#xff0c;查看个人大数据信用报告需要了解报告的内容和格式 一般来说&#xff0c;…

【Python】Flask问答系统Demo项目

学习视频 我是跟着知了传课学的Flask&#xff0c;起初了解Flask还是GPT告诉我的&#xff0c;现在可以说用Flask做后端是真的方便&#xff01; https://www.bilibili.com/video/BV17r4y1y7jJ 项目结构与下载 FlaskOA&#xff08;项目文件夹&#xff09; │ app.py │ conf…

skywalking9.4 链路追踪

下载&#xff0c;很慢很慢很慢&#xff01;&#xff01;&#xff01;&#xff01; jdk 使用jdk17 skywalking-apm 9.4 java-agent 9.0 idea 本地开发配置 第1行配置按实际来&#xff1b; 第2行自定义&#xff0c;一般和微服务名称相同&#xff1b; 第3行ip写安装的机器ip,端…

同余式,乘法逆元,费马小定理

同余式 同余式是 数论 的基本概念之一&#xff0c;设m是给定的一个正整数&#xff0c;a、b是整数&#xff0c;若满足m| (a-b)&#xff0c;则称a与b对模m 同余 &#xff0c;记为a≡b (mod m)&#xff0c;或记为a≡b (m)。 这个式子称为模m的同余式&#xff0c;若m∤ (a-b)&…

机器学习-支持向量机

目录 一支持向量机 1.支持向量机SVM 2构建svm目标函数 3.拉格朗日乘法&#xff0c;kkt条件 拉格朗日乘法&#xff1a; kkt条件 对偶问题 4.最小化SVM目标函数 kkt条件&#xff1a; 对偶转换&#xff1a; 5软间隔及优化 优化svm目标函数 构造拉格朗日函数 对偶转换关系…

orbslam2代码解读(4):loopclosing回环检测线程

书接上回&#xff0c;介绍完了局部建图线程&#xff0c;局部建图线程在进行局部BA之后&#xff0c;也会将新的关键帧mpLoopCloser放进回环线程的mlpLoopKeyFrameQueue容器中。所以这时候回环检测线程就根据这个新的关键帧来进行回环检测的操作。 回环检测的主要程序 // 线程主…

177.二叉树:从前序与中序遍历序列构造二叉树(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

176.二叉树:从中序与后序遍历序列构造二叉树(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

OpenGauss常操作

OpenGauss官网已经有很详细的说明了,但是对于新手而言还有一些需要注意的地方; 安装 yum一键安装; yum -y install libaio-devel yum -y install readline-devel yum -y install libnsl 单独创建用户和组; groupadd dbgroup useradd -g dbgroup omm passwd omm 取消打开文…

DriveWorld:一个预训练模型大幅提升检测+地图+跟踪+运动预测+Occ多个任务性能

1. 写在前面 以视觉为中心的自动驾驶技术近期因其较低的成本而引起了广泛关注&#xff0c;而预训练对于提取通用表示至关重要。然而&#xff0c;当前的以视觉为中心的预训练通常依赖于2D或3D预训练任务&#xff0c;忽视了自动驾驶作为4D场景理解任务的时序特征。这里通过引入一…

中国最受欢迎起名大师,国内顶级的姓名学大师是谁

唐王古镇龙泉边&#xff0c; 端午邀约凤凰山&#xff0c; 举国共享康宁日&#xff0c; 泉星福瑞天下安… 颜廷利敬献---福祐安康 中国最受欢迎起名大师&#xff0c;国内顶级的姓名学大师是谁&#xff1f;在齐鲁的心脏&#xff0c;济南的历史悠长的唐王古镇边&#xff0c;龙泉大…

香港优才是不是智商税?搞懂香港身份不后悔

很多中产家庭和明星通过不同方式获得香港身份&#xff0c;以享受其诸多优势&#xff0c;如子女教育和事业发展。明星如孙俪/邓超夫妇、汤唯、朗朗、章子怡和黄晓明等都获得了香港身份。 一、香港身份「优才计划」是什么&#xff1f;是否值得追求&#xff1f; 香港身份&#xf…

知识管理与集体创新——从企业到新型研发机构的实践与思考︱PMO大会

全国PMO专业人士年度盛会 鹏城实验室人力资源处人才发展主管&#xff0c;原华为供应链管理部知识管理负责人王万翎女士受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“知识管理与集体创新——从企业到新型研发机构的实践与思考”。大会将于6月29-…

springboot在线雅思学习平台 -计算机毕业设计源码44566

目 录 摘要 1 绪论 1.1系统背景 1.2课题背景及目的 2 1.3系统开发技术的特色 3 1.4 springboot框架介绍 3 1.5论文结构与章节安排 4 2 在线雅思学习平台系统分析 5 2.1 可行性分析 5 2.2 系统流程分析 5 2.2.1数据增加流程 5 2.2.2数据修改流程 6 2.2.3数据删除流程 6 2.3 …

Autoware 定位之EKF 滤波定位(四)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…

网络抓取的最佳用户代理 2024 | 避免在抓取时被禁止使用 UA

你是经常进行网页抓取的人吗&#xff1f;你对你的隐私非常小心吗&#xff1f;那么你一定多次听说过“用户代理”。你知道什么是用户代理吗&#xff1f;它如何影响我们的在线生活呢&#xff1f; 请开始阅读&#xff0c;你会对这篇博客中的一切感兴趣&#xff01; 什么是用户代…

经典文献阅读之--FlashOcc(快速且内存高效的占用预测模块)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务&#xff0c;并且需要GPU资源&#xff0c;可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU&#xff0c;按时收费每卡2.6元&#xff0c;月卡只需要1.7元每小时&…