贪 吃 蛇

简介

简易贪吃蛇,使用 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.mfbz.cn/a/699391.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[总线]AMBA总线架构的发展历程

目录 引言 发展历程 第一代AMBA&#xff08;AMBA 1&#xff09; 第二代AMBA&#xff08;AMBA 2&#xff09; 第三代AMBA&#xff08;AMBA 3&#xff09; 第四代AMBA&#xff08;AMBA 4&#xff09; 第五代AMBA&#xff08;AMBA 5&#xff09; AMBA协议简介 ASB&#x…

用 Kotlin 多平台开发构建跨平台应用程序:深入探索 KMP 模板工程

用 Kotlin 多平台开发构建跨平台应用程序&#xff1a;深入探索 KMP 模板工程 Kotlin 多平台开发 (KMP) 是一种强大的工具&#xff0c;可用于构建跨平台移动、桌面和 Web 应用程序。它提供了一种统一的代码基础&#xff0c;使开发人员能够高效地针对多个平台开发应用程序。 KM…

【工具】新手如何正确使用Pycharm?

1. 什么是JetBrains Toolbox JetBrains Toolbox是一个管理工具&#xff0c;用于安装、更新和管理JetBrains开发工具的所有版本。它可以简化多个IDE的管理&#xff0c;并确保你总是使用最新版本的软件。 2. 安装JetBrains Toolbox 步骤1&#xff1a;下载Toolbox 访问JetBrai…

演出门票小程序开发

一、实时票务信息更新的重要性 在演出票务市场&#xff0c;票务信息的实时性对于消费者来说至关重要。一旦票务信息出现滞后或错误&#xff0c;不仅可能导致消费者错过心仪的演出&#xff0c;还可能引发一系列不必要的纠纷和投诉。因此&#xff0c;演出门票小程序通过引入实时…

外汇天眼:跟单社区or资金盘 几招教你快速识别

今年有不少外汇跟单社区伙同黑平台收割投资人跑路事件&#xff0c;应天眼老粉要求&#xff0c;今天写一篇与跟单社区相关的内容&#xff0c;教大家如何辨别正规的外汇跟单社区与资金盘诈骗。 相信做过几年外汇的人&#xff0c;应该对跟单社区多少有所耳闻。但外汇跟单社区究竟…

物联网学习小记

https://www.cnblogs.com/senior-engineer/p/10045658.html GOSP: 提供类似Qt的API接口&#xff0c;仅需要几百KB的硬件资源&#xff08;比Qt小的多&#xff09;&#xff0c;能运行在Qt不支持的低配置硬件上&#xff08;对Qt生态形成补充&#xff09;&#xff0c;适用于嵌入式…

python-找第一个只出现一次的字符

[题目描述] 给定一个只包含小写字母的字符串&#xff0c;请你找到第一个仅出现一次的字符。如果没有&#xff0c;输出 no。输入&#xff1a; 一个字符串&#xff0c;长度小于 1100。输出&#xff1a; 输出第一个仅出现一次的字符&#xff0c;若没有则输出 no。样例输入1 abcabd…

数字影像产业园:打造数字经济高地,赋能未来产业

成都国际数字影像产业园凭借其得天独厚的区位优势、完善的配套设施、先进的产业定位和便捷的交通条件&#xff0c;逐步成为成都市乃至全国数字影像、文创、媒体产业的重要聚集地。 成都国际数字影像产业园位于成都市金牛区的核心地带&#xff0c;其主导产业为数字影像、文创、媒…

模型的手工下载技巧-代码自动批量下载模型文件

之前分享过通过镜像网站手工下载模型文件的技巧&#xff08;见这里模型的手工下载技巧-镜像网站的使用&#xff09;。但有的时候&#xff0c;模型文件数量较多&#xff0c;一个个​手工下载非常不便。比如著名的“麦橘写实”模型。 有没有什么好办法可以把整个目录都下载下来呢…

vulhub之httpd篇

Apache 换行解析漏洞&#xff08;CVE-2017-15715&#xff09; Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&#xff0c;在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过一…

中电联系列三:rocket手把手教你理解中电联协议!

分享《慧哥的充电桩开源SAAS系统&#xff0c;支持汽车充电桩、二轮自行车充电桩。》 前 言 T/CEC102《电动汽车充换电服务信息交换》共分为四个部分&#xff1a; ——第1部分&#xff1a;总则&#xff1b; ——第2部分&#xff1a;公共信息交换规范&#xff1b; ——第3部分&a…

JavaScript的数据类型(基础数据类型和数据类型转换)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【初阶数据结构】深入解析顺序表:探索底层逻辑

&#x1f525;引言 本篇将深入解析顺序表:探索底层逻辑&#xff0c;理解底层是如何实现并了解该接口实现的优缺点&#xff0c;以便于我们在编写程序灵活地使用该数据结构。 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &…

java:使用JSqlParser给sql语句增加tenant_id和deleted条件

# 示例代码 【pom.xml】 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.4.3.1</version> </dependency>【MyJSqlParserTest.java】 package com.chz.myJSqlParser;pu…

如何利用 Google 搜索结果页来引导?

在数据驱动的决策世界中&#xff0c;获取准确而全面的信息至关重要。Google 搜索结果抓取是一种强大的技术&#xff0c;可以让企业、调查人员和研究人员从搜索引擎结果中提取可靠的数据。本综合指南将深入研究 Google 搜索结果的最佳实践、工具和道德考量&#xff0c;以确定能够…

4、视觉里程计:特征点法、直接法和半直接法

先说一下我自己的总体理解&#xff1a; 特征点法&#xff0c;基于最小化重投影误。 提取特征点&#xff0c;计算描述子&#xff0c;匹配&#xff0c;运动估计。 计算描述子和匹配部分可以用光流法跟踪替代 总体上先知道像素之间的关系&#xff0c;在估计运动&#xff08;最…

铝合金板件加工迎来3D视觉新时代

在制造业的浩瀚星空中&#xff0c;铝合金板件加工一直以其轻质、高强度、耐腐蚀的特性&#xff0c;扮演着举足轻重的角色。然而&#xff0c;随着市场竞争的加剧和产品需求的多样化&#xff0c;传统的加工方式已难以满足现代制造业对高效率、高精度的追求。在这个关键时刻&#…

详细教学wps中公式如何居中,公式编号如何右对齐

废话少说&#xff0c;首先打开WPS&#xff0c;新建一个空白文档。 详细步骤如下&#xff1a; &#xff08;1&#xff09;新建一个模板样式&#xff0c;在开始一栏中&#xff0c;点击新建样式具体操作看下图&#xff1a; &#xff08;2&#xff09;设计样式 修改样式名称为公…

2024年制作AI问答机器人给企业带来的几大好处

引言 在当今数字化时代&#xff0c;企业需要不断寻求创新&#xff0c;以提升客户服务水平、降低成本&#xff0c;并改善用户体验。其中&#xff0c;AI问答机器人作为一种智能化解决方案&#xff0c;正在成为越来越多企业的首选。本文将探讨制作AI问答机器人给企业内外部带来的…

期权交易单位是什么?期权懂新手必看!

今天带你了解期权交易单位是什么&#xff1f;很多对期权还不太熟悉的朋友&#xff0c;不知道期权的单位是什么&#xff0c;下面小编就来告诉你期权的交易单位到底是什么&#xff1f; 期权交易单位是什么&#xff1f; 50ETF期权的交易单位&#xff0c;用大白话来说&#xff0c;…