Android开发手把手入门精讲2:音乐井字棋

零 成品展示

功能

  • 轮流落子,提示落子方,判定输赢
  • 胜负分晓或棋盘满后toast提示需要重新开始
  • 播放/暂停背景音乐
  • 按下按钮重新开始游戏

一 源码

MainActivity.java代码

package com.example.tictactoe;

import android.app.*;
import androidx.appcompat.app.AppCompatActivity;

import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;

import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private TextView info;
    private int moveNow = 0;
    boolean musicOn = true;

    private enum StateEnum {
        RedCircle,
        GreenCross,
        Blank
    }

    private ImageView[] BoardList = new ImageView[9];
    private StateEnum[][] BoardState = new StateEnum[3][3];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        info = findViewById(R.id.infoview);
        setList();
        initBoard();
        setOnClickListener();

    }

    private void click(View v) {
        if (!hasWon() && !hasDrawn()) {
            //RedCircle First
            makeMove(v, moveNow % 2 == 0 ? StateEnum.RedCircle : StateEnum.GreenCross);
        } else {
            Toast.makeText(getApplicationContext(), "please restart the game", Toast.LENGTH_SHORT).show();
        }
    }

    //成功落子返回true
    private boolean makeMove(View v, StateEnum s) {
        try {
            int index = findIndex((ImageView) v);
            if (index == -1) {
                return false;
            }
            if (BoardState[index / 3][index % 3] == StateEnum.Blank) {
                BoardState[index / 3][index % 3] = s;
                moveNow++;
                if (s == StateEnum.GreenCross) {
                    ((ImageView) v).setBackgroundResource(R.drawable.cross);
                    info.setText("Red Hand");
                    info.setTextColor((Color.RED));
                } else {
                    //red cross
                    ((ImageView) v).setBackgroundResource(R.drawable.circle);
                    info.setText("Green Hand");
                    info.setTextColor(Color.GREEN);
                }
                if(hasWon()){
                    if(moveNow%2==0){
                        info.setText("Green Hand Won");
                        info.setTextColor(Color.GREEN);
                    }
                    else{
                        info.setText("Red Hand Won");
                        info.setTextColor((Color.RED));
                    }

                }
                else if(hasDrawn()){
                    info.setText("Draw");
                    info.setTextColor((Color.BLACK));
                }
                return true;
            }
            //若棋盘被占,重新落子
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    //初始化ImageView-下标词典
    private void setList() {
        String[] names = new String[9];
        for (int i = 1; i < 4; i++) {
            for (int j = 1; j < 4; j++) {
                names[(i - 1) * 3 + j - 1] = "image" + i + j;
            }
        }
        Resources res = getResources();
        for (int i = 0; i < 9; i++) {
            int id = res.getIdentifier(names[i], "id", getPackageName());
            BoardList[i] = findViewById(id);
        }
    }

    //返回view的下标
    private int findIndex(ImageView v) {
        for (int i = 0; i < 9; i++) {
            if (BoardList[i].equals(v)) {
                return i;
            }
        }
        return -1;
    }

    private boolean hasWon() {
        int[][] winningpattern = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, {0, 4, 8}, {2, 4, 6}};
        StateEnum[] players = {StateEnum.GreenCross, StateEnum.RedCircle};
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 2; j++) {
                StateEnum player = players[j];
                if (BoardState[winningpattern[i][0] / 3][winningpattern[i][0] % 3] == player
                        && BoardState[winningpattern[i][1] / 3][winningpattern[i][1] % 3] == player
                        && BoardState[winningpattern[i][2] / 3][winningpattern[i][2] % 3] == player) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasDrawn() {
        int cnt = 0;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (BoardState[i][j] == StateEnum.Blank) {
                    cnt++;
                }
            }
        }
        if (cnt < 1) {
            return true;
        }
        return false;
    }

    private void setOnClickListener() {
        for (int i = 0; i < 9; i++) {
            ImageView v = BoardList[i];
            v.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    click(v);
                }
            });
        }
        Button buttonRestart = findViewById(R.id.buttonRestart);
        buttonRestart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                initBoard();
                moveNow = 0;
            }
        });
        ImageButton musicButton = findViewById(R.id.musicButton);
        musicButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!musicOn){
                   onResume();
                }
                else{
                    onPause();
                }
                musicOn = !musicOn;
            }
        });
    }

    private void initBoard() {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                BoardState[i][j] = StateEnum.Blank;
                BoardList[i*3+j].setBackgroundResource(R.drawable.blank);
            }
        }
        info.setText("Red Hand");
        info.setTextColor((Color.RED));
    }


    @Override
    protected void onPause() {
        super.onPause();
        MusicServer.stop(this);
    }


    @Override
    protected void onResume() {
        super.onResume();
        MusicServer.play(this, R.raw.song);
    }


}

 MusicServer.java

package com.example.tictactoe;

import android.content.Context;
import android.media.MediaPlayer;

public class MusicServer{
    private static MediaPlayer mp =null;
    public static void play(Context context, int resource){
        stop(context);
        mp = MediaPlayer.create(context, resource);
        mp.setLooping(true);
        mp.start();
    }

