Українська DIY-консоль Лілка. Повний гайд
Усім привіт. Нещодавно вийшов перший реліз української навчально-хакерської консолі Лілка на базі мікроконтролера ESP32-S3 від Андерсона та його друзів. І, як на мене, спричинив немалий ажіотаж в українській IT-спільноті. Я в повному захваті від цього проєкту. Тож у цій статті докладно ознайомлю вас із Лілкою та її можливостями.
Лілка (GitHub) це open source портативна консоль для розробки на базі мікроконтролера ESP32-S3-wroom1, зібрана з доступних компонентів. Лілка має власну друковану плату, операційну систему, готову бібліотеку для спрощення розробки, програми, ігри, демо, детальну документацію та чудову спільноту.
Де придбати Лілку
Орієнтовна вартість усіх деталей базової версії —
- Будь-хто може легко зібрати Лілку, придбавши комплект для збірки (на цей момент дві перші партії розкупили, коли буде наступна — невідомо).
- Якщо наборів Лілки немає у продажі можна написати на електрону пошту магазину Imrad ([email protected]) та зробити передзамовлення наступної партії.
- Можна запитати, чи немає у когось зайвої плати Лілки у загальній гілці Discord-спільноти. Проте компоненти потрібно купити самостійно. Або написати в гілку купівля-продаж.
- Плату Лілки також можна надрукувати самостійно за допомогою китайських сайтів JLC PCB, або PCBWay. Для цього потрібно завантажити усі .gbr, .drl та .gbrjob-файли звідси. Додати їх в ZIP-архів та завантажити на сайт. Налаштування друку плати можна подивитись тут. Виготовлення займає приблизно тиждень, дорога ще два тижні.
- Якщо хочеться швидше, можна надрукувати в Україні — pcb24.com.ua. Для цього також потрібно завантажити усі .gbr, .drl та .gbrjob-файли звідси та відправити їх на електрону пошту компанії.
- Отримати допомогу та поради стосовно самостійного замовлення плати Лілки можна у гілці Лілкаv2: розробка PCB.

Що може Лілка
Лілку вже називають українським Flipper Zero або ігровою консоллю. Для мене відео Aндерсона має вайб епохи компʼютерів ZX-Spectrum та Comadore. А Лілка асоціюється з часом, коли я намагався отримати максимум можливостей від свого Java-телефону. Для вас цей проєкт може мати свої асоціації.
Сама Лілка позиціонується як народна портативна навчально-хакерська консоль для технічної творчості. І як багатофункціональна зручна платформа для розробки, що дає набір інструментів, а ви можете перетворити її на те, на що забажаєте.
Лілка може бути використана як:
- Інструмент для самостійного вивчення мов програмування.
- Платформа для створення пет-проєкту.
- Пристрій для навчання Arduino та мікроконтролерам на уроках інформатики у школах та олімпіадах.
- Пристрій для моніторингу та вимірювань (температури, вологості, якості повітря тощо).
- Пристрій для вивчення схемотехніки, мереж, алгоритмів, автоматизацій, розробки інтелектуальних пристроїв в університетах.
- Контролер ЧПУ-верстатів (числове програмне керування, CNC).
- Пристрій для створення та запуску ігор.
- Бездротовий Bluetooth-геймпад.
- Інструмент пентестингу та хакерства (Wi-Fi jamming, bad USB тощо).
- SDR (RFID, NFC, 443).
- Пристрій для керування кліматом, освітленням, безпекою, інтеграції з MQTT, Rest, Post, ESPHome, Home Assistant тощо.
- Контролер для створення автономних дронів чи інших транспортних засобів.
- Пристрій для написання та прослуховування трекерної музики.
- Пристрій для демосцени.
- Пристрій для створення інтерактивних артінсталяцій або музичних інструментів.
І це не повний список того, чим може бути Лілка. Межа сфер використання залежить від вашої фантазії.
Для збірки Лілки потрібна друкована плата проєкту та набір електронних компонентів. Їх можна придбати у готовому наборі (на момент написання статті розкупили), запитати у Discord-спільноті, чи немає в кого зайвої плати, або надрукувати самостійно, завантаживши окремий Gerber-файл або весь каталог. В Україні можна роздрукувати тут: pcb24, у Китаї популярні сайти pcbway та jlcpcb. Мій знайомий друкував на jlc pcb 10 плат за 25 доларів.
Повний список усіх деталей можна знайти у документації проєкту Лілка. Мені дуже сподобалось, що є посилання, де купити в Україні на Ali та Mouser.
Для здешевлення вартості проєкту існує три різних комплектації.
- Базова — має дисплей, кнопки, п’єзо-динамік, слот micro sd, живиться від кабеля usb type-c.
- Базова + Батарея — має все те саме, що і базова комплектація, але дозволяє приєднати акумулятор, щоб консоль стала портативною, не привʼязаною до дротів. Можна використати будь-який акумулятор на 3.7 вольт. 18650, bl 5c, АА акумулятори або будь-який інший зручний для вас варіант.
- Базова + Звук — має все те саме, що й базова комплектація, але додатково ще й підсилювач MAX98357 та маленький динамік 8 Ohm 1 W або порт 3.5 для навушників.
Мене здивувало, чому потрібно обирати. Дуже круто, що увесь процес збірки покроково документований.

