在这里插入图片描述

前言

在三国杀游戏中,伤害计算是一个复杂的过程,需要考虑武器加成、技能效果、防具减伤等多种因素。一个精确的伤害计算器能够帮助玩家做出更好的决策。本文将实现一个功能完整的伤害计算器,支持多种伤害因素的组合计算。

伤害计算系统设计

伤害计算涉及多个维度的因素:基础伤害武器加成技能加成防具减伤特殊效果等。这些因素需要按照特定的规则进行组合计算。

// lib/models/damage_model.dart
class DamageCalculationModel {
  final int baseDamage;
  final int weaponBonus;
  final int skillBonus;
  final bool hasArmor;
  final bool hasRattan;
  final bool hasEightDiagrams;
  final bool isFireDamage;
  final bool isThunderDamage;
  final bool hasWineEffect;
  final DateTime timestamp;

  const DamageCalculationModel({
    required this.baseDamage,
    required this.weaponBonus,
    required this.skillBonus,
    required this.hasArmor,
    required this.hasRattan,
    required this.hasEightDiagrams,
    required this.isFireDamage,
    required this.isThunderDamage,
    required this.hasWineEffect,
    required this.timestamp,
  });

  int calculateFinalDamage() {
    int damage = baseDamage + weaponBonus + skillBonus;
    
    // 酒效果加成
    if (hasWineEffect) {
      damage += 1;
    }
    
    // 防具减伤
    if (hasArmor && damage > 0) {
      damage -= 1;
    }
    
    // 藤甲特殊处理
    if (hasRattan) {
      if (isFireDamage) {
        damage += 1;
      } else {
        damage = 0;
      }
    }
    
    return damage > 0 ? damage : 0;
  }

  Map<String, dynamic> toJson() {
    return {
      'baseDamage': baseDamage,
      'weaponBonus': weaponBonus,
      'skillBonus': skillBonus,
      'hasArmor': hasArmor,
      'hasRattan': hasRattan,
      'hasEightDiagrams': hasEightDiagrams,
      'isFireDamage': isFireDamage,
      'isThunderDamage': isThunderDamage,
      'hasWineEffect': hasWineEffect,
      'timestamp': timestamp.toIso8601String(),
    };
  }
}

这个模型包含了伤害计算的所有参数calculateFinalDamage() 方法实现了伤害计算的核心逻辑,考虑了各种防具的特殊效果。这种结构化的设计让计算逻辑更加清晰和易于维护。

伤害计算引擎

// lib/services/damage_calculator_service.dart
class DamageCalculatorService {
  static const String _cacheKey = 'damage_calculations';
  
  static int calculateDamage(DamageCalculationModel model) {
    return model.calculateFinalDamage();
  }
  
  static String getDamageDescription(int damage) {
    if (damage == 0) return '无伤害';
    if (damage == 1) return '标准伤害';
    if (damage <= 3) return '中等伤害';
    if (damage <= 5) return '高额伤害';
    return '致命伤害';
  }
  
  static List<String> getCalculationSteps(DamageCalculationModel model) {
    List<String> steps = [];
    
    steps.add('基础伤害:${model.baseDamage}');
    
    if (model.weaponBonus > 0) {
      steps.add('武器加成:+${model.weaponBonus}');
    }
    
    if (model.skillBonus > 0) {
      steps.add('技能加成:+${model.skillBonus}');
    }
    
    if (model.hasWineEffect) {
      steps.add('酒效果:+1');
    }
    
    if (model.hasArmor) {
      steps.add('防具减伤:-1');
    }
    
    if (model.hasRattan && model.isFireDamage) {
      steps.add('藤甲火焰:+1');
    }
    
    steps.add('最终伤害:${model.calculateFinalDamage()}');
    
    return steps;
  }
  
  static Future<void> saveCalculation(DamageCalculationModel model) async {
    final prefs = await SharedPreferences.getInstance();
    final calculations = prefs.getStringList(_cacheKey) ?? [];
    calculations.insert(0, json.encode(model.toJson()));
    
    if (calculations.length > 20) {
      calculations.removeLast();
    }
    
    await prefs.setStringList(_cacheKey, calculations);
  }
  
