贪 吃 蛇
简介
简易贪吃蛇,使用 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);}
}
效果展示
游戏启动

游戏暂停

相关文章:
贪 吃 蛇
简介 简易贪吃蛇,使用 javax.swing 组件构建游戏界面,通过监听键盘按键实现游戏操纵。 功能设计 按1 - 开始游戏按2 - 重新开始按3 - 暂停/继续按Esc-退出游戏统计吃到的苹果个数(得分)难度控制,得分超过阈值时难度…...
多人中招!企业裁员前的十大征兆!
(1)公司业绩下滑: 增长放缓:企业业绩增速放缓,低于行业平均水平。 如果公司的业绩增长慢下来了,甚至比不上同行业的其他公司,那就得小心了。利润也开始下滑,成本却不断上升&#x…...
R语言:使用 tidyr 进行数据整理
在数据分析和处理的过程中,数据整理是一项至关重要的任务。R 语言中的 tidyr 包提供了一组强大的函数,用于将数据转换为更易于分析的格式。tidyr 包的设计准则如下: 每个变量都有自己的列。每个观察值都有自己的行。每个值都有自己的单元格。…...
帝国CMS火车头采集发布模块详细使用方法
火车头采集文章数据发布到帝国CMS系统操作步骤如下: 1. 下载火车头采集帝国cms发布模块:帝国cms发布模块接口下载地址(免登录)-CSDN ; 2. 帝国cms发布模块导入火车头采集软件; 3. 填写帝国cms数据库中相…...
Unity 数据存储
在Unity中,资源的存储是非常重要的,所以了解资源的存储方式是有必要的,接下来说明一个重要的部分。 1.Unity存储 Unity为我们提供了自带的永久存储方式,PlayerPrefs,使用方法可以参考我这篇文章..点击导航 当然&…...
Doris 少数SQL在Datagrip无法执行,而在DorisUI或程序调用可以执行的问题
问题:Doris 少数SQL在Datagrip无法执行,而在DorisUI或程序调用可以执行 解决:Datagrip 执行SQL切分异常,设置默认执行语句方式,将分句改为整句执行 但是 支持多SQL批量分开执行更好用...
若依RuoYi-Vue分离版—配置多数据源
若依RuoYi-Vue分离版—配置多数据源 一、修改application-druid.yml二、修改pom文件,引入依赖第一种:下载jar包到本地,然后引入(我这边用的是这种)本地引入的,打包时需要加上配置 第二种:从远程…...
电子科技大学卓中卓二轮——分析笔记
1. 子系统的关键工作原理 在Linux子系统(Subsystem for Linux, 简称WSL)中,API(应用程序编程接口)的转换和映射是一个关键过程,目的是让Windows应用程序能够与Linux环境中的系统调用无缝交互。WSL使用了名…...
代码随想录算法训练营第三十五天|1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果
LeetCode 1005.K次取反后最大化的数组和 题目链接:1005.K次取反后最大化的数组和 踩坑:没有 思路:数组里有正有负,肯定先对负数进行取反,且从小开始。如果所有负数都为正后还可以取反,则如果此时次数为奇…...
鸿蒙开发HarmonyOS Next 网络框架retrofit 封装 viemodel使用
新手刚开始学习harmonyos开发,之前搞安卓开发习惯使用retrofit,结果在三方库中还真搜到了,然后就模拟学习一下。有不对的地方请指点一下。新手新手 oh-package.json5 引入库 retofit 需要使用2.0.1-rc.0 以上版本,修复了retrofit发送网络请…...
什么是SpringMVC
StringMvc简介 Spring web mvc和Struts2都属于表现层的框架,它是Spring框架的一部分,我们可以从Spring的整体结构中看得出来:...
【PowerDesigner】PDM生成建表脚本
目录 🌊1. PowerDesigner简介 🌍1.1 常用模型文件 🌍1.2 PowerDesigner使用环境 🌊2. PDM生成建表脚本 🌊3. 研究心得 🌊1. PowerDesigner简介 🌍1.1 常用模型文件 主要使用PowerDesigne…...
React实现在线预览word报告/本地选择报告预览
标题使用的核心技术点是docx-preview,读取到文件的File对象,用File去做文件展示,这里是才用将文件转base64字符串存储到localStorage中 在线预览word报告且包含word样式 下载需要使用的min.js文件进项目的public目录中(上zip已包…...
计算机哈佛架构、冯·诺依曼架构对比
哈佛架构和冯诺依曼架构是两种不同的计算机系统架构,它们在存储器组织方式上有着显著的区别。下面是它们的原理、优缺点的对比以及一些常见的 MCU 采用的架构: 哈佛架构: 原理:哈佛架构将指令存储器(程序存储器&#x…...
单片机串口发送为空中断和发送完成中断有什么区别?
单片机串口发送的空中断和发送完成中断在触发条件和功能上存在明显的区别。以下是关于这两种中断的详细解释: 【发送为空】中断(Transmit Data Register Empty Interrupt): 触发条件:当发送数据寄存器(TDR…...
css特效:对多个tag标签实现模拟地球仪特效
要实现对多个<a>标签(比如链接)的模拟地球仪特效和鼠标跟随特效,你可以使用CSS和一点点JavaScript来完成。下面是一个基本的示例代码:HTML代码: <!DOCTYPE html> <html lang"en"> <h…...
【2024Python教程】Python文件打包成exe,如果有图片怎么打包?有手就会的超简单教程
目录 pyinstaller模块打包exe(无图片或其他文件打包版) 第一步 安装pyinstaller模块: 第二步 找到需要打包的主程序文件夹 第三步 打包exe文件 第四步 确认exe文件是否可以打开 pyinstaller模块打包exe(有图片打包版--方法一…...
mac环境基于llama3和metaGPT自动开发2048游戏
1.准备虚拟环境 conda create -n metagpt python3.9 && conda activate metagpt 2.安装metagpt pip install --upgrade metagpt 3.初始化配置文件 metagpt --init-config 4. 安装llama3 5. 修改配置文件 6.让metegpt自动开发2048游戏 7.经过多轮迭代,最终…...
这些Linux知识可不是靠背就会的!
在信息技术日新月异的今天,Linux以其开源、稳定、高效的特性,逐渐成为了众多专业人士的首选操作系统。然而,关于Linux知识的学习,却常常陷入一个误区——许多人认为,掌握Linux就是死记硬背各种命令和参数。这种观念&am…...
openlayers 绘图功能,绘制多边形,draw组件的使用,一个简单的需求引发的思考(一)
1 需求 使用openlayers绘图功能绘制多边形 2 分析 主要是openlayers中draw功能的使用,感觉比较简单,祖传CV大法搞起来 3 实现 为了方便,就不加载底图了,直接使用绘制功能 2.1 简单实现 <template><div id"ma…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...
软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
