无swing,高级javaSE毕业之贪吃蛇游戏(含模块构建,多线程监听服务),已录制视频

news2025/1/21 18:50:40

JavaSE,无框架实现贪吃蛇

B站已发视频:无swing,纯JavaSE贪吃蛇游戏设计构建

文章目录

  • JavaSE,无框架实现贪吃蛇
    • 1.整体思考
    • 2.可能的难点思考
      • 2.1 如何表示游戏界面
      • 2.2 如何渲染游戏界面
      • 2.3 如何让游戏动起来
      • 2.4 蛇如何移动
    • 3.流程图制作
    • 4.模块划分
    • 5.模块完善
      • 5.0常量优化
      • 5.1监听键盘服务
        • i.输入存储
        • ii.键盘监听
      • 5.2棋盘类方法(地图)
        • i.节点渲染
        • ii.边界判断
        • iii.地图显示
        • iV.食物生成
        • V.地图初始化
      • 5.3蛇类方法
        • i.蛇体初始化
        • ii.自定义异常
        • iii.食物监测
        • iV.自我碰撞监测
        • V.移动
    • 6.业务流程编写

本篇文章没有使用任何框架,纯JavaSE编写的贪吃蛇。主要探讨点为程序设计,比如流程绘制,模块划分。

如果需要源代码,公众号’一只学Java的飞哥呀’,回复贪吃蛇,即可获取源码和讲义。此外附赠1年前用GUI写的贪吃蛇

JavaSE无框架实现贪吃蛇效果

贪吃蛇JavaSE无框架


JavaGUI实现贪吃蛇效果,但文章内容并无涉及GUI代码编写,仅仅在公众号上提供相应代码

贪吃蛇GUI

1.整体思考

  • 游戏明面上组成部分有2。蛇、地图。在JavaSE的知识体系内。地图可以使用二维数组表示,蛇可以用一维数组表示
  • 通过在控制台打印数组的形式,来静态展示贪吃蛇游戏
  • 游戏本质上是一组连续的图片,每一秒打印一次数组,以此让游戏动起来
  • 游戏需要通过用户敲击键盘,实现方向移动。程序需要监听键盘输入,并将输入结果传递给蛇,以此操作蛇的移动

2.可能的难点思考

2.1 如何表示游戏界面

public class GameMap{
    private static int row = 20;
    private static int col = 20;
	// String的二维数组, 用来表示地图
    public static String[][] gameMap = new String[row][col];    

    // 初始化地图
    public GameMap() {
        // o 为地图, * 为蛇, @ 为食物
        for (int i = 0; i < gameMap.length; ++i) {
            for (int j = 0; j < gameMap[0].length; ++j) {
                gameMap[i][j] = "o";
            }
        }
    }
    //...
}

// Node的一维列表, 用来表示蛇的坐标
public class Node{
    int x;
    int y;
    public Node() {}
    public Node(int x, int y) {
        this.x = x;
		this.y = y;
	}
}

public class Snake{
	Deque<Node> snakeLoc = new ArrayDeque<>(); 
	// ...
}

2.2 如何渲染游戏界面

打印地图,相当于渲染游戏界面

void printMap() {
    // 循环打印map   
}

2.3 如何让游戏动起来

用循环,持续不断的打印界面,就可以形成动起来的效果

while(true) {
	// ...
    printMap(map, snake);
    // ...
}

2.4 蛇如何移动

蛇的移动属于蛇对象的行为,因此我们可以在Snake类中封装move方法,移动的本质是:蛇尾移动到蛇头.

public class Snake{
    // 返回尾坐标,头坐标
    public void move(int[] direction) {
        // 获取蛇尾坐标
        Node lastNode = snakeLoc.removeLast();
        // 移动
        Node newNode = moveTo(direction);
    	// 添加到蛇头
        snakeLoc.addFirst(newNode);
    }
    
    private Node moveTo(Node node, int[] direction) {
        // 获取头节点
        Node firstNode = snakeLoc.getFirst();
        // 执行移动逻辑
        int x = firstNode.getX();
        int y = firstNode.getY();
        x += direction[0];
        y += direction[1];
        firstNode.setX(x);
        firstNode.setY(y);
        return firstNode;
    }
}

