贪吃蛇是一款非常经典的手机游戏,本文将使用MIDP实现这款著名的游戏。首先我将介
绍下主要用到的七个类:

WormMain:最主要的类,控制所有其它类的运行和销毁。

WormPit:处理键盘输入事件并实例化Worm类和WormFood类的。

Worm:抽象了贪吃蛇的属性和动作

WormFood:抽象了食物的属性和动作

WormScore:用来纪录分数的类

WormLink:抽象了蛇身上的一段,保存了这段的坐标、方向和所有状态。

WormException:处理异常类


基本概念介绍

节:一条蛇可以看成有许多正方形的“小格子”拼凑成,我把它称作节。节是蛇身
上最小的单位。

段:当许多节连成一条直线,我称它为段。上图的贪吃蛇只有一段,如果它拐弯就
变成两段。

链表:用来保存每一段的状态,链表的元素单位是段。且链表的最后一个元素表示
蛇的头部段。

坐标系:MIDP中的坐标以左上角那点为(0,0),向右则x递增,向下则y递增。

Worm类

一条完整的贪吃蛇是由一段一段组成的。链表中保存的第一个元素是蛇的尾巴段,最
后一个元素是蛇的头部段。当蛇运动的时候,它头部段增加一节而尾段减少一节。如果它
吃到了食物,尾部段就不减少一节。也就是说,蛇是从头部段开始长的。

下面的代码段显示了Worm类保存的各种属性:

