我应该使用什么数据结构来制作贪吃蛇游戏?

10

我有一份学校作业,需要用Delphi制作像Nokia游戏中的贪吃蛇游戏。我想知道哪种解决方案最好。我想让我的贪吃蛇成为一个类(class),身体是一个点的数组(父类)或点的链表。哪个更好? 数组还是链表?


我总是喜欢在SO上回答作业问题。好奇是哪个学校和班级。。。 - Jim McKeeth
7个回答

10
一个简单的解决方案是创建一个类型为array[horizontal][vertical]的数组,这样屏幕上每个坐标都有一个元素。每个元素可以是蛇的方向、食物、毒药、墙壁或空白。这意味着你只需要记住蛇头和蛇尾的位置,以及食物和毒药的数量,而数组描述了屏幕的外观。
这消除了处理蛇的元素的麻烦,并使在屏幕上定位新的食物或毒药变得容易,确保你不会将其放入已经被占据的位置。
当你需要移除蛇的尾部元素时,使用direction:=array[tailx,taily]获取尾部的方向,然后将array[tailx,taily]:=empty。之后,根据方向更新tailx和taily。就这样。

9
一个链表更好。(每个节点可以指向前一个和后一个节点)在链表末尾添加节点更容易。如果使用数组,你需要调整数组大小或将其初始化为可能的最大蛇长度,这可能会浪费内存。
更新:本文讨论了Delphi中的指针,甚至建议使用简单的节点定义。请参考此Delphi文章

1
既然这是一份作业,任何使用指针的练习都会非常有帮助。 - skamradt
@skamradt 建议您提出另一个问题。您没有指定使用的编程语言。我通常不使用 Pascal(Delphi)进行编码,但我认为与 C++ 不同,它隐藏了指针的使用。 - Andrew
@Andrew,你仍然可以在Delphi中使用指针。它们并没有被隐藏,只是对于类实例来说是隐含的。 - skamradt
@skamradt - 很有趣 - 添加了一篇文章的链接,可能会有所帮助。Delphi比我意识到的更加多才多艺(我通常使用C#或Java编码,而C++则变得越来越遥远)。 - Andrew
1
Pascal(因此也包括Delphi)一直支持指针。我记得在1984年就写过链表! - Gerry Coll

3
我在我的贪吃蛇实现中使用了不同的方法。思路是存储:
  1. 蛇头的位置
  2. 蛇的长度
  3. 蛇头的方向
  4. “弯曲”对象的数组,其中一个弯曲由方向(左或右)和偏移量组成(例如,当蛇的第三个位置处有一个向左的弯曲时,偏移量为3)
这个方法非常高效,不像简单的数组那样愚笨,并且可以轻松地用于绘制“更加复杂”的蛇,例如带有圆角的蛇。

队列怎么会是愚蠢的呢?它显然是一种数据结构。如果你想要创建圆角,可以在渲染部分实现,游戏逻辑可以保持简单。 - user1267177

2

在Delphi中,我会使用Contnrs单元定义的TQueue。您可以将新坐标(蛇头)“push”到其中,当达到最大蛇大小时,只需调用“pop”即可释放蛇尾。

lp := new(PPoint);
lp^.X := x;
lp^.X := y;

Body.Push(lp);    

if Body.count > iSnakeLength then
  Dispose(Body.Pop); // Free the last TCoord that is pop'ed.

接下来,你需要做的就是绘制TObjectQueue中的内容。要访问TQueue的列表,你需要暴露List属性...为了实现这一点,只需像这样定义你的蛇身类;

  TSnakeBody = class(TObjectQueue)
  public
    property List;  //Expose the list
  end;

从队列中弹出元素会移除头部元素,而不是尾部。在哪个版本的贪吃蛇游戏中,蛇有最大长度?它应该不断增长 - 这是游戏的挑战。如果您需要访问队列的内部内容,则队列不是正确的数据结构。请改用普通列表。此外,我不确定New的语法是否有效。 - Rob Kennedy
队列是一种先进先出(FIFO)的数据结构,与栈相比,栈是后进先出(LIFO)的。 你正在添加到你的收藏中,而不是删除项目。 - Andrew
使用Delphi的TQueue(不是TStack),pop将删除第一个输入的数据。因此,您将推入头部,弹出尾部... 在我的贪吃蛇版本中,您必须让蛇吃东西才能使其变大。获取队列的内部内容是绘制蛇所必需的。 - Pmax

2

1
我不想让任何人替我完成作业,我只是想要有关数组或链表的建议。 - bAN

0

我有一个非常老的TurboPascal贪吃蛇程序。它使用一个数组来表示蛇的身体。

const MaxBodyLength = 100;
type
  TSnake = record
    Dir : (nord,sud,est,oest);
    Head : tpoint;
    BodyLength : integer;
    Body : array[1..MaxBodyLength] of tPoint;
    Tail : tpoint;
  end;    
var
  Snake : TSnake;
  Fruit : tPoint;

以及移动蛇的代码...

procedure Slither;
var i : integer;
    npos,lpos : tPoint;
    hasEaten:boolean;
begin
  npos:=Snake.Head;
  lpos:=Snake.Tail;
  case Snake.dir of
    East  : inc(npos.x);
    West  : dec(npos.x);
    South : inc(npos.y);
    North : dec(npos.y);
  end;
  hasEaten:=(npos.x=fruit.x) and (npos.y=fruit.y);
  if hasEaten then 
    inc(Snake.BodyLength)
  else
    Snake.Tail:=Snake.Body[Snake.BodyLength];

  for i:=Snake.BodyLength downto 2 do
    Snake.Body[i]:=Snake.Body[i-1];
  Snake.Body[1]:=Snake.Head; 

  if not hasEaten then
    Snake.Head:=npos;

  writeP(idHead,Snake.Head);  
  writeP(idBody,Snake.Body[1]); 

  if not hasEaten then 
   begin
    writeP(idTail,Snake.Tail); 
    writeP(idResidual,lPos); 
   end;
  if hasEaten then 
    NewFruit;
end;

我相当确定那不是Turbo Pascal。记录的数组长度是由记录的一个成员定义的?这是不允许的。记录在编译时具有固定的大小。你不能增加BodyLength字段并使数组变长。 - Rob Kennedy

0
你可以使用一个循环缓冲区。具体而言:
获取一个足够大的数组,以容纳最长的蛇。建立两个指针,一个用于头部,一个用于尾部。
一开始,尾部会在第一个单元格中,头部会在第三个单元格中。随着蛇的移动,将头指针向右移动并写入新坐标。然后,如果没有吃到食物,也将尾指针向右移动。如果任何一个指针试图超过数组的最右端,则将它们回绕到开头。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接