치지직 채팅창 인식 추가 및 주사위 횟수 증가 추가.
This commit is contained in:
182
app/page.tsx
182
app/page.tsx
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ASSET_ASPECT,
|
||||
@@ -316,6 +316,7 @@ function BoardEditor({
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const [assets, setAssets] = useState<DrinkmarbleAssets>(DEFAULT_ASSETS);
|
||||
const [board, setBoard] = useState<BoardConfig | null>(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
@@ -323,6 +324,13 @@ export default function Home() {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const packInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [roomCheesePerDice, setRoomCheesePerDice] = useState(1000);
|
||||
const [roomMultiplyByCheese, setRoomMultiplyByCheese] = useState(true);
|
||||
const [roomMaxMultiplier, setRoomMaxMultiplier] = useState(10);
|
||||
const [joinChannelInput, setJoinChannelInput] = useState('');
|
||||
const [roomBusy, setRoomBusy] = useState(false);
|
||||
const [roomError, setRoomError] = useState<string | null>(null);
|
||||
|
||||
const reload = () => {
|
||||
setAssets(getAssets());
|
||||
setBoard(getOrCreateBoardConfig());
|
||||
@@ -333,6 +341,67 @@ export default function Home() {
|
||||
setReady(true);
|
||||
}, []);
|
||||
|
||||
const roomCheeseOptions = () => ({
|
||||
cheesePerDice: roomCheesePerDice,
|
||||
multiplyByCheese: roomMultiplyByCheese,
|
||||
maxMultiplier: roomMultiplyByCheese ? roomMaxMultiplier : 0,
|
||||
});
|
||||
|
||||
const joinByChzzkId = async () => {
|
||||
const value = joinChannelInput.trim();
|
||||
if (!value) {
|
||||
setRoomError('치지직 채널 ID 또는 라이브 URL을 입력하세요.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setRoomBusy(true);
|
||||
setRoomError(null);
|
||||
const res = await fetch('/api/rooms/join', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
channelIdOrUrl: value,
|
||||
...roomCheeseOptions(),
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) {
|
||||
setRoomError(json.error || '룸을 찾을 수 없습니다.');
|
||||
if (json.room?.id) {
|
||||
router.push(`/game?roomId=${json.room.id}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push(`/game?roomId=${json.room.id}`);
|
||||
} catch (e) {
|
||||
setRoomError(e instanceof Error ? e.message : '룸 입장에 실패했습니다.');
|
||||
} finally {
|
||||
setRoomBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const enterWithoutIntegration = async () => {
|
||||
try {
|
||||
setRoomBusy(true);
|
||||
setRoomError(null);
|
||||
const res = await fetch('/api/rooms', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) {
|
||||
setRoomError(json.error || '룸 생성에 실패했습니다.');
|
||||
return;
|
||||
}
|
||||
router.push(`/game?roomId=${json.room.id}`);
|
||||
} catch (e) {
|
||||
setRoomError(e instanceof Error ? e.message : '입장에 실패했습니다.');
|
||||
} finally {
|
||||
setRoomBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (
|
||||
kind: 'background' | 'chessman' | 'diceFloor' | { slot: DiceSlot; face: DiceFace },
|
||||
file: File,
|
||||
@@ -390,23 +459,110 @@ export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#1c1224] text-white">
|
||||
<div className="mx-auto flex min-h-screen max-w-3xl flex-col gap-8 px-6 py-10">
|
||||
<header className="flex flex-col items-start gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-white/60">온라인 주루마블</p>
|
||||
<h1 className="mt-1 text-4xl font-bold tracking-tight">Drinkmarble</h1>
|
||||
<p className="mt-2 max-w-md text-sm text-white/65">
|
||||
이미지와 판 칸 설정은 서버가 아니라 이 브라우저 localStorage에만 저장됩니다.
|
||||
<header className="flex w-full flex-col items-stretch gap-5">
|
||||
<div className="w-full">
|
||||
{/*<p className="text-sm text-white/60">온라인 주루마블</p>*/}
|
||||
<h1 className="relative mt-1 inline-block w-full text-center text-4xl font-bold tracking-tight">
|
||||
<span className="relative inline-block pr-12">
|
||||
온라인 주루마블!
|
||||
<span className="pointer-events-none absolute bottom-1 left-full -ml-0.5 whitespace-nowrap text-[11px] font-normal tracking-normal text-white/35">
|
||||
by kimyu.
|
||||
</span>
|
||||
</span>
|
||||
</h1>
|
||||
<br></br>
|
||||
<p className="border-b border-white/10 w-full"></p>
|
||||
<p className="mt-2 w-full text-sm text-white/65">
|
||||
이미지와 판 칸 설정은 서버가 아니라 이 브라우저에만 저장됩니다.
|
||||
처음이면 칸은 랜덤으로 채워집니다.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/game"
|
||||
className="inline-flex items-center justify-center rounded-full bg-amber-500 px-6 py-3 text-base font-semibold text-stone-950 transition hover:bg-amber-400"
|
||||
>
|
||||
게임 시작
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<section className="flex flex-col gap-4 rounded-2xl border border-white/10 bg-white/5 p-5">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">룸 입장</h2>
|
||||
<p className="mt-1 text-[11px] text-white/50">
|
||||
치즈 규칙을 정한 뒤, 치지직 채널로 입장하세요. 룸이 없으면 자동으로 만들어집니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="flex flex-col gap-1 text-sm">
|
||||
<span className="text-white/70">몇 치즈(원)마다 주사위 1개</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
step={100}
|
||||
value={roomCheesePerDice}
|
||||
onChange={(e) => setRoomCheesePerDice(Number(e.target.value) || 1)}
|
||||
className="rounded-lg border border-white/15 bg-black/40 px-3 py-2"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 self-end pb-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={roomMultiplyByCheese}
|
||||
onChange={(e) => setRoomMultiplyByCheese(e.target.checked)}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span>배수 누적 (금액 ÷ 기준만큼 주사위)</span>
|
||||
</label>
|
||||
{roomMultiplyByCheese && (
|
||||
<label className="flex flex-col gap-1 text-sm sm:col-span-2">
|
||||
<span className="text-white/70">최대 배수 (한 후원당 최대 주사위)</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={roomMaxMultiplier}
|
||||
onChange={(e) => setRoomMaxMultiplier(Math.max(1, Number(e.target.value) || 1))}
|
||||
className="rounded-lg border border-white/15 bg-black/40 px-3 py-2"
|
||||
/>
|
||||
<span className="text-[11px] text-white/45">
|
||||
예: 기준 {roomCheesePerDice}원 · 최대 {roomMaxMultiplier}배 → 한 번에 최대{' '}
|
||||
{roomMaxMultiplier}개
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-end gap-2 border-t border-white/10 pt-3">
|
||||
<label className="flex min-w-[220px] flex-1 flex-col gap-1 text-sm">
|
||||
<span className="text-white/70">치지직 채널 ID로 입장</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="채널ID 또는 https://chzzk.naver.com/live/…"
|
||||
value={joinChannelInput}
|
||||
onChange={(e) => setJoinChannelInput(e.target.value)}
|
||||
className="rounded-lg border border-white/15 bg-black/40 px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy || !joinChannelInput.trim()}
|
||||
onClick={() => void joinByChzzkId()}
|
||||
className="rounded-full bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-violet-500 disabled:opacity-50"
|
||||
>
|
||||
{roomBusy ? '처리 중…' : '입장'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy}
|
||||
onClick={() => void enterWithoutIntegration()}
|
||||
className="self-start rounded-full bg-white/15 px-4 py-2.5 text-sm text-white hover:bg-white/25 disabled:opacity-50"
|
||||
>
|
||||
연동 없이 입장
|
||||
<span className="ml-1 text-white/50">(채팅·치지직 없음 · 수동 굴림)</span>
|
||||
</button>
|
||||
|
||||
{roomError && (
|
||||
<p className="rounded-lg bg-red-900/70 px-3 py-2 text-sm">{roomError}</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{!ready || !board ? (
|
||||
<p className="text-white/50">불러오는 중…</p>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user