博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
完全用链表实现的贪吃蛇
阅读量:6692 次
发布时间:2019-06-25

本文共 15311 字,大约阅读时间需要 51 分钟。

1.链表设计

同事突然说想实现一个贪吃蛇,这使我想起了几年前实现的一个很糟糕的贪吃蛇程序,代码可以在《
》里面找到。如今,突然想再实现一个贪吃蛇,不过这次绝对不能再那么糟糕了。

     用链表实现并且只用链表实现贪吃蛇是一个不错的主意,于是初步的打算就是先规划出到底需要什么链表,图示如下:


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

2.数据结构

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

2.1.位置结构

struct posision {     int x;     int y; };

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

2.2.节点结构

struct body {     struct list_head list;     struct posision pos;     struct posision direct;     void (*go_on) (struct body* this);     void *private; };

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

2.3.游戏面板

struct pad {     struct list_head snake_body;     struct list_head changes;     struct list_head kill_body;     struct list_head food;     unsigned int curr_status;     void (*game_handler) (struct pad* this);     void (*key_handler) (struct pad* this, struct posision key);     void (*random_gen_food) (struct pad* this);     void (*random_gen_fence) (struct pad* this);     void (*eat) (struct pad* this); };

该数据结构囊括了整个游戏的面板以及操作。

2.4.代码初步实现

有了上述的数据结构,实现代码就很简单了

首先,go_on的逻辑就是当前位置加上方向矢量

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

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

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

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

