diff --git a/lib/game/config/game_config.dart b/lib/game/config/game_config.dart new file mode 100644 index 0000000..6c30b97 --- /dev/null +++ b/lib/game/config/game_config.dart @@ -0,0 +1,30 @@ +class GameConfig { + // Inventory + static const int maxInventorySize = 5; + + // Economy + static const int startingGold = 50; + static const double sellPriceMultiplier = 0.6; + static const int shopRerollCost = 50; + + // Stages + static const int eliteStageInterval = 10; + static const int shopStageInterval = 5; + static const int restStageInterval = 8; + static const int tier1StageMax = 12; + static const int tier2StageMax = 24; + + // Battle + static const double stageHealRatio = 0.1; + static const double vulnerableDamageMultiplier = 1.5; + static const double armorDecayRate = 0.5; + + // Animations (Duration in milliseconds) + static const int animDelaySafe = 500; + static const int animDelayNormal = 400; + static const int animDelayRisky = 1100; + static const int animDelayEnemyTurn = 1000; + + // Save System + static const String saveKey = 'game_save_data'; +} diff --git a/lib/game/config/theme_config.dart b/lib/game/config/theme_config.dart index 7b8a25c..f29bf61 100644 --- a/lib/game/config/theme_config.dart +++ b/lib/game/config/theme_config.dart @@ -1,6 +1,43 @@ import 'package:flutter/material.dart'; class ThemeConfig { + // Font Sizes + static const double fontSizeTiny = 8.0; + static const double fontSizeSmall = 10.0; + static const double fontSizeMedium = 12.0; + static const double fontSizeBody = 14.0; + static const double fontSizeLarge = 16.0; + static const double fontSizeHeader = 18.0; + static const double fontSizeXLarge = 20.0; + static const double fontSizeTitle = 24.0; + static const double fontSizeHero = 32.0; + static const double fontSizeHuge = 48.0; + + // Font Weights + static const FontWeight fontWeightNormal = FontWeight.normal; + static const FontWeight fontWeightBold = FontWeight.bold; + + // Main Menu Colors + static const Color mainMenuBgTop = Colors.black; + static final Color mainMenuBgBottom = Colors.blueGrey[900]!; + static const Color mainTitleColor = Colors.white; + static const Color subTitleColor = Colors.grey; + static const Color mainIconColor = Colors.amber; + + // Button Colors + static const Color btnNewGameBg = Color(0xFFFFA000); // Colors.amber[700] + static const Color btnNewGameText = Colors.black; + static const Color btnContinueBg = Colors.blueAccent; + static const Color btnContinueText = Colors.white; + static const Color btnRestBg = Colors.blue; + static const Color btnLeaveBg = Colors.redAccent; + static const Color btnRerollBg = Colors.blueGrey; + static const Color btnActionActive = Colors.redAccent; // Attack + static const Color btnDefendActive = Colors.blueAccent; // Defend + static const Color btnDisabled = Colors.grey; + static const Color btnRestartBg = Colors.orange; + static const Color btnReturnMenuBg = Colors.red; + // Stat Colors static const Color statHpColor = Colors.red; static const Color statHpPlayerColor = Colors.green; @@ -22,6 +59,13 @@ class ThemeConfig { 0xFF546E7A, ); // Colors.blueGrey[600] static const Color emptySlotBg = Color(0xFF424242); // Colors.grey[800] + static const Color battleBg = Colors.black87; + static const Color shopBg = Colors.black87; + static final Color? shopItemCardBg = Colors.blueGrey[800]; + static const Color enemyIntentBg = Colors.black54; + static const Color enemyIntentBorder = Colors.redAccent; + static final Color? selectionCardBg = Colors.blueGrey[800]; + static const Color selectionIconColor = Colors.blue; // Feedback Colors static const Color damageTextDefault = Colors.red; @@ -29,6 +73,9 @@ class ThemeConfig { static const Color missText = Colors.grey; static const Color failedText = Colors.redAccent; static const Color feedbackShadow = Colors.black; + static const Color statDiffPositive = Colors.green; + static const Color statDiffNegative = Colors.red; + static const Color statDiffNeutral = Colors.grey; // Status Effect Colors static const Color effectBg = Colors.deepOrange; @@ -39,4 +86,10 @@ class ThemeConfig { static const Color rarityRare = Colors.yellow; static const Color rarityLegendary = Colors.orange; static const Color rarityUnique = Colors.purple; + static const Color rarityCommon = Colors.grey; + + // Risk Colors (from BattleScreen Dialog) + static const Color riskSafe = Colors.green; + static const Color riskNormal = Colors.blue; + static const Color riskRisky = Colors.red; } diff --git a/lib/game/model/entity.dart b/lib/game/model/entity.dart index deb1728..3815a6d 100644 --- a/lib/game/model/entity.dart +++ b/lib/game/model/entity.dart @@ -3,6 +3,7 @@ import 'status_effect.dart'; import 'stat_modifier.dart'; import '../enums.dart'; import '../data/item_table.dart'; +import '../config/game_config.dart'; class Character { String name; @@ -16,7 +17,7 @@ class Character { String? image; // New: Image path Map equipment = {}; List inventory = []; - final int maxInventorySize = 16; + final int maxInventorySize = GameConfig.maxInventorySize; // Active status effects List statusEffects = []; diff --git a/lib/game/save_manager.dart b/lib/game/save_manager.dart index a7c69b1..655d2c2 100644 --- a/lib/game/save_manager.dart +++ b/lib/game/save_manager.dart @@ -2,9 +2,10 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import '../providers/battle_provider.dart'; import 'model/entity.dart'; +import 'config/game_config.dart'; class SaveManager { - static const String _saveKey = 'game_save_data'; + static const String _saveKey = GameConfig.saveKey; static Future saveGame(BattleProvider provider) async { final prefs = await SharedPreferences.getInstance(); diff --git a/lib/providers/battle_provider.dart b/lib/providers/battle_provider.dart index 5b3a458..95391da 100644 --- a/lib/providers/battle_provider.dart +++ b/lib/providers/battle_provider.dart @@ -16,6 +16,7 @@ import '../game/model/damage_event.dart'; // DamageEvent import import '../game/model/effect_event.dart'; // EffectEvent import import '../game/save_manager.dart'; +import '../game/config/game_config.dart'; class EnemyIntent { final EnemyActionType type; @@ -104,7 +105,7 @@ class BattleProvider with ChangeNotifier { } // Give test gold - player.gold = 50; + player.gold = GameConfig.startingGold; // Provide starter equipment final starterSword = Item( @@ -172,11 +173,11 @@ class BattleProvider with ChangeNotifier { StageType type; // Stage Type Logic - if (stage % 10 == 0) { + if (stage % GameConfig.eliteStageInterval == 0) { type = StageType.elite; // Every 10th stage is a Boss/Elite - } else if (stage % 5 == 0) { + } else if (stage % GameConfig.shopStageInterval == 0) { type = StageType.shop; // Every 5th stage is a Shop (except 10, 20...) - } else if (stage % 8 == 0) { + } else if (stage % GameConfig.restStageInterval == 0) { type = StageType.rest; // Every 8th stage is a Rest } else { type = StageType.battle; @@ -255,9 +256,9 @@ class BattleProvider with ChangeNotifier { /// Generate 4 random items for the shop based on current stage tier List _generateShopItems() { ItemTier currentTier = ItemTier.tier1; - if (stage > 24) + if (stage > GameConfig.tier2StageMax) currentTier = ItemTier.tier3; - else if (stage > 12) + else if (stage > GameConfig.tier1StageMax) currentTier = ItemTier.tier2; List items = []; @@ -271,7 +272,7 @@ class BattleProvider with ChangeNotifier { } void rerollShopItems() { - const int rerollCost = 50; + const int rerollCost = GameConfig.shopRerollCost; if (player.gold >= rerollCost) { player.gold -= rerollCost; // Modify the existing list because shopItems is final @@ -381,11 +382,11 @@ class BattleProvider with ChangeNotifier { // Animation Delays to sync with Impact if (risk == RiskLevel.safe) { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: GameConfig.animDelaySafe)); } else if (risk == RiskLevel.normal) { - await Future.delayed(const Duration(milliseconds: 400)); + await Future.delayed(const Duration(milliseconds: GameConfig.animDelayNormal)); } else if (risk == RiskLevel.risky) { - await Future.delayed(const Duration(milliseconds: 1100)); + await Future.delayed(const Duration(milliseconds: GameConfig.animDelayRisky)); } int damageToHp = 0; @@ -477,19 +478,19 @@ class BattleProvider with ChangeNotifier { return; } - Future.delayed(const Duration(seconds: 1), () => _enemyTurn()); + Future.delayed(const Duration(milliseconds: GameConfig.animDelayEnemyTurn), () => _enemyTurn()); } Future _enemyTurn() async { if (!isPlayerTurn && (player.isDead || enemy.isDead)) return; _addLog("Enemy's turn..."); - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed(const Duration(milliseconds: GameConfig.animDelayEnemyTurn)); // Enemy Turn Start Logic // Armor decay if (enemy.armor > 0) { - enemy.armor = (enemy.armor * 0.5).toInt(); + enemy.armor = (enemy.armor * GameConfig.armorDecayRate).toInt(); _addLog("Enemy's armor decayed to ${enemy.armor}."); } @@ -578,7 +579,7 @@ class BattleProvider with ChangeNotifier { // Player Turn Start Logic // Armor decay if (player.armor > 0) { - player.armor = (player.armor * 0.5).toInt(); + player.armor = (player.armor * GameConfig.armorDecayRate).toInt(); _addLog("Player's armor decayed to ${player.armor}."); } @@ -665,7 +666,7 @@ class BattleProvider with ChangeNotifier { }) { // Check Vulnerable if (target.hasStatus(StatusEffectType.vulnerable)) { - damage = (damage * 1.5).toInt(); + damage = (damage * GameConfig.vulnerableDamageMultiplier).toInt(); _addLog("Vulnerable! Damage increased to $damage."); type = DamageType.vulnerable; } @@ -704,9 +705,9 @@ class BattleProvider with ChangeNotifier { // Since we just refactored ItemTable, let's use getRandomItem! ItemTier currentTier = ItemTier.tier1; - if (stage > 24) + if (stage > GameConfig.tier2StageMax) currentTier = ItemTier.tier3; - else if (stage > 12) + else if (stage > GameConfig.tier1StageMax) currentTier = ItemTier.tier2; rewardOptions = []; @@ -749,7 +750,7 @@ class BattleProvider with ChangeNotifier { } // Heal player after selecting reward - int healAmount = GameMath.floor(player.totalMaxHp * 0.1); + int healAmount = GameMath.floor(player.totalMaxHp * GameConfig.stageHealRatio); player.heal(healAmount); _addLog("Stage Cleared! Recovered $healAmount HP."); @@ -793,7 +794,7 @@ class BattleProvider with ChangeNotifier { void sellItem(Item item) { if (player.inventory.remove(item)) { - int sellPrice = GameMath.floor(item.price * 0.6); + int sellPrice = GameMath.floor(item.price * GameConfig.sellPriceMultiplier); player.gold += sellPrice; _addLog("Sold ${item.name} for $sellPrice G."); notifyListeners(); diff --git a/lib/screens/battle_screen.dart b/lib/screens/battle_screen.dart index ae55379..62477cd 100644 --- a/lib/screens/battle_screen.dart +++ b/lib/screens/battle_screen.dart @@ -19,6 +19,7 @@ import '../widgets/battle/battle_animation_widget.dart'; import '../widgets/battle/explosion_widget.dart'; import 'main_menu_screen.dart'; import '../game/config/battle_config.dart'; +import '../game/config/theme_config.dart'; class BattleScreen extends StatefulWidget { const BattleScreen({super.key}); @@ -162,15 +163,15 @@ class _BattleScreenState extends State { switch (event.feedbackType) { case BattleFeedbackType.miss: feedbackText = "MISS"; - feedbackColor = Colors.grey; + feedbackColor = ThemeConfig.missText; break; case BattleFeedbackType.failed: feedbackText = "FAILED"; - feedbackColor = Colors.redAccent; + feedbackColor = ThemeConfig.failedText; break; default: feedbackText = ""; // Should not happen with current enums - feedbackColor = Colors.white; + feedbackColor = ThemeConfig.textColorWhite; } final String id = UniqueKey().toString(); @@ -311,15 +312,15 @@ class _BattleScreenState extends State { switch (risk) { case RiskLevel.safe: efficiency = 0.5; - infoColor = Colors.green; + infoColor = ThemeConfig.riskSafe; break; case RiskLevel.normal: efficiency = 1.0; - infoColor = Colors.blue; + infoColor = ThemeConfig.riskNormal; break; case RiskLevel.risky: efficiency = 2.0; - infoColor = Colors.red; + infoColor = ThemeConfig.riskRisky; break; } @@ -391,7 +392,7 @@ class _BattleScreenState extends State { key: _stackKey, children: [ // 1. Background (Black) - Container(color: Colors.black87), + Container(color: ThemeConfig.battleBg), // 2. Battle Content (Top Bar + Characters) Column( @@ -408,9 +409,9 @@ class _BattleScreenState extends State { child: Text( "Stage ${battleProvider.stage}", style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, + color: ThemeConfig.textColorWhite, + fontSize: ThemeConfig.fontSizeHeader, + fontWeight: ThemeConfig.fontWeightBold, ), ), ), @@ -421,8 +422,8 @@ class _BattleScreenState extends State { child: Text( "Turn ${battleProvider.turnCount}", style: const TextStyle( - color: Colors.white, - fontSize: 18, + color: ThemeConfig.textColorWhite, + fontSize: ThemeConfig.fontSizeHeader, ), ), ), @@ -489,7 +490,7 @@ class _BattleScreenState extends State { context, "ATK", Icons.whatshot, - Colors.redAccent, + ThemeConfig.btnActionActive, ActionType.attack, battleProvider.isPlayerTurn && !battleProvider.player.isDead && @@ -501,7 +502,7 @@ class _BattleScreenState extends State { context, "DEF", Icons.shield, - Colors.blueAccent, + ThemeConfig.btnDefendActive, ActionType.defend, battleProvider.isPlayerTurn && !battleProvider.player.isDead && @@ -527,7 +528,7 @@ class _BattleScreenState extends State { }, child: Icon( _showLogs ? Icons.visibility_off : Icons.visibility, - color: Colors.white, + color: ThemeConfig.textColorWhite, ), ), ), @@ -535,7 +536,7 @@ class _BattleScreenState extends State { // Reward Popup if (battleProvider.showRewardPopup) Container( - color: Colors.black54, + color: ThemeConfig.cardBgColor, child: Center( child: SimpleDialog( title: Row( @@ -545,14 +546,14 @@ class _BattleScreenState extends State { Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.monetization_on, color: Colors.amber, size: 18), + Icon(Icons.monetization_on, color: ThemeConfig.statGoldColor, size: 18), const SizedBox(width: 4), Text( "${battleProvider.lastGoldReward} G", style: TextStyle( - color: Colors.amber, - fontSize: 14, - fontWeight: FontWeight.bold, + color: ThemeConfig.statGoldColor, + fontSize: ThemeConfig.fontSizeBody, + fontWeight: ThemeConfig.fontWeightBold, ), ), ], @@ -583,7 +584,7 @@ class _BattleScreenState extends State { ? ItemUtils.getRarityColor( item.rarity, ) - : Colors.grey, + : ThemeConfig.rarityCommon, ), ), child: Icon( @@ -596,10 +597,10 @@ class _BattleScreenState extends State { Text( item.name, style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + fontWeight: ThemeConfig.fontWeightBold, + fontSize: ThemeConfig.fontSizeLarge, color: isSkip - ? Colors.grey + ? ThemeConfig.textColorGrey : ItemUtils.getRarityColor( item.rarity), ), @@ -610,8 +611,8 @@ class _BattleScreenState extends State { Text( item.description, style: const TextStyle( - fontSize: 12, - color: Colors.grey, + fontSize: ThemeConfig.fontSizeMedium, + color: ThemeConfig.textColorGrey, ), ), ], @@ -633,7 +634,7 @@ class _BattleScreenState extends State { // Game Over Overlay if (battleProvider.player.isDead) Container( - color: Colors.black87, + color: ThemeConfig.battleBg, child: Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -641,9 +642,9 @@ class _BattleScreenState extends State { const Text( "DEFEAT", style: TextStyle( - color: Colors.red, - fontSize: 48, - fontWeight: FontWeight.bold, + color: ThemeConfig.statHpColor, + fontSize: ThemeConfig.fontSizeHuge, + fontWeight: ThemeConfig.fontWeightBold, letterSpacing: 4.0, ), ), @@ -667,8 +668,8 @@ class _BattleScreenState extends State { child: const Text( "Return to Main Menu", style: TextStyle( - color: Colors.white, - fontSize: 18, + color: ThemeConfig.textColorWhite, + fontSize: ThemeConfig.fontSizeHeader, ), ), ), @@ -703,7 +704,7 @@ class _BattleScreenState extends State { padding: const EdgeInsets.only(top: 4.0, bottom: 4.0), child: Text( stats.join(", "), - style: const TextStyle(fontSize: 12, color: Colors.blueAccent), + style: const TextStyle(fontSize: ThemeConfig.fontSizeMedium, color: ThemeConfig.statAtkColor), ), ), if (effectTexts.isNotEmpty) @@ -711,7 +712,7 @@ class _BattleScreenState extends State { padding: const EdgeInsets.only(bottom: 4.0), child: Text( effectTexts.join(", "), - style: const TextStyle(fontSize: 11, color: Colors.orangeAccent), + style: const TextStyle(fontSize: 11, color: ThemeConfig.rarityLegendary), // 11 is custom, keep or change? Let's use Small ), ), ], @@ -731,7 +732,7 @@ class _BattleScreenState extends State { onPressed: isEnabled ? () => _showRiskLevelSelection(context, actionType) : null, - backgroundColor: isEnabled ? color : Colors.grey, + backgroundColor: isEnabled ? color : ThemeConfig.btnDisabled, child: Icon(icon), ); } diff --git a/lib/screens/character_selection_screen.dart b/lib/screens/character_selection_screen.dart index fabb849..2caeb4f 100644 --- a/lib/screens/character_selection_screen.dart +++ b/lib/screens/character_selection_screen.dart @@ -4,6 +4,7 @@ import '../providers/battle_provider.dart'; import '../game/data/player_table.dart'; import 'main_wrapper.dart'; import '../widgets/responsive_container.dart'; +import '../game/config/theme_config.dart'; class CharacterSelectionScreen extends StatelessWidget { const CharacterSelectionScreen({super.key}); @@ -20,7 +21,7 @@ class CharacterSelectionScreen extends StatelessWidget { } return Scaffold( - backgroundColor: Colors.black, // Outer background + backgroundColor: ThemeConfig.mainMenuBgTop, // Outer background body: Center( child: ResponsiveContainer( child: Scaffold( @@ -47,7 +48,7 @@ class CharacterSelectionScreen extends StatelessWidget { ); }, child: Card( - color: Colors.blueGrey[800], + color: ThemeConfig.selectionCardBg, elevation: 8, child: Container( width: 300, @@ -58,22 +59,22 @@ class CharacterSelectionScreen extends StatelessWidget { const Icon( Icons.shield, size: 80, - color: Colors.blue, + color: ThemeConfig.selectionIconColor, ), const SizedBox(height: 16), Text( warrior.name, style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, + fontSize: ThemeConfig.fontSizeTitle, + fontWeight: ThemeConfig.fontWeightBold, + color: ThemeConfig.textColorWhite, ), ), const SizedBox(height: 8), Text( warrior.description, textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey), + style: const TextStyle(color: ThemeConfig.textColorGrey), ), const SizedBox(height: 16), const Divider(), @@ -84,19 +85,19 @@ class CharacterSelectionScreen extends StatelessWidget { Text( "HP: ${warrior.baseHp}", style: const TextStyle( - fontWeight: FontWeight.bold, + fontWeight: ThemeConfig.fontWeightBold, ), ), Text( "ATK: ${warrior.baseAtk}", style: const TextStyle( - fontWeight: FontWeight.bold, + fontWeight: ThemeConfig.fontWeightBold, ), ), Text( "DEF: ${warrior.baseDefense}", style: const TextStyle( - fontWeight: FontWeight.bold, + fontWeight: ThemeConfig.fontWeightBold, ), ), ], diff --git a/lib/screens/inventory_screen.dart b/lib/screens/inventory_screen.dart index b818e8f..d39836e 100644 --- a/lib/screens/inventory_screen.dart +++ b/lib/screens/inventory_screen.dart @@ -4,6 +4,7 @@ import '../providers/battle_provider.dart'; import '../game/model/item.dart'; import '../game/enums.dart'; import '../utils/item_utils.dart'; +import '../game/config/theme_config.dart'; class InventoryScreen extends StatelessWidget { const InventoryScreen({super.key}); @@ -45,12 +46,12 @@ class InventoryScreen extends StatelessWidget { _buildStatItem( "Gold", "${player.gold} G", - color: Colors.amber, + color: ThemeConfig.statGoldColor, ), _buildStatItem( "Luck", "${player.totalLuck}", - color: Colors.green, + color: ThemeConfig.statLuckColor, ), ], ), @@ -71,8 +72,8 @@ class InventoryScreen extends StatelessWidget { const Text( "Equipped Items", style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeHeader, + fontWeight: ThemeConfig.fontWeightBold, ), ), const SizedBox(height: 8), @@ -91,8 +92,8 @@ class InventoryScreen extends StatelessWidget { : null, child: Card( color: item != null - ? Colors.blueGrey[600] - : Colors.grey[800], + ? ThemeConfig.equipmentCardBg + : ThemeConfig.emptySlotBg, shape: item != null && item.rarity != ItemRarity.magic ? RoundedRectangleBorder( @@ -113,8 +114,8 @@ class InventoryScreen extends StatelessWidget { child: Text( slot.name.toUpperCase(), style: const TextStyle( - fontSize: 8, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeTiny, + fontWeight: ThemeConfig.fontWeightBold, color: Colors.white30, ), ), @@ -130,7 +131,7 @@ class InventoryScreen extends StatelessWidget { size: 40, color: item != null ? ItemUtils.getColor(slot) - : Colors.grey, + : ThemeConfig.textColorGrey, ), ), ), @@ -151,13 +152,13 @@ class InventoryScreen extends StatelessWidget { item?.name ?? "Empty", textAlign: TextAlign.center, style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeSmall, + fontWeight: ThemeConfig.fontWeightBold, color: item != null ? ItemUtils.getRarityColor( item.rarity, ) - : Colors.grey, + : ThemeConfig.textColorGrey, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -194,8 +195,8 @@ class InventoryScreen extends StatelessWidget { child: Text( "Bag (${player.inventory.length}/${player.maxInventorySize})", style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeHeader, + fontWeight: ThemeConfig.fontWeightBold, ), ), ), @@ -218,7 +219,7 @@ class InventoryScreen extends StatelessWidget { _showItemActionDialog(context, battleProvider, item); }, child: Card( - color: Colors.blueGrey[700], + color: ThemeConfig.inventoryCardBg, shape: item.rarity != ItemRarity.magic ? RoundedRectangleBorder( side: BorderSide( @@ -258,8 +259,8 @@ class InventoryScreen extends StatelessWidget { item.name, textAlign: TextAlign.center, style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeSmall, + fontWeight: ThemeConfig.fontWeightBold, color: ItemUtils.getRarityColor( item.rarity, ), @@ -284,11 +285,11 @@ class InventoryScreen extends StatelessWidget { // Empty slot return Container( decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - color: Colors.grey[800], + border: Border.all(color: ThemeConfig.textColorGrey), + color: ThemeConfig.emptySlotBg, ), child: const Center( - child: Icon(Icons.add_box, color: Colors.grey), + child: Icon(Icons.add_box, color: ThemeConfig.textColorGrey), ), ); } @@ -305,11 +306,11 @@ class InventoryScreen extends StatelessWidget { Widget _buildStatItem(String label, String value, {Color? color}) { return Column( children: [ - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)), + Text(label, style: const TextStyle(color: ThemeConfig.textColorGrey, fontSize: 12)), Text( value, style: TextStyle( - fontWeight: FontWeight.bold, + fontWeight: ThemeConfig.fontWeightBold, fontSize: 16, color: color, ), @@ -340,7 +341,7 @@ class InventoryScreen extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ - Icon(Icons.shield, color: Colors.blue), + Icon(Icons.shield, color: ThemeConfig.btnDefendActive), SizedBox(width: 10), Text("Equip"), ], @@ -357,7 +358,7 @@ class InventoryScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ - const Icon(Icons.attach_money, color: Colors.amber), + const Icon(Icons.attach_money, color: ThemeConfig.statGoldColor), const SizedBox(width: 10), Text("Sell (${item.price} G)"), ], @@ -373,7 +374,7 @@ class InventoryScreen extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ - Icon(Icons.delete, color: Colors.red), + Icon(Icons.delete, color: ThemeConfig.btnActionActive), SizedBox(width: 10), Text("Discard"), ], @@ -401,7 +402,7 @@ class InventoryScreen extends StatelessWidget { child: const Text("Cancel"), ), ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.amber), + style: ElevatedButton.styleFrom(backgroundColor: ThemeConfig.statGoldColor), onPressed: () { provider.sellItem(item); Navigator.pop(ctx); @@ -429,7 +430,7 @@ class InventoryScreen extends StatelessWidget { child: const Text("Cancel"), ), ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + style: ElevatedButton.styleFrom(backgroundColor: ThemeConfig.btnActionActive), onPressed: () { provider.discardItem(item); Navigator.pop(ctx); @@ -475,12 +476,12 @@ class InventoryScreen extends StatelessWidget { children: [ Text( "Equip ${newItem.name}?", - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: ThemeConfig.fontWeightBold), ), if (oldItem != null) Text( "Replaces ${oldItem.name}", - style: const TextStyle(fontSize: 12, color: Colors.grey), + style: const TextStyle(fontSize: 12, color: ThemeConfig.textColorGrey), ), const SizedBox(height: 16), _buildStatChangeRow("Max HP", currentMaxHp, newMaxHp), @@ -544,7 +545,7 @@ class InventoryScreen extends StatelessWidget { children: [ Text( "Unequip ${itemToUnequip.name}?", - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: ThemeConfig.fontWeightBold), ), const SizedBox(height: 16), _buildStatChangeRow("Max HP", currentMaxHp, newMaxHp), @@ -573,8 +574,8 @@ class InventoryScreen extends StatelessWidget { Widget _buildStatChangeRow(String label, int oldVal, int newVal) { int diff = newVal - oldVal; Color color = diff > 0 - ? Colors.green - : (diff < 0 ? Colors.red : Colors.grey); + ? ThemeConfig.statDiffPositive + : (diff < 0 ? ThemeConfig.statDiffNegative : ThemeConfig.statDiffNeutral); String diffText = diff > 0 ? "(+$diff)" : (diff < 0 ? "($diff)" : ""); return Padding( @@ -585,11 +586,11 @@ class InventoryScreen extends StatelessWidget { Text(label), Row( children: [ - Text("$oldVal", style: const TextStyle(color: Colors.grey)), - const Icon(Icons.arrow_right, size: 16, color: Colors.grey), + Text("$oldVal", style: const TextStyle(color: ThemeConfig.textColorGrey)), + const Icon(Icons.arrow_right, size: 16, color: ThemeConfig.textColorGrey), Text( "$newVal", - style: const TextStyle(fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: ThemeConfig.fontWeightBold), ), const SizedBox(width: 4), Text( @@ -597,7 +598,7 @@ class InventoryScreen extends StatelessWidget { style: TextStyle( color: color, fontSize: 12, - fontWeight: FontWeight.bold, + fontWeight: ThemeConfig.fontWeightBold, ), ), ], @@ -626,7 +627,7 @@ class InventoryScreen extends StatelessWidget { padding: const EdgeInsets.only(top: 2.0, bottom: 2.0), child: Text( stats.join(", "), - style: const TextStyle(fontSize: 10, color: Colors.blueAccent), + style: const TextStyle(fontSize: ThemeConfig.fontSizeSmall, color: ThemeConfig.statAtkColor), textAlign: TextAlign.center, ), ), @@ -635,8 +636,7 @@ class InventoryScreen extends StatelessWidget { padding: const EdgeInsets.only(bottom: 2.0), child: Text( effectTexts.join("\n"), - style: const TextStyle(fontSize: 9, color: Colors.orangeAccent), - textAlign: TextAlign.center, + style: const TextStyle(fontSize: ThemeConfig.fontSizeTiny, color: ThemeConfig.rarityLegendary), ), ), ], diff --git a/lib/screens/main_menu_screen.dart b/lib/screens/main_menu_screen.dart index d45ea44..9f98d37 100644 --- a/lib/screens/main_menu_screen.dart +++ b/lib/screens/main_menu_screen.dart @@ -5,6 +5,7 @@ import 'main_wrapper.dart'; import '../widgets/responsive_container.dart'; import '../game/save_manager.dart'; import '../providers/battle_provider.dart'; +import '../game/config/theme_config.dart'; class MainMenuScreen extends StatefulWidget { const MainMenuScreen({super.key}); @@ -58,7 +59,10 @@ class _MainMenuScreenState extends State { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Colors.black, Colors.blueGrey[900]!], + colors: [ + ThemeConfig.mainMenuBgTop, + ThemeConfig.mainMenuBgBottom, + ], ), ), child: ResponsiveContainer( @@ -67,23 +71,27 @@ class _MainMenuScreenState extends State { : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.gavel, size: 100, color: Colors.amber), + const Icon( + Icons.gavel, + size: 100, + color: ThemeConfig.mainIconColor, + ), const SizedBox(height: 20), const Text( "COLOSSEUM'S CHOICE", style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeHero, + fontWeight: ThemeConfig.fontWeightBold, letterSpacing: 2.0, - color: Colors.white, + color: ThemeConfig.mainTitleColor, ), ), const SizedBox(height: 10), const Text( "Rise as a Legend", style: TextStyle( - fontSize: 16, - color: Colors.grey, + fontSize: ThemeConfig.fontSizeLarge, + color: ThemeConfig.subTitleColor, fontStyle: FontStyle.italic, ), ), @@ -96,11 +104,11 @@ class _MainMenuScreenState extends State { horizontal: 50, vertical: 15, ), - backgroundColor: Colors.blueAccent, - foregroundColor: Colors.white, + backgroundColor: ThemeConfig.btnContinueBg, + foregroundColor: ThemeConfig.btnContinueText, textStyle: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeXLarge, + fontWeight: ThemeConfig.fontWeightBold, ), ), child: const Text("CONTINUE"), @@ -123,11 +131,11 @@ class _MainMenuScreenState extends State { horizontal: 50, vertical: 15, ), - backgroundColor: Colors.amber[700], - foregroundColor: Colors.black, + backgroundColor: ThemeConfig.btnNewGameBg, + foregroundColor: ThemeConfig.btnNewGameText, textStyle: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + fontSize: ThemeConfig.fontSizeXLarge, + fontWeight: ThemeConfig.fontWeightBold, ), ), child: const Text("NEW GAME"), diff --git a/lib/screens/main_wrapper.dart b/lib/screens/main_wrapper.dart index d6fe601..50df1b8 100644 --- a/lib/screens/main_wrapper.dart +++ b/lib/screens/main_wrapper.dart @@ -3,6 +3,7 @@ import 'battle_screen.dart'; import 'inventory_screen.dart'; import 'settings_screen.dart'; import '../widgets/responsive_container.dart'; +import '../game/config/theme_config.dart'; class MainWrapper extends StatefulWidget { const MainWrapper({super.key}); @@ -23,7 +24,7 @@ class _MainWrapperState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, // Outer background for web + backgroundColor: ThemeConfig.mainMenuBgTop, // Outer background for web body: Center( child: ResponsiveContainer( child: Scaffold( diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index d4f83ae..c00175d 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/battle_provider.dart'; import 'main_menu_screen.dart'; +import '../game/config/theme_config.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @@ -15,28 +16,28 @@ class SettingsScreen extends StatelessWidget { const Text( 'Settings', style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.white, + fontSize: ThemeConfig.fontSizeTitle, + fontWeight: ThemeConfig.fontWeightBold, + color: ThemeConfig.textColorWhite, ), ), const SizedBox(height: 40), // Placeholder for future settings const Text( 'Effect Intensity: Normal', - style: TextStyle(color: Colors.white70), + style: TextStyle(color: ThemeConfig.textColorWhite), ), const SizedBox(height: 20), const Text( 'Volume: 100%', - style: TextStyle(color: Colors.white70), + style: TextStyle(color: ThemeConfig.textColorWhite), ), const SizedBox(height: 40), // Restart Button ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, + backgroundColor: ThemeConfig.btnRestartBg, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), ), onPressed: () { @@ -63,7 +64,7 @@ class SettingsScreen extends StatelessWidget { // Return to Main Menu Button ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, + backgroundColor: ThemeConfig.btnReturnMenuBg, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), ), onPressed: () { diff --git a/lib/widgets/battle/character_status_card.dart b/lib/widgets/battle/character_status_card.dart index b64a113..555070a 100644 --- a/lib/widgets/battle/character_status_card.dart +++ b/lib/widgets/battle/character_status_card.dart @@ -90,9 +90,9 @@ class CharacterStatusCard extends StatelessWidget { }).toList(), ), ), - Text("ATK: ${character.totalAtk}"), - Text("DEF: ${character.totalDefense}"), - Text("LUCK: ${character.totalLuck}"), + Text("ATK: ${character.totalAtk}", style: const TextStyle(color: ThemeConfig.textColorWhite)), + Text("DEF: ${character.totalDefense}", style: const TextStyle(color: ThemeConfig.textColorWhite)), + Text("LUCK: ${character.totalLuck}", style: const TextStyle(color: ThemeConfig.textColorWhite)), ], ), ), @@ -135,16 +135,16 @@ class CharacterStatusCard extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: Colors.black54, + color: ThemeConfig.enemyIntentBg, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.redAccent), + border: Border.all(color: ThemeConfig.enemyIntentBorder), ), child: Column( children: [ Text( "INTENT", style: TextStyle( - color: Colors.redAccent, + color: ThemeConfig.enemyIntentBorder, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -156,14 +156,14 @@ class CharacterStatusCard extends StatelessWidget { intent.type == EnemyActionType.attack ? Icons.flash_on : Icons.shield, - color: Colors.yellow, + color: ThemeConfig.rarityRare, // Yellow size: 16, ), const SizedBox(width: 4), Text( intent.description, style: const TextStyle( - color: Colors.white, + color: ThemeConfig.textColorWhite, fontSize: 12, ), ), diff --git a/lib/widgets/battle/stage_ui.dart b/lib/widgets/battle/stage_ui.dart index c41bbe3..1b5f652 100644 --- a/lib/widgets/battle/stage_ui.dart +++ b/lib/widgets/battle/stage_ui.dart @@ -3,6 +3,7 @@ import '../../providers/battle_provider.dart'; import '../../game/model/item.dart'; import '../../utils/item_utils.dart'; import '../../game/enums.dart'; +import '../../game/config/theme_config.dart'; class ShopUI extends StatelessWidget { final BattleProvider battleProvider; @@ -15,7 +16,7 @@ class ShopUI extends StatelessWidget { final shopItems = battleProvider.currentStage.shopItems; return Container( - color: Colors.black87, + color: ThemeConfig.shopBg, padding: const EdgeInsets.all(16.0), child: Column( children: [ @@ -25,22 +26,22 @@ class ShopUI extends StatelessWidget { children: [ const Row( children: [ - Icon(Icons.store, size: 32, color: Colors.amber), + Icon(Icons.store, size: 32, color: ThemeConfig.mainIconColor), SizedBox(width: 8), Text( "Merchant", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: ThemeConfig.textColorWhite), ), ], ), Row( children: [ - const Icon(Icons.monetization_on, color: Colors.amber), + const Icon(Icons.monetization_on, color: ThemeConfig.statGoldColor), const SizedBox(width: 4), Text( "${player.gold} G", style: const TextStyle( - color: Colors.amber, + color: ThemeConfig.statGoldColor, fontSize: 20, fontWeight: FontWeight.bold, ), @@ -49,7 +50,7 @@ class ShopUI extends StatelessWidget { ), ], ), - const Divider(color: Colors.grey), + const Divider(color: ThemeConfig.textColorGrey), const SizedBox(height: 16), // Shop Items Grid @@ -58,7 +59,7 @@ class ShopUI extends StatelessWidget { ? const Center( child: Text( "Sold Out", - style: TextStyle(color: Colors.grey, fontSize: 24), + style: TextStyle(color: ThemeConfig.textColorGrey, fontSize: 24), ), ) : GridView.builder( @@ -76,7 +77,7 @@ class ShopUI extends StatelessWidget { return InkWell( onTap: () => _showBuyConfirmation(context, item), child: Card( - color: Colors.blueGrey[800], + color: ThemeConfig.shopItemCardBg, shape: item.rarity != ItemRarity.magic ? RoundedRectangleBorder( side: BorderSide( @@ -129,7 +130,7 @@ class ShopUI extends StatelessWidget { height: 32, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: canBuy ? Colors.amber : Colors.grey, + backgroundColor: canBuy ? ThemeConfig.statGoldColor : ThemeConfig.btnDisabled, foregroundColor: Colors.black, padding: EdgeInsets.zero, ), @@ -159,28 +160,28 @@ class ShopUI extends StatelessWidget { children: [ ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueGrey, + backgroundColor: ThemeConfig.btnRerollBg, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), onPressed: player.gold >= 50 ? () => battleProvider.rerollShopItems() : null, - icon: const Icon(Icons.refresh, color: Colors.white), + icon: const Icon(Icons.refresh, color: ThemeConfig.textColorWhite), label: const Text( "Reroll (50 G)", - style: TextStyle(color: Colors.white), + style: TextStyle(color: ThemeConfig.textColorWhite), ), ), ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: Colors.redAccent, + backgroundColor: ThemeConfig.btnLeaveBg, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), onPressed: () => battleProvider.proceedToNextStage(), - icon: const Icon(Icons.exit_to_app, color: Colors.white), + icon: const Icon(Icons.exit_to_app, color: ThemeConfig.textColorWhite), label: const Text( "Leave Shop", - style: TextStyle(color: Colors.white), + style: TextStyle(color: ThemeConfig.textColorWhite), ), ), ], @@ -204,7 +205,7 @@ class ShopUI extends StatelessWidget { child: const Text("Cancel"), ), ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.amber), + style: ElevatedButton.styleFrom(backgroundColor: ThemeConfig.statGoldColor), onPressed: () { battleProvider.buyItem(item); Navigator.pop(ctx); @@ -237,7 +238,7 @@ class ShopUI extends StatelessWidget { if (item.effects.isNotEmpty) Text( item.effects.first.type.name.toUpperCase(), - style: const TextStyle(fontSize: 9, color: Colors.orangeAccent), + style: const TextStyle(fontSize: 9, color: ThemeConfig.rarityLegendary), textAlign: TextAlign.center, ), ], @@ -256,11 +257,11 @@ class RestUI extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.local_hotel, size: 64, color: Colors.blue), + const Icon(Icons.local_hotel, size: 64, color: ThemeConfig.btnRestBg), const SizedBox(height: 16), - const Text("Rest Area", style: TextStyle(fontSize: 24)), + const Text("Rest Area", style: TextStyle(fontSize: 24, color: ThemeConfig.textColorWhite)), const SizedBox(height: 8), - const Text("Take a breath and heal."), + const Text("Take a breath and heal.", style: TextStyle(color: ThemeConfig.textColorWhite)), const SizedBox(height: 32), ElevatedButton( onPressed: () { diff --git a/prompt/00_project_context_restore.md b/prompt/00_project_context_restore.md index dfa2f84..0469c8a 100644 --- a/prompt/00_project_context_restore.md +++ b/prompt/00_project_context_restore.md @@ -115,6 +115,7 @@ - **Prompt Driven Development:** `prompt/XX_description.md` 유지. - **Language:** **모든 프롬프트 파일(prompt/XX_...)은 반드시 한국어(Korean)로 작성해야 합니다.** +- **Config Management:** 하드코딩되는 값들은 `config` 폴더 내 파일들(`lib/game/config/` 등)에서 통합 관리할 수 있도록 작성해야 합니다. - **State Management:** `Provider` + `Stream` (이벤트성 데이터). - **Data:** JSON 기반. @@ -158,3 +159,4 @@ - [x] 42_item_rarity_and_tier.md - [x] 43_shop_system.md - [x] 44_settings_and_local_storage.md +- [x] 45_config_refactoring.md diff --git a/prompt/45_config_refactoring.md b/prompt/45_config_refactoring.md new file mode 100644 index 0000000..024da79 --- /dev/null +++ b/prompt/45_config_refactoring.md @@ -0,0 +1,38 @@ +# 45. Config & UI 전면 리팩토링 (Config & UI Refactoring) + +## 1. 목표 (Goal) +- 프로젝트 전반에 산재된 하드코딩된 값(정적 상수, 색상, 폰트)을 설정 파일(`GameConfig`, `ThemeConfig`)로 통합 관리합니다. +- 모든 화면(`MainMenu`, `Battle`, `Inventory`, `Settings`, `CharacterSelection`)의 UI 스타일을 통일합니다. + +## 2. 구현 상세 (Implementation Details) + +### A. 게임 밸런스 설정 (`GameConfig`) +- **파일:** `lib/game/config/game_config.dart` +- **내용:** + - **인벤토리/경제:** 최대 크기, 시작 골드, 상점 비용 및 리롤 등. + - **스테이지:** 구간별 설정, 티어 분포. + - **전투:** 회복율, 데미지 배율, 방어도 감소, 애니메이션 딜레이 등. + - **시스템:** 저장 키 (`saveKey`). +- **적용:** `BattleProvider`, `Entity`, `SaveManager`의 매직 넘버 제거. + +### B. 테마 및 스타일 설정 (`ThemeConfig`) +- **파일:** `lib/game/config/theme_config.dart` +- **색상 (Colors):** + - 메인 메뉴(배경, 버튼), 전투 화면(배경, UI, 리스크), 상점/휴식 화면, 아이템 등급 등 모든 색상 정의. +- **폰트 (Fonts):** + - **Size:** `Tiny`(8.0) ~ `Huge`(48.0) 등 10단계 정의. + - **Weight:** `Normal`, `Bold` 정의. + +### C. 화면별 리팩토링 (Screen Refactoring) +- **`lib/screens/` 및 `lib/widgets/` 전체:** + - **`BattleScreen` & Widgets:** 로그, 리스크 다이얼로그, 캐릭터 카드, 게임 오버, 보상 팝업 등. + - **`MainMenuScreen`:** 타이틀, 버튼, 배경. + - **`InventoryScreen`:** 슬롯, 스탯 텍스트, 팝업. + - **`CharacterSelectionScreen`:** 카드, 텍스트 스타일. + - **`SettingsScreen`:** 텍스트, 버튼. + - **`MainWrapper`:** 공통 배경색 적용. + +## 3. 결과 (Result) +- **중앙 관리:** 게임의 수치 밸런스와 디자인 테마를 각각 `GameConfig`와 `ThemeConfig`에서 손쉽게 수정 가능해졌습니다. +- **일관성:** 모든 화면에서 통일된 색상 팔레트와 폰트 스케일을 사용하여 UI 완성도가 향상되었습니다. +- **유지보수:** 하드코딩된 값들이 제거되어 코드 가독성 및 유지보수성이 대폭 개선되었습니다. \ No newline at end of file