Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android實訓案例(八)——單機五子棋游戲,自定義棋盤,線條,棋子,游戲邏輯,游戲狀態存儲,再來一局

Android實訓案例(八)——單機五子棋游戲,自定義棋盤,線條,棋子,游戲邏輯,游戲狀態存儲,再來一局

編輯:關於Android編程

 

阿法狗讓圍棋突然就被熱議了,鴻洋大神也順勢出了篇五子棋單機游戲的視頻,我看到了就像膜拜膜拜,就學習了一下,寫篇博客梳理一下自己的思路,加深一下印象

視頻鏈接:http://www.imooc.com/learn/641

一.棋盤

我們一看就知道,我們必須自定義View,這裡我們定義一個GameView來做游戲主類,第一步,先測量,我們這裡不難知道,五子棋他的棋盤是一個正方形,所以我們需要去測量

 /**
     * 測量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //獲取高寬值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int hightSize = MeasureSpec.getSize(heightMeasureSpec);
        int hightMode = MeasureSpec.getMode(heightMeasureSpec);

        //拿到寬和高的最小值,也就是寬
        int width = Math.min(widthSize, heightMeasureSpec);

        //根據測量模式細節處理
        if (widthMode == MeasureSpec.UNSPECIFIED) {
            width = hightSize;
        } else if (hightMode == MeasureSpec.UNSPECIFIED) {
            width = widthSize;
        }

        //設置這樣就是一個正方形了
        setMeasuredDimension(width, width);

    }

這裡的邏輯還是十分簡單的,我們拿到長和寬去比較一下,設置這個View的長寬Wie最小值,就是一個正方形了,所以我們的layout_main.xml是這樣寫的

<linearlayout 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" android:orientation="vertical" android:background="@drawable/main_bg">

    <com.lgl.fiverow.gameview android:layout_width="match_parent" android:layout_height="match_parent">


</com.lgl.fiverow.gameview></linearlayout>
</code>

這裡我在構造方法中設置了一個半透明的紅色背景,是在我們調試的時候可以更加清晰的看清楚GameView的大小,所以,運行的結果

這裡寫圖片描述

二.線條

這個應該也算是棋盤的一部分吧,就是棋盤上的線條,我們應該怎麼去畫,首先,我們要去定義一些屬性

    //線條數量
    private static final int MAX_LINE = 10;

    //線條的寬度
    private int mPanelWidth;

    //線條的高度
    private float mLineHeight;

然後,我們要去確定大小

/**
     * 測量大小
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //拿到寬
        mPanelWidth = w;
        //分割
        mLineHeight = mPanelWidth * 1.0f / MAX_LINE;
    }

不要著急,這些都只是一些准備的工作,我們畫線條是必須要在onDraw(0方法裡的,但是前期我們要准備一只畫筆,對吧,所以我們要初始化畫筆

     /**
     * 初始化畫筆
     */
    private void initPaint() {
        //設置顏色
        mPaint.setColor(0x88000000);
        //抗鋸齒
        mPaint.setAntiAlias(true);
        //設置防抖動
        mPaint.setDither(true);
        //設置Style
        mPaint.setStyle(Paint.Style.STROKE);
    }

