人体汉字识别游戏

在这里插入图片描述

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>人体汉字识别游戏</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
            background: linear-gradient(135deg, #1a237e 0%, #311b92 30%, #4a148c 100%);
            color: white;
            min-height: 100vh;
            overflow-x: hidden;
        }
        
        .game-container {
            display: grid;
            grid-template-columns: 1fr 350px;
            gap: 20px;
            padding: 20px;
            height: 100vh;
            max-width: 1400px;
            margin: 0 auto;
        }
        
        /* 游戏主区域 */
        .game-main {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        
        /* 摄像头区域 */
        .camera-area {
            flex: 1;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 15px;
            border: 3px solid rgba(255, 255, 255, 0.2);
            position: relative;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        }
        
        #video-canvas {
            width: 100%;
            height: 100%;
            display: block;
            transform: scaleX(-1); /* 镜像翻转 */
        }
        
        .camera-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
        }
        
        /* 游戏头部 */
        .game-header {
            background: rgba(0, 0, 0, 0.7);
            border-radius: 15px;
            padding: 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border: 2px solid rgba(255, 255, 255, 0.2);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        .game-title {
            font-size: 2.2rem;
            font-weight: bold;
            background: linear-gradient(90deg, #00bcd4, #4caf50);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            text-shadow: 0 0 20px rgba(0, 188, 212, 0.5);
        }
        
        .game-info {
            display: flex;
            gap: 30px;
        }
        
        .info-item {
            text-align: center;
        }
        
        .info-value {
            font-size: 2rem;
            font-weight: bold;
            color: #00e5ff;
            text-shadow: 0 0 10px #00e5ff;
        }
        
        .info-label {
            font-size: 0.9rem;
            color: #b3e5fc;
            margin-top: 5px;
        }
        
        /* 侧边控制面板 */
        .control-panel {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        
        /* 当前汉字区域 */
        .character-display {
            background: rgba(0, 0, 0, 0.8);
            border-radius: 15px;
            padding: 25px;
            text-align: center;
            border: 3px solid rgba(0, 188, 212, 0.4);
            box-shadow: 0 0 30px rgba(0, 188, 212, 0.3);
        }
        
        .character-title {
            font-size: 1.2rem;
            color: #80deea;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        .character {
            font-size: 8rem;
            font-weight: bold;
            color: #fff;
            text-shadow: 0 0 30px #00e5ff;
            line-height: 1;
            margin: 10px 0;
            font-family: 'KaiTi', '楷体', 'STKaiti', serif;
        }
        
        .character-pinyin {
            font-size: 1.5rem;
            color: #b3e5fc;
            margin-top: 10px;
        }
        
        /* 汉字列表 */
        .characters-list {
            background: rgba(0, 0, 0, 0.7);
            border-radius: 15px;
            padding: 20px;
            border: 2px solid rgba(255, 215, 0, 0.3);
            flex: 1;
            overflow-y: auto;
        }
        
        .characters-title {
            font-size: 1.2rem;
            color: #ffd700;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .characters-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
        }
        
        .character-item {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            padding: 15px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
            border: 2px solid transparent;
        }
        
        .character-item:hover {
            background: rgba(255, 255, 255, 0.2);
            transform: translateY(-3px);
        }
        
        .character-item.active {
            background: rgba(0, 188, 212, 0.3);
            border-color: #00bcd4;
            box-shadow: 0 0 15px rgba(0, 188, 212, 0.5);
        }
        
        .char-item {
            font-size: 2.5rem;
            font-weight: bold;
            font-family: 'KaiTi', '楷体', 'STKaiti', serif;
        }
        
        .char-desc {
            font-size: 0.8rem;
            color: #b3e5fc;
            margin-top: 5px;
        }
        
        /* 游戏控制 */
        .game-controls {
            background: rgba(0, 0, 0, 0.7);
            border-radius: 15px;
            padding: 20px;
            border: 2px solid rgba(76, 175, 80, 0.3);
        }
        
        .control-title {
            font-size: 1.2rem;
            color: #4caf50;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .control-buttons {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
        }
        
        .control-btn {
            padding: 12px;
            border: none;
            border-radius: 10px;
            font-size: 1rem;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        
        .control-btn.start {
            background: linear-gradient(135deg, #4caf50, #2e7d32);
            color: white;
        }
        
        .control-btn.pause {
            background: linear-gradient(135deg, #ff9800, #ef6c00);
            color: white;
        }
        
        .control-btn.next {
            background: linear-gradient(135deg, #2196f3, #0d47a1);
            color: white;
        }
        
        .control-btn.reset {
            background: linear-gradient(135deg, #f44336, #b71c1c);
            color: white;
        }
        
        .control-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        .control-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }
        
        /* 识别状态 */
        .recognition-status {
            position: absolute;
            bottom: 20px;
            left: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 15px;
            display: flex;
            align-items: center;
            gap: 15px;
            border: 2px solid rgba(255, 215, 0, 0.4);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
        }
        
        .status-indicator {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: #f44336;
            animation: pulse 1.5s infinite;
        }
        
        .status-indicator.detecting {
            background: #4caf50;
        }
        
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
        
        .status-text {
            flex: 1;
            font-size: 1rem;
        }
        
        .match-progress {
            height: 8px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
            overflow: hidden;
            flex: 1;
        }
        
        .match-fill {
            height: 100%;
            background: linear-gradient(90deg, #4caf50, #8bc34a);
            width: 0%;
            transition: width 0.3s ease;
        }
        
        /* 得分效果 */
        .score-effect {
            position: fixed;
            pointer-events: none;
            z-index: 1000;
            font-size: 3rem;
            font-weight: bold;
            color: #ffeb3b;
            text-shadow: 0 0 20px #ffeb3b;
            animation: floatUp 1.5s ease-out forwards;
        }
        
        @keyframes floatUp {
            0% {
                transform: translateY(0) scale(1);
                opacity: 1;
            }
            100% {
                transform: translateY(-100px) scale(1.5);
                opacity: 0;
            }
        }
        
        /* 提示信息 */
        .hint-message {
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 15px;
            max-width: 300px;
            border-left: 4px solid #00bcd4;
            animation: slideIn 0.5s ease;
        }
        
        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateX(-20px);
            }
            to {
                opacity: 1;
                transform: translateX(0);
            }
        }
        
        .hint-title {
            color: #00bcd4;
            font-weight: bold;
            margin-bottom: 5px;
        }
        
        .hint-text {
            font-size: 0.9rem;
            color: #e0f7fa;
        }
        
        /* 关节显示样式 */
        .joint-info {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 10px;
            padding: 15px;
            max-width: 200px;
            border: 1px solid rgba(255, 215, 0, 0.3);
        }
        
        .joint-title {
            color: #ffd700;
            font-weight: bold;
            margin-bottom: 10px;
            font-size: 0.9rem;
        }
        
        .joint-list {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
        }
        
        .joint-tag {
            background: rgba(255, 215, 0, 0.2);
            color: #ffd700;
            padding: 3px 8px;
            border-radius: 5px;
            font-size: 0.8rem;
        }
        
        /* 响应式设计 */
        @media (max-width: 1200px) {
            .game-container {
                grid-template-columns: 1fr;
                grid-template-rows: auto 1fr auto;
            }
            
            .control-panel {
                grid-row: 3;
                flex-direction: row;
            }
            
            .characters-list {
                flex: 1;
            }
            
            .character-display {
                flex: 0 0 200px;
            }
            
            .game-controls {
                flex: 0 0 200px;
            }
        }
        
        @media (max-width: 768px) {
            .game-container {
                padding: 10px;
                gap: 10px;
            }
            
            .character {
                font-size: 5rem;
            }
            
            .info-value {
                font-size: 1.5rem;
            }
            
            .characters-grid {
                grid-template-columns: repeat(2, 1fr);
            }
        }
    </style>
    
    <!-- MediaPipe Pose -->
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/pose/pose.js" crossorigin="anonymous"></script>
</head>
<body>
    <div class="game-container">
        <!-- 游戏主区域 -->
        <div class="game-main">
            <!-- 游戏头部 -->
            <div class="game-header">
                <div class="game-title">👤 人体汉字识别游戏</div>
                <div class="game-info">
                    <div class="info-item">
                        <div class="info-value" id="score">0</div>
                        <div class="info-label">得分</div>
                    </div>
                    <div class="info-item">
                        <div class="info-value" id="level">1</div>
                        <div class="info-label">关卡</div>
                    </div>
                    <div class="info-item">
                        <div class="info-value" id="streak">0</div>
                        <div class="info-label">连击</div>
                    </div>
                    <div class="info-item">
                        <div class="info-value" id="accuracy">0%</div>
                        <div class="info-label">准确率</div>
                    </div>
                </div>
            </div>
            
            <!-- 摄像头区域 -->
            <div class="camera-area">
                <canvas id="video-canvas"></canvas>
                <div class="camera-overlay" id="pose-overlay"></div>
                
                <!-- 识别状态 -->
                <div class="recognition-status">
                    <div class="status-indicator" id="status-indicator"></div>
                    <div class="status-text" id="status-text">等待摄像头启动...</div>
                    <div class="match-progress">
                        <div class="match-fill" id="match-fill"></div>
                    </div>
                </div>
                
                <!-- 提示信息 -->
                <div class="hint-message" id="hint-message" style="display: none;">
                    <div class="hint-title">💡 游戏提示</div>
                    <div class="hint-text">请用身体摆出当前汉字的形状!</div>
                </div>
                
                <!-- 关节信息 -->
                <div class="joint-info" id="joint-info" style="display: none;">
                    <div class="joint-title">🦴 检测到的关节</div>
                    <div class="joint-list" id="joint-list"></div>
                </div>
            </div>
        </div>
        
        <!-- 侧边控制面板 -->
        <div class="control-panel">
            <!-- 当前汉字区域 -->
            <div class="character-display">
                <div class="character-title">
                    <span>🎯 当前汉字</span>
                </div>
                <div class="character" id="current-character"></div>
                <div class="character-pinyin" id="current-pinyin"></div>
            </div>
            
            <!-- 汉字列表 -->
            <div class="characters-list">
                <div class="characters-title">
                    <span>📖 可识别的汉字</span>
                </div>
                <div class="characters-grid" id="characters-grid">
                    <!-- 汉字列表由JS生成 -->
                </div>
            </div>
            
            <!-- 游戏控制 -->
            <div class="game-controls">
                <div class="control-title">
                    <span>🎮 游戏控制</span>
                </div>
                <div class="control-buttons">
                    <button class="control-btn start" id="start-btn">
                        <span>▶️</span>
                        <span>开始游戏</span>
                    </button>
                    <button class="control-btn pause" id="pause-btn">
                        <span>⏸️</span>
                        <span>暂停</span>
                    </button>
                    <button class="control-btn next" id="next-btn">
                        <span>⏭️</span>
                        <span>下一个</span>
                    </button>
                    <button class="control-btn reset" id="reset-btn">
                        <span>🔄</span>
                        <span>重新开始</span>
                    </button>
                </div>
            </div>
        </div>
    </div>
    
    <!-- 视频元素(隐藏) -->
    <video class="input_video" id="input_video" style="display: none;"></video>
    
    <script>
        // ==========================================
        // 1. 游戏配置
        // ==========================================
        const GameConfig = {
            // 汉字库 - 人体可表达的汉字
            characters: [
                { char: '大', pinyin: 'dà', description: '张开四肢站立', difficulty: 1 },
                { char: '小', pinyin: 'xiǎo', description: '双腿并拢站立', difficulty: 1 },
                { char: '人', pinyin: 'rén', description: '双腿分开站立', difficulty: 1 },
                { char: '十', pinyin: 'shí', description: '双臂平伸站立', difficulty: 2 },
                { char: '土', pinyin: 'tǔ', description: 'T字形站立', difficulty: 2 },
                { char: '工', pinyin: 'gōng', description: '双臂下垂站立', difficulty: 2 },
                { char: '口', pinyin: 'kǒu', description: '双手在头顶合拢', difficulty: 3 },
                { char: '中', pinyin: 'zhōng', description: '中心站立姿势', difficulty: 3 },
                { char: '天', pinyin: 'tiān', description: '头顶双手', difficulty: 3 },
                { char: '木', pinyin: 'mù', description: '树状姿势', difficulty: 3 },
                { char: '火', pinyin: 'huǒ', description: '火焰状姿势', difficulty: 4 },
                { char: '山', pinyin: 'shān', description: '山峰状姿势', difficulty: 4 }
            ],
            
            // 姿势识别配置
            poseDetection: {
                minDetectionConfidence: 0.5,
                minTrackingConfidence: 0.5,
                modelComplexity: 1
            },
            
            // 游戏配置
            game: {
                initialScore: 0,
                basePoints: 100,
                streakBonus: 50,
                matchThreshold: 0.7, // 匹配阈值
                recognitionDelay: 2000, // 识别延迟(毫秒)
                maxStreak: 10,
                levelUpThreshold: 500 // 每500分升一级
            }
        };

        // ==========================================
        // 2. 游戏状态管理
        // ==========================================
        const GameState = {
            isRunning: false,
            isPaused: false,
            currentCharacter: null,
            currentPose: null,
            score: 0,
            level: 1,
            streak: 0,
            correctCount: 0,
            totalAttempts: 0,
            lastRecognitionTime: 0,
            isRecognizing: false,
            poseHistory: [],
            detectedJoints: new Set(),
            selectedCharacter: null
        };

        // ==========================================
        // 3. 汉字姿势检测器
        // ==========================================
        class CharacterPoseDetector {
            constructor() {
                this.poseLandmarks = null;
                this.lastDetectionTime = 0;
            }
            
            // 计算姿势与汉字的匹配度
            matchCharacter(characterChar, landmarks) {
                if (!landmarks) return 0;
                
                // 根据不同的汉字,使用不同的匹配算法
                switch(characterChar) {
                    case '大':
                        return this.matchDa(landmarks);
                    case '小':
                        return this.matchXiao(landmarks);
                    case '人':
                        return this.matchRen(landmarks);
                    case '十':
                        return this.matchShi(landmarks);
                    case '土':
                        return this.matchTu(landmarks);
                    case '工':
                        return this.matchGong(landmarks);
                    case '口':
                        return this.matchKou(landmarks);
                    case '中':
                        return this.matchZhong(landmarks);
                    case '天':
                        return this.matchTian(landmarks);
                    case '木':
                        return this.matchMu(landmarks);
                    case '火':
                        return this.matchHuo(landmarks);
                    case '山':
                        return this.matchShan(landmarks);
                    default:
                        return 0;
                }
            }
            
            // 计算关键点之间的角度
            calculateAngle(pointA, pointB, pointC) {
                const AB = Math.sqrt(Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2));
                const BC = Math.sqrt(Math.pow(pointB.x - pointC.x, 2) + Math.pow(pointB.y - pointC.y, 2));
                const AC = Math.sqrt(Math.pow(pointC.x - pointA.x, 2) + Math.pow(pointC.y - pointA.y, 2));
                
                if (AB === 0 || BC === 0) return 0;
                
                const cosAngle = (Math.pow(AB, 2) + Math.pow(BC, 2) - Math.pow(AC, 2)) / (2 * AB * BC);
                return Math.acos(Math.min(Math.max(cosAngle, -1), 1)) * (180 / Math.PI);
            }
            
            // 计算关键点之间的距离
            calculateDistance(pointA, pointB) {
                return Math.sqrt(Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2));
            }
            
            // 检查关键点是否在一条直线上
            checkCollinear(pointA, pointB, pointC, threshold = 10) {
                const area = Math.abs(
                    pointA.x * (pointB.y - pointC.y) +
                    pointB.x * (pointC.y - pointA.y) +
                    pointC.x * (pointA.y - pointB.y)
                ) / 2;
                
                return area < threshold;
            }
            
            // 匹配"大"字 - 张开四肢站立
            matchDa(landmarks) {
                try {
                    const leftShoulder = landmarks[11];
                    const rightShoulder = landmarks[12];
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    const leftKnee = landmarks[25];
                    const rightKnee = landmarks[26];
                    const leftAnkle = landmarks[27];
                    const rightAnkle = landmarks[28];
                    
                    // 检查双腿分开
                    const hipDistance = this.calculateDistance(leftHip, rightHip);
                    const shoulderDistance = this.calculateDistance(leftShoulder, rightShoulder);
                    const legSpread = hipDistance / shoulderDistance;
                    
                    // 检查手臂伸展(如果可见)
                    const leftArmAngle = this.calculateAngle(leftShoulder, leftHip, leftAnkle);
                    const rightArmAngle = this.calculateAngle(rightShoulder, rightHip, rightAnkle);
                    
                    let score = 0;
                    
                    // 腿部分开度评分(最佳比例 1.5-2.5)
                    if (legSpread > 1.2 && legSpread < 3) {
                        score += 0.4;
                    }
                    
                    // 腿部伸直评分
                    const leftLegStraightness = Math.abs(180 - this.calculateAngle(leftHip, leftKnee, leftAnkle));
                    const rightLegStraightness = Math.abs(180 - this.calculateAngle(rightHip, rightKnee, rightAnkle));
                    
                    if (leftLegStraightness < 30 && rightLegStraightness < 30) {
                        score += 0.3;
                    }
                    
                    // 身体直立评分
                    const bodyAlignment = this.checkCollinear(leftShoulder, leftHip, leftAnkle);
                    if (bodyAlignment) {
                        score += 0.3;
                    }
                    
                    return Math.min(score, 1);
                } catch (e) {
                    console.error('匹配"大"字时出错:', e);
                    return 0;
                }
            }
            
            // 匹配"小"字 - 双腿并拢站立
            matchXiao(landmarks) {
                try {
                    const leftShoulder = landmarks[11];
                    const rightShoulder = landmarks[12];
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    const leftAnkle = landmarks[27];
                    const rightAnkle = landmarks[28];
                    
                    // 检查双腿并拢
                    const hipDistance = this.calculateDistance(leftHip, rightHip);
                    const ankleDistance = this.calculateDistance(leftAnkle, rightAnkle);
                    
                    let score = 0;
                    
                    // 腿部和脚踝靠近
                    if (hipDistance < 0.1 && ankleDistance < 0.1) {
                        score += 0.6;
                    }
                    
                    // 身体直立
                    const bodyAlignment = this.checkCollinear(leftShoulder, leftHip, leftAnkle);
                    if (bodyAlignment) {
                        score += 0.4;
                    }
                    
                    return Math.min(score, 1);
                } catch (e) {
                    console.error('匹配"小"字时出错:', e);
                    return 0;
                }
            }
            
            // 匹配"人"字 - 双腿分开站立
            matchRen(landmarks) {
                try {
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    const leftAnkle = landmarks[27];
                    const rightAnkle = landmarks[28];
                    
                    // 计算腿部分开角度
                    const angle = this.calculateAngle(leftHip, leftAnkle, rightAnkle);
                    
                    let score = 0;
                    
                    // 理想的"人"字角度大约30-60度
                    if (angle > 20 && angle < 80) {
                        score = Math.min(angle / 60, 1);
                    }
                    
                    return score;
                } catch (e) {
                    console.error('匹配"人"字时出错:', e);
                    return 0;
                }
            }
            
            // 匹配"十"字 - 双臂平伸站立
            matchShi(landmarks) {
                try {
                    const leftShoulder = landmarks[11];
                    const rightShoulder = landmarks[12];
                    const leftElbow = landmarks[13];
                    const rightElbow = landmarks[14];
                    const leftWrist = landmarks[15];
                    const rightWrist = landmarks[16];
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    
                    let score = 0;
                    
                    // 检查双臂水平伸展
                    const leftArmHorizontal = Math.abs(leftShoulder.y - leftWrist.y) < 0.1;
                    const rightArmHorizontal = Math.abs(rightShoulder.y - rightWrist.y) < 0.1;
                    
                    if (leftArmHorizontal && rightArmHorizontal) {
                        score += 0.5;
                    }
                    
                    // 检查手臂伸展程度
                    const leftArmLength = this.calculateDistance(leftShoulder, leftWrist);
                    const rightArmLength = this.calculateDistance(rightShoulder, rightWrist);
                    const shoulderWidth = this.calculateDistance(leftShoulder, rightShoulder);
                    
                    if (leftArmLength > shoulderWidth * 0.8 && rightArmLength > shoulderWidth * 0.8) {
                        score += 0.3;
                    }
                    
                    // 检查身体直立
                    const bodyStraight = Math.abs(leftShoulder.x - leftHip.x) < 0.1;
                    if (bodyStraight) {
                        score += 0.2;
                    }
                    
                    return Math.min(score, 1);
                } catch (e) {
                    console.error('匹配"十"字时出错:', e);
                    return 0;
                }
            }
            
            // 匹配"土"字 - T字形站立
            matchTu(landmarks) {
                try {
                    const leftShoulder = landmarks[11];
                    const rightShoulder = landmarks[12];
                    const leftWrist = landmarks[15];
                    const rightWrist = landmarks[16];
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    
                    let score = 0;
                    
                    // 检查双臂水平(T字的上横)
                    const armsHorizontal = Math.abs(leftShoulder.y - rightShoulder.y) < 0.1;
                    const armsExtended = this.calculateDistance(leftWrist, rightWrist) > 
                                        this.calculateDistance(leftShoulder, rightShoulder) * 1.5;
                    
                    if (armsHorizontal && armsExtended) {
                        score += 0.6;
                    }
                    
                    // 检查身体垂直(T字的竖)
                    const bodyVertical = Math.abs(leftShoulder.x - leftHip.x) < 0.1;
                    if (bodyVertical) {
                        score += 0.4;
                    }
                    
                    return Math.min(score, 1);
                } catch (e) {
                    console.error('匹配"土"字时出错:', e);
                    return 0;
                }
            }
            
            // 匹配"工"字 - 双臂下垂站立
            matchGong(landmarks) {
                try {
                    const leftShoulder = landmarks[11];
                    const rightShoulder = landmarks[12];
                    const leftElbow = landmarks[13];
                    const rightElbow = landmarks[14];
                    const leftWrist = landmarks[15];
                    const rightWrist = landmarks[16];
                    const leftHip = landmarks[23];
                    const rightHip = landmarks[24];
                    
                    let score = 0;
                    
                    // 检查双臂下垂
                    const leftArmVertical = Math.abs(leftShoulder.x - leftWrist.x) < 0.1;
                    const rightArmVertical = Math.abs(rightShoulder.x - rightWrist.x) < 0.1;
                    const leftArmStraight = this.calculateAngle(leftShoulder, leftElbow, leftWrist) > 150;
                    const rightArmStraight = this.calculateAngle(rightShoulder, rightElbow, rightWrist) > 150;
                    
                    if (leftArmVertical && rightArmVertical && leftArmStraight && rightArmStraight) {
                        score += 0.5;
                    }
                    
                    // 检查身体直立
                    const bodyStraight = Math.abs(leftShoulder.x - leftHip.x) < 0.1;
                    if (bodyStraight) {
                        score += 0.3;
                    }
                    
                    // 检查双腿并拢
                    const hipsClose = this.calculateDistance(leftHip, rightHip) < 0.15;
                    if (hipsClose) {
                        score += 0.2;
                    }
                    
                    return Math.min(score, 1);
                } catch (e) {
                    console.error('匹配"工"字时出错:', e);
                    return 0;
                }
            }
            
            // 其他汉字匹配方法(简化版)
            matchKou(landmarks) {
                // "口"字:双手在头顶合拢形成方形
                const leftWrist = landmarks[15];
                const rightWrist = landmarks[16];
                const nose = landmarks[0];
                
                const wristDistance = this.calculateDistance(leftWrist, rightWrist);
                const heightAboveHead = nose.y - leftWrist.y;
                
                let score = 0;
                if (wristDistance < 0.2 && heightAboveHead > 0.1) {
                    score = 0.7;
                }
                
                return score;
            }
            
            matchZhong(landmarks) {
                // "中"字:中心对称姿势
                return this.matchShi(landmarks) * 0.8;
            }
            
            matchTian(landmarks) {
                // "天"字:头顶双手
                const leftWrist = landmarks[15];
                const rightWrist = landmarks[16];
                const nose = landmarks[0];
                
                const wristHeight = Math.min(leftWrist.y, rightWrist.y);
                let score = 0;
                
                if (wristHeight < nose.y - 0.1) {
                    score = 0.6;
                }
                
                return score;
            }
            
            matchMu(landmarks) {
                // "木"字:树状姿势,双臂上举
                const leftWrist = landmarks[15];
                const rightWrist = landmarks[16];
                const leftShoulder = landmarks[11];
                
                let score = 0;
                if (leftWrist.y < leftShoulder.y && rightWrist.y < leftShoulder.y) {
                    score = 0.7;
                }
                
                return score;
            }
            
            matchHuo(landmarks) {
                // "火"字:火焰状,不对称姿势
                const leftWrist = landmarks[15];
                const rightWrist = landmarks[16];
                const leftShoulder = landmarks[11];
                const rightShoulder = landmarks[12];
                
                const leftArmAngle = this.calculateAngle(leftShoulder, leftWrist, rightShoulder);
                const rightArmAngle = this.calculateAngle(rightShoulder, rightWrist, leftShoulder);
                
                let score = 0;
                if (Math.abs(leftArmAngle - rightArmAngle) > 30) {
                    score = 0.6;
                }
                
                return score;
            }
            
            matchShan(landmarks) {
                // "山"字:山峰状,双臂形成三个顶点
                const leftWrist = landmarks[15];
                const rightWrist = landmarks[16];
                const head = landmarks[0];
                
                let score = 0;
                const wristHeightDiff = Math.abs(leftWrist.y - rightWrist.y);
                const headToWristDiff = Math.abs(head.y - (leftWrist.y + rightWrist.y) / 2);
                
                if (wristHeightDiff > 0.1 && headToWristDiff > 0.2) {
                    score = 0.5;
                }
                
                return score;
            }
            
            // 获取检测到的关节信息
            getDetectedJoints(landmarks) {
                const joints = new Set();
                if (!landmarks) return joints;
                
                // MediaPipe Pose的33个关键点
                const jointNames = [
                    '鼻子', '左眼内', '左眼', '左眼外', '右眼内', '右眼', '右眼外',
                    '左耳', '右耳', '左嘴', '右嘴', '左肩', '右肩', '左肘', '右肘',
                    '左手腕', '右手腕', '左小指', '右小指', '左食指', '右食指',
                    '左大拇指', '右大拇指', '左髋', '右髋', '左膝', '右膝',
                    '左踝', '右踝', '左脚跟', '右脚跟', '左脚尖', '右脚尖'
                ];
                
                landmarks.forEach((landmark, index) => {
                    if (landmark.visibility > 0.5) {
                        joints.add(jointNames[index]);
                    }
                });
                
                return joints;
            }
        }

        // ==========================================
        // 4. 游戏主逻辑
        // ==========================================
        class GameManager {
            constructor() {
                this.poseDetector = new CharacterPoseDetector();
                this.currentCharacterIndex = 0;
                this.initializeUI();
                this.initializePoseDetection();
                this.setupEventListeners();
            }
            
            initializeUI() {
                // 初始化汉字列表
                const charactersGrid = document.getElementById('characters-grid');
                charactersGrid.innerHTML = '';
                
                GameConfig.characters.forEach((char, index) => {
                    const charElement = document.createElement('div');
                    charElement.className = 'character-item';
                    charElement.dataset.index = index;
                    charElement.innerHTML = `
                        <div class="char-item">${char.char}</div>
                        <div class="char-desc">${char.description}</div>
                    `;
                    
                    charElement.addEventListener('click', () => {
                        this.selectCharacter(index);
                    });
                    
                    charactersGrid.appendChild(charElement);
                });
                
                // 设置初始汉字
                this.selectCharacter(0);
            }
            
            selectCharacter(index) {
                // 移除之前的选择
                document.querySelectorAll('.character-item').forEach(item => {
                    item.classList.remove('active');
                });
                
                // 设置新的选择
                const charElement = document.querySelector(`[data-index="${index}"]`);
                charElement.classList.add('active');
                
                GameState.selectedCharacter = GameConfig.characters[index];
                this.updateCurrentCharacter(GameConfig.characters[index]);
            }
            
            updateCurrentCharacter(character) {
                GameState.currentCharacter = character;
                
                document.getElementById('current-character').textContent = character.char;
                document.getElementById('current-pinyin').textContent = character.pinyin;
                
                // 显示提示
                this.showHint(`请摆出"${character.char}"字的姿势:${character.description}`);
            }
            
            updateGameStats() {
                document.getElementById('score').textContent = GameState.score;
                document.getElementById('level').textContent = GameState.level;
                document.getElementById('streak').textContent = GameState.streak;
                
                if (GameState.totalAttempts > 0) {
                    const accuracy = Math.round((GameState.correctCount / GameState.totalAttempts) * 100);
                    document.getElementById('accuracy').textContent = `${accuracy}%`;
                }
            }
            
            showHint(message, duration = 3000) {
                const hintElement = document.getElementById('hint-message');
                const hintText = document.querySelector('.hint-text');
                
                hintText.textContent = message;
                hintElement.style.display = 'block';
                
                if (duration > 0) {
                    setTimeout(() => {
                        hintElement.style.display = 'none';
                    }, duration);
                }
            }
            
            showScoreEffect(points, x, y) {
                const effect = document.createElement('div');
                effect.className = 'score-effect';
                effect.textContent = `+${points}`;
                effect.style.left = `${x}px`;
                effect.style.top = `${y}px`;
                
                document.body.appendChild(effect);
                
                setTimeout(() => {
                    effect.remove();
                }, 1500);
            }
            
            updateJointInfo(joints) {
                const jointList = document.getElementById('joint-list');
                const jointInfo = document.getElementById('joint-info');
                
                if (joints.size > 0) {
                    jointList.innerHTML = '';
                    joints.forEach(joint => {
                        const tag = document.createElement('span');
                        tag.className = 'joint-tag';
                        tag.textContent = joint;
                        jointList.appendChild(tag);
                    });
                    
                    jointInfo.style.display = 'block';
                } else {
                    jointInfo.style.display = 'none';
                }
            }
            
            checkPoseMatch(landmarks) {
                if (!GameState.isRunning || GameState.isPaused || !GameState.currentCharacter) return;
                
                const now = Date.now();
                if (now - GameState.lastRecognitionTime < GameConfig.game.recognitionDelay) return;
                
                // 获取检测到的关节
                const detectedJoints = this.poseDetector.getDetectedJoints(landmarks);
                GameState.detectedJoints = detectedJoints;
                this.updateJointInfo(detectedJoints);
                
                // 计算匹配度
                const matchScore = this.poseDetector.matchCharacter(
                    GameState.currentCharacter.char,
                    landmarks
                );
                
                // 更新匹配进度条
                const matchFill = document.getElementById('match-fill');
                matchFill.style.width = `${matchScore * 100}%`;
                
                // 更新状态指示器
                const statusIndicator = document.getElementById('status-indicator');
                const statusText = document.getElementById('status-text');
                
                if (matchScore > 0) {
                    statusIndicator.className = 'status-indicator detecting';
                    statusText.textContent = `匹配度: ${Math.round(matchScore * 100)}%`;
                    
                    // 如果匹配度超过阈值
                    if (matchScore >= GameConfig.game.matchThreshold) {
                        this.onPoseMatched(matchScore);
                        GameState.lastRecognitionTime = now;
                    }
                } else {
                    statusIndicator.className = 'status-indicator';
                    statusText.textContent = '请摆出正确的姿势...';
                }
            }
            
            onPoseMatched(matchScore) {
                if (!GameState.isRunning) return;
                
                GameState.totalAttempts++;
                GameState.correctCount++;
                
                // 计算得分
                const basePoints = GameConfig.game.basePoints;
                const streakBonus = GameState.streak * GameConfig.game.streakBonus;
                const accuracyBonus = Math.round(matchScore * 50);
                const totalPoints = basePoints + streakBonus + accuracyBonus;
                
                // 更新游戏状态
                GameState.score += totalPoints;
                GameState.streak++;
                
                // 检查升级
                if (GameState.score >= GameState.level * GameConfig.game.levelUpThreshold) {
                    GameState.level++;
                }
                
                // 限制连击数
                if (GameState.streak > GameConfig.game.maxStreak) {
                    GameState.streak = GameConfig.game.maxStreak;
                }
                
                // 更新UI
                this.updateGameStats();
                
                // 显示得分效果
                const canvas = document.getElementById('video-canvas');
                const x = Math.random() * canvas.clientWidth;
                const y = canvas.clientHeight / 2;
                this.showScoreEffect(totalPoints, x, y);
                
                // 显示成功消息
                this.showHint(`太棒了!成功匹配"${GameState.currentCharacter.char}"字!得分+${totalPoints}`, 2000);
                
                // 自动选择下一个汉字(如果游戏正在运行)
                setTimeout(() => {
                    if (GameState.isRunning && !GameState.isPaused) {
                        this.nextCharacter();
                    }
                }, 1500);
            }
            
            nextCharacter() {
                const currentIndex = GameConfig.characters.findIndex(
                    char => char.char === GameState.currentCharacter.char
                );
                
                let nextIndex = (currentIndex + 1) % GameConfig.characters.length;
                this.selectCharacter(nextIndex);
                
                // 重置匹配进度
                document.getElementById('match-fill').style.width = '0%';
            }
            
            startGame() {
                GameState.isRunning = true;
                GameState.isPaused = false;
                
                document.getElementById('start-btn').disabled = true;
                document.getElementById('pause-btn').disabled = false;
                
                this.showHint('游戏开始!请用身体摆出当前汉字的形状。');
                
                // 重置状态指示器
                const statusIndicator = document.getElementById('status-indicator');
                const statusText = document.getElementById('status-text');
                statusIndicator.className = 'status-indicator';
                statusText.textContent = '等待姿势识别...';
            }
            
            pauseGame() {
                GameState.isPaused = !GameState.isPaused;
                
                const pauseBtn = document.getElementById('pause-btn');
                pauseBtn.innerHTML = GameState.isPaused ? 
                    '<span>▶️</span><span>继续</span>' : 
                    '<span>⏸️</span><span>暂停</span>';
                
                const statusText = document.getElementById('status-text');
                statusText.textContent = GameState.isPaused ? '游戏已暂停' : '等待姿势识别...';
            }
            
            resetGame() {
                GameState.score = 0;
                GameState.level = 1;
                GameState.streak = 0;
                GameState.correctCount = 0;
                GameState.totalAttempts = 0;
                
                this.updateGameStats();
                this.selectCharacter(0);
                
                document.getElementById('match-fill').style.width = '0%';
                
                this.showHint('游戏已重置,请重新开始。');
            }
            
            setupEventListeners() {
                // 开始按钮
                document.getElementById('start-btn').addEventListener('click', () => {
                    this.startGame();
                });
                
                // 暂停按钮
                document.getElementById('pause-btn').addEventListener('click', () => {
                    this.pauseGame();
                });
                
                // 下一个按钮
                document.getElementById('next-btn').addEventListener('click', () => {
                    if (GameState.isRunning && !GameState.isPaused) {
                        this.nextCharacter();
                    }
                });
                
                // 重置按钮
                document.getElementById('reset-btn').addEventListener('click', () => {
                    this.resetGame();
                });
                
                // 键盘快捷键
                document.addEventListener('keydown', (e) => {
                    if (e.code === 'Space') {
                        e.preventDefault();
                        if (!GameState.isRunning) {
                            this.startGame();
                        } else {
                            this.pauseGame();
                        }
                    }
                    
                    if (e.code === 'ArrowRight' && GameState.isRunning && !GameState.isPaused) {
                        this.nextCharacter();
                    }
                    
                    if (e.code === 'KeyR') {
                        this.resetGame();
                    }
                    
                    // 数字键1-9选择汉字
                    if (e.code >= 'Digit1' && e.code <= 'Digit9') {
                        const index = parseInt(e.code[5]) - 1;
                        if (index < GameConfig.characters.length) {
                            this.selectCharacter(index);
                        }
                    }
                });
            }
            
            initializePoseDetection() {
                const videoElement = document.getElementById('input_video');
                const canvasElement = document.getElementById('video-canvas');
                const canvasCtx = canvasElement.getContext('2d');
                
                function onResults(results) {
                    if (!results.poseLandmarks) {
                        // 没有检测到姿势
                        const statusText = document.getElementById('status-text');
                        statusText.textContent = '未检测到人体,请站在摄像头前';
                        return;
                    }
                    
                    // 绘制摄像头画面
                    canvasCtx.save();
                    canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
                    canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
                    
                    // 绘制姿势关键点和连线
                    if (results.poseLandmarks) {
                        if (window.drawConnectors && window.drawLandmarks) {
                            drawConnectors(canvasCtx, results.poseLandmarks, POSE_CONNECTIONS, {
                                color: '#00FF00',
                                lineWidth: 2
                            });
                            drawLandmarks(canvasCtx, results.poseLandmarks, {
                                color: '#FF0000',
                                lineWidth: 1,
                                radius: 4
                            });
                        }
                    }
                    
                    canvasCtx.restore();
                    
                    // 检查姿势匹配
                    gameManager.checkPoseMatch(results.poseLandmarks);
                }
                
                // 初始化MediaPipe Pose
                const pose = new Pose({
                    locateFile: (file) => {
                        return `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`;
                    }
                });
                
                pose.setOptions({
                    modelComplexity: GameConfig.poseDetection.modelComplexity,
                    smoothLandmarks: true,
                    minDetectionConfidence: GameConfig.poseDetection.minDetectionConfidence,
                    minTrackingConfidence: GameConfig.poseDetection.minTrackingConfidence
                });
                
                pose.onResults(onResults);
                
                // 启动摄像头
                const camera = new Camera(videoElement, {
                    onFrame: async () => {
                        await pose.send({image: videoElement});
                    },
                    width: 640,
                    height: 480
                });
                
                camera.start().then(() => {
                    console.log('摄像头已启动,姿势识别准备就绪');
                    
                    // 更新状态显示
                    const statusIndicator = document.getElementById('status-indicator');
                    const statusText = document.getElementById('status-text');
                    statusIndicator.className = 'status-indicator';
                    statusText.textContent = '姿势识别已就绪,请开始游戏';
                    
                    // 显示提示
                    gameManager.showHint('摄像头已启动,请点击"开始游戏"按钮开始。');
                    
                }).catch(error => {
                    console.error('摄像头启动失败:', error);
                    
                    const statusText = document.getElementById('status-text');
                    statusText.textContent = '摄像头启动失败,请检查权限';
                    statusText.style.color = '#ff4444';
                    
                    gameManager.showHint('摄像头启动失败,请检查浏览器权限设置。', 5000);
                });
            }
        }

        // ==========================================
        // 5. 初始化游戏
        // ==========================================
        let gameManager;

        window.addEventListener('DOMContentLoaded', () => {
            gameManager = new GameManager();
            gameManager.updateGameStats();
        });

        // 窗口大小调整处理
        window.addEventListener('resize', () => {
            const canvas = document.getElementById('video-canvas');
            if (canvas) {
                // 保持摄像头画面的宽高比
                const container = canvas.parentElement;
                canvas.width = container.clientWidth;
                canvas.height = container.clientHeight;
            }
        });
    </script>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值