## 写在前面
为了应付安卓课的大作业,又写了一个俄罗斯方块。很幸运的是,确实学到了很多知识,~~虽然这些知识可能没什么用~~,但是还是非常的有意思。上一次写俄罗斯方块是高三的时候是在 hp-39gii 图形机上拿着简陋的 hp-basic 写的,最后写出来的结果是这样的
![陪我度过高三的 hp-39gii](https://www.cjovi.icu/usr/uploads/2022/05/2726082610.jpg)
遗憾的是当时的源代码已经丢失了,编译后的字节码[倒还是在](https://github.com/chujDK/TETRIX-HP39GII),重新导入回计算器还能把源码找回来,但是我已经懒得折腾这个了,属于是时代的眼泪了。
话说回来,这学期选了一节安卓移动开发的专业选修课,当时是有打算入门一下安卓,之后浏览器或者内核安全研究不下去了,也可以试试移动安全。不过上了课发现这课主要的可能还是偏向于让我们能写出来一个 app,而不是了解安卓的思想。学到的东西也是 api 怎么用,gui 怎么调。不能说没意思吧,其实做点实际的东西出来还是很有成就感的。期末作业有许多选择,都不太感兴趣。然后正好看到这篇文章:[用Jetpack Compose做一个俄罗斯方块游戏机](https://blog.csdn.net/vitaviva/article/details/115878190),我也不知道 jetpack 或者 compose 是什么,里面还提到了 MVI 架构,都是没接触过的名词,为了学习也好,为了以后吹牛皮也好,为了抄起来方便也好,我就选择了拿 compose 写俄罗斯方块这个作业。真的非常感谢这位作者:),我吹爆!
功能实现方面呢,参考的文章里面只实现了一个很炫酷的游戏界面,课程的要求为了让我们涉及更多的东西,还要求要做到
* 只能有五种方块,这个好解决,生成的时候限制一下范围就行
* 需要能把分数存到数据库里面
* 需要能够按名称和按起止时间搜索记录
* 挂了之后要能播放音乐
首先照着文章抄,把游戏主体逻辑实现了,这里我花的时间最少,(**绝对不是因为有的抄!**),俄罗斯方块这个游戏的特点就是不断地等待用户输入,输入了之后渲染界面(spirit 会持续下落,也可以理解为是用户的输入),这种模型据说非常符号前端的开发思想,数据驱动。
## MVI
这里使用的主要就是 MVI 架构了,即
* **Model**:主要指 UI 的状态。UI 本质就是一堆控件分布在各个位置上。我们稍微抽象一下就可以把 UI 状态存到一个数据类中,我们称之为一个 state。
* **View**:与 MV* 中的 View 一样,指任意一个 Activity、Fragment 等 UI 承载单元。在这个项目中,使用了 compose 这套较新的 API,其自动、智能重组的特性也很适合做 View 层。
* **Intent**:这个 intent 指的是用户的操作的意图(和 Activity 中的那个 Intent 不是一个东西)。把它封装到一个 Action 中再发送给 Model 进行数据请求(一次 reduce 操作)。
这个架构模式满足单向数据流,整个流向如下
![mvi](https://www.cjovi.icu/usr/uploads/2022/05/236817953.png)
也就是说流程为:用户输入封装为 intent 发送给 ViewModel,ViewModel 根据 intent 进行 reduce 更新 state,View 根据 state 刷新 UI,再显示给用户。
抄下一下网络上总结的架构优缺点
优点:
- UI的所有变化来自State,所以只需聚焦State,架构更简单、易于调试
- 数据单向流动,很容易对状态变化进行跟踪和回溯
- state实例都是不可变的,确保线程安全
- UI只是反应State的变化,没有额外逻辑,可以被轻松替换或复用
缺点:
- 所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀
- state是不变的,每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销
- 有些事件类的UI变化不适合用state描述,例如弹出一个 toast 或者 snackbar
安卓为开发者提供了非常多的基础类,让 MVI 的实现变得十分容易,接下来我以此俄罗斯方块项目为例展示一下。
### View
首先来实现 view 层,这里使用 compose 这个函数式、声明式的 api。
先实现一个方块的绘制
```kotlin
// this function can draw a single Brick
// to a single Brick, there is there part
// |--------|
// ||------||
// |||----|||
// ||| |||
// |||----|||
// ||------||
// |--------|
// 1.0 0.8 0.5
// here we should draw the inner two part
// named inner part and outer part
private fun DrawScope.drawBrick(
brickSize : Float,
relativeOffset : Offset,
color : Color
) {
val location = Offset(
relativeOffset.x * brickSize,
relativeOffset.y * brickSize
)
val outerSize = brickSize * 0.8f
val outerOffset = (brickSize - outerSize) / 2f
drawRect(
color = color,
topLeft = location + Offset(outerOffset, outerOffset),
size = Size(outerSize, outerSize),
style = Stroke(outerSize / 10f)
)
val innerSize = brickSize * 0.5f
val innerOffset = (brickSize - innerSize) / 2f
drawRect(
color = color,
topLeft = location + Offset(innerOffset, innerOffset),
size = Size(innerSize, innerSize),
)
}
```
preview 出来看看
```kotlin
@Preview(showBackground = true)
@Composable
fun BrickPreview() {
Canvas(modifier = Modifier.fillMaxSize()) {
drawBrick(size.width, Offset(0f, 0f), Color.Green)
}
}
```
![single brick](https://www.cjovi.icu/usr/uploads/2022/05/4275261622.png)
效果不错。compose 的一大优点就是能够实时预览 UI 形状,效果和真机的差距也不是很大。
然后把整个背景板画出来
```kotlin
fun DrawScope.drawGrid(
blockSize : Float,
gridSize : Pair<Int, Int>
) {
(0 until gridSize.first).forEach { x ->
(0 until gridSize.second).forEach { y ->
drawBrick(
blockSize,
Offset(x.toFloat(), y.toFloat()),
BrickGrid
)
}
}
}
```
preview 一下效果如下
![background](https://www.cjovi.icu/usr/uploads/2022/05/2784982806.png)
然后我们定义所有的下落方块(spirit)的形状,形状通过一个 Offset 链表描述
```kotlin
val SpiritType = listOf(
listOf(Offset(1f, -1f), Offset(1f, 0f), Offset(0f, 0f), Offset(0f, 1f)),//Z
listOf(Offset(0f, -1f), Offset(0f, 0f), Offset(0f, 1f), Offset(0f, 2f)),//I
listOf(Offset(0f, 1f), Offset(0f, 0f), Offset(0f, -1f), Offset(1f, 0f)),//T
listOf(Offset(1f, 0f), Offset(0f, 0f), Offset(1f, -1f), Offset(0f, -1f)),//O
listOf(Offset(1f, -1f), Offset(0f, -1f), Offset(0f, 0f), Offset(0f, 1f)),//J
// here starts the unwanted
listOf(Offset(0f, -1f), Offset(1f, -1f), Offset(1f, 0f), Offset(1f, 1f)),//L
listOf(Offset(0f, -1f), Offset(0f, 0f), Offset(1f, 0f), Offset(1f, 1f)),//S
)
```
课程的作业要求只能有前五种方块,很好解决,生成的时候限制一下随机数的范围即可。然后定义一下他们的颜色
```kotlin
val SpiritColor = listOf(
Color.Blue,
Color.Red,
Color.Yellow,
Color.Green,
Color.Magenta,
Color.Cyan,
Color.Black
)
```
实现 spirit 的绘制
```kotlin
fun DrawScope.drawSpirit(spirit: Spirit, brickSize: Float, gridSize: Pair<Int, Int>) {
clipRect(0f, 0f, gridSize.first * brickSize, gridSize.second * brickSize) {
spirit.location.forEach {
drawBrick(
brickSize,
it,
spirit.color
)
}
}
}
```
遍历一遍链表就可以了。
然后我们把三者结合起来
```kotlin
@Composable
fun GridScreen(modifier: Modifier = Modifier) {
val viewModel = viewModel<GameViewModel>()
val viewState = viewModel.viewState.value
Box(
modifier = modifier
.backgro
妄北y
- 粉丝: 2w+
- 资源: 1万+
最新资源
- 有机硅导热灌封加热台方案sw17可编辑全套技术资料100%好用.zip
- 锂离子电池的充电状态和健康状态估计 石墨-磷酸铁锂电池的循环寿命模型 该项目的目标是设计一个强大的观测器,可以估计锂离子电池的SOC和SOH 在该项目中,等效电路模型用于电池建模,电流和环境温度作为
- Python基于YOLOv5目标检测算法开发的FPS射击类游戏辅助瞄准系统源码+文档说明
- 三菱fx3uPLC和昆仑通泰触摸屏控制松下伺服电机使用例程3,带CAD接线图,带伺服电机使用手册
- 真空干泵驱动用磁阻式同步电动机设计与特性分析 先设计一款真空干泵驱动电机,使其符合真空干泵的驱动要求,各项性能参数(如电机效率、最大转矩倍数、空载气隙磁密等)设计达标 接着在设计好的驱动电机基础上
- MATLAB Simulink仿真,蓄电池SOC均衡(锂电池) 根据微网内功率盈余,两组SOC不同的蓄电池采用分段下垂控制,随着出力的不同SOC趋于一致;同时对直流母线电压进行补偿、功率保持稳定无波动
- 泳池水循环过滤系统sw18可编辑全套技术资料100%好用.zip
- 实训项目-Java-CA数字证书颁发系统+完整功能(增加了文件上传和验证颁发)
- 144区域综合能源系统太阳能消纳能力评估模型,采用distflow二阶锥模型进行配电潮流计算,采用了线性热网能量流模型进行热网潮流计算,光电消纳+光热消纳 改进点:采用结合热网模型的多区域综合能源
- 24-25-1期末模拟试卷1.pdf
- 圆柱磨削及端面加工sw16全套技术资料100%好用.zip
- 载具回流输送机sw16全套技术资料100%好用.zip
- 2006-2020年各省最终消费率(%)数据
- comsol金属贴片,能带计算
- 圆片油槽分选机sw20可编辑全套技术资料100%好用.zip
- 栈板放置机step全套技术资料100%好用.zip
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