"""Генератор страниц раздела «Проекты»: список /projects/, игра /projects/amans-world/ и хоумлаб /projects/homelab/. Запуск из корня репозитория: python3 tools/gen_projects.py (сначала должен существовать tools/gen_arch.py: из него берётся общая шапка)""" import os, html, subprocess, re W = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'web') + '/' E = html.escape src = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gen_arch.py')).read() # берём head() и TAIL из генератора архитектуры, чтобы шапка была одинаковой ns = {'os': os, 'html': html, 'W': W, 'E': E} exec(src[src.index('def head('):src.index('# ---------- данные проектов ----------')], ns) head, TAIL = ns['head'], ns['TAIL'] def dims(img): o = subprocess.run(['magick', 'identify', '-format', '%w %h', W + f'img/proj/{img}.webp'], capture_output=True, text=True).stdout.split() return int(o[0]), int(o[1]) def srcset(img): w, _ = dims(img); parts = [] for suf, ww in (('-sm', 640), ('-md', 960)): if os.path.exists(W + f'img/proj/{img}{suf}.webp') and ww < w: parts.append(f'/img/proj/{img}{suf}.webp {ww}w') parts.append(f'/img/proj/{img}.webp {w}w'); return ', '.join(parts) def fig(img, cap, kind='', lead=False): w, h = dims(img) cls = 'shot' + (f' shot--{kind}' if kind else '') + (' shot--lead' if lead else '') lz = ' fetchpriority="high"' if lead else ' loading="lazy" decoding="async" fetchpriority="low"' return f'''
{E(cap)}
{E(cap)}
''' GAME = 'https://grid.ibrash.duckdns.org/' # ---------- страница игры ---------- title = "Aman's World" desc = "Aman's World: 3D-игра с паркуром и гонками, которая запускается прямо в браузере. Неоновый хаб, гонки Sector 3 Run и Ascend Only." stats = [('5','порталов в хабе'),('8','чекпоинтов, Sector 3 Run'),('13','чекпоинтов, Ascend Only'),('40','МБ при первом запуске')] secs = [ ('Неоновый хаб', 'Ты начинаешь в хабе: зал с неоновыми колоннами, вращающейся скульптурой-атомом и порталами на карты. Над гоночными порталами висят таблицы рекордов. Перед входом вводишь ник до 16 символов.'), ('Паркур', 'Движение собрано под паркур: бег, прыжок, двойной прыжок, рывок, скольжение, бег по стенам, прыжок от стены и подтягивание на уступы. На трассах лежат полосы-ускорители.'), ('Две гоночные трассы', 'Sector 3 Run гонит по вечернему оранжевому городу, на трассе 8 чекпоинтов. Ascend Only ведёт всё выше по фиолетовому ночному городу, там 13 чекпоинтов. Перед стартом нажимаешь R, то есть «готов», а счётчик (0/1) показывает, сколько игроков готово. Время идёт, и побеждает самый быстрый.'), ('На чём играть', 'Игра работает только на компьютере, на телефоне она не запустится. Управлять можно клавиатурой и мышью или геймпадом вроде Xbox.'), ('Песочница и Backrooms', 'Ещё из хаба можно уйти в песочницу, где просто отрабатываешь движение, и на карту Backrooms.'), ('Под капотом', 'Игру я сделал сам на движке Godot 4 и выложил на свой сервер. При первом запуске браузер загружает около 40 МБ, зато потом всё работает быстро.')] stat_html = ''.join(f'
  • {n}{E(l)}
  • ' for n, l in stats) body = ''.join(f'

    {E(t)}

    {E(p)}

    ' for t, p in secs) h = head(title, desc, 'projects-amans-world.jpg') h += f'''

    ← Проекты

    {E(title)}

    Паркур и гонки в неоновом городе. Запускается прямо в браузере.

    Играть

    Работает только на компьютере. Можно играть с геймпадом (Xbox).

    Игра, в которую можно зайти по ссылке. Без установки и без регистрации.

    {body}

    Кадры из игры

    ''' + TAIL os.makedirs(W + 'projects/amans-world', exist_ok=True) open(W + 'projects/amans-world/index.html', 'w').write(h) CARD2 = '''
    Иллюстрация: домашний сервер с тремя сервисами

    Хоумлаб

    Мой домашний сервер: файлы, игра и эксперименты. И Linux, на котором я всё делаю.

    Смотреть проект
    ''' # ---------- список проектов ---------- w0, h0 = dims('game-ascend') idx = head('Проекты', "Мои проекты: Aman's World, 3D-игра с паркуром и гонками, которая запускается прямо в браузере.", 'projects.jpg') idx += f'''

    Проекты

    Что я делаю

    Кроме учёбы и работы я собираю свои штуки. Выбери проект и загляни внутрь.

    Aman's World: старт трассы Ascend Only
    • 3D
    • в браузере
    • только на ПК
    • Godot 4

    Aman's World

    Паркур и гонки в неоновом городе. Запускается прямо в браузере, ничего ставить не надо.

    Смотреть проект
    ''' + CARD2 + '''
    ''' + TAIL open(W + 'projects/index.html', 'w').write(idx) # ---------- страница хоумлаба ---------- hl_title = 'Хоумлаб' hl_desc = 'Мой домашний сервер и Fedora Linux: Nextcloud, моя игра и эксперименты, на которых я учусь DevOps.' hl = head(hl_title, hl_desc, 'projects-homelab.jpg') hl += '''

    ← Проекты

    Хоумлаб

    Мой домашний сервер и Linux, на котором я всё делаю.

    Свой маленький интернет у меня дома.

    Что это

    Хоумлаб — это мой домашний сервер. Я работаю системным администратором и расту в сторону DevOps, поэтому держу дома место, где можно пробовать всё на настоящем железе.

    Дома можно спокойно пробовать, ломать и чинить. Так я учусь автоматизации и всему, что окружает современный сервер.

    Что на нём живёт

    Nextcloud

    Мои файлы и проекты лежат у меня, а не в чужом облаке.

    Aman's World

    Моя браузерная игра запускается с этого сервера.

    Смотреть игру →

    DuckDNS

    Бесплатные адреса, чтобы до домашнего сервера можно было достучаться снаружи.

    Этот сайт

    Он тоже переезжает сюда. Выкладку хочу автоматизировать через CI.

    И много чего ещё

    Список растёт: я постоянно что-то поднимаю и пробую.

    Fedora Linux

    На своём компьютере я пользуюсь Fedora Linux. В ней я учусь, работаю и делаю свои проекты, в том числе этот сайт.

    ''' + TAIL os.makedirs(W + 'projects/homelab', exist_ok=True) open(W + 'projects/homelab/index.html', 'w').write(hl) # sitemap: дописываем недостающие адреса sm_path = W + 'sitemap.xml' sm = open(sm_path).read() for u in ['/projects/', '/projects/amans-world/', '/projects/homelab/']: if f'https://me.ibrash.duckdns.org{u}' not in sm: sm = sm.replace('', f' https://me.ibrash.duckdns.org{u}\n') open(sm_path, 'w').write(sm) print('projects pages: ok')