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の学習のために始めたことなので今はこれで十分かと思います。