我一直在开发一个 3 级的基本贪吃蛇游戏。其中前两个工作完全正常,但第三个却造成了麻烦。尽管除了一个额外的功能(额外的墙,由 viewModel 的 extraWalls 变量和 walls() 函数实现)之外,所有代码都是相同的,但我不知道为什么这个错误只发生在这个中。
完整项目位于:https://github.com/tauqirnizami/SnakeGame
我的第三(困难)级别的视图模型是:
class DifficultSnakeViewModel : ViewModel() {
/*Pair(length/y-coordinate, width/x-coordinate)*/
var coordinates = mutableStateListOf(Pair(14, 14), Pair(15, 14), Pair(16, 14))
private set
private var gameGoing by mutableStateOf(true)
private val eachExtraWallWidthPlusOne = 4
val extraWalls: List<Pair<Int, Int>> = listOf(Pair(2,3), Pair(3,4), Pair(4,5)) //xtra walls added to snake game's grid to increase difficulty. This is the one causing problem.
init {
foodCoordinates = Pair(10, 9)
score = 0L
directions =
mutableStateListOf(0) //Using a list instead of a single int to keep track when user changes directions too quickly while the viewModel is on delay
giantFoodCoordinates = null
giantFoodCounter = 1
viewModelScope.launch(Dispatchers.Main) {
while (gameGoing) {
delay(if (score < 333) 500 - (score * 1.5).toLong() else 0) //This controls the snake speed
coordinatesUpdation()
}
}
}
private suspend fun coordinatesUpdation() {
// This code was working fine with other 2 levels
}
private fun food(otherFood: Pair<Int, Int>?): Pair<Int, Int> {
//code to generate food coordinates. Working fine (at least in other 2 levels)
}
private fun walls(): List<Pair<Int, Int>> {
val walls = mutableListOf<Pair<Int, Int>>()
val uniquePositions = mutableSetOf<Pair<Int, Int>>()
while (uniquePositions.size < 9) {
var position: Pair<Int, Int>
do {
position = Pair((2 until gridLength).random(), (2 until gridWidth - eachExtraWallWidthPlusOne).random())
} while (coordinates.contains(position) || position == foodCoordinates || position == Pair(coordinates[0].first + 1, coordinates[0].second))
walls.add(position)
uniquePositions.add(position)
repeat(2) {
position = Pair(position.first, position.second + 1)
walls.add(position)
uniquePositions.add(position)
}
}
return uniquePositions.toList()
}
}
我的可组合文件是:
@Composable
fun DifficultDisplayGrid(
modifier: Modifier,
snakeViewModel: DifficultSnakeViewModel
) {
val colors = difficultGenerateColorGrid(coordinates = snakeViewModel.coordinates, extraWalls = snakeViewModel.extraWalls)
Column {
ColorGrid(colors = colors, modifier = modifier)
Text(text = "Score = $score")
}
}
@Composable
fun DifficultScreen(
modifier: Modifier = Modifier,
snakeViewModel: DifficultSnakeViewModel = DifficultSnakeViewModel()
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
) {
DifficultDisplayGrid(modifier, snakeViewModel = snakeViewModel)
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Button(onClick = { directions.add(2) }) {
Icon(imageVector = Icons.Filled.KeyboardArrowLeft, contentDescription = "Left")
}
//other buttons
}
}
//other code
出现的问题是,在这个(困难)关卡中,当我运行程序时,墙壁(extraWalls变量)不断变化,而蛇的坐标保持不变。坐标更新功能与中级和简单级完全相同,都运行得很好。
我认为错误可能是因为在访问变量时,walls() 可能会被调用,所以我尝试将值存储在单独的数据类中,将数据类的变量值分配给另一个变量并使用该变量的值由可组合项检索。正如所料,这也根本不起作用!
Ps:我知道墙壁()函数并不完美,墙壁有时可能会与蛇或食物重叠,但这是次要问题。
正如有些人问的,这里是最小可执行示例: 查看模型:
const val gridLength = 21
const val gridWidth = 15
val cellSize = 17.dp
class ExampleViewModel : ViewModel() {
var coordinates = mutableStateListOf(Pair(14, 14), Pair(15, 14), Pair(16, 14))
private set
private var gameGoing by mutableStateOf(true)
private val eachExtraWallWidthPlusOne = 4
var extraWalls: List<Pair<Int, Int>> = walls()
private set
init {
viewModelScope.launch(Dispatchers.Main) {
while (gameGoing) {
delay(500) //This controls the snake speed
coordinatesUpdation()
}
}
}
private fun coordinatesUpdation() {
val head: Pair<Int, Int> = coordinates[0]
val newHead = Pair(head.first + 1, head.second)
coordinates.add(0, newHead)
coordinates.removeLast()
}
private fun walls(): List<Pair<Int, Int>> {
val walls = mutableListOf<Pair<Int, Int>>()
val uniquePositions = mutableSetOf<Pair<Int, Int>>()
while (uniquePositions.size < 9) {
var position: Pair<Int, Int>
do {
position = Pair(
(2 until gridLength).random(),
(2 until gridWidth - eachExtraWallWidthPlusOne).random()
)
} while (coordinates.any { it == position })
walls.add(position)
uniquePositions.add(position)
repeat(2) {
position = Pair(position.first, position.second + 1)
walls.add(position)
uniquePositions.add(position)
}
}
return uniquePositions.toList()
}
}
可组合:
@Composable
fun Display(
exampleViewModel: ExampleViewModel,
modifier: Modifier = Modifier
) {
val colors = difficultGenerateColorGrid(
coordinates = exampleViewModel.coordinates,
extraWalls = exampleViewModel.extraWalls
)
ColorGrid(colors = colors, modifier = modifier)
}
@Composable
fun ExampleScreen(
exampleViewModel: ExampleViewModel = ExampleViewModel(),
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
Display(exampleViewModel = exampleViewModel, modifier = modifier)
Button(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Filled.KeyboardArrowUp, contentDescription = "Up")
}
}
}
fun difficultGenerateColorGrid(
coordinates: List<Pair<Int, Int>>,
extraWalls: List<Pair<Int, Int>>
): List<Color> {
val coloursList: MutableList<Color> = mutableListOf()
for (i in 1..gridLength) {
for (j in 1..gridWidth) {
if (i == 1 || j == 1 || i == gridLength || j == gridWidth/*side walls, no worries*/ || extraWalls.any { it == Pair(i, j) }/*EXTRA WALLS*/) {
coloursList.add(Color.Red)
} else if (coordinates.any { it == Pair(i, j) }) {
coloursList.add(Color.DarkGray)
} else {
coloursList.add(Color.White)
}
}
}
return coloursList
}
@Composable
fun ColorGrid(colors: List<Color>, modifier: Modifier = Modifier) {
LazyVerticalGrid(
columns = GridCells.Fixed(gridWidth),
modifier = modifier
.size(gridWidth * cellSize, gridLength * cellSize)
) {
items(colors.size) { index ->
Box(
modifier = Modifier
.background(colors[index])
.size(cellSize)
)
}
}
}
这是由您检索视图模型实例的方式引起的:
exampleViewModel: ExampleViewModel = ExampleViewModel(),
每次重组时都会再次执行并创建
ExampleViewModel
的新实例。视图模型的 extraWalls
属性使用 walls()
进行初始化,这会创建一些随机墙。
因此,每当实例化视图模型时,您都会得到新的随机墙。由于您在每次重组时都会创建视图模型的新实例,因此每当 UI 发生任何变化时,您都会获得新的墙。
这不是视图模型的使用方式。相反,视图模型应该绑定到 ViewModelStoreOwner,并且每次该所有者请求视图模型时,它都应该获得 same 实例。
viewModel()
函数已经方便地内置了所有这些功能,因此您可以将代码更改为:
exampleViewModel: ExampleViewModel = viewModel(),