void gen_random_gen_food(struct pad* game) {     //random pos avoid snake_body or kill_body     //ALLOC body -> fb     list_add (fb, &(game->food)); }

最重要的就是以下这个game_handler了,它处理游戏的中心逻辑

unsigned int game_handler() {     struct list_head *snake;     //以下的遍历判断蛇节点是否需要拐弯     list_for_each(snake, &(g_pad->snake_body)){         struct body *sb = list_entry(snake, struct body, list);         struct list_head *change;         list_for_each(change, &(g_pad->changes)){                 struct body *cp = list_entry(change, struct body, list);             if (sb->pos.x == cp->pos.x && sb->pos.y == cp->pos.y) {                 sb->direct = cp->direct;                 if (snake->next == &snake_body) {                     list_del (change);                 }                 break;             }         }         (*sb->go_on)(sb);     }     //以下的遍历判断蛇头是否碰到自身或者边墙     struct body *head = snake_body.next;     struct list_head *kb;     list_for_each(kb, &(g_pad->kill_body)){             struct body *cp = list_entry(kb, struct body, list);         if (head->pos.x == cp->pos.x && head->pos.y == cp->pos.y) {             g_pad->curr_status = 1;         }     }     //以下的遍历判断蛇头是否碰到了食物,然后吃掉它     struct list_head *fd;     list_for_each(fd, &(g_pad->food)){             struct body *cp = list_entry(fd, struct body, list);         if (head->pos.x == cp->pos.x && head->pos.y == cp->pos.y) {             list_del (cp);             list_add (cp, &(g_pad->snake_body));                 (*g_pad->eat)(g_pad);             (*g_pad->random_gen_food)(g_pad);         }     } }

最后需要一个处理按键的函数

void key_handler() {     //get key     (*g_pad->key_handler)(g_pad, key_pos); }

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

3.Java实现

由于用C语言编写的代码在测试的时候需要图形库,而我最烦的就是GUI编程了,关键是因为自己太懒了,而GUI环境部署又需要一定的工作量,因此难耐了。在别人看来,部署一个qt开发环境是小菜一碟,在我看来比登天还难...

     后来想在Windows平台搞,可以编译起来出了那么多的问题,不是缺这就是少那...最终,还是java是避开GUI环境的好办法。java的好处是环境部署最简单,因此还是用java来实现吧,和2004年实现的那个一样。虽然java中没有list_head,但是由于其本身就是面向对象的,且容器支持泛型,因此使用LinkedList也是不错的。

     由于贪吃蛇的中心逻辑不在UI,因此也就只是简单的实现了一个UI,代码如下:

import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*;  class posision {     int x;     int y;     public posision(){}     public posision(int x, int y) {         this.x = x;         this.y = y;     } } /**  * 静态数据,保存所有的位置和方向信息  * 这原本是可以定义到各个使用类内部以内部类实现的  * @author marywangran  */ class static_pos {     public static posision LEFT = new posision(-1, 0);     public static posision RIGHT = new posision(1, 0);     public static posision UP = new posision(0, -1);     public static posision DOWN = new posision(0, 1);     public static posision HALT = new posision(0, 0);     public static posision pos[][];     public static void setpos (int width, int height) {         int i = 0, j = 0;         int num = width*height;         pos = new posision[width][height];         for (i = 0; i < width; i++) {             for (j = 0; j < height; j++) {             pos[i][j] = new posision(i, j);             }         }     }     public static posision go_on(posision curr, posision direct) {         return pos[curr.x + direct.x][curr.y + direct.y];     } } /**  * 游戏中所有的元素都是链表,定义为body类  * @author marywangran  */ class body extends Object{     posision pos;     posision direct;     extension ext;     public body(){}     public body (posision pos, posision direct){         this.pos = pos;         this.direct = direct;     }     boolean go_on (boolean change, posision pos) {         if (change)             this.pos = static_pos.go_on(this.pos, this.direct);         else {             return (static_pos.go_on(this.pos, this.direct).x == pos.x)&&             (static_pos.go_on(this.pos, this.direct).y == pos.y);         }         return true;     }     void set_extension(extension ext) {         this.ext = ext;     }     extension get_extension() {         return this.ext;     }     //void *private; } /**  * 对应于C代码的void *private;每一个节点的可能有的所有属性  * @author marywangran  */ interface extension {     public void set_timeout(int timeout);     public long get_timeout();     public long star_time();     public void set_score(int score);     public int get_score(); } /**  * 食物的属性  * @author marywangran  *  */ class food_extension implements extension {     long time_out;     int score;     long start;     public void set_timeout(int timeout) {         this.time_out = timeout*1000;         this.start = System.currentTimeMillis();     }     public long get_timeout() {         return this.time_out;     }     public long star_time(){         return this.start;     }     public void set_score(int score) {         this.score = score;     }     public int get_score() {         return this.score;     } } /**  * 完全由链表实现的贪吃蛇游戏类,其本质就是处理body链表  * @author marywangran  */ class Snake_Game_Pad {     LinkedList snake_body;     LinkedList change_body;     LinkedList kill_body;     LinkedList food_body;     LinkedList free_body;     LinkedList
replace_body; int curr_status; int curr_level; int curr_score; int width, height; public Snake_Game_Pad () { snake_body = new LinkedList(); change_body = new LinkedList(); kill_body = new LinkedList(); food_body = new LinkedList(); free_body = new LinkedList(); replace_body = new LinkedList(); } /** * 贪吃蛇游戏初始化 * @param width 横向元素数量 * @param height 纵向元素数量 * @param init_length 初始蛇长度 */ public void init(int width, int height, int init_length) { this.width = width; this.height = height; int i = 0, j = 0; for (i = 0; i < this.width; i++){ for (j = 0; j < this.height; j ++) { free_body.add(new body(static_pos.pos[j][i], static_pos.HALT)); } } for (i = 0; i < this.width; i++){ body kb1 = free_body.get(0); body kb2 = free_body.get(free_body.size()-1); free_body.remove(kb1); kill_body.add(kb1); free_body.remove(kb2); kill_body.add(kb2); } for (i = 0; i < this.height-2; i ++) { body kb1 = free_body.get(i*(this.width-2)); body kb2 = free_body.get(i*(this.width-2)+this.width-1); free_body.remove(kb1); kill_body.add(kb1); free_body.remove(kb2); kill_body.add(kb2); } for (i = 0; i < init_length-1; i ++) { body sb = free_body.get(6); sb.direct = static_pos.RIGHT; free_body.remove(sb); snake_body.add(0,sb); kill_body.add(0,sb); } body sb = free_body.get(6); sb.direct = static_pos.RIGHT; free_body.remove(sb); snake_body.add(0,sb); random_gen_food (); } public void reset() { snake_body.clear(); change_body.clear(); kill_body.clear(); food_body.clear(); free_body.clear(); replace_body.clear(); } int get_curr_score() { return this.curr_score; } int get_curr_level(){ return this.curr_level; } /** * 贪吃蛇主处理函数 * @return 需要重绘的free_body元素的链表 */ LinkedList
game_handler() { body head = snake_body.get(0); body tail = snake_body.get(snake_body.size()-1); posision tail_pos = tail.pos; body replace_free; boolean need_gen = false; Iterator snake_iter = snake_body.iterator(); replace_body.clear(); Iterator food_iter = food_body.iterator(); while (food_iter.hasNext()){ body fb = food_iter.next(); extension fext = fb.get_extension(); //食物超时时间到了,食物消失。 if ((System.currentTimeMillis()-fext.star_time()) > fext.get_timeout()) { food_iter.remove(); free_body.add(fb); replace_body.add(fb.pos); need_gen = true; continue; } //贪吃蛇吃食物 if (head.go_on(false, fb.pos)) { food_iter.remove(); fb.direct = head.direct; snake_body.add(0, fb); kill_body.add(head); eat_food(); head = snake_body.get(0); random_gen_food (); } } if (need_gen) { //食物由于超时消失了,重新生成一个 this.random_gen_food(); } while (snake_iter.hasNext()) { body sb = snake_iter.next(); Iterator change_iter = change_body.iterator(); while (change_iter.hasNext()) { body cb = change_iter.next(); if ((sb.pos.x == cb.pos.x) && (sb.pos.y == cb.pos.y)) { sb.direct = cb.direct; if (!snake_iter.hasNext()) { change_body.remove(cb); } break; } } sb.go_on(true, null); } Iterator kill_iter = kill_body.iterator(); while (kill_iter.hasNext()) { body kb = kill_iter.next(); if ((kb.pos.x == head.pos.x) && (kb.pos.y == head.pos.y)) { this.curr_status = 1; /** * 死了的逻辑是什么,这是一个问题。 */ //System.exit(1); } } replace_free = free_body.get(head.pos.y*this.width+head.pos.x); replace_free.pos = tail_pos; replace_body.add(tail_pos); return replace_body; } void key_handler (posision direct) { body pb = new body(snake_body.get(0).pos, direct); snake_body.get(0).direct = pb.direct; change_body.add(pb); } /** * 该方法没有完全实现,现有的实现很丑陋。TODO * 1.将分值设定参数化,策略化,和当前级别联动 * 2.将超时时间和当前级别以及最近距离联动 */ void random_gen_food () { body fb; Random random =new java.util.Random(); Random big_random =new java.util.Random(); int big_score = 0; int size = free_body.size(); fb = free_body.get(random.nextInt(size)); free_body.remove(fb); food_extension fext = new food_extension(); fext.set_timeout(2000); fext.set_score(10); fb.set_extension(fext); food_body.add(fb); } /** * 该方法TODO * 实现设置障碍物,贪吃蛇除了不能触动自身以及边墙之外,也不能触动这些障碍 * 实现很简单,只需要将障碍添加到kill_body链表中即可 */ void random_gen_fence () { } void eat_food () { //实现加分等操作 } } /** * 简单的一个UI,测试用的,十分不完善,什么功能都没有 * @author marywangran */ class GameThread extends JFrame implements KeyListener ,Runnable { Snake_Game_Pad game; posision pos; JPanel mainp; public GameThread() { static_pos.setpos(200,200); game = new Snake_Game_Pad(); game.init(200, 200, 20); mainp=new JPanel(); mainp.addKeyListener(this); mainp.setFocusable(true); getContentPane().setLayout(null); getContentPane().add(mainp); mainp.setBounds(0,0,650,650); setSize(650,650); setResizable(false); setVisible(true); new Thread(this).start(); } public void prepaint(){ Graphics g = mainp.getGraphics(); this.setBackground(Color.white); int i ,j; for(i=0;i<600;i+=3) for(j=0;j<600;j+=3) { g.setColor(Color.green); g.fillRect(i,j,3,3); } g.setColor(Color.blue); } public void paint(Graphics g) { g = mainp.getGraphics(); Iterator snake_iter = game.snake_body.iterator(); while (snake_iter.hasNext()) { body sb = snake_iter.next(); g.setColor(Color.blue); g.fillRect(sb.pos.x*3, sb.pos.y*3,3,3); } Iterator food_iter = game.food_body.iterator(); while (food_iter.hasNext()) { body fb = food_iter.next(); g.setColor(Color.red); g.fillRect(fb.pos.x*3, fb.pos.y*3,3,3); } } public void paint_replace(LinkedList
pos_body){ Graphics g = mainp.getGraphics(); g.setColor(Color.green); Iterator
pos_iter = pos_body.iterator(); while (pos_iter.hasNext()){ posision pos = pos_iter.next(); g.fillRect(pos.x*3, pos.y*3,3,3); }//打印引起的悲哀 } public void run () { LinkedList
clist = null; prepaint(); while (true) { try { clist = game.game_handler(); repaint(); paint_replace(clist); Thread.sleep(100); }catch (Exception e) { e.printStackTrace();//System.exit(1); } } } public void keyPressed(KeyEvent ke) { } public void keyReleased(KeyEvent ke) { } public void keyTyped(KeyEvent key) { String kv = ""+key.getKeyChar(); if (kv.equals("w")) game.key_handler(static_pos.UP); else if (kv.equals("s")) game.key_handler(static_pos.DOWN); else if(kv.equals("a")) game.key_handler(static_pos.LEFT); else if(kv.equals("d")) game.key_handler(static_pos.RIGHT); } } public class Main { public static void main(String[] args) { new GameThread(); } }

在Eclipse中建立一个java project,然后把上述代码贴进Main.java中去,运行,将会出现以下的游戏画面:

 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1270941

转载地址:http://cijoo.baihongyu.com/

你可能感兴趣的文章
css3动画属性整理
查看>>
脚本 用python检测python 批量检查注释率是否达标
查看>>
如何针对性替换数组里的某几个对象
查看>>
git基础整理
查看>>
【前端】 form.get 方式上传对象数组给后台
查看>>
阿里智能工作软件机器人——码栈应用教程,让一切变得自动化
查看>>
Angular service 详解
查看>>
百度研发面经
查看>>
深度解析 Go 语言中「切片」的三种特殊状态
查看>>
Android Things 专题 3 开发环境搭建
查看>>
简述大数据实时处理框架
查看>>
hadoop(1)--前期准备,免密登录
查看>>
ES6 - 函数扩展
查看>>
Linux中apt与apt-get命令的区别与解释(转)
查看>>
原生js 类名操作 增加 删除
查看>>
iOS 中多音频处理
查看>>
Linux基本操作命令
查看>>
java.lang.IllegalStateException: aidl is missing
查看>>
求有向图G的转置图GT
查看>>
JS 加法知多少?
查看>>