Для дисплея рекомендується спочатку припаяти розʼєм до плати, а потім вставити в нього дисплей. Це полегшить його монтаж і демонтаж за необхідності.
У переліку деталей не вказані стійки під дисплей. На сайті імрад вказані такі деталі стійки: TFF-M2.5×10 (6 шт), TFM-M2×10 (3 шт). Гвинти та гайки: Гайка M2 (3 шт), Гвинт M2.5×6 (12 шт), Гвинт M2×6 (3 шт). Під дисплей використовуються стійки гайки та гвинти М2 — ті, що в кількості 3 шт.
Також в переліку трохи оманливе посилання на кнопку SW11 на AliExpress. Вона не кутова, коли припаюється вимикач, дивиться не в ту сторону. На Ali є бічні кнопки, називаються вони MSK-05G2.0 або horizontal slide switch dip. Якщо ви плануєте друкувати корпус, перегляньте розділ про нього, бо пайка type c та дисплею має бути висока.
Як прошити Лілку
Після того, як пристрій зібрано, при під’єднанні його кабелем usb type-c (для передачі даних) він має відобразитись як USB-пристрій. Якщо так і є, можна його прошивати.
Якщо Лілка не відображається в компʼютері, можливо, щось залишилось недопаяне. Ви можете сфотографувати свою плату з обох боків та надіслати в загальну гілку Discord-спільноти. Там можна запитати, чому не працює, та отримати допомогу.
1. Завантажте та встановіть Visual Studio Code з офіційного сайту.
2. Перейдіть у розділ Extensions та встановіть плагін PlatformIO IDE.

3. Перейдіть у розділ Source Control (за необхідності завантажте Git) >> натисніть Clone Repository >> вставте посилання github.com/lilka-dev/keira (посилання оновлено 25.06.25) і завантажте код проєкту.

4. Перейдіть до File >> Open Folder >> lilka >> firmware >> kiera.
5. Встановіть драйвери СЗ210x USB to UART.
6. Під’єднайте Лілку кабелем usb type-c — type для передачі даних до компʼютера. Обовʼязково потрібен кабель type c — type a, оскільки usb 3.0 і кабелі type-c type-c не підтримуються.
У Linux можна перегянути підключені USB-пристрої за допомогою команд sudo dmesg -wH або lsusb.
У MacOS: sudo dmesg або ls /dev/tty.* /dev/cu.*ioreg -p IOUSB — виводить інформацію про всі підключені USB-пристрої, використовуючи дерево IORegistry.
У Windows — можна переглянути через диспетчер пристроїв.
7. Перейдіть у вкладку з мурахою PlatformIO. Розкрийте каталог v2 >> General >> Build та зачекайте збірки прошивки Лілки.

При успішній збірці отримаєте повідомлення success.

8. Тепер вам потрібно натиснути та утримувати кнопку SELECT і увімкнути Лілку. Після цього можна відпустити кнопку SELECT. Це переведе Лілку в режим завантаження (bootloader mode): в ньому ви можете завантажити в неї нову прошивку.
9. Розкрийте каталог v2 >> General >> Upload.

Keira OS
Keira (Операційна Система «Кіра») — це прошивка та операційна система, що базується на проєкті FreeRTOS і містить основні функції для демонстрації можливостей пристрою. Вона підтримує мультизадачність (одночасне виконання кількох завдань), мережу, запуск програм та прошивок прямо з SD-карти.
Примітка: зазвичай прошивкою називається будь-яка скомпільована програма, розроблена для мікроконтролера.
Усі програми Kiera можна поділити на дві категорії.
Вбудовані (постійні) програми — це програми, написані на C++, які компілюються в прошивку і зберігаються в пам’яті ESP. Вони є частиною основної системи та завжди доступні для виконання.
Зовнішні (завантажувані) програми зберігаються на microSD-картці. Їх можна запускати без змін у прошивці, що дозволяє додавати нові функції або пробувати нові програми без необхідності перепрошивати пристрій.
Написання програм на C++
Keira написана на C++, і вона містить ряд вбудованих програм. Всі програми є вбудованими, тобто вони мають бути частиною Keira. Це означає, що ви не можете додати свою програму до Keira, якщо не зміните код Keira і не перепрошиєте Лілку новим кодом.
Усі програми у Лілці зберігаються за адресою firmware/keira/src/apps. Найпростіша програма складається з двох файлів: myapp.h та myapp.cpp. Переглянути приклад програми можна тут.
- myapp.cpp — це файл, де знаходиться основний текст вашої програми. Тут описано, що саме вона робить.
- myapp.h — це файл, де записано список важливих частин програми (наприклад, назв функцій), які можуть знадобитися в інших місцях.
Основна програма Кіри — Launcher. Вона відображає меню програм, налаштувань, інформації й запускає програми. Щоб ваша програма з’явилася в меню, потрібно зареєструвати її в Launcher. Найпростіше — додати її до меню застосунків. Для цього знайдіть у файлі launcher.cpp код зі списком програм і додайте вашу програму до нього.
Бібліотека lilka
Хоча для програмування Лілки на C++ можна використовувати функції Arduino або ESP-IDF, бібліотека lilka забезпечує значно простіший і зручніший спосіб роботи з усіма ключовими компонентами пристрою. Вона дозволяє легко працювати з дисплеєм, кнопками, звуком, акумулятором, файлами, SPI та іншими функціями. Використання цієї бібліотеки не тільки спрощує написання коду, але й допомагає зменшити кількість рядків програми та ймовірність помилок.
Бібліотека вже доступна в репозиторії PlatformIO, тож її легко додати до вашого проєкту. Детальніше про усі функції бібліотеки можна прочитати тут.
Завантажувані програми та скрипти
Бінарні файли (.bin) — це скомпільовані програми, які містять машинний код, готовий до виконання мікроконтролером. Вони дозволяють запускати вторинні прошивки або програми без перепрошивки основної пам’яті пристрою.
Lua-скрипти (.lua) — це текстові файли з кодом, які виконуються безпосередньо на Лілці завдяки вбудованому інтерпретатору Lua версії 5.4.6. Їх не потрібно компілювати заздалегідь — достатньо скопіювати файл на SD-картку та запустити. Це економить час і дозволяє легко ділитися скриптами для використання на інших пристроях. Усі функції Lua можна зручно переглянути на цій сторінці.
mJS-скрипти (.js) — це мінімалістичний діалект JavaScript, що дозволяє запускати .js-файли безпосередньо з SD-картки на Лілці. Хоча це додає певної гнучкості, mJS має суттєві обмеження: відсутність класів, обмежений набір функцій і несумісність зі стандартними бібліотеками JavaScript.
Через ці недоліки команда проєкту рекомендує використовувати Lua, яка є більш функціональною, зручною та стабільною для роботи на Лілці.
Планується також портувати інтерпретатор MicroPython, але через ряд технічних складнощів ця ідея перебуває на ранній стадії реалізації.
Альтернативні прошивки Лілки
Проєкт Rustilka націлений на підтримку мови програмування Rust для платформ Lilka. Це мініпроєкт, який дозволяє розробникам використовувати Rust для створення прошивок для Lilka без залежності від офіційного SDK. Для використання Rust необхідно встановити спеціальну прошивку, що доступна на rust.lilka.dev.
Цей проєкт спрощує налаштування та розробку, а також надає бібліотеку для роботи з апаратними можливостями Lilka. Він орієнтований на розробників, які мають досвід роботи з Rust та базові знання в області embedded-систем. Ось гілка, присвячена Rustilka.
MeowUI — це альтернативна прошивка для Лілки v2 на основі фреймворку meowui. Призначена для створення гарних графічних інтерфейсів користувача на мікроконтролерах esp32, esp32-s3. MeowUI пропонує набір віджетів (меню, скролбар, клавіатура, перемикачі тощо), підтримує створення макетів вікон із контекстами, обробляє стани пінів, включає простий рушій для 2D-ігор і бібліотеки для роботи з периферією (i2s, spi). Віднедавна рушій дозволяє створювати багатокористувацькі ігри. Гілка присвячена MeowUI. Також нещодавно в проєкту зʼявилась гарна документація. У ній окремий розділ присвячений створенню гри з нуля.

