欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

atomgit仓库地址:https://atomgit.com/feng8403000/qichuangzhanzheng

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、项目概述

1.1 项目背景

起床战争是一款将日常起床行为游戏化的桌面应用。每个人都经历过早上被闹钟叫醒却难以离开温暖被窝的痛苦,这款应用通过将闹钟和床铺拟人化为对战双方,让用户以"战斗"的方式完成起床过程。游戏化的设计不仅让起床变得有趣,还能通过统计和成就系统帮助用户养成良好的作息习惯。

本项目基于鸿蒙PC Electron框架开发,采用纯前端技术实现,无需复杂的后端支持,即可提供流畅的交互体验和可靠的数据持久化功能。

1.2 游戏设计理念

传统的起床应用往往是简单的计时器或提醒工具,用户很容易忽略或关闭它们。起床战争的核心设计理念是将起床行为转化为一场"能量消耗战":

  • 赖床能量:代表睡意和对被窝的留恋,初始值较高(80%)
  • 闹钟攻势:代表起床的动力和紧迫感,初始值较低(20%)

用户通过使用各种"技能"来消耗赖床能量,当赖床能量归零时,起床成功。这种设计让抽象的起床过程变得可视化、可量化,用户可以清晰地看到自己的"战斗进度"。

1.3 功能特性

功能模块 说明
战斗场景 闹钟与床铺的实时对战动画
能量系统 双能量条可视化显示
技能按钮 贪睡、起床、泼水、咖啡四种技能
闹钟设置 自定义时间、开关、重复日期
战斗记录 实时记录操作时间线
战绩统计 成功次数、连胜天数、最佳成绩
成就系统 五项成就条件触发解锁
数据持久化 localStorage保存所有数据

二、技术架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                    前端应用层                              │
│         (HTML/CSS/JavaScript + 起床战斗游戏)              │
├─────────────────────────────────────────────────────────────┤
│              Electron + Preload 层                        │
│              (IPC 通信、API 暴露)                        │
├─────────────────────────────────────────────────────────────┤
│               HarmonyOS 原生层                             │
│         (libadapter.so + ETS Adapters)                    │
└─────────────────────────────────────────────────────────────┘

2.2 模块划分

模块 职责 文件
视图层 HTML结构、战斗场景、UI组件 index.html
样式层 星空/日出主题、动画效果 style.css
业务层 游戏状态、技能逻辑、成就判定 js/app.js
数据层 游戏数据、成就定义 js/app.js (内置)

2.3 游戏状态机

let gameState = {
    bedPower: 80,           // 赖床能量 0-100
    alarmPower: 20,         // 闹钟攻势 0-100
    snoozeCount: 0,         // 贪睡次数
    wakeUpTime: null,       // 起床时间
    alarmStartTime: null,   // 闹钟响起时间
    isBattleActive: false,  // 战斗是否进行中
    currentStreak: 0,       // 当前连胜
    totalWakeups: 0,        // 总成功起床次数
    bestTime: null,         // 最佳用时(分钟)
    achievements: new Set(), // 已解锁成就
    battleLog: [],          // 战斗记录
    alarmSettings: {        // 闹钟配置
        hour: 7,
        minute: 0,
        enabled: true,
        days: [6]
    }
};

三、核心功能实现详解

3.1 游戏初始化流程

应用启动时执行完整的初始化流程:

function initApp() {
    // 1. 从本地存储加载游戏状态
    loadGameState();
    
    // 2. 更新时钟显示
    updateClock();
    
    // 3. 更新日期显示
    updateDate();
    
    // 4. 更新战斗界面
    updateBattleUI();
    
    // 5. 更新统计数据
    updateStats();
    
    // 6. 更新成就显示
    updateAchievements();
    
    // 7. 渲染战斗记录
    renderBattleLog();
    
    // 8. 设置事件监听
    setupEventListeners();
    
    // 9. 启动定时器每秒更新时间
    setInterval(updateClock, 1000);
    
    // 10. 开始战斗
    startBattle();
}

时钟更新函数每秒刷新一次,保持界面上时间与系统时间同步:

function updateClock() {
    const now = new Date();
    const hours = String(now.getHours()).padStart(2, '0');
    const minutes = String(now.getMinutes()).padStart(2, '0');
    document.getElementById('clockTime').textContent = `${hours}:${minutes}`;
}

3.2 战斗系统核心逻辑

开始战斗函数初始化所有战斗相关状态:

