Explorar el Código

Обновить 'docs/file.md'

u23-27dybenko hace 1 semana
padre
commit
f3b9b70de6
Se han modificado 1 ficheros con 208 adiciones y 78 borrados
  1. 208 78
      docs/file.md

+ 208 - 78
docs/file.md

@@ -359,29 +359,43 @@
     <button class="btn" id="btnNext4" type="button" disabled>Далее →</button>
   </section>
 
-  <!-- SCENE 5 -->
+    <!-- SCENE 5 -->
   <section class="scene" id="scene5">
     <h2>Виды волн. Звук — продольная волна</h2>
+
     <div class="controls">
       <button class="btn" id="btnLong" type="button">Продольная</button>
       <button class="btn pink" id="btnTrans" type="button">Поперечная</button>
     </div>
-    <div class="canvas-wrap"><canvas id="typeCanvas" width="800" height="200"></canvas></div>
 
-    <div class="controls">
-      <div class="slider-box">
-        <label>Частота волны: <span id="typeFreqVal">3</span> Гц</label>
-        <input type="range" id="typeFreqSlider" min="1" max="10" value="3">
+    <div class="canvas-wrap" style="position:relative;">
+      <canvas id="typeCanvas" width="800" height="220"></canvas>
+    </div>
+
+    <!-- 🎮 Мини-игра -->
+    <div id="waveGame" style="max-width:720px;text-align:center;">
+      <p style="margin-bottom:8px;">
+        🎯 <b>Мини-игра:</b> нажми <b>«Толкнуть поршень»</b> и кликай по
+        <span style="color:var(--neon-pink);font-weight:800;">розовым сгусткам (сжатиям)</span>,
+        когда они проплывают мимо.
+      </p>
+      <div class="controls">
+        <button class="btn green" id="btnPiston" type="button">🔊 Толкнуть поршень</button>
+        <button class="btn pink" id="btnReset5" type="button">🔄 Сброс</button>
       </div>
+      <p class="hint" id="scene5Hint">Поймано сжатий: 0 / 5</p>
     </div>
 
-    <div class="table" style="max-width:720px;">
-      💡 <b>Главное:</b> молекулы среды <span>колеблются на месте</span>, а волна переносит
-      <span>энергию</span>, а не вещество.<br>
-      🔊 В продольной волне чередуются <span>сжатия</span> и <span>разрежения</span> — так звук идёт через воздух.
+    <!-- 💡 Пояснение (появляется после победы) -->
+    <div id="waveFact" class="table" style="max-width:720px;display:none;">
+      🎓 <b>Что ты сейчас сделал:</b><br>
+      Поршень толкает молекулы → они <span>сжимаются</span>, потом расходятся → возникает
+      <span>разрежение</span>. Волна <span>бежит вправо</span>, а каждая молекула
+      <span>колеблется на месте</span> и никуда не улетает.<br>
+      🔊 Именно так звук распространяется через воздух — продольной волной!
     </div>
 
-    <button class="btn" id="btnNext5" type="button">Далее →</button>
+    <button class="btn" id="btnNext5" type="button" disabled>Далее →</button>
   </section>
 
   <!-- SCENE 6 -->
@@ -814,39 +828,102 @@ document.getElementById('btnCheck4').addEventListener('click', function(){
 });
 document.getElementById('btnNext4').addEventListener('click', function(){ goToScene(4); });
 
-/* ================= SCENE 5: виды волн ================= */
+/* ================= SCENE 5: виды волн + мини-игра ================= */
 var typeCanvas = document.getElementById('typeCanvas');
 var typeCtx = typeCanvas.getContext('2d');
 var typeT = 0;
 var waveType = 'long';       // 'long' | 'trans'
-var typeFreq = 3;            // 🎚️ частота волны (1–10 Гц)
 
-// Слайдер частоты продольной/поперечной волны
-document.getElementById('typeFreqSlider').addEventListener('input', function(e){
-  typeFreq = +e.target.value;
-  document.getElementById('typeFreqVal').textContent = typeFreq;
-  clickSound();
-});
+// ---- Мини-игра ----
+var gameActive = false;      // идёт ли волна сейчас
+var gameCaught = 0;          // сколько сжатий поймано
+var gamePhase = 0;           // фаза волны (0..2π повторяется)
+var gameSpeed = 0.06;        // скорость бега волны
+var gameBursts = [];         // визуальные "вспышки" от кликов
+var gameZones = [];          // текущие зоны сжатий (пересчитывается каждый кадр)
+
+// Параметры волны
+var WAVE_N = 50;             // сколько молекул
+var WAVE_LEN = 4;            // сколько длин волн на экране
 