/* 贪吃蛇可能移动的方向 */
public final static byte DOWN = 2;
public final static byte LEFT = 4;
public final static byte RIGHT = 6;
public final static byte UP = 8;
// 贪吃蛇的当前方向
private byte currentDirection;
// 保存贪吃蛇每一段的列表
private Vector worm = new Vector(5, 2);
// 是否需要更新状态
private boolean needUpdate;
// 是否在运动中
private boolean moveOnNextUpdate;
// 是否吃到食物
private boolean hasEaten;
// 贪吃蛇的初始位置、长度和方向
private final static int INIT_X = 3;
private final static int INIT_Y = 8;
private final static int INIT_LEN = 8;
private final static byte INIT_DIR = RIGHT;
//下面重点介绍下Worm类中的几个方法:
public void setDirection(byte direction) {
    //这个方法用来改变贪吃蛇运动的方向,只能90度。看下面的实现代码:
    if ((direction != currentDirection) && !needUpdate) {
        // 取出列表中的最后一个元素(蛇的头部)
        WormLink sl = (WormLink) worm.lastElement();
        int x = sl.getEndX();
        int y = sl.getEndY();
        // 不同的运动方向坐标的改变也不一样
        switch (direction) {
        case UP: // 当这段向上运动的时候
            if (currentDirection != DOWN) {
                y--;
                needUpdate = true;
            }
            break;
        case DOWN: // 当这段向下运动的时候
            if (currentDirection != UP) {
                y++;
                needUpdate = true;
            }
            break;
        case LEFT: // 当这段向左运动的时候
            if (currentDirection != RIGHT) {
                x--;
                needUpdate = true;
            }
            break;
        case RIGHT: // 当这段向右运动的时候
            if (currentDirection != LEFT) {
                x++;
                needUpdate = true;
            }
            break;
        }
        // 当更改方向后需要更新
        if (needUpdate == true) {
            worm.addElement(new WormLink(x, y, 0, direction));
            currentDirection = direction;
        }
    }
}
public void update(Graphics g) {
    //这个函数是更新贪吃蛇状态。每次更新都把头部增加一节,尾部减少一节。如果它吃
    //到食物尾部段就不减少一节。看起来就像整只蛇长了一节。
    // 把贪吃蛇头部增加一格
    head = (WormLink) worm.lastElement();
    head.increaseLength();
    // 如果没有吃到食物则尾部减少一格
    if (!hasEaten) {
        WormLink tail;
        tail = (WormLink) worm.firstElement();
        int tailX = tail.getX();
        int tailY = tail.getY();
        // 如果尾部块长度为0就删除
        tail.decreaseLength();
        if (tail.getLength() == 0) {
            worm.removeElement(tail);
        }
        // 尾部减少一格
        g.setColor(WormPit.ERASE_COLOUR);
        drawLink(g, tailX, tailY, tailX, tailY, 1);
    } else {
        // 如果吃到食物就不删除尾部
        hasEaten = false;
    }
    needUpdate = false;
    // 确认是否在边界中
    if (!WormPit.isInBounds(head.getEndX(), head.getEndY())) {
        // 如果不在,就死了
        throw new WormException("over the edge");
    }
    headX = (byte) head.getEndX();
    headY = (byte) head.getEndY();
    //贪吃蛇的头部增加一格
    g.setColor(WormPit.DRAW_COLOUR);
    drawLink(g, headX, headY, headX, headY, 1);
    // 判断是否吃到自己
    for (int i = 0; i < worm.size() - 1; i++) {
        sl = (WormLink) worm.elementAt(i);
        if (sl.contains(headX, headY)) {
            throw new WormException("you ate yourself");
        }
    }
    void drawLink(Graphics g, int x1, int y1, int x2, int y2, int len)
            //这个函数用来画蛇的一段 ,一只完整的蛇是一段一段组成的 。
            // 把长度转换成像素长度
            len *= WormPit.CELL_SIZE;
    // (x1 == x2)说明这一段是垂直的
    if (x1 == x2) {
        // 把x1转成像素长度
        x1 *= WormPit.CELL_SIZE;
        // (y2 < y1)说明是向上运动
        if (y2 < y1) {
            // 就把头、尾左边交换并转成像素
            y1 = y2 * WormPit.CELL_SIZE;
        } else {
            // 把y1转成像素
            y1 *= WormPit.CELL_SIZE;
        }
        g.fillRect(x1, y1, WormPit.CELL_SIZE, len);
    } else {
        // 这是水平的一段
        y1 *= WormPit.CELL_SIZE;
        if (x2 < x1) {
            // 就把头、尾左边交换并转成像素
            x1 = x2 * WormPit.CELL_SIZE;
        } else {
            x1 *= WormPit.CELL_SIZE;
        }
        g.fillRect(x1, y1, len, WormPit.CELL_SIZE);
    }
}
public void paint(Graphics g) {
    //画出一只完整的贪吃蛇
    WormLink sl;
    int x1, x2, y1, y2;
    int len;
    for (int i = 0; i < worm.size(); i++) {
        // 取出每一段,然后画出这一段,连起来就是一只完整的蛇
        sl = (WormLink) worm.elementAt(i);
        x1 = sl.getX();
        x2 = sl.getEndX();
        y1 = sl.getY();
        y2 = sl.getEndY();
        len = sl.getLength();
        drawLink(g, x1, y1, x2, y2, len);
    }
}
//WormLink类
//贪吃蛇是由一节一节组成的。因为它经常有一些节连成一条直线形成段,所以这是一
//种相对有效的方法来保存整个蛇。[X,Y]表示段头部的坐标,然后段的头部开始按照方向向
//后画若干节。(段的头尾和蛇的头尾不是一个概念)
//下面代码段是WormLink中的段得属性:
// 段头部坐标
private int x, y;
// 段长度
private int len;
// 移动方向
private byte dir;
//下面重点介绍几个重要函数:
public void decreaseLength() {
    //这是从段的头部减少一格
    // 首先段的总长度减少1
    len--;
    switch (dir) { // 不同的方向左边的改变也不一样
    case Worm.LEFT:
        x--;
        break;
    case Worm.RIGHT:
        x++;
        break;
    case Worm.UP:
        y--;
        break;
    case Worm.DOWN:
        y++;
        break;
    }
    public boolean contains(int x, int y)
            判断所给的坐标[x, y] 是否包含在段中
            switch (dir) { // 不同的方向判断的方法也不一样
    case Worm.LEFT:
        return ((y == this.y) && ((x <= this.x) && (x >= getEndX())));
    case Worm.RIGHT:
        return ((y == this.y) && ((x >= this.x) && (x <= getEndX())));
    case Worm.UP:
        return ((x == this.x) && ((y <= this.y) && (y >= getEndY())));
    case Worm.DOWN:
        return ((x == this.x) && ((y >= this.y) && (y <= getEndY())));
    }
}
public int getEndX() {
    //得到这一段的尾部x坐标(段方向指向的最后一格的坐标),当这段是蛇的头部段时,得
    //到的是头部最前面的坐标。
    // 不同的方向判断方法不一样
    if (dir == Worm.LEFT) {
        return x - len;
    }
    if (dir == Worm.RIGHT) {
        return x + len;
    }
    return x;
}
//WormPit类
//WormPit类中包括了Worm和WormFood。贪吃蛇将会在画面中移动寻找食物。如果它吃到
//食物它将会长一格。如果它碰到边界或者吃到自己将Game Over。
//下面介绍几个重要的函数:
private void paintPitContents(Graphics g) {
    //重绘屏幕上的所有元素
    // 更新贪吃蛇的状态
    myWorm.update(g);
    // 头部的位置和食物的位置重合就吃到食物
    if (myFood.isAt(myWorm.getX(), myWorm.getY())) {
        myWorm.eat();
        score += level;
        foodEaten++;
        if (foodEaten > (level << 1)) {
            /* 增加游戏难度 */
            forceRedraw = true;
            foodEaten = 0;
            level++;
            if (tonePlayer != null) {
                try {
                    tonePlayer.setMediaTime(0);
                    tonePlayer.start();
                } catch (MediaException me) {}
            }
        } else {
            if (audioPlayer != null) {
                try {
                    Manager.playTone(69, 50, 100); // Play audio
                } catch (MediaException me) {}
            }
        }
        g.setColor(WormPit.ERASE_COLOUR);
        // 填充长方形(三个字的宽度)
        g.fillRect((width - (SCORE_CHAR_WIDTH * 3)) - START_POS,
                   height - START_POS,
                   (SCORE_CHAR_WIDTH * 3),
                   SCORE_CHAR_HEIGHT);
        g.setColor(WormPit.DRAW_COLOUR);
        // 显示新的分数
        g.drawString("" + score,
                     width - (SCORE_CHAR_WIDTH * 3) - START_POS,
                     height - START_POS, Graphics.TOP | Graphics.LEFT);
        // 重新生成食物
        myFood.regenerate();
        int x = myFood.getX();
        int y = myFood.getY();
        while (myWorm.contains(x, y)) {
            // 如果食物和贪吃蛇的身体重复就重新生成
            myFood.regenerate();
            x = myFood.getX();
            y = myFood.getY();
        }
    }
    // 画出食物
    myFood.paint(g);
}
catch (WormException se) {
    gameOver = true;
}

