【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解

 

Snake也是一个经典游戏了,Nokia蓝屏机的王牌游戏之一。Android SDK
1.5就有了它的身影。我们这里就来详细解析一下Android
SDK Sample中的Snake工程。本工程基于SDK 2.3.3版本中的工程,路径为:%Android_SDK_HOME%
/samples/android-10/Snake

一、Eclipse工程

 通过File-New
Project-Android-Android Project,选择“Create project from
existing sample”创建自己的应用SnakeAndroid,如下图:

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />

 

运行效果如下图:

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />
 

二、工程结构和类图

 其实Snake的工程蛮简单的,源文件就三个:Snake.java
SnakeView.java TileView.javaSnake类是这个游戏的入口点,TitleView类进行游戏的绘画,SnakeView类则是对游戏控制操作的处理。CoordinateRefreshHandler2个辅助类,也是SnakeView类中的内部类。其中,Coordinate是一个点的坐标(xy),RefreshHandlerRefreshHandler对象绑定某个线程并给它发送消息。如下图:

 

 【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />

任何游戏都需要有个引擎来推动游戏的运行,最简化的游戏引擎就是:在一个线程中While循环,检测用户操作,对用户的操作作出反应,更新游戏的界面,直到用户退出游戏。

Snake这个游戏中,辅助类RefreshHandler继承自Handler,用来把RefreshHandler与当前线程进行绑定,从而可以直接给线程发送消息并处理消息。注意一点:Handle对消息的处理都是异步。RefreshHandlerHandler的基础上增加sleep()接口,用来每隔一个时间段后给当前线程发送一个消息。handleMessage()方法在接受消息后,根据当前的游戏状态重绘界面,运行机制如下:

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />
 

这比较类似定时器的概念,在特定的时刻发送消息,根据消息处理相应的事件。update()sleep()间接的相互调用就构成了一个循环。这里要注意:mRedrawHandle绑定的是Avtivity所在的线程,也就是程序的主线程;另外由于sleep()是个异步函数,所以update()sleep()之间的相互调用才没有构成死循环。

最后分析下游戏数据的保存机制,如下:

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>1.Android <wbr>SDK <wbr>Sample-Snake详解1.Android SDK Sample-Snake详解” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 1.Android SDK Sample-Snake详解” />

 

这里考虑了Activity的生命周期:如果用户在游戏期间离开游戏界面,游戏暂停;或者由于内存比较紧张,Android关闭游戏释放内存,那么当用户返回游戏界面的时候恢复到上次离开时的界面。

 

三、源码解析

 

详细解析下源代码,由于代码量不大,以注释的方式列出如下:

1、Snake.java

 

package com.deaboway.snake;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

// 贪吃蛇: 经典游戏,在一个花园中找苹果吃,吃了苹果会变长,速度变快。碰到自己和墙就挂掉。
public class Snake extends Activity {

    private
SnakeView mSnakeView;

    private
static String ICICLE_KEY = “snake-view”;

    
    // 在
activity 第一次创建时被调用
  
 @Override
    public void
onCreate(Bundle savedInstanceState) {

  
   
 super.onCreate(savedInstanceState);
  
   
 setContentView(R.layout.snake_layout);

  
   
 mSnakeView = (SnakeView)
findViewById(R.id.snake);
  
   
 mSnakeView.setTextView((TextView)
findViewById(R.id.text));

  
   
 // 检查存贮状态以确定是重新开始还是恢复状态
  
   
 if (savedInstanceState == null) {
  
   
   
 // 存储状态为空,说明刚启动可以切换到准备状态
  
   
   
 mSnakeView.setMode(SnakeView.READY);
  
   
 } else {
  
   
   
 // 已经保存过,那么就去恢复原有状态
  
   
   
 Bundle map =
savedInstanceState.getBundle(ICICLE_KEY);
  
   
   
 if (map != null) {
  
   
   
   
 // 恢复状态
  
   
   
   
 mSnakeView.restoreState(map);
  
   
   
 } else {
  
   
   
   
 // 设置状态为暂停
  
   
   
   
 mSnakeView.setMode(SnakeView.PAUSE);
  
   
   
 }
  
   
 }
    }

