1.链表设计
同事突然说想实现一个贪吃蛇,这使我想起了几年前实现的一个很糟糕的贪吃蛇程序,代码可以在《 一个java写的贪吃蛇程序 》里面找到。如今,突然想再实现一个贪吃蛇,不过这次绝对不能再那么糟糕了。用链表实现并且只用链表实现贪吃蛇是一个不错的主意,于是初步的打算就是先规划出到底需要什么链表,图示如下:
游戏面板上的所有的元素都处于一条或者多条链表之中,这样整个游戏的操作就简单了,无非就是将元素在链表之间移动来移动去。另外一个要点就是将“位置”信息作为静态数据保存,将元素本身和元素的位置完全分离,只是将位置当成元素的一个属性信息。
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<body> snake_body;
- LinkedList<body> change_body;
- LinkedList<body> kill_body;
- LinkedList<body> food_body;
- LinkedList<body> free_body;
- LinkedList<posision> 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<posision> 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<body> snake_iter = snake_body.iterator();
- replace_body.clear();
- Iterator<body> 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<body> 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<body> 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<body> 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<body> 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<posision> pos_body){
- Graphics g = mainp.getGraphics();
- g.setColor(Color.green);
- Iterator<posision> 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<posision> 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中去,运行,将会出现以下的游戏画面: