‼️ 特別ミッション ‼️
雑草引き抜きゲームをクリアせよ🌱
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ザッソ!
)}
{/* ================= ゲーム画面 ================= */}
{state.screen === 'game' && (
{isClear && (
)}
)}
VSザッソ!
雑草引き抜きゲーム
雑草をタップして引き抜こう。
UMAをタップしちゃうと減点だよ!
クリア得点に到達すると、
ノベルティに引き換えできるかも!
UMAをタップしちゃうと減点だよ!
クリア得点に到達すると、
ノベルティに引き換えできるかも!
{Object.entries(DIFFICULTY).map(([key, mode]) => (
))}
{/* 上部10% (ヘッダー) - 一時停止ボタンのみ右上に配置 */}
{/* 中部11%〜80% (ゲームグリッド) */}
)}
{/* 一時停止メニュー */}
{state.isPaused && (
)}
{/* タイムアップ表示 */}
{state.isTimeUpMode && (
)}
)}
{/* ================= 結果画面 ================= */}
{state.screen === 'result' && (
{holesRef.current.map((h, i) => (
{/* 下部20% (情報表示: 左に時間、右にスコア) */}
handleHit(i)}
className="w-full h-full relative cursor-pointer overflow-hidden flex items-end justify-center rounded-xl"
>
{h && (
)}
{popupsRef.current.filter(p => p.holeIndex === i).map(p => (
))}
{p.text}
))}
{/* 左側:残り時間 */}
{/* 右側:スコア類 */}
{/* はじめるボタンオーバーレイ */}
{!state.isStarted && !state.isFinished && (
残り時間
{timeLeft}秒
目標: {currentModeRef.current?.clearScore}
{totalScore}
コンボ {state.combo}
一時停止中
そこまで!
{isClear ? 'クリアおめでとう!' : 'ゲームオーバー...'}
難易度: {currentModeRef.current?.name}
雑草収穫ポイント
{state.scoreWeed}
UMAタッチ減点
{state.scoreUma}
最大コンボ
{state.maxCombo}
合計スコア
{totalScore}
✨ノベルティを
ゲット!✨
ゲット!✨