    //
暂停事件被触发时
  
 @Override
    protected
void onPause() {
  
   
 super.onPause();
  
   
 // Pause the game along with the activity
  
   
 mSnakeView.setMode(SnakeView.PAUSE);
    }

    //
状态保存
  
 @Override
    public void
onSaveInstanceState(Bundle outState) {
  
   
 // 存储游戏状态到View里
  
   
 outState.putBundle(ICICLE_KEY,
mSnakeView.saveState());
    }

}

 

2、SnakeView.java

 

package com.deaboway.snake;

import java.util.ArrayList;
import java.util.Random;

import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;

public class SnakeView extends TileView {

    private
static final String TAG = “Deaboway”;

    
    //
游戏状态,默认值是准备状态
    private int
mMode = READY;

    // 游戏的四个状态
暂停 准备 运行 和 失败
    public
static final int PAUSE = 0;
    public
static final int READY = 1;
    public
static final int RUNNING = 2;
    public
static final int LOSE = 3;

    //
游戏中蛇的前进方向,默认值北方
    private int
mDirection = NORTH;
    //
下一步的移动方向,默认值北方
    private int
mNextDirection = NORTH;

    // 游戏方向设定 北
南 东 西
    private
static final int NORTH = 1;
    private
static final int SOUTH = 2;
    private
static final int EAST = 3;
    private
static final int WEST = 4;

    
    //
三种游戏元
    private
static final int RED_STAR = 1;
    private
static final int YELLOW_STAR = 2;
    private
static final int GREEN_STAR = 3;

    
    //
游戏得分
    private long
mScore = 0;

    //
移动延迟
    private long
mMoveDelay = 600;

    
    //
最后一次移动时的毫秒时刻
    private long
mLastMove;

    
    //
显示游戏状态的文本组件
    private
TextView mStatusText;

    
    //
蛇身数组(数组以坐标对象为元素)
    private
ArrayList<Coordinate> mSnakeTrail =
new ArrayList<Coordinate>();

    //
苹果数组(数组以坐标对象为元素)
    private
ArrayList<Coordinate> mAppleList =
new ArrayList<Coordinate>();

    
    // 随机数
    private
static final Random RNG = new Random();

    
    //
创建一个Refresh Handler来产生动画: 通过sleep()来实现
    private
RefreshHandler mRedrawHandler = new RefreshHandler();

    //
一个Handler
    class
RefreshHandler extends Handler {

  
   
 // 处理消息队列
  
   
 @Override
  
   
 public void handleMessage(Message msg) {
  
   
   
 // 更新View对象
  
   
   
 SnakeView.this.update();
  
   
   
 // 强制重绘
  
   
   
 SnakeView.this.invalidate();
  
   
 }

  
   
 // 延迟发送消息
  
   
 public void sleep(long delayMillis) {
  
   
   
 this.removeMessages(0);
  
   
   
 sendMessageDelayed(obtainMessage(0),
delayMillis);
  
   
 }
    };

    
    //
构造函数
    public
SnakeView(Context context, AttributeSet attrs) {
  
   
 super(context, attrs);
  
   
 // 构造时初始化
  
   
 initSnakeView();
    }

    public
SnakeView(Context context, AttributeSet attrs, int defStyle)
{
  
   
 super(context, attrs, defStyle);
  
   
 initSnakeView();
    }

    // 初始化
    private void
initSnakeView() {
  
   
 // 可选焦点
  
   
 setFocusable(true);

  
   
 Resources r =
this.getContext().getResources();

  
   
 // 设置贴片图片数组
  
   
 resetTiles(4);

  
   
 // 把三种图片存到Bitmap对象数组
  
   
 loadTile(RED_STAR,
r.getDrawable(R.drawable.redstar));
  
   
 loadTile(YELLOW_STAR,
r.getDrawable(R.drawable.yellowstar));
  
   
 loadTile(GREEN_STAR,
r.getDrawable(R.drawable.greenstar));

    }

