Kotlinのサンプルを紹介します ④
前回まででブロックの積み上げまで実装できました。
今回でやっと完成です。
配置されているブロックの更新をとゲームの終了処理を実装します。
ブロックの更新
ブロックがフィールドに追加されたときに、一行すべてにブロックが配置されているかを判定します。
その場合、その行よりも上のブロックをすべて一行下にずらします。
Fieldクラスに以下のメソッドを追加します。
正直これに関してはどういう方法が正しいかはわかりませんが素直に実装した結果こうなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// フィールドに配置されているブロックを更新する 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未満)だった場合終了と判断します。
そのあとフィールドの状態を初期化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// フィールドに表示するブロックを追加する 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 } |
そして初期化するメソッドです。
1 2 3 4 |
// フィールドの状態を初期化 fun initField() { blkOnField = Array(SQUARENUM, {null}) } |
そしてGraphicクラスのupdateBlockPosメソッドを以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// ブロックの位置を更新 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の場合(終了の場合)、フィールドを初期化します。
実行したらこうなりました。
ブロックが詰めなくなると確かにリセットされてますね。

ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * 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 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
/** * 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() } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/** * 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 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** * 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 ) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
/** * 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}) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
/** * 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の学習のために始めたことなので今はこれで十分かと思います。