图3-4 通过事件监听建立联系
3.5 系统数据结构设计
3.5.1 蛇、石头、食物的表示
用一个大的表格来表示显示区域,里面的格子就是组成蛇的基本单位,一个格子可以代表一个食物,也可以代表一个石头,几块连在一起的石头就可以代表一条蛇。使用坐标来区分这些格子。
图3-5 蛇、石头、食物的表示
这里用一个集合来存储这些坐标,并且要求能够区分出蛇头和蛇尾。
3.5.2 蛇的移动
蛇向前移动一步,可以看作是蛇头前面增加了一个节点,蛇的尾巴上少了一个节点(即去尾,加头)
7
图3-6 蛇的移动
3.5.3 存储蛇身的数据结构
由于要区分蛇头和蛇尾,因此这个数据结构必须是有序的。另一方面,我们需要经常的访问第一个和最后一个节点,所以LinkedList最合适。因为它有getFirst(),getLast(),和removeLast()方法。
图3-7 根据原蛇头和方向计算新蛇头的坐标
3.5.4 蛇的移动方向
蛇下一步移动到哪里,是由方向来控制的。根据原蛇头的坐标和方向来确定新蛇头的坐标,因此在这里用int型常量来表示蛇的移动方向。在原坐标的基础上,根据蛇头的运动方向来确定新蛇头的坐标,比如向上移动就是y-1。
在Snake中增加蛇的方向向量:
public static final int UP = 1;上 public static final int DOWN = -1;下 public static final int LEFT = 2;左 public static final int RIGHT = -2;右
8
3.5.5 如何显示蛇身
显示蛇身,其实就是填充,用颜色填充格子。
需要4个参数,像素坐标x,像素坐标y,格子宽度,格子高度。 像素坐标就是矩形左上角的坐标
图3-8 格子坐标和像素坐标
3.5.6 蛇身的移动方向 (1)相反方向
和上一次移动的方向相反的方向称为相反方向,这种输入应该忽略。这样可以防止蛇从自己的身体中穿过。 (2)无效方向
在蛇的这一次移动之后和下一次移动之前这个时间间隔内输入了多个非相反方向,只有最后一个是有效方向,其他的都是无效方向。
蛇的最终运动方向以有效方向为准。
(3)Snake中增加监听器,并另外启动一个线程来不断调用蛇的move(),在Snake中提供一个启动线程的方法。 3.5.7 吃到食物判定
通过判定坐标是否重合来判断,只需要判断蛇头是否和食物重合就可以了,因为蛇的身体都是走蛇头走过的地方,蛇吃掉食物后身体就会变长。在Controller的snakeMoved中添加如下代码:
/* 判断是否吃到食物 */
if (food != null && food.isSnakeEatFood(snake)) {
/* 吃到食物后, 蛇增加身体, 再重新丢一个食物 */
9
snake.eatFood();
food.setLocation(ground == null ? food.getNew() : ground
.getFreePoint());
}/* 如果吃到食物, 就肯定不会吃到石头 */
else if (ground != null && ground.isSnakeEatRock(snake