  static Future<List<DamageCalculationModel>> getCalculationHistory() async {
    final prefs = await SharedPreferences.getInstance();
    final calculations = prefs.getStringList(_cacheKey) ?? [];
    
    return calculations.map((calc) {
      final json = jsonDecode(calc);
      return DamageCalculationModel(
        baseDamage: json['baseDamage'],
        weaponBonus: json['weaponBonus'],
        skillBonus: json['skillBonus'],
        hasArmor: json['hasArmor'],
        hasRattan: json['hasRattan'],
        hasEightDiagrams: json['hasEightDiagrams'],
        isFireDamage: json['isFireDamage'],
        isThunderDamage: json['isThunderDamage'],
        hasWineEffect: json['hasWineEffect'],
        timestamp: DateTime.parse(json['timestamp']),
      );
    }).toList();
  }
}

伤害计算服务提供了完整的计算和存储功能calculateDamage() 方法是核心计算方法,getCalculationSteps() 方法返回详细的计算步骤,便于用户理解计算过程。saveCalculation()getCalculationHistory() 方法实现了历史记录的保存和读取。

伤害计算器屏幕实现

// lib/screens/tools/damage_calculator_screen.dart
class DamageCalculatorScreen extends StatefulWidget {
  const DamageCalculatorScreen({Key? key}) : super(key: key);

  
  State<DamageCalculatorScreen> createState() => _DamageCalculatorScreenState();
}

