好友
阅读权限10
听众
最后登录1970-1-1
|
上班太无聊了,想了想自己走过的路,真的是有挫折有激励有进步,随手借助AI写了一个人生重开模拟器・打工人本地重生版的游戏,试了试,还行。
[backcolor=lab(9.03835 1.15298 1.92955)]
主要实现了以下核心功能:[backcolor=lab(9.03835 1.15298 1.92955)]
核心玩法[CSS] 纯文本查看 复制代码 import React from 'react';[/size][/font][/backcolor][/color][/size][/font][/backcolor][/color]
// 状态表情组件 - 根据数值显示不同表情
export function StatusEmoji({ value, type }: { value: number; type: 'brain' | 'physique' | 'family' | 'dignity' | 'mood' | 'savings' }) {
const getEmoji = () => {
if (type === 'savings') {
if (value >= 500000) return '💰';
if (value >= 100000) return '💵';
if (value >= 0) return '💸';
if (value >= -50000) return '😰';
return '😭';
}
// 其他属性(0-100)
if (value >= 80) return '😄';
if (value >= 60) return '😊';
if (value >= 40) return '😐';
if (value >= 20) return '😔';
return '😢';
};
return (
<span className="animate-float text-2xl ml-2 inline-block">
{getEmoji()}
</span>
);
}
// 状态趋势图标
export function TrendIndicator({ change }: { change: number }) {
if (change === 0) return null;
const isPositive = change > 0;
return (
<span className={`inline-flex items-center ml-2 ${
isPositive ? 'text-green-400' : 'text-red-400'
} animate-number-change`}>
{isPositive ? '↑' : '↓'}
<span className="text-xs ml-1">{Math.abs(change)}</span>
</span>
);
}
// 动态进度条组件
export function AnimatedProgressBar({
value,
max = 100,
color = 'from-yellow-400 to-orange-500',
showAnimation = true
}: {
value: number;
max?: number;
color?: string;
showAnimation?: boolean;
}) {
const percentage = Math.min((value / max) * 100, 100);
return (
<div className="relative bg-slate-800 rounded-full h-3 overflow-hidden">
<div
className={`bg-gradient-to-r ${color} h-3 rounded-full transition-all duration-500 ${
showAnimation ? 'progress-bar-animated' : ''
}`}
style={{ width: `${percentage}%` }}
/>
{showAnimation && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="animate-shimmer w-full h-full" />
</div>
)}
</div>
);
}
// 数值显示组件(带动画)
export function AnimatedNumber({
value,
prefix = '',
suffix = '',
className = ''
}: {
value: number;
prefix?: string;
suffix?: string;
className?: string;
}) {
const isPositive = value >= 0;
return (
<span className={`number-animate font-bold ${className} ${
isPositive ? 'text-green-400' : 'text-red-400'
}`}>
{prefix}{value.toLocaleString()}{suffix}
</span>
);
}
// 年龄阶段图标
export function AgeStageIcon({ age }: { age: number }) {
const getStageIcon = () => {
if (age < 22) return '🎓';
if (age < 30) return '💼';
if (age < 40) return '👨‍💼';
if (age < 50) return '🧓';
if (age < 60) return '👴';
return '🪦';
};
const getStageName = () => {
if (age < 22) return '校园阶段';
if (age < 30) return '职场新人';
if (age < 40) return '职场成熟';
if (age < 50) return '中年阶段';
if (age < 60) return '退休前期';
return '人生终点';
};
return (
<div className="flex items-center animate-slide-in">
<span className="text-3xl mr-2 animate-float">{getStageIcon()}</span>
<span className="text-sm text-slate-400">{getStageName()}</span>
</div>
);
}
// 结局类型图标
export function EndingTypeIcon({ category }: { category: string }) {
const icons: Record<string, string> = {
highlight: '🏆',
stable: '🏠',
tragic: '💔',
chill: '🏖️',
bottom: '⚠️',
};
return (
<span className="text-4xl animate-bounce-in">
{icons[category] || '❓'}
</span>
);
}
// 天赋闪光效果
export function TalentGlow({ rarity }: { rarity: string }) {
if (rarity !== 'orange') return null;
return (
<div className="absolute inset-0 animate-glow rounded-lg pointer-events-none" />
);
}
// 加载动画
export function LoadingSpinner() {
return (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-yellow-400" />
</div>
);
}
// 状态指示灯
export function StatusIndicator({ status }: { status: 'good' | 'warning' | 'danger' }) {
return (
<div className={`status-indicator status-${status} inline-block w-3 h-3 rounded-full mr-2 ${
status === 'good' ? 'bg-green-400' :
status === 'warning' ? 'bg-yellow-400' :
'bg-red-400'
} animate-pulse-custom`} />
);
}
// 属性卡片组件(带动画)
export function AnimatedAttributeCard({
name,
value,
change,
icon
}: {
name: string;
value: number;
change?: number;
icon?: string;
}) {
const getStatusColor = (val: number) => {
if (val >= 60) return 'from-green-400 to-emerald-500';
if (val >= 40) return 'from-yellow-400 to-orange-500';
return 'from-red-400 to-pink-500';
};
return (
<div className="bg-slate-800 rounded-lg p-3 card-animate hover-lift">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center">
{icon && <span className="text-xl mr-2 animate-float">{icon}</span>}
<span className="font-medium">{name}</span>
</div>
<div className="flex items-center">
<AnimatedNumber value={value} className="text-lg" />
{change !== undefined && <TrendIndicator change={change} />}
</div>
</div>
<AnimatedProgressBar
value={value}
color={getStatusColor(value)}
showAnimation={value > 60}
/>
</div>
);
}
// 事件类型图标
export function EventTypeIcon({ pool }: { pool: string }) {
const icons: Record<string, string> = {
campus: '🎓',
workplace: '💼',
crisis: '⚠️',
retirement: '🏖️',
};
return (
<span className="text-2xl animate-bounce-in">
{icons[pool] || '📌'}
</span>
);
}[JavaScript] 纯文本查看 复制代码 import React, { useState, useEffect, useCallback } from 'react';
import { GameEngine } from '../data/engine';
import { allTalents } from '../data/talents';
import { allEndings, getEndingCategoryName, getEndingCategoryColor } from '../data/endings';
import { exportLifeHistory, copyToClipboard } from '../data/storage';
import type { Attributes, Talent, SaveData, GameState, YearEvent } from '../types/game';
import { ATTRIBUTE_NAMES, ATTRIBUTE_DESCRIPTIONS } from '../types/game';
import {
StatusEmoji,
AnimatedProgressBar,
AnimatedNumber,
AgeStageIcon,
EndingTypeIcon,
TalentGlow,
EventTypeIcon,
AnimatedAttributeCard,
TrendIndicator,
} from './AnimatedElements';
// 稀有度颜色映射
const rarityColors: Record<string, string> = {
white: 'bg-gray-100 text-gray-800 border-gray-300',
blue: 'bg-blue-100 text-blue-800 border-blue-300',
purple: 'bg-purple-100 text-purple-800 border-purple-300',
orange: 'bg-orange-100 text-orange-800 border-orange-300',
};
const rarityNames: Record<string, string> = {
white: '普通',
blue: '优质',
purple: '稀有',
orange: '逆天',
};
// 属性图标映射
const attributeIcons: Record<string, string> = {
brain: '🧠',
physique: '💪',
family: '🏠',
dignity: '👑',
savings: '💰',
mood: '❤️',
};
export function App() {
const [engine] = useState(() => new GameEngine());
const [state, setState] = useState<GameState>(() => engine.getState());
const [page, setPage] = useState<'start' | 'talent' | 'attribute' | 'game' | 'ending' | 'collection' | 'saves'>('start');
const [talentPool, setTalentPool] = useState<Talent[]>([]);
const [selectedTalents, setSelectedTalents] = useState<string[]>([]);
const [attributePoints, setAttributePoints] = useState<Partial<Attributes>>({});
const [remainingPoints, setRemainingPoints] = useState(20);
const [yearEvent, setYearEvent] = useState<YearEvent | null>(null);
const [showEventModal, setShowEventModal] = useState(false);
const [currentEventIndex, setCurrentEventIndex] = useState(0);
const [saveList, setSaveList] = useState<SaveData[]>([]);
// 更新状态
const updateState = useCallback(() => {
setState(engine.getState());
}, [engine]);
// 开始新游戏
const startNewGame = () => {
engine.resetGame();
updateState();
setPage('start');
};
// 选择重生路线
const selectPath = (path: 'college' | 'career') => {
engine.selectRebirthPath(path);
const pool = engine.getRandomTalentPool();
setTalentPool(pool);
setSelectedTalents([]);
updateState();
setPage('talent');
};
// 选择天赋
const toggleTalent = (talentId: string) => {
const talent = allTalents.find(t => t.id === talentId);
if (!talent) return;
// 检查互斥
if (talent.exclusive) {
for (const excludedId of talent.exclusive) {
if (selectedTalents.includes(excludedId)) {
return; // 已选择互斥天赋,不能选择
}
}
}
if (selectedTalents.includes(talentId)) {
setSelectedTalents(selectedTalents.filter(id => id !== talentId));
} else if (selectedTalents.length < 3) {
setSelectedTalents([...selectedTalents, talentId]);
}
};
// 确认天赋选择
const confirmTalents = () => {
if (selectedTalents.length === 0) return;
engine.selectTalents(selectedTalents);
setAttributePoints({});
setRemainingPoints(20);
updateState();
setPage('attribute');
};
// 分配属性点
const adjustAttribute = (attr: keyof Attributes, delta: number) => {
if (attr === 'savings' || attr === 'mood') return; // 不能分配存款和心气
const current = attributePoints[attr] || 0;
const newPoints = remainingPoints - delta;
if (newPoints < 0 || current + delta < 0 || current + delta > 20) return;
setAttributePoints({ ...attributePoints, [attr]: current + delta });
setRemainingPoints(newPoints);
};
// 确认属性分配
const confirmAttributes = () => {
engine.assignAttributes(attributePoints);
updateState();
setPage('game');
};
// 推进一年
const advanceYear = () => {
const event = engine.advanceYear();
updateState();
if (event) {
setYearEvent(event);
if (event.events.length > 0) {
setCurrentEventIndex(0);
setShowEventModal(true);
}
} else {
// 游戏结束
setPage('ending');
}
};
// 关闭事件弹窗
const closeEventModal = () => {
setShowEventModal(false);
setYearEvent(null);
setCurrentEventIndex(0);
// 检查是否触发结局
if (state.phase === 'ending') {
setPage('ending');
}
};
// 下一个事件
const nextEvent = () => {
if (yearEvent && currentEventIndex < yearEvent.events.length - 1) {
setCurrentEventIndex(currentEventIndex + 1);
} else {
closeEventModal();
}
};
// 快速推进到结局
const fastForward = () => {
while (state.age < 65 && state.phase === 'playing') {
engine.advanceYear();
updateState();
}
setPage('ending');
};
// 查看图鉴
const viewCollection = () => {
setPage('collection');
};
// 查看存档
const viewSaves = () => {
setSaveList(engine.getSaves());
setPage('saves');
};
// 导出人生历程
const exportHistory = async (save: SaveData) => {
const text = exportLifeHistory(save);
const success = await copyToClipboard(text);
if (success) {
alert('人生历程已复制到剪贴板!');
} else {
alert('复制失败,请手动复制');
}
};
// 获取结局详情
const getEndingDetail = () => {
if (!state.currentEnding) return null;
return allEndings.find(e => e.id === state.currentEnding);
};
return (
<div className="min-h-screen bg-gradient-to-b from-slate-900 to-slate-800 text-white">
{/* 主页 */}
{page === 'start' && (
<div className="flex flex-col items-center justify-center min-h-screen p-8 animate-fade-in">
<div className="max-w-md w-full text-center">
<h1 className="text-4xl font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 to-orange-500 animate-shimmer title-animate">
人生重开·打工人本地重生模拟器
</h1>
<p className="text-slate-400 mb-8 text-sm animate-slide-up delay-100">
带着前世记忆重生,规避职场陷阱,拒绝内卷躺平,开启理想人生
</p>
<div className="space-y-4 mb-8">
<button
onClick={() => selectPath('college')}
className="w-full py-4 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg font-bold hover:opacity-90 transition-all btn-animate hover-scale animate-slide-up delay-200"
>
<span className="inline-flex items-center">
🎓 大学重生(18岁开局)
</span>
</button>
<p className="text-xs text-slate-500 animate-slide-up delay-300">
记得未来风口、房价走势、踩坑副业、渣男烂同事
</p>
<button
onClick={() => selectPath('career')}
className="w-full py-4 bg-gradient-to-r from-green-500 to-teal-600 rounded-lg font-bold hover:opacity-90 transition-all btn-animate hover-scale animate-slide-up delay-400"
>
<span className="inline-flex items-center">
💼 入职重生(22岁开局)
</span>
</button>
<p className="text-xs text-slate-500 animate-slide-up delay-500">
记得领导性格、裁员名单、项目坑点、晋升潜规则
</p>
</div>
<div className="flex gap-4 justify-center animate-slide-up delay-500">
<button
onClick={viewCollection}
className="px-6 py-2 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift"
>
📚 结局图鉴
</button>
<button
onClick={viewSaves}
className="px-6 py-2 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift"
>
💾 轮回存档
</button>
</div>
<div className="mt-8 text-xs text-slate-500 animate-fade-in delay-500">
已解锁结局: {engine.getCollection().endings.length}/{allEndings.length} |
游戏次数: {engine.getCollection().totalGames}
</div>
</div>
</div>
)}
{/* 天赋选择 */}
{page === 'talent' && (
<div className="min-h-screen p-8">
<div className="max-w-2xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold mb-2">选择天赋(最多3个)</h2>
<p className="text-slate-400">
已选择 {selectedTalents.length}/3 个天赋
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
{talentPool.map(talent => {
const isSelected = selectedTalents.includes(talent.id);
const isExcluded = talent.exclusive?.some(id => selectedTalents.includes(id));
return (
<div
key={talent.id}
onClick={() => !isExcluded && toggleTalent(talent.id)}
className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
isSelected
? 'border-yellow-400 bg-yellow-400/20'
: isExcluded
? 'border-slate-600 bg-slate-700/50 opacity-50 cursor-not-allowed'
: 'border-slate-600 bg-slate-700 hover:border-slate-500'
}`}
>
<div className="flex items-center gap-2 mb-2">
<span className={`px-2 py-1 rounded text-xs font-bold ${
rarityColors[talent.rarity]
}`}>
{rarityNames[talent.rarity]}
</span>
<h3 className="font-bold">{talent.name}</h3>
</div>
<p className="text-sm text-slate-400">{talent.description}</p>
{talent.exclusive && talent.exclusive.length > 0 && (
<p className="text-xs text-red-400 mt-2">
与 {talent.exclusive.map(id => allTalents.find(t => t.id === id)?.name).join('、')} 互斥
</p>
)}
</div>
);
})}
</div>
<div className="flex justify-center gap-4">
<button
onClick={() => setPage('start')}
className="px-6 py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors"
>
返回
</button>
<button
onClick={confirmTalents}
disabled={selectedTalents.length === 0}
className="px-6 py-3 bg-gradient-to-r from-yellow-500 to-orange-600 rounded-lg font-bold hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
>
确认选择
</button>
</div>
</div>
</div>
)}
{/* 属性分配 */}
{page === 'attribute' && (
<div className="min-h-screen p-8">
<div className="max-w-xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold mb-2">分配属性点</h2>
<p className="text-slate-400">
剩余可分配点数: <span className="text-yellow-400 font-bold">{remainingPoints}</span>
</p>
</div>
<div className="space-y-4 mb-8">
{(['brain', 'physique', 'family', 'dignity'] as const).map(attr => (
<div key={attr} className="bg-slate-700 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-bold">{ATTRIBUTE_NAMES[attr]}</h3>
<p className="text-xs text-slate-400">{ATTRIBUTE_DESCRIPTIONS[attr]}</p>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-slate-400">初始: {state.attributes[attr]}</span>
<span className="text-sm text-yellow-400">+{attributePoints[attr] || 0}</span>
</div>
</div>
<div className="flex items-center gap-4">
<button
onClick={() => adjustAttribute(attr, -1)}
className="px-4 py-2 bg-slate-600 rounded hover:bg-slate-500 transition-colors"
>
-1
</button>
<div className="flex-1 bg-slate-800 rounded-full h-2">
<div
className="bg-gradient-to-r from-yellow-400 to-orange-500 h-2 rounded-full transition-all"
style={{ width: `${((state.attributes[attr] + (attributePoints[attr] || 0)) / 100) * 100}%` }}
/>
</div>
<button
onClick={() => adjustAttribute(attr, 1)}
className="px-4 py-2 bg-slate-600 rounded hover:bg-slate-500 transition-colors"
>
+1
</button>
</div>
</div>
))}
</div>
<div className="flex justify-center gap-4">
<button
onClick={() => setPage('talent')}
className="px-6 py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors"
>
返回
</button>
<button
onClick={confirmAttributes}
className="px-6 py-3 bg-gradient-to-r from-yellow-500 to-orange-600 rounded-lg font-bold hover:opacity-90 transition-opacity"
>
开始人生
</button>
</div>
</div>
</div>
)}
{/* 游戏进行 */}
{page === 'game' && (
<div className="min-h-screen p-8 animate-fade-in">
<div className="max-w-xl mx-auto">
{/* 属性面板 */}
<div className="bg-slate-700 rounded-lg p-4 mb-6 card-animate hover-lift">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-xl">当前属性</h3>
<div className="flex items-center">
<AgeStageIcon age={state.age} />
<span className="text-2xl font-bold text-yellow-400 ml-4 animate-pulse-custom">{state.age}岁</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
{(['brain', 'physique', 'family', 'dignity', 'mood'] as const).map((attr, i) => (
<div key={attr} className={`flex items-center justify-between bg-slate-800 rounded p-2 animate-slide-in delay-${i * 100}`}>
<div className="flex items-center">
<span className="text-lg mr-2 animate-float">{attributeIcons[attr]}</span>
<span>{ATTRIBUTE_NAMES[attr]}</span>
</div>
<div className="flex items-center">
<span className="font-bold">{state.attributes[attr]}</span>
<StatusEmoji value={state.attributes[attr]} type={attr} />
</div>
</div>
))}
<div className="flex items-center justify-between bg-slate-800 rounded p-2 col-span-2 animate-slide-in delay-500">
<div className="flex items-center">
<span className="text-lg mr-2 animate-float">{attributeIcons.savings}</span>
<span>存款</span>
</div>
<div className="flex items-center">
<AnimatedNumber
value={state.attributes.savings}
prefix="¥"
className="text-base"
/>
<StatusEmoji value={state.attributes.savings} type="savings" />
</div>
</div>
</div>
{/* 属性进度条 */}
<div className="mt-4 grid grid-cols-3 gap-2">
{(['brain', 'physique', 'mood'] as const).map(attr => (
<div key={attr} className="text-xs">
<div className="flex items-center justify-between mb-1">
<span>{ATTRIBUTE_NAMES[attr]}</span>
<span>{state.attributes[attr]}</span>
</div>
<AnimatedProgressBar
value={state.attributes[attr]}
showAnimation={state.attributes[attr] > 60}
/>
</div>
))}
</div>
{/* 持有天赋 */}
{state.selectedTalents.length > 0 && (
<div className="mt-4 pt-4 border-t border-slate-600">
<p className="text-xs text-slate-400 mb-2">持有天赋:</p>
<div className="flex flex-wrap gap-2">
{state.selectedTalents.map((id, i) => {
const talent = allTalents.find(t => t.id === id);
return talent ? (
<span
key={id}
className={`px-2 py-1 rounded text-xs animate-slide-in delay-${i * 100} ${
rarityColors[talent.rarity]
} ${talent.rarity === 'orange' ? 'animate-glow' : ''}`}
>
{talent.name}
</span>
) : null;
})}
</div>
</div>
)}
</div>
{/* 历史记录 */}
<div className="bg-slate-700 rounded-lg p-4 mb-6 max-h-60 overflow-y-auto animate-slide-up">
<h3 className="font-bold mb-4">📜 人生历程</h3>
<div className="space-y-2 text-sm">
{state.history.slice(-10).map((h, i) => (
<div key={i} className="bg-slate-800 rounded p-2 animate-slide-in delay-${i * 50}">
<span className="text-yellow-400 font-bold">{h.age}岁</span>
<span className="text-slate-400 ml-2">{h.description}</span>
{h.majorEvent && (
<span className="text-orange-400 text-xs ml-2">📌 {h.majorEvent}</span>
)}
</div>
))}
</div>
</div>
{/* 操作按钮 */}
<div className="flex flex-col gap-4">
<button
onClick={advanceYear}
className="py-4 bg-gradient-to-r from-yellow-500 to-orange-600 rounded-lg font-bold hover:opacity-90 transition-all btn-animate hover-scale animate-pulse-custom"
>
<span className="inline-flex items-center">
🚀 推进一年 → {state.age + 1}岁
</span>
</button>
<button
onClick={fastForward}
className="py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift"
>
⏩ 快速推进到结局
</button>
</div>
</div>
{/* 事件弹窗 */}
{showEventModal && yearEvent && yearEvent.events.length > 0 && (
<div className="fixed inset-0 bg-black/80 flex items-center justify-center p-4 z-50 modal-animate">
<div className="bg-slate-800 rounded-lg p-6 max-w-md w-full modal-content-animate hover-glow">
<div className="flex items-center gap-3 mb-4">
<EventTypeIcon pool={yearEvent.events[currentEventIndex].eventId.split('_')[0]} />
<h3 className="text-xl font-bold text-yellow-400 animate-fade-in">
{yearEvent.events[currentEventIndex].title}
</h3>
</div>
<p className="text-slate-400 mb-4 animate-slide-in">
✓ 选择: {yearEvent.events[currentEventIndex].choice}
</p>
<div className="mb-4 animate-slide-up delay-100">
<p className="text-xs text-slate-500 mb-2">📊 属性变化:</p>
<div className="flex flex-wrap gap-2">
{Object.entries(yearEvent.events[currentEventIndex].effects).map(([key, value]) => (
<span key={key} className={`px-3 py-1 rounded text-sm animate-bounce-in ${
value > 0 ? 'bg-green-900 text-green-400' : 'bg-red-900 text-red-400'
}`}>
{attributeIcons[key] || '📊'} {ATTRIBUTE_NAMES[key as keyof Attributes] || key}
<span className="ml-1">{value > 0 ? '+' : ''}{value}</span>
</span>
))}
</div>
</div>
<button
onClick={nextEvent}
className="w-full py-3 bg-gradient-to-r from-yellow-500 to-orange-600 rounded-lg font-bold hover:opacity-90 transition-opacity btn-animate"
>
{currentEventIndex < yearEvent.events.length - 1 ? '👉 下一个事件' : '✨ 继续人生'}
</button>
</div>
</div>
)}
</div>
)}
{/* 结局页面 */}
{page === 'ending' && (
<div className="min-h-screen p-8 animate-fade-in">
<div className="max-w-md mx-auto text-center">
<h2 className="text-3xl font-bold mb-2 animate-bounce-in">人生结束</h2>
<p className="text-slate-400 mb-4 animate-slide-up delay-100">
享年 {state.age} 岁
</p>
{getEndingDetail() && (
<div className="bg-slate-700 rounded-lg p-6 mb-6 card-animate hover-glow">
<div className="mb-4 animate-float">
<EndingTypeIcon category={getEndingDetail()!.category} />
</div>
<div className={`inline-block px-3 py-1 rounded-full mb-4 animate-pulse-custom ${
getEndingCategoryColor(getEndingDetail()!.category)
}`}>
{getEndingCategoryName(getEndingDetail()!.category)}
</div>
<h3 className="text-2xl font-bold mb-2 text-yellow-400 animate-slide-up">
{getEndingDetail()!.name}
</h3>
<p className="text-slate-400 animate-slide-up delay-200">
{getEndingDetail()!.description}
</p>
</div>
)}
{/* 最终属性 */}
<div className="bg-slate-700 rounded-lg p-4 mb-6 animate-slide-up delay-300">
<h4 className="font-bold mb-2">📊 最终属性</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
{(['brain', 'physique', 'family', 'dignity', 'mood'] as const).map((attr, i) => (
<div key={attr} className={`flex items-center justify-between bg-slate-800 rounded p-2 animate-slide-in delay-${i * 100}`}>
<div className="flex items-center">
<span className="text-lg mr-2">{attributeIcons[attr]}</span>
<span>{ATTRIBUTE_NAMES[attr]}</span>
</div>
<AnimatedNumber value={state.attributes[attr]} />
</div>
))}
<div className="flex items-center justify-between bg-slate-800 rounded p-2 col-span-2 animate-slide-in delay-500">
<div className="flex items-center">
<span className="text-lg mr-2">{attributeIcons.savings}</span>
<span>存款</span>
</div>
<AnimatedNumber
value={state.attributes.savings}
prefix="¥"
/>
</div>
</div>
</div>
<div className="flex flex-col gap-4 animate-slide-up delay-400">
<button
onClick={startNewGame}
className="py-4 bg-gradient-to-r from-yellow-500 to-orange-600 rounded-lg font-bold hover:opacity-90 transition-all btn-animate hover-scale animate-pulse-custom"
>
🔄 再来一轮
</button>
<div className="flex gap-4">
<button
onClick={viewCollection}
className="flex-1 py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift"
>
📚 结局图鉴
</button>
<button
onClick={viewSaves}
className="flex-1 py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift"
>
💾 轮回存档
</button>
</div>
</div>
</div>
</div>
)}
{/* 图鉴页面 */}
{page === 'collection' && (
<div className="min-h-screen p-8 animate-fade-in">
<div className="max-w-2xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold mb-2 animate-bounce-in">📚 结局图鉴</h2>
<p className="text-slate-400 animate-slide-up">
已解锁 {engine.getCollection().endings.length}/{allEndings.length} 个结局
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
{allEndings.map((ending, i) => {
const unlocked = engine.getCollection().endings.includes(ending.id);
return (
<div
key={ending.id}
className={`p-4 rounded-lg border-2 card-animate hover-lift ${
unlocked
? 'border-yellow-400 bg-yellow-400/20 animate-glow'
: 'border-slate-600 bg-slate-700/50'
}`}
style={{ animationDelay: `${i * 50}ms` }}
>
<div className="flex items-center gap-2 mb-2">
<EndingTypeIcon category={ending.category} />
<span className={`px-2 py-1 rounded text-xs font-bold ${
getEndingCategoryColor(ending.category)
}`}>
{getEndingCategoryName(ending.category)}
</span>
<h3 className="font-bold">
{unlocked ? ending.name : '❓ ???'}
</h3>
</div>
<p className="text-sm text-slate-400 animate-slide-up">
{unlocked ? ending.description : '🔒 尚未解锁此结局'}
</p>
{unlocked && (
<div className="mt-2 flex items-center text-xs text-slate-500 animate-fade-in">
<span className="animate-float mr-2">⭐</span>
稀有度: {ending.rarity}/5
</div>
)}
</div>
);
})}
</div>
<button
onClick={() => setPage('start')}
className="w-full py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift animate-slide-up"
>
🏠 返回主页
</button>
</div>
</div>
)}
{/* 存档页面 */}
{page === 'saves' && (
<div className="min-h-screen p-8 animate-fade-in">
<div className="max-w-2xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold mb-2 animate-bounce-in">💾 轮回存档</h2>
<p className="text-slate-400 animate-slide-up">
共 {saveList.length} 轮人生记录
</p>
</div>
{saveList.length === 0 ? (
<div className="text-center text-slate-500 py-12 animate-pulse-custom">
📭 还没有人生记录,快去开启第一轮吧!
</div>
) : (
<div className="space-y-4 mb-8">
{saveList.map((save, i) => (
<div
key={save.id}
className="bg-slate-700 rounded-lg p-4 card-animate hover-lift"
style={{ animationDelay: `${i * 100}ms` }}
>
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-bold text-yellow-400 animate-slide-in">
{save.ending?.name || '未知结局'}
</h3>
<p className="text-xs text-slate-400 animate-slide-in delay-100">
📅 {new Date(save.timestamp).toLocaleString()} | 享年 {save.gameState.age}岁
</p>
</div>
<div className="flex items-center">
{save.ending && <EndingTypeIcon category={save.ending.category} />}
<span className={`ml-2 px-2 py-1 rounded text-xs ${
save.ending ? getEndingCategoryColor(save.ending.category) : 'bg-slate-600'
} animate-fade-in`}>
{save.ending ? getEndingCategoryName(save.ending.category) : '未知'}
</span>
</div>
</div>
<div className="grid grid-cols-3 gap-2 text-xs mb-2 animate-slide-up">
<div className="bg-slate-800 rounded p-2 flex items-center">
<span className="mr-1">{attributeIcons.savings}</span>
<span>存款: ¥{save.gameState.attributes.savings.toLocaleString()}</span>
</div>
<div className="bg-slate-800 rounded p-2 flex items-center">
<span className="mr-1">{attributeIcons.mood}</span>
<span>心气: {save.gameState.attributes.mood}</span>
</div>
<div className="bg-slate-800 rounded p-2 flex items-center">
<span className="mr-1">{attributeIcons.physique}</span>
<span>体魄: {save.gameState.attributes.physique}</span>
</div>
</div>
<div className="flex gap-2 animate-slide-up delay-200">
<button
onClick={() => exportHistory(save)}
className="flex-1 py-2 bg-slate-600 rounded hover:bg-slate-500 transition-colors hover-lift"
>
📤 导出历程
</button>
</div>
</div>
))}
</div>
)}
<button
onClick={() => setPage('start')}
className="w-full py-3 bg-slate-700 rounded-lg hover:bg-slate-600 transition-colors hover-lift animate-slide-up"
>
🏠 返回主页
</button>
</div>
</div>
)}
</div>
);
}- 六维属性系统(脑力、体魄、家底、尊严、存款、心气)
- 双重生路线选择(大学重生 18 岁 / 入职重生 22 岁)
- 天赋系统(4 种稀有度,最多 3 个天赋,含互斥机制)
- 属性点分配(20 点可分配,影响初始状态)
[backcolor=lab(9.03835 1.15298 1.92955)]
游戏机制- 年龄推进系统(18-65 岁职场人生)
- 自动收支计算(薪资、房租 / 房贷、赡养费、生活费)
- 年度属性变化(天赋效果、自然变化)
- 随机事件触发(校园 / 职场 / 危机 / 退休四大事件池)
[backcolor=lab(9.03835 1.15298 1.92955)]
结局系统- 五大类结局:高光逆袭 (22 个)、普通安稳 (26 个)、中年悲剧 (20 个)、躺平佛系 (12 个)、极端底层 (6 个)
- 根据属性、存款、年龄、天赋、事件综合判定
- 结局图鉴收集系统
[backcolor=lab(9.03835 1.15298 1.92955)]
本地功能- 轮回存档管理
- 结局图鉴解锁
- 人生历程导出(文本复制)
- 无联网依赖,完全本地运行
|
-
8a98fe7bfa1b427d8a748eda09652540.png
(73.33 KB, 下载次数: 1)
-
d6915f8e9d984c47a96902d6e634007e.png
(92.45 KB, 下载次数: 0)
-
70fc2f3c4ae4473b83f12ec7830725f3.png
(94.38 KB, 下载次数: 0)
-
7f2cd7f2c46047b2ad36b63a7f9af143.png
(92.45 KB, 下载次数: 0)
-
993d171189ad44cb946cc431a27bb1fd.png
(91.51 KB, 下载次数: 0)
-
15ff5ca144824e8cbf49ba73468903e3.png
(137.83 KB, 下载次数: 0)
-
c4e2e8cc38bf4bd196961f2974633b0d.png
(120.75 KB, 下载次数: 0)
-
99215b9b55b4436982f58b85a0bd6893.png
(99.91 KB, 下载次数: 0)
-
873887a92d3846dab4a5f15724357ae4.png
(87.31 KB, 下载次数: 0)
-
02047c5cd1364786aa1b5c512a2e6ff1.png
(108.41 KB, 下载次数: 0)
-
3ff35bc4d388446c9768a67efe635e01.png
(49.83 KB, 下载次数: 0)
-
143c3af13ff140bebb3015bc0b0fa851.png
(141.36 KB, 下载次数: 0)
免费评分
-
查看全部评分
|