    public static void stop(Context context) {
        if(mp!= null){
            mp.stop();
            mp.release();
            mp = null;
        }
    }

}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:weightSum="100"
    android:background="#ccffff"
    >
    <RelativeLayout
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        android:layout_weight = "20"
        >
        <ImageButton
            android:id="@+id/musicButton"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginStart="20dp"
            android:layout_marginTop="20dp"

            android:background="@drawable/music" />

        <TextView
            android:id="@+id/infoview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/musicButton"
            android:text="Now: Red Hand"
            android:textColor="#ff0000"
            android:textSize="30sp" />


    </RelativeLayout>
    <RelativeLayout
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        android:layout_weight = "50"
        >

        <TableLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#70f3ff"
            android:alpha="0.5">

            <TableRow
                android:layout_width="match_parent"
                android:layout_height="0px"
                android:layout_weight = "1">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image11"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image12"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image13"/>
            </TableRow>

            <TableRow
                android:layout_width="match_parent"
                android:layout_height="0px"
                android:layout_weight = "1">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image21"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image22"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image23"/>
            </TableRow>

            <TableRow
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image31"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image32"/>
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/blank"
                    android:id="@+id/image33"/>
            </TableRow>


        </TableLayout>
    </RelativeLayout>

    <Button
        android:id="@+id/buttonRestart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Resume" />
</LinearLayout>

二 文件结构

红圈部分需要自行添加。其中blank.png为棋盘空格子图片。circle.png为和cross.png为不同棋子的图片,根据井字棋的传统,选择一圈一叉。music.png为音乐图标。raw/song.mp3是背景音乐文件,建议选择压缩后的比较小的文件,我的文件尺寸为1798KB。

可以在以下链接下载,也可以使用其他合适的图片与音频,但注意项目中的文件名与代码中的变量要一一对应。

circle.png下载链接

cross.png下载链接

blank.png下载链接

music.png下载链接

song.mp3下载链接

三 解析

对Android开发还不了解的友友看这里的前置知识: Android开发手把手入门精讲1:英里公里转换器

这里讲解XML文件和MainActivity.java文件。MusicServer.java文件比较简单,省略。 

XML解析 

这是Design布局的效果。可以看到,界面垂直方向被一个LinearLayout上分为上、中、下三块,每块是一个RelativeLayout。中间的RelativeLayout均分为3个TableRow,每个TableRow均分为3个ImageView。

(1)均分宽度

给同一行的3个ImageView添加如下属性能让三者均分父容器的宽度

android:layout_width="wrap_content"

(2)按比例分配高度

给LinearLayout添加如下属性

android:orientation="vertical"
    android:weightSum="100"

再给它的孩子容器,RelativeLayout,添加如下属性

android:layout_height = "0dp"
android:layout_weight = "20"

 就可以让该RelativeLayout占总高度的20/100。

MainActivity.java解析

private enum StateEnum {
        RedCircle,
        GreenCross,
        Blank
    }

StateEnum定义了单个棋盘格子的三种状态:Blank表示未落子的空格,RedCircel与GreenCross则是双方的棋子。

private StateEnum[][] BoardState = new StateEnum[3][3];

BoardState二维数组的保存3*3棋盘下每个格子的落子情况。开始游戏以及重新开始游戏时需要全部初始化为Blank。

private ImageView[] BoardList = new ImageView[9];
//初始化ImageView-下标词典
    private void setList() {
        String[] names = new String[9];
        for (int i = 1; i < 4; i++) {
            for (int j = 1; j < 4; j++) {
                names[(i - 1) * 3 + j - 1] = "image" + i + j;
            }
        }
        Resources res = getResources();
        for (int i = 0; i < 9; i++) {
            int id = res.getIdentifier(names[i], "id", getPackageName());
            BoardList[i] = findViewById(id);
        }
    }
//返回view的下标
    private int findIndex(ImageView v) {
        for (int i = 0; i < 9; i++) {
            if (BoardList[i].equals(v)) {
                return i;
            }
        }
        return -1;
    }

BoardList里的9个成员对应组成棋盘的9个格子,类型采用ImageView是为了显示未落子或落某方棋子的画面。

setList()初始化了BoardList[],即把XML定义的9个带id信息的ImageList与BoardList关联起来。

findIndex(ImageView)方法用于得到ImageView的映射(0-8),具体是,左上的ImageView格子映射为0,中上的格子映射为1,...右下的格子映射为8。这样,每次点击其中一个格子,得到其映射i,就可以关联到BoardState[i/3][i%3],获取、修改该格子的状态,改变格子的图片代表落子。

private boolean hasWon() {
        int[][] winningpattern = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, {0, 4, 8}, {2, 4, 6}};
        StateEnum[] players = {StateEnum.GreenCross, StateEnum.RedCircle};
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 2; j++) {
                StateEnum player = players[j];
                if (BoardState[winningpattern[i][0] / 3][winningpattern[i][0] % 3] == player
                        && BoardState[winningpattern[i][1] / 3][winningpattern[i][1] % 3] == player
                        && BoardState[winningpattern[i][2] / 3][winningpattern[i][2] % 3] == player) {
                    return true;
                }
            }
        }
        return false;
    }

hasWon()是判定BoardState的当前状态是否满足一方胜利的函数,若胜负已定,返回true。

winningpattern数组保存了井字棋胜利的所有连珠模式,如{0,1,2}代表0号(左上)、1号(中上)、2号(右上)三个格子内棋子持方一致。

如果还有什么不清楚的地方,或者其他想法与建议,欢迎留言私信~

The End

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值