zoukankan      html  css  js  c++  java
  • 自做贪吃蛇游戏的android实现

    一直想自己动手做一个手机游戏,安装在自己的手机上,尽管应用或许会看起来很简单效果也不是很拉风,可是自己做的,那心情那感觉终究是不一样。今天,让我们一起探秘贪吃蛇游戏,用自己的双手玩转java代码打造属于自己的游戏。

    贪吃蛇是一款足够经典的游戏。它的经典,在于用户操作的简单,在于技术实现的简介,在于他的经久不衰,在于它的独领风骚。

    这里的贪吃蛇的android实现,是SDK Samples中的开源例程。可能各位都有看过-界面如下图啦。。。

    作为一个刚入门或者还没入门的新手,着实花了我一些力气来理解这段代码。

    对于各种不懂的地方,慢慢查询资料,对于新的方法,通过修改代码尝试效果。到现在终于能算个一知半解。

    在代码中,对于自己有所收获的地方,我都做了相应的注释。

    回过头来,觉得从这段代码中,能学到不少东西~~

    包括android应用的基本架构,他的面向对象的思想,以及代码的简洁明了。

    于是,我想到,何不将这些东西分享出来,如果碰巧对感兴趣的朋友们有搜帮助,那就更好了~

    好了,闲话不说~代码和注释如下(处于对源码的敬意,原本的英文注释部分都没有删去~大家可以配合理解):

    ************************************************************************************************************************************

    Snake工程中,总共有三个文件: *TileView是基于Android的View类实现的方块图类,用来支撑上层类的调用,绘制方块图的显 示界面。通过这些代码,能打之了解如何 扩展View,实现特色的界面效果。 *SnakeView调用了TileView,实现了游戏逻辑 和 具体的显示。 *Snake为主Activity类。

    建议大家按照上面的顺序看三个文件,可能逻辑上更舒服一点~~

    下面贴上代码和注释。

    PS:  调试版本为android2.2。 其他版本应该也没问题,不过得用虚拟机。

    TileView.java

    1.   
    1. package com.example.android.snake;  
    2.   
    3. import android.content.Context;  
    4. import android.content.res.TypedArray;  
    5. import android.graphics.Bitmap;  
    6. import android.graphics.Canvas;  
    7. import android.graphics.Paint;  
    8. import android.graphics.drawable.Drawable;  
    9. import android.util.AttributeSet;  
    10. import android.view.View;  
    11.   
    12.   
    13. /** 
    14.  * TileView: a View-variant designed for handling arrays of "icons" or other 
    15.  * drawables. 
    16.  *  
    17.  */  
    18.   
    19. public class TileView extends View {  
    20.   
    21.     /** 
    22.      * Parameters controlling the size of the tiles and their range within view. 
    23.      * Width/Height are in pixels, and Drawables will be scaled to fit to these 
    24.      * dimensions. X/Y Tile Counts are the number of tiles that will be drawn. 
    25.      */  
    26.   
    27.     protected static int mTileSize; //每个tile的边长的像素数量  
    28.   
    29.     protected static int mXTileCount; //屏幕内能容纳的 X方向上方块的总数量  
    30.     protected static int mYTileCount;//屏幕内能容纳的 Y方向上方块的总数量  
    31.   
    32.     private static int mXOffset; //原点坐标,按pixel计。  
    33.     private static int mYOffset;  
    34.   
    35.   
    36.     /** 
    37.      * A hash that maps integer handles specified by the subclasser to the 
    38.      * drawable that will be used for that reference 
    39.      * 存储着不同种类的bitmap图。通过resetTiles,loadTile,将游戏中的方块加载到这个数组。 
    40.      * 可以理解为 砖块字典 
    41.      */  
    42.     private Bitmap[] mTileArray;      
    43.   
    44.     /** 
    45.      * A two-dimensional array of integers in which the number represents the 
    46.      * index of the tile that should be drawn at that locations 
    47.      * 存储整个界面内每个tile位置应该绘制的tile。 
    48.      * 可看作是我们直接操作的画布。 
    49.      * 通过setTile、clearTile 进行图形显示的修改操作。  
    50.      *  
    51.      */  
    52.     private int[][] mTileGrid;   
    53.   
    54.     //画笔,canvas的图形绘制,需要画笔Paint实现。  
    55.     private final Paint mPaint = new Paint();  
    56.   
    57.       
    58.     public TileView(Context context, AttributeSet attrs, int defStyle) {  
    59.         super(context, attrs, defStyle);  
    60.         //使用TypedArray,获取在attrs.xml中为TileView定义的新属性tileSize 。参考: http://weizhulin.blog.51cto.com/1556324/311453  
    61.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);  
    62.         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
    63.         a.recycle();  
    64.     }  
    65.   
    66.     public TileView(Context context, AttributeSet attrs) {  
    67.         super(context, attrs);  
    68.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);  
    69.         mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);  
    70.         a.recycle();  
    71.     }  
    72.   
    73.       
    74.       
    75.     /** 
    76.      * Rests the internal array of Bitmaps used for drawing tiles, and 
    77.      * sets the maximum index of tiles to be inserted 
    78.      * 重置清零mTileArray,在游戏初始的时候使用。 
    79.      * 即清空砖块字典 
    80.      * @param tilecount 
    81.      */  
    82.     public void resetTiles(int tilecount) {  
    83.         mTileArray = new Bitmap[tilecount];  
    84.     }  
    85.   
    86.       
    87.     /* 
    88.      * 当改变屏幕大小尺寸时,同时修改tile的相关计数指标。 
    89.      */  
    90.       
    91.     @Override  
    92.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
    93.         mXTileCount = (int) Math.floor(w / mTileSize);  
    94.         mYTileCount = (int) Math.floor(h / mTileSize);  
    95.   
    96.         //mXOffset mYOffset是绘图的起点坐标。  
    97.         mXOffset = ((w - (mTileSize * mXTileCount)) / 2);  
    98.         mYOffset = ((h - (mTileSize * mYTileCount)) / 2);  
    99.   
    100.         mTileGrid = new int[mXTileCount][mYTileCount];  
    101.         clearTiles();  
    102.     }  
    103.   
    104.       
    105.     /** 
    106.      * Function to set the specified Drawable as the tile for a particular 
    107.      * integer key. 
    108.      * 加载具体的砖块图片 到 砖块字典。 
    109.      * 即将对应的砖块的图片 对应的加载到 mTileArray数组中 
    110.      * @param key 
    111.      * @param tile 
    112.      */  
    113.     public void loadTile(int key, Drawable tile) {  
    114.         //这里做了一个 Drawable 到 bitmap 的转换。由于外部程序使用的时候是直接读取资源文件中的图片,  
    115.         //是drawable格式,而我们的数组是bitmap格式,方便最终的绘制。所以,需要进行一次到 bitmap的转换。  
    116.         Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);  
    117.         Canvas canvas = new Canvas(bitmap);  
    118.         tile.setBounds(00, mTileSize, mTileSize);  
    119.         tile.draw(canvas);  
    120.           
    121.         mTileArray[key] = bitmap;  
    122.     }  
    123.   
    124.     /** 
    125.      * Used to indicate that a particular tile (set with loadTile and referenced 
    126.      * by an integer) should be drawn at the given x/y coordinates during the 
    127.      * next invalidate/draw cycle. 
    128.      * 在相应的坐标位置绘制相应的砖块 
    129.      * 记得哦,mTileGrid其实就是我们直接操作的画布。 
    130.      * @param tileindex 
    131.      * @param x 
    132.      * @param y 
    133.      */  
    134.     public void setTile(int tileindex, int x, int y) {  
    135.         mTileGrid[x][y] = tileindex;  
    136.     }  
    137.   
    138.     /** 
    139.      * Resets all tiles to 0 (empty) 
    140.      * 清空图形显示。 
    141.      * 用以更新画面。 
    142.      * 调用了绘图的setTile()。 
    143.      */  
    144.     public void clearTiles() {  
    145.         for (int x = 0; x < mXTileCount; x++) {  
    146.             for (int y = 0; y < mYTileCount; y++) {  
    147.                 setTile(0, x, y);  
    148.             }  
    149.         }  
    150.     }  
    151.   
    152. /* 
    153.  * 将我们直接操作的画布绘制到手机界面上! 
    154.  * @see android.view.View#onDraw(android.graphics.Canvas) 
    155.  */  
    156.     @Override  
    157.     public void onDraw(Canvas canvas) {  
    158.         super.onDraw(canvas);  
    159.         for (int x = 0; x < mXTileCount; x += 1) {  
    160.             for (int y = 0; y < mYTileCount; y += 1) {  
    161.                 if (mTileGrid[x][y] > 0) {  
    162.                     canvas.drawBitmap(mTileArray[mTileGrid[x][y]],   
    163.                             mXOffset + x * mTileSize,  
    164.                             mYOffset + y * mTileSize,  
    165.                             mPaint);  
    166.                 }  
    167.             }  
    168.         }  
    169.     }  
    170.   
    171. }  

    SnakeView.java

    1. package com.example.android.snake;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.Random;  
    5.   
    6. import android.content.Context;  
    7. import android.content.res.Resources;  
    8. import android.os.Handler;  
    9. import android.os.Message;  
    10. import android.util.AttributeSet;  
    11. import android.os.Bundle;  
    12. import android.util.Log;  
    13. import android.view.KeyEvent;  
    14. import android.view.View;  
    15. import android.widget.TextView;  
    16.   
    17. /** 
    18.  * SnakeView: implementation of a simple game of Snake 
    19.  */  
    20. public class SnakeView extends TileView {  
    21.   
    22.     private static final String TAG = "SnakeView";  
    23.   
    24.     /** 
    25.      * Current mode of application: READY to run, RUNNING, or you have already 
    26.      * lost. static final ints are used instead of an enum for performance 
    27.      * reasons. 
    28.      * 游戏的四种状态。初始时为 预备开始的状态。 
    29.      */  
    30.     private int mMode = READY;      
    31.     public static final int PAUSE = 0;  //暂停  
    32.     public static final int READY = 1;  //准备好了,预备开始  
    33.     public static final int RUNNING = 2;//正在运行  
    34.     public static final int LOSE = 3;   //结束,输了游戏  
    35.   
    36.     /** 
    37.      * Current direction the snake is headed. 
    38.      * 蛇体运动的方向标识。 
    39.      */  
    40.     private int mDirection = NORTH;  
    41.     private int mNextDirection = NORTH;  
    42.     private static final int NORTH = 1;  
    43.     private static final int SOUTH = 2;  
    44.     private static final int EAST = 3;  
    45.     private static final int WEST = 4;  
    46.   
    47.     /** 
    48.      * Labels for the drawables that will be loaded into the TileView class 
    49.      * 游戏中仅有的三种砖块对应的数值。 
    50.      */   
    51.     private static final int RED_STAR = 1;  
    52.     private static final int YELLOW_STAR = 2;  
    53.     private static final int GREEN_STAR = 3;  
    54.   
    55.     /** 
    56.      * mScore: used to track the number of apples captured mMoveDelay: number of 
    57.      * milliseconds between snake movements. This will decrease as apples are 
    58.      * captured. 
    59.      */  
    60.     private long mScore = 0;   //记录获得的分数。  
    61.     private long mMoveDelay = 600;  //每移动一步的延时。初始时设置为600ms,以后每吃一个果子,打个9折  
    62.                                 //造成的结果是速度越来越快。  
    63.       
    64.     /** 
    65.      * mLastMove: tracks the absolute time when the snake last moved, and is used 
    66.      * to determine if a move should be made based on mMoveDelay. 
    67.      * 记录上次移动的确切时间。 
    68.      * 同mMoveDelay一起处理与用户的异步操作的协同问题。 
    69.      */  
    70.     private long mLastMove;  
    71.       
    72.       
    73.     /** 
    74.      * mStatusText: text shows to the user in some run states 
    75.      * 用来显示游戏状态的TextView 
    76.      */  
    77.     private TextView mStatusText;  
    78.   
    79.     /** 
    80.      * mSnakeTrail: a list of Coordinates that make up the snake's body 
    81.      * mAppleList: the secret location of the juicy apples the snake craves. 
    82.      * 两个链表,分别用来存储 蛇体 和 果子的坐标。 
    83.      * 每次蛇体的运动,蛇体的增长,产生新的苹果,被吃掉苹果,都会在这里记录。 
    84.      */  
    85.     private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();  
    86.     private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();  
    87.   
    88.     /** 
    89.      * Everyone needs a little randomness in their life 
    90.      * 随机数生成器。用来产生随机的苹果。在addRandomApple()中使用。 
    91.      */  
    92.     private static final Random RNG = new Random();  
    93.   
    94.     /** 
    95.      * Create a simple handler that we can use to cause animation to happen.  We 
    96.      * set ourselves as a target and we can use the sleep() 
    97.      * function to cause an update/invalidate to occur at a later date. 
    98.      * 用Handler机制实现定时刷新。 
    99.      * 为什么使用Handler呢?大家可以参考 android 的线程模型(注意UI线程不是线程安全的~) 
    100.      * 具体使用方法网上的资源很多,在此不赘述~ 
    101.      */  
    102.     private RefreshHandler mRedrawHandler = new RefreshHandler();  
    103.   
    104.     class RefreshHandler extends Handler {  
    105.   
    106.         //获取消息并处理  
    107.         @Override  
    108.         public void handleMessage(Message msg) {  
    109.             SnakeView.this.update();  
    110.             SnakeView.this.invalidate(); //刷新view为基类的界面  
    111.         }  
    112.   
    113.         //定时发送消息给UI线程,以此达到更新的效果。  
    114.         public void sleep(long delayMillis) {  
    115.             this.removeMessages(0); //清空消息队列,Handler进入对新消息的等待  
    116.             sendMessageDelayed(obtainMessage(0), delayMillis); //定时发送新消息,激活handler  
    117.         }  
    118.     };  
    119.   
    120.   
    121.       
    122.     public SnakeView(Context context, AttributeSet attrs) {  
    123.         super(context, attrs);  
    124.         initSnakeView();  //构造函数中,别忘了,初始化游戏~  
    125.    }  
    126.   
    127.     public SnakeView(Context context, AttributeSet attrs, int defStyle) {  
    128.         super(context, attrs, defStyle);  
    129.         initSnakeView();  
    130.     }  
    131.   
    132.     //初始化SnakeView类,注意,这根初始化游戏是不一样的。  
    133.     private void initSnakeView() {  
    134.         setFocusable(true); //设置焦点,由于存在 文字界面 和 游戏界面的跳转。这个focus是不可或缺的。  
    135.   
    136.         //取得资源中的图片,加载到 砖块字典 中。  
    137.         Resources r = this.getContext().getResources();  
    138.         resetTiles(4);  
    139.         loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));  
    140.         loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));  
    141.         loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));  
    142.           
    143.     }  
    144.       
    145.     //如果不是从暂停中回复,就绪要 初始化游戏了。  
    146.     private void initNewGame() {  
    147.         //清空保存蛇体和果子的数据结构。  
    148.         mSnakeTrail.clear();  
    149.         mAppleList.clear();  
    150.   
    151.         // For now we're just going to load up a short default eastbound snake  
    152.         // that's just turned north  
    153.         // 设定初始状态的蛇体的位置。   
    154.           
    155.         mSnakeTrail.add(new Coordinate(77));  
    156.         mSnakeTrail.add(new Coordinate(67));  
    157.         mSnakeTrail.add(new Coordinate(57));  
    158.         mSnakeTrail.add(new Coordinate(47));  
    159.         mSnakeTrail.add(new Coordinate(37));  
    160.         mSnakeTrail.add(new Coordinate(27));  
    161.         mNextDirection = NORTH;  
    162.   
    163.         // Two apples to start with  
    164.         addRandomApple();  
    165.         addRandomApple();  
    166.   
    167.         mMoveDelay = 600;  
    168.         mScore = 0;  
    169.     }  
    170.   
    171.   
    172.     /** 
    173.      * Given a ArrayList of coordinates, we need to flatten them into an array of 
    174.      * ints before we can stuff them into a map for flattening and storage. 
    175.      *  
    176.      * @param cvec : a ArrayList of Coordinate objects 
    177.      * @return : a simple array containing the x/y values of the coordinates 
    178.      * as [x1,y1,x2,y2,x3,y3...】 
    179.      * 在游戏暂停时,需要通过Bundle方式保存数据。见saveState()。 
    180.      * Bundle支持简单的数组。 
    181.      * 所以需要将我们的部分数据结构,如蛇体和苹果位置的数组,转换成简单的序列化的int数组。 
    182.      */  
    183.     private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {  
    184.         int count = cvec.size();  
    185.         int[] rawArray = new int[count * 2];  
    186.         for (int index = 0; index < count; index++) {  
    187.             Coordinate c = cvec.get(index);  
    188.             rawArray[2 * index] = c.x;  
    189.             rawArray[2 * index + 1] = c.y;  
    190.         }  
    191.         return rawArray;  
    192.     }  
    193.   
    194.     /** 
    195.      * Save game state so that the user does not lose anything 
    196.      * if the game process is killed while we are in the  
    197.      * background. 
    198.      * 在意外情况下,暂时性保存游戏数据,在下次打开游戏时,可以继续游戏。如来电话了。 
    199.      * @return a Bundle with this view's state 
    200.      */  
    201.     public Bundle saveState() {  
    202.         Bundle map = new Bundle();  
    203.   
    204.         map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));  
    205.         map.putInt("mDirection", Integer.valueOf(mDirection));  
    206.         map.putInt("mNextDirection", Integer.valueOf(mNextDirection));  
    207.         map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));  
    208.         map.putLong("mScore", Long.valueOf(mScore));  
    209.         map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));  
    210.   
    211.         return map;  
    212.     }  
    213.   
    214.     /** 
    215.      * Given a flattened array of ordinate pairs, we reconstitute them into a 
    216.      * ArrayList of Coordinate objects 
    217.      * 是coordArrayListToArray()的逆过程,用来读取保存在Bundle中的数据。 
    218.      * @param rawArray : [x1,y1,x2,y2,...] 
    219.      * @return a ArrayList of Coordinates 
    220.      */  
    221.     private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {  
    222.         ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();  
    223.   
    224.         int coordCount = rawArray.length;  
    225.         for (int index = 0; index < coordCount; index += 2) {  
    226.             Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);  
    227.             coordArrayList.add(c);  
    228.         }  
    229.         return coordArrayList;  
    230.     }  
    231.   
    232.     /** 
    233.      * Restore game state if our process is being relaunched 
    234.      * 回复游戏数据。是saveState()的逆过程 
    235.      * @param icicle a Bundle containing the game state 
    236.      */  
    237.     public void restoreState(Bundle icicle) {  
    238.         setMode(PAUSE);  
    239.   
    240.         mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));  
    241.         mDirection = icicle.getInt("mDirection");  
    242.         mNextDirection = icicle.getInt("mNextDirection");  
    243.         mMoveDelay = icicle.getLong("mMoveDelay");  
    244.         mScore = icicle.getLong("mScore");  
    245.         mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));  
    246.     }  
    247.   
    248.     /* 
    249.      * handles key events in the game. Update the direction our snake is traveling 
    250.      * based on the DPAD. Ignore events that would cause the snake to immediately 
    251.      * turn back on itself. 
    252.      * 按键的监听。 
    253.      * 现在大多数的android手机都没有按键了。 
    254.      * 笔者就是在自己的模拟机上才能正常的使用这款小游戏的 - -# 
    255.      * @see android.view.View#onKeyDown(int, android.os.KeyEvent) 
    256.      */  
    257.     @Override  
    258.     public boolean onKeyDown(int keyCode, KeyEvent msg) {  
    259.         //这里是游戏的基本逻辑。如果你还没尝试一下它,先玩玩再说吧。那有助于你对代码的理解~  
    260.         if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {  
    261.             if (mMode == READY | mMode == LOSE) {  
    262.                 /* 
    263.                  * At the beginning of the game, or the end of a previous one, 
    264.                  * we should start a new game. 
    265.                  */  
    266.                 initNewGame();  
    267.                 setMode(RUNNING);  
    268.                 update(); //update()实现了对游戏数据的更新,是整个游戏的推动力。  
    269.                 return (true);  
    270.             }  
    271.   
    272.             if (mMode == PAUSE) {  
    273.                 /* 
    274.                  * If the game is merely paused, we should just continue where 
    275.                  * we left off. 
    276.                  */  
    277.                 setMode(RUNNING);  
    278.                 update();  
    279.                 return (true);  
    280.             }  
    281.   
    282.             if (mDirection != SOUTH) {  //如果按键的方向 跟蛇本身的运动方向完全相反,则无法执行  
    283.                 mNextDirection = NORTH;    
    284.             }  
    285.             return (true);  
    286.         }  
    287.   
    288.         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {  
    289.             if (mDirection != NORTH) {  
    290.                 mNextDirection = SOUTH;  
    291.             }  
    292.             return (true);  
    293.         }  
    294.   
    295.         if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {  
    296.             if (mDirection != EAST) {  
    297.                 mNextDirection = WEST;  
    298.             }  
    299.             return (true);  
    300.         }  
    301.   
    302.         if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {  
    303.             if (mDirection != WEST) {  
    304.                 mNextDirection = EAST;  
    305.             }  
    306.             return (true);  
    307.         }  
    308.   
    309.         return super.onKeyDown(keyCode, msg);  
    310.     }  
    311.   
    312.     /** 
    313.      * Sets the TextView that will be used to give information (such as "Game 
    314.      * Over" to the user. 
    315.      * 起初不明白这个方法有什么作用。删除了以后才发现错误。Snake类会调用到它,来绑定到相应的textview. 
    316.      */  
    317.     public void setTextView(TextView newView) {    
    318.         mStatusText = newView;  
    319.     }  
    320.   
    321.     /** 
    322.      * Updates the current mode of the application (RUNNING or PAUSED or the like) 
    323.      * as well as sets the visibility of textview for notification 
    324.      *  
    325.      * @param newMode 
    326.      */  
    327.     public void setMode(int newMode) {  
    328.         int oldMode = mMode;  
    329.         mMode = newMode;  
    330.   
    331.         if (newMode == RUNNING & oldMode != RUNNING) {  
    332.             mStatusText.setVisibility(View.INVISIBLE); //游戏开始后,将TextView的文字显示设置为不可见。  
    333.             update(); //注意到,在initGame中也有update(),不过放心~ 多次重复 update不会影响效果的,  
    334.                         //蛇的移动有mLastMove 和 mMoveDelay 来校验。这会在Update()中体现。  
    335.                         //当然,经过实验,注释掉这个update()似乎不会影响结果噢。  
    336.             return;  
    337.         }  
    338.   
    339.         Resources res = getContext().getResources();  
    340.         CharSequence str = "";  
    341.         if (newMode == PAUSE) {  
    342.             str = res.getText(R.string.mode_pause);  
    343.         }  
    344.         if (newMode == READY) {  
    345.             str = res.getText(R.string.mode_ready);  
    346.         }  
    347.         if (newMode == LOSE) {  
    348.             str = res.getString(R.string.mode_lose_prefix) + mScore  
    349.                   + res.getString(R.string.mode_lose_suffix);  
    350.         }  
    351.   
    352.         mStatusText.setText(str);  
    353.         mStatusText.setVisibility(View.VISIBLE);  
    354.     }  
    355.   
    356.     /** 
    357.      * Selects a random location within the garden that is not currently covered 
    358.      * by the snake. Currently _could_ go into an infinite loop if the snake 
    359.      * currently fills the garden, but we'll leave discovery of this prize to a 
    360.      * truly excellent snake-player. 
    361.      * 在地图上随机的增加果子。注意苹果的位置不可以是蛇体所在噢~这里有个小bug,没有检测 
    362.      * 产生的果子位置 可能与 另一个果子位置重合。 
    363.      * 新产生的果子的坐标会增加到mApplist的数组上。 
    364.      */  
    365.     private void addRandomApple() {  
    366.         Coordinate newCoord = null;  
    367.         boolean found = false;  
    368.         while (!found) {  
    369.             // Choose a new location for our apple  
    370.             //注意别产生在边框上的果子  
    371.             int newX = 1 + RNG.nextInt(mXTileCount - 2);  
    372.             int newY = 1 + RNG.nextInt(mYTileCount - 2);  
    373.             newCoord = new Coordinate(newX, newY);  
    374.   
    375.             // Make sure it's not already under the snake  
    376.             boolean collision = false;  
    377.             int snakelength = mSnakeTrail.size();  
    378.             for (int index = 0; index < snakelength; index++) {  
    379.                 if (mSnakeTrail.get(index).equals(newCoord)) {  
    380.                     collision = true;  
    381.                 }  
    382.             }  
    383.             // if we're here and there's been no collision, then we have  
    384.             // a good location for an apple. Otherwise, we'll circle back  
    385.             // and try again  
    386.             found = !collision;  
    387.         }  
    388.         if (newCoord == null) {  
    389.             Log.e(TAG, "Somehow ended up with a null newCoord!");  
    390.         }  
    391.         mAppleList.add(newCoord);  
    392.     }  
    393.   
    394.   
    395.     /** 
    396.      * Handles the basic update loop, checking to see if we are in the running 
    397.      * state, determining if a move should be made, updating the snake's location. 
    398.      * 刷新游戏状态。每次游戏画面的更新、游戏数据的更新,都是依靠这个update()来完成的。 
    399.      */  
    400.     public void update() {  
    401.         if (mMode == RUNNING) {  
    402.             long now = System.currentTimeMillis();  
    403.   
    404.             if (now - mLastMove > mMoveDelay) {  //这里是对蛇体游戏刚开始时连续的两个移动速率的控制  
    405.                                     //主要作用应该是mMode变化时,对update()正确效果的保障。  
    406.                 clearTiles();       //清空 界面画布。        
    407.                 updateWalls();      //重新绘制墙壁  
    408.                 updateSnake();     //对蛇的 游戏逻辑 的处理 以及绘制  
    409.                 updateApples();   //对果子的 游戏逻辑 的处理 以及绘制  
    410.                 mLastMove = now;  
    411.             }  
    412.             mRedrawHandler.sleep(mMoveDelay);   //利用Handler进行 定时刷新的控制  
    413.         }  
    414.   
    415.     }  
    416.   
    417.     /** 
    418.      * Draws some walls. 
    419.      * 用setTile绘制墙壁 
    420.      */  
    421.     private void updateWalls() {  
    422.         for (int x = 0; x < mXTileCount; x++) {  
    423.             setTile(GREEN_STAR, x, 0);  
    424.             setTile(GREEN_STAR, x, mYTileCount - 1);  
    425.         }  
    426.         for (int y = 1; y < mYTileCount - 1; y++) {  
    427.             setTile(GREEN_STAR, 0, y);  
    428.             setTile(GREEN_STAR, mXTileCount - 1, y);  
    429.         }  
    430.     }  
    431.   
    432.     /** 
    433.      * Draws some apples. 
    434.      * 绘制果子 
    435.      */  
    436.     private void updateApples() {  
    437.         for (Coordinate c : mAppleList) {  
    438.             setTile(YELLOW_STAR, c.x, c.y);  
    439.         }  
    440.     }  
    441.   
    442.     /** 
    443.      * Figure out which way the snake is going, see if he's run into anything (the 
    444.      * walls, himself, or an apple). If he's not going to die, we then add to the 
    445.      * front and subtract from the rear in order to simulate motion. If we want to 
    446.      * grow him, we don't subtract from the rear. 
    447.      *  
    448.      */  
    449.     private void updateSnake() {  
    450.         boolean growSnake = false;  //吃过果子的蛇会长长。这个变量即为它的标记。  
    451.   
    452.         // grab the snake by the head  
    453.         Coordinate head = mSnakeTrail.get(0);  //头部很重要,只有头部可能碰到果子。  
    454.         Coordinate newHead = new Coordinate(11); //蛇下一步一定会前移,也就试newHead。长长只会从尾部增加。  
    455.                         //那么为啥不用Coordinate newHead 呢?反正肯定会给他赋值的。  
    456.                         //注意到之后咱们的程序是在switch语句中给newHead赋值的,这个是编译无法通过的~  
    457.         mDirection = mNextDirection;  
    458.   
    459.         switch (mDirection) {  
    460.         case EAST: {  
    461.             newHead = new Coordinate(head.x + 1, head.y);  
    462.             break;  
    463.         }  
    464.         case WEST: {  
    465.             newHead = new Coordinate(head.x - 1, head.y);  
    466.             break;  
    467.         }  
    468.         case NORTH: {  
    469.             newHead = new Coordinate(head.x, head.y - 1);  
    470.             break;  
    471.         }  
    472.         case SOUTH: {  
    473.             newHead = new Coordinate(head.x, head.y + 1);  
    474.             break;  
    475.         }  
    476.         }  
    477.   
    478.         // Collision detection  
    479.         // For now we have a 1-square wall around the entire arena  
    480.         //撞墙检测  
    481.         if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)  
    482.                 || (newHead.y > mYTileCount - 2)) {  
    483.             setMode(LOSE);  
    484.             return;  
    485.   
    486.         }  
    487.   
    488.         // Look for collisions with itself  
    489.         //撞自己检测  
    490.         int snakelength = mSnakeTrail.size();  
    491.         for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {  
    492.             Coordinate c = mSnakeTrail.get(snakeindex);  
    493.             if (c.equals(newHead)) {  
    494.                 setMode(LOSE);  
    495.                 return;  
    496.             }  
    497.         }  
    498.   
    499.         // Look for apples  
    500.         //吃果子检测  
    501.         int applecount = mAppleList.size();  
    502.         for (int appleindex = 0; appleindex < applecount; appleindex++) {  
    503.             Coordinate c = mAppleList.get(appleindex);  
    504.             if (c.equals(newHead)) {  
    505.                 mAppleList.remove(c);  
    506.                 addRandomApple();  
    507.                   
    508.                 mScore++;  
    509.                 mMoveDelay *= 0.9;  
    510.   
    511.                 growSnake = true;  
    512.             }  
    513.         }  
    514.   
    515.         // push a new head onto the ArrayList and pull off the tail  
    516.         //前进  
    517.         mSnakeTrail.add(0, newHead);  
    518.         // except if we want the snake to grow  
    519.         if (!growSnake) {  
    520.             mSnakeTrail.remove(mSnakeTrail.size() - 1);  
    521.         }  
    522.   
    523.         //绘制新的蛇体  
    524.         int index = 0;  
    525.         for (Coordinate c : mSnakeTrail) {  
    526.             if (index == 0) {  
    527.                 setTile(YELLOW_STAR, c.x, c.y);  
    528.             } else {  
    529.                 setTile(RED_STAR, c.x, c.y);  
    530.             }  
    531.             index++;  
    532.         }  
    533.   
    534.     }  
    535.   
    536.     /** 
    537.      * Simple class containing two integer values and a comparison function. 
    538.      * There's probably something I should use instead, but this was quick and 
    539.      * easy to build. 
    540.      * 这是坐标点的类。很简单的存储XY坐标。 
    541.      */  
    542.     private class Coordinate {  
    543.         public int x;  
    544.         public int y;  
    545.   
    546.         public Coordinate(int newX, int newY) {  
    547.             x = newX;  
    548.             y = newY;  
    549.         }  
    550.   
    551.         public boolean equals(Coordinate other) {  
    552.             if (x == other.x && y == other.y) {  
    553.                 return true;  
    554.             }  
    555.             return false;  
    556.         }  
    557.   
    558.         @Override  
    559.         public String toString() {  
    560.             return "Coordinate: [" + x + "," + y + "]";  
    561.         }  
    562.     }  
    563.       
    564. }  

    Snake.java

    1. package com.example.android.snake;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.view.Window;  
    6. import android.widget.TextView;  
    7.   
    8. /** 
    9.  * Snake: a simple game that everyone can enjoy. 
    10.  * This is an implementation of the classic Game "Snake", in which you control a 
    11.  * serpent roaming around the garden looking for apples. Be careful, though, 
    12.  * because when you catch one, not only will you become longer, but you'll move 
    13.  * faster. Running into yourself or the walls will end the game. 
    14.  */  
    15.   
    16. public class Snake extends Activity {  
    17.   
    18.   
    19.     private SnakeView mSnakeView;  
    20.       
    21.     private static String ICICLE_KEY = "snake-view";  
    22.   
    23.     /** 
    24.      * Called when Activity is first created. Turns off the title bar, sets up 
    25.      * the content views, and fires up the SnakeView. 
    26.      *  
    27.      */  
    28.     @Override  
    29.     public void onCreate(Bundle savedInstanceState) {  
    30.         super.onCreate(savedInstanceState);  
    31.   
    32.         setContentView(R.layout.snake_layout);  
    33.   
    34.         mSnakeView = (SnakeView) findViewById(R.id.snake);  
    35.         mSnakeView.setTextView((TextView) findViewById(R.id.text));  
    36.   
    37.         if (savedInstanceState == null) {  
    38.             // We were just launched -- set up a new game  
    39.             mSnakeView.setMode(SnakeView.READY);  
    40.         } else {  
    41.             // We are being restored  
    42.             Bundle map = savedInstanceState.getBundle(ICICLE_KEY);  
    43.             if (map != null) {  
    44.                 mSnakeView.restoreState(map);  
    45.             } else {  
    46.                 mSnakeView.setMode(SnakeView.PAUSE);  
    47.             }  
    48.         }  
    49.     }  
    50.   
    51.     @Override  
    52.     protected void onPause() {  
    53.         super.onPause();  
    54.         // Pause the game along with the activity  
    55.         mSnakeView.setMode(SnakeView.PAUSE);  
    56.     }  
    57.   
    58.     @Override  
    59.     public void onSaveInstanceState(Bundle outState) {  
    60.         //Store the game state  
    61.         outState.putBundle(ICICLE_KEY, mSnakeView.saveState());  
    62.     }  
    63.   
    64. }  
  • 相关阅读:
    Python—将py文件编译成so文件
    Python—网络通信编程之套接字基本原理
    Python—IO多路复用之epoll总结
    Python—IO多路复用之poll总结
    Python—IO多路复用之select总结
    希尔排序
    直接插入排序
    冒泡排序
    选择排序(C++,Java,Python实现)
    二叉搜索树的相关功能的实现
  • 原文地址:https://www.cnblogs.com/love-in-paradise/p/3403861.html
Copyright © 2011-2022 走看看