Spaces:
Running
Running
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>CNN 演算パズル:Pooling & Flatten</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> | |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> | |
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); | |
| body { font-family: 'Noto Sans JP', sans-serif; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| // --- アイコンコンポーネント (SVG直接埋め込み) --- | |
| const Layers = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></svg> | |
| ); | |
| const Maximize2 = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg> | |
| ); | |
| const MoveRight = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M18 8L22 12L18 16"/><path d="M2 12H22"/></svg> | |
| ); | |
| const Layout = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg> | |
| ); | |
| const Info = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg> | |
| ); | |
| const CheckCircle2 = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/><path d="m9 12 2 2 4-4"/></svg> | |
| ); | |
| const XCircle = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg> | |
| ); | |
| const ChevronRight = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="m9 18 6-6-6-6"/></svg> | |
| ); | |
| const ChevronLeft = ({className}) => ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="m15 18-6-6 6-6"/></svg> | |
| ); | |
| const App = () => { | |
| const [activeTab, setActiveTab] = React.useState('pooling'); | |
| const [poolingMode, setPoolingMode] = React.useState('max'); | |
| const [step, setStep] = React.useState(0); | |
| const [userInput, setUserInput] = React.useState(''); | |
| const [feedback, setFeedback] = React.useState(null); | |
| const [score, setScore] = React.useState(0); | |
| const poolingInput = [ | |
| [12, 20, 30, 0], [8, 12, 2, 15], [34, 70, 37, 4], [112, 100, 25, 12] | |
| ]; | |
| const flattenInput = [ | |
| [1, 0, 1], [0, 1, 0], [1, 1, 0] | |
| ]; | |
| const getCorrectPoolingValue = (s) => { | |
| const r = Math.floor(s / 2) * 2; | |
| const c = (s % 2) * 2; | |
| const values = [poolingInput[r][c], poolingInput[r][c + 1], poolingInput[r + 1][c], poolingInput[r + 1][c + 1]]; | |
| if (poolingMode === 'max') return Math.max(...values); | |
| return values.reduce((a, b) => a + b) / 4; | |
| }; | |
| const getCorrectFlattenValue = (s) => flattenInput.flat()[s]; | |
| const handleCheck = () => { | |
| const correctValue = activeTab === 'pooling' ? getCorrectPoolingValue(step) : getCorrectFlattenValue(step); | |
| if (parseFloat(userInput) === correctValue) { | |
| setFeedback('correct'); | |
| setScore(s => s + 1); | |
| } else { | |
| setFeedback('wrong'); | |
| } | |
| }; | |
| const nextStep = () => { | |
| const maxSteps = activeTab === 'pooling' ? 4 : 9; | |
| setStep((prev) => (prev + 1) % maxSteps); | |
| setUserInput(''); | |
| setFeedback(null); | |
| }; | |
| const resetGame = (tab) => { | |
| setActiveTab(tab); | |
| setStep(0); | |
| setUserInput(''); | |
| setFeedback(null); | |
| }; | |
| const renderPoolingGrid = () => { | |
| const targetRow = Math.floor(step / 2) * 2; | |
| const targetCol = (step % 2) * 2; | |
| return ( | |
| <div className="grid grid-cols-4 gap-2 bg-slate-100 p-2 rounded-lg border-4 border-slate-200"> | |
| {poolingInput.flat().map((val, i) => { | |
| const r = Math.floor(i / 4), c = i % 4; | |
| const isTarget = r >= targetRow && r < targetRow + 2 && c >= targetCol && c < targetCol + 2; | |
| return <div key={i} className={`w-12 h-12 md:w-16 md:h-16 flex items-center justify-center font-bold rounded shadow-sm transition-all ${isTarget ? 'bg-amber-400 text-amber-900 ring-4 ring-white scale-105 z-10' : 'bg-white text-slate-400 opacity-50'}`}>{val}</div> | |
| })} | |
| </div> | |
| ); | |
| }; | |
| const renderFlattenGrid = () => ( | |
| <div className="grid grid-cols-3 gap-2 bg-slate-100 p-2 rounded-lg border-4 border-slate-200"> | |
| {flattenInput.flat().map((val, i) => ( | |
| <div key={i} className={`w-12 h-12 md:w-16 md:h-16 flex items-center justify-center font-bold rounded shadow-sm transition-all ${i === step ? 'bg-indigo-500 text-white ring-4 ring-white scale-105 z-10' : 'bg-white text-slate-300'}`}>{val}</div> | |
| ))} | |
| </div> | |
| ); | |
| return ( | |
| <div className="min-h-screen bg-slate-50 p-4 md:p-8 text-slate-800"> | |
| <div className="max-w-2xl mx-auto space-y-6 text-center"> | |
| <header> | |
| <h1 className="text-3xl font-bold text-indigo-900 mb-2">CNN 演算パズル:Pooling & Flatten</h1> | |
| <p className="text-slate-600">データの圧縮と並べ替えを体験しましょう</p> | |
| </header> | |
| {/* タブ */} | |
| <div className="flex justify-center bg-white p-1 rounded-2xl shadow-sm border border-slate-200 inline-flex overflow-hidden"> | |
| <button onClick={() => resetGame('pooling')} className={`px-4 md:px-8 py-3 rounded-xl font-bold transition-all flex items-center gap-2 ${activeTab === 'pooling' ? 'bg-indigo-600 text-white shadow-md' : 'text-slate-500 hover:bg-slate-50'}`}><Maximize2 className="w-5 h-5" /> Pooling</button> | |
| <button onClick={() => resetGame('flatten')} className={`px-4 md:px-8 py-3 rounded-xl font-bold transition-all flex items-center gap-2 ${activeTab === 'flatten' ? 'bg-indigo-600 text-white shadow-md' : 'text-slate-500 hover:bg-slate-50'}`}><Layers className="w-5 h-5" /> Flatten</button> | |
| </div> | |
| {/* 説明・設定カード */} | |
| <div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-200"> | |
| <h2 className="text-lg font-semibold mb-4 flex items-center justify-center gap-2"><Info className="w-5 h-5 text-indigo-600" /> {activeTab === 'pooling' ? 'プーリング設定' : 'フラット化とは'}</h2> | |
| {activeTab === 'pooling' ? ( | |
| <div className="space-y-4"> | |
| <div className="flex p-1 bg-slate-100 rounded-lg max-w-xs mx-auto"> | |
| <button onClick={() => { setPoolingMode('max'); setFeedback(null); setUserInput(''); }} className={`flex-1 py-2 text-sm font-bold rounded-md transition-all ${poolingMode === 'max' ? 'bg-white shadow-sm text-indigo-600' : 'text-slate-500'}`}>Max</button> | |
| <button onClick={() => { setPoolingMode('avg'); setFeedback(null); setUserInput(''); }} className={`flex-1 py-2 text-sm font-bold rounded-md transition-all ${poolingMode === 'avg' ? 'bg-white shadow-sm text-indigo-600' : 'text-slate-500'}`}>Avg</button> | |
| </div> | |
| <p className="text-xs text-slate-500">{poolingMode === 'max' ? '領域内の最大値を抽出します。' : '領域内の平均値を計算します。'}</p> | |
| </div> | |
| ) : ( | |
| <p className="text-sm text-slate-600 leading-relaxed">2次元のデータを1次元(1列)に変換します。左上から順番に並べます。</p> | |
| )} | |
| </div> | |
| {/* 演習エリア */} | |
| <div className="bg-white p-6 rounded-2xl shadow-sm border border-slate-200 flex flex-col items-center"> | |
| <div className="w-full flex justify-between items-center mb-6"> | |
| <h2 className="font-bold text-slate-800">演算シミュレート</h2> | |
| <div className="text-sm font-medium text-indigo-600 bg-indigo-50 px-3 py-1 rounded-full">Score: {score}</div> | |
| </div> | |
| <div className="mb-6">{activeTab === 'pooling' ? renderPoolingGrid() : renderFlattenGrid()}</div> | |
| <div className="w-full max-w-xs space-y-4"> | |
| <div className="bg-slate-50 p-4 rounded-xl border border-slate-200"> | |
| <div className="flex gap-2"> | |
| <input type="number" step="0.1" value={userInput} onChange={(e) => setUserInput(e.target.value)} className="flex-1 px-4 py-3 text-2xl font-bold text-center rounded-xl border-2 border-indigo-200 focus:border-indigo-500 outline-none" placeholder="?" /> | |
| <button onClick={handleCheck} disabled={userInput === '' || feedback === 'correct'} className="bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-300 text-white px-6 py-2 rounded-xl font-bold">判定</button> | |
| </div> | |
| {feedback === 'correct' && <div className="mt-4 text-green-600 font-bold animate-bounce">✓ 正解!</div>} | |
| {feedback === 'wrong' && <div className="mt-4 text-red-500 font-bold">✗ 計算ミスかな?</div>} | |
| </div> | |
| <div className="flex gap-2"> | |
| <button onClick={() => setStep(s => (s - 1 + (activeTab === 'pooling' ? 4 : 9)) % (activeTab === 'pooling' ? 4 : 9))} className="flex-1 py-3 border border-slate-200 rounded-xl hover:bg-slate-50 text-slate-500 flex items-center justify-center gap-1 font-medium"><ChevronLeft className="w-4 h-4"/>戻る</button> | |
| <button onClick={nextStep} className="flex-1 py-3 bg-slate-800 text-white rounded-xl hover:bg-slate-900 flex items-center justify-center gap-1 font-medium">次へ <ChevronRight className="w-4 h-4"/></button> | |
| </div> | |
| </div> | |
| {/* 出力イメージ */} | |
| <div className="mt-12 w-full border-t border-slate-100 pt-8 flex flex-col items-center"> | |
| <h3 className="text-[10px] font-bold text-slate-400 mb-6 uppercase tracking-widest text-center">Output Structure</h3> | |
| <div className="flex justify-center"> | |
| {activeTab === 'pooling' ? ( | |
| <div className="grid grid-cols-2 gap-4"> | |
| {Array.from({ length: 4 }).map((_, i) => { | |
| const isDone = i < step || (i === step && feedback === 'correct'); | |
| const val = getCorrectPoolingValue(i); | |
| return <div key={i} className={`w-16 h-16 md:w-20 md:h-20 border-2 rounded-2xl flex items-center justify-center text-lg font-mono transition-all ${isDone ? 'bg-indigo-50 border-indigo-500 text-indigo-700 shadow-lg' : 'bg-transparent border-dashed border-slate-200 text-transparent'}`}>{isDone ? val : ''}</div> | |
| })} | |
| </div> | |
| ) : ( | |
| <div className="flex flex-wrap justify-center gap-2 max-w-md"> | |
| {Array.from({ length: 9 }).map((_, i) => { | |
| const isDone = i < step || (i === step && feedback === 'correct'); | |
| const val = getCorrectFlattenValue(i); | |
| return <div key={i} className={`w-10 h-10 md:w-12 md:h-12 border-2 rounded flex items-center justify-center text-sm font-mono transition-all ${isDone ? 'bg-emerald-50 border-emerald-500 text-emerald-700 shadow-md' : 'bg-transparent border-dashed border-slate-200 text-transparent'}`}>{isDone ? val : ''}</div> | |
| })} | |
| </div> | |
| )} | |
| </div> | |
| <div className="mt-6"> | |
| {activeTab === 'pooling' ? ( | |
| <div className="inline-flex items-center gap-2 bg-amber-50 text-amber-700 px-4 py-2 rounded-full text-xs font-bold"><Layout className="w-4 h-4" /> 情報を圧縮しました!</div> | |
| ) : ( | |
| <div className="inline-flex items-center gap-2 bg-indigo-50 text-indigo-700 px-4 py-2 rounded-full text-xs font-bold"><Layers className="w-4 h-4" /> 1次元形式になりました!</div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const root = ReactDOM.createRoot(document.getElementById('root')); | |
| root.render(<App />); | |
| </script> | |
| </body> | |
| </html> | |