3.流程图制作

在这里插入图片描述

4.模块划分

在这里插入图片描述

5.模块完善

5.0常量优化

public interface Constants {
    /**
     * 蛇的标记
     */
    String SNAKE_FLAG = "o";
    /**
     * 地图的标记
     */
    String MAP_FLAG = "*";
    /**
     * 食物的标记
     */
    String FOOD_FLAG = "@";
    /**
     * 地图行数
     */
    int row = 10;
    /**
     * 地图列数
     */
    int col = 10;
}

5.1监听键盘服务

考虑到还在JavaSE的范畴,swing的键盘监听功能我们不会去使用,而网上有没有找到合适的代替方案。因此,我们采用最原始的方法,Scanner输入,来代替监听功能。但scanner会有阻塞现象,一旦把主游戏进程阻塞,那么后续的流程都将无法进行。因此,我们需要开启子线程来监听用户输入

i.输入存储

/**
 * 存储用户的输入
 */
public class StoreInput{
    private static String input = "a";
    /**    w
      * a  s  d
      */
    private static List<String> validDir = Arrays.asList("w", "a", "s", "d");
    
    public static void set(String in) {
        if (validDir.contains(in)) {
        	input = in;    
        }
    }
    
    public static int[] get() {
        if ("w".equals(input)) {
            int[] dir = {0, 1};
            return dir;
        }else if ("a".equals(input)) {
			int[] dir = {-1, 0};
            return dir;    
        }else if ("s".equals(input)) {
            int[] dir = {0, -1};
            return dir;
        }else {
            int[] dir = {1, 0};
            return dir;
        }
    }
}

ii.键盘监听

/**
 * 监听器, 监听输入
 */
public class ScanerListener{
    public void start() {
        // 创建线程
        new Thread(new Runnable() {
            Scanner scanner = new Scanner(System.in);

            @Override
            public void run() {
                while(true) {
                    if (scanner.hasNext()) {
                        // 存储最后一个字符
                        int length = scanner.next().length();
                        char inputChar = scanner.next().charAt(length - 1);
                    	StoreInput.set(String.valueOf(inputChar));
                    }
                }
            }
        }).start();
    }
}

5.2棋盘类方法(地图)

  • 地图节点渲染(蛇/食物 坐标渲染)
  • 地图边界判断

i.节点渲染

// 地图坐标更新
public static void updateMap(Node node, String type) {
	gameMap[node.getX()][node.getY()] = type;
}

ii.边界判断

// 判断是否到达地图边缘
public static boolean isValid(Node node) {
	if (node.getX() < 0 || node.getX() >= Constants.row || node.getY() < 0 || node.getY() >= Constants.col) {
		// 非法
   		return false;         
	}
    // 合法
    return true;
}

iii.地图显示

循环打印地图数组

public void show() {	
	for (int i = 0; i < gameMap.length; ++i) {
		for (int j = 0; j < gameMap[0].length; ++j) {
    	    System.out.print(gameMap[i][j]);
            System.out.print(" ");
        }
        System.out.println();
    }
}

iV.食物生成

private static Random random = new Random();

private static Node food = new Node(1, 1);

/**
 * 生成食物, 且保证不是在蛇的身体上
 */ 
public static void generateFood() {
    // 循环生成成对坐标, 并且坐标不能落在蛇体上
    int x = 0;
    int y = 0;
    do {
        x = random.nextInt(Constants.row);
        y = random.nextInt(Constants.col);
    }while( isSnake(x, y) );
    food = new Node(x, y);
    updateMap(food, Constants.SNAKE_FLAG);
}

/**
 * 返回食物节点
 */
public static Node getFood() {
    return food;
}

private static boolean isSnake(int x, int y) {
    return gameMap[x][y].equals(Constants.SNAKE_FLAG);
}

V.地图初始化

1.初始化食物,地图,蛇