    //
开始新的游戏——初始化
    private void
initNewGame() {
  
   
 // 清空ArrayList列表
  
   
 mSnakeTrail.clear();
  
   
 mAppleList.clear();

  
   
 // For now we’re just going to load up a short
default eastbound snake
  
   
 // that’s just turned north
  
   
 // 创建蛇身

  
   
 mSnakeTrail.add(new Coordinate(7, 7));
  
   
 mSnakeTrail.add(new Coordinate(6, 7));
  
   
 mSnakeTrail.add(new Coordinate(5, 7));
  
   
 mSnakeTrail.add(new Coordinate(4, 7));
  
   
 mSnakeTrail.add(new Coordinate(3, 7));
  
   
 mSnakeTrail.add(new Coordinate(2, 7));

  
   
 // 新的方向 :北方
  
   
 mNextDirection = NORTH;

  
   
 // 2个随机位置的苹果
  
   
 addRandomApple();
  
   
 addRandomApple();

  
   
 // 移动延迟
  
   
 mMoveDelay = 600;
  
   
 // 初始得分0
  
   
 mScore = 0;
    }

 

3、TileView.java

 

package com.deaboway.snake;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

// View 变种,用来处理 一组 贴片—— “icons”或其它可绘制的对象
public class TileView extends View {

    

    protected
static int mTileSize;

    //
X轴的贴片数量
    protected
static int mXTileCount;
    //
Y轴的贴片数量
    protected
static int mYTileCount;

    //
X偏移量
    private
static int mXOffset;
    //
Y偏移量
    private
static int mYOffset;

    
    //
贴片图像的图像数组
    private
Bitmap[] mTileArray;

    
    //
保存每个贴片的索引——二维数组
    private
int[][] mTileGrid;

    //
Paint对象(画笔、颜料)
    private
final Paint mPaint = new Paint();

    //
构造函数
    public
TileView(Context context, AttributeSet attrs, int defStyle) {
  
   
 super(context, attrs, defStyle);

  
   
 TypedArray a =
context.obtainStyledAttributes(attrs,
  
   
   
   
 R.styleable.TileView);

  
   
 mTileSize =
a.getInt(R.styleable.TileView_tileSize, 12);

  
   
 a.recycle();
    }

    public
TileView(Context context, AttributeSet attrs) {
  
   
 super(context, attrs);

  
   
 TypedArray a =
context.obtainStyledAttributes(attrs,
  
   
   
   
 R.styleable.TileView);

  
   
 mTileSize =
a.getInt(R.styleable.TileView_tileSize, 12);

  
   
 a.recycle();
    }

    
    //
设置贴片图片数组
    public void
resetTiles(int tilecount) {
  
   
 mTileArray = new Bitmap[tilecount];
    }

    //
回调:当该View的尺寸改变时调用,在onDraw()方法调用之前就会被调用,所以用来设置一些变量的初始值
    //
在视图大小改变的时候调用,比如说手机由垂直旋转为水平
  
 @Override
    protected
void onSizeChanged(int w, int h, int oldw, int oldh) {

  
   
 // 定义X轴贴片数量
  
   
 mXTileCount = (int) Math.floor(w /
mTileSize);
  
   
 mYTileCount = (int) Math.floor(h /
mTileSize);

  
   
 // X轴偏移量
  
   
 mXOffset = ((w – (mTileSize * mXTileCount)) /
2);

  
   
 // Y轴偏移量
  
   
 mYOffset = ((h – (mTileSize * mYTileCount)) /
2);

  
   
 // 定义贴片的二维数组
  
   
 mTileGrid = new
int[mXTileCount][mYTileCount];

  
   
 // 清空所有贴片
  
   
 clearTiles();
    }

    
    //
给mTileArray这个Bitmap图片数组设置值
    public void
loadTile(int key, Drawable tile) {
  
   
 Bitmap bitmap = Bitmap.createBitmap(mTileSize,
mTileSize,
  
   
   
   
 Bitmap.Config.ARGB_8888);
  
   
 Canvas canvas = new Canvas(bitmap);
  
   
 tile.setBounds(0, 0, mTileSize, mTileSize);
  
   
 // 把一个drawable转成一个Bitmap
  
   
 tile.draw(canvas);
  
   
 // 在数组里存入该Bitmap
  
   
 mTileArray[key] = bitmap;
    }

    
    //
清空所有贴片
    public void
clearTiles() {
  
   
 for (int x = 0; x < mXTileCount;
x++) {
  
   
   
 for (int y = 0; y < mYTileCount;
y++) {
  
   
   
   
 // 全部设置为0
  
   
   
   
 setTile(0, x, y);
  
   
   
 }
  
   
 }
    }

    
    //
给某个贴片位置设置一个状态索引
    public void
setTile(int tileindex, int x, int y) {
  
   
 mTileGrid[x][y] = tileindex;
    }