Розʼєм розширення
Роз’єм розширення (англ. extension header) — дозволяє підключати до основної плати додаткові модулі, наприклад: мікрофон, реле, сервоприводи, GPS, гіроскоп, LTE, LoRa, ERLS тощо. Лілка має


Корпус для Лілки
Костянтин Поліщук розробив дуже гарний корпус для Лілки — LilCase. Відео з ним можна переглянути тут. Наразі це вже третя версія корпусу. Для використання важливо запаювати дисплей і роз’єм живлення, як на фото, і стійки для дисплея 8 мм.

Нижньої частини немає — вона кастомна під різні типи акумуляторів. Автор має намір з часом проєктувати нижню кришечку під різні типи акумуляторів: 18650 або ті, що є в одноразках 18350, 17350, 16350, 13400. Поредагувати корпус онлайн можна тут. Детальніше про розробку корпусу можна дізнатись у гілці Лілка v2: корпус.

Slim Case — це компактний корпус для Лілки. Його розміри 24×58×149 мм, і він повністю сумісний з комплектуючими від Imrad без потреби у додаткових деталях. Конструкція передбачає використання тонкого акумулятора PR-285083. Процес складання потребує ретельної підгонки та паяння до латунних стійок.

Low profile case — компактний корпус (22мм), простий у друку, без зайвих деталей, вміщує акумулятор BL-5C/BL-6C (аккумулятор nokia) або аналогічний до 53×34мм. Дисплей встановлюється на стійках M2×5мм врівень із кнопками, можна припаяти напряму або через нестандартний роз’єм. USB-модуль припаяний впритул, microSD — з відступом. Кнопки можна використовувати різної висоти, вимикач — будь-який повзунковий, конденсатор С1 — не вище 5мм.

Case for Lilka (Shashel) — Корпус для плати Лілка з припаяним дисплеєм на

Також ще одним варіантом захисту Лілки є використання задньої кришечки. Придбати її окремо від комплекту немає можливості, але можна надрукувати на 3D принтері, або замовити, вирізати з акрилу чи фанери.
Більше про корпуси, кнопки та акумулятори для Лілки можна подискутувати в гілці Лілка v2 корпус.
Форматування Micro SD для Лілки
Для роботи у Лілці Micro SD карта має бути форматована у форматі FAT32. Обʼєм памʼяті не важливий, обмежень немає. Користувачі запускали на Лілці картки на 128 GB. Форматувати картку можна за допомогою Windows, програми Rufus, Gparted або fdisk на Linux. Також можна форматувати за допомогою кнопкового мобільного телефону.
Проте у деяких користувачів періодично виникають проблеми з форматуванням Micro SD. Можливо, не усі картки підходять. Помилки з форматуванням досі трапляються, тому підтримку з налаштування карти можна знайти у гілці Проблема з SD-картами.
Також ви можете завантажити та записати на карту демонстраційні програми. Вони знаходяться в директорії firmware/keira/sdcard в репозиторії проєкту на GitHub.
Ігри Лілки
Лілка не позиціонується як ігрова консоль, проте це не заважає писати та грати ігри на ній.
NES — під Keira портований проєкт Nofrendo, що дозволяє запускати NES (денді) ігри у форматах .rom або .nes з SD-карти.