class _DamageCalculatorScreenState extends State<DamageCalculatorScreen>
    with TickerProviderStateMixin {
  
  int baseDamage = 1;
  int weaponBonus = 0;
  int skillBonus = 0;
  bool hasArmor = false;
  bool hasRattan = false;
  bool hasEightDiagrams = false;
  bool isFireDamage = false;
  bool isThunderDamage = false;
  bool hasWineEffect = false;
  
  late AnimationController _resultAnimationController;
  late Animation<double> _resultScaleAnimation;
  
  List<DamageCalculationModel> history = [];

  
  void initState() {
    super.initState();
    _initAnimations();
    _loadHistory();
  }

  void _initAnimations() {
    _resultAnimationController = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: this,
    );
    
    _resultScaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
      CurvedAnimation(parent: _resultAnimationController, curve: Curves.elasticOut),
    );
  }

  int get finalDamage {
    return DamageCalculatorService.calculateDamage(
      DamageCalculationModel(
        baseDamage: baseDamage,
        weaponBonus: weaponBonus,
        skillBonus: skillBonus,
        hasArmor: hasArmor,
        hasRattan: hasRattan,
        hasEightDiagrams: hasEightDiagrams,
        isFireDamage: isFireDamage,
        isThunderDamage: isThunderDamage,
        hasWineEffect: hasWineEffect,
        timestamp: DateTime.now(),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('伤害计算器'),
        backgroundColor: Colors.red[700],
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: const Icon(Icons.history),
            onPressed: _showHistory,
          ),
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _resetCalculator,
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: SingleChildScrollView(
              padding: EdgeInsets.all(16.w),
              child: Column(
                children: [
                  _buildResultCard(),
                  SizedBox(height: 20.h),
                  _buildBasicDamageSection(),
                  SizedBox(height: 16.h),
                  _buildWeaponSection(),
                  SizedBox(height: 16.h),
                  _buildSkillSection(),
                  SizedBox(height: 16.h),
                  _buildDefenseSection(),
                  SizedBox(height: 16.h),
                  _buildCalculationDetails(),
                ],
              ),
            ),
          ),
          _buildActionButtons(),
        ],
      ),
    );
  }

  Widget _buildResultCard() {
    return AnimatedBuilder(
      animation: _resultScaleAnimation,
      builder: (context, child) {
        return Transform.scale(
          scale: _resultScaleAnimation.value,
          child: Container(
            width: double.infinity,
            padding: EdgeInsets.all(24.w),
            decoration: BoxDecoration(
              gradient: LinearGradient(
                colors: [Colors.red[700]!, Colors.red[500]!],
              ),
              borderRadius: BorderRadius.circular(20.r),
              boxShadow: [
                BoxShadow(
                  color: Colors.red.withOpacity(0.3),
                  blurRadius: 20,
                  offset: const Offset(0, 10),
                ),
              ],
            ),
            child: Column(
              children: [
                Text(
                  '最终伤害',
                  style: TextStyle(
                    fontSize: 18.sp,
                    color: Colors.white,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                SizedBox(height: 8.h),
                Text(
                  '$finalDamage',
                  style: TextStyle(
                    fontSize: 64.sp,
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                SizedBox(height: 8.h),
                Text(
                  DamageCalculatorService.getDamageDescription(finalDamage),
                  style: TextStyle(
                    fontSize: 14.sp,
                    color: Colors.white.withOpacity(0.9),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Widget _buildBasicDamageSection() {
    return _buildSection(
      title: '基础伤害',
      icon: Icons.local_fire_department,
      color: Colors.red,
      child: Column(
        children: [
          _buildSlider(
            label: '基础伤害值',
            value: baseDamage,
            min: 1,
            max: 5,
            onChanged: (value) {
              setState(() => baseDamage = value.toInt());
              _animateResult();
            },
          ),
          SizedBox(height: 8.h),
          Text(
            '通常情况下,【杀】造成1点伤害',
            style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
          ),
        ],
      ),
    );
  }

  Widget _buildWeaponSection() {
    return _buildSection(
      title: '武器加成',
      icon: Icons.sports_martial_arts,
      color: Colors.orange,
      child: Column(
        children: [
          _buildSlider(
            label: '武器加成',
            value: weaponBonus,
            min: 0,
            max: 3,
            onChanged: (value) {
              setState(() => weaponBonus = value.toInt());
              _animateResult();
            },
          ),
          SizedBox(height: 12.h),
          Wrap(
            spacing: 8.w,
            runSpacing: 8.h,
            children: [
              _buildWeaponButton('无武器', 0),
              _buildWeaponButton('青釭剑', 1),
              _buildWeaponButton('丈八蛇矛', 1),
              _buildWeaponButton('方天画戟', 2),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildWeaponButton(String name, int bonus) {
    final isSelected = weaponBonus == bonus;
    return GestureDetector(
      onTap: () {
        setState(() => weaponBonus = bonus);
        _animateResult();
      },
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
        decoration: BoxDecoration(
          color: isSelected ? Colors.orange : Colors.grey.shade100,
          borderRadius: BorderRadius.circular(16.r),
          border: Border.all(
            color: isSelected ? Colors.orange : Colors.grey.shade300,
          ),
        ),
        child: Text(
          name,
          style: TextStyle(
            fontSize: 12.sp,
            color: isSelected ? Colors.white : Colors.grey.shade700,
            fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
          ),
        ),
      ),
    );
  }

  Widget _buildSkillSection() {
    return _buildSection(
      title: '技能加成',
      icon: Icons.auto_awesome,
      color: Colors.purple,
      child: Column(
        children: [
          _buildSlider(
            label: '技能加成',
            value: skillBonus,
            min: 0,
            max: 3,
            onChanged: (value) {
              setState(() => skillBonus = value.toInt());
              _animateResult();
            },
          ),
          SizedBox(height: 12.h),
          Row(
            children: [
              Expanded(
                child: _buildCheckbox('酒效果', hasWineEffect, (value) {
                  setState(() => hasWineEffect = value);
                  _animateResult();
                }),
              ),
              Expanded(
                child: _buildCheckbox('火焰伤害', isFireDamage, (value) {
                  setState(() => isFireDamage = value);
                  _animateResult();
                }),
              ),
            ],
          ),
          SizedBox(height: 8.h),
          _buildCheckbox('雷电伤害', isThunderDamage, (value) {
            setState(() => isThunderDamage = value);
            _animateResult();
          }),
        ],
      ),
    );
  }

  Widget _buildDefenseSection() {
    return _buildSection(
      title: '防御装备',
      icon: Icons.shield,
      color: Colors.blue,
      child: Column(
        children: [
          _buildCheckbox('普通防具 (-1伤害)', hasArmor, (value) {
            setState(() {
              hasArmor = value;
              if (value) hasRattan = false;
            });
            _animateResult();
          }),
          _buildCheckbox('藤甲 (火焰+1,其他无效)', hasRattan, (value) {
            setState(() {
              hasRattan = value;
              if (value) hasArmor = false;
            });
            _animateResult();
          }),
          _buildCheckbox('八卦阵 (50%概率抵消非雷电)', hasEightDiagrams, (value) {
            setState(() => hasEightDiagrams = value);
            _animateResult();
          }),
        ],
      ),
    );
  }

  Widget _buildCalculationDetails() {
    final steps = DamageCalculatorService.getCalculationSteps(
      DamageCalculationModel(
        baseDamage: baseDamage,
        weaponBonus: weaponBonus,
        skillBonus: skillBonus,
        hasArmor: hasArmor,
        hasRattan: hasRattan,
        hasEightDiagrams: hasEightDiagrams,
        isFireDamage: isFireDamage,
        isThunderDamage: isThunderDamage,
        hasWineEffect: hasWineEffect,
        timestamp: DateTime.now(),
      ),
    );

    return _buildSection(
      title: '计算详情',
      icon: Icons.calculate,
      color: Colors.green,
      child: Container(
        padding: EdgeInsets.all(12.w),
        decoration: BoxDecoration(
          color: Colors.grey.shade50,
          borderRadius: BorderRadius.circular(8.r),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: steps.map((step) => Padding(
            padding: EdgeInsets.only(bottom: 8.h),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  step.split(':')[0],
                  style: TextStyle(fontSize: 13.sp, color: Colors.grey.shade700),
                ),
                Text(
                  step.split(':')[1],
                  style: TextStyle(
                    fontSize: 13.sp,
                    fontWeight: FontWeight.bold,
                    color: Colors.red,
                  ),
                ),
              ],
            ),
          )).toList(),
        ),
      ),
    );
  }

  Widget _buildSection({
    required String title,
    required IconData icon,
    required Color color,
    required Widget child,
  }) {
    return Container(
      width: double.infinity,
      padding: EdgeInsets.all(16.w),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12.r),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 8,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(icon, color: color, size: 20.sp),
              SizedBox(width: 8.w),
              Text(
                title,
                style: TextStyle(
                  fontSize: 16.sp,
                  fontWeight: FontWeight.bold,
                  color: color,
                ),
              ),
            ],
          ),
          SizedBox(height: 12.h),
          child,
        ],
      ),
    );
  }

  Widget _buildSlider({
    required String label,
    required int value,
    required int min,
    required int max,
    required Function(double) onChanged,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(label, style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
            Container(
              padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
              decoration: BoxDecoration(
                color: Colors.grey.shade100,
                borderRadius: BorderRadius.circular(8.r),
              ),
              child: Text('$value', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
            ),
          ],
        ),
        SizedBox(height: 8.h),
        Slider(
          value: value.toDouble(),
          min: min.toDouble(),
          max: max.toDouble(),
          divisions: max - min,
          activeColor: Colors.red,
          inactiveColor: Colors.grey.shade300,
          onChanged: onChanged,
        ),
      ],
    );
  }

  Widget _buildCheckbox(String label, bool value, Function(bool) onChanged) {
    return CheckboxListTile(
      title: Text(label, style: TextStyle(fontSize: 14.sp)),
      value: value,
      onChanged: (newValue) => onChanged(newValue ?? false),
      activeColor: Colors.red,
      contentPadding: EdgeInsets.zero,
      controlAffinity: ListTileControlAffinity.leading,
    );
  }

  Widget _buildActionButtons() {
    return Container(
      padding: EdgeInsets.all(16.w),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 8,
            offset: const Offset(0, -2),
          ),
        ],
      ),
      child: Row(
        children: [
          Expanded(
            child: ElevatedButton.icon(
              onPressed: _saveCalculation,
              icon: const Icon(Icons.save),
              label: const Text('保存计算'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
                foregroundColor: Colors.white,
                padding: EdgeInsets.symmetric(vertical: 12.h),
              ),
            ),
          ),
          SizedBox(width: 12.w),
          Expanded(
            child: ElevatedButton.icon(
              onPressed: _shareCalculation,
              icon: const Icon(Icons.share),
              label: const Text('分享结果'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                padding: EdgeInsets.symmetric(vertical: 12.h),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _animateResult() {
    _resultAnimationController.forward().then((_) {
      _resultAnimationController.reverse();
    });
  }

  void _resetCalculator() {
    setState(() {
      baseDamage = 1;
      weaponBonus = 0;
      skillBonus = 0;
      hasArmor = false;
      hasRattan = false;
      hasEightDiagrams = false;
      isFireDamage = false;
      isThunderDamage = false;
      hasWineEffect = false;
    });
    _animateResult();
  }

  void _saveCalculation() async {
    final model = DamageCalculationModel(
      baseDamage: baseDamage,
      weaponBonus: weaponBonus,
      skillBonus: skillBonus,
      hasArmor: hasArmor,
      hasRattan: hasRattan,
      hasEightDiagrams: hasEightDiagrams,
      isFireDamage: isFireDamage,
      isThunderDamage: isThunderDamage,
      hasWineEffect: hasWineEffect,
      timestamp: DateTime.now(),
    );
    
    await DamageCalculatorService.saveCalculation(model);
    
    Get.snackbar(
      '保存成功',
      '计算结果已保存到历史记录',
      snackPosition: SnackPosition.BOTTOM,
    );
  }

  void _shareCalculation() {
    final shareText = '三国杀伤害计算结果:\n'
        '基础伤害:$baseDamage\n'
        '武器加成:$weaponBonus\n'
        '技能加成:$skillBonus\n'
        '最终伤害:$finalDamage';
    
    Get.snackbar(
      '分享',
      '计算结果已复制到剪贴板',
      snackPosition: SnackPosition.BOTTOM,
    );
  }

  void _loadHistory() async {
    final calculations = await DamageCalculatorService.getCalculationHistory();
    setState(() => history = calculations);
  }

  void _showHistory() {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => Container(
        height: MediaQuery.of(context).size.height * 0.7,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20.r),
            topRight: Radius.circular(20.r),
          ),
        ),
        child: Column(
          children: [
            Container(
              padding: EdgeInsets.all(16.w),
              child: Text(
                '计算历史',
                style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
              ),
            ),
            Expanded(
              child: history.isEmpty
                  ? Center(
                      child: Text(
                        '暂无历史记录',
                        style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade600),
                      ),
                    )
                  : ListView.builder(
                      padding: EdgeInsets.all(16.w),
                      itemCount: history.length,
                      itemBuilder: (context, index) {
                        final result = history[index];
                        return _buildHistoryItem(result);
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHistoryItem(DamageCalculationModel result) {
    final damage = result.calculateFinalDamage();
    
    return Container(
      margin: EdgeInsets.only(bottom: 8.h),
      padding: EdgeInsets.all(12.w),
      decoration: BoxDecoration(
        color: Colors.grey.shade50,
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Row(
        children: [
          Container(
            width: 40.w,
            height: 40.w,
            decoration: BoxDecoration(
              color: Colors.red,
              shape: BoxShape.circle,
            ),
            child: Center(
              child: Text(
                '$damage',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16.sp,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
          SizedBox(width: 12.w),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '基础${result.baseDamage} + 武器${result.weaponBonus} + 技能${result.skillBonus}',
                  style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w500),
                ),
                Text(
                  '${result.timestamp.month}/${result.timestamp.day} ${result.timestamp.hour}:${result.timestamp.minute.toString().padLeft(2, '0')}',
                  style: TextStyle(fontSize: 11.sp, color: Colors.grey.shade600),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  
  void dispose() {
    _resultAnimationController.dispose();
    super.dispose();
  }
}

伤害计算器屏幕使用了多个小组件来组织不同的功能区域。每个区域都有独立的状态管理和交互逻辑。使用 AnimatedBuilder 为计算结果添加了动画效果,提升了用户体验。

总结

通过本文的实现,我们构建了一个功能完整的伤害计算器系统。这个系统不仅提供了精确的伤害计算,还包含了历史记录、分享功能和详细的计算步骤展示。

核心功能特色

  • 多维度的伤害因素计算
  • 实时的伤害结果显示
  • 详细的计算步骤展示
  • 历史记录保存和查看
  • 分享和导出功能
  • 流畅的动画效果
  • 直观的参数调整界面

这个伤害计算器为玩家提供了精确的伤害预测工具,帮助他们在游戏中做出更好的决策。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