## 写在前面
为了应付安卓课的大作业,又写了一个俄罗斯方块。很幸运的是,确实学到了很多知识,~~虽然这些知识可能没什么用~~,但是还是非常的有意思。上一次写俄罗斯方块是高三的时候是在 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
没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
收起资源包目录
安卓移动开发课程大作业 compose 实现的俄罗斯方块.zip (54个子文件)
compose-a-tetris-main
gradle.properties 1KB
gradle
wrapper
gradle-wrapper.jar 58KB
gradle-wrapper.properties 232B
app
src
androidTest
java
com
chuj
compose_a_tetris
ExampleInstrumentedTest.kt 677B
test
java
com
chuj
compose_a_tetris
ExampleUnitTest.kt 349B
main
java
com
chuj
compose_a_tetris
MainActivity.kt 3KB
Utils.kt 33B
ScoreActivity.kt 14KB
GameActivity.kt 4KB
ui
Buttons.kt 7KB
GridScreen.kt 8KB
UIConfig.kt 309B
Brick.kt 4KB
theme
Type.kt 994B
Theme.kt 2KB
Color.kt 289B
logic
ScoreContract.kt 400B
ViewState.kt 2KB
GameViewModel.kt 9KB
LogicConfig.kt 100B
ScoreDBHelper.kt 4KB
res
mipmap-xxhdpi
ic_launcher_round.webp 6KB
icon.png 16KB
ic_launcher.webp 3KB
mipmap-hdpi
ic_launcher_round.webp 3KB
ic_launcher.webp 1KB
drawable-v24
ic_launcher_foreground.xml 2KB
mipmap-anydpi-v26
ic_launcher.xml 272B
ic_launcher_round.xml 272B
mipmap-mdpi
ic_launcher_round.webp 2KB
ic_launcher.webp 982B
mipmap-xxxhdpi
ic_launcher_round.webp 8KB
ic_launcher.webp 4KB
mipmap-xhdpi
ic_launcher_round.webp 4KB
ic_launcher.webp 2KB
xml
data_extraction_rules.xml 551B
backup_rules.xml 478B
values
colors.xml 378B
strings.xml 1KB
themes.xml 158B
drawable
ic_launcher_background.xml 5KB
raw
on_lost_animation_music.mp3 56KB
AndroidManifest.xml 1KB
proguard-rules.pro 750B
build.gradle 3KB
release
compose_a_tetris.apk 10.37MB
output-metadata.json 379B
.gitignore 6B
gradlew.bat 3KB
build.gradle 430B
settings.gradle 333B
gradlew 6KB
.gitignore 225B
README.md 38KB
共 54 条
- 1
资源评论
日刷百题
- 粉丝: 5375
- 资源: 951
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 基于Typescript的兔子饭店经营类游戏源码设计免费送cocoscreator
- 基于Java的web快速开发数据权限管理脚手架wonder-server设计源码
- 基于Apache Log4cxx的C++日志库设计源码
- 基于Vue3的likeadmin免费任意商用管理后台设计源码
- 基于JavaScript的Chrome扩展WeNote分享插件设计源码
- 基于C++的中泰EM9108S动态库开发示例源码
- gxlx2-p291-1g.dts和gxlx2-p291-1g.dtb
- STM32WBxx Keil芯片包
- 基于CNN+Bi-LSTM+Attention 的自动对对联系统
- 基于Java的IndexBar Android字母索引栏设计源码
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功