// 初始化地图
public GameMap() {
    // o 为地图, * 为蛇, @ 为食物
    for (int i = 0; i < gameMap.length; ++i) {
    	for (int j = 0; j < gameMap[0].length; ++j) {
        	gameMap[i][j] = Constants.MAP_FLAG;
        }
    }
	generateFood();
}

5.3蛇类方法

初步完善如下功能:

  • 位置移动
  • 自我碰撞监测
  • 食物监测

i.蛇体初始化

public class Snake{
    // 初始化贪吃蛇
	public Snake() {
		Node node = new Node(Constants.row / 2, Constants.col / 2);
		snakeLoc.addFirst(node);
		GameMap.updateMap(node, Constants.SNAKE_FLAG);

		Node node1 = new Node(node.getX() + 1, node.getY());
		snakeLoc.addLast(node1);
		GameMap.updateMap(node1, Constants.SNAKE_FLAG);
	}
}

ii.自定义异常

public SnakeException extends RuntimeException{
    public SnakeException(String msg) {
        super(msg);
    }
}

iii.食物监测

/**
 * 监测食物
 */
public void detectFood(Node firstNode) {
	boolean flag = isFood(firstNode);
	if (flag) {
		System.out.println("吃掉!");
		// 长度增加
		longgerSelf();
		// 随机生成食物
        GameMap.generateFood();
	}
}

/**
 * 增长自己
 */ 
private void longgerSelf(){
	// 获取当前方向
	int[] dir = StoreInput.get();
	// 方向取反, 获得尾巴需要添加的方向
	int x = -1 * dir[0];
	int y = -1 * dir[1];
	// 在尾部添加节点
	Node lastNode = snakeLoc.getLast();
	Node newNode = new Node(lastNode.getX() + x, lastNode.getY() + y);
	// 添加节点到尾部
	snakeLoc.addLast(newNode);
	// 更新节点
	GameMap.updateMap(newNode, Constants.SNAKE_FLAG);
}

/**
 * 判断节点是否是食物
 * @param firstNode
 */
private boolean isFood(Node firstNode) {
	Node foodNode = GameMap.getFood();
	return firstNode.getX() == foodNode.getX() && firstNode.getY() == foodNode.getY();
}

iV.自我碰撞监测

/**
 * 传入新的头节点, 判断是否和身体节点冲突
 */
public boolean detectSelf(Node firstNode) {
    // 判断是否和余下的节点冲突
    for (Node node : snakeLoc) {
        if (node.getX() == firstNode.getX() && node.getY() == firstNode.getY()) {
            return true;
        }
    }
    return false;
}

V.移动

因为我们已经有输入存储模块,我们可以直接从中获取

// 返回尾坐标,头坐标
public void move() {
	// 获取蛇尾坐标
	Node lastNode = snakeLoc.removeLast();
	// 获取方向
	int[] direction = StoreInput.get();
	// 移动
	Node newNode = moveTo(direction);
	// 墙体监测
	if (!GameMap.isValid(newNode)) {
		throw new SnakeException("撞墙!游戏结束");
	}
	// 自我碰撞监测
	if (detectSelf(newNode)) {
		throw new SnakeException("撞到自己!游戏结束");
	}
	// 返回更改坐标
	GameMap.updateMap(lastNode, Constants.MAP_FLAG);
	GameMap.updateMap(newNode, Constants.SNAKE_FLAG);
	// 食物探测
	detectFood(newNode);
	// 添加到蛇头
	snakeLoc.addFirst(newNode);
}
    
private Node moveTo(int[] direction) {
	// 获取头节点
	Node firstNode = snakeLoc.getFirst();
	// 执行移动逻辑
	int x = firstNode.getX();
	int y = firstNode.getY();
	x += direction[0];
	y += direction[1];
	// 创建新节点
	return new Node(x, y);
}

6.业务流程编写

游戏类,主要控制全局的游戏流程

public class SnakeGame {
	// 创建地图
    private static GameMap map = new GameMap();
    // 创建蛇对象
    private static Snake snake = new Snake();
    // 创建监听服务
    private static ScanerListener listener = new ScanerListener();

