Files
ProjectSS/client/src/components/GameScreen.jsx

190 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}