function startBattle() {
    gameState.isBattleActive = true;
    gameState.bedPower = 80;          // 赖床能量初始化
    gameState.alarmPower = 20;         // 闹钟攻势初始化
    gameState.snoozeCount = 0;
    gameState.wakeUpTime = null;
    gameState.alarmStartTime = new Date();  // 记录战斗开始时间
    gameState.battleLog = [];
    
    addBattleLog('⏰ 闹钟响起!战斗开始!', false);
    updateBattleUI();
}

更新战斗UI是核心渲染函数,负责同步界面与状态:

function updateBattleUI() {
    // 更新能量条宽度
    document.getElementById('bedPower').style.width = `${gameState.bedPower}%`;
    document.getElementById('bedPowerValue').textContent = `${gameState.bedPower}%`;
    document.getElementById('alarmPower').style.width = `${gameState.alarmPower}%`;
    document.getElementById('alarmPowerValue').textContent = `${gameState.alarmPower}%`;
    
    // 更新角色表情
    updateCharacterExpressions();
    
    // 更新对话气泡
    updateSpeeches();
}

3.3 技能系统实现

贪睡技能是最特殊的技能,它会增加赖床能量、减少闹钟攻势,模拟"再睡一会儿"的行为:

function useSnooze() {
    if (!gameState.isBattleActive) return;
    
    gameState.snoozeCount++;
    gameState.bedPower = Math.min(100, gameState.bedPower + 5);   // 上限100
    gameState.alarmPower = Math.max(0, gameState.alarmPower - 10); // 下限0
    
    addBattleLog('⏰ 使用了「贪睡5分钟」', false);
    updateBattleUI();
}

起床技能直接清空赖床能量,是最有效的结束战斗方式:

function useWakeUp() {
    if (!gameState.isBattleActive) return;
    
    gameState.bedPower = 0;
    gameState.alarmPower = Math.min(100, gameState.alarmPower + 50);
    
    addBattleLog('💪 使用了「起床!」', false);
    
    // 检查是否胜利
    if (gameState.bedPower <= 0) {
        victory();
    }
    
    updateBattleUI();
}

泼水清醒咖啡续命是中间效率的技能:

function useWater() {
    if (!gameState.isBattleActive) return;
    
    gameState.bedPower = Math.max(0, gameState.bedPower - 30);
    gameState.alarmPower = Math.min(100, gameState.alarmPower + 30);
    
    addBattleLog('💦 使用了「泼水清醒」', false);
    
    if (gameState.bedPower <= 0) {
        victory();
    }
    
    updateBattleUI();
}

function useCoffee() {
    if (!gameState.isBattleActive) return;
    
    gameState.bedPower = Math.max(0, gameState.bedPower - 20);
    gameState.alarmPower = Math.min(100, gameState.alarmPower + 20);
    
    addBattleLog('☕ 使用了「咖啡续命」', false);
    
    if (gameState.bedPower <= 0) {
        victory();
    }
    
    updateBattleUI();
}

3.4 角色表情与对话系统

根据赖床能量值动态更新角色表情:

function updateCharacterExpressions() {
    const sleepingPerson = document.getElementById('sleepingPerson');
    const clockRings = document.getElementById('clockRings');
    
    // 根据赖床能量决定表情
    if (gameState.bedPower > 60) {
        sleepingPerson.textContent = '😴';  // 熟睡
    } else if (gameState.bedPower > 30) {
        sleepingPerson.textContent = '😐';  // 勉强清醒
    } else if (gameState.bedPower > 10) {
        sleepingPerson.textContent = '😣';  // 挣扎
    } else {
        sleepingPerson.textContent = '😤';  // 即将起床
    }
    
    // 闹钟是否在响
    clockRings.classList.toggle('active', gameState.alarmPower < 100);
}

对话气泡内容根据能量比例动态选择:

function updateSpeeches() {
    const bedSpeech = document.getElementById('bedSpeech');
    const alarmSpeech = document.getElementById('alarmSpeech');
    
    // 赖床方的对话
    const bedSpeeches = [
        '再睡5分钟...',
        '被子太暖和了...',
        '再眯一会儿...',
        '闹钟别叫了...',
        '好困啊...',
        '让我再躺会儿...'
    ];
    
    // 闹钟方的对话
    const alarmSpeeches = [
        '响铃时间到!',
        '快起床!',
        '不能再睡了!',
        '起床起床!',
        '早安!该起床了!'
    ];
    
    if (gameState.bedPower > 50) {
        bedSpeech.textContent = bedSpeeches[Math.floor(Math.random() * bedSpeeches.length)];
    } else if (gameState.bedPower > 20) {
        bedSpeech.textContent = '好吧...再最后5分钟...';
    } else {
        bedSpeech.textContent = '好好好...起来了...';
    }
    
    if (gameState.alarmPower > 50) {
        alarmSpeech.textContent = alarmSpeeches[Math.floor(Math.random() * alarmSpeeches.length)];
    } else if (gameState.alarmPower > 20) {
        alarmSpeech.textContent = '加油!快赢了!';
    } else {
        alarmSpeech.textContent = '坚持就是胜利!';
    }
}

