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

news2024/12/23 22:16:10

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 如何表示游戏界面

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的一维列表, 用来表示蛇的坐标
class Node{
    int x;
    int y;
    public Node() {}
    public Node(int x, int y) {
        this.x = x;
		this.y = y;
	}
}

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

2.2 如何渲染游戏界面

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

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

2.3 如何让游戏动起来

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

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

2.4 蛇如何移动

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

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.输入存储

/**
 * 存储用户的输入
 */
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.键盘监听

/**
 * 监听器, 监听输入
 */
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.蛇体初始化

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);
                    // 清空控制台
                    cls();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 移动蛇
                snake.move();
            }
        } 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/987941.html

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

相关文章

win10 sourcetree打开一闪就退出

参考文档: 解决方案参考文档一: Solved: cant install Sourcetree on Windows 10Solved: when I double-click the installation file or run as administrator,I can see a splash screen and disappear in 10 seconds,then I open thehttps://community.atlassian.com/t5…

GoFrame框架

GoFrame框架 GoFrame简介第一个GoFrame应用使用GoFrame实现一个完整的用户案例GoFrame对象管理GoFrame配置管理-配置对象GoFrame配置管理-文件配置GoFrame日志组件GoFrame错误处理GoFrame数据校验GoFrame Web服务简介GoFrame Web服务-路由规则GoFrame Web服务-路由注册GoFrame…

长胜证券:A股三大指数缩量探底券商股逆势拉升

周四&#xff0c;A股全天低开低走&#xff0c;三大指数均跌超1%&#xff0c;创业板指领跌。截至收盘&#xff0c;上证综指报收3122.35点&#xff0c;跌落1.13%&#xff1b;深证成指报收10321.44点&#xff0c;跌落1.84%&#xff1b;创业板指报收2056.98点&#xff0c;跌落2.11%…

索尼 toio ™应用创意开发征文|一个理想的绘画小助手

引言 toio™机器人是索尼推出的一款创意玩具&#xff0c;它的小巧和可编程性使其成为一个理想的绘画助手。通过编程控制机器人的运动和绘画工具&#xff0c;我们可以为小朋友提供一个有趣的绘画体验。 创意描述 我们可以通过JavaScript编程来控制toio™机器人的运动和绘画工具…

自己设计CPU学习之路——基于Xilinx FPGA应用开发

1. 一个32组位宽为32的寄存器堆 框图 代码 regfile.h ifndef __FEGFILE_HEADER__define __REGFILE_HEADER__define HIGH 1b1define LOW 1b0define ENABLE_ 1b0define DISABLE_ 1b1define DATA_W 32define DataBus 31:0define DATA_D 32de…

谷歌SEO手工外链工具好用吗?

答案是&#xff1a;不建议使用工具做谷歌外链&#xff0c;会被判断为作弊惩罚。 在当今的SEO环境中&#xff0c;外链仍然是一个核心组件&#xff0c;影响着网站的权威性和在搜索引擎结果页面&#xff08;SERP&#xff09;上的排名。 但是&#xff0c;随着时间的推移&#xff…

可视化大屏UI模板,随需切换,一秒生效

善用模板能够最大化地提高效率&#xff0c;美化可视化大屏&#xff0c;同时也能腾出更多时间去做深度数据分析挖掘。奥威BI可视化大屏支持UI模板随需切换&#xff0c;一秒生效。用户可以根据自己的喜好和需求&#xff0c;选择不同的UI模板&#xff0c;快速改变可视化大屏的外观…

密钥协商的几种方式和过程

密钥协商是在通信双方之间建立共享密钥的过程&#xff0c;以确保在通信过程中的数据安全性和机密性。下面将介绍几种常见的密钥协商方式和过程。 对称密钥协商 对称密钥协商是最简单和常见的密钥协商方式。在对称密钥协商中&#xff0c;通信双方通过一个安全的信道交换密钥。这…

如何将DHTMLX Suite集成到Scheduler Lightbox中?让项目管理更可控!

在构建JavaScript调度器时&#xff0c;通常需要为最终用户提供一个他们喜欢的方式来计划事件&#xff0c;这是Web开发人员喜欢认可DHTMLX Scheduler的重要原因&#xff0c;它在这方面提供了完全的操作自由&#xff0c;它带有lightbox弹出窗口&#xff0c;允许通过各种控件动态更…