public void run() {
    //主循环体:
    while (!gameDestroyed) { // 游戏不终止就一直循环执行
        try {
            synchronized (myWorm) { // 多线程中要进行同步
                // 如果游戏结束
                if (gameOver) {
                    if (WormScore.getHighScore(level) < score) {
                        // 把最高分保存
                        WormScore.setHighScore(level, score, "me");
                    }
                    if ((audioPlayer != null) &&
                        (audioPlayer.getState() == Player.STARTED)) {
                        try {
                            audioPlayer.stop();
                            Manager.playTone(60, 400, 100);
                        } catch (Exception ex) {}
                    }
                    // 重绘
                    repaint();
                    // 游戏结束时等待用户重新开始
                    myWorm.wait();
                } else if (gamePaused) {
                    //重绘
                    repaint();
                    // 游戏暂停时等待用户重新开始
                    myWorm.wait();
                } else {
                    // 游戏继续
                    myWorm.moveOnUpdate();
                    repaint();
                    // 这里的等待时间决定了游戏难度!!!
                    myWorm.wait(DEFAULT_WAIT - (level * 40));
                }
            }
        } catch (java.lang.InterruptedException ie) {
        }
    }
}
//WormMain类
//最主要的类,继承自MIDlet父类并实现了CommandListener接口。
protected void startApp() {
    //实现MIDlet父类的方法,当开始程序时首先执行这个函数
    // 显示画板
    Display.getDisplay(this).setCurrent(theGame);
    try {
        // 开始游戏线程
        Thread myThread = new Thread(theGame);
        myThread.start();
    } catch (Error e) {
        destroyApp(false);
        notifyDestroyed();
    }
}
public void commandAction(Command c, Displayable d) {
    //接受并处理用户输入事件
    // 重新开始
    if (c == restartCmd) {
        theGame.restart();
    }

    // 改变难度等级
    if (c == levelCmd) {
        Item[] levelItem = {
                           new Gauge("Level", true, 9, theGame.getLevel())
        };
        Form f = new Form("Change Level", levelItem);
        f.addCommand(OKCmd);
        f.addCommand(cancelCmd);
        f.setCommandListener(this);
        Display.getDisplay(this).setCurrent(f);
    }

    // 离开游戏
    if (c == exitCmd) {
        destroyApp(false);
        notifyDestroyed();
    }

    // 开始游戏
    if (c == startCmd) {
        theGame.removeCommand(startCmd);
        theGame.addCommand(restartCmd);
        theGame.restart();
    }

    // 确定
    if (c == OKCmd) {
        Form f = (Form) d;
        Gauge g = (Gauge) f.get(0);
        theGame.setLevel(g.getValue());
        Display.getDisplay(this).setCurrent(theGame);
    }

    // 取消
    if (c == cancelCmd) {
        Display.getDisplay(this).setCurrent(theGame);
    }

    // 打开音效
    if (c == audioOnCmd) {
        /* 打开音效 */
        theGame.createAudioPlayer();
        theGame.removeCommand(audioOnCmd);
        theGame.addCommand(audioOffCmd);
    }

    // 关闭音效
    if (c == audioOffCmd) {
        /* 关闭音效 */
        theGame.destroyAudioPlayer();
        theGame.removeCommand(audioOffCmd);
        theGame.addCommand(audioOnCmd);
    }
}