Files
ProjectSS/client/src/components/CouncilScreen.jsx
T

221 lines
8.4 KiB
React

import React, { useState, useEffect } from 'react';
import './CouncilScreen.css';
export default function CouncilScreen({ params, setParams, tokens, setTokens, tags, setTags, bankruptcyCount, bankruptcyWarning, globalFactions = [], onBribe, onResolve }) {
const [members, setMembers] = useState([]);
const [agenda, setAgenda] = useState('');
const [impeachment, setImpeachment] = useState(false);
const [actionTaken, setActionTaken] = useState(false);
const [result, setResult] = useState(null);
const [isBankruptcyWarning, setIsBankruptcyWarning] = useState(false);
const [bankruptPenalty, setBankruptPenalty] = useState('');
useEffect(() => {
const isNegative = tokens.budget < 0 || tokens.personnel < 0 || tokens.info < 0;
if (isNegative) {
if (bankruptcyWarning) {
setAgenda('재무 건전성 악화 및 횡령 배임 혐의');
setResult('당신은 파산했고 자금 횡령으로 체포되었습니다. 감옥의 식사는 다행히 무료입니다.');
return;
}
setIsBankruptcyWarning(true);
setAgenda('파산 문책: 재무 경고');
if (bankruptcyCount >= 5) {
setBankruptPenalty(`파산 카운터 ${bankruptcyCount}회: 의회가 매우 분노했습니다. 모든 위험도 파라미터가 상승합니다.`);
setParams(prev => ({
resistance: Math.min(5, prev.resistance + 1),
entropy: Math.min(5, prev.entropy + 1),
panic: Math.min(5, prev.panic + 1),
risk: Math.min(5, prev.risk + 1),
}));
} else {
setBankruptPenalty(`파산 카운터 ${bankruptcyCount}회: 의회에서 강력한 경고가 나왔습니다. 다음 의회까지 복구하지 못하면 해임됩니다.`);
}
return;
}
// 상임 이사회 8명: 위험 수용 계수 기반 무작위 (성향 태그 세부 연동은 Phase 3 범위 밖)
const boardMembers = Array.from({ length: 8 }).map((_, i) => {
const rand = Math.random() * 100;
let vote = '미정';
if (rand < params.risk * 20) vote = '반대';
else if (rand > 50) vote = '찬성';
return { id: i, type: 'board', vote, immune: false, label: '상임 이사회' };
});
// 글로벌 지부장 5명: 파벌별 개별 우호도 계산 결과를 그대로 투표에 반영
const globalMembers = globalFactions.map((f, i) => ({
id: 8 + i, type: 'global', vote: f.vote, immune: f.immune, label: `${f.name} (${f.tier})`,
}));
const newMembers = [...boardMembers, ...globalMembers];
setMembers(newMembers);
const againstCount = newMembers.filter(m => m.vote === '반대').length;
if (againstCount >= 9) {
setAgenda('정치적 탄핵안 상정');
setImpeachment(true);
} else {
const randAgenda = Math.random() > 0.5 ? '이득 규약 법안 상정' : '표적 규약 법안 상정';
setAgenda(randAgenda);
}
// 의회는 마운트 시 1회만 초기화한다 (deps에 tokens/params.risk를 넣으면 매수 액션 등으로
// 값이 바뀔 때마다 재실행되어 방금 강제 전환한 투표 결과를 덮어써버리는 버그가 있었다)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleAction = (type) => {
if (actionTaken) return;
const newMembers = [...members];
if (type === 'bribe' && tokens.budget >= 30) {
setTokens(prev => ({ ...prev, budget: prev.budget - 30 }));
setParams(prev => ({ ...prev, risk: Math.min(5, prev.risk + 1) }));
onBribe && onBribe();
// 동맹/파국 성향 임원은 국장의 개입이 통하지 않는다
let changed = 0;
for (let m of newMembers) {
if (m.vote === '반대' && !m.immune && changed < 2) {
m.vote = '찬성';
changed++;
}
}
} else if (type === 'manipulate' && tokens.info >= 30) {
setTokens(prev => ({ ...prev, info: prev.info - 30 }));
setParams(prev => ({ ...prev, entropy: Math.min(5, prev.entropy + 1) }));
// Delay agenda
setResult('법안 상정 이월 (장부 조작 성공)');
setActionTaken(true);
return;
} else if (type === 'audit' && tokens.personnel >= 30) {
setTokens(prev => ({ ...prev, personnel: prev.personnel - 30 }));
setParams(prev => ({ ...prev, resistance: Math.min(5, prev.resistance + 1) }));
// 동맹/파국 성향 임원은 국장의 개입이 통하지 않는다
let changed = 0;
for (let m of newMembers) {
if (m.vote === '반대' && !m.immune && changed < 3) {
m.vote = '미정';
changed++;
}
}
} else {
return; // Not enough tokens
}
setMembers(newMembers);
setActionTaken(true);
};
const finalize = () => {
if (result && result.includes('파산')) {
onResolve({ bankrupted: true });
return;
}
if (result) {
onResolve({ impeachment: false });
return;
}
if (isBankruptcyWarning) {
onResolve({ impeachment: false, bankruptcyWarning: true });
return;
}
const againstCount = members.filter(m => m.vote === '반대').length;
if (impeachment) {
if (againstCount >= 9) {
onResolve({ impeachment: true });
} else {
onResolve({ impeachment: false, bankruptcyWarning: false });
}
} else {
const supportCount = members.filter(m => m.vote === '찬성').length;
if (supportCount > againstCount) {
if (agenda.includes('이득')) {
setTags(prev => ({ ...prev, wealth: prev.wealth + 1 }));
} else {
setTags(prev => ({ ...prev, wealth: Math.max(0, prev.wealth - 1) }));
}
}
// 토큰이 정상 범위로 돌아왔다면 파산 경고를 해제한다
onResolve({ impeachment: false, bankruptcyWarning: false });
}
};
return (
<div className="council-screen">
<div className="council-header">
<h2>[📜 이번 정기 상정: {agenda}]</h2>
<div className="risk-bar-container">
<span>위험 수용 계수:</span>
<div className="risk-bar">
<div className="risk-fill" style={{ width: `${(params.risk / 5) * 100}%` }}></div>
</div>
</div>
{impeachment && <div className="warning-blink">[🚨 정치적 탄핵안 상정!]</div>}
</div>
{isBankruptcyWarning ? (
<div className="bankruptcy-warning-box">
<p className="penalty-text">{bankruptPenalty}</p>
</div>
) : (
<div className="members-grid">
{members.map(m => (
<div key={m.id} className={`member-icon ${m.type}`} title={m.label}>
<span className="member-avatar">👤</span>
<span className="member-badge">
{m.vote === '찬성' ? '✅' : m.vote === '반대' ? '❌' : '❓'}
</span>
</div>
))}
</div>
)}
{result ? (
<div className="council-result">
<h3>결과: {result}</h3>
<button className="fat-btn" onClick={finalize}>의회 종료</button>
</div>
) : isBankruptcyWarning ? (
<div className="council-actions">
<button className="fat-btn action-proceed" onClick={finalize}>경고 수용 및 의회 종료</button>
</div>
) : (
<div className="council-actions">
<button
className={`fat-btn ${tokens.budget < 30 ? 'disabled' : ''}`}
onClick={() => handleAction('bribe')}
disabled={actionTaken || tokens.budget < 30}
>
💰 매수 (예산 30 소모 / 임원 찬성)
</button>
<button
className={`fat-btn ${tokens.info < 30 ? 'disabled' : ''}`}
onClick={() => handleAction('manipulate')}
disabled={actionTaken || tokens.info < 30}
>
👁️ 장부 조작 (정보 30 소모 / 안건 이월)
</button>
<button
className={`fat-btn ${tokens.personnel < 30 ? 'disabled' : ''}`}
onClick={() => handleAction('audit')}
disabled={actionTaken || tokens.personnel < 30}
>
🩸 내부 감사 (인력 30 소모 / 기권 유도)
</button>
{actionTaken && (
<button className="fat-btn action-proceed" onClick={finalize}>표결 진행</button>
)}
{!actionTaken && (
<button className="fat-btn action-proceed" onClick={finalize}>개입 없이 표결 진행</button>
)}
</div>
)}
</div>
);
}