完全用链表实现的贪吃蛇

  • 格式:doc
  • 大小:200.00 KB
  • 文档页数:15

完全用链表实现的贪吃蛇 1.链表设计

同事突然说想实现一个贪吃蛇,这使我想起了几年前实现的一个很糟糕的贪吃蛇程序,代码可以在《一个java写的贪吃蛇程序》里面找到。如今,突然想再实现一个贪吃蛇,不过这次绝对不能再那么糟糕了。 用链表实现并且只用链表实现贪吃蛇是一个不错的主意,于是初步的打算就是先规划出到底需要什么链表,图示如下:

游戏面板上的所有的元素都处于一条或者多条链表之中,这样整个游戏的操作就简单了,无非就是将元素在链表之间移动来移动去。另外一个要点就是将“位置”信息作为静态数据保存,将元素本身和元素的位置完全分离,只是将位置当成元素的一个属性信息。 2.数据结构

到底用什么链表呢?Linux内核的“侵入式链表”list_head是一个不错的选择,这样就不用管理大量的链表节点内存了,因为“本身即链表”。因此设计以下的数据结构: 2.1.位置结构

view plain 1. struct posision { 2. int x; 3. int y; 4. };

很简单,没什么好说的,坐标而已,也可以扩展成三维坐标。需要注意,方向信息也作为一个posision保存,之所以可以这样做是因为posision是一个坐标,而一个坐标在坐标系中可以表示一个失量,矢量的含义就是有方向的数量,因此posision既可以表示位置,又可以表示方向。 2.2.节点结构

view plain 1. struct body { 2. struct list_head list; 3. struct posision pos; 4. struct posision direct; 5. void (*go_on) (struct body* this); 6. void *private; 7. };

其中go_on回调函数实现了蛇的前进动作,但是由于像障碍,边墙,食物本身也是链表元素,因此需要一个private数据来做扩展。需要指明的是,整个游戏中还有一个隐含的链表,那就是“拐弯”处的节点,这个链表记录了蛇身体该在哪个地方拐弯,因此很显然,拐弯链表是需要和键盘按键侦听器联系在一起的。 2.3.游戏面板

view plain 1. struct pad { 2. struct list_head snake_body; 3. struct list_head changes; 4. struct list_head kill_body; 5. struct list_head food; 6. unsigned int curr_status; 7. void (*game_handler) (struct pad* this); 8. void (*key_handler) (struct pad* this, struct posision key); 9. void (*random_gen_food) (struct pad* this); 10. void (*random_gen_fence) (struct pad* this); 11. void (*eat) (struct pad* this); 12. }; 该数据结构囊括了整个游戏的面板以及操作。 2.4.代码初步实现

有了上述的数据结构,实现代码就很简单了 首先,go_on的逻辑就是当前位置加上方向矢量 view plain

1. void gen_go_on(struct body* this) 2. { 3. sb->pos.x += sb->direct.x; 4. sb->pos.y += sb->direct.y; 5. }

键盘处理就是根据不同的按键来生成方向信息 view plain

1. void gen_key_handler(struct pad* game, unsigned int key) 2. { 3. //{0,-1},{0,1},{-1,0},{1,0} //这几个是静态的方向数据,全部是posision结构体 4. //ALLOC body -> cb 5. list_add (cb, &(game->changes)); 6. }

食物生成比较复杂,但是最简单的实现就是随机在面板的空闲链表中取出一个加入食物链表,我的这个贪吃蛇实现中,不但食物一次可以有多份,体现蛇的贪婪,并且还可以设置物体障碍,表现蛇玩命贪吃。 view plain

1. void gen_random_gen_food(struct pad* game) 2. { 3. //random pos avoid snake_body or kill_body 4. //ALLOC body -> fb 5. list_add (fb, &(game->food)); 6. } 最重要的就是以下这个game_handler了,它处理游戏的中心逻辑 view plain

1. unsigned int game_handler() 2. { 3. struct list_head *snake; 4. //以下的遍历判断蛇节点是否需要拐弯 5. list_for_each(snake, &(g_pad->snake_body)){ 6. struct body *sb = list_entry(snake, struct body, list); 7. struct list_head *change; 8. list_for_each(change, &(g_pad->changes)){ 9. struct body *cp = list_entry(change, struct body, list); 10. if (sb->pos.x == cp->pos.x && sb->pos.y == cp->pos.y) { 11. sb->direct = cp->direct; 12. if (snake->next == &snake_body) { 13. list_del (change); 14. } 15. break; 16. } 17. } 18. (*sb->go_on)(sb); 19. } 20. //以下的遍历判断蛇头是否碰到自身或者边墙 21. struct body *head = snake_body.next; 22. struct list_head *kb; 23. list_for_each(kb, &(g_pad->kill_body)){ 24. struct body *cp = list_entry(kb, struct body, list); 25. if (head->pos.x == cp->pos.x && head->pos.y == cp->pos.y) { 26. g_pad->curr_status = 1; 27. } 28. } 29. //以下的遍历判断蛇头是否碰到了食物,然后吃掉它 30. struct list_head *fd; 31. list_for_each(fd, &(g_pad->food)){ 32. struct body *cp = list_entry(fd, struct body, list); 33. if (head->pos.x == cp->pos.x && head->pos.y == cp->pos.y) { 34. list_del (cp); 35. list_add (cp, &(g_pad->snake_body)); 36. (*g_pad->eat)(g_pad); 37. (*g_pad->random_gen_food)(g_pad); 38. } 39. } 40. } 最后需要一个处理按键的函数 view plain

1. void key_handler() 2. { 3. //get key 4. (*g_pad->key_handler)(g_pad, key_pos); 5. }

最终我们发现还缺点什么,那就是这个实现测试起来很麻烦,还需要单独抽取linux内核的list.h。 3.Java实现

由于用C语言编写的代码在测试的时候需要图形库,而我最烦的就是GUI编程了,关键是因为自己太懒了,而GUI环境部署又需要一定的工作量,因此难耐了。在别人看来,部署一个qt开发环境是小菜一碟,在我看来比登天还难... 后来想在Windows平台搞,可以编译起来出了那么多的问题,不是缺这就是少那...最终,还是java是避开GUI环境的好办法。java的好处是环境部署最简单,因此还是用java来实现吧,和2004年实现的那个一样。虽然java中没有list_head,但是由于其本身就是面向对象的,且容器支持泛型,因此使用LinkedList也是不错的。 由于贪吃蛇的中心逻辑不在UI,因此也就只是简单的实现了一个UI,代码如下: view plain

1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. import java.util.*; 5. 6. class posision { 7. int x; 8. int y; 9. public posision(){} 10. public posision(int x, int y) { 11. this.x = x; 12. this.y = y; 13. } 14. } 15. /** 16. * 静态数据,保存所有的位置和方向信息