天码营java贪吃蛇源代码_如何定义一只贪吃蛇?集合类的使用

如何设计一个类

在总体设计中我们给出了几个类,构成了应用的整体概览。具体到每一个类,则需要我们继续去定义其内部结构。

设计一个类时,往往还要考虑它的接口和继承层次,这里我们暂时无需考虑。

简单地理解,一个类的内部无外乎两部分:

成员变量:一个类操作的数据和内容应该被定义为成员变量,这些成员变量共同构成了一个对象的状态。

成员方法:公有方法就是这个类提供给外部世界的接口,系统中的其他类可以通过公有方法来操作这个类的数据,因此需要考虑这个类的职责和功能,从而确定公有方法。私有方法则一般为公有方法的辅助方法,供内部调用。

现在我们来考虑如何编写Snake类。

设计成员变量

一条贪吃蛇是由一个一个的节点组成的,在传统的贪吃蛇应用中这个节点通常展示为一个黑色的小方块。所以我们需要选择一种数据结构来表示这些相互连接的节点。不过在这之前,需要先定义出节点这个东西。

显然,表示节点状态的就是它的X坐标和Y坐标,那么我们通过一个类来定义节点:

package com.tianmaying.snake;

public class Node{

private final int x;

private final int y;

public Node(int x, int y){

this.x = x;

this.y = y;

}

public int getX(){

return x;

}

public int getY(){

return y;

}

}

提示

成员变量x和y构成了一个Node的状态。注意这两个成员变量使用final修饰了,表示进行初始赋值之后就不能改变。

选择数据结构

为了表示相互连接在一起的节点,我们可以为Snake定义一个集合类型的成员变量,让集合来保存所有节点。

你可能会说也可以使用数组来存储一组节点,但是数组的尺寸是固定的,通常情况下程序总是在运行时根据条件来创建对象,我们可能无法预知将要创建对象的个数(贪吃蛇的身体会不断变长),这时Java的集合(Collection)类了(通常也称集合为容器)就是一个很好的选择,因为它们可以帮我们方便地组织和管理一组对象。

提示

关于集合请参考Java集合。

常用的集合类包括Map、 List和Set,这里显然List是比较适合的,它提供了一系列操作一个元素序列的方法。

接下来要考虑的问题是选择哪一种List,因为List也有许多种,常见的有ArrayList和LinkedList。这两者的主要不同在于:

ArrayList:通过下标随机访问元素快,但是插入、删除元素较慢

LinkedList:插入、删除和移动元素快,但是通过下标随机访问元素性能较低

其实ArrayList是基于数组实现的,而LinkedList是基于链表实现的。这两种数据结构的特点决定了这两个容器的不同之处。

结合我们自己的应用场景可以发现,贪吃蛇不断变长小经常做插入操作,而且我们不需要随机去访问贪吃蛇中的某一个节点。因此,果断选择LinkedList。

有了这个思考过程,接下来Snake的成员变量就很清晰了:

package com.tianmaying.snake;

import java.util.LinkedList;

public class Snake{

private LinkedList body = new LinkedList<>();

}

设计方法

Snake应该提供什么方法来操作自己的状态呢?贪吃蛇有两种情况下会有状态的变化,一种是吃到食物的时候, 一种就是做了一次移动的时候。

此外,贪吃蛇也需要定一些查询自己状态和信息的公有方法。比如获取贪吃蛇的头部,获取贪吃蛇的body,对应可以加入这些方法。

一开始可能定义的方法不够完整,没关系,在编码过程中你会很自然地发现需要Snake提供更多方法来完成特定功能,这个时候你再添加即可。

把这些方法加入进去之后,Snake的代码看起来就丰富多了:

package com.tianmaying.snake;

import java.util.LinkedList;

public class Snake{

private LinkedList body = new LinkedList<>();

public Nodeeat(Nodefood) {

// 如果food与头部相邻,则将food这个Node加入到body中,返回food

// 否则不做任何操作,返回null

}

public Nodemove(Direction direction) {

// 根据方向更新贪吃蛇的body

// 返回移动之前的尾部Node

}

public NodegetHead() {

return body.getFirst();

}

public NodeaddTail(Nodearea) {

this.body.addLast(area);

return area;

}

public LinkedList getBody(){

return body;

}

}

eat和move方法都给出了详细的处理流程,来动手练习一下吧。

提高

这里简单解释一下贪吃蛇移动一格的处理。第一感觉是让body中每个Node的坐标都改变一次,这是一个很笨的o(n)的做法,其实只需要在头部增加一个Node,尾部删除一个Node即可。

97b5c1de5e89e2edd319240cd3eb512a.png

定义意义明确的私有方法

一般情况下类中的每个方法不应该做太多的事情,体现在代码量上就是一个方法不要包含太多的代码。

一种最简单也是非常有用的方法就是提取出意义明确的私有方法,这样会让代码更加易懂,调试和维护都会更加方便。

大家可以对比一下下面两种写法:

public Nodeeat(Nodefood) {

if (Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) == 1) {

// 相邻情况下的处理

}

}

public Nodeeat(Nodefood) {

if (isNeighbor(body.getFirst(),food)) {

// 相邻情况下的处理

}

}

private boolean isNeighbor(Nodea, Nodeb) {

return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()) == 1;

}

我们推崇第二种写法,将节点相邻判断的逻辑提取到一个新的方法中,阅读eat()方法的代码时,一眼就知道if语句块要处理的问题。而第一种情况下,时间长了,你可能会一时想不起来这个长长的条件语句用来干嘛的了。

如果你说可以加注释的话,那么你想想让方法命名本身就成为有意义的“注释”是不是一种更好的方式呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值