구현 우선 순위 1차 진행 완료
- 1차 — 코어 검증 (여기까지만 되어도 재미 판정 가능) 1. **기호 체계 + 책상 상태 상시 노출 UI** (§3) 2. **책상 적재 + 누진 패시브 페널티 A·B** (§4) 3. **표준 족보 판정 및 합본 실행** (§5) 4. **합본 기반 토큰 산출** (§8.2) 게임 플레이 후 피드백 해결 - 내용은 아래 문서 참고 - docs/decisions/2026-09-05_core-graybox-ux-feedback.md
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import SymbolBadge from './SymbolBadge';
|
||||
import { detectHands, resolveCombine, HAND_DEFS } from '../engine/hands';
|
||||
import './CombinePanel.css';
|
||||
|
||||
// 합본 결재 (§5) — 책상에서 카드를 다중 선택하면 성립 가능한 족보를 자동 판정해 보여준다.
|
||||
// 여러 족보가 동시 성립하면 최고 족보를 기본값으로 보여주되, 하위 족보도 선택할 수 있게 한다 (§5.3) —
|
||||
// 명분 종류를 고르기 위해 일부러 하위 족보를 쓰는 선택이 존재해야 하기 때문이다.
|
||||
export default function CombinePanel({ deskCards, onConfirm, onClose }) {
|
||||
const [selectedIds, setSelectedIds] = useState([]);
|
||||
|
||||
const selection = useMemo(
|
||||
() => deskCards.filter((c) => selectedIds.includes(c.id)),
|
||||
[deskCards, selectedIds]
|
||||
);
|
||||
const matches = useMemo(() => detectHands(selection), [selection]);
|
||||
|
||||
const toggle = (id) => {
|
||||
setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
|
||||
};
|
||||
|
||||
const confirm = (patternId) => {
|
||||
const result = resolveCombine(selection, patternId);
|
||||
if (result) onConfirm(result);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="combine-overlay" onClick={onClose}>
|
||||
<div className="combine-panel" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="combine-header">
|
||||
<h2>합본 결재</h2>
|
||||
<button className="combine-close" onClick={onClose}>×</button>
|
||||
</div>
|
||||
|
||||
<p className="combine-hint">책상에서 카드를 선택하면 성립하는 족보를 판정합니다.</p>
|
||||
|
||||
<div className="combine-desk-grid">
|
||||
{deskCards.length === 0 && <p className="combine-empty">책상이 비어 있습니다.</p>}
|
||||
{deskCards.map((c) => (
|
||||
<button
|
||||
key={c.id}
|
||||
className={`combine-card ${selectedIds.includes(c.id) ? 'selected' : ''}`}
|
||||
onClick={() => toggle(c.id)}
|
||||
>
|
||||
<SymbolBadge axis={c.axis} grade={c.grade} />
|
||||
{c.name && <span className="combine-card-name">{c.name}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="combine-results">
|
||||
{selection.length > 0 && matches.length === 0 && (
|
||||
<p className="combine-no-match">선택한 {selection.length}장은 성립하는 족보가 없습니다.</p>
|
||||
)}
|
||||
{matches.map((patternId) => {
|
||||
const def = HAND_DEFS[patternId];
|
||||
return (
|
||||
<button key={patternId} className="combine-match" onClick={() => confirm(patternId)}>
|
||||
<span className="match-name">{def.name}</span>
|
||||
<span className="match-score">실적 +{def.score}</span>
|
||||
<span className="match-justification">명분 +{def.baseJustification}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user