-// Переключатели вида волны
+// 🔘 Переключатели вида волны
 document.getElementById('btnLong').addEventListener('click', function(){
   waveType = 'long'; clickSound();
+  document.getElementById('waveGame').style.display = 'block';
 });
 document.getElementById('btnTrans').addEventListener('click', function(){
   waveType = 'trans'; clickSound();
+  // В поперечной волне мини-игра не нужна — прячем
+  document.getElementById('waveGame').style.display = 'none';
 });
 
-// Кнопка «Далее»
-document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
+// 🎮 Кнопка "Толкнуть поршень"
+document.getElementById('btnPiston').addEventListener('click', function(){
+  if(gameActive) return;                 // уже идёт
+  gameActive = true;
+  gamePhase = 0;
+  gameCaught = 0;
+  gameBursts = [];
+  document.getElementById('scene5Hint').textContent = 'Поймано сжатий: 0 / 5';
+  document.getElementById('waveFact').style.display = 'none';
+  document.getElementById('btnNext5').disabled = true;
+  ensureAudio();
+  beep(180, 0.25, 'sawtooth', 0.20);      // низкий "толчок поршня"
+});
+
+// 🔄 Кнопка "Сброс"
+document.getElementById('btnReset5').addEventListener('click', function(){
+  gameActive = false;
+  gameCaught = 0;
+  gamePhase = 0;
+  gameBursts = [];
+  document.getElementById('scene5Hint').textContent = 'Поймано сжатий: 0 / 5';
+  document.getElementById('waveFact').style.display = 'none';
+  document.getElementById('btnNext5').disabled = true;
+  clickSound();
+});
+
+// 👆 Клик по canvas — попытка поймать сжатие
+typeCanvas.addEventListener('click', function(e){
+  if(!gameActive || waveType !== 'long') return;
+  var rect = typeCanvas.getBoundingClientRect();
+  var scaleX = typeCanvas.width / rect.width;
+  var clickX = (e.clientX - rect.left) * scaleX;
+
+  // Ищем, попал ли клик в зону сжатия
+  var hit = false;
+  for(var i=0; i<gameZones.length; i++){
+    var z = gameZones[i];
+    if(clickX >= z.x - z.w/2 && clickX <= z.x + z.w/2){
+      hit = true;
+      break;
+    }
+  }
+  if(hit){
+    gameCaught++;
+    correctSound();
+    gameBursts.push({ x: clickX, y: typeCanvas.height/2, r: 10, alpha: 1 });
+    document.getElementById('scene5Hint').textContent =
+      'Поймано сжатий: ' + gameCaught + ' / 5';
+    if(gameCaught >= 5){
+      gameActive = false;
+      document.getElementById('scene5Hint').textContent =
+        '🎉 Отлично! Ты собрал продольную волну!';
+      document.getElementById('waveFact').style.display = 'block';
+      document.getElementById('btnNext5').disabled = false;
+      correctSound();
+      setTimeout(function(){ beep(1320, 0.4, 'triangle', 0.2); }, 200);
+    }
+  } else {
+    wrongSound();
+  }
+});
 
 // 🔁 Основной цикл анимации
 function drawType(){
   var W = typeCanvas.width, H = typeCanvas.height;
   typeCtx.clearRect(0,0,W,H);
 
-  // 🎚️ Скорость фазы зависит от частоты (чем больше Гц — тем быстрее бежит волна)
-  typeT += 0.02 * typeFreq;
-
   if(waveType === 'long'){
     drawLongitudinal(W, H);
   } else {
@@ -855,25 +932,36 @@ function drawType(){
   requestAnimationFrame(drawType);
 }
 
-// 🌊 ПРОДОЛЬНАЯ ВОЛНА — сжатия и разрежения
+// 🌊 ПРОДОЛЬНАЯ ВОЛНА — сжатия/разрежения + мини-игра
 function drawLongitudinal(W, H){
-  var N = 45;
-  var spacing = W / (N + 1);
+  var spacing = W / (WAVE_N + 1);
 
-  // Рисуем «коридор» среды
+  // Если игра активна — фаза бежит. Иначе — статичная картинка.
+  if(gameActive){
+    gamePhase += gameSpeed;
+  }
+
+  // Фоновый "коридор" среды
   typeCtx.fillStyle = 'rgba(0,229,255,.04)';
-  typeCtx.fillRect(0, H/2 - 40, W, 80);
+  typeCtx.fillRect(0, H/2 - 45, W, 90);
+
+  // Определяем зоны сжатий (там, где sin > 0.7)
+  gameZones = [];
 
-  for(var i=0;i<N;i++){
+  // Рисуем молекулы
+  for(var i=0; i<WAVE_N; i++){
     var baseX = spacing * (i + 1);
 
-    // Смещение молекулы: колеблется ВОКРУГ своей позиции, но не уезжает вместе с волной
-    var shift = Math.sin(typeT - i * 0.35) * 12;
+    // Смещение молекул: колеблются вокруг своей позиции.
+    // Формула: shift = sin(phase - i * k) — классическая продольная волна.
+    var k = (2 * Math.PI * WAVE_LEN) / WAVE_N;
+    var shift = Math.sin(gamePhase - i * k) * 14;
+
     var x = baseX + shift;
 
-    // Плотность: сжатие / разрежение (используется для радиуса и яркости)
-    var density = 0.5 + 0.5 * Math.sin(typeT - i * 0.35); // 0..1
-    var r = 5 + density * 3;                               // сжатие → крупнее и ярче
+    // Плотность: сжатие / разрежение
+    var density = 0.5 + 0.5 * Math.sin(gamePhase - i * k);  // 0..1
+    var r = 4 + density * 4;                                 // сжатие = крупнее
     var alpha = 0.35 + density * 0.65;
 
     typeCtx.beginPath();
@@ -881,56 +969,78 @@ function drawLongitudinal(W, H){
     typeCtx.shadowBlur = 12; typeCtx.shadowColor = '#ff2ec4';
     typeCtx.arc(x, H/2, r, 0, Math.PI*2);
     typeCtx.fill();
+
+    // Запоминаем позиции сжатий (для игры)
+    if(density > 0.85){
+      gameZones.push({ x: x, w: spacing * 1.6 });
+    }
   }
   typeCtx.shadowBlur = 0;
 
+  // 🎯 Подсветка зон сжатий (пульсирующий кружок)
+  if(gameActive){
+    for(var g=0; g<gameZones.length; g++){
+      var z = gameZones[g];
+      var pulse = 1 + 0.3 * Math.sin(typeT * 6);
+      typeCtx.beginPath();
+      typeCtx.strokeStyle = 'rgba(57,255,20,.6)';
+      typeCtx.lineWidth = 2;
+      typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#39ff14';
+      typeCtx.arc(z.x, H/2, 22 * pulse, 0, Math.PI*2);
+      typeCtx.stroke();
+    }
+    typeCtx.shadowBlur = 0;
+  }
+
+  // ✨ Вспышки от успешных кликов
+  for(var b=gameBursts.length - 1; b >= 0; b--){
+    var burst = gameBursts[b];
+    typeCtx.beginPath();
+    typeCtx.strokeStyle = 'rgba(57,255,20,' + burst.alpha + ')';
+    typeCtx.lineWidth = 3;
+    typeCtx.shadowBlur = 25; typeCtx.shadowColor = '#39ff14';
+    typeCtx.arc(burst.x, burst.y, burst.r, 0, Math.PI*2);
+    typeCtx.stroke();
+    burst.r += 4;
+    burst.alpha -= 0.05;
+    if(burst.alpha <= 0) gameBursts.splice(b, 1);
+  }
+  typeCtx.shadowBlur = 0;
+
+  // 🎚️ ПОРШЕНЬ слева
+  typeCtx.fillStyle = '#39ff14';
+  typeCtx.shadowBlur = 20; typeCtx.shadowColor = '#39ff14';
+  typeCtx.fillRect(10, H/2 - 30, 22, 60);
+  typeCtx.shadowBlur = 0;
+  typeCtx.fillStyle = '#39ff14';
+  typeCtx.font = 'bold 12px Segoe UI';
+  typeCtx.fillText('🔊', 14, H/2 + 5);
+
+  // 👂 УХО справа
+  typeCtx.fillStyle = '#00e5ff';
+  typeCtx.font = 'bold 22px Segoe UI';
+  typeCtx.fillText('👂', W - 35, H/2 + 8);
+
   // Подписи
   typeCtx.fillStyle = '#00e5ff';
   typeCtx.font = 'bold 14px Segoe UI';
-  typeCtx.fillText('Продольная (звук в воздухе)', 20, 30);
+  typeCtx.fillText('Продольная (звук в воздухе)', 50, 25);
 
-  // Стрелка направления движения волны
+  // Стрелка направления волны
   typeCtx.strokeStyle = '#39ff14';
   typeCtx.lineWidth = 2;
-  typeCtx.shadowBlur = 12; typeCtx.shadowColor = '#39ff14';
   typeCtx.beginPath();
-  typeCtx.moveTo(W - 140, 30);
-  typeCtx.lineTo(W - 40, 30);
-  typeCtx.lineTo(W - 55, 22);
-  typeCtx.moveTo(W - 40, 30);
-  typeCtx.lineTo(W - 55, 38);
+  typeCtx.moveTo(W - 140, 25);
+  typeCtx.lineTo(W - 45, 25);
+  typeCtx.lineTo(W - 60, 18);
+  typeCtx.moveTo(W - 45, 25);
+  typeCtx.lineTo(W - 60, 32);
   typeCtx.stroke();
-  typeCtx.shadowBlur = 0;
-  typeCtx.fillStyle = '#39ff14';
-  typeCtx.font = '12px Segoe UI';
-  typeCtx.fillText('волна →', W - 135, 20);
 
-  // Пунктирная разметка «сжатие / разрежение»
-  typeCtx.strokeStyle = 'rgba(255,255,255,.08)';
-  typeCtx.setLineDash([4,6]);
-  typeCtx.lineWidth = 1;
-  for(var k=0;k<5;k++){
-    var cx = 60 + k * (W - 120) / 4;
-    typeCtx.beginPath();
-    typeCtx.moveTo(cx, H/2 - 45);
-    typeCtx.lineTo(cx, H/2 + 45);
-    typeCtx.stroke();
-  }
-  typeCtx.setLineDash([]);
-
-  // Подписи сжатие/разрежение (плавно переключаются)
-  var labels = ['сжатие', 'разрежение'];
-  for(var m=0;m<4;m++){
-    var lx = 60 + m * (W - 120) / 4 + 30;
-    var phase = Math.sin(typeT - m * 1.4);
-    var lbl = phase > 0 ? 'сжатие' : 'разрежение';
-    typeCtx.fillStyle = phase > 0 ? 'rgba(255,46,196,.85)' : 'rgba(0,229,255,.85)';
-    typeCtx.font = '11px Segoe UI';
-    typeCtx.fillText(lbl, lx, H/2 + 60);
-  }
+  typeT += 0.03;
 }
 
-// 🌊 ПОПЕРЕЧНАЯ ВОЛНА — синусоида
+// 🌊 ПОПЕРЕЧНАЯ ВОЛНА — синусоида, ширина зависит от периода
 function drawTransverse(W, H){
   // Ось
   typeCtx.strokeStyle = 'rgba(0,229,255,.15)';
@@ -940,22 +1050,39 @@ function drawTransverse(W, H){
   typeCtx.lineTo(W, H/2);
   typeCtx.stroke();
 
+  // 4 гребня на экране
+  var wavesAcross = 4;
+  var pixelAmp = 55;
+
+  // Пунктирные границы
+  typeCtx.strokeStyle = 'rgba(255,46,196,.2)';
+  typeCtx.setLineDash([4,4]);
+  typeCtx.lineWidth = 1;
+  typeCtx.beginPath();
+  typeCtx.moveTo(0, H/2 - pixelAmp);  typeCtx.lineTo(W, H/2 - pixelAmp);
+  typeCtx.moveTo(0, H/2 + pixelAmp);  typeCtx.lineTo(W, H/2 + pixelAmp);
+  typeCtx.stroke();
+  typeCtx.setLineDash([]);
+
   // Сама волна
   typeCtx.strokeStyle = '#00e5ff';
   typeCtx.lineWidth = 2;
   typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#00e5ff';
   typeCtx.beginPath();
-  for(var x=0; x<W; x+=4){
-    var y = H/2 + Math.sin(x * 0.02 + typeT) * 55;
+  for(var x=0; x<W; x+=3){
+    var phaseX = (x / W) * Math.PI * 2 * wavesAcross;
+    var y = H/2 + Math.sin(phaseX + typeT * 0.8) * pixelAmp;
     if(x===0) typeCtx.moveTo(x,y); else typeCtx.lineTo(x,y);
   }
   typeCtx.stroke();
   typeCtx.shadowBlur = 0;
 
   // Частицы на гребнях
-  for(var k=0;k<20;k++){
-    var xx = (k+1) * (W/21);
-    var yy = H/2 + Math.sin(xx * 0.02 + typeT) * 55;
+  var dotsCount = Math.round(wavesAcross * 6);
+  for(var k=0;k<dotsCount;k++){
+    var xx = (k + 0.5) * (W / dotsCount);
+    var phaseXDot = (xx / W) * Math.PI * 2 * wavesAcross;
+    var yy = H/2 + Math.sin(phaseXDot + typeT * 0.8) * pixelAmp;
     typeCtx.beginPath();
     typeCtx.fillStyle = '#ff2ec4';
     typeCtx.shadowBlur = 15; typeCtx.shadowColor = '#ff2ec4';
@@ -969,7 +1096,10 @@ function drawTransverse(W, H){
   typeCtx.fillText('Поперечная (волны на воде, струна)', 20, 30);
 }
 
-// Запуск
+// ▶️ Кнопка Далее
+document.getElementById('btnNext5').addEventListener('click', function(){ goToScene(5); });
+
+// Запуск цикла
 drawType();
 
 /* ================= SCENE 6: карточки + пары ================= */