3.5 胜利判定与处理

当赖床能量归零时触发胜利:

function victory() {
    gameState.isBattleActive = false;
    gameState.wakeUpTime = new Date();
    
    // 计算用时(分钟)
    const duration = Math.round(
        (gameState.wakeUpTime - gameState.alarmStartTime) / 1000 / 60
    );
    
    // 更新统计数据
    gameState.totalWakeups++;
    gameState.currentStreak++;
    
    // 更新最佳成绩
    if (gameState.bestTime === null || duration < gameState.bestTime) {
        gameState.bestTime = duration;
    }
    
    // 添加胜利记录
    addBattleLog('🎉 起床成功!', true);
    
    // 检查成就解锁
    checkAchievements();
    
    // 保存状态到本地
    saveGameState();
    
    // 显示胜利弹窗
    showVictoryModal(duration);
    
    // 更新显示
    updateStats();
    updateAchievements();
}

成就检查函数在每次胜利后执行:

function checkAchievements() {
    for (const [id, def] of Object.entries(achievementDefs)) {
        if (!gameState.achievements.has(id) && def.condition()) {
            gameState.achievements.add(id);
            addBattleLog(`🌟 解锁成就:「${def.name}`, false);
        }
    }
    saveGameState();
}

成就定义表

const achievementDefs = {
    first_wake: { 
        name: '第一次起床', 
        icon: '🌅', 
        condition: () => gameState.totalWakeups >= 1 
    },
    streak_3: { 
        name: '三连胜', 
        icon: '🔥', 
        condition: () => gameState.currentStreak >= 3 
    },
    fast_5: { 
        name: '5分钟内起床', 
        icon: '⚡', 
        condition: () => gameState.bestTime !== null && gameState.bestTime <= 5 
    },
    no_snooze: { 
        name: '不贪睡', 
        icon: '🚫', 
        condition: () => gameState.totalWakeups >= 1 && gameState.snoozeCount === 0 
    },
    week_warrior: { 
        name: '一周战士', 
        icon: '⚔️', 
        condition: () => gameState.currentStreak >= 7 
    }
};

3.6 战斗记录系统

function addBattleLog(message, isVictory) {
    const now = new Date();
    const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
    
    gameState.battleLog.unshift({
        time,
        message,
        isVictory
    });
    
    // 限制记录数量为20条
    if (gameState.battleLog.length > 20) {
        gameState.battleLog.pop();
    }
    
    renderBattleLog();
}

function renderBattleLog() {
    const container = document.getElementById('logContainer');
    container.innerHTML = gameState.battleLog.map(log => `
        <div class="log-entry ${log.isVictory ? 'victory' : ''}">
            <span class="log-time">${log.time}</span>
            <span class="log-content">${log.message}</span>
        </div>
    `).join('');
}

3.7 数据持久化

保存游戏状态

function saveGameState() {
    const toSave = {
        totalWakeups: gameState.totalWakeups,
        currentStreak: gameState.currentStreak,
        bestTime: gameState.bestTime,
        achievements: [...gameState.achievements],
        alarmSettings: gameState.alarmSettings
    };
    localStorage.setItem('wakeupWarState', JSON.stringify(toSave));
}

加载游戏状态

function loadGameState() {
    const saved = localStorage.getItem('wakeupWarState');
    if (saved) {
        const parsed = JSON.parse(saved);
        gameState.totalWakeups = parsed.totalWakeups || 0;
        gameState.currentStreak = parsed.currentStreak || 0;
        gameState.bestTime = parsed.bestTime || null;
        gameState.achievements = new Set(parsed.achievements || []);
        gameState.alarmSettings = parsed.alarmSettings || gameState.alarmSettings;
        
        // 恢复闹钟设置UI
        document.getElementById('alarmHour').value = gameState.alarmSettings.hour;
        document.getElementById('alarmMinute').value = gameState.alarmSettings.minute;
        document.getElementById('alarmEnabled').checked = gameState.alarmSettings.enabled;
        updateToggleLabel();
    }
}

四、前端样式设计

4.1 主题色彩系统