    public static void main(String[] args) {
		// 开启游戏
        // 启动键盘监听服务
    	listener.start();
        try {
        	while(true) {
            	// 绘图
            	map.show();
            	// 睡眠1秒
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 移动蛇
                snake.move();
		        // 清空控制台
                cls();
            }
        } catch(SnakeException e) {
            e.printStackTrace();
        }
    }

    private static void cls() {
//        System.out.print("Everything on the console will cleared");
        System.out.print("\033[H\033[2J");
        System.out.flush();
    }
}

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

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

相关文章

Jetsonnano B01 笔记7:Mediapipe与人脸手势识别

今日继续我的Jetsonnano学习之路&#xff0c;今日学习安装使用的是&#xff1a;MediaPipe 一款开源的多媒体机器学习模型应用框架。可在移动设备、工作站和服务 器上跨平台运行&#xff0c;并支持移动 GPU 加速。 介绍与程序搬运官方&#xff0c;只是自己的学习记录笔记&am…

QT 完成登陆界面跳转到聊天室+完成学生管理系统的查找和删除功能

一、完成登陆界面跳转到聊天室 1> 项目结构 2> 源码 ① .pro ②main #include "mywnd.h" #include"chatCli.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);MyWnd w;w.show();Form f;QObject::co…

Vue2+Vue3基础入门到实战项目(前接六 副线一)—— 面经 项目

day1 接口文档地址&#xff1a;https://www.apifox.cn/apidoc/project-934563/api-20384515 一、项目功能演示 1.目标 启动准备好的代码&#xff0c;演示移动端面经内容&#xff0c;明确功能模块 2.项目收获 二、项目创建目录初始化 vue-cli 建项目 1.安装脚手架 (已安装…

对Spring核心思想的理解(二)

Spring的第二大核心思想&#xff0c;面向切面编程。 官方定义是&#xff1a;通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续&#xff0c;是软件开发中的一个热点&#xff0c;也是Spring框架中的一个重要内容&#xff0c;是函数式编程的一种衍…

【大数据实训】用Hbase模拟电影搜索引擎(四)

博主介绍&#xff1a;✌全网粉丝6W,csdn特邀作者、博客专家、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于大数据技术领域和毕业项目实战✌ &#x1f345;文末获取项目联系&#x1f345; 《云计算与大数据处理》课程大作业评分表 项目考核内…

python读取监控流通过websocket连接发送到java服务端,服务端推流到前端

python读取逐帧读取监控 import websocket import base64 import cv2 import numpy as npvideoPath "rtmp://ns8.indexforce.com/home/mystream" // 此为公开RTSP流def on_message(ws, message):print(1)def connection_tmp(ws):websocket.enableTrace(True)ws w…

时序分解 | MATLAB实现基于小波分解信号分解分量可视化

时序分解 | MATLAB实现基于小波分解信号分解分量可视化 目录 时序分解 | MATLAB实现基于小波分解信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于小波分解的分量可视化&#xff0c;MATLAB编程程序&#xff0c;用于将信号分解成不同尺度和频率的子信…

动态库的制作和使用

动态库和静态库的工作原理 配置环境变量 方式1&#xff1a; 坏处&#xff1a;环境变量是临时的 方式2&#xff1a; 1 用户级别的配置&#xff1a; 进入到/home&#xff0c;找到.bashrc&#xff0c;进入 先去找到库的路径 然后再到.bashrc最后一行输入路径 使其生效 2 系统…

芯片产业链补齐,中国成全球唯一拥有全部工业门类的国家

随着一款国产5G手机的发布&#xff0c;中国制造在芯片产业链方面也形成了自己的完整体系&#xff0c;成为全球唯一一个拥有全部工业门类的国家&#xff0c;这是非常值得高兴的消息。 中国早在2010年就成为全球最大制造国&#xff0c;不过当时中国制造所需要的芯片大多都需要从海…

解决img标签和p标签不能水平居中对齐的问题

现象如下&#xff1a; <div class"children"><div class"wrap"><ul><li class"product"><a href"#"><img src"./images/miphone1.jpg"><p>小米手机</p></a></li&…

