- 浏览: 90139 次
- 性别:
- 来自: 广州
-
文章分类
- 全部博客 (78)
- 生活 (3)
- 云计算与虚拟化 (26)
- IT技术 (13)
- VDI (7)
- WEB 2.0 (3)
- social network (1)
- API (1)
- java (1)
- tools (1)
- javascript (3)
- framework (1)
- web (1)
- virtualization (3)
- hypervisor (1)
- linux (6)
- kvm (1)
- VDI,vmware (2)
- wine (1)
- android (4)
- NoSQL (1)
- version control (1)
- (1)
- xendesktop (1)
- citrix (1)
- mobile (2)
- ebook (1)
- GUI (2)
- C# (1)
- google map (1)
- 围棋 (1)
- coding (1)
- programming (1)
最新评论
打造一款 Android 联网 tic-tac-toe 游戏
打造一款 Android 联网 tic-tac-toe 游戏
使用 PHP、XML 和 Android 开发包打造一款联网的多玩家 tic-tac-toe 游戏
Jack D Herrington, 高级软件工程师, Fortify Software, 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
TABLE IF EXISTS games;TABLE games(
id INT NOT NULL AUTO_INCREMENT,
primary key ( id ) );
TABLE IF EXISTS moves;TABLE moves(
id INT NOT NULL AUTO_INCREMENT,
game INT NOT NULL,
x INT NOT NULL,
y INT NOT NULL,
color INT NOT NULL,
primary key ( id ) );
第一个表是 games 表,用于保存游戏的惟一 ID。在生产应用程序中,您可能有一个用户表,而 games 表正是包含了两位玩家的用户 ID。但为了让事情变得简单,我放弃使用这种方法,而是集中讲述存储游戏数据、在客户端与服务器之间通信,以及打造前端的基础知识。
第二个表是 moves 表,它用于保存给定游戏的步着,因此共有五列。第一列是步着的惟一 ID。第二列是这一步着应用的游戏 ID。然后是动作所在的 x 和 y 坐标。因为格子是 3x3 的,这些值应该在 0 与 2 之间。最后一个字段是步着的 “颜色”,它是一个值为 X 或 O 的整数。
为了构造数据库,首先使用 mysqladmin 创建它,然后使用 mysql 命令运行 db.sql 脚本,如下所示:
% mysqladmin --user=root --password=foo create ttt
% mysql --user=root --password=foo ttt < db.sql
这个步骤创建了一个名为 “ttt” 的新数据库,用于保存 tic-tac-toe 模式。
现在有了模式,您需要创建启动游戏的途径。为此,您有一个 start.php 脚本,如 清单 2 中所示。
清单 2. start.php
<?php( 'Content-Type:text/xml' );
$dd = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'INSERT INTO games VALUES ( 0 )';
$sth = $dd->prepare($sql);
$sth->execute( array() );
$qid = $dd->lastInsertId();
$doc = new DOMDocument();
$r = $doc->createElement( "game" );
$r->setAttribute( 'id', $qid );
$doc->appendChild( $r );
$doc->saveXML();
?>
此脚本从连接数据库开始,然后对 games 表执行一条 INSERT 语句,并获取生成的 ID。接下来,它创建一个 XML 文档,将 ID 添加给一个游戏标签,并导出 XML。
您需要运行此脚本以在数据库中插入一个游戏,因为简单的 Android 应用程序没有用于创建游戏的界面。代码如下所示:
$ php start.php
<?xml version="1.0"?>
<game id="1"/>
$
现在您有了自己的第一个游戏。要查看游戏列表,使用 清单 3 中的 games.php 脚本。
清单 3. games.php
<?php( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'SELECT * FROM games';
$q = $dbh->prepare( $sql );
$q->execute( array() );
$doc = new DOMDocument();
$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 标签,然后为每个可用的游戏添加游戏标签。
当您从命令行运行这个脚本时,将看到如下内容:
$ php games.php
<?xml version="1.0"?>
<games><game id="1"/></games>
$
您还可以从 Web 浏览器运行这个脚本,得到的输出相同。
很好!由于游戏 API 已经就绪,是时候编写处理步着的服务器代码了。这段代码一开始构建一个名为 show_moves 的 helper 脚本,用于获得给定游戏的当前步着,并把它们导出为 XML。 清单 4 显示了这个 helper 函数的 PHP 代码。
清单 4. show_moves.php
<?phpshow_moves( $dbh, $game ) {
$sql = 'SELECT * FROM moves WHERE game=?';
$q = $dbh->prepare( $sql );
$q->execute( array( $game ) );
$doc = new DOMDocument();
$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 = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
_moves( $dbh, $_REQUEST['game'] );
?>
这个简单的脚本包含 helper 函数代码,连接到数据库,然后使用指定的游戏 ID 调用 show_moves 函数。要测试这段代码,使用 curl 命令从命令行调用服务器上的脚本:
$ curl "http://localhost/ttt/moves.php?game=1"
<?xml version="1.0"?>
<moves/>
$
遗憾的是,您尚未走出任何步着,因此输出没有什么特别意义。 为了弥补这一点,您需要给服务器 API 添加最后一个脚本。 清单 6 显示了 move.php 脚本。
清单 6. move.php
<?php_once( 'show_moves.php' );
( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'DELETE FROM moves WHERE game=? AND x=? AND y=?';
$sth = $dbh->prepare($sql);
$sth->execute( array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y']
) );
$sql = 'INSERT INTO moves VALUES ( 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"
<?xml version="1.0"?>
<moves><move x="1" y="2" color="1"/></moves>
完成游戏服务器代码之后,您可以构建这个多玩家联网游戏的 Android 前端。
回页首
构建 Android 前端
首先安装 Android SDK 和一些 Android 平台版本,最后是一些 Eclipse 和 Android Eclipse 插件。幸运的是,所有这些软件均在 Android 网站(参见 参考资料 中的链接)上可以找到。关于如何建立开发环境的深入描述已经超出了本文的范围。
建立起开发环境之后,启动 Eclipse 并创建一个新的 Android 项目。您看到的画面应该与 图 1 类似。
图 1. 在 Eclipse 中创建 Android 应用程序
New Android Project 向导屏幕截图,显示示例项目的详细信息
图 1 显示了 Android 应用程序的项目向导。输入一个项目名称,选择 Create new project in workspace 单选按钮,并指定 UI 元素的代码位置。在 Build Target 一览表中,选择一个 Android 平台。对于这段代码,我使用的是 Android 2.3.1。代码十分简单,您可以使用喜欢的任意版本。如果没有看到有任何平台列出,您需要下载并安装 Android SDK 安装说明中所提的平台。要注意,下载所有这些平台需要花费很长时间。
在 Properties 部分,填写应用程序名称和包名称。我使用的分别是 “Tic Tac Toe” 和 “com.jherrington.tictactoe”。接下来,选择 Create Activity 复选框,并输入活动名称。我使用 “TicTacToeActivity” 作为活动名称。
单击 Finish 可以看到类似于 图 2 的一个新项目。
图 2. TicTacToe 项目文件
新 TicTacToe 项目的文件和文件夹屏幕截图
图 2 显示了一个 Android 应用程序的顶级目录和文件(目录有 src、gen、Android 2.3.1 和 res,文件有 assets、.xml、default.properties 和 proguard.cfg)。其中重要的几项包括:
res 目录,包含资源
src 目录,包含 Java™ 源文件
清单文件,其中包含应用程序相关的传记信息
首先要编辑的是清单文件。大多数文件已经是正确的,但您需要添加 Internet 权限,以便让应用程序能够通过 Internet 发送请求。清单 7 显示了完成之后的清单文件。
清单 7. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns: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-sdk android:minSdkVersion="5" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="TicTacToeActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
惟一的变化是在文件顶部增加了 uses-permission 标签。
您的下一个任务是设计 UI。为此,调整位于 res/layout 目录中的 layout.xml 文件。清单 8 显示了这个文件的新内容。
清单 8. layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout android:layout_height="wrap_content"
android:layout_width="match_parent" android:id="@+id/linearLayout1">
<Button android:text="Play X" android:id="@+id/playx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
<Button android:text="Play O" android:id="@+id/playo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
</LinearLayout>
<com.jherrington.tictactoe.BoardView android:id="@+id/bview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></com.jherrington.tictactoe.BoardView>
</LinearLayout>
这是一种直观的布局。顶部是一组封装在水平方向的线形布局中的两个按钮。这两个按钮就是用户用于指定玩游戏时所用颜色的 X 和 O 按钮。
余下代码是 BoardView 类,用于显示当前游戏的 Tic Tac Toe 面板。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;
class TicTacToeActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button playx = (Button)this.findViewById(R.id.playx);
playx.setOnClickListener( this );
Button playo = (Button)this.findViewById(R.id.playo);
playo.setOnClickListener( this );
Timer timer = new Timer();
UpdateTimer ut = new UpdateTimer();
ut.boardView = (BoardView)this.findViewById(R.id.bview);
timer.schedule( ut, 200, 200 );
}
public void onClick(View v) {
BoardView board = (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;
class GameService {
private static GameService _instance = new GameService();
public int[][] positions = new int[][] {
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
public static GameService getInstance() {
return _instance;
}
private void updatePositions( Document doc ) {
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
positions[x][y] = 0;
}
}
doc.getDocumentElement().normalize();
NodeList items = doc.getElementsByTagName("move");
for (int i=0;i<items.getLength();i++){
Element me = (Element)items.item(i);
int x = Integer.parseInt( me.getAttribute("x") );
int y = Integer.parseInt( me.getAttribute("y") );
int color = Integer.parseInt( me.getAttribute("color") );
positions[x][y] = color;
}
}
public void startGame( int game ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/moves.php");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game", Integer.toString(game)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
public void setPosition( int game, int x, int y, int color ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/move.php");
positions[x][y] = color;
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game", Integer.toString(game)));
nameValuePairs.add(new BasicNameValuePair("x", Integer.toString(x)));
nameValuePairs.add(new BasicNameValuePair("y", Integer.toString(y)));
nameValuePairs.add(new BasicNameValuePair("color", Integer.toString(color)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
}
这段代码是应用程序中最有意思的一些代码。首先,使用 updatePositions 方法获取服务器返回的 XML并查找步着元素,然后使用当前的步着集合更新位置数组。该位置数组对面板上的每个位置都有一个相应的值,0 表示空白,1 表示 “O”,而 2 表示 “X”。
其他两个函数 startGame 与 setPosition 用于和服务器通信。startGame 方法从服务器请求当前的步着集合,并更新位置列表。setPosition 方法创建一个 HTTP post 请求,并使用一个名称/值对的数组(传输时将进行编码)为这个 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;
class BoardView extends View {
private int _color = 1;
public void setColor( int c ) {
_color = c;
}
public BoardView(Context context) {
super(context);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs) {
super(context,attrs);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context,attrs,defStyle);
GameService.getInstance().startGame(0);
}
public boolean onTouchEvent( MotionEvent event ) {
if ( event.getAction() != MotionEvent.ACTION_UP )
return true;
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
Rect r = new Rect( ( 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();
return true;
}
}
}
return true;
}
private int getSize() {
return (int) ( (float)
( ( getWidth() < getHeight() ) ? getWidth() : getHeight() ) * 0.8 );
}
private int getOffsetX() {
return ( getWidth() / 2 ) - ( getSize( ) / 2 );
}
private int getOffsetY() {
return ( getHeight() / 2 ) - ( getSize() / 2 );
}
private int getLineSize() {
return ( getSize() / 3 );
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(), paint);
int size = getSize();
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth( 5 );
for( int col = 0; col < 2; col++ ) {
int cx = offsetX + ( ( col + 1 ) * lineSize );
canvas.drawLine(cx, offsetY, cx, offsetY + size, paint);
}
for( int row = 0; row < 2; row++ ) {
int cy = offsetY + ( ( row + 1 ) * lineSize );
canvas.drawLine(offsetX, cy, offsetX + size, cy, paint);
}
int inset = (int) ( (float)lineSize * 0.1 );
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth( 10 );
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
Rect r = new Rect( ( 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;
class UpdateTimer extends TimerTask {
public BoardView boardView;
@Override
public void run() {
GameService.getInstance().startGame( 0 );
boardView.post(new Runnable(){ public void run(){ boardView.invalidate(); } });
}
}
当应用程序启动时,TicTacToeActivity 类对定时器进行初始化。这个定时器是一种轮询机制。这并非在客户端与服务器之间进行通信的最有效方式,但却最简单和最可靠。最有效的方式是使用 HTTP 协议的 1.1 版本保持连接打开,并在走出步着时让服务器把更新发送给客户端。这种方法要复杂得多。它需要客户端与服务器都支持 1.1 协议,而且连接数量也存在可伸缩性的问题。该方法已经超出了本文的范围。对于像这样的简单演示游戏,轮询机制已经足够满足要求。
代码完成之后,您可以对应用程序进行测试。这意味着要启动仿真器。启动后,您应该看到类似于 图 3 的画面。
图 3. 启动 Android 仿真器
Android 仿真器启动时的屏幕截图
这个仿真器正在加载迷人的 “A N D R O I D” 界面。加载完毕之后,您将看到如 图 4 所示的启动画面。
图 4. 仿真器启动并准备运行
仿真器屏幕截图,显示时间、日期、功率电平指示器及音量和锁定图标
要进入手机,将锁定图标滑动到右侧。之后便可看到主屏,您正在调试的应用程序启动。在这个例子中,此操作会显示 图 5 中的游戏画面。
图 5. 走出步着之前的游戏
Tic Tac Toe 游戏的屏幕截图,在开始新游戏时显示 ‘Play X’ 与 ‘Play O’ 按钮
根据服务器的状态,您可以看到或看不到任何步着。在这个例子中,游戏是空的。Play X 与 Play O 按钮显示在显示屏中间 tic-tac-toe 游戏面板的顶部。接下来,点击 Play X 按钮,再点击中心格子,就可以看到如 图 6 所示的画面。
图 6. X 占据中心格子
X 占据中心方格时的 Tic Tac Toe 游戏屏幕截图
图 6 显示了中心方格填充 X 之后的游戏面板。要验证已连接上服务器,您可以在服务器上通过 curl 命令来执行 moves.php 脚本,从而获取游戏动作的最新列表。
为了测试 O 能够运作,点击 Play O 按钮,然后选择一个角上的方格,如 图 7 所示。
图 7. O 占据角上方格
X 占据中心方格而 O 占据右上角方格时的 Tic Tac Toe 游戏屏幕截图
玩游戏时既可以使用 X,也可以使用 O。应用程序连接到服务器,在一个共享位置中保存游戏状态。由于更新定时器的存在,每个用户都能看到对方的举动。
结束语
这个游戏完成了吗?并不尽然。目前还缺少胜利条件检查,玩家可以覆盖位置,而且没有轮流检查。但基本的技术组件都已经完成:一台在玩家之间共享存储状态的游戏服务器,连接到游戏服务器的移动设备上的一个本机图形化应用程序,用于为游戏提供界面。您可以使用这个游戏作为您自己游戏的一个起点,并按照自己的意愿来打造游戏。只要记住保持它的休闲性和娱乐性,您就可能打造出下一个 Words With Friends 或多玩家版本的 Angry Birds。
参考资料
学习
Eclipse:了解关于本文中用于开发 Android 应用程序的 IDE 的更多信息。还可以找到 Eclipse 下载文件和插件。
PHP Development Tools for Eclipse:需要开发 PHP 的 IDE 吗?Eclipse 项目对它和其他 Eclipse 插件的所有内容都进行了扩展。
Android 市场:编写自己的 Android 联网多玩家休闲游戏之后,可将它上传到 Android 市场中。请在本文的评论区域让我们知道您已经这么做了。
PHP 网站:浏览最好的 PHP 参考资料。
W3C:访问有关标准的优秀网站,尤其是与本文相关的 XML 标准。
此作者的更多文章 (Jack Herrington, developerWorks, 2005 年 3 月到现在):阅读关于 Ajax、JSON、PHP、XML 和其他技术的文章。
XML 新手入门 获取学习 XML 所需的资源。
developerWorks 中国网站 XML 技术专区:在 XML 专区获取提高您的专业技能所需的资源,包括 DTD、模式和 XSLT。参阅 XML 技术文档库,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
developerWorks 技术活动 与 网络广播:随时关注这些活动中的技术。
developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
developerWorks 演示中心:观看演示,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
Android Developer 网站:下载 SDK 和 Eclipse 插件。
IBM 产品评估试用版软件:下载或 IBM SOA 人员沙箱 ,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
XML 专区讨论论坛:参与任何一个 XML 相关讨论。
The developerWorks 中文社区:查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。
关于作者
Jack Herrington 的照片
Jack Herrington 是一位生活和工作在海湾地区的工程师、作家和主持人。您可以通过 http://jackherrington.com 来关注他的工作和作品。
打造一款 Android 联网 tic-tac-toe 游戏
使用 PHP、XML 和 Android 开发包打造一款联网的多玩家 tic-tac-toe 游戏
Jack D Herrington, 高级软件工程师, Fortify Software, 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
TABLE IF EXISTS games;TABLE games(
id INT NOT NULL AUTO_INCREMENT,
primary key ( id ) );
TABLE IF EXISTS moves;TABLE moves(
id INT NOT NULL AUTO_INCREMENT,
game INT NOT NULL,
x INT NOT NULL,
y INT NOT NULL,
color INT NOT NULL,
primary key ( id ) );
第一个表是 games 表,用于保存游戏的惟一 ID。在生产应用程序中,您可能有一个用户表,而 games 表正是包含了两位玩家的用户 ID。但为了让事情变得简单,我放弃使用这种方法,而是集中讲述存储游戏数据、在客户端与服务器之间通信,以及打造前端的基础知识。
第二个表是 moves 表,它用于保存给定游戏的步着,因此共有五列。第一列是步着的惟一 ID。第二列是这一步着应用的游戏 ID。然后是动作所在的 x 和 y 坐标。因为格子是 3x3 的,这些值应该在 0 与 2 之间。最后一个字段是步着的 “颜色”,它是一个值为 X 或 O 的整数。
为了构造数据库,首先使用 mysqladmin 创建它,然后使用 mysql 命令运行 db.sql 脚本,如下所示:
% mysqladmin --user=root --password=foo create ttt
% mysql --user=root --password=foo ttt < db.sql
这个步骤创建了一个名为 “ttt” 的新数据库,用于保存 tic-tac-toe 模式。
现在有了模式,您需要创建启动游戏的途径。为此,您有一个 start.php 脚本,如 清单 2 中所示。
清单 2. start.php
<?php( 'Content-Type:text/xml' );
$dd = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'INSERT INTO games VALUES ( 0 )';
$sth = $dd->prepare($sql);
$sth->execute( array() );
$qid = $dd->lastInsertId();
$doc = new DOMDocument();
$r = $doc->createElement( "game" );
$r->setAttribute( 'id', $qid );
$doc->appendChild( $r );
$doc->saveXML();
?>
此脚本从连接数据库开始,然后对 games 表执行一条 INSERT 语句,并获取生成的 ID。接下来,它创建一个 XML 文档,将 ID 添加给一个游戏标签,并导出 XML。
您需要运行此脚本以在数据库中插入一个游戏,因为简单的 Android 应用程序没有用于创建游戏的界面。代码如下所示:
$ php start.php
<?xml version="1.0"?>
<game id="1"/>
$
现在您有了自己的第一个游戏。要查看游戏列表,使用 清单 3 中的 games.php 脚本。
清单 3. games.php
<?php( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'SELECT * FROM games';
$q = $dbh->prepare( $sql );
$q->execute( array() );
$doc = new DOMDocument();
$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 标签,然后为每个可用的游戏添加游戏标签。
当您从命令行运行这个脚本时,将看到如下内容:
$ php games.php
<?xml version="1.0"?>
<games><game id="1"/></games>
$
您还可以从 Web 浏览器运行这个脚本,得到的输出相同。
很好!由于游戏 API 已经就绪,是时候编写处理步着的服务器代码了。这段代码一开始构建一个名为 show_moves 的 helper 脚本,用于获得给定游戏的当前步着,并把它们导出为 XML。 清单 4 显示了这个 helper 函数的 PHP 代码。
清单 4. show_moves.php
<?phpshow_moves( $dbh, $game ) {
$sql = 'SELECT * FROM moves WHERE game=?';
$q = $dbh->prepare( $sql );
$q->execute( array( $game ) );
$doc = new DOMDocument();
$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 = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
_moves( $dbh, $_REQUEST['game'] );
?>
这个简单的脚本包含 helper 函数代码,连接到数据库,然后使用指定的游戏 ID 调用 show_moves 函数。要测试这段代码,使用 curl 命令从命令行调用服务器上的脚本:
$ curl "http://localhost/ttt/moves.php?game=1"
<?xml version="1.0"?>
<moves/>
$
遗憾的是,您尚未走出任何步着,因此输出没有什么特别意义。 为了弥补这一点,您需要给服务器 API 添加最后一个脚本。 清单 6 显示了 move.php 脚本。
清单 6. move.php
<?php_once( 'show_moves.php' );
( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'DELETE FROM moves WHERE game=? AND x=? AND y=?';
$sth = $dbh->prepare($sql);
$sth->execute( array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y']
) );
$sql = 'INSERT INTO moves VALUES ( 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"
<?xml version="1.0"?>
<moves><move x="1" y="2" color="1"/></moves>
完成游戏服务器代码之后,您可以构建这个多玩家联网游戏的 Android 前端。
回页首
构建 Android 前端
首先安装 Android SDK 和一些 Android 平台版本,最后是一些 Eclipse 和 Android Eclipse 插件。幸运的是,所有这些软件均在 Android 网站(参见 参考资料 中的链接)上可以找到。关于如何建立开发环境的深入描述已经超出了本文的范围。
建立起开发环境之后,启动 Eclipse 并创建一个新的 Android 项目。您看到的画面应该与 图 1 类似。
图 1. 在 Eclipse 中创建 Android 应用程序
New Android Project 向导屏幕截图,显示示例项目的详细信息
图 1 显示了 Android 应用程序的项目向导。输入一个项目名称,选择 Create new project in workspace 单选按钮,并指定 UI 元素的代码位置。在 Build Target 一览表中,选择一个 Android 平台。对于这段代码,我使用的是 Android 2.3.1。代码十分简单,您可以使用喜欢的任意版本。如果没有看到有任何平台列出,您需要下载并安装 Android SDK 安装说明中所提的平台。要注意,下载所有这些平台需要花费很长时间。
在 Properties 部分,填写应用程序名称和包名称。我使用的分别是 “Tic Tac Toe” 和 “com.jherrington.tictactoe”。接下来,选择 Create Activity 复选框,并输入活动名称。我使用 “TicTacToeActivity” 作为活动名称。
单击 Finish 可以看到类似于 图 2 的一个新项目。
图 2. TicTacToe 项目文件
新 TicTacToe 项目的文件和文件夹屏幕截图
图 2 显示了一个 Android 应用程序的顶级目录和文件(目录有 src、gen、Android 2.3.1 和 res,文件有 assets、.xml、default.properties 和 proguard.cfg)。其中重要的几项包括:
res 目录,包含资源
src 目录,包含 Java™ 源文件
清单文件,其中包含应用程序相关的传记信息
首先要编辑的是清单文件。大多数文件已经是正确的,但您需要添加 Internet 权限,以便让应用程序能够通过 Internet 发送请求。清单 7 显示了完成之后的清单文件。
清单 7. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns: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-sdk android:minSdkVersion="5" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="TicTacToeActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
惟一的变化是在文件顶部增加了 uses-permission 标签。
您的下一个任务是设计 UI。为此,调整位于 res/layout 目录中的 layout.xml 文件。清单 8 显示了这个文件的新内容。
清单 8. layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout android:layout_height="wrap_content"
android:layout_width="match_parent" android:id="@+id/linearLayout1">
<Button android:text="Play X" android:id="@+id/playx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
<Button android:text="Play O" android:id="@+id/playo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Button>
</LinearLayout>
<com.jherrington.tictactoe.BoardView android:id="@+id/bview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
></com.jherrington.tictactoe.BoardView>
</LinearLayout>
这是一种直观的布局。顶部是一组封装在水平方向的线形布局中的两个按钮。这两个按钮就是用户用于指定玩游戏时所用颜色的 X 和 O 按钮。
余下代码是 BoardView 类,用于显示当前游戏的 Tic Tac Toe 面板。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;
class TicTacToeActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button playx = (Button)this.findViewById(R.id.playx);
playx.setOnClickListener( this );
Button playo = (Button)this.findViewById(R.id.playo);
playo.setOnClickListener( this );
Timer timer = new Timer();
UpdateTimer ut = new UpdateTimer();
ut.boardView = (BoardView)this.findViewById(R.id.bview);
timer.schedule( ut, 200, 200 );
}
public void onClick(View v) {
BoardView board = (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;
class GameService {
private static GameService _instance = new GameService();
public int[][] positions = new int[][] {
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
public static GameService getInstance() {
return _instance;
}
private void updatePositions( Document doc ) {
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
positions[x][y] = 0;
}
}
doc.getDocumentElement().normalize();
NodeList items = doc.getElementsByTagName("move");
for (int i=0;i<items.getLength();i++){
Element me = (Element)items.item(i);
int x = Integer.parseInt( me.getAttribute("x") );
int y = Integer.parseInt( me.getAttribute("y") );
int color = Integer.parseInt( me.getAttribute("color") );
positions[x][y] = color;
}
}
public void startGame( int game ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/moves.php");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game", Integer.toString(game)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
public void setPosition( int game, int x, int y, int color ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/move.php");
positions[x][y] = color;
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game", Integer.toString(game)));
nameValuePairs.add(new BasicNameValuePair("x", Integer.toString(x)));
nameValuePairs.add(new BasicNameValuePair("y", Integer.toString(y)));
nameValuePairs.add(new BasicNameValuePair("color", Integer.toString(color)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
}
这段代码是应用程序中最有意思的一些代码。首先,使用 updatePositions 方法获取服务器返回的 XML并查找步着元素,然后使用当前的步着集合更新位置数组。该位置数组对面板上的每个位置都有一个相应的值,0 表示空白,1 表示 “O”,而 2 表示 “X”。
其他两个函数 startGame 与 setPosition 用于和服务器通信。startGame 方法从服务器请求当前的步着集合,并更新位置列表。setPosition 方法创建一个 HTTP post 请求,并使用一个名称/值对的数组(传输时将进行编码)为这个 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;
class BoardView extends View {
private int _color = 1;
public void setColor( int c ) {
_color = c;
}
public BoardView(Context context) {
super(context);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs) {
super(context,attrs);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context,attrs,defStyle);
GameService.getInstance().startGame(0);
}
public boolean onTouchEvent( MotionEvent event ) {
if ( event.getAction() != MotionEvent.ACTION_UP )
return true;
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
Rect r = new Rect( ( 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();
return true;
}
}
}
return true;
}
private int getSize() {
return (int) ( (float)
( ( getWidth() < getHeight() ) ? getWidth() : getHeight() ) * 0.8 );
}
private int getOffsetX() {
return ( getWidth() / 2 ) - ( getSize( ) / 2 );
}
private int getOffsetY() {
return ( getHeight() / 2 ) - ( getSize() / 2 );
}
private int getLineSize() {
return ( getSize() / 3 );
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(), paint);
int size = getSize();
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth( 5 );
for( int col = 0; col < 2; col++ ) {
int cx = offsetX + ( ( col + 1 ) * lineSize );
canvas.drawLine(cx, offsetY, cx, offsetY + size, paint);
}
for( int row = 0; row < 2; row++ ) {
int cy = offsetY + ( ( row + 1 ) * lineSize );
canvas.drawLine(offsetX, cy, offsetX + size, cy, paint);
}
int inset = (int) ( (float)lineSize * 0.1 );
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth( 10 );
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
Rect r = new Rect( ( 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;
class UpdateTimer extends TimerTask {
public BoardView boardView;
@Override
public void run() {
GameService.getInstance().startGame( 0 );
boardView.post(new Runnable(){ public void run(){ boardView.invalidate(); } });
}
}
当应用程序启动时,TicTacToeActivity 类对定时器进行初始化。这个定时器是一种轮询机制。这并非在客户端与服务器之间进行通信的最有效方式,但却最简单和最可靠。最有效的方式是使用 HTTP 协议的 1.1 版本保持连接打开,并在走出步着时让服务器把更新发送给客户端。这种方法要复杂得多。它需要客户端与服务器都支持 1.1 协议,而且连接数量也存在可伸缩性的问题。该方法已经超出了本文的范围。对于像这样的简单演示游戏,轮询机制已经足够满足要求。
代码完成之后,您可以对应用程序进行测试。这意味着要启动仿真器。启动后,您应该看到类似于 图 3 的画面。
图 3. 启动 Android 仿真器
Android 仿真器启动时的屏幕截图
这个仿真器正在加载迷人的 “A N D R O I D” 界面。加载完毕之后,您将看到如 图 4 所示的启动画面。
图 4. 仿真器启动并准备运行
仿真器屏幕截图,显示时间、日期、功率电平指示器及音量和锁定图标
要进入手机,将锁定图标滑动到右侧。之后便可看到主屏,您正在调试的应用程序启动。在这个例子中,此操作会显示 图 5 中的游戏画面。
图 5. 走出步着之前的游戏
Tic Tac Toe 游戏的屏幕截图,在开始新游戏时显示 ‘Play X’ 与 ‘Play O’ 按钮
根据服务器的状态,您可以看到或看不到任何步着。在这个例子中,游戏是空的。Play X 与 Play O 按钮显示在显示屏中间 tic-tac-toe 游戏面板的顶部。接下来,点击 Play X 按钮,再点击中心格子,就可以看到如 图 6 所示的画面。
图 6. X 占据中心格子
X 占据中心方格时的 Tic Tac Toe 游戏屏幕截图
图 6 显示了中心方格填充 X 之后的游戏面板。要验证已连接上服务器,您可以在服务器上通过 curl 命令来执行 moves.php 脚本,从而获取游戏动作的最新列表。
为了测试 O 能够运作,点击 Play O 按钮,然后选择一个角上的方格,如 图 7 所示。
图 7. O 占据角上方格
X 占据中心方格而 O 占据右上角方格时的 Tic Tac Toe 游戏屏幕截图
玩游戏时既可以使用 X,也可以使用 O。应用程序连接到服务器,在一个共享位置中保存游戏状态。由于更新定时器的存在,每个用户都能看到对方的举动。
结束语
这个游戏完成了吗?并不尽然。目前还缺少胜利条件检查,玩家可以覆盖位置,而且没有轮流检查。但基本的技术组件都已经完成:一台在玩家之间共享存储状态的游戏服务器,连接到游戏服务器的移动设备上的一个本机图形化应用程序,用于为游戏提供界面。您可以使用这个游戏作为您自己游戏的一个起点,并按照自己的意愿来打造游戏。只要记住保持它的休闲性和娱乐性,您就可能打造出下一个 Words With Friends 或多玩家版本的 Angry Birds。
参考资料
学习
Eclipse:了解关于本文中用于开发 Android 应用程序的 IDE 的更多信息。还可以找到 Eclipse 下载文件和插件。
PHP Development Tools for Eclipse:需要开发 PHP 的 IDE 吗?Eclipse 项目对它和其他 Eclipse 插件的所有内容都进行了扩展。
Android 市场:编写自己的 Android 联网多玩家休闲游戏之后,可将它上传到 Android 市场中。请在本文的评论区域让我们知道您已经这么做了。
PHP 网站:浏览最好的 PHP 参考资料。
W3C:访问有关标准的优秀网站,尤其是与本文相关的 XML 标准。
此作者的更多文章 (Jack Herrington, developerWorks, 2005 年 3 月到现在):阅读关于 Ajax、JSON、PHP、XML 和其他技术的文章。
XML 新手入门 获取学习 XML 所需的资源。
developerWorks 中国网站 XML 技术专区:在 XML 专区获取提高您的专业技能所需的资源,包括 DTD、模式和 XSLT。参阅 XML 技术文档库,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
developerWorks 技术活动 与 网络广播:随时关注这些活动中的技术。
developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
developerWorks 演示中心:观看演示,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
Android Developer 网站:下载 SDK 和 Eclipse 插件。
IBM 产品评估试用版软件:下载或 IBM SOA 人员沙箱 ,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
XML 专区讨论论坛:参与任何一个 XML 相关讨论。
The developerWorks 中文社区:查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。
关于作者
Jack Herrington 的照片
Jack Herrington 是一位生活和工作在海湾地区的工程师、作家和主持人。您可以通过 http://jackherrington.com 来关注他的工作和作品。
相关推荐
构建依赖关系(Xcode和Android Studio) 我应该使用ExpoKit吗? 故障排除 联网 iOS模拟器无法打开 QR码不扫描 更新到新版本 您只需要很少(理想情况下永远不需要)更新全局安装的create-react-native-app 。 ...
漫画作品与时间旅行题材
Spring Boot特点: 1、创建一个单独的Spring应用程序; 2、嵌入式Tomcat,无需部署WAR文件; 3、简化Maven配置; 4、自动配置Spring; 5、提供生产就绪功能,如指标,健康检查和外部配置; 6、绝对没有代码生成和XML的配置要求;第一章 绪 论 1 1.1背景及意义 1 1.2国内外研究概况 2 1.3 研究的内容 2 第二章 关键技术的研究 3 2.1 相关技术 3 2.2 Java技术 3 2.3 ECLIPSE 开发环境 4 2.4 Tomcat介绍 4 2.5 Spring Boot框架 5 第三章 系统分析 5 3.1 系统设计目标 6 3.2 系统可行性分析 6 3.3 系统功能分析和描述 7 3.4系统UML用例分析 8 3.4.1管理员用例 9 3.4.2用户用例 9 3.5系统流程分析 10 3.5.1添加信息流程 11 3.5.2操作流程 12 3.5.3删除信息流程 13 第四章 系统设计 14 4.1 系统体系结构 15 4.2 数据库设计原则 16 4.3 数据表 17 第五章 系统实现 18 5.1用户功能模块 18 5.2
内容概要:本文作为PyTorch的入门指南,首先介绍了PyTorch相较于TensorFlow的优势——动态计算图、自动微分和丰富API。接着讲解了环境搭建、PyTorch核心组件如张量(Tensor)、autograd模块以及神经网络的定义方式(如nn.Module),并且给出了详细的神经网络训练流程,包括前向传播、计算损失值、进行反向传播以计算梯度,最终调整权重参数。此外还简要提及了一些拓展资源以便进一步探索这个深度学习工具。 适用人群:初次接触深度学习技术的新学者和技术爱好者,有一定程序基础并希望通过PyTorch深入理解机器学习算法实现的人。 使用场景及目标:该文档有助于建立使用者对于深度学习及其具体实践有更加直观的理解,在完成本教程之后,读者应当能够在个人设备上正确部署Python环境,并依据指示独立创建自己的简易深度学习项目。 其他说明:文中所提及的所有示例均可被完整重现,同时官方提供的资料链接也可以方便有兴趣的人士对感兴趣之处继续挖掘,这不仅加深了对PyTorch本身的熟悉程度,也为未来的研究或者工程项目打下了良好的理论基础和实践经验。
古镇美食自驾游:舌尖上的历史韵味
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
漫画作品与神话传说融合
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
ADC推理软件AI程序
漫画作品与科幻元素融合
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
java-springboot+vue景区民宿预约系统实现源码(完整前后端+mysql+说明文档+LunW+PPT).zip
在智慧城市建设的大潮中,智慧园区作为其中的璀璨明珠,正以其独特的魅力引领着产业园区的新一轮变革。想象一下,一个集绿色、高端、智能、创新于一体的未来园区,它不仅融合了科技研发、商业居住、办公文创等多种功能,更通过深度应用信息技术,实现了从传统到智慧的华丽转身。 智慧园区通过“四化”建设——即园区运营精细化、园区体验智能化、园区服务专业化和园区设施信息化,彻底颠覆了传统园区的管理模式。在这里,基础设施的数据收集与分析让管理变得更加主动和高效,从温湿度监控到烟雾报警,从消防水箱液位监测到消防栓防盗水装置,每一处细节都彰显着智能的力量。而远程抄表、空调和变配电的智能化管控,更是在节能降耗的同时,极大地提升了园区的运维效率。更令人兴奋的是,通过智慧监控、人流统计和自动访客系统等高科技手段,园区的安全防范能力得到了质的飞跃,让每一位入驻企业和个人都能享受到“拎包入住”般的便捷与安心。 更令人瞩目的是,智慧园区还构建了集信息服务、企业服务、物业服务于一体的综合服务体系。无论是通过园区门户进行信息查询、投诉反馈,还是享受便捷的电商服务、法律咨询和融资支持,亦或是利用云ERP和云OA系统提升企业的管理水平和运营效率,智慧园区都以其全面、专业、高效的服务,为企业的发展插上了腾飞的翅膀。而这一切的背后,是大数据、云计算、人工智能等前沿技术的深度融合与应用,它们如同智慧的大脑,让园区的管理和服务变得更加聪明、更加贴心。走进智慧园区,就像踏入了一个充满无限可能的未来世界,这里不仅有科技的魅力,更有生活的温度,让人不禁对未来充满了无限的憧憬与期待。
边境自驾游异国风情深度体验
在智慧城市建设的大潮中,智慧园区作为其中的璀璨明珠,正以其独特的魅力引领着产业园区的新一轮变革。想象一下,一个集绿色、高端、智能、创新于一体的未来园区,它不仅融合了科技研发、商业居住、办公文创等多种功能,更通过深度应用信息技术,实现了从传统到智慧的华丽转身。 智慧园区通过“四化”建设——即园区运营精细化、园区体验智能化、园区服务专业化和园区设施信息化,彻底颠覆了传统园区的管理模式。在这里,基础设施的数据收集与分析让管理变得更加主动和高效,从温湿度监控到烟雾报警,从消防水箱液位监测到消防栓防盗水装置,每一处细节都彰显着智能的力量。而远程抄表、空调和变配电的智能化管控,更是在节能降耗的同时,极大地提升了园区的运维效率。更令人兴奋的是,通过智慧监控、人流统计和自动访客系统等高科技手段,园区的安全防范能力得到了质的飞跃,让每一位入驻企业和个人都能享受到“拎包入住”般的便捷与安心。 更令人瞩目的是,智慧园区还构建了集信息服务、企业服务、物业服务于一体的综合服务体系。无论是通过园区门户进行信息查询、投诉反馈,还是享受便捷的电商服务、法律咨询和融资支持,亦或是利用云ERP和云OA系统提升企业的管理水平和运营效率,智慧园区都以其全面、专业、高效的服务,为企业的发展插上了腾飞的翅膀。而这一切的背后,是大数据、云计算、人工智能等前沿技术的深度融合与应用,它们如同智慧的大脑,让园区的管理和服务变得更加聪明、更加贴心。走进智慧园区,就像踏入了一个充满无限可能的未来世界,这里不仅有科技的魅力,更有生活的温度,让人不禁对未来充满了无限的憧憬与期待。
,,CAD、DXF导图,自动进行位置路径规划,源码可进行简单功能添加实现设备所需功能,已经在冲孔机,点胶机上应用,性价比超高。 打孔机实测一分钟1400个孔 ,CAD、DXF导图;自动位置路径规划;源码功能添加;设备功能实现;冲孔机点胶机应用;高性价比。,CAD导图DXF,自动规划位置路径,实测打孔速度惊人!性价比超高冲孔机实现多功能定制
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。