开发一个 Android 的井字游戏 已翻译 100%

oschina 投递于 2013/01/14 22:31 (共 9 段, 翻译完成于 01-15)
阅读 6755
收藏 51
7
加载中

在我的上一篇博客里我展示了一个小小的安卓作弊程序,用来辅助完成填字游戏。今天我想讲讲怎么编写一个简单的安卓井字游戏。

搭建项目

首先,在你的任意IDE里创建一个新的Android项目。我用的是IntelliJ IDEA,所以我点击File->New Project:

I have it in it's Dark Theme

我用的是深色主题

在Eclipse你应当打开File->New->Android Application Project:

I quite like the configuration options you get here.

我非常喜欢这里的配置选项

我的机器上安装了包括最新版在内的好几个版本的SDK,其中包括我现在手机以及以前手机(2.2)的版本。这样能保证应用能在大多数设备上运行。这里的最低版本我选择了2.2。

下一步,我创建了一些文件,没有太多需要注意的地方。我在创建项目的时候犯了点小错误,项目名不太对。我已经在一个xml文件里修正了这个问题,不过最终决定不在整个项目里都进行修改(部分原因是我已经在GitHub上用这个名字创建了一个项目)。

MuhammadW
MuhammadW
翻译于 2013/01/14 23:29
2

布局

我做的下一件事情是设计应用的布局。我的文件是自动生成的,文件名为main.xml,位于res文件夹下的layout子文件夹。也许在Eclipse的项目里它可能叫别的名字,但是应该是一个孤零零的文件在那个子文件夹里(译注:Eclipse通常为res/layout/activity_main.xml)。

Idea拥有一个非常好用的UI设计工具(Eclipse也有类似的工具),这让UI设计变得非常好上手。我最终的效果如下:

DesignIdea

在这里有:

  • 一个写着我的名字和游戏标题的TextView。
  • 一个写着New Game的按钮(Button)。
  • 一个包含3*3排列的按钮的TableLayout(表格布局)

我修改了每个东西的属性,使它们达到上面看上去的效果:

  • TextView的layout:gravity属性设置为center it horizontally(译注:Eclipse为Layout Parameters中的Gravity属性设置为center_horizontal)。
  • Button的layout:gravity属性也设置为center it horizontally。另外它的layout:width设置为fill the parent。(译注:Eclipse为Layout Parameters中的Gravity属性设置为center_horizontalLayout。Parameters中的Width从2.2版本起建议设置为match_parent而不是fill_parent,两者效果一样)。
  • TableLayout的layout:gravity设置为centre both horizontally and vertically。
  • TableLayout中的每一个Button的width和height属性设置为100dp,这样它们就变成了正方形。

我不会向你展示真正的xml文件,因为我认为你自己动手实验会更好一些。

我也修改了AndroidManifest.xml文件(位于项目的根目录)来防止这个应用程序旋转成横向的。在Activity标签里添加如下代码:

android:screenOrientation="portrait"
MuhammadW
MuhammadW
翻译于 2013/01/15 00:06
2

转到代码部分

现在开始完成编码!

当你开始一个新的Android项目时,你可能看到一个如下的java文件(为了减少错误和格式):

package com.example.NaughtsAndCrosses;
 
import android.app.Activity;
import android.os.Bundle;
 
/**
 * Tic Tac Toe Game
 * ---
 *
 * Code by Lyndon Armitage
 * For learning purposes
 *
 * @author Lyndon Armitage
 */