【LeetCode-中等题】27. 移除元素

文章目录 题目方法一&#xff1a;快慢指针 题目 方法一&#xff1a;快慢指针 int fast 0;// 快指针 用于扫描需要的元素int slow 0;//慢指针 用于记录需要存放元素的位置class Solution { // 快慢指针public int removeElement(int[] nums, int val) {int fast 0;// 快指针…

页面分布引导新手指引(driver.js)

页面分布引导&#xff08;driver.js&#xff09; 最近由于有一个需求——做新手指引&#xff0c;在新用户进入页面的时候提供指引和帮助,快速让用户熟悉页面的功能,但是为了不要过多影响现有的页面逻辑和样式,找到一款非常好用的工具driver.js:Driver.js是一个功能强大且高度可…

golang-bufio 缓冲扫描

前面两篇博客&#xff0c;介绍了 bufio 包中的缓冲读和写&#xff08;bufio.go&#xff09;&#xff0c;下面再来介绍一下缓冲扫描&#xff08;scan.go&#xff09;。这个扫描的是用来对缓存读的更高级封装&#xff0c;提供了一些更易用的方法。 缓冲扫描 Scanner 提供了一个…

电子技术基础(三)__第2章放大电路原理__英文简称

静态分析&#xff0c; 又称为直流分析&#xff0c; 用于求出电路的直流工作状态&#xff0c; 即l输入信号 。 一 . 先看几个英文符号 : 集电极及发射极间电压, 简称管压降 : 发射结电压降&#xff0c; 二. 接着看 加上Q点的英文简称 Q点: 放大电路的静态工作点&#…

【Spring面试】二、BeanFactory与IoC容器的加载

文章目录 Q1、BeanFactory的作用是什么&#xff1f;Q2、BeanDefinition的作用是什么&#xff1f;Q3、BeanFactory和ApplicationContext有什么区别&#xff1f;Q4、BeanFactory和FactoryBean有什么区别&#xff1f;Q5、说下Spring IoC容器的加载过程&#xff08;※&#xff09;Q…

《向量数据库》——向量数据库的使用场景有哪些?

向量数据库在许多应用领域都有广泛的用途,特别是那些需要存储、检索和分析向量数据的场景。以下是一些常见的向量数据库使用场景: 1、相似性搜索: 推荐系统:用于根据用户的历史行为或兴趣,搜索相似用户或物品,以提供个性化推荐。图像检索:允许用户通过图像查询相似的图像…

Leangoo领歌 -敏捷任务管理软件,任务管理更轻松更透明

​任务管理&#xff0c;简单易懂&#xff0c;就是对任务进行管理。那怎么可以更好进行任务管理呢&#xff1f;怎么样样可以让任务进度可视化&#xff0c;一目了然呢&#xff1f;有效的管理可以让我们事半功倍。 接下来我们看一下如何借助任务管理软件高效的做任务管理。 首先…

机器学习实战-系列教程6:SVM分类实战1(鸢尾花数据集/软间隔/线性SVM/非线性SVM/scikit-learn框架)项目实战、原理解读、代码解读

&#x1f308;&#x1f308;&#x1f308;机器学习 实战系列 总目录 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 SVM分类实战1 SVM分类实战2 支持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09;&#xff0c;用于分类和…

开箱报告,Simulink Toolbox库模块使用指南(六)——S-Fuction模块(TLC)

文章目录 前言 Target Language Compiler&#xff08;TLC&#xff09; C MEX S-Function模块 编写TLC文件 生成代码 Tips 分析和应用 总结 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;一&#xff09;——powergui模块》 见《开箱报告&am…

Unity中Shader抓取屏幕并实现扭曲效果实现

文章目录 前言一、屏幕抓取&#xff0c;在上一篇文章已经写了二、实现抓取后的屏幕扭曲实现思路&#xff1a;1、屏幕扭曲要借助传入 UV 贴图进行扭曲2、传入贴图后在顶点着色器的输入参数处&#xff0c;传入一个 float2 uv : TEXCOORD&#xff0c;用于之后对扭曲贴图进行采样3、…