【マインスイーパー開発 #15】数字をタップしたときに開く範囲を強調表示する|Android & Kotlinアプリ開発

Android & Kotlin

Android & Kotlinの環境でマインスイーパーを開発する方法を説明します。

今回は、#14で実装した旗数が正しい数字をタップしたら周りのタイルを再起的に開く処理の対象タイルを強調表示します。

動作イメージ

旗タイルの右にある数字タイルの「1」をタップしたケースの動作イメージです。

ソースコード

開発環境は次の通りです。

PCMacBook Pro(2016年モデル)
IDEAndroid Studio 4.1.1
Android SDKminSdkVersion 21
targetSdkVersion 30
言語Kotlin 1.3.72
package com.sosotata.minesweeper.ui.widgets

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import com.sosotata.minesweeper.LOG
import com.sosotata.minesweeper.R
import com.sosotata.minesweeper.model.TileController

/**
 * 四角タイルゲーム画面
 */
class SquareTileGameView : SosotataImageView {

    /** タイル描画用ビットマップ */
    private lateinit var mSourceBitmap: Bitmap

    /** 強調表示用ビットマップ */
    private val mEmphasisBitmap: Bitmap

    /** 強調表示タイル */
    private val mEmphasisPos: Point = Point(-1, -1)

    /** タイル描画用キャンバス */
    private lateinit var mRenderCanvas: Canvas

    /** タイル制御 */
    private lateinit var mTile: TileController

    /**
     * コンストラクタ
     */
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        mEmphasisBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.base_emphasis)
    }

    /**
     * タイルを描画する
     */
    fun initialize(tile: TileController) {
        mTile = tile
        mSourceBitmap = Bitmap.createBitmap(
            mTile.tileWidth * mTile.numX, mTile.tileHeight * mTile.numY, Bitmap.Config.ARGB_8888
        )
        mRenderCanvas = Canvas(this.mSourceBitmap)
        for (y in 0 until mTile.numY) {
            for (x in 0 until mTile.numX) {
                mRenderCanvas.drawBitmap(
                    mTile.getTileStateImage(x, y),
                    mTile.tileWidth.toFloat() * x,
                    mTile.tileHeight.toFloat() * y,
                    null
                )
            }
        }
        setImage(mSourceBitmap)

        // タイル更新
        mTile.updateTileState = { i, j, image ->
            // タッチしたタイルを描画する
            mRenderCanvas.drawBitmap(
                image, (image.width * i).toFloat(), (image.height * j).toFloat(), null)
        }
    }

    /**
     * [android.view.View.onTouchEvent]
     */
    override fun onTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            if (it.action == MotionEvent.ACTION_UP) {
                unemphasis()
            }
        }
        return super.onTouchEvent(e)
    }

    /**
     * [android.view.GestureDetector.OnGestureListener.onDown]
     */
    override fun onDown(e: MotionEvent?): Boolean {
        LOG.d("onDown $e")
        e?.let {
            if (mTile.isGaming()) {
                val p = coordinatesToTilePos(it)
                emphasis(p)
            }
        }
        return true
    }

    /**
     * [android.view.GestureDetector.OnGestureListener.onSingleTapUp]
     */
    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        LOG.d("onSingleTapUp $e")
        if (e!= null) {
            val p = coordinatesToTilePos(e)
            mTile.open(p.x, p.y)
        }
        return true
    }

    /**
     * タッチ座標からタイル位置に変換
     */
    private fun coordinatesToTilePos(e: MotionEvent): Point {
        val values = FloatArray(9)
        this.mRenderMatrix.getValues(values)
        return Point(
            ((e.x - values[Matrix.MTRANS_X]) / values[Matrix.MSCALE_X] / mTile.tileWidth).toInt(),
            ((e.y - values[Matrix.MTRANS_Y]) / values[Matrix.MSCALE_Y] / mTile.tileHeight).toInt()
        )
    }

    /**
     * 強調表示する
     */
    private fun emphasis(p: Point) {
        if (0 <= x && x < mTile.numX && 0 <= y && y < mTile.numY) {
            if (mTile.isNumberState(p.x, p.y)) {
                mEmphasisPos.set(p.x, p.y)
                renderEmphasis(p.x - 1, p.y - 1, true)
                renderEmphasis(p.x, p.y - 1, true)
                renderEmphasis(p.x + 1, p.y - 1, true)
                renderEmphasis(p.x - 1, p.y, true)
                renderEmphasis(p.x + 1, p.y, true)
                renderEmphasis(p.x - 1, p.y + 1, true)
                renderEmphasis(p.x, p.y + 1, true)
                renderEmphasis(p.x + 1, p.y + 1, true)
            }
        }
    }

    /**
     * 強調表示を解除する
     */
    private fun unemphasis() {
        if (mEmphasisPos.x >= 0 && mEmphasisPos.y >= 0) {
            val p = mEmphasisPos
            renderEmphasis(p.x - 1, p.y - 1, false)
            renderEmphasis(p.x, p.y - 1, false)
            renderEmphasis(p.x + 1, p.y - 1, false)
            renderEmphasis(p.x - 1, p.y, false)
            renderEmphasis(p.x + 1, p.y, false)
            renderEmphasis(p.x - 1, p.y + 1, false)
            renderEmphasis(p.x, p.y + 1, false)
            renderEmphasis(p.x + 1, p.y + 1, false)
        }
    }

    /**
     * 強調表示を描画する
     */
    private fun renderEmphasis(x: Int, y: Int, on: Boolean) {
        if (0 <= x && x < mTile.numX && 0 <= y && y < mTile.numY) {
            if (mTile.isBaseTile(x, y)) {
                val image = if (on) mEmphasisBitmap else mTile.getTileStateImage(x, y)
                mRenderCanvas.drawBitmap(
                    image, (image.width * x).toFloat(), (image.height * y).toFloat(), null
                )
                invalidate()
            }
        }
    }
}

解説

SquareTileGameViewクラスのコンストラクタで強調表示用ビットマップをmEmphasisBitmapに読み込んでおきます。

タッチダウン(onDown)で強調表示するemphasisメソッド、タッチアップ(onTouchEventのACTION_UP)で強調表示を解除するunemphasisメソッドを呼び出します。

そそたた
そそたた

強調表示の解除をonSingleTapUpではなくonTouchEventで実行している理由は、例えば長押しした場合にはonSingleTapUpは実行されないためです。

最後に、emphasisメソッド、unemphasisメソッドから強調表示と解除の描画を実行するrenderEmphasisメソッドを呼び出して完成です。

コメント

タイトルとURLをコピーしました