    // onDraw
在视图需要重画的时候调用,比如说使用invalidate刷新界面上的某个矩形区域
  
 @Override
    public void
onDraw(Canvas canvas) {

  
   
 super.onDraw(canvas);
  
   
 for (int x = 0; x < mXTileCount; x
+= 1) {
  
   
   
 for (int y = 0; y < mYTileCount; y
+= 1) {
  
   
   
   
 // 当索引大于零,也就是不空时
  
   
   
   
 if (mTileGrid[x][y] > 0) {
  
   
   
   
   
 // mTileGrid中不为零时画此贴片
  
   
   
   
   
 canvas.drawBitmap(mTileArray[mTileGrid[x][y]],
mXOffset + x
  
   
   
   
   
   
   
 * mTileSize, mYOffset + y * mTileSize,
mPaint);
  
   
   
   
 }
  
   
   
 }
  
   
 }

    }
}

 

四、工程文件下载

 为了方便大家阅读,可以到如下地址下载工程源代码:

  http://ishare.iask.sina.com.cn/f/14312223.html

五、小结及下期预告:

 本次详细解析了Android SDK
自带 Sample——Snake的结构和功能。下次将会把这个游戏移植到J2ME平台上,并且比较AndroidJ2ME的区别和相通之处,让从事过J2ME开发的朋友对Android开发有个更加直观的认识。

转帖:相信谁—日本遭受地震海啸和随之而来的核电站危机

日本遭受地震海啸和随之而来的核电站危机,颇受世界瞩目,和利比亚事件一起,连续一周占据了全球媒体的头版头条,那边的地震刚刚告一段落,这边就看到网上的帖子开始对“日本人的下场”表示幸灾乐祸,于是不意外地料到这次事件在中国定会引起“爱国主义”的人们和“大爱无疆”人们的一场论战。但没料到的是,事情会在上周末演变成一场莫名其妙的抢盐闹剧,随后的是政府的辟谣,幕后黑手的查出等等相关新闻,然后是海底核试验的说法满天飞,然后是媒体的辟谣,没完没了,又似曾相识,惊诧之余,不禁对常识之缺乏,想象力之丰富,和信任的完全缺失而感叹。
不禁想起,上华盛顿大学EMBA的第一节课,是一个源自美国的危机处理模拟练习,班上同学分为七组,危机的背景是一样的:大家乘坐的飞机在沙漠中坠毁,坠毁前不久某组员曾留意到飞机刚飞过一个小镇,幸存者只有自己的组员,手里有着有限的物资:食物,水,汽油,化妆包,帆布,各种工具等若干。
模拟要求每组幸存者讨论并实施如何生存和获救。通过一小时的激烈争论后,每小组推选自己的领袖发表自己组的求生方案,包括如何善用有限的物资等。发表完后,由教授根据沙漠救援专家的指示发表意见,评估各项措施的得与失,比如,化妆包中的小镜子可用作向空中反射阳光来指示位置求救,或是白天必须用帆布覆盖身体以求活命等等。七组中,有三组主张离开原地向疑似有人烟的方向进发求救;四组主张呆在飞机残骸原地等待救援。教授的评判是,所有在烈日下走开求救的方法都是死路一条,原地保存实力才是正确答案。可想而知,这样的评判本身也遭遇了质疑,教授只能拿出救援专家的详解来平息这场争论。有趣的是,大家在争论结束后,竟然发现三个主张离开求救的组,他们的组长都是中国人,他们的意见在组里占上风;而四个得高分的组,无一例外的,都是老外话事的组,包括一个美国人,一个ABC,一个德国人和一个日本人。
上完课中午吃饭的时候大家还意犹未尽地在讨论,一个中国同学忽然一语中的地说:这样迥然不同的自救方法完全是来自于文化差异,在大多数老外的心目中,早早植根了若有危险,保持镇定,很快会有人来救援的信心的种子,这种信心,来源于对政府,对社团,对专家,对其他人的信任。而中国的同学们,潜意识里早早就没有指望别人,也不相信会有人快速来救,这样信任感高度缺失的情况下,自救是上上之选,那么纷纷走上沙漠中自取灭亡之路也就一点都不奇怪了。而案例的评判者所依据的,也是美国的生存法则,如果换做中国专家,或许会有完全相反的建议和评判了。大家听完,细想之下,都纷纷称是。
其实中国人现在的极端缺乏信任感并不只体现在危机之前,就是在日常生活的办公室里,又有多少人敢靠上司?靠下属?还有靠同事?激烈的竞争,僧多粥少的焦虑和道德底线的越来越低,令到大部分人不敢靠人,也不想被人靠,恶性循环之下,大家都选择了只敢靠自己。我的一个美国同事,就频频向我抱怨他们在中国的公司,就总是公司政治气氛浓郁,人和人之间合作的少,猜疑的多。
记得两年前有个旧段子,说的是:看完《色戒》,觉得女人不可靠;看完《苹果》,觉得老婆不可靠,看完《投名状》,觉得兄弟不可靠;看完《集结号》,觉得组织不可靠;看来看去,还是自己最可靠。不知道是这些电影反映了这些个“不可靠”,还是这些电影,洗脑了人们觉得什么都不可靠。而最后这貌似坚强的“靠自己”背后,又隐藏了多少的孤独和无奈?

php5配置与IIS中isapi筛选器不能加载PHP的解决办法

在WINDOWS 2003 SP2
中安装PHP就遇到了问题,IIS里面ISAPI加载不了,在isapi筛选器里添加PHP后重启IIS就不能访问网站了,打开网页就显示service
unavailable
打开ISAPI筛选器发现PHP是一个向下的红色箭头,说明没有加载。找了很久的原因,最后才发现是权限的问题。

第一步:把php-5.0.0-Win32.zip解压放到C:\php

第二步:php.ini-dist改名为php.ini,查找 ./ 找到第438行改成 extension_dir =
c:\php\ext

第三步:去掉565行;extension=php_mysql.dll前面的”;”注释符号

;extension=php_mcrypt.dll将前面的;号去掉

第四步:复制php.ini到Winnt目录下,再复制php5ts.dll和libmysql.dll到WINNT\system32\中

第五步:打开IIS,添加一个php后缀. (指向c:\php\php5isapi.dll) 
isapi筛选器里添加c:\php\php5isapi.dll

第六步:重新启动IIS

然后将 php安装目录/ext/php_mysql.dll  
php_mcrypt.dll 复制到 “c:/windows/system32/” 下

测试

<?php
echo phpinfo();
?>

isapi筛选器加载不了PHP的原因:

要将C盘的PHP目录的权限加上user,可写可修改,就可以加载

还有重要一点,如果按上面配置,打开PHP文件,出现“无法找到该页,您正在搜索的页面可能已经删除、更名或暂时不可用。”的话,这个记得在IIS里面的WEB服务扩展加上PHP的服务扩展。加一个PHP指向c:\php\php5isapi.dll,并设为允许。

黑莓 Bold9000和Nokia N97的一些使用

黑莓 Bold9000

复制:按下aA键之后滑动光标,选中需要的内容–黑莓键–复制。
粘贴:在需要粘贴的地方按黑莓键–粘贴。
彩信翻页:
光标移到页面的最后,按下ALT键再向右滑动下轨迹球就好了;还有种方法不用翻页,就是在看这条彩信前按F键,也就是转发,这样就可以一页显示完整条彩信的内容,不用翻页了。

复制粘贴的进阶版:只使用aA和轨迹球的确定键。
例如要复制一段话,按住aA键选中那段话,再按轨迹球,出现菜单,选择复制;在要粘贴的地方直接先按住aA再按下轨迹球,这样就直接粘过去了,更快速,不需要使用黑莓键。

Nokia N97
关闭虚拟键盘:设置-程序管理-已安装的程序-找到目标应用-选项-套件设置-关闭虚拟键盘