public class MainActivity extends Activity {
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

我们的第一个工作点是如何确定如果去表示游戏的各种状态。所以为了做到这一点,我们需要建立一些具体的数值;这里只有两种角色(X和O),放置在一个3X3的画板上,网格中只有X,O或者空白。

根据这些具体的数值,我的定义如下:

// Representing the game state:
private boolean noughtsTurn = false; // Who's turn is it? false=X true=O
private char board[][] = new char[3][3]; // for now we will represent the board as an array of characters

另一个种替代使用char数组的方式是使用枚举类型,不过这里为了简单先使用char数组。同样我也可以使用枚举的确定谁是轮到

苏谷子
苏谷子
翻译于 2013/01/15 10:06
3

现在我们知道了如何表示游戏的状态了,我们要弄清楚如果现在人与游戏的交互。具体代码实现上,我已经决定通过使用多个在同一TableLayout(表格布局)中的Button和一个在外部的表示开始有些的Button。

我决定首先要实现每个Button都是可交互的;改变Button上的文字和当点击事具体要做的响应。写代码实际上比我想的要更为复杂。最初我以为会有一个简单的调用来获得元素TableLayout在某一个位置,有点像getViewAt(int x,int y)但是没有。这是因为TableLayout可以包含不在行和列中的数据。它能够在下方或者在两个普通的TableRows中间包含一个EditText元素。

例如:

Good come in handy actually

实际上可能会派上用场,当你思考布局时,这样的设计就是情理之中,这意味着你可以朝网格的不同部分延伸。

苏谷子
苏谷子
翻译于 2013/01/15 11:35
2

所以为了得到每个Button的位置,我们不得不查看每个TableRow对象所包含的Button,同时使用他们的y和x的position值分别做为y和x轴的坐标值。

/**
 * This will add the OnClickListener to each button inside out TableLayout
 */
private void setupOnClickListeners() {
    TableLayout T = (TableLayout) findViewById(R.id.tableLayout);
    for(int y = 0; y < T.getChildCount(); y ++) {
        if(T.getChildAt(y) instanceof TableRow) {
            TableRow R = (TableRow) T.getChildAt(y);
            for(int x = 0; x < R.getChildCount(); x ++) {
                View V = R.getChildAt(x); // In our case this will be each button on the grid
                V.setOnClickListener(new PlayOnClick(x, y));
            }
        }
    }
}

我马上就会解释PlayOnClick,怎样通过代码绑定tableLayout,遍历它的子控件以及遍历它子控件的子控件,并设置点击事的处理。我需要通过编写我的TableLayout同时给它一个ID,为的是我们能通过findViewById()找到他。我在xml文件中简单的设置android:id属性如下:

android:id="@+id/tableLayout"

PlayOnClick是我为了这个项目自动的OnClickListener的实现。我可以创建九个不同的方法(每个方法对应一个Button),同时是着每个Button的监听,但是通过实现OnClickListener动态的设置每个button的使用是最好的选择,没有之一。

苏谷子
苏谷子
翻译于 2013/01/15 11:52
1

  在我们的游戏中对此的实现如下(这是 MainActivity 类的一个内置类):

/**
 * Custom OnClickListener for Noughts and Crosses
 * Each Button for Noughts and Crosses has a position we need to take into account
 * @author Lyndon Armitage
 */
private class PlayOnClick implements View.OnClickListener {
 
    private int x = 0;
    private int y = 0;
 