Doom — згідно з популярною фразою «If it has a processor, it can run Doom», Лілка також може запускати Doom. Для цього потрібно скомпілювати прошивку в каталозі firmware/doom, використовуючи shareware-версію Doom за цим посиланням.

Тамагочі — гра-симулятор догляду за віртуальним улюбленцем. Гравець має годувати, розважати, лікувати та підтримувати його щастя й здоров’я, щоб улюбленець був задоволений.

Стугна — стратегічний 2D-слешер про російсько-українську війну (відео). Головний персонаж — дівчина з прізвищем Стугна, що попадає в епіцентр бойових дій без навчання, але швидко вчиться та перетворюється на машину смерті. Розробку цієї гри наразі призупинено.

Астероїди — клон Asteroids 1979. У цій аркадній грі гравець керує космічним кораблем, який повинен знищувати астероїди та уникати зіткнень з ними.

Застосунки, тести та демо Лілки

Лілтрекер — відео. Триканальний трекер для Лілки. Підтримує різні ефекти, такі як тремоло, вібрато, арпеджіо. Також можна зберігати написані треки у форматі .lt та .waw на SD-карті. Звучання Лілтрекера нагадує суміш комодорівського SID та нінтендівського 2a03. ESP32 не має вбудованих звукових чіпів, увесь звук синтезується програмно на льоту. Андерсон написав декілька демонстраційних треків.

Modplayer — простий програвач MOD-файлів на базі бібліотеки ESP8266Audio. Не плутайте з попередньою програмою Лілтрекером. Mod-файли можна завантажити тут.

Погода — при підключенні до інтернету показує прогноз погоди за вказаними координатами.

BLE Геймпад — дозволяє використовувати Лілку у якості бездротового геймпада (джойстика).
GPS Tracker — модуль для відображення мапи з GPS-координатами.

Li’l Video — це конвертер відео для Lilka, що перетворює відеофайли у формат, сумісний із пристроєм. Він зменшує роздільну здатність і оптимізує дані для відтворення на Lilka, забезпечуючи ефективне кодування під обмеження консолі. Приклад роботи відеоплеєра.
Скріншот — при затиснені клавіші select + start робить знімок з екрана.
FTP-сервер — запускає FTP-сервер, доступний з локальної мережі. дозволяє бездротово надсилати файли на Лілку. примітка: наразі FTP сервер має особливість при передачі файлів в обидві сторони. Для передачі потрібно вимкнути та увімкнути FTP сервіс в меню Kiera, або повторно натиснути на кнопку швидке з’єднання у FileZilla.

Pastebin — завантаження файлів з сайту pastebin.
Runner — невеличка гра-раннер, де персонажу потрібно ухилятися від перешкод або виконувати інші завдання.
Live Lua дозволяє запускати Lua-код на Лілці без необхідності зберігати файли на SD-картці. Підключивши Лілку до комп’ютера через USB, ви можете тестувати програму без зайвих кроків з копіюванням файлів. Однак, якщо програма використовує додаткові ресурси (зображення, звуки), їх потрібно завантажити на SD-картку вручну. Для використання Live Lua потрібно встановити розширення Serial Monitor для Visual Studio Code.
Lua REPL (Read-Eval-Print Loop) — це інтерактивне середовище, яке дозволяє вам вводити та виконувати Lua-код на Лілці прямо з комп’ютера через USB-кабель, по одному рядку за раз. Це зручно для тестування функцій, вивчення API або відлагодження.
Щоб використовувати REPL, відкрийте меню «Розробка» -> «Lua REPL» на Лілці, потім у Visual Studio Code відкрийте «Serial Monitor», виберіть потрібний COM-порт і натисніть «Start Monitoring». Після цього ви можете вводити код, і результат з’явиться в консолі.
Демо:

Клавіатура — дозволяє відкрити екрану клавіатуру та набирати текст.

Тест SPI — демо-програма для роботи зі SPI на Lilka. Вона ініціалізує SPI-з’єднання, надсилає та приймає дані, демонструючи обмін із зовнішніми пристроями. Це корисно для тестування периферії та налагодження взаємодії через SPI.
I2C сканер — це демо-програма для сканування пристроїв на шині I2C. Вона ініціалізує з’єднання, перебирає всі можливі адреси та надсилає запити, перевіряючи, які пристрої відповідають. Виявлені адреси виводяться у зручному форматі, що дозволяє швидко визначити підключені I2C-девайси.
Лінії — демо-програма для виведення ліній на екрані Lilka. Вона малює лінії різної довжини, напрямку та кольору, демонструючи можливості графічного рендерингу. Це дозволяє перевірити коректність роботи відображення та протестувати продуктивність малювання графіки. Відео.
М’ячик — програма анімує м’ячик, що падає під дією гравітації та відскакує від країв екрана. Користувач може керувати її рухом стрілками, а кнопка «A» завершує роботу. На екрані відображається FPS, анімація оновлюється в реальному часі.
ЛілКаталог — каталог Lua програм, скриптів та прошивок для Лілки. Має зручний графічний інтерфейс та дозволяє в один клік заватнтажувати ігри програми та прошивки. Якщо ви створили гру ви можете звернутись до автора, або в загальну гілку Discord спільноти для включення вашої програми в каталог.
Як працюють Lua-програми на Лілці
Щоб зрозуміти, як написати програму, пропоную поглянути на приклад наявних програм. До прикладу, Сat — проста програма з котиком у стилі Bongo Cat, який б’є по столу, коли натискаєш кнопочки. Відео гри можна переглянути тут.

