feat: 实现游戏核心功能与前端界面

- 添加游戏核心系统:战斗、任务、日志、随机数生成等
- 实现前端主界面、HUD、底部导航和各类卡片组件
- 添加用户认证系统与游戏存档持久化
- 配置项目基础架构与开发环境
- 补充文档说明与Docker部署支持
This commit is contained in:
2026-04-28 22:16:58 +08:00
parent 2e1b58bada
commit e783e6e533
124 changed files with 9759 additions and 1 deletions
@@ -0,0 +1,101 @@
import type { GameAction, GameView } from '@tinywaste/game-core';
import { edgeDestination, getActiveQuestCount, getCurrentPlace, getRiskDots, getRouteRiskTier } from '../hudModel';
import { getPlaceThumbStyle } from '../uiAssets';
import { PanelHeader } from '../../shared/components/PanelHeader';
export function RoutePanel({
isWorking,
onSendAction,
view,
}: {
isWorking: boolean;
onSendAction: (action: GameAction) => void;
view: GameView;
}) {
const currentPlace = getCurrentPlace(view);
const activeQuestCount = getActiveQuestCount(view);
return (
<section className="panel panel-route">
<PanelHeader title="Current Location" subtitle="路线、风险、耗时全部固定在当前视口内" />
<article className="route-summary-card">
<span className="intel-kicker">CURRENT SECTOR</span>
<h2>{currentPlace?.name ?? view.header.placeName}</h2>
<p>{view.header.placeDesc}</p>
<div className="route-summary-meta">
<div>
<small></small>
<strong>{view.header.riskLabel}</strong>
</div>
<div>
<small></small>
<strong>{view.place.services.length || 1} </strong>
</div>
<div>
<small></small>
<strong>{activeQuestCount}</strong>
</div>
</div>
<div className="intel-meta">
{(currentPlace?.tags ?? []).map((tag) => (
<span key={tag} className="hud-chip muted">
{tag}
</span>
))}
</div>
</article>
<div className="route-list">
{view.map.edges.map((edge) => {
const destination = edgeDestination(view, edge);
if (!destination) return null;
const riskTier = getRouteRiskTier(edge.risk);
const riskDots = getRiskDots(edge.risk);
return (
<button
key={`${edge.from}-${edge.to}`}
className={`route-card tone-${riskTier.tone}`}
disabled={Boolean(edge.blockedReason) || isWorking}
onClick={() => onSendAction({ type: 'travel', toPlaceId: edge.to })}
>
<div className="route-card-thumb" style={getPlaceThumbStyle(destination.id)} />
<div className="route-card-copy">
<strong>{destination.name}</strong>
<p>{destination.desc}</p>
<div className="route-card-detail">
<span>{edge.travelTimeMin} </span>
<span>{riskTier.label}</span>
</div>
<div className="risk-dots" aria-hidden="true">
{riskDots.map((active, index) => (
<span key={`${destination.id}-${index}`} className={active ? 'active' : ''} />
))}
</div>
</div>
<div className="route-card-state">
<span>{destination.visited ? '已探明' : '未知'}</span>
{edge.blockedReason ? <em>{edge.blockedReason}</em> : null}
</div>
</button>
);
})}
</div>
<section className="world-map-panel">
<PanelHeader title="World Map" subtitle="节点式世界图用于压缩表达地理推进" compact />
<div className="map-node-grid">
{view.map.places.map((place) => (
<article key={place.id} className={`map-node-card ${place.current ? 'current' : ''}`}>
<strong>{place.name}</strong>
<span>{place.visited ? '已探明' : '待深入'}</span>
<small>{place.tags.join(' · ') || 'unknown'}</small>
</article>
))}
</div>
</section>
</section>
);
}