Android & Kotlinの環境でマインスイーパーを開発する方法を説明します。
今回は、#14で実装した旗数が正しい数字をタップしたら周りのタイルを再起的に開く処理の対象タイルを強調表示します。
動作イメージ
旗タイルの右にある数字タイルの「1」をタップしたケースの動作イメージです。
ソースコード
開発環境は次の通りです。
PC | MacBook Pro(2016年モデル) |
IDE | Android Studio 4.1.1 |
Android SDK | minSdkVersion 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メソッドを呼び出して完成です。
コメント