zoukankan      html  css  js  c++  java
  • 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》

  • 相关阅读:
    js web简单的路由管理器
    Flutter Android Toast Message(flutter访问Android Toast Message)
    web 常用开发工具
    vim 常用指令
    Bootstrap5 多级dropdown
    nginx proxy
    asm align 对齐数据
    nodejs stream 创建读写流
    asm FPU 寄存器
    Nestjs 上传文件到七牛云
  • 原文地址:https://www.cnblogs.com/farewell-farewell/p/6489383.html
Copyright © 2011-2022 走看看