All files / components/screens/combat/components/hud DifficultyIndicator.tsx

90.9% Statements 20/22
90.9% Branches 20/22
100% Functions 3/3
90.9% Lines 20/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                                      70x   62x   1x   4x   1x   2x                     70x   62x   1x   4x   1x   2x                       3x       70x 70x 70x     70x 70x             70x   70x                                                                                                
/**
 * DifficultyIndicator Component - AI Difficulty Tier Display
 *
 * Shows the current AI difficulty tier with Korean-English bilingual text
 * and color-coded visual feedback based on tier level.
 *
 * **Korean Philosophy (난이도 표시)**:
 * Provides transparent feedback to player about AI challenge level,
 * helping them understand their skill progression.
 */
 
import React from "react";
import { DifficultyTier } from "../../../../../systems/ai";
import { FONT_FAMILY, KOREAN_COLORS } from "../../../../../types/constants";
import { hexColorToCSS, hexToRgbaString } from "../../../../../utils/colorUtils";
 
export interface DifficultyIndicatorProps {
  /** Current difficulty tier (1-5) */
  readonly tier: DifficultyTier;
  /** Whether to use mobile-optimized sizing */
  readonly isMobile: boolean;
}
 
/**
 * Get tier display name with Korean and English
 */
function getTierName(tier: DifficultyTier): { korean: string; english: string } {
  switch (tier) {
    case DifficultyTier.BEGINNER:
      return { korean: "초보", english: "Beginner" };
    case DifficultyTier.NOVICE:
      return { korean: "입문", english: "Novice" };
    case DifficultyTier.INTERMEDIATE:
      return { korean: "중급", english: "Intermediate" };
    case DifficultyTier.ADVANCED:
      return { korean: "고급", english: "Advanced" };
    case DifficultyTier.EXPERT:
      return { korean: "전문", english: "Expert" };
    default:
      return { korean: "중급", english: "Intermediate" };
  }
}
 
/**
 * Get numeric color for difficulty tier using KOREAN_COLORS constants
 * Maps tiers to existing color scheme for consistency
 */
function getTierColorValue(tier: DifficultyTier): number {
  switch (tier) {
    case DifficultyTier.BEGINNER:
      return KOREAN_COLORS.POSITIVE_GREEN; // Green - Easy
    case DifficultyTier.NOVICE:
      return KOREAN_COLORS.ACCENT_GREEN; // Light Green
    case DifficultyTier.INTERMEDIATE:
      return KOREAN_COLORS.ACCENT_GOLD; // Gold - Medium
    case DifficultyTier.ADVANCED:
      return KOREAN_COLORS.SECONDARY_ORANGE; // Orange
    case DifficultyTier.EXPERT:
      return KOREAN_COLORS.NEGATIVE_RED; // Red - Hard
    default:
      return KOREAN_COLORS.ACCENT_GOLD; // Default to medium
  }
}
 
/**
 * DifficultyIndicator - Visual feedback for current AI difficulty tier
 * 
 * Positioned below Player 2 HUD to avoid overlap
 * Calculates vertical offset based on HUD height (name + health + stamina + stance)
 */
export const DifficultyIndicator: React.FC<DifficultyIndicatorProps> = ({
  tier,
  isMobile,
}) => {
  const tierName = getTierName(tier);
  const tierColorValue = getTierColorValue(tier);
  const tierColor = hexColorToCSS(tierColorValue);
  
  // Responsive sizing
  const fontSize = isMobile ? 11 : 13;
  const padding = isMobile ? "6px 10px" : "8px 12px";
  
  // Position below Player 2 HUD to avoid overlap
  // NOTE: This uses a calculated offset based on typical HUD height.
  // If Player HUD layout changes, this may need adjustment.
  // Consider using CSS Grid or exported HUD constants for better maintainability.
  // Current estimate: name (20px) + health bar (20px) + stamina bar (20px) + stance (15px) + gaps (18px) ≈ 93px
  const topOffset = isMobile ? 100 : 110;
 
  return (
    <div
      data-testid="difficulty-indicator"
      style={{
        position: "absolute",
        top: `${topOffset}px`,
        right: isMobile ? "8px" : "12px",
        padding,
        background: hexToRgbaString(tierColorValue, 0.13), // 13% opacity background
        border: `2px solid ${tierColor}`,
        borderRadius: "4px",
        fontFamily: FONT_FAMILY.KOREAN,
        fontSize: `${fontSize}px`,
        color: tierColor,
        textShadow: "0 0 4px rgba(0,0,0,0.8)",
        pointerEvents: "none",
        zIndex: 100,
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        gap: "2px",
        transition: "all 0.3s ease-in-out", // Smooth color/border transitions
        boxShadow: `0 0 8px ${hexToRgbaString(tierColorValue, 0.27)}`, // Subtle glow effect (27% opacity)
      }}
    >
      <div
        data-testid="difficulty-label"
        style={{
          fontSize: isMobile ? "9px" : "10px",
          opacity: 0.8,
          letterSpacing: "0.5px",
        }}
      >
        AI Difficulty
      </div>
      <div
        data-testid="difficulty-tier"
        style={{
          fontWeight: "bold",
          whiteSpace: "nowrap",
          letterSpacing: "0.5px",
        }}
      >
        {tierName.korean} | {tierName.english}
      </div>
    </div>
  );
};