現在我們可以去繪制了,我們在OnDraw(0方法裡寫一個drawLine方法來專門繪制線條

 /**
     * 繪制棋盤的方法
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        //獲取高寬
        int w = mPanelWidth;
        float lineHeight = mLineHeight;

        //遍歷,繪制線條
        for (int i = 0; i < MAX_LINE; i++) {
            //橫坐標
            int startX = (int) (lineHeight / 2);
            int endX = (int) (w - lineHeight / 2);

            //縱坐標
            int y = (int) ((0.5 + i) * lineHeight);

            //繪制橫
            canvas.drawLine(startX, y, endX, y, mPaint);
            //繪制縱
            canvas.drawLine(y, startX, y, endX, mPaint);
        }

    }

我們運行一下

這裡寫圖片描述

好的,這裡,注意一下,我在activity_main.xml中定義了一個

android:gravity="center"

屬性,所以讓他居中,同樣的,我們在initPaint中加上點代碼讓我們看的更加直觀一點

//設置顏色
mPaint.setColor(Color.BLACK);
//設置線條寬度
mPaint.setStrokeWidth(3);

同樣的,我們把構造法裡的設置背景的測試代碼注釋掉

//測試代碼
//setBackgroundColor(0x44ff0000);

這樣,我們運行一下

這裡寫圖片描述

得,我們現在有模有樣了

三.棋子

棋子我們事先准備好了兩張圖片,但是這裡我們要考慮他的大小的問題了,我們的思路是讓他是行高的四分之三大小,所以先聲明

 //黑棋子
 private Bitmap mBlack;

 //白棋子
 private Bitmap mWhite;

//比例,棋子的大小是高的四分之三
 private float rowSize = 3 * 1.0f / 4;

然後我們定義一個方法區初始化Bitmap

     /**
     * 初始化棋子
     */
    private void initBitmap() {
        //拿到圖片資源
        mBlack = BitmapFactory.decodeResource(getResources(), R.drawable.stone_black);
        mWhite = BitmapFactory.decodeResource(getResources(), R.drawable.stone_white);
    }

拿到資源之後我們就可以設置大小了,我們在onSizeChanged()裡面設置

 //棋子寬度
 int mWhiteWidth = (int) (mLineHeight * rowSize);

 //修改棋子大小
 mWhite = Bitmap.createScaledBitmap(mWhite, mWhiteWidth, mWhiteWidth, false);
 mBlack = Bitmap.createScaledBitmap(mBlack, mWhiteWidth, mWhiteWidth, false);

不過棋子可沒我們想象的那麼簡單,我們要點擊一下再去繪制一個棋子,這樣的思路該怎麼去實現呢?我們實現它的點擊事件,這裡先定義幾個變量

    //存儲用戶點擊的坐標
    private List mWhiteArray = new ArrayList<>();
    private List mBlackArray = new ArrayList<>();

    //標記,是執黑子還是白子 ,白棋先手
    private boolean mIsWhite = true;

這樣才和觸摸事件相得映彰

 /**
     * 觸摸事件
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            //按下事件
            case MotionEvent.ACTION_UP:

                int x = (int) event.getX();
                int y = (int) event.getY();

                //封裝成一個Point
                Point p = getValidPoint(x, y);

                //判斷當前這個點是否有棋子了
                if(mWhiteArray.contains(p) || mBlackArray.contains(p)){

                    //點擊不生效
                    return false;
                }

                //判斷如果是白子就存白棋集合,反之則黑棋集合
                if (mIsWhite) {
                    mWhiteArray.add(p);
                } else {
                    mBlackArray.add(p);
                }

                //刷新
                invalidate();

                //改變值
                mIsWhite = !mIsWhite;

                break;
        }

        return true;
    }

這樣,有幾點是要說明一下的,首先我們new Point的時候為了避免重復繪制我們是實現了一個方法

/**
     * 不能重復點擊
     *
     * @param x
     * @param y
     * @return
     */
    private Point getValidPoint(int x, int y) {

        return new Point((int) (x / mLineHeight), (int) (y / mLineHeight));
    }

緊接著我們就判斷,要是重復點擊,返回false,而且我們在action選擇也是選擇了ACTION_UP,為什麼?為什麼不是ACTION_DOWN?因為這個畢竟是一個View,父View會攔截(某些場景),所以我們選在UP上才是合情合理的

好的,當我們點擊之後就要繪制棋子了,這裡我們也寫一個方法

 /**
     * 繪制棋子的方法
     *
     * @param canvas
     */
    private void drawPieces(Canvas canvas) {
        for (int i = 0; i < mWhiteArray.size(); i++) {
            //獲取白棋子的坐標
            Point whitePoint = mWhiteArray.get(i);
            canvas.drawBitmap(mBlack, (whitePoint.x + (1 - rowSize) / 2) * mLineHeight, (whitePoint.y + (1 - rowSize) / 2) * mLineHeight, null);
        }

        for (int i = 0; i < mBlackArray.size(); i++) {
            //獲取黑棋子的坐標
            Point blackPoint = mBlackArray.get(i);
            canvas.drawBitmap(mWhite, (blackPoint.x + (1 - rowSize) / 2) * mLineHeight, (blackPoint.y + (1 - rowSize) / 2) * mLineHeight, null);
        }
    }

OK,我們實際運行一下

這裡寫圖片描述

四.游戲邏輯

現在什麼都有了,基本上都可用玩了,但是還少了重要的一點就是游戲結束,你到了五顆也需要判斷是否勝利呀,對吧,我們寫一個方法,在每次繪制完成之後就去判斷是否有贏家

  /**
     * 判斷是否勝利
     */
    private void checkWin() {

        //判斷白棋是否有五個相同的棋子相連
        boolean mWhiteWin = checkFiveLine(mWhiteArray);
        //判斷黑棋是否有五個相同的棋子相連
        boolean mBlackWin = checkFiveLine(mBlackArray);

        //只要有一個勝利,游戲就結束
        if (mWhiteWin || mBlackWin) {

            mIsGameOver = true;

            mIsWhiteWin = mWhiteWin;

            Toast.makeText(getContext(), mIsWhiteWin ? "白棋勝利" : "黑棋勝利", Toast.LENGTH_SHORT).show();

        }
    }

好的,我們重點邏輯就在checkFiveLine這個方法上了,這裡,我們所知道的勝利有四種情況

這裡寫圖片描述

我們先定義一個常量

    //勝利棋子數量
    private static final int MAX_COUNT_IN_LINE = 5;

OK,接下來我們可以實現以下勝利的邏輯了

 /**
     * //判斷棋子是否有五個相同的棋子相連
     *
     * @param points
     * @return
     */
    private boolean checkFiveLine(List points) {

        //遍歷棋子
        for (Point p : points) {
            //拿到棋盤上的位置
            int x = p.x;
            int y = p.y;

            /**
             * 四種情況勝利,橫,豎,左斜,右斜
             */

            //橫
            boolean win = checkHorizontal(x, y, points);
            if (win) return true;
            //豎
            win = checkVertical(x, y, points);
            if (win) return true;
            //左斜
            win = checkLeft(x, y, points);
            if (win) return true;
            //右斜
            win = checkRight(x, y, points);
            if (win) return true;

        }
        return false;
    }

我們不管哪個方向只要返回true就返回true,然後彈Toast,這裡,四個方向的邏輯

 /**
     * 判斷橫向的棋子
     *
     * @param x
     * @param y
     * @param points
     */
    private boolean checkHorizontal(int x, int y, List points) {

        //棋子標記,記錄是否有五個  =1是因為自身是一個
        int count = 1;

        //左
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        //右
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }

 /**
     * 判斷縱向的棋子
     *
     * @param x
     * @param y
     * @param points
     */
    private boolean checkVertical(int x, int y, List points) {

        //棋子標記,記錄是否有五個  =1是因為自身是一個
        int count = 1;

        //上
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        //下
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }
左斜
 /**
     * 判斷左斜向的棋子
     *
     * @param x
     * @param y
     * @param points
     */
    private boolean checkLeft(int x, int y, List points) {

        //棋子標記,記錄是否有五個  =1是因為自身是一個
        int count = 1;

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }
右斜
 /**
     * 判斷右斜向的棋子
     *
     * @param x
     * @param y
     * @param points
     */
    private boolean checkRight(int x, int y, List points) {

        //棋子標記,記錄是否有五個  =1是因為自身是一個
        int count = 1;

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五個就為true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }

這樣,我們運行一下

這裡寫圖片描述

嘿嘿,好玩吧!<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjwvYmxvY2txdW90ZT4NCjxoMiBpZD0="五游戲狀態存儲">五.游戲狀態存儲

這個就是當我們游戲掛後台之後,再回來游戲就沒了,我們應該存儲他的狀態,讓他每一次進入的時候要是上一句沒有下完接著下,那我們該怎麼去實現呢?和Activity一樣,我們View也有存儲狀態的方法

  /**
     * 存儲狀態
     *
     * @return
     */
    @Override
    protected Parcelable onSaveInstanceState() {

        Bundle bundle = new Bundle();
        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
        bundle.putBoolean(INSTANCE_GAMEOVER, mIsGameOver);
        bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY, mWhiteArray);
        bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY, mBlackArray);
        return bundle;
    }

    /**
     * 重新運行
     *
     * @param state
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state) {

        //取值
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            mIsGameOver = bundle.getBoolean(INSTANCE_GAMEOVER);
            mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY);
            mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY);

            //調用
            super.onRestoreInstanceState(bundle.getParcelable(INSTANCE));
            return;
        }
        super.onRestoreInstanceState(state);
    }

這樣就可以了,但是,有一點要知道,不要忘記在布局文件上給控件加上ID,不然狀態不會存儲哦

 

六.再來一局

既然我們的游戲邏輯差不多了,那我們應該考慮一下當你勝利的時候,你是不是應該再來一局,所以我們還要實現這個邏輯,這個很簡單

   /**
     * 再來一局
     */
    public void RestartGame() {
        mWhiteArray.clear();
        mBlackArray.clear();
        mIsGameOver = false;
        mIsWhiteWin = false;
        invalidate();
    }

這樣,我們就可以直接調用了,我們來看看MainActivity

package com.lgl.fiverow;

import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

/**
 * 五子棋游戲
 */
public class MainActivity extends AppCompatActivity {

    //重來按鈕
    private FloatingActionButton fab;
    //游戲
    private GameView game;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        game = (GameView) findViewById(R.id.mGameView);
        fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                game.RestartGame();
            }
        });
    }
}

OK,我們最終運行一下

這裡寫圖片描述

OK,到這裡,就算開發完成了

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved