转:打造一款 Android 联网 tic-tac-toe 游戏
打造一款Android联网tic-tac-toe游戏
打造一款Android联网tic-tac-toe游戏
使用PHP、XML和Android开发包打造一款联网的多玩家tic-tac-toe游戏
JackDHerrington,高级软件工程师,FortifySoftware,Inc.
简介:本文讲述了如何使用本机Android前端应用程序打造一个支持联网对战的多玩家tic-tac-toe游戏的后端。
本文的标签:android,android小游戏,android开发,android游戏,game,php_(超文本预处理器),tic-tac-toe,xml,业务建模,应用开发...更多标签
游戏,联网
标记本文!
发布日期:2011年10月17日
级别:初级
原创语言:英文
访问情况:5438次浏览
评论:1(查看|添加评论-登录)
平均分5星共8个评分平均分(8个评分)
为本文评分
联网的多玩家tic-tac-toe游戏
常用缩略词
API:应用程序编程接口
HTTP:超文本传输协议
IP:Internet协议
SDK:软件开发包
SQL:结构化查询语言
UI:用户界面
XML:可扩展标记语言
休闲游戏十分流行,而且发展空间巨大,原因很显然。并非所有年龄段的所有人都对在线游戏感兴趣,第一人称射击游戏只适合反应快速的青少年群体。有时候,玩有时间思考和制定战略或者目标是与他人合作取得胜利的游戏更加吸引人。
从开发人员的角度看,休闲游戏的好处是,它们比图形密集的第一人称设计或运动游戏更容易打造。因此,一位或一群开发人员更容易做出全新的游戏。
在本文中,我们讲述了创建一个休闲、联网的多玩家tic-tac-toe游戏的基础。游戏服务器是一个基于MySQL和PHP、带有XML接口的Web应用程序。前端是一个运行在Android手机上的本机Android应用程序。
回页首
构建后端
后端从一个有两个表的简单MySQL数据库开始。清单1显示了该数据库的模式。
清单1.db.sql
TABLEIFEXISTSgames;TABLEgames(
idINTNOTNULLAUTO_INCREMENT,
primarykey(id));
TABLEIFEXISTSmoves;TABLEmoves(
idINTNOTNULLAUTO_INCREMENT,
gameINTNOTNULL,
xINTNOTNULL,
yINTNOTNULL,
colorINTNOTNULL,
primarykey(id));
第一个表是games表,用于保存游戏的惟一ID。在生产应用程序中,您可能有一个用户表,而games表正是包含了两位玩家的用户ID。但为了让事情变得简单,我放弃使用这种方法,而是集中讲述存储游戏数据、在客户端与服务器之间通信,以及打造前端的基础知识。
第二个表是moves表,它用于保存给定游戏的步着,因此共有五列。第一列是步着的惟一ID。第二列是这一步着应用的游戏ID。然后是动作所在的x和y坐标。因为格子是3x3的,这些值应该在0与2之间。最后一个字段是步着的“颜色”,它是一个值为X或O的整数。
为了构造数据库,首先使用mysqladmin创建它,然后使用mysql命令运行db.sql脚本,如下所示:
%mysqladmin--user=root--password=foocreatettt
%mysql--user=root--password=foottt<db.sql
这个步骤创建了一个名为“ttt”的新数据库,用于保存tic-tac-toe模式。
现在有了模式,您需要创建启动游戏的途径。为此,您有一个start.php脚本,如清单2中所示。
清单2.start.php
<?php('Content-Type:text/xml');
$dd=newPDO('mysql:host=localhost;dbname=ttt','root','');
$sql='INSERTINTOgamesVALUES(0)';
$sth=$dd->prepare($sql);
$sth->execute(array());
$qid=$dd->lastInsertId();
$doc=newDOMDocument();
$r=$doc->createElement("game");
$r->setAttribute('id',$qid);
$doc->appendChild($r);
$doc->saveXML();
?>
此脚本从连接数据库开始,然后对games表执行一条INSERT语句,并获取生成的ID。接下来,它创建一个XML文档,将ID添加给一个游戏标签,并导出XML。
您需要运行此脚本以在数据库中插入一个游戏,因为简单的Android应用程序没有用于创建游戏的界面。代码如下所示:
$phpstart.php
<?xmlversion="1.0"?>
<gameid="1"/>
$
现在您有了自己的第一个游戏。要查看游戏列表,使用清单3中的games.php脚本。
清单3.games.php
<?php('Content-Type:text/xml');
$dbh=newPDO('mysql:host=localhost;dbname=ttt','root','');
$sql='SELECT*FROMgames';
$q=$dbh->prepare($sql);
$q->execute(array());
$doc=newDOMDocument();
$r=$doc->createElement("games");
$doc->appendChild($r);
($q->fetchAll()as$row){
$e=$doc->createElement("game");
$e->setAttribute('id',$row['id']);
$r->appendChild($e);
}
$doc->saveXML();
?>
和start.php脚本一样,这个脚本也从连接数据库开始。然后它查询games表中的可用游戏。之后它创建一个新的XML文档,添加一个games标签,然后为每个可用的游戏添加游戏标签。
当您从命令行运行这个脚本时,将看到如下内容:
$phpgames.php
<?xmlversion="1.0"?>
<games><gameid="1"/></games>
$
您还可以从Web浏览器运行这个脚本,得到的输出相同。
很好!由于游戏API已经就绪,是时候编写处理步着的服务器代码了。这段代码一开始构建一个名为show_moves的helper脚本,用于获得给定游戏的当前步着,并把它们导出为XML。清单4显示了这个helper函数的PHP代码。
清单4.show_moves.php
<?phpshow_moves($dbh,$game){
$sql='SELECT*FROMmovesWHEREgame=?';
$q=$dbh->prepare($sql);
$q->execute(array($game));
$doc=newDOMDocument();
$r=$doc->createElement("moves");
$doc->appendChild($r);
foreach($q->fetchAll()as$row){
$e=$doc->createElement("move");
$e->setAttribute('x',$row['x']);
$e->setAttribute('y',$row['y']);
$e->setAttribute('color',$row['color']);
$r->appendChild($e);
}
print$doc->saveXML();
}
?>
此脚本获得了一个数据库句柄和游戏ID。之后,它执行SQL来获取步着列表。然后,它使用给定游戏的步着创建了一个XML文档。
创建这个helper函数的原因是有两个脚本将会用到它。第一个脚本是moves.php脚本,用于返回指定游戏的当前步着。清单5显示了这个脚本。
清单5.moves.php
<?php_once('show_moves.php');
('Content-Type:text/xml');
$dbh=newPDO('mysql:host=localhost;dbname=ttt','root','');
_moves($dbh,$_REQUEST['game']);
?>
这个简单的脚本包含helper函数代码,连接到数据库,然后使用指定的游戏ID调用show_moves函数。要测试这段代码,使用curl命令从命令行调用服务器上的脚本:
$curl"http://localhost/ttt/moves.php?game=1"
<?xmlversion="1.0"?>
<moves/>
$
遗憾的是,您尚未走出任何步着,因此输出没有什么特别意义。为了弥补这一点,您需要给服务器API添加最后一个脚本。清单6显示了move.php脚本。
清单6.move.php
<?php_once('show_moves.php');
('Content-Type:text/xml');
$dbh=newPDO('mysql:host=localhost;dbname=ttt','root','');
$sql='DELETEFROMmovesWHEREgame=?ANDx=?ANDy=?';
$sth=$dbh->prepare($sql);
$sth->execute(array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y']
));
$sql='INSERTINTOmovesVALUES(0,?,?,?,?)';
$sth=$dbh->prepare($sql);
$sth->execute(array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y'],
$_REQUEST['color']
));
_moves($dbh,$_REQUEST['game']);
?>
此脚本首先包含helper函数并连接数据库,然后执行两条SQL语句。第一条SQL语句用于删除可能与送入的步着相冲突的所有步着。第二条用于为指定步着在moves表中插入一个新行。该脚本接着将步着列表返回给客户端。这个步骤让客户端可以不必在每走出一个步着时都发出两个请求。带宽并不便宜,因此请求数量应该尽量减少。
为了测试以上内容有效,您可以走一个步着:
$curl"http://localhost/ttt/move.php?game=1&x=1&y=2&color=1"
<?xmlversion="1.0"?>
<moves><movex="1"y="2"color="1"/></moves>
完成游戏服务器代码之后,您可以构建这个多玩家联网游戏的Android前端。
回页首
构建Android前端
首先安装AndroidSDK和一些Android平台版本,最后是一些Eclipse和AndroidEclipse插件。幸运的是,所有这些软件均在Android网站(参见参考资料中的链接)上可以找到。关于如何建立开发环境的深入描述已经超出了本文的范围。
建立起开发环境之后,启动Eclipse并创建一个新的Android项目。您看到的画面应该与图1类似。
图1.在Eclipse中创建Android应用程序
NewAndroidProject向导屏幕截图,显示示例项目的详细信息
图1显示了Android应用程序的项目向导。输入一个项目名称,选择Createnewprojectinworkspace单选按钮,并指定UI元素的代码位置。在BuildTarget一览表中,选择一个Android平台。对于这段代码,我使用的是Android2.3.1。代码十分简单,您可以使用喜欢的任意版本。如果没有看到有任何平台列出,您需要下载并安装AndroidSDK安装说明中所提的平台。要注意,下载所有这些平台需要花费很长时间。
在Properties部分,填写应用程序名称和包名称。我使用的分别是“TicTacToe”和“com.jherrington.tictactoe”。接下来,选择CreateActivity复选框,并输入活动名称。我使用“TicTacToeActivity”作为活动名称。
单击Finish可以看到类似于图2的一个新项目。
图2.TicTacToe项目文件
新TicTacToe项目的文件和文件夹屏幕截图
图2显示了一个Android应用程序的顶级目录和文件(目录有src、gen、Android2.3.1和res,文件有assets、.xml、default.properties和proguard.cfg)。其中重要的几项包括:
res目录,包含资源
src目录,包含Java™源文件
清单文件,其中包含应用程序相关的传记信息
首先要编辑的是清单文件。大多数文件已经是正确的,但您需要添加Internet权限,以便让应用程序能够通过Internet发送请求。清单7显示了完成之后的清单文件。
清单7.AndroidManifest.xml
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionname="1.0"package="com.jherrington.tictactoe">
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-sdkandroid:minSdkVersion="5"/>
<applicationandroid:icon="@drawable/icon"android:label="@string/app_name">
<activityandroid:name="TicTacToeActivity"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
惟一的变化是在文件顶部增加了uses-permission标签。
您的下一个任务是设计UI。为此,调整位于res/layout目录中的layout.xml文件。清单8显示了这个文件的新内容。
清单8.layout.xml
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayoutandroid:layout_height="wrap_content"
android:layout_width="match_parent"android:id="@+id/linearLayout1">
<Buttonandroid:text="PlayX"android:id="@+id/playx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
<Buttonandroid:text="PlayO"android:id="@+id/playo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
</LinearLayout>
<com.jherrington.tictactoe.BoardViewandroid:id="@+id/bview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></com.jherrington.tictactoe.BoardView>
</LinearLayout>
这是一种直观的布局。顶部是一组封装在水平方向的线形布局中的两个按钮。这两个按钮就是用户用于指定玩游戏时所用颜色的X和O按钮。
余下代码是BoardView类,用于显示当前游戏的TicTacToe面板。BoardView类的代码如清单11所示。
布局就绪之后,是时候为应用程序编写一些Java代码了。首先编写清单9中的TicTacToeActivity类。活动是Android应用程序的基本构建块。每个应用程序都有一个或多个活动,代表着应用程序的各种状态。当您在应用程序中导航时,就创建了一个活动堆栈,使用手机上的后退按钮就就可从该堆栈中出来。TicTacToe应用程序只有一个活动。
清单9.TicTacToeActivity.java
com.jherrington.tictactoe;
java.util.Timer;
android.app.Activity;
android.os.Bundle;
android.view.View;
android.view.View.OnClickListener;
android.view.ViewGroup.LayoutParams;
android.widget.Button;
android.widget.Gallery;
android.widget.LinearLayout;
classTicTacToeActivityextendsActivityimplementsOnClickListener{
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Buttonplayx=(Button)this.findViewById(R.id.playx);
playx.setOnClickListener(this);
Buttonplayo=(Button)this.findViewById(R.id.playo);
playo.setOnClickListener(this);
Timertimer=newTimer();
UpdateTimerut=newUpdateTimer();
ut.boardView=(BoardView)this.findViewById(R.id.bview);
timer.schedule(ut,200,200);
}
publicvoidonClick(Viewv){
BoardViewboard=(BoardView)this.findViewById(R.id.bview);
if(v.getId()==R.id.playx){
board.setColor(2);
}
if(v.getId()==R.id.playo){
board.setColor(1);
}
}
}
活动有两个方法。第一个是onCreate方法,用于构建用户界面,将onClick处理程序关联到X与O按钮,并启动更新定时器。更新定时器用于每200毫秒刷新一次游戏状态。这项功能允许两位玩家在对方出着时进行观察。
onClick处理程序根据用户点击的是X还是O按钮设置面板的当前颜色。
清单10中的GameService类是一个单例类,代表给定游戏的游戏服务器和当前状态。
清单10.GameService.java
com.jherrington.tictactoe;
java.util.ArrayList;java.util.List;
javax.xml.parsers.DocumentBuilder;javax.xml.parsers.DocumentBuilderFactory;
org.apache.http.HttpResponse;
org.apache.http.NameValuePair;
org.apache.http.client.HttpClient;
org.apache.http.client.entity.UrlEncodedFormEntity;
org.apache.http.client.methods.HttpPost;
org.apache.http.impl.client.DefaultHttpClient;
org.apache.http.message.BasicNameValuePair;
org.w3c.dom.Document;
org.w3c.dom.Element;
org.w3c.dom.NodeList;
android.util.Log;
classGameService{
privatestaticGameService_instance=newGameService();
publicint[][]positions=newint[][]{
{0,0,0},
{0,0,0},
{0,0,0}
};
publicstaticGameServicegetInstance(){
return_instance;
}
privatevoidupdatePositions(Documentdoc){
for(intx=0;x<3;x++){
for(inty=0;y<3;y++){
positions[x][y]=0;
}
}
doc.getDocumentElement().normalize();
NodeListitems=doc.getElementsByTagName("move");
for(inti=0;i<items.getLength();i++){
Elementme=(Element)items.item(i);
intx=Integer.parseInt(me.getAttribute("x"));
inty=Integer.parseInt(me.getAttribute("y"));
intcolor=Integer.parseInt(me.getAttribute("color"));
positions[x][y]=color;
}
}
publicvoidstartGame(intgame){
HttpClienthttpclient=newDefaultHttpClient();
HttpPosthttppost=newHttpPost("http://10.0.2.2/ttt/moves.php");
try{
List<NameValuePair>nameValuePairs=newArrayList<NameValuePair>(2);
nameValuePairs.add(newBasicNameValuePair("game",Integer.toString(game)));
httppost.setEntity(newUrlEncodedFormEntity(nameValuePairs));
HttpResponseresponse=httpclient.execute(httppost);
DocumentBuilderFactorydbf=DocumentBuilderFactory.newInstance();
DocumentBuilderdb=dbf.newDocumentBuilder();
updatePositions(db.parse(response.getEntity().getContent()));
}catch(Exceptione){
Log.v("ioexception",e.toString());
}
}
publicvoidsetPosition(intgame,intx,inty,intcolor){
HttpClienthttpclient=newDefaultHttpClient();
HttpPosthttppost=newHttpPost("http://10.0.2.2/ttt/move.php");
positions[x][y]=color;
try{
List<NameValuePair>nameValuePairs=newArrayList<NameValuePair>(2);
nameValuePairs.add(newBasicNameValuePair("game",Integer.toString(game)));
nameValuePairs.add(newBasicNameValuePair("x",Integer.toString(x)));
nameValuePairs.add(newBasicNameValuePair("y",Integer.toString(y)));
nameValuePairs.add(newBasicNameValuePair("color",Integer.toString(color)));
httppost.setEntity(newUrlEncodedFormEntity(nameValuePairs));
HttpResponseresponse=httpclient.execute(httppost);
DocumentBuilderFactorydbf=DocumentBuilderFactory.newInstance();
DocumentBuilderdb=dbf.newDocumentBuilder();
updatePositions(db.parse(response.getEntity().getContent()));
}catch(Exceptione){
Log.v("ioexception",e.toString());
}
}
}
这段代码是应用程序中最有意思的一些代码。首先,使用updatePositions方法获取服务器返回的XML并查找步着元素,然后使用当前的步着集合更新位置数组。该位置数组对面板上的每个位置都有一个相应的值,0表示空白,1表示“O”,而2表示“X”。
其他两个函数startGame与setPosition用于和服务器通信。startGame方法从服务器请求当前的步着集合,并更新位置列表。setPosition方法创建一个HTTPpost请求,并使用一个名称/值对的数组(传输时将进行编码)为这个post请求装配数据,将步着发送给服务器。它接着会解析响应XML来更新位置列表。
如果您仔细观察,用于连接服务器的IP实际上很有意思。它不是“localhost”或“127.0.0.1”,而是“10.0.2.2”,即运行仿真器的计算机的别名。因为Android手机本身是UNIX®系统,它自己的服务位于localhost上。这很吸引人,不是吗?很明显,手机本身实际上并非手机,而是一台功能完备、手掌大小的计算机,只不过恰好内置了手机功能而已,这种情况还真不常见。
我们进展到哪儿了呢?我们已经有了应用程序的主要组件,即活动,设置了UI布局,编写了Java代码来连接到服务器。现在,您需要绘制游戏面板,而这需要通过清单11中的BoardView类来完成。
清单11.BoardView.java
com.jherrington.tictactoe;
android.content.Context;
android.graphics.Canvas;
android.graphics.Color;
android.graphics.Paint;
android.graphics.Rect;
android.util.AttributeSet;
android.view.MotionEvent;
android.view.View;
classBoardViewextendsView{
privateint_color=1;
publicvoidsetColor(intc){
_color=c;
}
publicBoardView(Contextcontext){
super(context);
GameService.getInstance().startGame(0);
}
publicBoardView(Contextcontext,AttributeSetattrs){
super(context,attrs);
GameService.getInstance().startGame(0);
}
publicBoardView(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);
GameService.getInstance().startGame(0);
}
publicbooleanonTouchEvent(MotionEventevent){
if(event.getAction()!=MotionEvent.ACTION_UP)
returntrue;
intoffsetX=getOffsetX();
intoffsetY=getOffsetY();
intlineSize=getLineSize();
for(intx=0;x<3;x++){
for(inty=0;y<3;y++){
Rectr=newRect((offsetX+(x*lineSize)),
(offsetY+(y*lineSize)),
((offsetX+(x*lineSize))+lineSize),
((offsetY+(y*lineSize))+lineSize));
if(r.contains((int)event.getX(),(int)event.getY())){
GameService.getInstance().setPosition(0,x,y,_color);
invalidate();
returntrue;
}
}
}
returntrue;
}
privateintgetSize(){
return(int)((float)
((getWidth()<getHeight())?getWidth():getHeight())*0.8);
}
privateintgetOffsetX(){
return(getWidth()/2)-(getSize()/2);
}
privateintgetOffsetY(){
return(getHeight()/2)-(getSize()/2);
}
privateintgetLineSize(){
return(getSize()/3);
}
protectedvoidonDraw(Canvascanvas){
Paintpaint=newPaint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(),paint);
intsize=getSize();
intoffsetX=getOffsetX();
intoffsetY=getOffsetY();
intlineSize=getLineSize();
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth(5);
for(intcol=0;col<2;col++){
intcx=offsetX+((col+1)*lineSize);
canvas.drawLine(cx,offsetY,cx,offsetY+size,paint);
}
for(introw=0;row<2;row++){
intcy=offsetY+((row+1)*lineSize);
canvas.drawLine(offsetX,cy,offsetX+size,cy,paint);
}
intinset=(int)((float)lineSize*0.1);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
for(intx=0;x<3;x++){
for(inty=0;y<3;y++){
Rectr=newRect((offsetX+(x*lineSize))+inset,
(offsetY+(y*lineSize))+inset,
((offsetX+(x*lineSize))+lineSize)-inset,
((offsetY+(y*lineSize))+lineSize)-inset);
if(GameService.getInstance().positions[x][y]==1){
canvas.drawCircle((r.right+r.left)/2,
(r.bottom+r.top)/2,
(r.right-r.left)/2,paint);
}
if(GameService.getInstance().positions[x][y]==2){
canvas.drawLine(r.left,r.top,r.right,r.bottom,paint);
canvas.drawLine(r.left,r.bottom,r.right,r.top,paint);
}
}
}
}
}
这里的大部分工作都是在onTouch和onDraw方法中完成的,前者可以响应用户触摸游戏面板上的特定格子,而后者使用Android的绘制机制绘制游戏面板。
onTouch方法使用尺寸调整函数将每个格子的位置识别为一个矩形,然后使用矩形的contains方法查看用户的点击是否落在格子内部。如果落在格子内,它就会请求游戏服务走出步着。
onDraw函数使用尺寸调整函数来绘制面板的线条和所有已经选定的X和O。GameServer单例用于它的位置数组,其中保存了游戏面板上每个格子的当前状态。
您需要的最后一个类是UpdateTimer,它使用游戏服务将面板位置更新为最新值。清单12显示了定时器的代码。
清单12.UpdateTimer.java
com.jherrington.tictactoe;
java.util.TimerTask;
classUpdateTimerextendsTimerTask{
publicBoardViewboardView;
@Override
publicvoidrun(){
GameService.getInstance().startGame(0);
boardView.post(newRunnable(){publicvoidrun(){boardView.invalidate();}});
}
}
当应用程序启动时,TicTacToeActivity类对定时器进行初始化。这个定时器是一种轮询机制。这并非在客户端与服务器之间进行通信的最有效方式,但却最简单和最可靠。最有效的方式是使用HTTP协议的1.1版本保持连接打开,并在走出步着时让服务器把更新发送给客户端。这种方法要复杂得多。它需要客户端与服务器都支持1.1协议,而且连接数量也存在可伸缩性的问题。该方法已经超出了本文的范围。对于像这样的简单演示游戏,轮询机制已经足够满足要求。
代码完成之后,您可以对应用程序进行测试。这意味着要启动仿真器。启动后,您应该看到类似于图3的画面。
图3.启动Android仿真器
Android仿真器启动时的屏幕截图
这个仿真器正在加载迷人的“ANDROID”界面。加载完毕之后,您将看到如图4所示的启动画面。
图4.仿真器启动并准备运行
仿真器屏幕截图,显示时间、日期、功率电平指示器及音量和锁定图标
要进入手机,将锁定图标滑动到右侧。之后便可看到主屏,您正在调试的应用程序启动。在这个例子中,此操作会显示图5中的游戏画面。
图5.走出步着之前的游戏
TicTacToe游戏的屏幕截图,在开始新游戏时显示‘PlayX’与‘PlayO’按钮
根据服务器的状态,您可以看到或看不到任何步着。在这个例子中,游戏是空的。PlayX与PlayO按钮显示在显示屏中间tic-tac-toe游戏面板的顶部。接下来,点击PlayX按钮,再点击中心格子,就可以看到如图6所示的画面。
图6.X占据中心格子
X占据中心方格时的TicTacToe游戏屏幕截图
图6显示了中心方格填充X之后的游戏面板。要验证已连接上服务器,您可以在服务器上通过curl命令来执行moves.php脚本,从而获取游戏动作的最新列表。
为了测试O能够运作,点击PlayO按钮,然后选择一个角上的方格,如图7所示。
图7.O占据角上方格
X占据中心方格而O占据右上角方格时的TicTacToe游戏屏幕截图
玩游戏时既可以使用X,也可以使用O。应用程序连接到服务器,在一个共享位置中保存游戏状态。由于更新定时器的存在,每个用户都能看到对方的举动。
结束语
这个游戏完成了吗?并不尽然。目前还缺少胜利条件检查,玩家可以覆盖位置,而且没有轮流检查。但基本的技术组件都已经完成:一台在玩家之间共享存储状态的游戏服务器,连接到游戏服务器的移动设备上的一个本机图形化应用程序,用于为游戏提供界面。您可以使用这个游戏作为您自己游戏的一个起点,并按照自己的意愿来打造游戏。只要记住保持它的休闲性和娱乐性,您就可能打造出下一个WordsWithFriends或多玩家版本的AngryBirds。
参考资料
学习
Eclipse:了解关于本文中用于开发Android应用程序的IDE的更多信息。还可以找到Eclipse下载文件和插件。
PHPDevelopmentToolsforEclipse:需要开发PHP的IDE吗?Eclipse项目对它和其他Eclipse插件的所有内容都进行了扩展。
Android市场:编写自己的Android联网多玩家休闲游戏之后,可将它上传到Android市场中。请在本文的评论区域让我们知道您已经这么做了。
PHP网站:浏览最好的PHP参考资料。
W3C:访问有关标准的优秀网站,尤其是与本文相关的XML标准。
此作者的更多文章(JackHerrington,developerWorks,2005年3月到现在):阅读关于Ajax、JSON、PHP、XML和其他技术的文章。
XML新手入门获取学习XML所需的资源。
developerWorks中国网站XML技术专区:在XML专区获取提高您的专业技能所需的资源,包括DTD、模式和XSLT。参阅XML技术文档库,获得广泛的技术文章和技巧、教程、标准和IBM红皮书。
IBMXML认证:了解如何才能成为一名IBM认证的XML和相关技术的开发人员。
developerWorks技术活动与网络广播:随时关注这些活动中的技术。
developerWorks播客:收听面向软件开发人员的有趣访谈和讨论。
developerWorks演示中心:观看演示,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
AndroidDeveloper网站:下载SDK和Eclipse插件。
IBM产品评估试用版软件:下载或IBMSOA人员沙箱,并开始使用来自DB2®、Lotus®、Rational®、Tivoli®和WebSphere®的应用程序开发工具和中间件产品。
讨论
XML专区讨论论坛:参与任何一个XML相关讨论。
ThedeveloperWorks中文社区:查看开发人员推动的博客、论坛、组和wikis,并与其他developerWorks用户交流。
关于作者
JackHerrington的照片
JackHerrington是一位生活和工作在海湾地区的工程师、作家和主持人。您可以通过http://jackherrington.com来关注他的工作和作品。