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

- И эксперименты теперь более глубокие и отражают более реальные значения за счёт кол-ва ранов 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 simulationSpeed: Int = 10,
val probability: Double = 0.5,
val oneDimensionalRule: OneDimensionalRule = OneDimensionalRule.RULE_30
val oneDimensionalRule: OneDimensionalRule = OneDimensionalRule.RULE_30,
val killAtBoundary: Boolean = true
)
@Serializable

View File

@@ -2,7 +2,8 @@ package model
class GameOfLife(
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 generation: Int = 0
@@ -56,9 +57,18 @@ class GameOfLife(
for (dy in -1..1) {
for (dx in -1..1) {
if (dx == 0 && dy == 0) continue
val nx = (x + dx + width) % width
val ny = (y + dy + height) % height
if (grid[ny][nx]) count++
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 ny = (y + dy + height) % height
if (grid[ny][nx]) count++
}
}
}
return count

View File

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

View File

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

View File

@@ -20,11 +20,11 @@ class ExperimentRunner {
width: Int,
height: Int,
): 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>()
for (density in densities) {
repeat(10) { run ->
repeat(100) { run ->
val result = runSingleExperiment(width, height, density)
results.add(result)
println("Density: $density, Run: ${run + 1}, Stabilization: ${result.stabilizationTime}")