    public PlayOnClick(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    @Override
    public void onClick(View view) {
        if(view instanceof Button) {
            Button B = (Button) view;
            board[x][y] =  noughtsTurn ? 'O' : 'X';
            B.setText(noughtsTurn ? "O" : "X");
            B.setEnabled(false);
            noughtsTurn = !noughtsTurn;
        }
    }
}
  如果现在运行此程序,它是可玩的,尽管没有任何得分统计和获胜检查。而且,“新游戏”按钮没有作用,让我们改变这种状况。
/**
 * Called when you press new game.
 * @param view the New Game Button
 */
public void newGame(View view) {
    noughtsTurn = false;
    board = new char[3][3];
    resetButtons();
}
 
/**
 * Reset each button in the grid to be blank and enabled.
 */
private void resetButtons() {
    TableLayout T = (TableLayout) findViewById(R.id.tableLayout);
    for (int y = 0; y < T.getChildCount(); y++) {
        if (T.getChildAt(y) instanceof TableRow) {
            TableRow R = (TableRow) T.getChildAt(y);
            for (int x = 0; x < R.getChildCount(); x++) {
                if(R.getChildAt(x) instanceof Button) {
                    Button B = (Button) R.getChildAt(x);
                    B.setText("");
                    B.setEnabled(true);
                }
            }
        }
    }
}
  使用 UI 编辑器,我设置了“新游戏”按钮来调用相应的方法,现在,当我点击它,网格将被清除。 同时我也在 onCreate() 方法中添加了对 resetButtons() 的调用,确保所有的按钮都被绑定。
  现在,如何进行获胜检查呢?恩,我知道,只要一条线中的3个格匹配,则玩家胜利。因此,我们应该编写一个计算方法,并在合适的地方调用。


K6F
K6F
翻译于 2013/01/15 10:47
1

下面是我用来检测玩家是否已经赢得游戏的方法实现,支持各种格子数的游戏。当然还可以改进为只剩下单循环,但是理解起来就非常有难度:

/**
 * This is a generic algorithm for checking if a specific player has won on a tic tac toe board of any size.
 *
 * @param board  the board itself
 * @param size   the width and height of the board
 * @param player the player, 'X' or 'O'
 * @return true if the specified player has won
 */
private boolean checkWinner(char[][] board, int size, char player) {
    // check each column
    for (int x = 0; x < size; x++) {
        int total = 0;
        for (int y = 0; y < size; y++) {
            if (board[x][y] == player) {
                total++;
            }
        }
        if (total >= size) {
            return true; // they win
        }
    }
 
    // check each row
    for (int y = 0; y < size; y++) {
        int total = 0;
        for (int x = 0; x < size; x++) {
            if (board[x][y] == player) {
                total++;
            }
        }
        if (total >= size) {
            return true; // they win
        }
    }
 
    // forward diag
    int total = 0;
    for (int x = 0; x < size; x++) {
        for (int y = 0; y < size; y++) {
            if (x == y && board[x][y] == player) {
                total++;
            }
        }
    }
    if (total >= size) {
        return true; // they win
    }
 
    // backward diag
    total = 0;
    for (int x = 0; x < size; x++) {
        for (int y = 0; y < size; y++) {
            if (x + y == size - 1 && board[x][y] == player) {
                total++;
            }
        }
    }
    if (total >= size) {
        return true; // they win
    }
 
    return false; // nobody won
}
我在另外一个地方来调用上面的方法:
/**
 * Method that returns true when someone has won and false when nobody has.
 * It also display the winner on screen.
 *
 * @return
 */
private boolean checkWin() {
 
    char winner = '\0';
    if (checkWinner(board, 3, 'X')) {
        winner = 'X';
    } else if (checkWinner(board, 3, 'O')) {
        winner = 'O';
    }
 
    if (winner == '\0') {
        return false; // nobody won
    } else {
        // display winner
        TextView T = (TextView) findViewById(R.id.titleText);
        T.setText(winner + " wins");
        return true;
    }
}

TextView 用来显示赢家信息。

红薯
红薯
翻译于 2013/01/15 06:59
3

现在,我们来简单地修改一下PlayOnClick类中的onClick方法,然后才能使用它:

public void onClick(View view) {
    if (view instanceof Button) {
        Button B = (Button) view;
        board[x][y] = noughtsTurn ? 'O' : 'X';
        B.setText(noughtsTurn ? "O" : "X");
        B.setEnabled(false);
        noughtsTurn = !noughtsTurn;
 
        // check if anyone has won
        if (checkWin()) {
            disableButtons();
        }
    }
}

当有玩家获胜时,disableButtons()方法简单地禁用棋盘上所有的按钮,这样就能阻止玩家继续玩下去。

就到这里吧!一步一步开发Android版井字游戏!

Live on my phone

我手机上的截图

如果你想看最终代码,可以在我的github仓库上看到。

SeabornLee
SeabornLee
翻译于 2013/01/15 00:23
1

还需要改进的地方:

  • 得分存储
  • 更多的格子
  • 联机游戏
  • 提供一个 AI 玩家
  • 更绚丽的色彩
  • 优化.

Happy Coding!

红薯
红薯
翻译于 2013/01/14 22:36
3
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(10)

Aeolus
Aeolus

引用来自“EREHMii”的评论

引用来自“Aeolus”的评论

引用来自“EREHMii”的评论

idea这是用的什么版本开发的android?

12

IntelliJ IDEA 12 有这两个版本:
Ultimate Edition (Free 30-day trial)
Community Edition (FREE)

ue吧。貌似社区版没法开发android,需要安装插件。
steve_Li
steve_Li
yinru引入
rockeyfish
rockeyfish
mark,才装了Intellij IDEA,可以试试手。
TigaPile
TigaPile

引用来自“李海珍”的评论

@司马奔 Now finally the code! 应该翻译为:现在终于该写代码了!

“最后的代码部分”
有初啊
有初啊

引用来自“Aeolus”的评论

引用来自“EREHMii”的评论

idea这是用的什么版本开发的android?

12

IntelliJ IDEA 12 有这两个版本:
Ultimate Edition (Free 30-day trial)
Community Edition (FREE)

WuWarren
WuWarren
@司马奔 "另一个种替代使用char数组的方式是使用枚举类型,"这里好像有点问题
Aeolus
Aeolus

引用来自“EREHMii”的评论

idea这是用的什么版本开发的android?

12
key4su
key4su
idea怎么把一个工程当lib给其他工程用?
有初啊
有初啊
idea这是用的什么版本开发的android?
代码会说话
代码会说话
@司马奔 Now finally the code! 应该翻译为:现在终于该写代码了!
返回顶部
顶部