Files
Learn_System/frontend/js/game/levels.js
T
Maxim Dolgolyov 351251d652 @
feat(quantik-game): фаза 1 — оболочка игры + физ-уровень + прогресс (MVP)

Страница /quantik монтирует уровень-спеку в SimEngine (игровой режим: HUD из
Ф0 + слайдеры закона + play/reset), на победу шлёт результат и показывает
экран успеха (звёзды/время/попытки, inline SVG). Уровень phys-artillery-1
как данные (levels.js): гравитация + запуск тела из угла/скорости, портал,
бонус-звезда. Бэкенд: миграция 076 game_progress (UNIQUE user+level),
/api/game/progress (GET свой / POST upsert best time/stars, attempts++,
auth-only, валидация входа), клиент LS.gameProgress*, пункт сайдбара.
game.test.js 13/13; npm test 251 pass/8 baseline; lint:routes 0.
Уровень проверен на реальном интеграторе (311 выигрышных комбо, 31 на 3★).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-13 15:31:25 +03:00

108 lines
6.4 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.
'use strict';
/* ════════════════════════════════════════════════════════════════════════
Квантик — Законы Мира · Реестр уровней (Фаза 1, MVP).
Уровень = СПЕКА SimForge (данные, не код) + блок `goal` (победа), который
движок (_sim_engine.js) умеет с Фазы 0. Игрок не управляет героем напрямую —
он «чинит закон мира»: крутит слайдеры params (угол/скорость), затем «Запуск»,
и симуляция проигрывается к цели.
ИСТОЧНИК УРОВНЕЙ (решение зафиксировано в CONTEXT.md):
— СЕЙЧАС (Фаза 1): встроенные данные здесь, window.QuantikLevels.
— ПОЗЖЕ (Фаза 5): уровни авторятся в sim-builder и хранятся в custom_sims
(cat='game'); реестр пополнится загрузкой опубликованных спек с сервера.
Форма записи уровня:
{ id, title, subject?, hint?, spec }
где spec — обычная спека SimForge с блоком goal. id == level_id для
/api/game/progress (LS.gameProgressSubmit(id, ...)).
⛔ Без eval/Function. Все «числовые» поля могут быть числом ИЛИ строкой-
выражением (их безопасно вычисляет SimExpr на клиенте).
════════════════════════════════════════════════════════════════════════ */
(function (global) {
/* ── Уровень 1: «Артиллерия Квантика» ──────────────────────────────────
Герой — светящаяся точка-тело (body) с кометной трассой (P2). Запускается
из начала координат под углом θ со скоростью v; гравитация тянет вниз.
Цель — попасть в портал; бонус-звезда — собрать кристалл по дороге.
Параметры подобраны так, чтобы уровень был ПРОХОДИМ в пределах слайдеров. */
var PORTAL_X = 8; // центр портала по X (мир)
var PORTAL_Y = 0; // центр портала по Y (на «земле» y=0)
var PORTAL_R = 0.7; // радиус попадания
var STAR_X = 4; // бонус-кристалл (на восходящей ветви хорошей дуги)
var STAR_Y = 2.6;
var STAR_R = 0.65;
var artillery1 = {
id: 'phys-artillery-1',
title: 'Артиллерия Квантика',
subject: 'physics',
hint: 'Подберите угол и скорость, чтобы Квантик долетел до портала. Соберите кристалл по дороге — это бонусная звезда.',
spec: {
specVersion: 1,
meta: { title: 'Артиллерия Квантика', desc: 'Закон движения: бросок под углом к горизонту.' },
viewport: { xmin: -1, xmax: 12, ymin: -1.2, ymax: 7, grid: true, axes: true, bg: '#0D0D1A' },
params: [
{ name: 'theta', label: 'Угол', min: 10, max: 80, step: 1, value: 45, unit: '°' },
{ name: 'v', label: 'Скорость', min: 5, max: 20, step: 0.5, value: 10, unit: 'м/с' }
],
physics: {
enabled: true,
gravity: { x: 0, y: -9.8 }
},
objects: [
// «Земля» — линия y=0 для ориентира.
{ type: 'segment', x1: -1, y1: 0, x2: 12, y2: 0, color: '#334155', width: 2 },
// Бонус-кристалл (звезда). Контурный кружок-маркер.
{ type: 'circle', x: STAR_X, y: STAR_Y, r: STAR_R, color: '#FBBF24', width: 2, glow: true },
{ type: 'label', x: STAR_X, y: STAR_Y + 0.7, text: 'кристалл', color: '#FBBF24', size: 12 },
// Портал — цель. Светящийся кружок.
{ type: 'circle', x: PORTAL_X, y: PORTAL_Y + PORTAL_R, r: PORTAL_R, color: '#22D3EE', width: 3, glow: true, glowColor: '#22D3EE' },
{ type: 'label', x: PORTAL_X, y: PORTAL_Y + 2.0, text: 'портал', color: '#22D3EE', size: 12 },
// Герой Квантик — физ-тело, стартует из (0,0) со скоростью (vx,vy).
// glow + кометная трасса (P2).
{
id: 'ball', type: 'point', r: 7, color: '#06D6E0',
x: 0, y: 0,
glow: true, glowColor: '#06D6E0', trail: true, trailColor: '#06D6E0',
body: {
mass: 1,
vx: 'v*cos(theta*pi/180)',
vy: 'v*sin(theta*pi/180)'
}
},
// Живые показания скорости (бейдж-оверлей).
{ type: 'readout', label: 'v', expr: 'v', unit: 'м/с', precision: 1 },
{ type: 'readout', label: 'θ', expr: 'theta', unit: '°', precision: 0 }
],
goal: {
title: 'Попади в портал',
hint: 'Квантик должен достичь портала. Бонус: собери кристалл по дороге.',
// Победа: герой в радиусе портала.
when: 'hypot(ball.x - ' + PORTAL_X + ', ball.y - ' + (PORTAL_Y + PORTAL_R) + ') < ' + PORTAL_R,
// Мягкий проигрыш: улетел далеко за поле (промах) — можно перезапустить.
fail: 'ball.x > 11.5 || ball.y < -1.0',
stars: [
{ when: 'hypot(ball.x - ' + STAR_X + ', ball.y - ' + STAR_Y + ') < ' + STAR_R, label: 'Собрал кристалл' }
]
}
}
};
var LEVELS = [artillery1];
function list() { return LEVELS.slice(); }
function get(id) {
for (var i = 0; i < LEVELS.length; i++) if (LEVELS[i].id === id) return LEVELS[i];
return null;
}
global.QuantikLevels = { list: list, get: get, LEVELS: LEVELS };
})(typeof window !== 'undefined' ? window : this);