Qt/C++音视频开发51-推流到各种流媒体服务程序

一、前言 最近将推流程序完善了很多功能,尤其是增加了对多种流媒体服务程序的支持,目前支持mediamtx、LiveQing、EasyDarwin、nginx-rtmp、ZLMediaKit、srs、ABLMediaServer等,其中经过大量的对比测试,个人比较建议使用mediamtx和ZLMediaKit,因为这两者支持的格式众多,不…

[Machine Learning][Day1]监督学习和无监督学习

目录 监督学习(Supervised Learning) 应用1&#xff1a;数据回归拟合 应用2&#xff1a;分类 无监督学习(Unsupervised Learning) 监督学习和无监督学习的区别 练习&#xff1a; 监督学习(Supervised Learning) 通过正确的输入和输出来进行学习的一种模型。输入x和正确的输…

你好,我是百川大模型|国内可开源免费商用Baichuan2揭秘

“ 百川智能发布了新一代语言模型Baichuan2。相比之前的第一代&#xff0c;新版本在各个学科领域的表现都有很大提升&#xff0c;特别是在数学、科学、安全方面的能力得到明显增强。Baichuan2以开源方式对外发布&#xff0c;为大模型领域提供了新的选择和可能。” 01 — 昨天下…

短信过滤 APP 开发

本文字数&#xff1a;7033字 预计阅读时间&#xff1a;42分钟 一直想开发一个自己的短信过滤 APP&#xff0c;但是一直没有具体实施&#xff0c;现在终于静下心来&#xff0c;边开发边记录下整体的开发过程。 01 垃圾短信样本 遇到的第一个问题是&#xff0c;既然要过滤垃圾短信…

JAR will be empty - no content was marked for inclusion!

现象 在对自建pom依赖组件打包时&#xff0c;出现JAR will be empty - no content was marked for inclusion!错误。 方案 在pom中怎么加packaging标签内容为pom&#xff0c;标识只打包pom文件 <?xml version"1.0" encoding"UTF-8"?> ...<grou…

MyBatis的逆向工程

文章目录 前言MyBatis的逆向工程创建逆向工程的步骤添加依赖和插件创建MyBatis的核心配置文件创建逆向工程的配置文件执行MBG插件的generate目标 QBC查询增改 总结 前言 MyBatis的逆向工程 正向工程&#xff1a;先创建Java实体类&#xff0c;由框架负责根据实体类生成数据库表…

《计算机网络安全》DNS与它的具体作用解析

初步了解DNS 个人介绍DNS定义DNS作用DNS的生活加速作用科普 个人介绍 &#x1f338;一名大四备考考研学子&#xff0c;喜欢前端&#xff0c;还有Android和JAVA开发 &#x1f332;爱看书和打游戏还有唱歌 &#x1f352;热爱编程和读古今中外名著 &#x1f33a;座右铭&#xff1…

JMeter 4.0 如何获取cookie

文章目录 前言JMeter 4.0 如何获取cookie1. 修改jmeter.properties 文件2. 添加HTTP Cookie 管理器3. 获取cookie信息 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天…

TikTok运营做不起来?IP如何选择?

作为跨境电商新蓝海&#xff0c;TikTok不断的加快自己推进电商业务的步伐&#xff0c;也吸引了越来越多的跨境商家入驻。但是很多人信心满满注册入驻后&#xff0c;却遇到了0播放&#xff0c;流量少&#xff0c;转化低的问题&#xff0c;Tiktok运营不起来&#xff0c;那你要注意…

大数据技术之Hadoop:MapReduce与Yarn概述(六)

目录 一、分布式计算 二、分布式资源调度 2.1 什么是分布式资源调度 2.2 yarn的架构 2.2.1 核心架构 2.2.2 辅助架构 前面我们提到了Hadoop的三大核心功能&#xff1a;分布式存储、分布式计算和资源调度&#xff0c;分别由Hadoop的三大核心组件可以担任。 即HDFS是分布式…

WebStorm2023新版设置多个窗口,支持同时显示多个项目工程

调整设置 Appearance & Behavior -> System Settings> Project open project in New window&#xff1a;