:root {
    /* 夜晚主题色 */
    --night-dark: #1a1a2e;
    --night-blue: #16213e;
    --night-purple: #0f3460;
    --star-gold: #ffd700;
    
    /* 白天主题色 */
    --sunrise-orange: #ff6b35;
    --sunrise-pink: #ff8c61;
    --sky-blue: #74c0fc;
    --cloud-white: #f8f9fa;
    
    /* 战斗色 */
    --alarm-red: #e74c3c;
    --alarm-orange: #e67e22;
    --bed-purple: #9b59b6;
    --bed-blue: #3498db;
}

4.2 星空背景动画

.night-sky {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
}

.stars {
    position: absolute;
    width: 100%;
    height: 100%;
    background-image: 
        radial-gradient(2px 2px at 20px 30px, white, transparent),
        radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.8), transparent),
        /* ... 更多星星 */
    background-repeat: repeat;
    background-size: 500px 300px;
    animation: twinkle 4s ease-in-out infinite;
}

@keyframes twinkle {
    0%, 100% { opacity: 0.8; }
    50% { opacity: 1; }
}

4.3 闹钟摇动动画

.logo-icon {
    font-size: 3rem;
    animation: alarmShake 1s ease-in-out infinite;
}

@keyframes alarmShake {
    0%, 100% { transform: rotate(-10deg); }
    50% { transform: rotate(10deg); }
}

4.4 VS标识动画

.vs-badge {
    font-size: 2rem;
    font-weight: 800;
    color: var(--alarm-red);
    text-shadow: 2px 2px 0 var(--alarm-orange);
    animation: vsShake 0.5s ease-in-out infinite;
}

@keyframes vsShake {
    0%, 100% { transform: rotate(-5deg); }
    50% { transform: rotate(5deg); }
}

4.5 技能按钮样式

.battle-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--spacing-md);
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: all var(--transition-fast);
}

.battle-btn:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
}

