
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🧬 一、元胞自动机:简单规则产生复杂行为
📚 1.1 元胞自动机的历史
元胞自动机(Cellular Automaton,简称 CA)是由数学家**约翰·冯·诺依曼(John von Neumann)**在 1940 年代提出的概念,最初用于研究自复制系统。1970 年,**约翰·康威(John Conway)**发明了著名的"生命游戏",使元胞自动机广为人知。
历史里程碑:
| 年份 | 人物 | 贡献 |
|---|---|---|
| 1940 | 斯坦尼斯瓦夫·乌拉姆 | 提出网格模型 |
| 1948 | 约翰·冯·诺依曼 | 自复制自动机 |
| 1970 | 约翰·康威 | 生命游戏 |
| 1983 | 斯蒂芬·沃尔夫拉姆 | 一维 CA 分类 |
| 2002 | 斯蒂芬·沃尔夫拉姆 | 《一种新科学》 |
📐 1.2 生命游戏的规则
康威生命游戏是最著名的二维元胞自动机,规则极其简单却能产生复杂的行为:
基本规则:
| 状态 | 邻居数量 | 结果 |
|---|---|---|
| 活细胞 | < 2 | 死亡(孤独) |
| 活细胞 | 2 或 3 | 存活 |
| 活细胞 | > 3 | 死亡(拥挤) |
| 死细胞 | = 3 | 复活(繁殖) |
生命游戏示意:
初始状态: 下一代:
□ □ □ □ □ □ □ ■ □ □
□ □ ■ □ □ → □ □ ■ □ □
□ □ ■ □ □ □ □ ■ □ □
□ □ ■ □ □ □ □ □ □ □
□ □ □ □ □ □ □ □ □ □
■ = 活细胞
□ = 死细胞
🔬 1.3 经典图案与行为
生命游戏中存在许多有趣的图案:
| 图案类型 | 名称 | 特点 |
|---|---|---|
| 🔄 振荡器 | 闪烁器、蟾蜍 | 周期性变化 |
| 🚀 飞船 | 滑翔机、轻量飞船 | 持续移动 |
| 🔫 枪 | 滑翔机枪 | 持续产生飞船 |
| 💥 爆发 | R-五格骨牌 | 快速扩张 |
| 🏛️ 静止 | 方块、蜂巢 | 稳定不变 |
滑翔机(Glider):
□ ■ □
□ □ ■
■ ■ ■
🎯 1.4 元胞自动机的应用
| 领域 | 应用 | 效果 |
|---|---|---|
| 🧬 生物学 | 生态系统模拟 | 种群动态 |
| 🌊 物理学 | 流体动力学 | 格子气模型 |
| 🔐 密码学 | 伪随机数生成 | 流密码 |
| 🎨 艺术 | 生成艺术 | 动态图案 |
| 🎵 音乐可视化 | 音频驱动演化 | 节奏响应 |
🔧 二、元胞自动机的 Dart 实现
🧮 2.1 基础网格类
dart
import 'dart:math';
import 'dart:typed_data';
/// 元胞状态
enum CellState { dead, alive }
/// 二维网格
class Grid2D {
final int width;
final int height;
final List<int> _cells;
Grid2D(this.width, this.height) : _cells = List<int>.filled(width * height, 0);
/// 获取单元格状态
int get(int x, int y) {
if (x < 0 || x >= width || y < 0 || y >= height) return 0;
return _cells[y * width + x];
}
/// 设置单元格状态
void set(int x, int y, int value) {
if (x >= 0 && x < width && y >= 0 && y < height) {
_cells[y * width + x] = value;
}
}
/// 获取邻居数量
int countNeighbors(int x, int y) {
int count = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
count += get(x + dx, y + dy);
}
}
return count;
}
/// 清空网格
void clear() {
_cells.fillRange(0, _cells.length, 0);
}
/// 随机填充
void randomize(double density, [Random? random]) {
random ??= Random();
for (int i = 0; i < _cells.length; i++) {
_cells[i] = random!.nextDouble() < density ? 1 : 0;
}
}
/// 复制网格
Grid2D copy() {
final newGrid = Grid2D(width, height);
for (int i = 0; i < _cells.length; i++) {
newGrid._cells[i] = _cells[i];
}
return newGrid;
}
/// 获取活细胞数量
int get aliveCount => _cells.fold(0, (sum, cell) => sum + cell);
/// 获取密度
double get density => aliveCount / _cells.length;
}
/// 生命游戏规则
class LifeRules {
final Set<int> birthConditions;
final Set<int> surviveConditions;
const LifeRules({
required this.birthConditions,
required this.surviveConditions,
});
/// 标准康威生命游戏规则
static const LifeRules conway = LifeRules(
birthConditions: {3},
surviveConditions: {2, 3},
);
/// HighLife 规则
static const LifeRules highLife = LifeRules(
birthConditions: {3, 6},
surviveConditions: {2, 3},
);
/// Day & Night 规则
static const LifeRules dayNight = LifeRules(
birthConditions: {3, 6, 7, 8},
surviveConditions: {3, 4, 6, 7, 8},
);
/// Seeds 规则(爆炸性增长)
static const LifeRules seeds = LifeRules(
birthConditions: {2},
surviveConditions: {},
);
/// 判断下一个状态
bool nextState(bool isAlive, int neighbors) {
if (isAlive) {
return surviveConditions.contains(neighbors);
} else {
return birthConditions.contains(neighbors);
}
}
}
⚡ 2.2 生命游戏引擎
dart
/// 生命游戏引擎
class LifeEngine {
Grid2D grid;
LifeRules rules;
int generation;
LifeEngine({
required int width,
required int height,
this.rules = LifeRules.conway,
}) : grid = Grid2D(width, height),
generation = 0;
/// 演化一代
void step() {
final newGrid = Grid2D(grid.width, grid.height);
for (int y = 0; y < grid.height; y++) {
for (int x = 0; x < grid.width; x++) {
final isAlive = grid.get(x, y) == 1;
final neighbors = grid.countNeighbors(x, y);
if (rules.nextState(isAlive, neighbors)) {
newGrid.set(x, y, 1);
}
}
}
grid = newGrid;
generation++;
}
/// 演化多代
void stepMultiple(int count) {
for (int i = 0; i < count; i++) {
step();
}
}
/// 重置
void reset() {
grid.clear();
generation = 0;
}
/// 随机初始化
void randomize(double density) {
grid.randomize(density);
generation = 0;
}
/// 添加图案
void addPattern(int x, int y, List<List<int>> pattern) {
for (int py = 0; py < pattern.length; py++) {
for (int px = 0; px < pattern[py].length; px++) {
grid.set(x + px, y + py, pattern[py][px]);
}
}
}
/// 预设图案
static const List<List<int>> glider = [
[0, 1, 0],
[0, 0, 1],
[1, 1, 1],
];
static const List<List<int>> blinker = [
[1, 1, 1],
];
static const List<List<int>> beacon = [
[1, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 1],
];
static const List<List<int>> pulsar = [
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
];
}
🎨 2.3 音频驱动的元胞自动机
dart
import 'package:flutter/material.dart';
import 'package:just_audio_ohos/just_audio_ohos.dart';
import 'package:audio_session/audio_session.dart';
/// 音频驱动的生命游戏控制器
class AudioLifeController extends ChangeNotifier {
final AudioPlayer _player = AudioPlayer();
late LifeEngine _engine;
bool _isPlaying = false;
bool _isRunning = false;
Duration _position = Duration.zero;
Duration _duration = Duration.zero;
Float32List _audioData = Float32List(128);
double _energy = 0;
double _bass = 0;
double _mid = 0;
double _treble = 0;
double _time = 0;
int _gridWidth = 80;
int _gridHeight = 80;
double _stepInterval = 0.1;
double _accumulatedTime = 0;
bool get isPlaying => _isPlaying;
bool get isRunning => _isRunning;
Duration get position => _position;
Duration get duration => _duration;
LifeEngine get engine => _engine;
Float32List get audioData => _audioData;
double get energy => _energy;
double get bass => _bass;
double get mid => _mid;
double get treble => _treble;
AudioPlayer get player => _player;
/// 初始化
Future<void> initialize() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
_engine = LifeEngine(width: _gridWidth, height: _gridHeight);
_engine.randomize(0.3);
_player.playerStateStream.listen((state) {
_isPlaying = state.playing;
notifyListeners();
});
_player.positionStream.listen((position) {
_position = position;
notifyListeners();
});
_player.durationStream.listen((duration) {
_duration = duration ?? Duration.zero;
notifyListeners();
});
}
/// 加载网络音频
Future<void> loadAudio(String url) async {
try {
await _player.setUrl(url);
} catch (e) {
debugPrint('加载音频失败: $e');
}
}
/// 更新
void update(double dt) {
_time += dt;
// 更新音频数据
_updateAudioData();
// 计算音频特征
_calculateAudioFeatures();
// 更新演化速度
_updateStepInterval();
// 演化逻辑
_accumulatedTime += dt;
if (_isRunning && _accumulatedTime >= _stepInterval) {
_engine.step();
_accumulatedTime = 0;
// 音频驱动的细胞注入
_injectCells();
}
notifyListeners();
}
void _updateAudioData() {
final random = Random();
for (int i = 0; i < 128; i++) {
if (_isPlaying) {
final freq = (i / 128) * 8 + 1;
final wave1 = sin(_time * freq) * 0.4;
final wave2 = sin(_time * freq * 1.5 + pi / 3) * 0.3;
final noise = (random.nextDouble() - 0.5) * 0.15;
final bassBoost = i < 32 ? 0.3 : 0;
_audioData[i] = _audioData[i] * 0.85 +
(wave1 + wave2 + noise + bassBoost) * 0.15;
} else {
_audioData[i] *= 0.95;
}
}
}
void _calculateAudioFeatures() {
double totalEnergy = 0;
double bassEnergy = 0;
double midEnergy = 0;
double trebleEnergy = 0;
for (int i = 0; i < 128; i++) {
final value = _audioData[i].abs();
totalEnergy += value;
if (i < 32) {
bassEnergy += value;
} else if (i < 96) {
midEnergy += value;
} else {
trebleEnergy += value;
}
}
_energy = totalEnergy / 128;
_bass = bassEnergy / 32;
_mid = midEnergy / 64;
_treble = trebleEnergy / 32;
}
void _updateStepInterval() {
// 能量越高,演化越快
_stepInterval = 0.2 - _energy * 0.15;
_stepInterval = _stepInterval.clamp(0.02, 0.2);
}
void _injectCells() {
final random = Random();
// 低音脉冲注入细胞
if (_bass > 0.5) {
final count = (_bass * 20).toInt();
for (int i = 0; i < count; i++) {
final x = random.nextInt(_gridWidth);
final y = random.nextInt(_gridHeight);
_engine.grid.set(x, y, 1);
}
}
// 高频注入滑翔机
if (_treble > 0.6 && random.nextDouble() < 0.3) {
final x = random.nextInt(_gridWidth - 3);
final y = random.nextInt(_gridHeight - 3);
_engine.addPattern(x, y, LifeEngine.glider);
}
}
/// 开始/暂停演化
void toggleRunning() {
_isRunning = !_isRunning;
notifyListeners();
}
/// 单步演化
void stepOnce() {
_engine.step();
notifyListeners();
}
/// 重置
void reset() {
_engine.reset();
_engine.randomize(0.3);
notifyListeners();
}
/// 切换单元格
void toggleCell(int x, int y) {
final current = _engine.grid.get(x, y);
_engine.grid.set(x, y, current == 0 ? 1 : 0);
notifyListeners();
}
/// 设置规则
void setRules(LifeRules rules) {
_engine.rules = rules;
notifyListeners();
}
/// 播放/暂停音频
Future<void> togglePlay() async {
if (_isPlaying) {
await _player.pause();
} else {
await _player.play();
}
}
/// 跳转
Future<void> seek(Duration position) async {
await _player.seek(position);
}
@override
void dispose() {
_player.dispose();
super.dispose();
}
}
📦 三、完整示例代码
以下是完整的元胞自动机音乐可视化示例代码:
dart
import 'package:flutter/material.dart';
import 'package:just_audio_ohos/just_audio_ohos.dart';
import 'package:audio_session/audio_session.dart';
import 'dart:math';
import 'dart:typed_data';
void main() {
runApp(const LifeGameApp());
}
class LifeGameApp extends StatelessWidget {
const LifeGameApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '元胞自动机',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark),
useMaterial3: true,
),
home: const LifeHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class LifeHomePage extends StatelessWidget {
const LifeHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('🧬 元胞自动机'), backgroundColor: Theme.of(context).colorScheme.inversePrimary),
body: ListView(padding: const EdgeInsets.all(16), children: [
_buildCard(context, title: '基础生命游戏', description: '康威生命游戏', icon: Icons.grid_on, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BasicLifeDemo()))),
_buildCard(context, title: '规则变体', description: '不同规则效果', icon: Icons.settings, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const RulesDemo()))),
_buildCard(context, title: '图案库', description: '经典图案展示', icon: Icons.category, color: Colors.cyan,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PatternsDemo()))),
_buildCard(context, title: '音乐生命', description: '音频驱动演化', icon: Icons.music_note, color: Colors.orange,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MusicLifeDemo()))),
_buildCard(context, title: '交互模式', description: '触摸绘制细胞', icon: Icons.touch_app, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const InteractiveLifeDemo()))),
]),
);
}
Widget _buildCard(BuildContext context, {required String title, required String description, required IconData icon,
required Color color, required VoidCallback onTap}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(children: [
Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
child: Icon(icon, color: color, size: 28)),
const SizedBox(width: 16),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(description, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
])),
Icon(Icons.chevron_right, color: Colors.grey[400]),
]),
),
),
);
}
}
/// 网格类
class Grid {
final int width, height;
final List<int> cells;
Grid(this.width, this.height) : cells = List<int>.filled(width * height, 0);
int get(int x, int y) => (x < 0 || x >= width || y < 0 || y >= height) ? 0 : cells[y * width + x];
void set(int x, int y, int v) { if (x >= 0 && x < width && y >= 0 && y < height) cells[y * width + x] = v; }
int countNeighbors(int x, int y) {
int c = 0;
for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++)
if (dx != 0 || dy != 0) c += get(x + dx, y + dy);
return c;
}
void randomize(double density) {
final r = Random();
for (int i = 0; i < cells.length; i++) cells[i] = r.nextDouble() < density ? 1 : 0;
}
void clear() => cells.fillRange(0, cells.length, 0);
}
/// 基础生命游戏演示
class BasicLifeDemo extends StatefulWidget {
const BasicLifeDemo({super.key});
@override
State<BasicLifeDemo> createState() => _BasicLifeDemoState();
}
class _BasicLifeDemoState extends State<BasicLifeDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Grid _grid;
bool _running = false;
int _generation = 0;
final int _size = 60;
@override
void initState() {
super.initState();
_grid = Grid(_size, _size);
_grid.randomize(0.3);
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 100))..repeat();
_controller.addListener(_update);
}
void _update() {
if (_running) {
_step();
setState(() {});
}
}
void _step() {
final newGrid = Grid(_size, _size);
for (int y = 0; y < _size; y++) {
for (int x = 0; x < _size; x++) {
final alive = _grid.get(x, y) == 1;
final n = _grid.countNeighbors(x, y);
newGrid.set(x, y, (alive && (n == 2 || n == 3)) || (!alive && n == 3) ? 1 : 0);
}
}
_grid = newGrid;
_generation++;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('基础生命游戏')),
body: Column(children: [
Expanded(child: CustomPaint(painter: LifePainter(_grid), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.black12,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Text('代数: $_generation', style: const TextStyle(color: Colors.white)),
IconButton(icon: Icon(_running ? Icons.pause : Icons.play_arrow, color: Colors.green),
onPressed: () => setState(() => _running = !_running)),
IconButton(icon: const Icon(Icons.skip_next, color: Colors.white),
onPressed: () { _step(); setState(() {}); }),
IconButton(icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: () { _grid.randomize(0.3); _generation = 0; setState(() {}); }),
]),
);
}
}
class LifePainter extends CustomPainter {
final Grid grid;
LifePainter(this.grid);
@override
void paint(Canvas canvas, Size size) {
final cellW = size.width / grid.width;
final cellH = size.height / grid.height;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
for (int y = 0; y < grid.height; y++) {
for (int x = 0; x < grid.width; x++) {
if (grid.get(x, y) == 1) {
final hue = (x + y) * 2 % 360;
canvas.drawRect(
Rect.fromLTWH(x * cellW, y * cellH, cellW - 1, cellH - 1),
Paint()..color = HSVColor.fromAHSV(1, hue.toDouble(), 0.7, 1).toColor(),
);
}
}
}
}
@override
bool shouldRepaint(covariant LifePainter old) => true;
}
/// 规则变体演示
class RulesDemo extends StatefulWidget {
const RulesDemo({super.key});
@override
State<RulesDemo> createState() => _RulesDemoState();
}
class _RulesDemoState extends State<RulesDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Grid _grid;
bool _running = false;
String _ruleName = 'Conway';
final int _size = 50;
final Map<String, Set<int>> _birthRules = {
'Conway': {3},
'HighLife': {3, 6},
'Day&Night': {3, 6, 7, 8},
'Seeds': {2},
};
final Map<String, Set<int>> _surviveRules = {
'Conway': {2, 3},
'HighLife': {2, 3},
'Day&Night': {3, 4, 6, 7, 8},
'Seeds': {},
};
@override
void initState() {
super.initState();
_grid = Grid(_size, _size);
_grid.randomize(0.3);
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 100))..repeat();
_controller.addListener(_update);
}
void _update() {
if (_running) {
_step();
setState(() {});
}
}
void _step() {
final birth = _birthRules[_ruleName]!;
final survive = _surviveRules[_ruleName]!;
final newGrid = Grid(_size, _size);
for (int y = 0; y < _size; y++) {
for (int x = 0; x < _size; x++) {
final alive = _grid.get(x, y) == 1;
final n = _grid.countNeighbors(x, y);
newGrid.set(x, y, (alive && survive.contains(n)) || (!alive && birth.contains(n)) ? 1 : 0);
}
}
_grid = newGrid;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('规则变体')),
body: Column(children: [
Expanded(child: CustomPaint(painter: LifePainter(_grid), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.black12,
child: Column(children: [
Wrap(spacing: 8, children: _birthRules.keys.map((name) =>
ChoiceChip(label: Text(name), selected: _ruleName == name,
onSelected: (_) => setState(() { _ruleName = name; _grid.randomize(0.3); }))).toList()),
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_running ? Icons.pause : Icons.play_arrow, color: Colors.green),
onPressed: () => setState(() => _running = !_running)),
IconButton(icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: () { _grid.randomize(0.3); setState(() {}); }),
]),
]),
);
}
}
/// 图案库演示
class PatternsDemo extends StatefulWidget {
const PatternsDemo({super.key});
@override
State<PatternsDemo> createState() => _PatternsDemoState();
}
class _PatternsDemoState extends State<PatternsDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Grid _grid;
bool _running = false;
final int _size = 60;
String _pattern = 'Glider';
final Map<String, List<List<int>>> _patterns = {
'Glider': [[0,1,0],[0,0,1],[1,1,1]],
'Blinker': [[1,1,1]],
'Beacon': [[1,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,1]],
'Toad': [[0,1,1,1],[1,1,1,0]],
'Pulsar': [
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[1,0,0,0,0,1,0,1,0,0,0,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,1,1,1,0,0,0,1,1,1,0,0],
],
};
@override
void initState() {
super.initState();
_grid = Grid(_size, _size);
_addPattern();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 150))..repeat();
_controller.addListener(_update);
}
void _addPattern() {
_grid.clear();
final pattern = _patterns[_pattern]!;
final x = (_size - pattern[0].length) ~/ 2;
final y = (_size - pattern.length) ~/ 2;
for (int py = 0; py < pattern.length; py++) {
for (int px = 0; px < pattern[py].length; px++) {
_grid.set(x + px, y + py, pattern[py][px]);
}
}
}
void _update() {
if (_running) {
_step();
setState(() {});
}
}
void _step() {
final newGrid = Grid(_size, _size);
for (int y = 0; y < _size; y++) {
for (int x = 0; x < _size; x++) {
final alive = _grid.get(x, y) == 1;
final n = _grid.countNeighbors(x, y);
newGrid.set(x, y, (alive && (n == 2 || n == 3)) || (!alive && n == 3) ? 1 : 0);
}
}
_grid = newGrid;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('图案库')),
body: Column(children: [
Expanded(child: CustomPaint(painter: LifePainter(_grid), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.black12,
child: Column(children: [
Wrap(spacing: 8, children: _patterns.keys.map((name) =>
ChoiceChip(label: Text(name), selected: _pattern == name,
onSelected: (_) => setState(() { _pattern = name; _addPattern(); }))).toList()),
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_running ? Icons.pause : Icons.play_arrow, color: Colors.green),
onPressed: () => setState(() => _running = !_running)),
IconButton(icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: () { _addPattern(); setState(() {}); }),
]),
]),
);
}
}
/// 音乐生命演示
class MusicLifeDemo extends StatefulWidget {
const MusicLifeDemo({super.key});
@override
State<MusicLifeDemo> createState() => _MusicLifeDemoState();
}
class _MusicLifeDemoState extends State<MusicLifeDemo> with TickerProviderStateMixin {
late AnimationController _animController;
late AudioPlayer _audioPlayer;
late Grid _grid;
Float32List _audioData = Float32List(128);
bool _isPlaying = false;
bool _running = true;
Duration _position = Duration.zero;
Duration _duration = Duration.zero;
double _energy = 0, _bass = 0;
double _time = 0, _stepTime = 0;
final int _size = 50;
static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
@override
void initState() {
super.initState();
_grid = Grid(_size, _size);
_grid.randomize(0.25);
_initAudio();
_animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_animController.addListener(_update);
}
Future<void> _initAudio() async {
_audioPlayer = AudioPlayer();
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());
_audioPlayer.playerStateStream.listen((s) => setState(() => _isPlaying = s.playing));
_audioPlayer.positionStream.listen((p) => setState(() => _position = p));
_audioPlayer.durationStream.listen((d) => setState(() => _duration = d ?? Duration.zero));
try { await _audioPlayer.setUrl(_audioUrl); } catch (e) { debugPrint('加载失败: $e'); }
}
void _update() {
_time += 0.016;
for (int i = 0; i < 128; i++) {
if (_isPlaying) {
final freq = (i / 128) * 8 + 1;
final wave = sin(_time * freq) * 0.4 + sin(_time * freq * 1.5) * 0.3;
final bass = i < 32 ? 0.3 : 0;
_audioData[i] = _audioData[i] * 0.85 + (wave + bass) * 0.15;
} else {
_audioData[i] *= 0.95;
}
}
double total = 0, bassE = 0;
for (int i = 0; i < 128; i++) {
total += _audioData[i].abs();
if (i < 32) bassE += _audioData[i].abs();
}
_energy = total / 128;
_bass = bassE / 32;
// 演化
final stepInterval = (0.15 - _energy * 0.1).clamp(0.03, 0.15);
_stepTime += 0.016;
if (_running && _stepTime >= stepInterval) {
_step();
_injectCells();
_stepTime = 0;
}
setState(() {});
}
void _step() {
final newGrid = Grid(_size, _size);
for (int y = 0; y < _size; y++) {
for (int x = 0; x < _size; x++) {
final alive = _grid.get(x, y) == 1;
final n = _grid.countNeighbors(x, y);
newGrid.set(x, y, (alive && (n == 2 || n == 3)) || (!alive && n == 3) ? 1 : 0);
}
}
_grid = newGrid;
}
void _injectCells() {
final r = Random();
if (_bass > 0.5) {
for (int i = 0; i < (_bass * 15).toInt(); i++) {
_grid.set(r.nextInt(_size), r.nextInt(_size), 1);
}
}
}
@override
void dispose() {
_animController.dispose();
_audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('音乐生命')),
body: Stack(children: [
CustomPaint(painter: MusicLifePainter(_grid, _energy), size: Size.infinite),
Positioned(bottom: 30, left: 20, right: 20, child: _buildControls()),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(16)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('🎵 SoundHelix - Song 1', style: TextStyle(color: Colors.white, fontSize: 14)),
const SizedBox(height: 12),
Slider(value: _duration.inMilliseconds > 0 ? _position.inMilliseconds.toDouble().clamp(0, _duration.inMilliseconds.toDouble()) : 0,
max: _duration.inMilliseconds > 0 ? _duration.inMilliseconds.toDouble() : 1,
onChanged: (v) => _audioPlayer.seek(Duration(milliseconds: v.toInt()))),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(icon: Icon(_running ? Icons.pause : Icons.play_arrow, color: Colors.green),
onPressed: () => setState(() => _running = !_running)),
IconButton(icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.orange, size: 36),
onPressed: () => _isPlaying ? _audioPlayer.pause() : _audioPlayer.play()),
IconButton(icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: () { _grid.randomize(0.25); setState(() {}); }),
]),
],
),
);
}
}
class MusicLifePainter extends CustomPainter {
final Grid grid;
final double energy;
MusicLifePainter(this.grid, this.energy);
@override
void paint(Canvas canvas, Size size) {
final cellW = size.width / grid.width;
final cellH = size.height / grid.height;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = Color.lerp(const Color(0xFF0a0a15), const Color(0xFF100a20), energy)!);
for (int y = 0; y < grid.height; y++) {
for (int x = 0; x < grid.width; x++) {
if (grid.get(x, y) == 1) {
final hue = (x * 3 + y * 5 + energy * 100) % 360;
final paint = Paint()..color = HSVColor.fromAHSV(0.8 + energy * 0.2, hue, 0.7, 1).toColor();
if (energy > 0.3) paint.maskFilter = MaskFilter.blur(BlurStyle.normal, 1 + energy * 2);
canvas.drawRect(Rect.fromLTWH(x * cellW, y * cellH, cellW - 1, cellH - 1), paint);
}
}
}
}
@override
bool shouldRepaint(covariant MusicLifePainter old) => true;
}
/// 交互模式演示
class InteractiveLifeDemo extends StatefulWidget {
const InteractiveLifeDemo({super.key});
@override
State<InteractiveLifeDemo> createState() => _InteractiveLifeDemoState();
}
class _InteractiveLifeDemoState extends State<InteractiveLifeDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Grid _grid;
bool _running = false;
final int _size = 40;
@override
void initState() {
super.initState();
_grid = Grid(_size, _size);
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 150))..repeat();
_controller.addListener(_update);
}
void _update() {
if (_running) {
_step();
setState(() {});
}
}
void _step() {
final newGrid = Grid(_size, _size);
for (int y = 0; y < _size; y++) {
for (int x = 0; x < _size; x++) {
final alive = _grid.get(x, y) == 1;
final n = _grid.countNeighbors(x, y);
newGrid.set(x, y, (alive && (n == 2 || n == 3)) || (!alive && n == 3) ? 1 : 0);
}
}
_grid = newGrid;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('交互模式')),
body: GestureDetector(
onPanStart: (details) => _toggleCell(details.localPosition),
onPanUpdate: (details) => _toggleCell(details.localPosition),
child: Stack(children: [
CustomPaint(painter: LifePainter(_grid), size: Size.infinite),
Positioned(bottom: 20, left: 20, child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(8)),
child: const Text('触摸绘制细胞', style: TextStyle(color: Colors.white54, fontSize: 12)),
)),
]),
),
floatingActionButton: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
FloatingActionButton(heroTag: 'play', backgroundColor: _running ? Colors.orange : Colors.green,
child: Icon(_running ? Icons.pause : Icons.play_arrow),
onPressed: () => setState(() => _running = !_running)),
const SizedBox(width: 10),
FloatingActionButton(heroTag: 'clear', backgroundColor: Colors.red,
child: const Icon(Icons.clear),
onPressed: () { _grid.clear(); setState(() {}); }),
]),
);
}
void _toggleCell(Offset localPosition) {
final RenderBox box = context.findRenderObject() as RenderBox;
final size = box.size;
final cellW = size.width / _size;
final cellH = size.height / _size;
final x = (localPosition.dx / cellW).floor();
final y = (localPosition.dy / cellH).floor();
if (x >= 0 && x < _size && y >= 0 && y < _size) {
setState(() => _grid.set(x, y, _grid.get(x, y) == 0 ? 1 : 0));
}
}
}
📝 四、总结
本篇文章深入探讨了元胞自动机在音乐可视化中的应用,从康威生命游戏到音频驱动的演化规则,构建了具有"生命感"的动态网格动画效果。
✅ 核心知识点回顾
| 知识点 | 说明 |
|---|---|
| 🧬 元胞自动机 | 网格、邻居、规则 |
| 🎮 生命游戏 | 康威规则、经典图案 |
| 📐 规则变体 | HighLife、Day&Night |
| 🎵 音频驱动 | 能量控制演化速度 |
| 🔊 网络音乐 | just_audio_ohos 在线播放 |
⭐ 最佳实践要点
- ✅ 使用双缓冲避免读写冲突
- ✅ 低分辨率网格保证性能
- ✅ 音频特征注入细胞
- ✅ 支持交互绘制
🚀 进阶方向
- 🔮 多状态元胞自动机
- ✨ 3D 元胞自动机
- 👆 多点触控绘制
- ⚡ GPU 并行计算