‼️ 特別ミッション ‼️
雑草引き抜きゲームをクリアせよ🌱

import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Pause } from 'lucide-react'; const ASSETS = { bg: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%891-scaled.jpeg', bgm: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%83%8B%E3%83%A5%E3%83%8B%E3%83%A5%E8%8D%89%E3%81%9D%E3%81%86%E3%83%8A%E3%83%B3%E3%83%90%E3%83%BC.mp3', se_whistle: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E8%AD%A6%E5%AE%98%E3%81%AE%E3%83%9B%E3%82%A4%E3%83%83%E3%82%B9%E3%83%AB1.mp3', se_hit: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%82%B6%E3%83%83.mp3', se_miss: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%83%96%E3%83%96%E3%83%BC-%E3%83%93%E3%83%BC%E3%83%97%E9%9F%B34.mp3', targets: { weed: [ { id: 'weed1', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%81%9F%E3%82%93%E3%81%BD%E3%81%BD.png', durationMultiplier: 0.6 }, { id: 'weed2', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%83%89%E3%82%AF%E3%83%80%E3%83%9F.png', durationMultiplier: 1.0 }, { id: 'weed3', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%E3%82%A8%E3%83%8E%E3%82%B3%E3%83%AD.png', durationMultiplier: 1.5 }, ], uma: [ { id: 'uma1', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%EF%BC%91%EF%BC%91.png', durationMultiplier: 0.6 }, { id: 'uma2', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%EF%BC%99.png', durationMultiplier: 1.0 }, { id: 'uma3', url: 'https://uma-adv-lab.com/wp-content/uploads/2026/03/%EF%BC%98.png', durationMultiplier: 1.5 }, ] } }; const DIFFICULTY = { easy: { name: '簡単', clearScore: 3000, weedCount: 40, umaCount: 8, baseDuration: 2200, penalty: 200 }, normal: { name: '普通', clearScore: 6000, weedCount: 100, umaCount: 20, baseDuration: 1500, penalty: 200 }, hard: { name: '難しい', clearScore: 10000, weedCount: 150, umaCount: 50, baseDuration: 1000, penalty: 500 } }; export default function App() { const [renderTick, setRenderTick] = useState(0); const forceRender = () => setRenderTick(t => t + 1); const stateRef = useRef({ screen: 'top', isStarted: false, isPaused: false, isFinished: false, isTimeUpMode: false, playTime: 0, lastTick: performance.now(), scoreWeed: 0, scoreUma: 0, combo: 0, maxCombo: 0, spawns: [], queueIndex: 0 }); const currentModeRef = useRef(null); const holesRef = useRef(Array(9).fill(null)); const popupsRef = useRef([]); const bgmRef = useRef(null); const reqRef = useRef(null); const playSE = (src, vol = 0.5) => { const audio = new Audio(src); audio.volume = vol; audio.play().catch(e => console.warn('Audio play failed:', e)); }; const unlockAudio = () => { [ASSETS.se_whistle, ASSETS.se_hit, ASSETS.se_miss].forEach(src => { const a = new Audio(src); a.volume = 0; a.play().then(() => a.pause()).catch(() => {}); }); }; const loop = useCallback(() => { const state = stateRef.current; if (state.screen === 'game' && state.isStarted && !state.isPaused && !state.isFinished) { const now = performance.now(); const delta = now - state.lastTick; state.lastTick = now; state.playTime += delta; let updated = false; while (state.queueIndex < state.spawns.length && state.spawns[state.queueIndex].time <= state.playTime) { const type = state.spawns[state.queueIndex].type; const emptyHoles = holesRef.current.map((h, i) => h === null ? i : -1).filter(i => i !== -1); if (emptyHoles.length > 0) { const holeIndex = emptyHoles[Math.floor(Math.random() * emptyHoles.length)]; const itemSet = type === 'weed' ? ASSETS.targets.weed : ASSETS.targets.uma; const item = itemSet[Math.floor(Math.random() * itemSet.length)]; const duration = currentModeRef.current.baseDuration * item.durationMultiplier; holesRef.current[holeIndex] = { type, item, id: Math.random().toString(), spawnTime: state.playTime, duration }; updated = true; } state.queueIndex++; } holesRef.current.forEach((h, i) => { if (h && state.playTime > h.spawnTime + h.duration) { holesRef.current[i] = null; if (h.type === 'weed') { state.combo = 0; } updated = true; } }); const newPopups = popupsRef.current.filter(p => state.playTime < p.createdAt + 800); if (newPopups.length !== popupsRef.current.length) { popupsRef.current = newPopups; updated = true; } if (state.playTime >= 60000) { state.isFinished = true; state.isTimeUpMode = true; playSE(ASSETS.se_whistle, 0.5); if (bgmRef.current) bgmRef.current.pause(); setTimeout(() => { state.isTimeUpMode = false; state.screen = 'result'; forceRender(); }, 1500); updated = true; } if (updated || Math.floor(state.playTime / 1000) !== Math.floor((state.playTime - delta) / 1000)) { forceRender(); } } else { state.lastTick = performance.now(); } reqRef.current = requestAnimationFrame(loop); }, []); useEffect(() => { reqRef.current = requestAnimationFrame(loop); return () => { cancelAnimationFrame(reqRef.current); if (bgmRef.current) { bgmRef.current.pause(); bgmRef.current.src = ''; } }; }, [loop]); const setupGame = (modeKey) => { currentModeRef.current = DIFFICULTY[modeKey]; const { weedCount, umaCount } = currentModeRef.current; let queue = []; for(let i=0; i Math.random() - 0.5); const interval = 60000 / (weedCount + umaCount); const spawns = queue.map((type, i) => ({ type, time: i * interval + (Math.random() * interval * 0.4) })); stateRef.current = { ...stateRef.current, screen: 'game', isStarted: false, isPaused: false, isFinished: false, isTimeUpMode: false, playTime: 0, scoreWeed: 0, scoreUma: 0, combo: 0, maxCombo: 0, spawns, queueIndex: 0 }; holesRef.current = Array(9).fill(null); popupsRef.current = []; forceRender(); }; const handleStartPlay = () => { unlockAudio(); playSE(ASSETS.se_whistle, 0.5); if (!bgmRef.current) { bgmRef.current = new Audio(ASSETS.bgm); bgmRef.current.loop = true; bgmRef.current.volume = 0.2; } bgmRef.current.play().catch(()=>{}); stateRef.current.isStarted = true; stateRef.current.lastTick = performance.now(); forceRender(); }; const handleHit = (index) => { const state = stateRef.current; const h = holesRef.current[index]; if (!h || !state.isStarted || state.isPaused || state.isFinished) return; if (h.type === 'weed') { playSE(ASSETS.se_hit); const points = 100 + Math.min(state.combo * 10, 100); state.scoreWeed += points; state.combo++; state.maxCombo = Math.max(state.maxCombo, state.combo); popupsRef.current.push({ id: Math.random().toString(), holeIndex: index, text: `+${points}`, colorClass: 'text-green-400', createdAt: state.playTime }); } else { playSE(ASSETS.se_miss); const penalty = currentModeRef.current.penalty; state.scoreUma -= penalty; state.combo = 0; popupsRef.current.push({ id: Math.random().toString(), holeIndex: index, text: `-${penalty}`, colorClass: 'text-red-500', createdAt: state.playTime }); } holesRef.current[index] = null; forceRender(); }; const handlePause = () => { stateRef.current.isPaused = true; if (bgmRef.current) bgmRef.current.pause(); forceRender(); }; const handleResume = () => { stateRef.current.isPaused = false; stateRef.current.lastTick = performance.now(); if (bgmRef.current) bgmRef.current.play().catch(()=>{}); forceRender(); }; const handleBackToTitle = () => { if (bgmRef.current) bgmRef.current.pause(); stateRef.current.screen = 'top'; forceRender(); }; const state = stateRef.current; const timeLeft = Math.ceil(Math.max(0, 60000 - state.playTime) / 1000); const totalScore = state.scoreWeed + state.scoreUma; const isClear = currentModeRef.current && totalScore >= currentModeRef.current.clearScore; return ( // fixed inset-0 を使用して画面全体に固定し、はみ出しを防止
{/* ゲームコンテナ (画面高さに合わせてアスペクト比9:16を維持) */}
{/* ================= トップ画面 ================= */} {state.screen === 'top' && (

VSザッソ!
雑草引き抜きゲーム

雑草をタップして引き抜こう。
UMAをタップしちゃうと減点だよ!
クリア得点に到達すると、
ノベルティに引き換えできるかも!
{Object.entries(DIFFICULTY).map(([key, mode]) => ( ))}
)} {/* ================= ゲーム画面 ================= */} {state.screen === 'game' && (
{/* 上部10% (ヘッダー) - 一時停止ボタンのみ右上に配置 */}
{/* 中部11%〜80% (ゲームグリッド) */}
{holesRef.current.map((h, i) => (
handleHit(i)} className="w-full h-full relative cursor-pointer overflow-hidden flex items-end justify-center rounded-xl" >
{h && ( {h.type} )} {popupsRef.current.filter(p => p.holeIndex === i).map(p => (
{p.text}
))}
))}
{/* 下部20% (情報表示: 左に時間、右にスコア) */}
{/* 左側:残り時間 */}
残り時間
{timeLeft}
{/* 右側:スコア類 */}
目標: {currentModeRef.current?.clearScore}
{totalScore}
コンボ {state.combo}
{/* はじめるボタンオーバーレイ */} {!state.isStarted && !state.isFinished && (
)} {/* 一時停止メニュー */} {state.isPaused && (

一時停止中

)} {/* タイムアップ表示 */} {state.isTimeUpMode && (

そこまで!

)}
)} {/* ================= 結果画面 ================= */} {state.screen === 'result' && (

{isClear ? 'クリアおめでとう!' : 'ゲームオーバー...'}

難易度: {currentModeRef.current?.name}
雑草収穫ポイント {state.scoreWeed}
UMAタッチ減点 {state.scoreUma}
最大コンボ {state.maxCombo}
合計スコア {totalScore}
{isClear && (
✨ノベルティを
ゲット!✨
)}
)}
); }