Kotlinのサンプルを紹介します ④


前回まででブロックの積み上げまで実装できました。


今回でやっと完成です。
配置されているブロックの更新をとゲームの終了処理を実装します。


ブロックの更新


ブロックがフィールドに追加されたときに、一行すべてにブロックが配置されているかを判定します。
その場合、その行よりも上のブロックをすべて一行下にずらします。
Fieldクラスに以下のメソッドを追加します。
正直これに関してはどういう方法が正しいかはわかりませんが素直に実装した結果こうなりました。

    // フィールドに配置されているブロックを更新する
    fun updateBlkOnField() {
        val h = FIELDHEIGHT/SQUARESIZE
        val w = FIELDWIDTH/ SQUARESIZE

        for (i in h-1 downTo 0) {
            var cnt = 0
            for ( j in 0..w-1) {
                if (blkOnField[j+i*w] != null) {
                    cnt += 1
                }
            }
            if (cnt == w) {
                for (k in i-1 downTo 0) {
                    for (l in 0..w-1) {
                        if (blkOnField[k*w+l] != null) {
                            blkOnField[k*w+l]!!.yPos += BLOCKSPACE
                        }

                        blkOnField[(k+1)*w+l] = blkOnField[k*w+l]
                        blkOnField[k*w+l] = null
                    }
                }
            }
        }
    }


これを実行するとこうなりました。

終了処理


終了処理といっても単にゲームをリスタートするだけです。
そのためにFieldクラスのaddBlockメソッドを修正します
ブロックする際にy座標を表すインデックスがフィールド外(0未満)だった場合終了と判断します。
そのあとフィールドの状態を初期化します。

    // フィールドに表示するブロックを追加する
    fun addBlock(block: Block): Boolean {
        for (bi in block.blockInfo) {
            val xInd = ( (block.xPos - FIELDSTARTX.toInt() + bi[0]) / SQUARESIZE )
            val yInd = ( (block.yPos - FIELDSTARTY.toInt() + bi[1]) / SQUARESIZE )

            if (yInd < 0) {
                return false
            }

            val index = xInd  + yInd * FIELDWIDTH / SQUARESIZE

            blkOnField[index] = Square(
                block.xPos+bi[0],
                block.yPos+bi[1],
                block.paint)

        }

        updateBlkOnField()
        return true
    }


そして初期化するメソッドです。

    // フィールドの状態を初期化
    fun initField() {
        blkOnField = Array(SQUARENUM, {null})
    }


そしてGraphicクラスのupdateBlockPosメソッドを以下のように修正します。

    // ブロックの位置を更新
    fun updateBlockPos(x: Int, y: Int) {
        val tempX = block.xPos
        val tempY = block.yPos
        block.updatePos(x,y)
        if (field.checkBlockPos(block) == CANTMOVE) {
            block.setPos(tempX, tempY)
            if (y != 0) {
                if (field.addBlock(block) == false) {
                    field.initField()
                }
                makeBlock()
            }
        }
    }


FieldクラスのaddBlockからの戻り値がfalseの場合(終了の場合)、フィールドを初期化します。
実行したらこうなりました。
ブロックが詰めなくなると確かにリセットされてますね。

ソースコード

/**
* MainActivity
*/
package euniclus.tetris

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.MotionEvent

class MainActivity : AppCompatActivity() {

    var graphic: Graphic? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        graphic = Graphic(this)
        setContentView(graphic!!)
    }

    // 画面がタップされたとき
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action) {
            MotionEvent.ACTION_DOWN -> {
                val x = event.x
                val y = event.y
                if (x <= FIELDSTARTX) {
                    graphic?.updateBlockPos(-BLOCKSPACE, 0)
                } else if (x >= FIELDSTARTX+FIELDWIDTH) {
                    graphic?.updateBlockPos(BLOCKSPACE, 0)
                } else if (y >= FIELDSTARTY+ FIELDHEIGHT) {
                    graphic?.updateBlockPos(0, BLOCKSPACE)
                }
            }
        }
        return true
    }
}
/**
 * Graphic
 */
package euniclus.tetris

import android.content.Context
import android.graphics.*
import android.view.View
import java.util.*

// 図形の表示に関する処理を行うクラス
class Graphic(context: Context): View(context) {
    var block = Block(BlockTypeC)
    val field = Field()
    var blockSleepCnt = 0

    override fun onDraw(canvas: Canvas) {

        // ブロックを表示
        drawBlock(canvas)

        // フィールドを表示
        drawField(canvas)

        reDraw()
    }


