Files
Learn_System/frontend/js/flagships/phys9_flag_F5_atwood.js
T
Maxim Dolgolyov d190fd2de9 feat(phys9 flagships): F5 Атвуд + F7 Лифт (Wave B пилоты)
F5. Машина Атвуда (§22):
- Canvas 640×420: блок с двумя массами на нити
- Slider'ы: m₁, m₂, трение в блоке
- Запуск: бо́льшая масса опускается, меньшая поднимается
- Физика: a = ((m₁-m₂)g - μ)/(m₁+m₂)
- Анимация: блок вращается, грузы движутся, размер пропорц. m
- Показ векторов сил тяжести (m₁g, m₂g) и натяжений (T) в покое
- Stats: a, T, v, t

F7. Лифт с динамометром (§24):
- Canvas 640×460: шахта с 5 этажами + большой циферблат справа
- Слева кабина с динамометром и грузом m
- Slider'ы: m груза, a разгона
- 5 режимов кнопок:
  - Разгон ⬆ (hold) → a = +a_in
  - Разгон ⬇ (hold) → a = -a_in
  - Стоп → a = 0
  - Свободное падение → a = -g (трос показывается пунктиром)
  - Сброс
- 2 динамометра: мини в кабине + большой круглый (шкала 0..2.5g)
- Stats: P, P/(mg), v лифта, h высота
- Контекстный feedback: невесомость / норма / перегрузка / P<0

Подключение в ch2: F5 на p22 (закон Ньютона II), F7 на p24 (вес).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:13:08 +03:00

