- Теперь можно убивать клетки за пределами поля

- И эксперименты теперь более глубокие и отражают более реальные значения за счёт кол-ва ранов 100 на каждую плотность (шаг 0.05)
This commit is contained in:
2026-05-15 01:53:47 +03:00
parent 7611276190
commit 5f4443b96d
5 changed files with 178 additions and 118 deletions

View File

@@ -9,7 +9,8 @@ data class AppConfig(
val gridHeight: Int = 50, val gridHeight: Int = 50,
val simulationSpeed: Int = 10, val simulationSpeed: Int = 10,
val probability: Double = 0.5, val probability: Double = 0.5,
val oneDimensionalRule: OneDimensionalRule = OneDimensionalRule.RULE_30 val oneDimensionalRule: OneDimensionalRule = OneDimensionalRule.RULE_30,
val killAtBoundary: Boolean = true
) )
@Serializable @Serializable

View File

@@ -2,7 +2,8 @@ package model
class GameOfLife( class GameOfLife(
val width: Int, val width: Int,
val height: Int val height: Int,
val killAtBoundary: Boolean = true
) { ) {
private var grid: Array<Array<Boolean>> = Array(height) { Array(width) { false } } private var grid: Array<Array<Boolean>> = Array(height) { Array(width) { false } }
private var generation: Int = 0 private var generation: Int = 0
@@ -56,11 +57,20 @@ class GameOfLife(
for (dy in -1..1) { for (dy in -1..1) {
for (dx in -1..1) { for (dx in -1..1) {
if (dx == 0 && dy == 0) continue if (dx == 0 && dy == 0) continue
if (killAtBoundary) {
val nx = x + dx
val ny = y + dy
if (nx in 0 until width && ny in 0 until height && grid[ny][nx]) {
count++
}
} else {
val nx = (x + dx + width) % width val nx = (x + dx + width) % width
val ny = (y + dy + height) % height val ny = (y + dy + height) % height
if (grid[ny][nx]) count++ if (grid[ny][nx]) count++
} }
} }
}
return count return count
} }

View File

@@ -7,6 +7,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -22,7 +23,7 @@ fun Sidebar(
config: AppConfig, config: AppConfig,
onConfigChange: (AppConfig) -> Unit, onConfigChange: (AppConfig) -> Unit,
onRandomize: () -> Unit = {}, onRandomize: () -> Unit = {},
onClear: () -> Unit = {} onClear: () -> Unit = {},
) { ) {
var isRunning by remember { mutableStateOf(false) } var isRunning by remember { mutableStateOf(false) }
var showExperimentResults by remember { mutableStateOf(false) } var showExperimentResults by remember { mutableStateOf(false) }
@@ -31,17 +32,18 @@ fun Sidebar(
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
Column( Column(
modifier = Modifier modifier =
Modifier
.width(280.dp) .width(280.dp)
.fillMaxHeight() .fillMaxHeight()
.padding(16.dp) .padding(16.dp)
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Text( Text(
"Настройки", "Настройки",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = primaryColor color = primaryColor,
) )
HorizontalDivider(color = primaryColor.copy(alpha = 0.3f)) HorizontalDivider(color = primaryColor.copy(alpha = 0.3f))
@@ -49,22 +51,22 @@ fun Sidebar(
Text( Text(
"Режим автомата", "Режим автомата",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = primaryColor color = primaryColor,
) )
SelectAutomatonMode( SelectAutomatonMode(
mode = config.mode, mode = config.mode,
onModeChange = { onConfigChange(config.copy(mode = it)) } onModeChange = { onConfigChange(config.copy(mode = it)) },
) )
if (config.mode == AutomatonMode.ONE_DIMENSIONAL) { if (config.mode == AutomatonMode.ONE_DIMENSIONAL) {
Text( Text(
"Правило", "Правило",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = primaryColor color = primaryColor,
) )
SelectRule( SelectRule(
rule = config.oneDimensionalRule, rule = config.oneDimensionalRule,
onRuleChange = { onConfigChange(config.copy(oneDimensionalRule = it)) } onRuleChange = { onConfigChange(config.copy(oneDimensionalRule = it)) },
) )
} }
@@ -73,13 +75,13 @@ fun Sidebar(
Text( Text(
"Размер поля", "Размер поля",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = primaryColor color = primaryColor,
) )
if (config.mode == AutomatonMode.GAME_OF_LIFE) { if (config.mode == AutomatonMode.GAME_OF_LIFE) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically,
) { ) {
OutlinedTextField( OutlinedTextField(
value = config.gridWidth.toString(), value = config.gridWidth.toString(),
@@ -90,13 +92,14 @@ fun Sidebar(
label = { Text("Ширина", color = primaryColor) }, label = { Text("Ширина", color = primaryColor) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
singleLine = true, singleLine = true,
colors = OutlinedTextFieldDefaults.colors( colors =
OutlinedTextFieldDefaults.colors(
focusedTextColor = primaryColor, focusedTextColor = primaryColor,
unfocusedTextColor = primaryColor, unfocusedTextColor = primaryColor,
focusedBorderColor = primaryColor, focusedBorderColor = primaryColor,
unfocusedBorderColor = primaryColor.copy(alpha = 0.5f), unfocusedBorderColor = primaryColor.copy(alpha = 0.5f),
cursorColor = primaryColor cursorColor = primaryColor,
) ),
) )
OutlinedTextField( OutlinedTextField(
value = config.gridHeight.toString(), value = config.gridHeight.toString(),
@@ -107,13 +110,14 @@ fun Sidebar(
label = { Text("Высота", color = primaryColor) }, label = { Text("Высота", color = primaryColor) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
singleLine = true, singleLine = true,
colors = OutlinedTextFieldDefaults.colors( colors =
OutlinedTextFieldDefaults.colors(
focusedTextColor = primaryColor, focusedTextColor = primaryColor,
unfocusedTextColor = primaryColor, unfocusedTextColor = primaryColor,
focusedBorderColor = primaryColor, focusedBorderColor = primaryColor,
unfocusedBorderColor = primaryColor.copy(alpha = 0.5f), unfocusedBorderColor = primaryColor.copy(alpha = 0.5f),
cursorColor = primaryColor cursorColor = primaryColor,
) ),
) )
} }
} else { } else {
@@ -126,13 +130,14 @@ fun Sidebar(
label = { Text("Ширина (ячеек)", color = primaryColor) }, label = { Text("Ширина (ячеек)", color = primaryColor) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLine = true, singleLine = true,
colors = OutlinedTextFieldDefaults.colors( colors =
OutlinedTextFieldDefaults.colors(
focusedTextColor = primaryColor, focusedTextColor = primaryColor,
unfocusedTextColor = primaryColor, unfocusedTextColor = primaryColor,
focusedBorderColor = primaryColor, focusedBorderColor = primaryColor,
unfocusedBorderColor = primaryColor.copy(alpha = 0.5f), unfocusedBorderColor = primaryColor.copy(alpha = 0.5f),
cursorColor = primaryColor cursorColor = primaryColor,
) ),
) )
} }
@@ -141,7 +146,7 @@ fun Sidebar(
Text( Text(
"Скорость симуляции", "Скорость симуляции",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = primaryColor color = primaryColor,
) )
Slider( Slider(
value = config.simulationSpeed.toFloat(), value = config.simulationSpeed.toFloat(),
@@ -149,16 +154,17 @@ fun Sidebar(
valueRange = 1f..60f, valueRange = 1f..60f,
steps = 58, steps = 58,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = SliderDefaults.colors( colors =
SliderDefaults.colors(
thumbColor = primaryColor, thumbColor = primaryColor,
activeTrackColor = primaryColor, activeTrackColor = primaryColor,
inactiveTrackColor = primaryColor.copy(alpha = 0.3f) inactiveTrackColor = primaryColor.copy(alpha = 0.3f),
) ),
) )
Text( Text(
"${config.simulationSpeed} FPS", "${config.simulationSpeed} FPS",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = primaryColor color = primaryColor,
) )
HorizontalDivider(color = primaryColor.copy(alpha = 0.3f)) HorizontalDivider(color = primaryColor.copy(alpha = 0.3f))
@@ -167,23 +173,55 @@ fun Sidebar(
Text( Text(
"Вероятность заполнения", "Вероятность заполнения",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = primaryColor color = primaryColor,
) )
Slider( Slider(
value = config.probability.toFloat(), value = config.probability.toFloat(),
onValueChange = { onConfigChange(config.copy(probability = it.toDouble())) }, onValueChange = { onConfigChange(config.copy(probability = it.toDouble())) },
valueRange = 0.0f..1.0f, valueRange = 0.0f..1.0f,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = SliderDefaults.colors( colors =
SliderDefaults.colors(
thumbColor = primaryColor, thumbColor = primaryColor,
activeTrackColor = primaryColor, activeTrackColor = primaryColor,
inactiveTrackColor = primaryColor.copy(alpha = 0.3f) inactiveTrackColor = primaryColor.copy(alpha = 0.3f),
) ),
) )
Text( Text(
"${(config.probability * 100).toInt()}%", "${(config.probability * 100).toInt()}%",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = primaryColor color = primaryColor,
)
HorizontalDivider(color = primaryColor.copy(alpha = 0.3f))
Text(
"Граничные условия",
style = MaterialTheme.typography.labelMedium,
color = primaryColor,
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = config.killAtBoundary,
onCheckedChange = { onConfigChange(config.copy(killAtBoundary = it)) },
colors = CheckboxDefaults.colors(
checkedColor = primaryColor,
uncheckedColor = primaryColor,
checkmarkColor = Color.Black
)
)
Text(
"Убивать клетки на границе",
color = primaryColor,
style = MaterialTheme.typography.bodySmall
)
}
Text(
if (config.killAtBoundary) "Клетки за границей мертвы" else "Поле замыкается (тороид)",
color = primaryColor.copy(alpha = 0.7f),
style = MaterialTheme.typography.bodySmall
) )
} }
@@ -191,15 +229,16 @@ fun Sidebar(
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Button( Button(
onClick = { isRunning = !isRunning }, onClick = { isRunning = !isRunning },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors( colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary,
) ),
) { ) {
Text(if (isRunning) "Стоп" else "Старт") Text(if (isRunning) "Стоп" else "Старт")
} }
@@ -207,9 +246,10 @@ fun Sidebar(
OutlinedButton( OutlinedButton(
onClick = onClear, onClick = onClear,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
colors = ButtonDefaults.outlinedButtonColors( colors =
contentColor = primaryColor ButtonDefaults.outlinedButtonColors(
) contentColor = primaryColor,
),
) { ) {
Text("Сброс") Text("Сброс")
} }
@@ -219,10 +259,11 @@ fun Sidebar(
Button( Button(
onClick = onRandomize, onClick = onRandomize,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors( colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary,
) ),
) { ) {
Text("Случайное заполнение") Text("Случайное заполнение")
} }
@@ -234,7 +275,7 @@ fun Sidebar(
Text( Text(
"Эксперименты", "Эксперименты",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = primaryColor color = primaryColor,
) )
Button( Button(
@@ -251,10 +292,11 @@ fun Sidebar(
} }
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors( colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary,
) ),
) { ) {
Text("Запустить эксперименты") Text("Запустить эксперименты")
} }
@@ -263,7 +305,7 @@ fun Sidebar(
Text( Text(
experimentProgress, experimentProgress,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = primaryColor color = primaryColor,
) )
} }
@@ -273,9 +315,10 @@ fun Sidebar(
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = false, enabled = false,
colors = ButtonDefaults.outlinedButtonColors( colors =
contentColor = primaryColor.copy(alpha = 0.5f) ButtonDefaults.outlinedButtonColors(
) contentColor = primaryColor.copy(alpha = 0.5f),
),
) { ) {
Text("Загрузить паттерн") Text("Загрузить паттерн")
} }
@@ -286,9 +329,10 @@ fun Sidebar(
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = false, enabled = false,
colors = ButtonDefaults.outlinedButtonColors( colors =
contentColor = primaryColor.copy(alpha = 0.5f) ButtonDefaults.outlinedButtonColors(
) contentColor = primaryColor.copy(alpha = 0.5f),
),
) { ) {
Text("Сохранить паттерн") Text("Сохранить паттерн")
} }
@@ -300,17 +344,18 @@ fun Sidebar(
@Composable @Composable
private fun SelectAutomatonMode( private fun SelectAutomatonMode(
mode: AutomatonMode, mode: AutomatonMode,
onModeChange: (AutomatonMode) -> Unit onModeChange: (AutomatonMode) -> Unit,
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = expanded, expanded = expanded,
onExpandedChange = { expanded = it } onExpandedChange = { expanded = it },
) { ) {
OutlinedTextField( OutlinedTextField(
value = when (mode) { value =
when (mode) {
AutomatonMode.GAME_OF_LIFE -> "Game of Life (2D)" AutomatonMode.GAME_OF_LIFE -> "Game of Life (2D)"
AutomatonMode.ONE_DIMENSIONAL -> "1D Cellular Automaton" AutomatonMode.ONE_DIMENSIONAL -> "1D Cellular Automaton"
}, },
@@ -319,36 +364,38 @@ private fun SelectAutomatonMode(
trailingIcon = { trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}, },
modifier = Modifier modifier =
Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable) .menuAnchor(MenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(), .fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors( colors =
OutlinedTextFieldDefaults.colors(
focusedTextColor = primaryColor, focusedTextColor = primaryColor,
unfocusedTextColor = primaryColor, unfocusedTextColor = primaryColor,
focusedBorderColor = primaryColor, focusedBorderColor = primaryColor,
unfocusedBorderColor = primaryColor.copy(alpha = 0.5f), unfocusedBorderColor = primaryColor.copy(alpha = 0.5f),
focusedTrailingIconColor = primaryColor, focusedTrailingIconColor = primaryColor,
unfocusedTrailingIconColor = primaryColor unfocusedTrailingIconColor = primaryColor,
) ),
) )
ExposedDropdownMenu( ExposedDropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = { expanded = false }, onDismissRequest = { expanded = false },
containerColor = MaterialTheme.colorScheme.surface containerColor = MaterialTheme.colorScheme.surface,
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text("Game of Life (2D)", color = primaryColor) }, text = { Text("Game of Life (2D)", color = primaryColor) },
onClick = { onClick = {
onModeChange(AutomatonMode.GAME_OF_LIFE) onModeChange(AutomatonMode.GAME_OF_LIFE)
expanded = false expanded = false
} },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("1D Cellular Automaton", color = primaryColor) }, text = { Text("1D Cellular Automaton", color = primaryColor) },
onClick = { onClick = {
onModeChange(AutomatonMode.ONE_DIMENSIONAL) onModeChange(AutomatonMode.ONE_DIMENSIONAL)
expanded = false expanded = false
} },
) )
} }
} }
@@ -358,14 +405,14 @@ private fun SelectAutomatonMode(
@Composable @Composable
private fun SelectRule( private fun SelectRule(
rule: OneDimensionalRule, rule: OneDimensionalRule,
onRuleChange: (OneDimensionalRule) -> Unit onRuleChange: (OneDimensionalRule) -> Unit,
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = expanded, expanded = expanded,
onExpandedChange = { expanded = it } onExpandedChange = { expanded = it },
) { ) {
OutlinedTextField( OutlinedTextField(
value = "Rule ${rule.value}", value = "Rule ${rule.value}",
@@ -374,22 +421,24 @@ private fun SelectRule(
trailingIcon = { trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}, },
modifier = Modifier modifier =
Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable) .menuAnchor(MenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(), .fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors( colors =
OutlinedTextFieldDefaults.colors(
focusedTextColor = primaryColor, focusedTextColor = primaryColor,
unfocusedTextColor = primaryColor, unfocusedTextColor = primaryColor,
focusedBorderColor = primaryColor, focusedBorderColor = primaryColor,
unfocusedBorderColor = primaryColor.copy(alpha = 0.5f), unfocusedBorderColor = primaryColor.copy(alpha = 0.5f),
focusedTrailingIconColor = primaryColor, focusedTrailingIconColor = primaryColor,
unfocusedTrailingIconColor = primaryColor unfocusedTrailingIconColor = primaryColor,
) ),
) )
ExposedDropdownMenu( ExposedDropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = { expanded = false }, onDismissRequest = { expanded = false },
containerColor = MaterialTheme.colorScheme.surface containerColor = MaterialTheme.colorScheme.surface,
) { ) {
OneDimensionalRule.entries.forEach { r -> OneDimensionalRule.entries.forEach { r ->
DropdownMenuItem( DropdownMenuItem(
@@ -397,7 +446,7 @@ private fun SelectRule(
onClick = { onClick = {
onRuleChange(r) onRuleChange(r)
expanded = false expanded = false
} },
) )
} }
} }

View File

@@ -26,8 +26,8 @@ fun GameOfLifeView(
onActionConsumed: () -> Unit, onActionConsumed: () -> Unit,
onConfigChange: (AppConfig) -> Unit onConfigChange: (AppConfig) -> Unit
) { ) {
var game by remember(config.gridWidth, config.gridHeight) { var game by remember(config.gridWidth, config.gridHeight, config.killAtBoundary) {
mutableStateOf(GameOfLife(config.gridWidth, config.gridHeight)) mutableStateOf(GameOfLife(config.gridWidth, config.gridHeight, config.killAtBoundary))
} }
var isRunning by remember { mutableStateOf(false) } var isRunning by remember { mutableStateOf(false) }
var generation by remember { mutableIntStateOf(0) } var generation by remember { mutableIntStateOf(0) }

View File

@@ -20,11 +20,11 @@ class ExperimentRunner {
width: Int, width: Int,
height: Int, height: Int,
): List<ExperimentResult> { ): List<ExperimentResult> {
val densities = listOf(0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5) val densities = generateSequence(0.1) { it + 0.05 }.takeWhile { it <= 0.5 }.toList()
val results = mutableListOf<ExperimentResult>() val results = mutableListOf<ExperimentResult>()
for (density in densities) { for (density in densities) {
repeat(10) { run -> repeat(100) { run ->
val result = runSingleExperiment(width, height, density) val result = runSingleExperiment(width, height, density)
results.add(result) results.add(result)
println("Density: $density, Run: ${run + 1}, Stabilization: ${result.stabilizationTime}") println("Density: $density, Run: ${run + 1}, Stabilization: ${result.stabilizationTime}")