.snooze-btn {
    background: linear-gradient(135deg, #9b59b6, #8e44ad);
}

.wakeup-btn {
    background: linear-gradient(135deg, #27ae60, #2ecc71);
}

.water-btn {
    background: linear-gradient(135deg, #3498db, #2980b9);
}

.coffee-btn {
    background: linear-gradient(135deg, #e67e22, #d35400);
}

4.6 开关切换样式

.switch {
    position: relative;
    width: 60px;
    height: 34px;
}

.slider {
    position: absolute;
    cursor: pointer;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: #ccc;
    transition: var(--transition-fast);
    border-radius: var(--radius-full);
}

.slider:before {
    position: absolute;
    content: "";
    height: 26px;
    width: 26px;
    left: 4px;
    bottom: 4px;
    background-color: white;
    transition: var(--transition-fast);
    border-radius: 50%;
}

input:checked + .slider {
    background-color: var(--alarm-red);
}

input:checked + .slider:before {
    transform: translateX(26px);
}

五、事件监听系统

function setupEventListeners() {
    // 战斗技能按钮
    document.getElementById('snoozeBtn').addEventListener('click', useSnooze);
    document.getElementById('wakeupBtn').addEventListener('click', useWakeUp);
    document.getElementById('waterBtn').addEventListener('click', useWater);
    document.getElementById('coffeeBtn').addEventListener('click', useCoffee);
    
    // 闹钟设置
    document.getElementById('alarmHour').addEventListener('change', updateAlarmSettings);
    document.getElementById('alarmMinute').addEventListener('change', updateAlarmSettings);
    document.getElementById('alarmEnabled').addEventListener('change', () => {
        updateToggleLabel();
        updateAlarmSettings();
    });
    
    // 星期按钮
    setupDayButtons();
    
    // 胜利弹窗
    document.getElementById('closeVictoryModal').addEventListener('click', closeVictoryModal);
    document.getElementById('victoryModal').addEventListener('click', (e) => {
        if (e.target === document.getElementById('victoryModal')) {
            closeVictoryModal();
        }
    });
}

星期按钮设置

function setupDayButtons() {
    const buttons = document.querySelectorAll('.day-btn');
    buttons.forEach(btn => {
        const day = parseInt(btn.dataset.day);
        if (gameState.alarmSettings.days.includes(day)) {
            btn.classList.add('active');
        }
        
        btn.addEventListener('click', () => {
            btn.classList.toggle('active');
            
            if (btn.classList.contains('active')) {
                if (!gameState.alarmSettings.days.includes(day)) {
                    gameState.alarmSettings.days.push(day);
                }
            } else {
                gameState.alarmSettings.days = gameState.alarmSettings.days.filter(d => d !== day);
            }
            
            saveGameState();
        });
    });
}

六、代码优化建议

6.1 性能优化

当前实现使用直接DOM操作,对于频繁更新的场景(如动画),建议使用以下优化:

// 批量DOM更新
function updateBattleUI() {
    // 创建文档片段减少重绘
    const fragment = document.createDocumentFragment();
    
    // 批量更新
    const bedPowerEl = document.getElementById('bedPower');
    const alarmPowerEl = document.getElementById('alarmPower');
    
    bedPowerEl.style.width = `${gameState.bedPower}%`;
    alarmPowerEl.style.width = `${gameState.alarmPower}%`;
    
    // ... 其他更新
}

6.2 错误处理

function saveGameState() {
    try {
        const toSave = {
            totalWakeups: gameState.totalWakeups,
            currentStreak: gameState.currentStreak,
            bestTime: gameState.bestTime,
            achievements: [...gameState.achievements],
            alarmSettings: gameState.alarmSettings
        };
        localStorage.setItem('wakeupWarState', JSON.stringify(toSave));
    } catch (error) {
        console.error('保存游戏状态失败:', error);
    }
}

6.3 扩展性设计

添加新技能时,只需在HTML中添加按钮,在JS中添加处理函数:

// 在achievementDefs后添加技能定义
const skillDefs = {
    splash: {
        name: '冷水洗脸',
        icon: '💧',
        bedPowerChange: -40,
        alarmPowerChange: 40,
        logMessage: '使用了「冷水洗脸」'
    }
};

// 添加对应的处理函数
function useSkill(skillId) {
    const skill = skillDefs[skillId];
    if (!skill || !gameState.isBattleActive) return;
    
    gameState.bedPower = Math.max(0, gameState.bedPower + skill.bedPowerChange);
    gameState.alarmPower = Math.min(100, gameState.alarmPower + skill.alarmPowerChange);
    
    addBattleLog(skill.logMessage, false);
    
    if (gameState.bedPower <= 0) {
        victory();
    }
    
    updateBattleUI();
}

七、总结与展望

7.1 项目成果

起床战争项目成功实现了以下功能:

  1. 完整的战斗系统:赖床能量与闹钟攻势的双能量条机制
  2. 四个战斗技能:贪睡、起床、泼水、咖啡,各有不同效果
  3. 动态界面:角色表情和对话根据状态实时变化
  4. 闹钟设置:时间选择、开关控制、重复日期设置
  5. 战斗记录:实时记录操作时间线
  6. 战绩统计:累计次数、连胜天数、最佳成绩
  7. 成就系统:五项成就条件触发解锁
  8. 数据持久化:localStorage保存所有数据

7.2 技术亮点

  • 游戏化设计:将日常行为转化为有趣的"战斗"
  • 状态驱动:清晰的state管理,状态变化驱动UI更新
  • 数据持久化:localStorage实现数据的本地存储
  • 动画效果:CSS关键帧实现丰富的动画
  • 响应式设计:适配多种屏幕尺寸

7.3 未来规划

优先级 功能 说明
音效系统 添加闹钟铃声和胜利音效
皮肤系统 解锁不同主题的皮肤
统计图表 显示每周/每月的起床统计
提醒推送 到达设定时间时系统通知
社交分享 分享战绩到社交平台
多人对战 与朋友比较起床速度

7.4 技术拓展

未来可以考虑引入以下技术:

  • Web Audio API:实现闹钟铃声播放
  • Service Worker:实现离线功能和推送通知
  • Canvas/WebGL:实现更复杂的粒子特效
  • IndexedDB:存储更大量的历史数据
  • PWA:支持添加到桌面,实现离线使用

附录

核心文件列表

文件 说明
index.html 游戏主页面,包含战斗场景和所有UI组件
style.css 星空/日出主题样式,包含所有动画
js/app.js 游戏核心逻辑,状态管理和事件处理

游戏参数表

参数 默认值 范围 说明
bedPower 80 0-100 赖床能量
alarmPower 20 0-100 闹钟攻势
snoozeBedBonus +5 - 贪睡增加赖床
snoozeAlarmPenalty -10 - 贪睡减少闹钟
wakeupBedPenalty -100 - 起床清空赖床
waterBedPenalty -30 - 泼水减少赖床
coffeeBedPenalty -20 - 咖啡减少赖床

浏览器兼容性

浏览器 最低版本
Chrome 80+
Firefox 75+
Safari 13+
Edge 80+

参考资料

  1. MDN Web Docs - localStorage
  2. MDN Web Docs - CSS动画
  3. Electron官方文档
  4. HarmonyOS开发者文档
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