183 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// F5. Машина Атвуда (§22 в ch2) — связанные массы через блок.
(function(){
'use strict';
const B = () => window.PHYS9_FLAG_BASE;
const C = () => window.PHYS9_COLORS || {};
function init(secId){
if (!B()) return false;
const body = ''
+ '<div class="flag-sliders">'
+ '<label>$m_1$ (левая), кг: <b id="F5-m1v">3</b><input type="range" id="F5-m1" min="0.1" max="10" step="0.1" value="3"></label>'
+ '<label>$m_2$ (правая), кг: <b id="F5-m2v">2</b><input type="range" id="F5-m2" min="0.1" max="10" step="0.1" value="2"></label>'
+ '<label>Трение в блоке: <b id="F5-frv">0</b><input type="range" id="F5-fr" min="0" max="3" step="0.05" value="0"></label>'
+ '</div>'
+ '<canvas id="F5-cv" class="flag-canvas" width="640" height="420" style="height:420px"></canvas>'
+ '<div class="flag-controls">'
+ '<button class="flag-btn primary" id="F5-go">Запустить</button>'
+ '<button class="flag-btn" id="F5-reset">Сброс</button>'
+ '</div>'
+ '<div class="flag-stats">'
+ '<div class="flag-stat"><span class="lbl">$a = (m_1-m_2)g/(m_1+m_2)$</span><span class="val" id="F5-a">1.96 м/с²</span></div>'
+ '<div class="flag-stat"><span class="lbl">Натяжение T</span><span class="val" id="F5-T">23.5 Н</span></div>'
+ '<div class="flag-stat"><span class="lbl">$v$ текущая</span><span class="val" id="F5-v">0 м/с</span></div>'
+ '<div class="flag-stat"><span class="lbl">$t$ прошло</span><span class="val" id="F5-t">0 с</span></div>'
+ '</div>';
const card = B().makeCard(secId,
'F5. Машина Атвуда',
'Две массы через блок на нити. Бо́льшая опускается, меньшая поднимается. Физика: $a = (m_1-m_2)g/(m_1+m_2)$.',
body);
if (!card) return false;
const cv = document.getElementById('F5-cv');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
const blockX = W/2, blockY = 60, blockR = 28;
let st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
/* y — расстояние от блока вниз, +v = m1 опускается */
function readSliders(){
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const fr = +document.getElementById('F5-fr').value;
document.getElementById('F5-m1v').textContent = m1.toFixed(2);
document.getElementById('F5-m2v').textContent = m2.toFixed(2);
document.getElementById('F5-frv').textContent = fr.toFixed(2);
const g = 9.8;
const a = ((m1 - m2)*g - fr) / (m1 + m2);
const T = m2 * (g + a);
document.getElementById('F5-a').textContent = a.toFixed(2) + ' м/с²';
document.getElementById('F5-T').textContent = T.toFixed(1) + ' Н';
}
function reset(){
st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
document.getElementById('F5-go').textContent = 'Запустить';
document.getElementById('F5-v').textContent = '0 м/с';
document.getElementById('F5-t').textContent = '0 с';
draw();
}
function tick(dt){
if (!st.running) { draw(); return; }
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const fr = +document.getElementById('F5-fr').value;
const g = 9.8;
const a = ((m1 - m2)*g - fr*Math.sign(st.v || (m1-m2))) / (m1 + m2);
st.v += a * dt;
/* движение: m1 опускается со скоростью v, m2 поднимается */
const dy = st.v * dt * 30; /* масштаб 30 px/м */
st.y1 += dy;
st.y2 -= dy;
/* ограничения */
if (st.y1 > H - 80){ st.y1 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y2 < 100){ st.y2 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y1 < 100){ st.y1 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y2 > H - 80){ st.y2 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
st.t += dt;
document.getElementById('F5-v').textContent = st.v.toFixed(2) + ' м/с';
document.getElementById('F5-t').textContent = st.t.toFixed(1) + ' с';
draw();
}
function draw(){
const col = C();
ctx.fillStyle = col.bg || '#fafafa';
ctx.fillRect(0, 0, W, H);
/* потолок */
ctx.fillStyle = col.surface || '#a16207';
ctx.fillRect(0, 0, W, 30);
for (let x = 0; x < W; x += 12){
ctx.strokeStyle = '#7c4a08';
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x+6, 6); ctx.stroke();
}
/* кронштейн к блоку */
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 4;
ctx.beginPath(); ctx.moveTo(blockX, 30); ctx.lineTo(blockX, blockY - blockR); ctx.stroke();
/* блок */
ctx.fillStyle = col.bodyLight || '#cbd5e1';
ctx.beginPath(); ctx.arc(blockX, blockY, blockR, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 2.5;
ctx.stroke();
/* радиальные линии (вращение) */
const rot = st.v * 0.1;
for (let i = 0; i < 4; i++){
const a = rot + i*Math.PI/2;
ctx.beginPath();
ctx.moveTo(blockX + (blockR-5)*Math.cos(a), blockY + (blockR-5)*Math.sin(a));
ctx.lineTo(blockX + 5*Math.cos(a), blockY + 5*Math.sin(a));
ctx.stroke();
}
/* нити */
const cordL = blockX - blockR;
const cordR = blockX + blockR;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(cordL, blockY); ctx.lineTo(cordL, st.y1);
ctx.moveTo(cordR, blockY); ctx.lineTo(cordR, st.y2);
ctx.stroke();
/* грузы */
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const r1 = Math.min(40, 12 + m1*3);
const r2 = Math.min(40, 12 + m2*3);
ctx.fillStyle = col.forceGravity || '#2563eb';
ctx.fillRect(cordL - r1, st.y1, r1*2, r1);
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 1.5;
ctx.strokeRect(cordL - r1, st.y1, r1*2, r1);
ctx.fillStyle = col.fail || '#dc2626';
ctx.fillRect(cordR - r2, st.y2, r2*2, r2);
ctx.strokeRect(cordR - r2, st.y2, r2*2, r2);
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px Inter,sans-serif';
ctx.textAlign = 'center';
ctx.fillText('m₁ = '+m1.toFixed(1)+' кг', cordL, st.y1 + r1/2 + 4);
ctx.fillText('m₂ = '+m2.toFixed(1)+' кг', cordR, st.y2 + r2/2 + 4);
ctx.textAlign = 'left';
/* векторы силы тяжести */
if (st.t < 0.5 || !st.running){
B().arrow(ctx, cordL, st.y1 + r1, cordL, st.y1 + r1 + 40, col.forceGravity || '#2563eb', 2);
ctx.fillStyle = col.forceGravity || '#2563eb';
ctx.font = 'bold 12px Inter,sans-serif';
ctx.fillText('m₁g', cordL + 8, st.y1 + r1 + 30);
B().arrow(ctx, cordR, st.y2 + r2, cordR, st.y2 + r2 + 40, col.forceGravity || '#2563eb', 2);
ctx.fillText('m₂g', cordR + 8, st.y2 + r2 + 30);
/* T снизу */
B().arrow(ctx, cordL, st.y1, cordL, st.y1 - 30, col.forceTension || '#16a34a', 2);
ctx.fillStyle = col.forceTension || '#16a34a';
ctx.fillText('T', cordL + 8, st.y1 - 20);
B().arrow(ctx, cordR, st.y2, cordR, st.y2 - 30, col.forceTension || '#16a34a', 2);
ctx.fillText('T', cordR + 8, st.y2 - 20);
}
}
document.getElementById('F5-go').addEventListener('click', ()=>{
if (st.y1 >= H - 80 || st.y2 >= H - 80 || st.y1 < 110 || st.y2 < 110) reset();
st.running = !st.running;
document.getElementById('F5-go').textContent = st.running ? 'Пауза' : 'Запустить';
});
document.getElementById('F5-reset').addEventListener('click', reset);
['F5-m1','F5-m2','F5-fr'].forEach(id => document.getElementById(id).addEventListener('input', ()=>{
readSliders();
if (!st.running) reset();
}));
readSliders();
draw();
B().startLoop('F5', cv, tick);
return true;
}
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
else document.addEventListener('DOMContentLoaded', ()=>{
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
});
})();