    private fun reDraw() {
        invalidate()
    }

    // ブロックを表示
    private fun drawBlock(canvas: Canvas) {
        blockSleepCnt += 1
        if (blockSleepCnt > BLOCKDOWNCNT) {
            updateBlockPos(0, BLOCKSPACE)
            blockSleepCnt = 0
        }

        block.draw(canvas)
    }

    // ブロックを生成
    private fun makeBlock() {
        val type = Random().nextInt(4)
        when(type) {
            0 -> block = Block(BlockTypeA)
            1 -> block = Block(BlockTypeB)
            2 -> block = Block(BlockTypeC)
            3 -> block = Block(BlockTypeD)
        }

    }

    // フィールドを表示
    private fun drawField(canvas: Canvas) {
        field.drawBlkOnField(canvas)
        field.draw(canvas, context)
    }

    // ブロックの位置を更新
    fun updateBlockPos(x: Int, y: Int) {
        val tempX = block.xPos
        val tempY = block.yPos
        block.updatePos(x,y)
        if (field.checkBlockPos(block) == CANTMOVE) {
            block.setPos(tempX, tempY)
            if (y != 0) {
                if (field.addBlock(block) == false) {
                    field.initField()
                }
                makeBlock()
            }
        }
    }

}
/**
 * Block
 */
package euniclus.tetris

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import java.util.*

// ブロックに関する処理をする
class Block(val blockInfo: Array<IntArray>) {

    var xPos = BLOCKSTARTX
    var yPos = BLOCKSTARTY
    var paint: Paint = Paint()

    init {
        paint.style= Paint.Style.FILL
        paint.color= Color.argb(
            255,
            Random().nextInt(200),
            Random().nextInt(200),
            Random().nextInt(200))
    }

    fun draw(canvas: Canvas) {

        for (i in 0..blockInfo.size-1) {
            canvas.drawRect(Rect(
                xPos+blockInfo[i][0],
                yPos+blockInfo[i][1],
                xPos+BLOCKSIZE+blockInfo[i][0],
                yPos+BLOCKSIZE+blockInfo[i][1]),
                paint)
        }
    }

    fun updatePos(x: Int, y:Int) {
        xPos += x
        yPos += y
    }

    fun setPos(x: Int, y: Int) {
        xPos = x
        yPos = y
    }

}
/**
 * Square
 */

package euniclus.tetris

import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect

// フィールドに配置されているブロック
class Square(var xPos: Int, var yPos: Int, var paint: Paint) {

    fun draw(canvas: Canvas) {
        canvas.drawRect(
            Rect(
                xPos,
                yPos,
                xPos+ BLOCKSIZE,
                yPos+BLOCKSIZE),
            paint
        )
    }

}
/**
 * Field
 */

package euniclus.tetris

import android.content.Context
import android.graphics.*
import android.view.WindowManager

// フィールドに関する処理をする
class Field {

    private var paint: Paint = Paint()
    private var blkOnField: Array<Square?> = Array(SQUARENUM, {null})

    // フィールドを表示する
    fun draw(canvas: Canvas, context: Context) {

        val wm: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val disp = wm.defaultDisplay
        val size = Point()
        disp.getSize(size)

        val startX = FIELDSTARTX
        val startY = FIELDSTARTY
        val height = FIELDHEIGHT
        val width = FIELDWIDTH
        val sqSize = SQUARESIZE
        for (i in 0..height/sqSize) {
            paint.style = Paint.Style.STROKE
            paint.color = Color.argb(255, 190,200,255)
            paint.strokeWidth = 10f
            canvas.drawLine(startX, startY+i*sqSize,startX+width,startY+i*sqSize, paint)

        }

        for (i in 0..width/sqSize) {
            paint.style = Paint.Style.STROKE
            paint.color = Color.argb(255, 190,200,255)
            paint.strokeWidth = 10f
            canvas.drawLine(startX+i*sqSize, startY,startX+i*sqSize,startY+height, paint)
        }
    }

    // ブロックの位置からインデックスを生成する
    fun checkBlockPos(block: Block) : Int {
        for (bi in block.blockInfo) {
            if (block.xPos < FIELDSTARTX.toInt()) {
                return CANTMOVE
            }
            val xInd = ( (block.xPos - FIELDSTARTX.toInt() + bi[0]) / SQUARESIZE )
            if (xInd >= FIELDWIDTH / SQUARESIZE) {
                return CANTMOVE
            }

            val yInd = ( (block.yPos - FIELDSTARTY.toInt() + bi[1]) / SQUARESIZE )
            if (yInd < 0) {
                continue
            }
            val index = xInd  + yInd * FIELDWIDTH / SQUARESIZE

            if (index >= SQUARENUM || blkOnField[index] != null) {
                return CANTMOVE
            }
        }
        return CANMOVE
    }

