Android井字游戏(二)游戏界面
图片与代码可见书配套官网下载
1 棋盘
1.1 先将游戏界面所需的图片放于“drawable-xxhdpi”文件夹中,后缀xxhdpi表示超高密度。
然后将图片封装到drawable中一个名为tile.xml的文件中
<?xml version="1.0" encoding="utf-8"?> <level-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/x_blue" android:maxLevel="0" /> <item android:drawable="@drawable/o_red" android:maxLevel="1" /> <item android:drawable="@drawable/tile_empty" android:maxLevel="2" /> <item android:drawable="@drawable/tile_available" android:maxLevel="3" /> </level-list>
每个格子都有4种可能的状态:X、O、空 和 可下。
下面是格子为空时的定义,在文件tile_empty.xml中
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
下面是格子可下时的定义,在文件tile_available.xml中
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <solid android:color="@color/available_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
1.2 小棋盘
由排列成3×3网格的9个格子组成,通过指定行号和列号确定每个格子的位置。
以下文件是位于layout的small_board.xml
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tile_background" android:elevation="@dimen/elevation_low" android:padding="@dimen/small_board_padding" tools:context=".GameActivity"> <ImageButton android:id="@+id/small1" style="@style/TileButton" android:layout_column="0" android:layout_row="0"/> <ImageButton android:id="@+id/small2" style="@style/TileButton" android:layout_column="1" android:layout_row="0"/> <ImageButton android:id="@+id/small3" style="@style/TileButton" android:layout_column="2" android:layout_row="0"/> <ImageButton android:id="@+id/small4" style="@style/TileButton" android:layout_column="0" android:layout_row="1"/> <ImageButton android:id="@+id/small5" style="@style/TileButton" android:layout_column="1" android:layout_row="1"/> <ImageButton android:id="@+id/small6" style="@style/TileButton" android:layout_column="2" android:layout_row="1"/> <ImageButton android:id="@+id/small7" style="@style/TileButton" android:layout_column="0" android:layout_row="2"/> <ImageButton android:id="@+id/small8" style="@style/TileButton" android:layout_column="1" android:layout_row="2"/> <ImageButton android:id="@+id/small9" style="@style/TileButton" android:layout_column="2" android:layout_row="2"/> </GridLayout>
修改styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Base application theme. --> <style name="AppTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen"> <!-- Customize your theme here. --> </style> <style name="TileButton"> <item name="android:layout_width">@dimen/tile_size</item> <item name="android:layout_height">@dimen/tile_size</item> <item name="android:layout_margin">@dimen/tile_margin</item> <item name="android:background">#00000000</item> <item name="android:padding">@dimen/tile_padding</item> <item name="android:scaleType">centerCrop</item> <item name="android:src">@drawable/tile</item> </style> </resources>
1.3 背景信息
drawable中tile_background.xml的定义如下
<?xml version="1.0" encoding="utf-8"?> <level-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/tile_blue" android:maxLevel="0"/> <item android:drawable="@drawable/tile_red" android:maxLevel="1"/> <item android:drawable="@drawable/tile_gray" android:maxLevel="2"/> <item android:drawable="@drawable/tile_purple" android:maxLevel="3"/> </level-list>
用蓝色格子表示格子被玩家X占用,红色表示被玩家O占用,灰色表示未被玩家占用,紫色表示被两个玩家占用(即表示两个玩家平手)。分别在tile_blue.xml, tile_red.xml, tile_gray.xml, tile_purple.xml 中定义,如下。
<!--blue--> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <solid android:color="@color/blue_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
<!--red--> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <solid android:color="@color/red_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
<!--gray--> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <solid android:color="@color/gray_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
<!--purple--> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <stroke android:width="@dimen/stroke_width" android:color="@color/dark_border_color"/> <solid android:color="@color/purple_color"/> <corners android:radius="@dimen/corner_radius"/> </shape>
1.4 大棋盘
large_board.xml
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:context=".GameActivity"> <include android:id="@+id/large1" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="0" android:layout_row="0"/> <include android:id="@+id/large2" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="1" android:layout_row="0"/> <include android:id="@+id/large3" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="2" android:layout_row="0"/> <include android:id="@+id/large4" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="0" android:layout_row="1"/> <include android:id="@+id/large5" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="1" android:layout_row="1"/> <include android:id="@+id/large6" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="2" android:layout_row="1"/> <include android:id="@+id/large7" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="0" android:layout_row="2"/> <include android:id="@+id/large8" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="1" android:layout_row="2"/> <include android:id="@+id/large9" layout="@layout/small_board" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/small_board_margin" android:layout_column="2" android:layout_row="2"/> </GridLayout>
其中<include>用于创建一个由属性layout指定的布局实例,并设置其他属性,再将视图放到父布局的指定位置。
1.5 组合在一起
将整个棋盘封装在一起
<!--fragment_game.xml--> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:context=".GameActivity"> <include layout="@layout/large_board" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>
<!--activity_game.xml--> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".TicTacToeActivity"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/sandy_beach"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <fragment android:id="@+id/fragment_game" class="org.example.tictactoe.GameFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:layout="@layout/fragment_game"/> <!-- Control fragment goes here... --> </LinearLayout> </FrameLayout>
2 开始游戏
2.1 将 “开始游戏” 和 “继续游戏” 按钮的功能加入,修改MainFragment.java
package org.example.tictactoe; import android.app.AlertDialog; import android.app.Fragment; import android.content.DialogInterface;import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class MainFragment extends Fragment { private AlertDialog mDialog; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); // Handle buttons here... View newButton = rootView.findViewById(R.id.new_button); View continueButton = rootView.findViewById(R.id.continue_button); View aboutButton = rootView.findViewById(R.id.about_button); newButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getActivity(), GameActivity.class); getActivity().startActivity(intent); } }); continueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(getActivity(), GameActivity.class); intent.putExtra(GameActivity.KEY_RESTORE, true); getActivity().startActivity(intent); } }); aboutButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.about_text); builder.setCancelable(false); builder.setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // nothing } }); mDialog = builder.show(); } }); return rootView; } @Override public void onPause() { super.onPause(); // Get rid of the about dialog if it's still up if (mDialog != null) mDialog.dismiss(); } }
其中 Intent 为红色, 将鼠标放置该单词上, 使用快捷键Alt + Enter 导入该类。
2.2 编写 GameActivity.java
2.2.1 方法onCreate()在活动启动时被调用。它根据前面创建的XML文件layout/activity_game.xml创建视图。
package org.example.tictactoe; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.util.Log; public class GameActivity extends Activity { public static final String KEY_RESTORE = "key_restore"; public static final String PREF_RESTORE = "pref_restore"; private GameFragment mGameFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_game); // 在此处恢复游戏 } }
每次在程序中添加新活动时,都必须修改AndroidManifest.xml以引用它。在application元素末尾添加以下代码。
<activity android:name="org.example.tictactoe.GameActivity"> </activity>
2.2.2 继续游戏
继续扩充GameActivity.java 代码
public class GameActivity extends Activity { //KEY_RESTORE是传递给新活动的标志的名称,用于将棋盘恢复到以前冻结的状态 public static final String KEY_RESTORE = "key_restore"; public static final String PREF_RESTORE = "pref_restore"; private GameFragment mGameFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_game); // 此处恢复游戏 //getFragmentManager获取一个指向跟踪所有片段的对象的句柄 //使用findFragmentById获取指向游戏片段的引用 mGameFragment = (GameFragment) getFragmentManager() .findFragmentById(R.id.fragment_game); //对Intent实例调用方法getBooleanExtra()以获得KEY_RESTORE的值 boolean restore = getIntent().getBooleanExtra(KEY_RESTORE, false); //若restore为true,就使用getPreferences()获取指向该活动的Android首项管理器的句柄 //再调用getString()获取PREF_RESTORE的值 if (restore) { String gameData = getPreferences(MODE_PRIVATE) .getString(PREF_RESTORE, null); if (gameData != null) { //使用putState来修改游戏的状态 mGameFragment.putState(gameData); } } Log.d("UT3", "restore = " + restore); } }
2.2.3 保存游戏
每当有活动不再处于运行状态时,就要保存游戏。在GameActivity.java添加以下方法
@Override protected void onPause() { super.onPause(); //getState获取游戏数据 String gameData = mGameFragment.getState(); //getPreferences获取指向首选项存储区的句柄,为首选项创建一个编辑器(edit) //使用键PREF_RESTORE保存游戏数据(putString),并将修改存储到首选项存储区(commit) getPreferences(MODE_PRIVATE).edit() .putString(PREF_RESTORE, gameData) .commit(); // Log.d负责将一条调试消息写入日志 Log.d("UT3", "state = " + gameData); }
2.2.4 重新开始游戏
public void restartGame() { mGameFragment.restartGame(); }
2.2.5 宣布获胜方
public void reportWinner(final Tile.Owner winner) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(getString(R.string.declare_winner, winner)); builder.setCancelable(false); builder.setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { finish(); } }); final Dialog dialog = builder.create(); dialog.show(); // Reset the board to the initial position mGameFragment.initGame(); }
上述代码使用AlterDialog.Builder类创建了一个消息框,其中包含一行文本和一个ok按键。用户点击ok活动将结束,即关闭游戏屏幕返回主菜单。
2.3 编写GameFragment.java
2.3.1 定义
方法onCreateView用于创建视图。
package org.example.tictactoe; import android.app.Fragment; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import java.util.HashSet; import java.util.Set; public class GameFragment extends Fragment { //此处定义数据结构 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); // 设备配置发生变化时保留该片段 initGame(); // 设置所有数据结构 } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.large_board, container, false); initViews(rootView); updateAllTiles(); return rootView; } }
2.3.2 数据结构
//此处定义数据结构 // mLargeIds和mSmallIds都是常量数组,分别用于将数字映射到小棋盘和格子资源id static private int mLargeIds[] = {R.id.large1, R.id.large2, R.id.large3, R.id.large4, R.id.large5, R.id.large6, R.id.large7, R.id.large8, R.id.large9,}; static private int mSmallIds[] = {R.id.small1, R.id.small2, R.id.small3, R.id.small4, R.id.small5, R.id.small6, R.id.small7, R.id.small8, R.id.small9,}; // 以下三个表示不同层级的格子。最顶层有1个,最底层有81个 private Tile mEntireBoard = new Tile(this); private Tile mLargeTiles[] = new Tile[9]; private Tile mSmallTiles[][] = new Tile[9][9]; // mPlayer 玩家id private Tile.Owner mPlayer = Tile.Owner.X; // mAvailable是列表,包含给定时点可下的所有格子 private Set<Tile> mAvailable = new HashSet<Tile>(); // mLastLarge和mLastSmall是最后一步棋的索引 private int mLastLarge; private int mLastSmall;
2.3.3 初始化游戏
创建片段时,在方法onCreate()中调用了方法initGame(),该方法将所有的数据结构初始化为起始状态。
public void initGame() { Log.d("UT3", "init game"); mEntireBoard = new Tile(this); // 创建所有格子 for (int large = 0; large < 9; large++) { mLargeTiles[large] = new Tile(this); for (int small = 0; small < 9; small++) { mSmallTiles[large][small] = new Tile(this); } mLargeTiles[large].setSubTiles(mSmallTiles[large]); } mEntireBoard.setSubTiles(mLargeTiles); // 设置先下棋子的玩家可下的格子 mLastSmall = -1; mLastLarge = -1; setAvailableFromLastMove(mLastSmall); }
2.3.4 初始化视图
private void initViews(View rootView) { mEntireBoard.setView(rootView); for (int large = 0; large < 9; large++) { View outer = rootView.findViewById(mLargeIds[large]); mLargeTiles[large].setView(outer); for (int small = 0; small < 9; small++) { ImageButton inner = (ImageButton) outer.findViewById (mSmallIds[small]); final int fLarge = large; final int fSmall = small; final Tile smallTile = mSmallTiles[large][small]; smallTile.setView(inner); inner.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (isAvailable(smallTile)) { makeMove(fLarge, fSmall); switchTurns(); } } }); } } }
2.3.5 将棋子下到格子中
每下一个棋子就要判断是否有玩家已取得胜利,若有,则游戏结束宣布winner;若没有,游戏继续。
private void makeMove(int large, int small) { mLastLarge = large; mLastSmall = small; Tile smallTile = mSmallTiles[large][small]; Tile largeTile = mLargeTiles[large]; smallTile.setOwner(mPlayer); setAvailableFromLastMove(small); Tile.Owner oldWinner = largeTile.getOwner(); Tile.Owner winner = largeTile.findWinner(); if (winner != oldWinner) { largeTile.setOwner(winner); } winner = mEntireBoard.findWinner(); mEntireBoard.setOwner(winner); updateAllTiles(); if (winner != Tile.Owner.NEITHER) { ((GameActivity)getActivity()).reportWinner(winner); } }
2.3.6 让另一个玩家接着下棋
切换mPlayer的值即可。
private void switchTurns() { mPlayer = mPlayer == Tile.Owner.X ? Tile.Owner.O : Tile .Owner.X; }
2.3.7 重新开始游戏
public void restartGame() { initGame(); initViews(getView()); updateAllTiles(); }
2.3.8 计算可下棋的格子
根据终极版井字游戏的规则,一个玩家下棋后,对手接下来只能在这步棋所处的格子对应的小棋盘中下棋。
// 用于清空可下棋格子列表,以便能够在其中添加格子 private void clearAvailable() { mAvailable.clear(); } // 用于将一个格子加入可下棋格子列表中 private void addAvailable(Tile tile) { mAvailable.add(tile); } // 用于判断指定的格子是否可下棋 public boolean isAvailable(Tile tile) { return mAvailable.contains(tile); } private void setAvailableFromLastMove(int small) { clearAvailable(); // 让目标小棋盘所有空格子都可下棋 if (small != -1) { for (int dest = 0; dest < 9; dest++) { Tile tile = mSmallTiles[small][dest]; if (tile.getOwner() == Tile.Owner.NEITHER) addAvailable(tile); } } // 若目标小棋盘没空格子,则令整个棋盘的空格子都可下棋 if (mAvailable.isEmpty()) { setAllAvailable(); } } private void setAllAvailable() { for (int large = 0; large < 9; large++) { for (int small = 0; small < 9; small++) { Tile tile = mSmallTiles[large][small]; if (tile.getOwner() == Tile.Owner.NEITHER) addAvailable(tile); } } }
2.3.9 处理状态
要将游戏当前状态转换为序列化形式存储下来。注意的是,明确每个格子由哪个玩家占领,还必须跟踪上一步棋子,因为接下来判断哪些地方可下棋取决于上一步棋子。
/** 创建包含游戏状态的字符串 */ public String getState() { StringBuilder builder = new StringBuilder(); builder.append(mLastLarge); builder.append(','); builder.append(mLastSmall); builder.append(','); for (int large = 0; large < 9; large++) { for (int small = 0; small < 9; small++) { builder.append(mSmallTiles[large][small].getOwner().name()); builder.append(','); } } return builder.toString(); } /** 根据给定的字符串恢复游戏状态 */ public void putState(String gameData) { String[] fields = gameData.split(","); int index = 0; mLastLarge = Integer.parseInt(fields[index++]); mLastSmall = Integer.parseInt(fields[index++]); for (int large = 0; large < 9; large++) { for (int small = 0; small < 9; small++) { Tile.Owner owner = Tile.Owner.valueOf(fields[index++]); mSmallTiles[large][small].setOwner(owner); } } setAvailableFromLastMove(mLastSmall); updateAllTiles(); }
接下来,重新计算可下棋格子列表,并更新所有格子的drawable状态。
private void updateAllTiles() { mEntireBoard.updateDrawableState(); for (int large = 0; large < 9; large++) { mLargeTiles[large].updateDrawableState(); for (int small = 0; small < 9; small++) { mSmallTiles[large][small].updateDrawableState(); } } }
2.4 定义Tile.java
Tile类 表示任何层次的棋盘格子。
2.4.1 Tile类的轮廓
Tile对象包含占据者和视图,还可能包含一系列格子。Tile类包含构造函数Tile,还包含占据者、视图和子格子的获取函数和设置函数。另外,它还包含管理drawale状态的代码以及判断谁是占据者的代码。
package org.example.tictactoe; import android.graphics.drawable.Drawable; import android.view.View; import android.widget.ImageButton; public class Tile { public enum Owner { X, O /* letter O */, NEITHER, BOTH } // 这些级别是在drawable中定义的 private static final int LEVEL_X = 0; private static final int LEVEL_O = 1; // letter O private static final int LEVEL_BLANK = 2; private static final int LEVEL_AVAILABLE = 3; private static final int LEVEL_TIE = 3; private final GameFragment mGame; private Owner mOwner = Owner.NEITHER; private View mView; private Tile mSubTiles[]; public Tile(GameFragment game) { this.mGame = game; } public View getView() { return mView; } public void setView(View view) { this.mView = view; } public Owner getOwner() { return mOwner; } public void setOwner(Owner owner) { this.mOwner = owner; } public Tile[] getSubTiles() { return mSubTiles; } public void setSubTiles(Tile[] subTiles) { this.mSubTiles = subTiles; } }
2.4.2 管理drawable状态的代码
// 通过调用getLevel来确定等级,然后根据视图泪习性,对视图背景或drawable调用setLevel public void updateDrawableState() { if (mView == null) return; int level = getLevel(); if (mView.getBackground() != null) { mView.getBackground().setLevel(level); } if (mView instanceof ImageButton) { Drawable drawable = ((ImageButton) mView).getDrawable(); drawable.setLevel(level); } } private int getLevel() { int level = LEVEL_BLANK; switch (mOwner) { case X: level = LEVEL_X; break; case O: // letter O level = LEVEL_O; break; case BOTH: level = LEVEL_TIE; break; case NEITHER: level = mGame.isAvailable(this) ? LEVEL_AVAILABLE : LEVEL_BLANK; break; } return level; }
2.4.3 编写确定占用者的代码
public Owner findWinner() { // 如果已确定占用者,就返回它 if (getOwner() != Owner.NEITHER) return getOwner(); int totalX[] = new int[4]; int totalO[] = new int[4]; countCaptures(totalX, totalO); if (totalX[3] > 0) return Owner.X; if (totalO[3] > 0) return Owner.O; // 检查是否平局 int total = 0; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { Owner owner = mSubTiles[3 * row + col].getOwner(); if (owner != Owner.NEITHER) total++; } if (total == 9) return Owner.BOTH; } // 未被玩家占用 return Owner.NEITHER; }
2.4.4 方法countCaptures()
如果有占用者,就返回他;否则,计算两玩家占据的格子数。若有一个玩家占据了3个连成一条线的格子那么该玩家为占据者;否则检查小棋盘的所有格子是否否不为空。若是,则为平局,返回BOTH;否则,说明未被任何玩家占据,因此返回NEITHER.
private void countCaptures(int totalX[], int totalO[]) { int capturedX, capturedO; // 检查是否有一个玩家的3个棋子成一行 for (int row = 0; row < 3; row++) { capturedX = capturedO = 0; for (int col = 0; col < 3; col++) { Owner owner = mSubTiles[3 * row + col].getOwner(); if (owner == Owner.X || owner == Owner.BOTH) capturedX++; if (owner == Owner.O || owner == Owner.BOTH) capturedO++; } totalX[capturedX]++; totalO[capturedO]++; } // 检查是否有一个玩家的3个棋子成一列 for (int col = 0; col < 3; col++) { capturedX = capturedO = 0; for (int row = 0; row < 3; row++) { Owner owner = mSubTiles[3 * row + col].getOwner(); if (owner == Owner.X || owner == Owner.BOTH) capturedX++; if (owner == Owner.O || owner == Owner.BOTH) capturedO++; } totalX[capturedX]++; totalO[capturedO]++; } // 检查是否有一个玩家的3个棋子成对角线 capturedX = capturedO = 0; for (int diag = 0; diag < 3; diag++) { Owner owner = mSubTiles[3 * diag + diag].getOwner(); if (owner == Owner.X || owner == Owner.BOTH) capturedX++; if (owner == Owner.O || owner == Owner.BOTH) capturedO++; } totalX[capturedX]++; totalO[capturedO]++; capturedX = capturedO = 0; for (int diag = 0; diag < 3; diag++) { Owner owner = mSubTiles[3 * diag + (2 - diag)].getOwner(); if (owner == Owner.X || owner == Owner.BOTH) capturedX++; if (owner == Owner.O || owner == Owner.BOTH) capturedO++; } totalX[capturedX]++; totalO[capturedO]++; }
首先,计算各行的X和O的数量,然后计算各列的X和O的数量,最后计算对角线的。结果是通过两个数组返回的:表示玩家X的数组totalX和表示玩家O的数组totalO。
2.5 控制游戏
玩游戏时,用户可能想重新开始或返回主菜单。以下提供一种返回主菜单的途径。定义在layout中fragment_control.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="@dimen/control_padding" tools:context=".GameActivity"> <Button android:id="@+id/button_restart" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="@dimen/elevation_low" android:drawableTop="@drawable/restart" android:text="@string/restart_label"/> <Button android:id="@+id/button_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="@dimen/elevation_low" android:drawableTop="@drawable/home" android:text="@string/main_menu_label"/> </LinearLayout>
其中所用到的图片位于drawable-xxhdpi中。接下来,要在activity_game.xml中包含一个新片段
<!-- --> <fragment android:id="@+id/fragment_game_controls" class="org.example.tictactoe.ControlFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:layout="@layout/fragment_control"/>
最后,要编写处理该布局的代码ControlFragment.java
package org.example.tictactoe; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class ControlFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_control, container, false); View main = rootView.findViewById(R.id.button_main); View restart = rootView.findViewById(R.id.button_restart); main.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getActivity().finish(); } }); restart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { ((GameActivity) getActivity()).restartGame(); } }); return rootView; } }
通过Main Menu按钮结束当前活动,与返回按钮效果相同。Restart按钮假定控制片段嵌入在GameActivity中,因此会将当前活动转换为GameActivity,并调用前面定义的重新开始游戏的方法。
2.6 参数定义
以下定义程序所使用的一些参数。
<!-- dimens.xml--> <dimen name="activity_horizontal_margin">8dp</dimen> <dimen name="activity_vertical_margin">8dp</dimen> <dimen name="tile_size">30dp</dimen> <dimen name="tile_margin">0dp</dimen> <dimen name="tile_padding">3dp</dimen> <dimen name="control_padding">20dp</dimen> <dimen name="small_board_padding">2dp</dimen> <dimen name="small_board_margin">2dp</dimen> <dimen name="elevation_low">4dp</dimen>
<!-- colors.xml --> <color name="dark_border_color">#4f4f4f</color> <color name="available_color">#7fbf7f</color> <color name="blue_color">#7f7fff</color> <color name="gray_color">#bfbfbf</color> <color name="purple_color">#7f007f</color> <color name="red_color">#ff7f7f</color>
<!-- strings.xml --> <string name="restart_label">Restart</string> <string name="main_menu_label">Main Menu</string> <string name="declare_winner">%1$s is the winner</string>
定义横向模式,即不同版本的activity_game.xml 和 fragment_control.xml , 存储在res/layout-land中,-land表示横向模式。
<!-- activity_game.xml --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".TicTacToeActivity"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/sandy_beach"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:baselineAligned="false" android:orientation="horizontal"> <fragment android:id="@+id/fragment_game" class="org.example.tictactoe.GameFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:layout="@layout/fragment_game"/> <fragment android:id="@+id/fragment_game_controls" class="org.example.tictactoe.ControlFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:layout="@layout/fragment_control"/> </LinearLayout> </FrameLayout>
<!-- fragment_control.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/control_padding" tools:context=".GameActivity"> <Button android:id="@+id/button_restart" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableTop="@drawable/restart" android:elevation="@dimen/elevation_low" android:text="@string/restart_label"/> <Button android:id="@+id/button_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableTop="@drawable/home" android:elevation="@dimen/elevation_low" android:text="@string/main_menu_label"/> </LinearLayout>
然后,就可以运行了。
摘自《Hello, Android》