package com.example.tetrisnoactivity.constants
enum class CellConstants(val value: Byte) {
EMPTY(0), EPHEMERAL(1)
}
package com.example.tetrisnoactivity.constants
enum class FieldConstants(val value: Int) {
COLUMN_COUNT(10), ROW_COUNT(20);
}
package com.example.tetrisnoactivity.models;
import android.graphics.Color;
import android.graphics.Point;
import androidx.annotation.NonNull;
import com.example.tetrisnoactivity.constants.FieldConstants;
import java.util.Random;
public class Block {
private int shapeIndex;
private int frameNumber;
private BlockColor color;
private Point position;
private Block(int shapeIndex, BlockColor blockColor) {
this.frameNumber = 0;
this.shapeIndex = shapeIndex;
this.color = blockColor;
this.position = new Point(FieldConstants.COLUMN_COUNT.getValue()/2, 0);
}
public static Block createBlock() {
Random random = new Random();
int shapeIndex = random.nextInt(Shape.values().length);
BlockColor blockColor = BlockColor.values()
[random.nextInt(BlockColor.values().length)];
Block block = new Block(shapeIndex, blockColor);
block.position.x = block.position.x - Shape.values()
[shapeIndex].getStartPosition();
return block;
}
public static int getColor(byte value) {
for (BlockColor colour : BlockColor.values()) {
if (value == colour.byteValue) {
return colour.rgbValue;
}
}
return -1;
}
public final void setState(int frame, Point position) {
this.frameNumber = frame;
this.position = position;
}
@NonNull
public final byte[][] getShape(int frameNumber) {
return Shape.values()[shapeIndex].getFrame(frameNumber).as2dByteArray();
}
public Point getPosition() {
return this.position;
}
public final int getFrameCount() {
return Shape.values()[shapeIndex].getFrameCount();
}
public int getFrameNumber() {
return frameNumber;
}
public int getColor() {
return color.rgbValue;
}
public byte getStaticValue() {
return color.byteValue;
}
public enum BlockColor {
PINK(Color.rgb(255,105,180), (byte) 2),
GREEN(Color.rgb(0,128,0), (byte) 3),
ORANGE(Color.rgb(255,149,0), (byte) 4),
YELLOW(Color.rgb(255,255,0), (byte) 5),
CYAN(Color.rgb(0,255,255), (byte) 6);
BlockColor(int rgbValue, byte value) {
this.rgbValue = rgbValue;
this.byteValue = value;
}
private final int rgbValue;
private final byte byteValue;
}
}
package com.example.tetrisnoactivity.models
import com.example.tetrisnoactivity.helpers.array2dOfByte
class Frame (private val width: Int){
val data: ArrayList<ByteArray> = ArrayList()
fun addRow(byteStr: String): Frame {
val row = ByteArray(byteStr.length)
for (idx in byteStr.indices) {
row[idx] = "${byteStr[idx]}".toByte()
}
data.add(row)
return this
}
fun as2dByteArray(): Array<ByteArray> {
val bytes = array2dOfByte(data.size, width)
return data.toArray(bytes)
}
}
package com.example.tetrisnoactivity.models
enum class Shape(val frameCount: Int, val startPosition: Int) {
Tetromino1(1,1) {
override fun getFrame(frameNumber: Int): Frame {
return Frame(2)
.addRow("11")
.addRow("11")
}
},
Tetromino2(2,1) {
override fun getFrame(frameNumber: Int): Frame {
return when (frameNumber) {
0 -> Frame(3)
.addRow("110")
.addRow("011")
1 -> Frame(2)
.addRow("01")
.addRow("11")
.addRow("10")
else -> throw IllegalArgumentException("$frameNumber is an invalid frame number.")
}
}
},
Tetromino3(2,1) {
override fun getFrame(frameNumber: Int): Frame {
return when (frameNumber) {
0 -> Frame(3)
.addRow("011")
.addRow("110")
1 -> Frame(2)
.addRow("10")
.addRow("11")
.addRow("01")
else -> throw IllegalArgumentException("$frameNumber is an invalid frame number.")
}
}
},
Tetromino4(2,2) {
override fun getFrame(frameNumber: Int): Frame {
return when (frameNumber) {
0 -> Frame(4).addRow("1111")
1 -> Frame(1)
.addRow("1")
.addRow("1")
.addRow("1")
.addRow("1")
else -> throw IllegalArgumentException("$frameNumber is an invalid frame number.")
}
}
},
Tetromino5(4,1) {
override fun getFrame(frameNumber: Int): Frame {
return when(frameNumber) {
0 -> Frame(3)
.addRow("010")
.addRow("111")
1 -> Frame(2)
.addRow("10")
.addRow("11")
.addRow("10")
2 -> Frame(3)
.addRow("111")
.addRow("010")
3 -> Frame(2)
.addRow("01")
.addRow("11")
.addRow("01")
else -> throw IllegalArgumentException("$frameNumber is an ivalid frame number.")
}
}
},
Tetromino6(4,1) {
override fun getFrame(frameNumber: Int): Frame {
return when (frameNumber) {
0 -> Frame(3)
.addRow("100")
.addRow("111")
1 -> Frame(2)
.addRow("11")
.addRow("10")
.addRow("10")
2 -> Frame(3)
.addRow("111")
.addRow("001")
3 -> Frame(2)
.addRow("01")
.addRow("01")
.addRow("11")
else -> throw IllegalArgumentException("$frameNumber is an invalid frame number.")
}
}
},
Tetromino7(4,1) {
override fun getFrame(frameNumber: Int): Frame {
return when (frameNumber) {
0 -> Frame(3)
.addRow("001")
.addRow("111")
1 -> Frame(2)
.addRow("10")
.addRow("10")
.addRow("11")
2 -> Frame(3)
.addRow("111")
.addRow("100")
3 -> Frame(2)
.addRow("11")
.addRow("01")
.addRow("01")
else -> throw IllegalArgumentException("$frameNumber is an invalid frame number.")
}
}
};
abstract fun getFrame(FrameNumber: Int): Frame
}
package com.example.tetrisnoactivity
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.tetrisnoactivity.models.AppModel
import com.example.tetrisnoactivity.storage.AppPreferences
import com.example.tetrisnoactivity.view.TetrisView
class GameActivity : AppCompatActivity() {
var tvHighScore: TextView? = null
var tvCurrentScore: TextView? = null
var appPreferences: AppPreferences? = null
private lateinit var tetrisView: TetrisView
private val appModel: AppModel = AppModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
appPreferences = AppPreferences(this)
val btnRestart = findViewById<Button>(R.id.btn_restart)
tvHighScore = findViewById<TextView>(R.id.tv_high_score)
tvCurrentScore = findViewById<TextView>(R.id.tv_current_score)
tetrisView = findViewById<TetrisView>(R.id.view_tetris)
tetrisView.setActivity(this)
tetrisView.setModel(appModel)
tetrisView.setOnTouchListener(this::onTetrisViewTouch)
btnRestart.setOnClickListener(this::btnRestartClick)
updateHighScore()
updateCurrentScore()
}
private fun btnRestartClick(view: View) {
appModel.restartGame()
}
private fun onTetrisViewTouch(view:View, event: MotionEvent): Boolean {
if (appModel.isGameOver() || appModel.isGameAwaitingStart()) {
appModel.startGame()
tetrisView.setGameCommandWithDelay(AppModel.Motions.DOWN)
} else if (appModel.isGameActive()) {
when (resolveTouchDirection(view, event)) {
0 -> moveTetromino(AppModel.Motions.LEFT)
1 -> moveTetromino(AppModel.Motions.ROTATE)
2 -> moveTetromino(AppModel.Motions.DOWN)
3 -> moveTetromino(AppModel.Motions.RIGHT)
}
}
return true
}
private fun resolveTouchDirection(view: View, event: MotionEvent): Int {
val x = event.x / view.width
val y = event.y / view.height
val direction: Int
direction = if (y > x) {
if (x > 1 -y) 2 else 0
} else {
if (x > 1 - y) 3 else 1
}
return direction
}
private fun moveTetromino(motion: AppModel.Motions) {
if (appModel.isGameActive()) {
tetrisView.setGameCommand(motion)
}
}
private fun updateHighScore() {
tvHighScore?.text = "${appPreferences?.getHighScore()}"
}
private fun updateCurrentScore() {
tvCurrentScore?.text = "0"
}
}
package com.example.tetrisnoactivity.storage
import android.content.Context
import android.content.SharedPreferences
class AppPreferences(ctx: Context) {
var data: SharedPreferences = ctx.getSharedPreferences(
"APP_PREFERENCES", Context.MODE_PRIVATE
)
fun saveHighScore(highScore: Int) {
data.edit().putInt("HIGH_SCORE", highScore).apply()
}
fun getHighScore(): Int{
return data.getInt("HIGH_SCORE", 0)
}
fun clearHighScore() {
data.edit().putInt("HIGH_SCORE", 0).apply()
}
}
код TetrisView:
<code>
package com.example.tetrisnoactivity.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.os.Handler
import android.os.Message
import android.util.AttributeSet
import android.view.View
import android.widget.Toast
//import androidx.annotation.Dimension
import com.example.tetrisnoactivity.GameActivity
import com.example.tetrisnoactivity.constants.CellConstants
import com.example.tetrisnoactivity.constants.FieldConstants
import com.example.tetrisnoactivity.models.AppModel
import com.example.tetrisnoactivity.models.Block
class TetrisView : View {
private val paint = Paint()
private var lastMove: Long = 0
private var model: AppModel? = null
private var activity: GameActivity? = null
private val viewHandler = ViewHandler(this)
private var cellSize: Dimension = Dimension(0,0)
private var frameOffset: Dimension = Dimension(0,0)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int): super(context, attrs, defStyle)
companion object {
private val DELAY = 500
private val BLOCK_OFFSET = 2
private val FRAME_OFFSET_BASE = 10
}
private class ViewHandler(private val owner: TetrisView): Handler() {
override fun handleMessage(msg: Message) {
if (msg.what == 0) {
if (owner.model != null) {
if (owner.model!!.isGameOver()) {
owner.model?.endGame()
Toast.makeText(owner.activity, "Game Over", Toast.LENGTH_LONG).show();
}
if (owner.model!!.isGameActive()) {
owner.setGameCommandWithDelay(AppModel.Motions.DOWN)
}
}
}
}
fun sleep(delay: Long) {
this.removeMessages(0)
sendMessageDelayed(obtainMessage(0), delay)
}
}
private data class Dimension(val width: Int, val height: Int)
fun setModel(model: AppModel) {
this.model = model
}
fun setActivity(gameActivity: GameActivity) {
this.activity = gameActivity
}
fun setGameCommand(move: AppModel.Motions) {
if ((model != null) && (model?.currentState == AppModel.Statuses.ACTIVE.name)) {
if (AppModel.Motions.DOWN == move) {
model?.generateField(move.name)
invalidate()
return
}
setGameCommandWithDelay(move)
}
}
fun setGameCommandWithDelay(move: AppModel.Motions) {
val now = System.currentTimeMillis()
if (now - lastMove > DELAY) {
model?.generateField(move.name)
invalidate()
lastMove = now
}
updateScores()
viewHandler.sleep(DELAY.toLong())
}
private fun updateScores() {
activity?.tvCurrentScore?.text = "${model?.score}"
activity?.tvHighScore?.text = "${activity?.appPreferences?.getHighScore()}"
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawFrame(canvas)
if (model != null) {
for (i in 0 until FieldConstants.ROW_COUNT.value) {
for(j in 0 until FieldConstants.COLUMN_COUNT.value) {
drawCell(canvas, i, j)
}
}
}
}
private fun drawFrame(canvas: Canvas) {
paint.color = Color.LTGRAY
canvas.drawRect(frameOffset.width.toFloat(),
frameOffset.height.toFloat(),
width - frameOffset.width.toFloat(),
height - frameOffset.height.toFloat(),
paint)
}
private fun drawCell(canvas: Canvas, row: Int, col: Int) {
val cellStatus = model?.getCellStatus(row,col)
if (CellConstants.EMPTY.value != cellStatus) {
val color = if (CellConstants.EPHEMERAL.value == cellStatus) {
model?.currentBlock?.color
} else {
Block.getColor(cellStatus as Byte)
}
drawCell(canvas,col,row, color as Int)
}
}
private fun drawCell(canvas: Canvas,x: Int, y: Int, rgbColor: Int) {
paint.color = rgbColor
val top: Float = (frameOffset.height + y * cellSize.height + BLOCK_OFFSET).toFloat()
val left: Float = (frameOffset.width + x * cellSize.width + BLOCK_OFFSET).toFloat()
val bottom: Float = (frameOffset.height + (y + 1) * cellSize.height - BLOCK_OFFSET).toFloat()
val right: Float = (frameOffset.width + (x + 1) * cellSize.width - BLOCK_OFFSET).toFloat()
val rectangle = RectF(left, top, right, bottom)
canvas.drawRoundRect(rectangle, 4F, 4F, paint)
}
override fun onSizeChanged(width: Int, height: Int, previousWidth: Int, previousHeight: Int) {
super.onSizeChanged(width, height, previousWidth, previousHeight)
val cellWidth = (width - 2 * FRAME_OFFSET_BASE) / FieldConstants.COLUMN_COUNT.value
val cellHeight = (height - 2 * FRAME_OFFSET_BASE) / FieldConstants.ROW_COUNT.value
val n = Math.min(cellWidth, cellHeight)
this.cellSize = Dimension(n, n)
val offsetX = (width - FieldConstants.COLUMN_COUNT.value * n) / 2
val offsetY = (height - FieldConstants.ROW_COUNT.value * n) / 2
this.frameOffset = Dimension(offsetX, offsetY)
}
}
</code>