Спочатку ми завантажуємо чотири зображення котика. У цьому випадку зображення на весь екран. Воно має розмір 280×240. Підтримуються зображення у форматі .bmp, а з недавніх пір ще й .png. Також ми придумуємо змінні no, left, right, both і задаємо цим змінним зображення котика без лапок на столі, з лівою лапкою, з правою лапкою та з обома.
state = controller.get_state() записує значення натиснених кнопок.
function lilka.update(delta) — функція, що покадрово оновлює стан гри; delta — це час у секундах з моменту останнього виклику, що дозволяє створювати рухи й анімації, незалежні від швидкості програми. За ідеальних обставин delta дорівнює приблизно 0.0333 секунди.
Після знову викликається state = controller.get_state(), щоб дізнатись, чи натиснена кнопка перед кожним відмальованим кадром гри.
Якщо була натиснута кнопка вліво (буква D), або кнопка вправо (кнопка B), відтворити писк п’єзо динаміка (англ. buzzer).
if state.b.just_pressed or state.d.just_pressed then buzzer.play(40, 100) end
При натиснені кнопки вгору (A) програма завершує роботу:
-- Завершуємо програму при натисканні кнопки "A" if state.a.pressed then util.exit() end end
Функція lilka.draw() малює зображення на екрані залежно від стану кнопок:
- Спочатку відображає базове зображення no.
- Якщо натиснута кнопка D, малює зображення left.
- Якщо натиснута кнопка B, малює зображення right.
- Якщо одночасно натиснуті B і D, малює зображення both.
- Якщо натиснута кнопка C, відтворює звук, коротко показує both, а потім повертається до no.
-- Завантажуємо зображення
no = resources.load_image("no.bmp")
left = resources.load_image("left.bmp")
right = resources.load_image("right.bmp")
both = resources.load_image("both.bmp")
-- Створюємо змінну в яку буде записуватись стан кнопок
state = controller.get_state()
function lilka.update(delta)
-- Оновлюємо змінну з станом кнопок
state = controller.get_state()
-- Якщо була натиснута кнопка - відтворюємо звук
if state.b.just_pressed or state.d.just_pressed then
buzzer.play(40, 100)
end
-- Завершуємо програму при натисканні кнопки "A"
if state.a.pressed then
util.exit()
end
end
function lilka.draw()
-- Перевіряємо стан кнопок і відмальовуємо відповідне зображення
display.draw_image(no, 0, 0)
if state.d.pressed then
display.draw_image(left, 0, 0)
end
if state.b.pressed then
display.draw_image(right, 0, 0)
end
if state.b.pressed and state.d.pressed then
display.draw_image(both, 0, 0)
end
if state.c.pressed then
util.sleep(0.1)
display.draw_image(both, 0, 0)
buzzer.play(40, 100)
display.queue_draw()
util.sleep(0.1)
display.draw_image(no, 0, 0)
end
end
Розглянемо приклад ще однієї простої програми — гру Кубики. Гра розрахована на двох людей без участі комп’ютера. Також цікаво, що гра була написана за допомогою штучного інтелекту claude.ai на базі гри Астероїди.

