190 lines
7.0 KiB
JavaScript
190 lines
7.0 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import Papa from 'papaparse';
|
||
import cardsCsvRaw from '../data/cards.csv?raw';
|
||
import { BookOpen, HandCoins, Droplet, Eye, Shield, Building2, EyeOff } from 'lucide-react';
|
||
import SwipeCard from './SwipeCard';
|
||
import './GameScreen.css';
|
||
|
||
export function GameScreen() {
|
||
const [params, setParams] = useState({ resistance: 0, entropy: 0, risk: 0 });
|
||
const [tokens, setTokens] = useState({ budget: 50, personnel: 50, info: 50 });
|
||
const [tags, setTags] = useState({ wealth: 0, force: 0, surveillance: 0 });
|
||
const [narrativeTags, setNarrativeTags] = useState(0);
|
||
const [phase, setPhase] = useState(1);
|
||
const [gameOver, setGameOver] = useState(false);
|
||
const [cards, setCards] = useState([]);
|
||
const [currentCardIndex, setCurrentCardIndex] = useState(0);
|
||
|
||
// CSV Load Effect
|
||
useEffect(() => {
|
||
const parsed = Papa.parse(cardsCsvRaw, { header: true, dynamicTyping: true, skipEmptyLines: true });
|
||
|
||
const formattedCards = parsed.data.map(row => ({
|
||
id: row.id,
|
||
type: row.type,
|
||
text: row.text,
|
||
flavor: row.flavor,
|
||
left: {
|
||
text: row.left_text,
|
||
stats: {
|
||
res: row.left_res || 0,
|
||
ent: row.left_ent || 0,
|
||
rsk: row.left_rsk || 0,
|
||
bud: row.left_bud || 0,
|
||
per: row.left_per || 0,
|
||
inf: row.left_inf || 0,
|
||
wea: row.left_wea || 0,
|
||
for: row.left_for || 0,
|
||
sur: row.left_sur || 0,
|
||
},
|
||
effect: () => {
|
||
setParams(p => ({
|
||
resistance: Math.max(0, p.resistance + (row.left_res || 0)),
|
||
entropy: Math.max(0, p.entropy + (row.left_ent || 0)),
|
||
risk: Math.max(0, p.risk + (row.left_rsk || 0)),
|
||
}));
|
||
setTokens(t => ({
|
||
budget: t.budget + (row.left_bud || 0),
|
||
personnel: t.personnel + (row.left_per || 0),
|
||
info: t.info + (row.left_inf || 0),
|
||
}));
|
||
setTags(tg => ({
|
||
wealth: tg.wealth + (row.left_wea || 0),
|
||
force: tg.force + (row.left_for || 0),
|
||
surveillance: tg.surveillance + (row.left_sur || 0),
|
||
}));
|
||
}
|
||
},
|
||
right: {
|
||
text: row.right_text,
|
||
stats: {
|
||
res: row.right_res || 0,
|
||
ent: row.right_ent || 0,
|
||
rsk: row.right_rsk || 0,
|
||
bud: row.right_bud || 0,
|
||
per: row.right_per || 0,
|
||
inf: row.right_inf || 0,
|
||
wea: row.right_wea || 0,
|
||
for: row.right_for || 0,
|
||
sur: row.right_sur || 0,
|
||
},
|
||
effect: () => {
|
||
setParams(p => ({
|
||
resistance: Math.max(0, p.resistance + (row.right_res || 0)),
|
||
entropy: Math.max(0, p.entropy + (row.right_ent || 0)),
|
||
risk: Math.max(0, p.risk + (row.right_rsk || 0)),
|
||
}));
|
||
setTokens(t => ({
|
||
budget: t.budget + (row.right_bud || 0),
|
||
personnel: t.personnel + (row.right_per || 0),
|
||
info: t.info + (row.right_inf || 0),
|
||
}));
|
||
setTags(tg => ({
|
||
wealth: tg.wealth + (row.right_wea || 0),
|
||
force: tg.force + (row.right_for || 0),
|
||
surveillance: tg.surveillance + (row.right_sur || 0),
|
||
}));
|
||
}
|
||
}
|
||
}));
|
||
|
||
setCards(formattedCards.sort(() => Math.random() - 0.5));
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (params.resistance >= 5 || params.entropy >= 5 || params.risk >= 5) {
|
||
setGameOver(true);
|
||
}
|
||
if (narrativeTags >= 4 && narrativeTags < 8) setPhase(2);
|
||
else if (narrativeTags >= 8) setPhase(3);
|
||
}, [params, narrativeTags]);
|
||
|
||
const handleSwipe = (direction) => {
|
||
if (cards.length === 0) return;
|
||
const card = cards[currentCardIndex % cards.length];
|
||
if (direction === 'left' && card.left) {
|
||
card.left.effect();
|
||
} else if (direction === 'right' && card.right) {
|
||
card.right.effect();
|
||
}
|
||
setNarrativeTags(n => n + 1);
|
||
setCurrentCardIndex(idx => idx + 1);
|
||
};
|
||
|
||
const renderDots = (value, max = 5) => {
|
||
return Array.from({ length: max }).map((_, i) => (
|
||
<div key={i} className={`dot ${i < value ? 'filled' : 'empty'}`}></div>
|
||
));
|
||
};
|
||
|
||
if (gameOver) {
|
||
return (
|
||
<div className="game-over">
|
||
<h1 className="glitch" data-text="시스템 붕괴">시스템 붕괴</h1>
|
||
<p>프로젝트가 파멸을 맞이했습니다. 임무 실패.</p>
|
||
<button className="restart-btn" onClick={() => window.location.reload()}>다음 회차 시작</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (cards.length === 0) {
|
||
return <div className="game-screen">데이터를 불러오는 중...</div>;
|
||
}
|
||
|
||
const phaseClass = phase === 1 ? 'phase-1' : phase === 2 ? 'phase-2' : 'phase-3';
|
||
|
||
return (
|
||
<div className={`game-screen ${phaseClass}`}>
|
||
{/* Top Bar */}
|
||
<div className="top-bar">
|
||
<div className="narrative-badge">
|
||
<BookOpen size={16} /> <span>{narrativeTags}/8</span>
|
||
</div>
|
||
<div className="parameters">
|
||
<div className={`param ${params.resistance >= 4 ? 'critical' : ''}`} style={{'--param-color': 'var(--color-resistance-critical)'}}>
|
||
<span className="icon">✊</span>
|
||
<div className="dots">{renderDots(params.resistance)}</div>
|
||
</div>
|
||
<div className={`param ${params.entropy >= 4 ? 'critical' : ''}`} style={{'--param-color': 'var(--color-entropy-critical)'}}>
|
||
<span className="icon">🌀</span>
|
||
<div className="dots">{renderDots(params.entropy)}</div>
|
||
</div>
|
||
<div className={`param ${params.risk >= 4 ? 'critical' : ''}`} style={{'--param-color': 'var(--color-risk-critical)'}}>
|
||
<span className="icon">⚠️</span>
|
||
<div className="dots">{renderDots(params.risk)}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Card Area */}
|
||
<div className="card-area">
|
||
<SwipeCard
|
||
card={cards[currentCardIndex % cards.length]}
|
||
onSwipe={handleSwipe}
|
||
phase={phase}
|
||
/>
|
||
</div>
|
||
|
||
{/* Bottom Inventory */}
|
||
<div className="bottom-bar">
|
||
<div className="tokens-row">
|
||
<div className="token"><HandCoins size={16} color="var(--color-token-budget)" /> <span>{tokens.budget}</span></div>
|
||
<div className="token"><Droplet size={16} color="var(--color-token-personnel)" /> <span>{tokens.personnel}</span></div>
|
||
<div className="token"><Eye size={16} color="var(--color-token-info)" /> <span>{tokens.info}</span></div>
|
||
</div>
|
||
<div className="tags-row">
|
||
<div className="tag"><Building2 size={14} /> <span>{tags.wealth}</span></div>
|
||
<div className="tag"><Shield size={14} /> <span>{tags.force}</span></div>
|
||
<div className="tag"><EyeOff size={14} /> <span>{tags.surveillance}</span></div>
|
||
</div>
|
||
<div className="asset-slots">
|
||
<div className="asset-slot"></div>
|
||
<div className="asset-slot"></div>
|
||
<div className="asset-slot"></div>
|
||
<div className="asset-slot"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|