    // フィールドに表示するブロックを追加する
    fun addBlock(block: Block): Boolean {
        for (bi in block.blockInfo) {
            val xInd = ( (block.xPos - FIELDSTARTX.toInt() + bi[0]) / SQUARESIZE )
            val yInd = ( (block.yPos - FIELDSTARTY.toInt() + bi[1]) / SQUARESIZE )

            if (yInd < 0) {
                return false
            }

            val index = xInd  + yInd * FIELDWIDTH / SQUARESIZE

            blkOnField[index] = Square(
                block.xPos+bi[0],
                block.yPos+bi[1],
                block.paint)

        }

        updateBlkOnField()
        return true
    }

    // フィールドに配置されているブロックを表示する
    fun drawBlkOnField(canvas: Canvas) {
        for (blk in blkOnField) {
            if (blk != null) {
                blk.draw(canvas)
            }

        }
    }

    // フィールドに配置されているブロックを更新する
    fun updateBlkOnField() {
        val h = FIELDHEIGHT/SQUARESIZE
        val w = FIELDWIDTH/ SQUARESIZE

        for (i in h-1 downTo 0) {
            var cnt = 0
            for ( j in 0..w-1) {
                if (blkOnField[j+i*w] != null) {
                    cnt += 1
                }
            }
            if (cnt == w) {
                for (k in i-1 downTo 0) {
                    for (l in 0..w-1) {
                        if (blkOnField[k*w+l] != null) {
                            blkOnField[k*w+l]!!.yPos += BLOCKSPACE
                        }

                        blkOnField[(k+1)*w+l] = blkOnField[k*w+l]
                        blkOnField[k*w+l] = null
                    }
                }
            }
        }
    }

    // フィールドの状態を初期化
    fun initField() {
        blkOnField = Array(SQUARENUM, {null})
    }

}
/**
* Const.kt
*/
package euniclus.tetris

/**
 * Block
 */
// 四角形のサイズ
val BLOCKSIZE = 80
// 間隔
val BLOCKSPACE = 20+BLOCKSIZE
// ブロックの表示開始位置 x
val BLOCKSTARTX = 210
// ブロックの表示開始位置 y
val BLOCKSTARTY = 210

// 表示開始位置からのオフセットを配列で持つ
val BlockTypeA = arrayOf(
    intArrayOf(0,0),
    intArrayOf(BLOCKSPACE,0),
    intArrayOf(BLOCKSPACE*2,0),
    intArrayOf(0,-BLOCKSPACE)
)

val BlockTypeB = arrayOf(
    intArrayOf(0,0),
    intArrayOf(0,-BLOCKSPACE),
    intArrayOf(0,-BLOCKSPACE*2),
    intArrayOf(0,-BLOCKSPACE*3)
)

val BlockTypeC = arrayOf(
    intArrayOf(0,0),
    intArrayOf(BLOCKSPACE,0),
    intArrayOf(BLOCKSPACE*2,0),
    intArrayOf(BLOCKSPACE,-BLOCKSPACE)
)

val BlockTypeD = arrayOf(
    intArrayOf(0,0),
    intArrayOf(BLOCKSPACE,0),
    intArrayOf(BLOCKSPACE,-BLOCKSPACE),
    intArrayOf(0,-BLOCKSPACE)
)

/**
 * Field
 */
// 表示開始位置
val FIELDSTARTX = 200f
// 表示開始位置
val FIELDSTARTY= 300f
// フィールドの高さ
val FIELDHEIGHT = 1000
// フィールドの幅
val FIELDWIDTH = 700
//  フィールドのマスの大きさ
val SQUARESIZE = 100
// マスの数
val SQUARENUM = (FIELDHEIGHT/SQUARESIZE) * (FIELDWIDTH/SQUARESIZE)

/**
 Parameter
 */
val CANTMOVE = 0
val CANMOVE = 1

val BLOCKDOWNCNT = 50


改善するべきところを挙げたらきりがないと思いますが、ひとまずこれで完成とします。
ブロックの回転とか絶対必要だと思ったのですが、もう余力がありません。
もともとKotlinの学習のために始めたことなので今はこれで十分かと思います。