Розберемо, як працює lilkacubes.lua.
Ігровий процес:
- Старт. Гра починається з екрану привітання. Гравець натискає A, щоб перейти до гри.
- Кидок кубиків. Гравці кидають кубики одночасно, натиснувши A. Кубики анімуються (змінюють значення) протягом короткого часу.
Результат. Після завершення кидка визначається переможець:
- Якщо значення кубиків однакові, це нічия.
- Якщо значення різні, переможець визначається за функцією get_winner.
Новий раунд: гравці можуть натиснути A для повторного раунду або B для виходу з гри.
Спочатку оголошуються кольори у форматі RGB, які будуть використані для відображення кубиків та фону.
ROLL_SOUND — звук кидання кубиків — це набір
Клас Dice моделює кубик із певними характеристиками, як-от координати, розмір, колір, поточне значення та статус (кидається чи ні). Кубик також може «пам’ятати», чи є він переможцем.
Метод Dice:new(o) створює новий об’єкт кубика, зберігаючи передані параметри або використовуючи значення за замовчуванням.
Метод Dice:roll(). Цей метод запускає «кидання» кубика. Якщо кубик не кидається, він змінює свій статус на «кидається» (is_rolling = true), зберігає час початку кидка (roll_start_time) і скидає статус переможця (is_winner = false).
Метод Dice:update() оновлює стан кубика: якщо триває кидок, встановлює випадкове значення від 1 до 6; якщо час закінчився — фіксує результат і зупиняє кидок.
Метод Dice:draw() відповідає за візуалізацію кубика. Якщо кубик виграв, додається виділення. На екран виводиться зображення, що відповідає його значенню.
Логіка гри. У грі є два стани: привітання (HELLO) і сам процес гри (IN_GAME).
Функція setup_game(). Ця функція налаштовує гру. Завантажуються необхідні зображення, а також створюються два кубики — по одному для кожного гравця. Вони розташовані симетрично на екрані.
Функція lilka.update(delta). Це головна функція, яка виконується під час роботи гри. Її завдання залежить від поточного стану гри:
- У стані HELLO вона чекає, поки гравець натисне кнопку A, щоб почати гру.
- У стані IN_GAME вона оновлює стан кубиків, перевіряє, чи натиснув гравець A для нового кидка або B для виходу.
- Коли обидва кубики закінчили кидатися, визначається переможець за допомогою функції get_winner. Переможцю присвоюється статус is_winner.
Функція lilka.draw(). Ця функція малює все, що відбувається в грі. У стані HELLO вона відображає екран привітання. У стані IN_GAME вона малює кубики, імена гравців, а також результат раунду після завершення кидка. Додатково виводяться інструкції з кнопками.
--[[
Гра "Кубики"
Гра з вибором кількості кубиків для консолі lilka.dev
]]
WHITE = display.color565(255, 255, 255)
BLACK = display.color565(0, 0, 0)
YELLOW = display.color565(255, 255, 0)
RED = display.color565(255, 0, 0)
-------------------------------------------------------------------------------
-- Завантаження ресурсів
-------------------------------------------------------------------------------
-- Звук кидання кубиків
ROLL_SOUND = {
{440, 8},
{523, 8},
{659, 8},
{784, 8},
{880, 8},
}
-- Звук вибору
SELECT_SOUND = {
{660, 8},
{880, 8},
}
-------------------------------------------------------------------------------
-- Клас кубика
-------------------------------------------------------------------------------
Dice = {
x = 0,
y = 0,
size = 80,
color = WHITE,
current_value = 1,
is_rolling = false,
roll_start_time = 0,
roll_duration = 1,
}
function Dice:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Dice:roll()
if not self.is_rolling then
self.is_rolling = true
self.roll_start_time = util.time()
end
end
function Dice:update()
if self.is_rolling then
local time_elapsed = util.time() - self.roll_start_time
if time_elapsed < self.roll_duration then
-- Під час анімації швидко змінюємо значення
self.current_value = math.floor(math.random() * 6) + 1
else
-- Зупиняємо анімацію і встановлюємо фінальне значення
self.current_value = math.floor(math.random() * 6) + 1
self.is_rolling = false
end
end
end
function Dice:draw()
-- Малюємо квадрат кубика
display.fill_rect(self.x - self.size/2, self.y - self.size/2, self.size, self.size, self.color)
-- Малюємо крапки відповідно до значення
local dot_size = 8
local padding = 18
if self.current_value == 1 then
-- Центральна крапка
display.fill_circle(self.x, self.y, dot_size, BLACK)
elseif self.current_value == 2 then
-- Дві крапки по діагоналі
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 3 then
-- Три крапки по діагоналі
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x, self.y, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 4 then
-- Чотири крапки по кутах
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 5 then
-- П'ять крапок
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x, self.y, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 6 then
-- Шість крапок
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
end
end
-------------------------------------------------------------------------------
-- Стани гри
-------------------------------------------------------------------------------
STATES = {
HELLO = 0, -- Початковий екран
SELECT = 1, -- Вибір кількості кубиків
IN_GAME = 2, -- Гра
}
-------------------------------------------------------------------------------
-- Змінні стану гри
-------------------------------------------------------------------------------
local game_state = STATES.HELLO
local selected_dice_count = 1 -- Вибрана кількість кубиків
local dice1 = nil
local dice2 = nil
-------------------------------------------------------------------------------
-- Головні цикли гри
-------------------------------------------------------------------------------
function setup_dice(count)
if count == 1 then
dice1 = Dice:new({
x = display.width/2,
y = display.height/2 - 50,
color = WHITE
})
dice2 = nil
else
dice1 = Dice:new({
x = display.width/2 - 70,
y = display.height/2 - 50,
color = WHITE
})
dice2 = Dice:new({
x = display.width/2 + 70,
y = display.height/2 - 50,
color = YELLOW
})
end
end
function lilka.update(delta)
local state = controller.get_state()
if game_state == STATES.HELLO then
if state.start.just_pressed then
game_state = STATES.SELECT
end
elseif game_state == STATES.SELECT then
-- Вибір кількості кубиків
if state.left.just_pressed or state.right.just_pressed then
selected_dice_count = selected_dice_count == 1 and 2 or 1
buzzer.play_melody(SELECT_SOUND, 400)
end
if state.start.just_pressed then
setup_dice(selected_dice_count)
game_state = STATES.IN_GAME
end
else
-- Оновлюємо стан кубиків
dice1:update()
if dice2 then
dice2:update()
end
-- Якщо натиснута кнопка A і кубики не крутяться
if state.a.just_pressed then
local can_roll = not dice1.is_rolling
if dice2 then
can_roll = can_roll and not dice2.is_rolling
end
if can_roll then
dice1:roll()
if dice2 then
dice2:roll()
end
buzzer.play_melody(ROLL_SOUND, 400)
end
end
-- Повернення до вибору кількості кубиків
if state.b.just_pressed then
game_state = STATES.SELECT
end
-- Вихід з гри
if state.start.just_pressed then
util.exit()
end
end
end
function lilka.draw()
if game_state == STATES.HELLO then
display.fill_screen(BLACK)
display.set_cursor(display.width/2 - 50, display.height/2 - 20)
display.print("ГРА КУБИКИ")
display.set_cursor(display.width/2 - 80, display.height/2 + 20)
display.print("Натисніть START")
elseif game_state == STATES.SELECT then
display.fill_screen(BLACK)
display.set_cursor(display.width/2 - 100, 30)
display.print("Оберіть кількість")
display.set_cursor(display.width/2 - 45, 50)
display.print("кубиків:")
-- Малюємо варіанти вибору
local y = display.height/2
-- Перший кубик
if selected_dice_count == 1 then
display.fill_rect(display.width/2 - 80, y - 15, 30, 30, RED)
else
display.fill_rect(display.width/2 - 80, y - 15, 30, 30, WHITE)
end
display.set_cursor(display.width/2 - 70, y + 30)
display.print("1")
-- Два кубики
if selected_dice_count == 2 then
display.fill_rect(display.width/2 + 50, y - 15, 30, 30, RED)
else
display.fill_rect(display.width/2 + 50, y - 15, 30, 30, WHITE)
end
display.set_cursor(display.width/2 + 60, y + 30)
display.print("2")
display.set_cursor(10, display.height - 60)
display.print("Ліво/Право - для вибору")
display.set_cursor(10, display.height - 40)
display.print("START - для підтвердження")
else
display.fill_screen(BLACK)
-- Малюємо кубики
dice1:draw()
if dice2 then
dice2:draw()
end
local instructions_y = display.height - 80
display.set_cursor(10, instructions_y)
if not dice1.is_rolling and (not dice2 or not dice2.is_rolling) then
local sum = dice1.current_value
if dice2 then
sum = sum + dice2.current_value
end
display.print("Сума: " .. sum)
else
display.print("Сума: --")
end
display.set_cursor(10, instructions_y + 20)
display.print("A - кинути кубики")
display.set_cursor(10, instructions_y + 40)
display.print("B - змінити кількість")
display.set_cursor(10, instructions_y + 60)
display.print("START - вихід")
end
end
Як працюють С++ програми на Лілці
Розглянемо, як працює Pastebin — це невелика програма для завантаження текстових файлів з вебсервісу Pastebin. Вона дозволяє користувачам вводити код посилання на файл, вказувати ім’я файлу, а потім завантажувати й зберігати вказаний текстовий файл на локальний носій.
Спочатку у програмі підключаються три бібліотеки.
- #include «pastebinApp.h» — це окремий файл, інструкція для програми, яка описує її структуру. Детальніше тут.
- #include <HTTPClient.h> — бібліотека для роботи з інтернетом. Вона дозволяє відправляти запити (GET, POST) і отримувати відповіді від будь-яких вебсайтів або серверів.
- #include <lilka/config.h> — налаштування платформи Lilka для взаємодії з кнопками, екраном і файлами.
Коли програма створюється, викликається спеціальний блок коду, який називається конструктором. Він відповідає за початкове налаштування програми, задає її основні параметри.
pastebinApp::pastebinApp() : App("pastebin loader") {
}
Тут ми бачимо конструктор для нашої програми pastebinApp. Він каже: «Я створюю програму з назвою pastebin loader».
Уявіть, що це як наклеїти етикетку на коробку, де написано: «Це програма для завантаження файлів із Pastebin». Ця назва допоможе програмі зрозуміти, хто вона, а нам — легше її ідентифікувати, якщо буде багато таких коробок-програм.
Функція run() є точкою входу для програми. Коли програма починає виконання, система автоматично викликає цей метод. Це правило закладено в базовому класі App, який визначає загальну структуру роботи застосунків.
У функції run() є лише один рядок — uiLoop();. Ця команда повідомляє програмі: «Запусти меню і покажи його користувачеві».
void pastebinApp::run() {
uiLoop();
}
WiFiClientSecure client;
HTTPClient http;
String link_code = "";
String filename = "";
String path_pastebin_folder = "/pastebin";
String pastebin_url = "https://pastebin.com/raw/";
У цьому фрагменті коду ми оголошуємо змінні, щоб підготувати програму до роботи з інтернетом, файловою системою та обробкою даних. Кожна змінна має свою роль і допомагає програмі правильно виконувати свою роботу.
Перша змінна — WiFiClientSecure client. Тут ми створюємо «клієнта», який буде працювати з інтернетом. Оскільки програма має використовувати захищений доступ до сайтів (https), ми створюємо саме безпечного клієнта. Це дозволяє програмі безпечно з’єднуватися з сайтами, такими як Pastebin.
Друга змінна — HTTPClient http. Це так званий «посередник». Він допомагає програмі відправляти запити до Pastebin, щоб отримати необхідні файли. Уявіть це як людину, яка телефонує до магазину і запитує, чи є у них потрібний товар. Ось цей «посередник» і робить запити, щоб програма могла отримати те, що їй потрібно.
Далі йдуть змінні String link_code та String filename. Це просто порожні рядки або «коробки», де програма зберігатиме інформацію. У link_code ми будемо зберігати код посилання на файл, а у filename — ім’я файлу, яке ми хочемо завантажити.
Змінна String path_pastebin_folder вказує на папку, куди програма збереже файли, що будуть завантажені з Pastebin. Це як адреса на комп’ютері, де будуть зберігатися всі файли, щоб їх можна було легко знайти й відкрити.
І остання змінна — String pastebin_url. Тут зберігається адреса сайту Pastebin, куди програма буде надсилати запити для отримання файлів. Це схоже на те, коли ви вводите адресу вебсайту в браузері, щоб перейти на потрібну сторінку.
#include "pastebinApp.h"
#include <HTTPClient.h>
#include <lilka/config.h>
pastebinApp::pastebinApp() : App("pastebin loader") {
}
void pastebinApp::run() {
uiLoop();
}
void pastebinApp::uiLoop() {
WiFiClientSecure client;
HTTPClient http;
String link_code = "";
String filename = "";
String path_pastebin_folder = "/pastebin";
String pastebin_url = "https://pastebin.com/raw/";
while (1) {
lilka::Menu settingsMenu("Pastebin");
settingsMenu.addActivationButton(lilka::Button::B);
settingsMenu.addItem("Код", 0, 0, link_code);
settingsMenu.addItem("Назва", 0, 0, filename);
settingsMenu.addItem("Завантажити", 0, 0, "");
while (!settingsMenu.isFinished()) {
settingsMenu.update();
settingsMenu.draw(canvas);
queueDraw();
}
if (settingsMenu.getButton() == lilka::Button::B) {
} else {
if (settingsMenu.getCursor() == 0) {
lilka::InputDialog inputDialog(String("Введіть код"));
inputDialog.setValue(link_code);
while (!inputDialog.isFinished()) {
inputDialog.update();
inputDialog.draw(canvas);
queueDraw();
}
link_code = inputDialog.getValue();
} else if (settingsMenu.getCursor() == 1) {
lilka::InputDialog inputDialog(String("Введіть назву"));
inputDialog.setValue(filename);
while (!inputDialog.isFinished()) {
inputDialog.update();
inputDialog.draw(canvas);
queueDraw();
}
filename = inputDialog.getValue();
} else if (settingsMenu.getCursor() == 2) {
if (filename.length() == 0) {
filename = link_code;
continue;
}
FRESULT res = f_stat(path_pastebin_folder.c_str(), nullptr);
if (res == FR_NO_FILE) {
res = f_mkdir(path_pastebin_folder.c_str());
if (res != FR_OK) {
lilka::Alert alert("pastebin", "Помилка створення директорії");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Помилка створення директорії %d\n", res);
}
} else if (res != FR_OK) {
lilka::Alert alert("pastebin", "Помилка створення директорії");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
} else {
String url = pastebin_url + link_code;
String fullPath = path_pastebin_folder + "/" + filename;
client.setInsecure();
http.begin(client, url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
// Open file for writing
FILE* file = fopen((lilka::fileutils.getSDRoot() + fullPath).c_str(), FILE_WRITE);
if (!file) {
lilka::Alert alert("pastebin", "Помилка відкривання файлу");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Помилка відкривання файлу");
break;
}
fprintf(file, "%s", http.getString().c_str());
fclose(file);
delay(10);
lilka::Alert alert("pastebin", "Файл завантажено, та збережено");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Файл завантажено, та збережено");
break;
} else {
lilka::Alert alert("pastebin", "HTTP GET failed, error: " + http.errorToString(httpCode));
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
}
}
}
}
}
}
При створені власних програм можна скористатись допомогою спільноти у гілці програмування. Також я використовував ChatGPT та Claude. Але перед тим, як писати якусь програму, я згодовую їм подібну існуючу програму або приклад програм з документації. Написаний код можна завантажити на Github, а посиланням поділитись в гілці Користувацькі програми та скрипти.
Цікаві проєкти, які можна реалізувати
Існує дуже багато цікавих проєктів, які можна реалізувати на Лілці. Приклади, що я опишу — не дорожня карта проєкту Лілка, а просто мої власні побажання. Можливо, вони надихнуть вас на якийсь власний порт чи програму.
Допомогти з портуванням Python на Лілку. MicroPython — це легковагова реалізація Python, оптимізована для мікроконтролерів. Вона дозволяє писати зрозумілий і гнучкий код для вбудованих систем, підтримує роботу з периферією (GPIO, I2C, SPI, UART), файлову систему та багатозадачність.
Проєкт наразі потребує допомоги, оскільки портуванням займається лише одна людина. Якщо ви хочете долучитися, напишіть у відповідну гілку.
Допомогти з розробкою та розумінням принципів роботи .mjs-скриптів. На цей час документація проєкту не має достатньо інформації.
Магазин застосунків. Оскільки .lua та .mjs-скрипти можна запускати відразу з файлів, тоді можна створити магазин застосунків, що дозволить завантажувати такі скрипти. Це можна зробити з нуля або надихнутись існуючою програмою для Лілки Pastebin.
Pico8 — це сучасний ігровий рушій, що імітує обмеження ігрових систем
Такі обмеження також надають іграм PICO-8 особливий вигляд і відчуття. Існує порт Pico-8 на ESP32. Проте для його портування потрібно подолати певні технічні труднощі.
Micro Hydra — це простий лаунчер застосунків на базі MicroPython з деякими функціями, схожими на ОС. Основною функцією MicroHydra є забезпечення інтерфейсу для легкого перемикання між застосунками MicroPython. Гілка, присвячена MicroHydra.
Wi-Fi Duck — це пристрій, який працює як бездротова клавіатура. Він створює Wi-Fi точку доступу, через яку можна віддалено вводити текст або команди на підключеному пристрої.
Модуль та програма для зчитування та записування та емулювання RFID-ключів. Щоб можна було відкрити двері підʼїзду Лілкою. Щось на кшталт пристрою Chameleon.
Я цікавлюсь розумним будинком. І можна портувати MQTT-клієнт, сервер. Або програму, схожу на homeThings, або ESPWA. Універсальний пульт керування інфрачервоними пристроями (кондиціонером, телевізором, вентилятором, зволожувачем тощо). Пристрій для пошуку IBeacon, запуск україномовного голосового асистенту Voice Assistant.
Meshtastic — це проєкт для створення бездротової мережі без інтернету з використанням LoRa. Лілку можна використовувати як ноду для обміну повідомленнями на великі відстані. До речі, в Ураїні є окрема спільнота, присвячена цьому проєкту. Також можна приєднатись до обговорення Meshtastic на Лілці в окремій гілці спільноти.
Можливості Лілки величезні — вона відкриває простір для технічної творчості, дозволяючи кожному створювати щось унікальне. Вона може бути як простою, так і складною, залежно від ваших ідей та бажань. Лілка — це не просто набір деталей, а надзвичайно крутий український проєкт із власною ідеєю, баченням та спільнотою.
Я вірю в його успіх і думаю, що з часом він стане ще більш популярним і затребуваним серед ентузіастів, розробників та дослідників. Лілка надихає на експерименти, відкриває нові можливості та об’єднує людей, які захоплюються інженерією та програмуванням. Це не просто платформа — це ідея, що розширює межі творчості та технічного розвитку. Я впевнений, що попереду ще багато цікавих відкриттів.
А який ваш досвід взаємодії з Лілкою? Напишіть в коментарях.
45 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів