tycono 0.1.94-beta.1 → 0.1.94-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/src/routes/execute.ts +4 -180
- package/src/api/src/services/supervisor-heartbeat.ts +25 -1
- package/src/api/src/services/wave-tracker.ts +104 -0
- package/src/web/dist/assets/{index-Dui2gg0N.js → index-DlFP0kZX.js} +1 -1
- package/src/web/dist/assets/{index-C69_ijxi.js → index-DyFUdV3e.js} +41 -41
- package/src/web/dist/assets/{preview-app-ClsM1ZiD.js → preview-app-3C1r9jSY.js} +1 -1
- package/src/web/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -21,7 +21,6 @@ import { earnCoinsInternal } from './coins.js';
|
|
|
21
21
|
import { appendFollowUpToWave } from '../services/wave-tracker.js';
|
|
22
22
|
import { waveMultiplexer } from '../services/wave-multiplexer.js';
|
|
23
23
|
import { supervisorHeartbeat } from '../services/supervisor-heartbeat.js';
|
|
24
|
-
import { readConfig } from '../services/company-config.js';
|
|
25
24
|
|
|
26
25
|
/* ─── Auto-attach child executions to wave multiplexer ── */
|
|
27
26
|
executionManager.onExecutionCreated((exec) => {
|
|
@@ -220,12 +219,8 @@ function handleStartJob(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
220
219
|
|
|
221
220
|
const targetRoles = body.targetRoles as string[] | undefined;
|
|
222
221
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
const supervisionMode = config.supervision?.mode ?? 'direct';
|
|
226
|
-
|
|
227
|
-
if (supervisionMode === 'supervisor') {
|
|
228
|
-
// Supervisor mode: start a single CEO Supervisor session that dispatches C-Levels
|
|
222
|
+
// Always use supervisor mode — CEO supervises C-Levels who supervise members
|
|
223
|
+
{
|
|
229
224
|
const state = supervisorHeartbeat.start(
|
|
230
225
|
`wave-${Date.now()}`,
|
|
231
226
|
directive,
|
|
@@ -245,71 +240,6 @@ function handleStartJob(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
245
240
|
});
|
|
246
241
|
return;
|
|
247
242
|
}
|
|
248
|
-
|
|
249
|
-
// Direct mode (default): dispatch all C-Levels simultaneously
|
|
250
|
-
const orgTree = buildOrgTree(COMPANY_ROOT);
|
|
251
|
-
let cLevelRoles = getSubordinates(orgTree, 'ceo');
|
|
252
|
-
|
|
253
|
-
if (targetRoles && Array.isArray(targetRoles) && targetRoles.length > 0) {
|
|
254
|
-
const allowed = new Set(targetRoles);
|
|
255
|
-
cLevelRoles = cLevelRoles.filter(r => allowed.has(r));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (cLevelRoles.length === 0) {
|
|
259
|
-
jsonResponse(res, 400, { error: 'No C-level roles found to dispatch wave.' });
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const fullTargetScope = targetRoles && targetRoles.length > 0 ? targetRoles : undefined;
|
|
264
|
-
|
|
265
|
-
const newWaveId = `wave-${Date.now()}`;
|
|
266
|
-
const sessionIds: string[] = [];
|
|
267
|
-
|
|
268
|
-
for (const cRole of cLevelRoles) {
|
|
269
|
-
const session = createSession(cRole, {
|
|
270
|
-
mode: 'do',
|
|
271
|
-
source: 'wave',
|
|
272
|
-
waveId: newWaveId,
|
|
273
|
-
});
|
|
274
|
-
sessionIds.push(session.id);
|
|
275
|
-
|
|
276
|
-
const ceoMsg: Message = {
|
|
277
|
-
id: `msg-${Date.now()}-ceo-${cRole}`,
|
|
278
|
-
from: 'ceo',
|
|
279
|
-
content: directive,
|
|
280
|
-
type: 'directive',
|
|
281
|
-
status: 'done',
|
|
282
|
-
timestamp: new Date().toISOString(),
|
|
283
|
-
attachments,
|
|
284
|
-
};
|
|
285
|
-
addMessage(session.id, ceoMsg);
|
|
286
|
-
|
|
287
|
-
const exec = executionManager.startExecution({
|
|
288
|
-
type: 'wave',
|
|
289
|
-
roleId: cRole,
|
|
290
|
-
task: `[CEO Wave] ${directive}`,
|
|
291
|
-
sourceRole: 'ceo',
|
|
292
|
-
parentSessionId,
|
|
293
|
-
targetRoles: fullTargetScope,
|
|
294
|
-
sessionId: session.id,
|
|
295
|
-
attachments,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
waveMultiplexer.registerSession(newWaveId, exec);
|
|
299
|
-
|
|
300
|
-
const roleMsg: Message = {
|
|
301
|
-
id: `msg-${Date.now() + 1}-role-${cRole}`,
|
|
302
|
-
from: 'role',
|
|
303
|
-
content: '',
|
|
304
|
-
type: 'conversation',
|
|
305
|
-
status: 'streaming',
|
|
306
|
-
timestamp: new Date().toISOString(),
|
|
307
|
-
};
|
|
308
|
-
addMessage(session.id, roleMsg, true);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
jsonResponse(res, 200, { sessionIds, waveId: newWaveId });
|
|
312
|
-
return;
|
|
313
243
|
}
|
|
314
244
|
|
|
315
245
|
// Assign
|
|
@@ -684,15 +614,8 @@ function handleWave(body: Record<string, unknown>, req: IncomingMessage, res: Se
|
|
|
684
614
|
|
|
685
615
|
const targetRoles = body.targetRoles as string[] | undefined;
|
|
686
616
|
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
const supervisionMode = config.supervision?.mode ?? 'direct';
|
|
690
|
-
|
|
691
|
-
if (supervisionMode === 'supervisor') {
|
|
692
|
-
handleWaveSupervisor(directive, targetRoles, req, res);
|
|
693
|
-
} else {
|
|
694
|
-
handleWaveDirect(directive, targetRoles, req, res);
|
|
695
|
-
}
|
|
617
|
+
// Always supervisor mode — CEO supervises C-Levels
|
|
618
|
+
handleWaveSupervisor(directive, targetRoles, req, res);
|
|
696
619
|
}
|
|
697
620
|
|
|
698
621
|
/**
|
|
@@ -721,105 +644,6 @@ function handleWaveSupervisor(directive: string, targetRoles: string[] | undefin
|
|
|
721
644
|
});
|
|
722
645
|
}
|
|
723
646
|
|
|
724
|
-
/**
|
|
725
|
-
* Direct mode (legacy): Dispatch all C-Levels simultaneously.
|
|
726
|
-
* No CEO Supervisor — each C-Level runs independently.
|
|
727
|
-
*/
|
|
728
|
-
function handleWaveDirect(directive: string, targetRoles: string[] | undefined, req: IncomingMessage, res: ServerResponse): void {
|
|
729
|
-
const orgTree = buildOrgTree(COMPANY_ROOT);
|
|
730
|
-
let cLevelRoles = getSubordinates(orgTree, 'ceo');
|
|
731
|
-
|
|
732
|
-
if (targetRoles && Array.isArray(targetRoles) && targetRoles.length > 0) {
|
|
733
|
-
const allowed = new Set(targetRoles);
|
|
734
|
-
cLevelRoles = cLevelRoles.filter(r => allowed.has(r));
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (cLevelRoles.length === 0) {
|
|
738
|
-
jsonResponse(res, 400, { error: 'No C-level roles found to dispatch wave.' });
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const fullTargetScope = targetRoles && targetRoles.length > 0 ? targetRoles : undefined;
|
|
743
|
-
|
|
744
|
-
const executions: Execution[] = [];
|
|
745
|
-
for (const cRole of cLevelRoles) {
|
|
746
|
-
const session = createSession(cRole, { mode: 'do' });
|
|
747
|
-
const exec = executionManager.startExecution({
|
|
748
|
-
type: 'wave',
|
|
749
|
-
roleId: cRole,
|
|
750
|
-
task: `[CEO Wave] ${directive}`,
|
|
751
|
-
sourceRole: 'ceo',
|
|
752
|
-
targetRoles: fullTargetScope,
|
|
753
|
-
sessionId: session.id,
|
|
754
|
-
});
|
|
755
|
-
executions.push(exec);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
startSSE(res);
|
|
759
|
-
sendSSE(res, 'start', {
|
|
760
|
-
ids: executions.map((e) => e.id),
|
|
761
|
-
directive,
|
|
762
|
-
targetRoles: cLevelRoles,
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
let doneCount = 0;
|
|
766
|
-
const subscribers: Array<{ exec: Execution; sub: ActivitySubscriber }> = [];
|
|
767
|
-
|
|
768
|
-
for (const exec of executions) {
|
|
769
|
-
const subscriber: ActivitySubscriber = (event: ActivityEvent) => {
|
|
770
|
-
const rolePrefix = exec.roleId;
|
|
771
|
-
switch (event.type) {
|
|
772
|
-
case 'text':
|
|
773
|
-
sendSSE(res, 'output', { roleId: rolePrefix, text: event.data.text });
|
|
774
|
-
break;
|
|
775
|
-
case 'thinking':
|
|
776
|
-
sendSSE(res, 'thinking', { roleId: rolePrefix, text: event.data.text });
|
|
777
|
-
break;
|
|
778
|
-
case 'tool:start':
|
|
779
|
-
sendSSE(res, 'tool', { roleId: rolePrefix, name: event.data.name, input: event.data.input });
|
|
780
|
-
break;
|
|
781
|
-
case 'dispatch:start':
|
|
782
|
-
sendSSE(res, 'dispatch', { roleId: rolePrefix, targetRoleId: event.data.targetRoleId, task: event.data.task, childSessionId: event.data.childSessionId });
|
|
783
|
-
break;
|
|
784
|
-
case 'msg:turn-complete':
|
|
785
|
-
sendSSE(res, 'turn', { roleId: rolePrefix, turn: event.data.turn });
|
|
786
|
-
break;
|
|
787
|
-
case 'stderr':
|
|
788
|
-
sendSSE(res, 'stderr', { roleId: rolePrefix, message: event.data.message });
|
|
789
|
-
break;
|
|
790
|
-
case 'msg:awaiting_input':
|
|
791
|
-
sendSSE(res, 'role:awaiting_input', { roleId: rolePrefix, question: event.data.question, targetRole: event.data.targetRole, reason: event.data.reason });
|
|
792
|
-
break;
|
|
793
|
-
case 'msg:done':
|
|
794
|
-
sendSSE(res, 'role:done', { roleId: rolePrefix, ...event.data });
|
|
795
|
-
doneCount++;
|
|
796
|
-
if (doneCount >= executions.length) {
|
|
797
|
-
sendSSE(res, 'done', { directive, completedRoles: cLevelRoles });
|
|
798
|
-
res.end();
|
|
799
|
-
}
|
|
800
|
-
break;
|
|
801
|
-
case 'msg:error':
|
|
802
|
-
sendSSE(res, 'role:error', { roleId: rolePrefix, message: event.data.message });
|
|
803
|
-
doneCount++;
|
|
804
|
-
if (doneCount >= executions.length) {
|
|
805
|
-
sendSSE(res, 'done', { directive, completedRoles: cLevelRoles });
|
|
806
|
-
res.end();
|
|
807
|
-
}
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
exec.stream.subscribe(subscriber);
|
|
813
|
-
subscribers.push({ exec, sub: subscriber });
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
req.on('close', () => {
|
|
817
|
-
for (const { exec, sub } of subscribers) {
|
|
818
|
-
exec.stream.unsubscribe(sub);
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
|
|
823
647
|
/* ─── POST /api/waves/:waveId/directive ──────── */
|
|
824
648
|
|
|
825
649
|
function handleWaveDirective(waveId: string, body: Record<string, unknown>, res: ServerResponse): void {
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
* - On restart, digest catches up with all missed events
|
|
13
13
|
*/
|
|
14
14
|
import { executionManager, type Execution } from './execution-manager.js';
|
|
15
|
-
import { createSession, getSession, listSessions } from './session-store.js';
|
|
15
|
+
import { createSession, getSession, listSessions, addMessage, type Message } from './session-store.js';
|
|
16
16
|
import { buildOrgTree, getSubordinates } from '../engine/org-tree.js';
|
|
17
17
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
18
18
|
import { ActivityStream } from './activity-stream.js';
|
|
19
|
+
import { saveCompletedWave } from './wave-tracker.js';
|
|
19
20
|
|
|
20
21
|
/* ─── Types ──────────────────────────────────── */
|
|
21
22
|
|
|
@@ -290,6 +291,17 @@ ${recoveryContext}
|
|
|
290
291
|
state.supervisorSessionId = session.id;
|
|
291
292
|
state.status = 'running';
|
|
292
293
|
|
|
294
|
+
// Add the directive as CEO message so the session isn't empty (prevents deleteEmpty cleanup)
|
|
295
|
+
const ceoMsg: Message = {
|
|
296
|
+
id: `msg-${Date.now()}-ceo-supervisor`,
|
|
297
|
+
from: 'ceo',
|
|
298
|
+
content: state.directive,
|
|
299
|
+
type: 'directive',
|
|
300
|
+
status: 'done',
|
|
301
|
+
timestamp: new Date().toISOString(),
|
|
302
|
+
};
|
|
303
|
+
addMessage(session.id, ceoMsg);
|
|
304
|
+
|
|
293
305
|
try {
|
|
294
306
|
const exec = executionManager.startExecution({
|
|
295
307
|
type: 'wave',
|
|
@@ -341,6 +353,18 @@ ${recoveryContext}
|
|
|
341
353
|
} else {
|
|
342
354
|
console.log(`[Supervisor] Wave ${state.waveId} complete. All subordinates done.`);
|
|
343
355
|
state.status = 'stopped';
|
|
356
|
+
|
|
357
|
+
// Auto-save the completed wave to operations/waves/
|
|
358
|
+
try {
|
|
359
|
+
const result = saveCompletedWave(state.waveId, state.directive);
|
|
360
|
+
if (result.ok) {
|
|
361
|
+
console.log(`[Supervisor] Wave auto-saved: ${result.path}`);
|
|
362
|
+
} else {
|
|
363
|
+
console.warn(`[Supervisor] Wave auto-save returned no result for ${state.waveId}`);
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
console.error(`[Supervisor] Failed to auto-save wave ${state.waveId}:`, err);
|
|
367
|
+
}
|
|
344
368
|
}
|
|
345
369
|
}
|
|
346
370
|
|
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
8
8
|
import { ActivityStream, type ActivityEvent } from './activity-stream.js';
|
|
9
9
|
import { executionManager } from './execution-manager.js';
|
|
10
|
+
import { listSessions } from './session-store.js';
|
|
10
11
|
import { type WaveRoleStatus, eventTypeToMessageStatus } from '../../../shared/types.js';
|
|
11
12
|
|
|
12
13
|
/* ─── Find wave file ──────────────────────── */
|
|
@@ -158,6 +159,109 @@ export function updateFollowUpInWave(waveId: string, sessionId: string, roleId:
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
/* ─── Save completed wave to operations/waves/ ── */
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Auto-save a completed wave to disk.
|
|
166
|
+
* Called by supervisor-heartbeat when all children are done.
|
|
167
|
+
* Mirrors the logic of handleSaveWave in execute.ts but callable from services.
|
|
168
|
+
*/
|
|
169
|
+
export function saveCompletedWave(waveId: string, directive: string): { ok: boolean; path?: string } {
|
|
170
|
+
try {
|
|
171
|
+
// Collect all sessionIds for this wave from session-store
|
|
172
|
+
const allSessions = listSessions();
|
|
173
|
+
const sessionIds = allSessions
|
|
174
|
+
.filter(s => s.waveId === waveId)
|
|
175
|
+
.map(s => s.id);
|
|
176
|
+
|
|
177
|
+
if (sessionIds.length === 0) {
|
|
178
|
+
console.warn(`[WaveTracker] No sessions found for wave ${waveId}, skipping save`);
|
|
179
|
+
return { ok: false };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(`[WaveTracker] Auto-saving wave ${waveId} with ${sessionIds.length} sessions`);
|
|
183
|
+
|
|
184
|
+
interface WaveRoleData {
|
|
185
|
+
roleId: string;
|
|
186
|
+
roleName: string;
|
|
187
|
+
sessionId: string;
|
|
188
|
+
status: WaveRoleStatus | 'unknown';
|
|
189
|
+
events: ReturnType<typeof ActivityStream.readAll>;
|
|
190
|
+
childSessions: Array<{ roleId: string; roleName: string; sessionId: string; status: WaveRoleStatus | 'unknown'; events: ReturnType<typeof ActivityStream.readAll> }>;
|
|
191
|
+
}
|
|
192
|
+
const rolesData: WaveRoleData[] = [];
|
|
193
|
+
|
|
194
|
+
for (const sid of sessionIds) {
|
|
195
|
+
const events = ActivityStream.readAll(sid);
|
|
196
|
+
const startEvent = events.find(e => e.type === 'msg:start');
|
|
197
|
+
const roleId = startEvent?.roleId ?? 'unknown';
|
|
198
|
+
const roleName = (startEvent?.data?.roleName as string) ?? roleId;
|
|
199
|
+
const doneEvent = events.find(e => e.type === 'msg:done' || e.type === 'msg:awaiting_input' || e.type === 'msg:error');
|
|
200
|
+
const status: WaveRoleStatus | 'unknown' = doneEvent ? eventTypeToMessageStatus(doneEvent.type) as WaveRoleStatus : 'unknown';
|
|
201
|
+
|
|
202
|
+
const childSessions: WaveRoleData['childSessions'] = [];
|
|
203
|
+
for (const e of events) {
|
|
204
|
+
const childSessionId = e.data.childSessionId as string | undefined;
|
|
205
|
+
if (e.type === 'dispatch:start' && childSessionId) {
|
|
206
|
+
const targetRoleId = (e.data.targetRoleId as string) ?? 'unknown';
|
|
207
|
+
const childEvents = ActivityStream.readAll(childSessionId);
|
|
208
|
+
const childDone = childEvents.find(ce => ce.type === 'msg:done' || ce.type === 'msg:error' || ce.type === 'msg:awaiting_input');
|
|
209
|
+
const childStatus: WaveRoleStatus | 'unknown' = childDone ? eventTypeToMessageStatus(childDone.type) as WaveRoleStatus : 'unknown';
|
|
210
|
+
childSessions.push({
|
|
211
|
+
roleId: targetRoleId,
|
|
212
|
+
roleName: (childEvents.find(ce => ce.type === 'msg:start')?.data?.roleName as string) ?? targetRoleId,
|
|
213
|
+
sessionId: childSessionId,
|
|
214
|
+
status: childStatus,
|
|
215
|
+
events: childEvents,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
rolesData.push({ roleId, roleName, sessionId: sid, status, events, childSessions });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const wavesDir = path.join(COMPANY_ROOT, 'operations', 'waves');
|
|
224
|
+
if (!fs.existsSync(wavesDir)) {
|
|
225
|
+
fs.mkdirSync(wavesDir, { recursive: true });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check if wave file already exists (e.g. from appendFollowUp)
|
|
229
|
+
const existing = findWaveFile(waveId);
|
|
230
|
+
const baseName = existing
|
|
231
|
+
? path.basename(existing, '.json')
|
|
232
|
+
: waveId;
|
|
233
|
+
const jsonPath = existing ?? path.join(wavesDir, `${baseName}.json`);
|
|
234
|
+
|
|
235
|
+
const now = new Date();
|
|
236
|
+
const waveJson = {
|
|
237
|
+
id: baseName,
|
|
238
|
+
directive,
|
|
239
|
+
startedAt: now.toISOString(),
|
|
240
|
+
duration: 0,
|
|
241
|
+
roles: rolesData,
|
|
242
|
+
waveId,
|
|
243
|
+
sessionIds,
|
|
244
|
+
};
|
|
245
|
+
fs.writeFileSync(jsonPath, JSON.stringify(waveJson, null, 2), 'utf-8');
|
|
246
|
+
|
|
247
|
+
const relativePath = `operations/waves/${baseName}.json`;
|
|
248
|
+
console.log(`[WaveTracker] Wave saved: ${relativePath} (${rolesData.length} roles)`);
|
|
249
|
+
|
|
250
|
+
// Earn coins for wave completion (non-critical)
|
|
251
|
+
try {
|
|
252
|
+
const { earnCoinsInternal } = require('../routes/coins.js');
|
|
253
|
+
if (rolesData.length > 0) {
|
|
254
|
+
earnCoinsInternal(rolesData.length * 500, `Wave done: ${rolesData.length} roles`, `wave:${baseName}`);
|
|
255
|
+
}
|
|
256
|
+
} catch { /* non-critical */ }
|
|
257
|
+
|
|
258
|
+
return { ok: true, path: relativePath };
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error(`[WaveTracker] Failed to auto-save wave ${waveId}:`, err);
|
|
261
|
+
return { ok: false };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
161
265
|
/* ─── Helpers ─────────────────────────────── */
|
|
162
266
|
|
|
163
267
|
function watchExecutionCompletion(waveId: string, sessionId: string, roleId: string): void {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as $,k as T,l as R,m as F,n as S,o as A,p as P,r as i,j as a}from"./index-C69_ijxi.js";const B=[{x:1,y:19,w:10,h:2,c:"#100A06",a:.15},{x:2,y:15,w:3,h:4,c:"$pants"},{x:7,y:15,w:3,h:4,c:"$pants"},{x:2,y:19,w:3,h:2,c:"$shoes"},{x:7,y:19,w:3,h:2,c:"$shoes"},{x:2,y:19,w:3,h:1,c:"lighten($shoes, 20)",a:.4},{x:7,y:19,w:3,h:1,c:"lighten($shoes, 20)",a:.4}],L=[{x:4,y:8,w:4,h:3,c:"$skin"},{x:1,y:1,w:10,h:8,c:"$skin"},{x:3,y:4,w:2,h:2,c:"#1A1A2E"},{x:7,y:4,w:2,h:2,c:"#1A1A2E"},{x:3,y:4,w:1,h:1,c:"#FFF",a:.35},{x:7,y:4,w:1,h:1,c:"#FFF",a:.35},{x:5,y:7,w:2,h:1,c:"darken($skin, 25)",a:.4},{x:0,y:4,w:1,h:1,c:"$skin"},{x:11,y:4,w:1,h:1,c:"$skin"}];function y(s,t,e,c,r,o){for(const l of t){const d=F(l.c,o);l.a!==void 0&&l.a!==1&&(s.globalAlpha=l.a),s.fillStyle=d,s.fillRect((l.x+c)*e,(l.y+r)*e,l.w*e,l.h*e),l.a!==void 0&&l.a!==1&&(s.globalAlpha=1)}}function O(s){var e;const t=A(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:A("short").layer.pixels}function D(s){var e;const t=S(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:S("tshirt").layer.pixels}function I(s){var e;const t=P(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:[]}function k(s,t){const e=(t==null?void 0:t.scale)??3,c=(t==null?void 0:t.padX)??2,r=(t==null?void 0:t.padY)??4,o=1,l=12+c*2,d=22+r+o,n=document.createElement("canvas");n.width=l*e,n.height=d*e,n.style.imageRendering="pixelated";const u=n.getContext("2d"),p=c,x=r;y(u,B,e,p,x,s);const h=s.outfitStyle||"tshirt";y(u,D(h),e,p,x,s),y(u,L,e,p,x,s);const f=s.hairStyle||"short";y(u,O(f),e,p,x,s);const j=s.accessory||"none";return y(u,I(j),e,p,x,s),n}const w=$().map(s=>s.id),_=T().map(s=>s.id),E=_,H=R().map(s=>s.id),C=["#FFE0BD","#F1C27D","#E0AC69","#C68642","#8D5524","#6B4423"],b=["#2C1B18","#724133","#C68642","#E6BE8A","#D4AF37","#B94E48"];function N(){return"#"+Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function g(s){return s[Math.floor(Math.random()*s.length)]}function G({onComplete:s}){const t=i.useRef(null),[e,c]=i.useState("플레이어"),[r,o]=i.useState({skinColor:C[0],hairColor:b[0],shirtColor:"#3498db",pantsColor:"#2C3E50",shoeColor:"#34495E",hairStyle:"short",outfitStyle:"tshirt",accessory:"none"});i.useEffect(()=>{if(!t.current)return;t.current.innerHTML="";const n=k(r,{scale:6});t.current.appendChild(n)},[r]);const l=()=>{o({skinColor:g(C),hairColor:g(b),shirtColor:N(),pantsColor:N(),shoeColor:N(),hairStyle:g(w),outfitStyle:g(E),accessory:g(H)})},d=()=>{s({appearance:r,stats:{name:e,level:1,hp:100,maxHp:100,mp:80,maxMp:80,attack:20,defense:10}})};return a.jsxs("div",{className:"character-creator",children:[a.jsx("h1",{children:"캐릭터 생성"}),a.jsxs("div",{className:"creator-layout",children:[a.jsx("div",{className:"preview-section",children:a.jsx("div",{className:"preview-canvas",ref:t})}),a.jsxs("div",{className:"customization-section",children:[a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"이름"}),a.jsx("input",{type:"text",value:e,onChange:n=>c(n.target.value),maxLength:10})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"피부색"}),a.jsx("div",{className:"color-palette",children:C.map(n=>a.jsx("button",{className:`color-btn ${r.skinColor===n?"active":""}`,style:{backgroundColor:n},onClick:()=>o({...r,skinColor:n})},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"헤어스타일"}),a.jsx("select",{value:r.hairStyle,onChange:n=>o({...r,hairStyle:n.target.value}),children:w.map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"헤어 색상"}),a.jsx("div",{className:"color-palette",children:b.map(n=>a.jsx("button",{className:`color-btn ${r.hairColor===n?"active":""}`,style:{backgroundColor:n},onClick:()=>o({...r,hairColor:n})},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"의상"}),a.jsx("select",{value:r.outfitStyle,onChange:n=>o({...r,outfitStyle:n.target.value}),children:E.map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"액세서리"}),a.jsx("select",{value:r.accessory,onChange:n=>o({...r,accessory:n.target.value}),children:H.slice(0,15).map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"button-group",children:[a.jsx("button",{onClick:l,className:"btn-secondary",children:"🎲 랜덤 생성"}),a.jsx("button",{onClick:d,className:"btn-primary",children:"⚔️ 게임 시작!"})]})]})]})]})}const m={attack:{name:"공격",emoji:"⚔️"},defend:{name:"방어",emoji:"🛡️"},heal:{name:"회복",mpCost:20,emoji:"💚"},flee:{name:"도망",emoji:"🏃"}};function Y(){const s=[{name:"슬라임",hp:50,attack:8,defense:3},{name:"고블린",hp:70,attack:12,defense:5},{name:"해골",hp:60,attack:15,defense:2},{name:"오크",hp:100,attack:18,defense:8}],t=s[Math.floor(Math.random()*s.length)];return{name:t.name,level:1,hp:t.hp,maxHp:t.hp,mp:0,maxMp:0,attack:t.attack,defense:t.defense}}function M(s,t,e){const c=s.attack-t.defense/2,r=Math.max(1,Math.floor(c));return e?Math.floor(r*.5):r}function z(s,t){const e={...s};if(t==="heal"&&e.player.mp<m.heal.mpCost)return{newState:e,continueToEnemyTurn:!1};let c;switch(t){case"attack":{const r=M(e.player,e.enemy,!1);e.enemy.hp=Math.max(0,e.enemy.hp-r),c={actorName:e.player.name,skill:"attack",damage:r,message:`${e.player.name}의 공격! ${r} 데미지!`},e.defendActive=!1;break}case"defend":{e.defendActive=!0,c={actorName:e.player.name,skill:"defend",message:`${e.player.name}이(가) 방어 태세를 취했다!`};break}case"heal":{const o=Math.min(30,e.player.maxHp-e.player.hp);e.player.hp=Math.min(e.player.maxHp,e.player.hp+30),e.player.mp-=m.heal.mpCost,c={actorName:e.player.name,skill:"heal",heal:o,message:`${e.player.name}이(가) 회복! HP +${o}`},e.defendActive=!1;break}case"flee":{Math.random()<.5?(e.status="fled",c={actorName:e.player.name,skill:"flee",message:`${e.player.name}이(가) 도망쳤다!`}):c={actorName:e.player.name,skill:"flee",message:"도망에 실패했다!"},e.defendActive=!1;break}}return e.log=[...e.log,c],e.enemy.hp<=0?(e.status="victory",{newState:e,continueToEnemyTurn:!1}):e.status==="fled"?{newState:e,continueToEnemyTurn:!1}:{newState:e,continueToEnemyTurn:!0}}function K(s){const t={...s},e=M(t.enemy,t.player,t.defendActive);t.player.hp=Math.max(0,t.player.hp-e);const c={actorName:t.enemy.name,skill:"attack",damage:e,message:`${t.enemy.name}의 공격! ${e} 데미지!`};return t.log=[...t.log,c],t.defendActive=!1,t.player.hp<=0&&(t.status="defeat"),t}function U(s,t){return{player:s,enemy:t,turn:"player",log:[],defendActive:!1,status:"ongoing"}}function W(){return{skinColor:"#6B8E23",hairColor:"#2F4F2F",shirtColor:"#8B4513",pantsColor:"#654321",shoeColor:"#3E2723",hairStyle:"messy",outfitStyle:"vest",accessory:"horns"}}function X({player:s,onRestart:t}){const e=i.useRef(null),c=i.useRef(null),r=i.useRef(null),[o]=i.useState(W()),[l,d]=i.useState(()=>U(s.stats,Y()));i.useEffect(()=>{if(e.current){e.current.innerHTML="";const h=k(s.appearance,{scale:6});e.current.appendChild(h)}if(c.current){c.current.innerHTML="";const h=k(o,{scale:6});c.current.appendChild(h)}},[s.appearance,o]),i.useEffect(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[l.log]);const n=h=>{if(l.status!=="ongoing")return;const{newState:f,continueToEnemyTurn:j}=z(l,h);d(f),j&&f.status==="ongoing"&&setTimeout(()=>{d(v=>v.status!=="ongoing"?v:K(v))},800)},u=l.player.hp/l.player.maxHp*100,p=l.enemy.hp/l.enemy.maxHp*100,x=l.player.mp/l.player.maxMp*100;return a.jsxs("div",{className:"battle-screen",children:[a.jsx("h1",{children:"⚔️ 배틀!"}),a.jsxs("div",{className:"battle-characters",children:[a.jsxs("div",{className:"character-box",children:[a.jsx("div",{className:"character-canvas",ref:e}),a.jsx("div",{className:"character-name",children:l.player.name}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["HP: ",l.player.hp,"/",l.player.maxHp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill hp",style:{width:`${u}%`}})})]}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["MP: ",l.player.mp,"/",l.player.maxMp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill mp",style:{width:`${x}%`}})})]})]}),a.jsx("div",{className:"vs-text",children:"VS"}),a.jsxs("div",{className:"character-box",children:[a.jsx("div",{className:"character-canvas",ref:c}),a.jsx("div",{className:"character-name",children:l.enemy.name}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["HP: ",l.enemy.hp,"/",l.enemy.maxHp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill hp",style:{width:`${p}%`}})})]})]})]}),a.jsxs("div",{className:"battle-log",ref:r,children:[l.log.length===0&&a.jsx("div",{className:"log-entry",children:"배틀 시작!"}),l.log.map((h,f)=>a.jsxs("div",{className:"log-entry",children:["> ",h.message]},f))]}),l.status!=="ongoing"&&a.jsxs("div",{className:"battle-result",children:[l.status==="victory"&&a.jsx("h2",{children:"🎉 승리!"}),l.status==="defeat"&&a.jsx("h2",{children:"💀 패배..."}),l.status==="fled"&&a.jsx("h2",{children:"🏃 도망쳤다!"}),a.jsx("button",{onClick:t,className:"btn-primary",children:"다시 시작"})]}),l.status==="ongoing"&&a.jsxs("div",{className:"skill-buttons",children:[a.jsxs("button",{onClick:()=>n("attack"),className:"skill-btn",children:[m.attack.emoji," ",m.attack.name]}),a.jsxs("button",{onClick:()=>n("defend"),className:"skill-btn",children:[m.defend.emoji," ",m.defend.name]}),a.jsxs("button",{onClick:()=>n("heal"),className:"skill-btn",disabled:l.player.mp<m.heal.mpCost,children:[m.heal.emoji," ",m.heal.name,a.jsxs("span",{className:"mp-cost",children:["(MP ",m.heal.mpCost,")"]})]}),a.jsxs("button",{onClick:()=>n("flee"),className:"skill-btn",children:[m.flee.emoji," ",m.flee.name]})]})]})}function q(){const[s,t]=i.useState("create"),[e,c]=i.useState(null),r=l=>{c(l),t("battle")},o=()=>{c(null),t("create")};return a.jsxs("div",{className:"rpg-game",children:[s==="create"&&a.jsx(G,{onComplete:r}),s==="battle"&&e&&a.jsx(X,{player:e,onRestart:o})]})}export{q as default};
|
|
1
|
+
import{i as $,k as T,l as R,m as F,n as S,o as A,p as P,r as i,j as a}from"./index-DyFUdV3e.js";const B=[{x:1,y:19,w:10,h:2,c:"#100A06",a:.15},{x:2,y:15,w:3,h:4,c:"$pants"},{x:7,y:15,w:3,h:4,c:"$pants"},{x:2,y:19,w:3,h:2,c:"$shoes"},{x:7,y:19,w:3,h:2,c:"$shoes"},{x:2,y:19,w:3,h:1,c:"lighten($shoes, 20)",a:.4},{x:7,y:19,w:3,h:1,c:"lighten($shoes, 20)",a:.4}],L=[{x:4,y:8,w:4,h:3,c:"$skin"},{x:1,y:1,w:10,h:8,c:"$skin"},{x:3,y:4,w:2,h:2,c:"#1A1A2E"},{x:7,y:4,w:2,h:2,c:"#1A1A2E"},{x:3,y:4,w:1,h:1,c:"#FFF",a:.35},{x:7,y:4,w:1,h:1,c:"#FFF",a:.35},{x:5,y:7,w:2,h:1,c:"darken($skin, 25)",a:.4},{x:0,y:4,w:1,h:1,c:"$skin"},{x:11,y:4,w:1,h:1,c:"$skin"}];function y(s,t,e,c,r,o){for(const l of t){const d=F(l.c,o);l.a!==void 0&&l.a!==1&&(s.globalAlpha=l.a),s.fillStyle=d,s.fillRect((l.x+c)*e,(l.y+r)*e,l.w*e,l.h*e),l.a!==void 0&&l.a!==1&&(s.globalAlpha=1)}}function O(s){var e;const t=A(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:A("short").layer.pixels}function D(s){var e;const t=S(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:S("tshirt").layer.pixels}function I(s){var e;const t=P(s);return t?(e=t.directions)!=null&&e.down?t.directions.down.pixels:t.layer.pixels:[]}function k(s,t){const e=(t==null?void 0:t.scale)??3,c=(t==null?void 0:t.padX)??2,r=(t==null?void 0:t.padY)??4,o=1,l=12+c*2,d=22+r+o,n=document.createElement("canvas");n.width=l*e,n.height=d*e,n.style.imageRendering="pixelated";const u=n.getContext("2d"),p=c,x=r;y(u,B,e,p,x,s);const h=s.outfitStyle||"tshirt";y(u,D(h),e,p,x,s),y(u,L,e,p,x,s);const f=s.hairStyle||"short";y(u,O(f),e,p,x,s);const j=s.accessory||"none";return y(u,I(j),e,p,x,s),n}const w=$().map(s=>s.id),_=T().map(s=>s.id),E=_,H=R().map(s=>s.id),C=["#FFE0BD","#F1C27D","#E0AC69","#C68642","#8D5524","#6B4423"],b=["#2C1B18","#724133","#C68642","#E6BE8A","#D4AF37","#B94E48"];function N(){return"#"+Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function g(s){return s[Math.floor(Math.random()*s.length)]}function G({onComplete:s}){const t=i.useRef(null),[e,c]=i.useState("플레이어"),[r,o]=i.useState({skinColor:C[0],hairColor:b[0],shirtColor:"#3498db",pantsColor:"#2C3E50",shoeColor:"#34495E",hairStyle:"short",outfitStyle:"tshirt",accessory:"none"});i.useEffect(()=>{if(!t.current)return;t.current.innerHTML="";const n=k(r,{scale:6});t.current.appendChild(n)},[r]);const l=()=>{o({skinColor:g(C),hairColor:g(b),shirtColor:N(),pantsColor:N(),shoeColor:N(),hairStyle:g(w),outfitStyle:g(E),accessory:g(H)})},d=()=>{s({appearance:r,stats:{name:e,level:1,hp:100,maxHp:100,mp:80,maxMp:80,attack:20,defense:10}})};return a.jsxs("div",{className:"character-creator",children:[a.jsx("h1",{children:"캐릭터 생성"}),a.jsxs("div",{className:"creator-layout",children:[a.jsx("div",{className:"preview-section",children:a.jsx("div",{className:"preview-canvas",ref:t})}),a.jsxs("div",{className:"customization-section",children:[a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"이름"}),a.jsx("input",{type:"text",value:e,onChange:n=>c(n.target.value),maxLength:10})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"피부색"}),a.jsx("div",{className:"color-palette",children:C.map(n=>a.jsx("button",{className:`color-btn ${r.skinColor===n?"active":""}`,style:{backgroundColor:n},onClick:()=>o({...r,skinColor:n})},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"헤어스타일"}),a.jsx("select",{value:r.hairStyle,onChange:n=>o({...r,hairStyle:n.target.value}),children:w.map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"헤어 색상"}),a.jsx("div",{className:"color-palette",children:b.map(n=>a.jsx("button",{className:`color-btn ${r.hairColor===n?"active":""}`,style:{backgroundColor:n},onClick:()=>o({...r,hairColor:n})},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"의상"}),a.jsx("select",{value:r.outfitStyle,onChange:n=>o({...r,outfitStyle:n.target.value}),children:E.map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"form-group",children:[a.jsx("label",{children:"액세서리"}),a.jsx("select",{value:r.accessory,onChange:n=>o({...r,accessory:n.target.value}),children:H.slice(0,15).map(n=>a.jsx("option",{value:n,children:n},n))})]}),a.jsxs("div",{className:"button-group",children:[a.jsx("button",{onClick:l,className:"btn-secondary",children:"🎲 랜덤 생성"}),a.jsx("button",{onClick:d,className:"btn-primary",children:"⚔️ 게임 시작!"})]})]})]})]})}const m={attack:{name:"공격",emoji:"⚔️"},defend:{name:"방어",emoji:"🛡️"},heal:{name:"회복",mpCost:20,emoji:"💚"},flee:{name:"도망",emoji:"🏃"}};function Y(){const s=[{name:"슬라임",hp:50,attack:8,defense:3},{name:"고블린",hp:70,attack:12,defense:5},{name:"해골",hp:60,attack:15,defense:2},{name:"오크",hp:100,attack:18,defense:8}],t=s[Math.floor(Math.random()*s.length)];return{name:t.name,level:1,hp:t.hp,maxHp:t.hp,mp:0,maxMp:0,attack:t.attack,defense:t.defense}}function M(s,t,e){const c=s.attack-t.defense/2,r=Math.max(1,Math.floor(c));return e?Math.floor(r*.5):r}function z(s,t){const e={...s};if(t==="heal"&&e.player.mp<m.heal.mpCost)return{newState:e,continueToEnemyTurn:!1};let c;switch(t){case"attack":{const r=M(e.player,e.enemy,!1);e.enemy.hp=Math.max(0,e.enemy.hp-r),c={actorName:e.player.name,skill:"attack",damage:r,message:`${e.player.name}의 공격! ${r} 데미지!`},e.defendActive=!1;break}case"defend":{e.defendActive=!0,c={actorName:e.player.name,skill:"defend",message:`${e.player.name}이(가) 방어 태세를 취했다!`};break}case"heal":{const o=Math.min(30,e.player.maxHp-e.player.hp);e.player.hp=Math.min(e.player.maxHp,e.player.hp+30),e.player.mp-=m.heal.mpCost,c={actorName:e.player.name,skill:"heal",heal:o,message:`${e.player.name}이(가) 회복! HP +${o}`},e.defendActive=!1;break}case"flee":{Math.random()<.5?(e.status="fled",c={actorName:e.player.name,skill:"flee",message:`${e.player.name}이(가) 도망쳤다!`}):c={actorName:e.player.name,skill:"flee",message:"도망에 실패했다!"},e.defendActive=!1;break}}return e.log=[...e.log,c],e.enemy.hp<=0?(e.status="victory",{newState:e,continueToEnemyTurn:!1}):e.status==="fled"?{newState:e,continueToEnemyTurn:!1}:{newState:e,continueToEnemyTurn:!0}}function K(s){const t={...s},e=M(t.enemy,t.player,t.defendActive);t.player.hp=Math.max(0,t.player.hp-e);const c={actorName:t.enemy.name,skill:"attack",damage:e,message:`${t.enemy.name}의 공격! ${e} 데미지!`};return t.log=[...t.log,c],t.defendActive=!1,t.player.hp<=0&&(t.status="defeat"),t}function U(s,t){return{player:s,enemy:t,turn:"player",log:[],defendActive:!1,status:"ongoing"}}function W(){return{skinColor:"#6B8E23",hairColor:"#2F4F2F",shirtColor:"#8B4513",pantsColor:"#654321",shoeColor:"#3E2723",hairStyle:"messy",outfitStyle:"vest",accessory:"horns"}}function X({player:s,onRestart:t}){const e=i.useRef(null),c=i.useRef(null),r=i.useRef(null),[o]=i.useState(W()),[l,d]=i.useState(()=>U(s.stats,Y()));i.useEffect(()=>{if(e.current){e.current.innerHTML="";const h=k(s.appearance,{scale:6});e.current.appendChild(h)}if(c.current){c.current.innerHTML="";const h=k(o,{scale:6});c.current.appendChild(h)}},[s.appearance,o]),i.useEffect(()=>{r.current&&(r.current.scrollTop=r.current.scrollHeight)},[l.log]);const n=h=>{if(l.status!=="ongoing")return;const{newState:f,continueToEnemyTurn:j}=z(l,h);d(f),j&&f.status==="ongoing"&&setTimeout(()=>{d(v=>v.status!=="ongoing"?v:K(v))},800)},u=l.player.hp/l.player.maxHp*100,p=l.enemy.hp/l.enemy.maxHp*100,x=l.player.mp/l.player.maxMp*100;return a.jsxs("div",{className:"battle-screen",children:[a.jsx("h1",{children:"⚔️ 배틀!"}),a.jsxs("div",{className:"battle-characters",children:[a.jsxs("div",{className:"character-box",children:[a.jsx("div",{className:"character-canvas",ref:e}),a.jsx("div",{className:"character-name",children:l.player.name}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["HP: ",l.player.hp,"/",l.player.maxHp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill hp",style:{width:`${u}%`}})})]}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["MP: ",l.player.mp,"/",l.player.maxMp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill mp",style:{width:`${x}%`}})})]})]}),a.jsx("div",{className:"vs-text",children:"VS"}),a.jsxs("div",{className:"character-box",children:[a.jsx("div",{className:"character-canvas",ref:c}),a.jsx("div",{className:"character-name",children:l.enemy.name}),a.jsxs("div",{className:"stat-bar",children:[a.jsxs("div",{className:"stat-label",children:["HP: ",l.enemy.hp,"/",l.enemy.maxHp]}),a.jsx("div",{className:"stat-bar-bg",children:a.jsx("div",{className:"stat-bar-fill hp",style:{width:`${p}%`}})})]})]})]}),a.jsxs("div",{className:"battle-log",ref:r,children:[l.log.length===0&&a.jsx("div",{className:"log-entry",children:"배틀 시작!"}),l.log.map((h,f)=>a.jsxs("div",{className:"log-entry",children:["> ",h.message]},f))]}),l.status!=="ongoing"&&a.jsxs("div",{className:"battle-result",children:[l.status==="victory"&&a.jsx("h2",{children:"🎉 승리!"}),l.status==="defeat"&&a.jsx("h2",{children:"💀 패배..."}),l.status==="fled"&&a.jsx("h2",{children:"🏃 도망쳤다!"}),a.jsx("button",{onClick:t,className:"btn-primary",children:"다시 시작"})]}),l.status==="ongoing"&&a.jsxs("div",{className:"skill-buttons",children:[a.jsxs("button",{onClick:()=>n("attack"),className:"skill-btn",children:[m.attack.emoji," ",m.attack.name]}),a.jsxs("button",{onClick:()=>n("defend"),className:"skill-btn",children:[m.defend.emoji," ",m.defend.name]}),a.jsxs("button",{onClick:()=>n("heal"),className:"skill-btn",disabled:l.player.mp<m.heal.mpCost,children:[m.heal.emoji," ",m.heal.name,a.jsxs("span",{className:"mp-cost",children:["(MP ",m.heal.mpCost,")"]})]}),a.jsxs("button",{onClick:()=>n("flee"),className:"skill-btn",children:[m.flee.emoji," ",m.flee.name]})]})]})}function q(){const[s,t]=i.useState("create"),[e,c]=i.useState(null),r=l=>{c(l),t("battle")},o=()=>{c(null),t("create")};return a.jsxs("div",{className:"rpg-game",children:[s==="create"&&a.jsx(G,{onComplete:r}),s==="battle"&&e&&a.jsx(X,{player:e,onRestart:o})]})}export{q as default};
|