Warehouse Management System
WIP
(2026)
C#
ASP.NET Core
EF Core
ASP.NET Identity
Scrutor
Serilog
FluentValidation
FluentAssertions
xUnit v3
Testcontainers
PostgreSQL
Docker
Azure
TypeScript
Vue 3
Pinia
TanStack Query
VeeValidate
Zod
PrimeVue
Tailwind CSS
Chart.js
ApexCharts

🔴 Live demo → https://wms.damian-z.uk/

System zarządzania magazynem obejmujący pełny cykl przyjęcie → składowanie → wydanie: dane podstawowe produktów i lokalizacji, śledzenie partii i dat ważności, wielowymiarową pojemność lokalizacji, planowane odkładanie i kompletację, niezmienialny rejestr ruchów magazynowych oraz pulpit analityczny.

Backend to Web API na ASP.NET Core (.NET 10), zbudowane w czystej, warstwowej architekturze. Frontend to aplikacja jednostronicowa (SPA) we Vue 3. Dane trzymane są w PostgreSQL.


Dane podstawowe
  • Produkty — SKU, opis, kategoria, wymagana strefa temperaturowa, masa i objętość jednostkowa (używane do obliczania obciążenia lokalizacji).
  • Kategorie produktów — hierarchiczne drzewo (rodzic/dziecko), z przeglądaniem i zmianą kolejności.
  • Lokalizacje — kod i ustrukturyzowany adres, typ (Storage / Quarantine / Returns), strefa temperaturowa (Ambient / Chilled / Frozen), pojemność w trzech wymiarach (jednostki / masa / objętość, każdy ograniczany niezależnie, a decyduje najbardziej restrykcyjny), reguły mieszania SKU i partii, stany aktywny i zablokowany oraz preferowane produkty dla danej lokalizacji.
  • Partie — numer partii i data ważności na potrzeby śledzenia partii i terminów.
Inwentarz
  • Ilości na stanie / zarezerwowane / dostępne śledzone według lokalizacji + produktu + partii.
  • Ręczne korekty zapasów (ze ścieżką audytu) i wyszukiwanie dostępności.
Przyjęcie — Stock-In (przyjmowanie i odkładanie)
  • Cykl życia: Draft → Putaway → Completed, z możliwością anulowania ze stanu Draft lub Putaway.
  • Rozmieszczenie planuje automatycznie planer odkładania. Dostępne strategie: PreferredLocation, ConsolidateSameLot, ConsolidateSameSku, Proximity, NearestEmpty, NearestAvailable.
  • W stanie Draft rozmieszczenia można ręcznie nadpisać albo zaplanować ponownie.
  • Pojemność jest rezerwowana w momencie rozpoczęcia odkładania (z blokadą wiersza), żeby równoległe przyjęcia nie przekroczyły dostępności lokalizacji.
  • Odkładanie potwierdza się pozycja po pozycji, a anulowanie zwalnia rezerwacje i wycofuje już rozmieszczony zapas.
Wydanie — Stock-Out (kompletacja)
  • Cykl życia: Draft → Picking → Completed, z możliwością anulowania.
  • Przydziały kompletacji w lokalizacjach i partiach planuje planer kompletacji. Strategie: FEFO (najpierw najkrótszy termin ważności), FIFO, LIFO, LeastQuantity oraz nadpisanie Manual.
  • W stanie Draft można edytować lokalizacje kompletacji i planować ponownie. Kompletację potwierdza się pozycja po pozycji, a anulowanie zwraca zarezerwowany zapas.
Przesunięcia magazynowe i rejestr ruchów
  • Przemieszczanie zapasu między lokalizacjami.
  • Każda zmiana ilości (przyjęcie, wydanie, przesunięcie, korekta oraz ich anulowania) zapisuje niezmienialny rekord StockMovement, tworzony jako efekt uboczny przez zdarzenia domenowe. Daje to kompletną, tylko dopisywalną ścieżkę audytu.
Pulpit i administracja
  • Pulpit z zakładkami: przegląd, przyjęcia, wydania, zapasy i pojemność (wykresy przez Chart.js / ApexCharts).
  • Uwierzytelnianie i role: logowanie JWT z trzema rolami — Admin, Manager, Worker. Wszystkie punkty końcowe domyślnie wymagają uwierzytelnienia, a operacje zapisu roli Admin lub Manager.
  • Panel administracyjny: zarządzanie użytkownikami (tworzenie, lista, usuwanie, zmiana haseł) oraz narzędzia bazodanowe (zasilenie danymi demonstracyjnymi / wyczyszczenie).
Testy
  • Ponad 220 przypadków testowych (xUnit v3 + FluentAssertions), podzielonych na testy jednostkowe domeny i testy integracyjne warstwy aplikacji.
  • Jednostkowe (bez bazy danych) — maszyny stanów przyjęć i wydań, arytmetyka ilości na stanie / zarezerwowanych / dostępnych, reguły akceptacji lokalizacji (pojemność, strefa temperaturowa, mieszanie SKU i partii) oraz planery odkładania i kompletacji wraz ze strategiami.
  • Integracyjne — handlery komend i zapytań uruchamiane na prawdziwym PostgreSQL w kontenerze (Testcontainers), każdy test na wyczyszczonej bazie: pełne przebiegi przyjęć, wydań, przesunięć i korekt, rezerwacje pojemności, wycofywanie zmian przy anulowaniu, zapis rejestru ruchów przez zdarzenia domenowe, przypadki błędne oraz wyścig dwóch przyjęć o tę samą lokalizację.
Job Offer Aggregator
(2026)
Kod źródłowy
Python
FastAPI
SQLAlchemy
Pydantic
Playwright
BeautifulSoup
SQLite
TypeScript
Vue 3
PrimeVue
Docker
OVHCloud

Agregator ogłoszeń o pracę w Belgii. Zestaw scraperów zbiera oferty z pięciu portali, zapisuje je do wspólnej bazy z deduplikacją i pokazuje w jednym panelu z filtrami. Przebiegi startują automatycznie według harmonogramu ustawianego z poziomu aplikacji albo ręcznie, jednym przyciskiem.

Projekt zrobiony na zlecenie firmy i wdrożony na serwerze klienta (w chmurze OVHCloud) jako zestaw kontenerów Docker.

Backend to FastAPI (SQLAlchemy + Pydantic, SQLite, migracje Alembic). Frontend to aplikacja jednostronicowa (SPA) we Vue 3 z TypeScriptem i PrimeVue.


Źródła ofert
  • Jooble — oficjalne API.
  • VDAB (belgijski urząd pracy) — pobieranie przez Playwright.
  • poloniusz.pl, axintor.be, impact.be — parsowanie listingów HTML (requests + BeautifulSoup).
  • Każde źródło ma własny scraper o wspólnym interfejsie. Scrapery nie dotykają bazy: zwracają zwalidowane oferty, a zapisem i deduplikacją zajmuje się runner.
  • Deduplikacja działa po kluczu źródło:identyfikator z indeksem UNIQUE, więc powtórne przebiegi dopisują tylko nowe ogłoszenia.
  • Błąd jednego scrapera nie przerywa pozostałych, a pełny traceback trafia do historii uruchomień.
Panel
  • Oferty — tabela z paginacją po stronie serwera, sortowaniem i filtrami (źródło, stanowisko, lokalizacja, firma, fragment tytułu, data publikacji); nowe ogłoszenia są oznaczane.
  • Historia — przebiegi scraperów pogrupowane we wsady, ze statusem, liczbą znalezionych i nowych ofert oraz podglądem błędu.
  • Stanowiska — słownik wyszukiwanych stanowisk (nazwa PL/NL/FR + adresy listingów dla poszczególnych portali), edytowalny z poziomu panelu.
  • Harmonogram — godziny automatycznych przebiegów dodawane i włączane bez zmian w konfiguracji serwera.
Uruchamianie i wdrożenie
  • Osobna usługa harmonogramu nie scrapuje sama. O wyznaczonej godzinie wywołuje API, dzięki czemu automat i przycisk w panelu dzielą jedną blokadę i przebiegi nigdy się nie nakładają.
  • Logowanie hasłem, tokeny JWT. API nie jest wystawione na zewnątrz: ruch obsługuje nginx, który serwuje panel i przekazuje żądania do backendu.
  • Całość startuje jednym poleceniem docker compose up. Baza i słownik stanowisk żyją w wolumenie, więc przebudowa obrazów ich nie narusza, a schemat bazy tworzą migracje wykonywane przy starcie.
Portfolio Template
(2026)
TypeScript
Angular
RxJS
SCSS
Bootstrap
GLightbox
JSON
Markdown

Szablon strony portfolio zbudowany na frameworku Angular. Aplikacja nie ma backendu: cała treść pochodzi z plików w katalogu public/assets, więc stworzenie własnego portfolio sprowadza się do ich podmiany, bez grzebania w kodzie źródłowym. Całość budowana jest jako strona statyczna (SSG), czyli każda podstrona w każdej wersji językowej trafia do gotowego HTML-a w czasie budowania i może być serwowana z CDN-u, bez żadnego serwera aplikacyjnego.

Dane strukturalne (profil, doświadczenie, projekty, technologie, ustawienia strony) opisane są w plikach JSON, a dłuższe opisy miejsc pracy i projektów w plikach Markdown, osobnych dla każdego języka.


Sekcje portfolio
  • O mnie
    • profil — awatar, odnośnik do GitHuba, tekst nagłówkowy i krótki opis
    • stos technologiczny — języki programowania, frameworki i biblioteki, narzędzia i środowisko
    • edukacja — uczelnia, kierunek, lata studiowania
    • certyfikaty — nazwa certyfikatu, wydawca, data wydania
    • języki obce
  • Doświadczenie — lista miejsc pracy: logo firmy z odnośnikiem, lata współpracy (z obsługą wariantu „teraz"), stos technologiczny i opis obowiązków.
  • Projekty — lista projektów: rok realizacji, stos technologiczny, opis, galeria multimediów i odnośnik do repozytorium. Projekty o zamkniętym kodzie oznaczane są kłódką, a te w trakcie realizacji znacznikiem WIP.
  • Kontakt — adres e-mail.
Funkcje
  • Prerendering (SSG) — tryb outputMode: static generuje statyczny HTML dla każdej trasy i każdego języka. Wyszukiwarki i podglądy linków dostają gotową treść zamiast pustego <app-root>, a w przeglądarce strona hydratuje się do pełnej aplikacji SPA. Dane JSON i opisy Markdown pobrane w czasie budowania są osadzane w wynikowym HTML-u, więc przeglądarka nie pobiera ich drugi raz.
  • Wielojęzyczność — język jest częścią adresu (/pl, /en), a każda wersja językowa ma własny, prerenderowany HTML. Teksty interfejsu pochodzą z plików tłumaczeń (@ngx-translate), a lista dostępnych języków z konfiguracji strony. Przy wejściu na adres główny język dobierany jest z wcześniejszego wyboru użytkownika, a gdy go nie ma, z preferencji przeglądarki. Przełącznik w pasku nawigacji zmienia język bez przeładowania i bez opuszczania podstrony.
  • SEO — każda podstrona ma odnośnik canonical i komplet odnośników hreflang do swoich wersji językowych (razem z x-default), a atrybut lang w znaczniku <html> odpowiada wyświetlanej treści. Strona pobierania CV jest wyłączona z indeksowania.
  • Tryb jasny i ciemny — motyw oparty na zmiennych CSS, przełączany klasą na elemencie body, z płynnym przejściem kolorów. Wybór jest zapamiętywany i nakładany jeszcze przed pierwszym malowaniem strony, więc przy wejściu nie widać mignięcia drugiego motywu.
  • Galeria multimediów — obsługa obrazów i wideo. Typ pliku rozpoznawany jest po rozszerzeniu, miniatury przewija się w poziomie, a podgląd otwiera się w oknie lightbox. GLightbox ładowany jest dopiero przy pierwszej galerii, jako jedna wspólna instancja dla całej strony.
  • Generator miniatur — skrypt tools/generate-thumbnails.py (Pillow, OpenCV) tworzy zoptymalizowane miniatury JPEG dla obrazów oraz z pierwszej klatki wideo, według konwencji nazw thumb_<nazwa>.jpg.
  • Zwijane opisy — długie opisy projektów są domyślnie skracane, z gradientowym wygaszeniem i przyciskiem „Pokaż więcej". Przycisk pojawia się tylko wtedy, gdy treść przekracza zadaną wysokość, a wysokość przeliczana jest ponownie przy zmianie rozmiaru okna i po zmianie języka.
  • Znaczniki technologii — każdej technologii można przypisać kolor w pliku technologies.json, a etykiety bez wpisu dostają kolor domyślny.
  • Animacje przy przewijaniu — sekcje i karty pojawiają się kaskadowo, wyzwalane przez IntersectionObserver; każdy element animuje się tylko raz.
  • RWD — układ oparty na siatce Bootstrapa 5, z rozwijanym menu na urządzeniach mobilnych.
M2Chat
WIP
(2025)
Kod źródłowy
C#
.NET MAUI
Blazor Hybrid
MudBlazor
Bootstrap
ASP.NET Core
EF Core
FluentValidation
PostgreSQL

Aplikacja desktopowa do monitorowania czatu w grze Metin2. Czat odczytywany jest wyłącznie z obrazu: okno gry przechwytywane jest co 100 ms, a tekst rozpoznaje własny algorytm OCR działający na pikselach. Aplikacja nie ingeruje w proces gry, czyli nie modyfikuje plików klienta, nie czyta pamięci i nie wstrzykuje kodu.

Klient to Blazor Hybrid (.NET MAUI, .NET 10) z interfejsem opartym o MudBlazor. Przechwytywanie ekranu jest zaimplementowane dla Windows (P/Invoke user32/gdi32: GetDC + BitBlt na obszarze klienta wskazanego okna). Backendem jest ASP.NET Core Web API z bazą PostgreSQL.


Funkcje programu
  • Wybór okna gry (dowolne okno wskazane przez użytkownika) i ciągły odczyt czatu
  • Podział na wiadomości graczy i komunikaty systemowe (rozpoznawane po kolorze tekstu)
  • Grupowanie duplikatów w listę unikalnych wiadomości ze znacznikami pierwszego i ostatniego wystąpienia
  • Wyciąganie linków do przedmiotów ([nazwa]) jako osobnych obiektów w wiadomości
  • Tracker bossów: pozostała liczba sztuk na każdym kanale + odliczanie do respawnu
  • Feed pokonanych bossów (czas, kanał, gracz, boss)
  • Rankingi zabójstw bossów wg gracza, z konfigurowalnym oknem czasowym
  • Statystyki aktywności czatu (unikalne wiadomości na minutę)
  • Konfiguracje pod różne serwery gry, z możliwością udostępniania innym użytkownikom
Rozpoznawanie znaków

Zamiast gotowego silnika OCR użyłem dopasowania wzorców bitowych, które dobrze pasuje do stałej, rastrowej czcionki gry:

  • Budowa charsetu: bitmapa czcionki (charset.bmp + lista znaków w charset.txt) skanowana jest kolumnami. Każdy glif kodowany jest jako maska UInt128 i zapisywany w słowniku w obie strony (maska <-> znak).
  • Odczyt linii: dla każdej z 20 linii czatu (od dołu w górę) skanowane są kolejne kolumny pikseli. Piksel w jednym z kolorów zdefiniowanych w konfiguracji ustawia bit w buforze UInt128 (|= z przesunięciem bitowym), a pusta kolumna kończy znak i wyzwala odpytanie słownika.
  • Sklejone glify: gdy pełna maska nie ma dopasowania, bufor jest kolejno przycinany maską bitową od najdłuższego wariantu, dopasowany fragment zdejmowany przesunięciem bitowym, a reszta trafia do następnej iteracji.
  • Spacje wykrywane są na podstawie liczby następujących po sobie pustych kolumn.
  • Nieznane znaki zapisywane są do logu razem z wycinkiem bitmapy, co pozwala łatwo uzupełnić charset.
  • Kolor znaku niesie informację: odróżnia komunikat systemowy od wiadomości gracza i wskazuje typ przedmiotu w linku (ekwipunek / pozostałe).
Konfiguracje serwerów gry

Serwery prywatne różnią się czcionką, kolorami czatu, formatem komunikatów, liczbą kanałów, godziną startu serwera oraz listą bossów (nazwa, liczba sztuk, czas respawnu).

  • Wzorce komunikatów zapisywane są ze znacznikami {Channel}, {Player}, {Boss} i kompilowane do wyrażeń regularnych z grupami nazwanymi, z których wyciągane są dane o zabójstwie.
  • Czasy respawnu wyliczane są z godziny startu serwera i interwału bossa. Licznik na trackerze resetuje się automatycznie 3 minuty przed respawnem (z zabezpieczeniem przed podwójnym resetem).
Usługa Web API

Minimal API na ASP.NET Core (.NET 10), EF Core + PostgreSQL, walidacja przez FluentValidation.

  • Uwierzytelnianie użytkowników za pomocą ASP.NET Core Identity (uwierzytelnianie ciasteczkowe).
  • Wersjonowanie konfiguracji: każdy zapis tworzy nową wersję (numer, JSON, hash) powiązaną z wpisem nadrzędnym.
  • Użytkownik może udostępnić konfigurację publicznie, a inni mogą ją zaimportować (liczba importów jest zliczana).
  • Import zapamiętuje numer wersji. Po aktualizacji przez autora importujący dostają informację o nowej wersji i mogą ją zsynchronizować w dowolnym momencie.
  • Autor może zmienić widoczność konfiguracji lub ją usunąć (soft-delete). Użytkownicy, którzy ją wcześniej zaimportowali, zachowują do niej dostęp.
Planowane funkcje
  • Filtrowanie wiadomości
  • Powiadomienia o zdarzeniach na czacie
  • Wyciszanie graczy
  • Odczytywanie bonusów przedmiotów z czatu
  • Rozbudowa panelu statystyk
  • Streaming czatu
Calyx Engine
(2024)
C++
OpenGL
GLM
ImGui
EnTT
Box2D
GameNetworkingSockets
Premake5

Silnik do gier 2D napisany w C++ z graficznym edytorem: renderer OpenGL, Entity Component System (EnTT), symulacja fizyki (Box2D), natywne skryptowanie i własny moduł sieciowy oparty o GameNetworkingSockets.

Rdzeń architektury bazuje na otwartej wersji silnika Hazel, ale wprowadziłem w nim sporo zmian i własnych rozwiązań. Największe z nich to moduł sieciowy, napisany w całości przeze mnie. To projekt hobbystyczny na wczesnym etapie, nieprzeznaczony do produkcji komercyjnych gier; obecnie nie jest rozwijany.

Moduł sieciowy powstał na potrzeby mojej pracy magisterskiej. Badałem w niej, jak opóźnienia sieciowe wpływają na jakość symulacji rozgrywki, a dokładniej: jak różne wartości parametrów predykcji po stronie klienta przekładają się na częstotliwość i skalę desynchronizacji.

Projekt konfigurowany jest przez Premake5 (Windows, Visual Studio) i budowany w trzech konfiguracjach: Debug, Release (z edytorem) oraz Distribution, czyli wersji bez edytora, która pełni rolę runtime'u gry.

Silnik powstawał wcześniej pod nazwą Proton2D. Cała historia gita znajduje się w starym repozytorium https://github.com/Damian-Zuk/proton2d.


Edytor (ImGui)
  • Viewport, hierarchia sceny, inspektor obiektów, przeglądarka treści (tekstury, sceny, prefabrykaty), panel ustawień i panel statystyk.
  • Sterowanie symulacją: start / pauza / stop, ze zrzutem stanu sceny i przywróceniem go po zatrzymaniu.
  • Wiele instancji gry w jednym procesie, każda z własnym viewportem, menedżerem scen i menedżerem sieci. Można więc uruchomić serwer i kilku klientów obok siebie i testować rozgrywkę multiplayer bez wychodzenia z edytora (instancja klienta może startować automatycznie po uruchomieniu serwera).
Renderer 2D (OpenGL / Glad)
  • Batch renderer: domyślnie do 10 000 quadów w jednej partii, liczba slotów tekstur odczytywana ze sterownika (do 32), osobne potoki dla quadów, okręgów, linii i tekstu.
  • Sprite'y i atlasy tekstur (TextureAtlas, Sprite), skalowanie techniką 9-slice, animacje oparte na atlasie.
  • Rendering tekstu z atlasów MSDF (msdf-atlas-gen), kamera ortograficzna z zoomem, framebuffery, licznik wywołań rysowania.
Scena, ECS i zasoby
  • Scene opakowuje rejestr EnTT. Entity to para identyfikatora i wskaźnika na scenę, a każdy obiekt dostaje UUID.
  • Hierarchia rodzic-dziecko zrobiona jest komponentem relacji (wskaźniki na pierwsze dziecko i rodzeństwo), z rozdzieleniem pozycji lokalnej i światowej.
  • Sceny i prefabrykaty są serializowane do formatu JSON. Prefabrykat to szablon obiektu współdzielony przez sceny, używany też przy spawnie po sieci.
  • Fizyka aktualizowana jest w stałym kroku czasowym (akumulator), niezależnie od liczby klatek na sekundę.
Skryptowanie natywne w C++
  • Hierarchia klas AppScript > GameScript > EntityScript (logika aplikacji, trybu gry i pojedynczego obiektu), fabryka tworząca skrypty po nazwie klasy i makro rejestrujące.
  • Pola skryptów rejestrowane makrem REGISTER_FIELD pojawiają się w inspektorze i są serializowane razem ze sceną. Obsługiwane typy to wartości skalarne, wektory, tekst i dowolne dane trywialnie kopiowalne.
  • Pole oznaczone REPLICATED_FIELD jest automatycznie synchronizowane po sieci, opcjonalnie z funkcją wywoływaną po odebraniu nowej wartości.
  • Nie ma hot-reloadingu; docelowo planowałem silnik skryptowy C#.
Fizyka (Box2D)

Ciała sztywne, kolidery prostokątne i okrągłe z parametrami materiału i filtrami kolizji, sensory, nasłuchiwanie kontaktów z wywołaniem zwrotnym w skrypcie oraz złączenia obrotowe (przypięcie obiektu do rodzica).

Moduł sieciowy

Architektura klient-serwer z w pełni autorytatywnym serwerem, w trybie listen server, dedykowanym lub klienta. Cała komunikacja idzie własnym binarnym strumieniem (nagłówek zapisywany jest wstecz po wyliczeniu rozmiaru ładunku), a każda odczytana wartość jest sprawdzana względem pozostałego rozmiaru bufora. Niespójny pakiet kończy się rozłączeniem klienta, a nie odczytem poza zakres.

  • Protokół: handshake przenosi wersję protokołu silnika i gry, a niezgodność którejkolwiek zrywa połączenie z konkretnym kodem błędu. Typy wiadomości obejmują spawn i despawn obiektów, replikację, potwierdzenia numerów sekwencyjnych oraz wiadomości niestandardowe wysyłane ze skryptów (nie ma RPC).
  • Replikacja różnicowa: serwer pamięta ostatnio wysłany stan osobno dla każdego klienta i wysyła tylko to, co się zmieniło. Składowe transformacji oznaczane są flagami bitowymi (PositionX, PositionY, ScaleX, ScaleY, Rotation), a zmiany pól skryptów wykrywane sumą kontrolną CRC32 liczoną per klient. Obiekt, w którym nic się nie zmieniło, jest wycofywany ze strumienia.
  • Cull distance: obiekty poza zadanym promieniem od gracza nie są replikowane, a ich ciała fizyczne po stronie klienta są usypiane.
  • Dołączanie w trakcie gry: serwer prowadzi rejestr wszystkich spawnów i despawnów, więc nowy klient dostaje pełny stan sceny i wymuszoną pełną replikację. Obiekt wysyłany jest jako identyfikator prefabrykatu, a jeśli nim nie jest, to jako pełny opis JSON.
  • Tickrate replikacji (domyślnie 64 Hz) jest niezależny od liczby klatek, opcjonalnie można go zsynchronizować z tickiem fizyki.
Synchronizacja transformacji

Metodę synchronizacji ustawia się osobno dla każdego obiektu w inspektorze, razem z progami rekoncyliacji i teleportacji oraz czasem trwania korekty:

  • Interpolacja: ruch odtwarzany jest pomiędzy dwoma ostatnimi stanami z serwera.
  • Ekstrapolacja: pozycja przewidywana jest z prędkości, która jest mieszanką prędkości lokalnego ciała Box2D i prędkości wynikającej z dwóch ostatnich stanów z serwera, wysuniętej do przodu o zmierzone opóźnienie.
  • Predykcja po stronie klienta (opis metody): zamiast powtarzać kolejkę wejść, klient buforuje różnice transformacji z każdego ticku razem z numerem sekwencyjnym i wysyła ten numer do serwera. Serwer odsyła ostatni przetworzony numer w wiadomości replikacyjnej, a klient odrzuca potwierdzone różnice i nakłada pozostałe na stan z serwera. Tak powstaje pozycja przewidywana.
  • Rekoncyliacja: korekta błędu rozłożona jest w czasie (interpolacja do stanu przewidywanego, z kompensacją różnic narastających w trakcie korekty) zamiast skokowego przeskoku. Dopiero przekroczenie progu teleportacji przenosi obiekt natychmiast.
  • Dynamiczna ekstrapolacja: mój własny pomysł. Obiekt synchronizowany interpolacją przełącza się na ekstrapolację w chwili kontaktu fizycznego z obiektem predykowanym (sterowanym lokalnie) i wraca do interpolacji, gdy rozbieżność wobec stanu serwera spadnie poniżej progu. Ogranicza to widoczne rozjeżdżanie się obiektów przy zderzeniach z postacią gracza.
Narzędzia diagnostyczne i badawcze
  • Symulacja opóźnienia sieciowego ustawiana z panelu edytora (sztuczne opóźnienie pakietów przychodzących i wychodzących).
  • Zapis statystyk sieciowych do pliku: ping, przepustowość w obu kierunkach i średnia liczba replikowanych obiektów na wiadomość, próbkowane w stałych odstępach, z nagłówkiem opisującym scenę i tickrate. Te dane posłużyły do pomiarów w pracy magisterskiej.
  • Podgląd statystyk w edytorze: liczba obiektów (w tym skryptowanych i sieciowych), czas klatki, ping i przepustowość dla każdego klienta.
  • Makra asercji, instrumentation profiler (zapis przebiegu do formatu śledzenia) i logger (spdlog).
CVE Report Generator
(2024)
Python
HTML
JavaScript
CSS
NVDLib
JSON
Nmap XML

Narzędzie CLI w Pythonie, które generuje raport podatności (CVE) dla urządzeń IoT. Na wejściu przyjmuje ręcznie przygotowaną listę urządzeń w formacie JSON (model, wersja firmware'u, adres MAC) i/lub skany sieci zrobione Nmapem (XML). Dla każdego znalezionego elementu oprogramowania, czyli firmware'u, usług na otwartych portach i systemu operacyjnego, skrypt odpytuje bazę NIST NVD i dołącza znalezione podatności do raportu wyjściowego.

Projekt powstał w ramach przedmiotu „Bezpieczeństwo systemów IoT" na Politechnice Rzeszowskiej.


Jak to działa
  1. Wczytanie listy urządzeń (devices.json). Dane wpisane ręcznie są podstawą raportu i mają pierwszeństwo przed danymi ze skanów.
  2. Parsowanie skanów Nmap (XML, nmap -A -oX). Hosty dopasowywane są do urządzeń z listy po adresie MAC, a nieznane trafiają do raportu jako nowe wpisy. Ze skanu wyciągane są: adres IPv4, producent (z MAC), porty w stanie open razem z usługami oraz wynik detekcji systemu operacyjnego.
  3. Ustalenie identyfikatorów CPE. Dla elementów bez CPE wykonywane jest wyszukiwanie po słowach kluczowych (model + wersja, produkt + wersja, nazwa OS + rodzina + wersja). Identyfikatory w starszym formacie CPE 2.2 są zamieniane na CPE 2.3.
  4. Pobranie podatności zapytaniami po cpeName. Jeśli dla urządzenia nie udało się ustalić CPE, wykonywane jest wyszukiwanie po słowach kluczowych, a CPE uzupełniane jest na podstawie pierwszej pasującej podatności.
  5. Zapis raportu do pliku JSON o strukturze takiej samej jak wejściowa, rozszerzonej o sekcję vulnerabilities.
Co zawiera raport

Raport to tablica urządzeń, a każdy wpis zawiera:

  • Identyfikację urządzenia: model, opis, adres MAC, adres IPv4, producenta, wersję firmware'u i przypisany CPE. Własne pola z pliku wejściowego są przenoszone do raportu i wyświetlane przez przeglądarkę raportów.
  • Otwarte porty i usługi: numer portu, nazwę usługi, produkt, wersję i CPE.
  • System operacyjny: nazwę, informację o dopasowaniu przez Nmapa z procentową trafnością oraz listę klas OS (producent, rodzina, wersja, trafność, CPE).
  • Podatności w trzech kategoriach: firmware, service (klucz: produkt wersja) i os (klucz: rodzina wersja). Każda kategoria ma łączną liczbę znalezionych CVE i ich listę, a pojedynczy wpis: identyfikator CVE, datę publikacji, opis, użytą metrykę CVSS, wynik punktowy i poziom istotności (LOW / MEDIUM / HIGH / CRITICAL).
Konfiguracja (config.ini)
  • Ścieżki: plik wyjściowy, lista urządzeń oraz katalog lub pojedynczy plik ze skanami Nmap.
  • Klucz API do NIST NVD, który skraca odstęp między zapytaniami z 6 s do 0,6 s.
  • Lista systemów operacyjnych pomijanych przy odpytywaniu bazy (np. linux, android). Dla nich liczba trafień jest tak duża, że wynik i tak nie byłby użyteczny, więc w raporcie zapisywana jest tylko adnotacja o pominięciu.
Przeglądarka raportów

Statyczna strona report-viewer.html, bez backendu i zależności. Plik JSON wczytuje się metodą przeciągnij i upuść. Urządzenia pokazywane są jako rozwijane panele, poziomy istotności mają swoje kolory, a identyfikatory CVE i CPE linkują do odpowiednich stron NVD. Ten sam widok obsługuje też plik wejściowy z listą urządzeń, bo ma on identyczną strukturę.

Ograniczenia
  • Dopasowanie CVE opiera się na konkretnym CPE. Zakresy wersji podatnego oprogramowania (versionEndIncluding) nie są analizowane, więc podatność opisana zakresem może nie zostać wykryta.
  • Trafność wyszukiwania po słowach kluczowych zależy od tego, jak producent nazywa swoje produkty w bazie NVD.
Ansible Project
(2024)
Ansible
Ansible Semaphore
Ubuntu
AWS
PHP
MySQL
Apache

Projekt studencki, w którym Ansible automatyzuje konfigurację maszyn wirtualnych, instalację pakietów systemowych i wdrożenie prostej aplikacji LAMP.

Całość postawiłem w chmurze AWS: na osobnych instancjach działa węzeł zarządzający oraz zarządzane serwery webowe. Playbooki wykonywane są przez Ansible Semaphore, czyli graficzny interfejs dla Ansible. Pozwala on uruchamiać zadania z przeglądarki, ustawiać harmonogramy, trzymać klucze dostępowe w jednym miejscu i przeglądać historię wykonania razem z logami.

Dużą częścią projektu było udokumentowanie całej konfiguracji: od przygotowania instancji i dostępu SSH, przez instalację i konfigurację Semaphore, po opis działania poszczególnych ról. W repozytorium znajdują się tylko pliki Ansible: playbooki, role i inwentarz.


Role Ansible
  • create_admin_user – zarządzanie kontami administratorów.
    • Tworzy użytkowników na podstawie listy admin (z określonym UID i opisem).
    • Konfiguruje dostęp SSH, dodając klucze publiczne do authorized_keys.
    • Nadaje uprawnienia sudo wpisem w pliku /etc/sudoers.
  • update_system – aktualizacja systemu operacyjnego.
    • Odświeża cache pakietów APT.
    • Aktualizuje wszystkie zainstalowane pakiety do najnowszych dostępnych wersji.
  • setup_apache_and_php – instalacja i konfiguracja środowiska webowego.
    • Instaluje serwer HTTP Apache.
    • Instaluje PHP razem z wymaganymi modułami.
    • Włącza moduł PHP w Apache.
    • Zwiększa memory_limit w konfiguracji PHP do 256 MB.
    • Restartuje Apache, żeby zmiany zaczęły obowiązywać.
  • setup_mysql_db – instalacja i konfiguracja serwera bazy danych MySQL.
    • Instaluje wymagane zależności (pip, PyMySQL).
    • Instaluje i uruchamia serwer MySQL.
    • Tworzy użytkownika MySQL z pełnymi uprawnieniami.
    • Tworzy bazę danych aplikacji.
    • Tworzy tabelę posts, jeśli jeszcze nie istnieje.
    • Wstawia do niej przykładowe dane na podstawie szablonu SQL.
  • deploy_website – wdrożenie aplikacji webowej razem z jej konfiguracją.
    • Pobiera najnowszą wersję aplikacji z repozytorium Git.
    • Synchronizuje kod do katalogu /var/www, podmieniając pliki tylko wtedy, gdy wykryto zmiany.
    • Tworzy katalog konfiguracyjny aplikacji i ładuje zmienne bazy danych.
    • Generuje plik config.php z szablonu Jinja2, wstawiając do niego dane dostępowe do bazy i parametry strony.
Poll Wizard
(2023)
Python
FastAPI
SQLAlchemy
Pydantic
PostgreSQL
Redis
TypeScript
React
Bootstrap
Docker

Aplikacja internetowa do tworzenia prostych ankiet jednopytaniowych i głosowania, wzorowana na platformie StrawPoll.com.

Backend to REST API na FastAPI (SQLAlchemy ORM, PostgreSQL, walidacja przez Pydantic) z Redisem, który trzyma stan sesji i liczniki limitów zapytań. Frontend to aplikacja jednostronicowa w React + TypeScript (Vite, Bootstrap). Całość uruchamiana jest przez Docker Compose, a dokumentacja API generowana automatycznie (Swagger).


Funkcje aplikacji
  • Konta użytkowników: rejestracja z reCAPTCHA, logowanie i uwierzytelnianie tokenami JWT.
  • Tworzenie ankiet: pytanie i od 2 do 16 unikalnych odpowiedzi (walidacja po stronie API).
  • Głosowanie: jeden głos na ankietę dla zalogowanego użytkownika, z natychmiastowym podglądem wyników (liczba głosów i udział procentowy na paskach).
  • Profil użytkownika z listą własnych ankiet; autor może usunąć swoją ankietę.
  • Rate limiting: limity zapytań liczone w Redisie, osobno dla całych grup punktów końcowych i dla operacji wrażliwych (np. 3 rejestracje na minutę, 5 nowych ankiet na minutę).
Własny moduł sesji JWT oparty na Redisie

Zamiast prostego wystawiania tokenów napisałem osobny moduł zarządzania sesjami (fastapi_jwt_redis), który integruje się z systemem zależności FastAPI:

  • Para tokenów: krótko żyjący token dostępowy i token odświeżający, każdy z własnym identyfikatorem jti, czasem ważności i czasem rozpoczęcia ważności.
  • Rotacja tokenów odświeżających: każde odświeżenie sesji wydaje nową parę tokenów, a stary token odświeżający jest oznaczany w Redisie jako zużyty.
  • Wykrywanie ponownego użycia: próba użycia tokenu odświeżającego drugi raz (typowy sygnał, że token został skradziony) traktowana jest jako naruszenie. Żądanie jest odrzucane, a cała sesja unieważniana.
  • Kaskadowe unieważnianie: Redis przechowuje powiązania między tokenami z kolejnych rotacji, więc wylogowanie albo wykryte nadużycie wpisuje na czarną listę cały łańcuch tokenów danej sesji, a nie tylko token okazany w żądaniu.
  • Weryfikacja w punkcie wejścia: własna klasa zabezpieczająca sprawdza schemat nagłówka, podpis, czas ważności, zgodność typu tokenu (token odświeżający nie przejdzie tam, gdzie wymagany jest dostępowy) i obecność identyfikatora na czarnej liście.
  • Wpisy w Redisie żyją tyle, ile token odświeżający, więc magazyn czyści się sam.
Bezpieczeństwo i walidacja
  • Hasła haszowane algorytmem Argon2, z wymogiem złożoności (długość, wielkie i małe litery, cyfra, znak specjalny) sprawdzanym przy rejestracji.
  • Walidacja danych wejściowych schematami Pydantic: ograniczenia długości pytania i odpowiedzi, wymóg unikalności odpowiedzi, kontrola formatu nazwy użytkownika oraz zajętości adresu e-mail i loginu.
  • Autoryzacja operacji na zasobach: ankietę może usunąć tylko jej autor, a ponowny głos w tej samej ankiecie jest odrzucany po stronie API.
  • Wrażliwe parametry (sekret JWT, czasy ważności, dane baz) konfigurowane są wyłącznie zmiennymi środowiskowymi, a endpointy deweloperskie rejestrowane tylko w trybie debug.
Frontend

Aplikacja React z routingiem po stronie klienta i odświeżaniem sesji w tle (token dostępowy wymieniany jest, zanim wygaśnie). Pojedyncza ankieta ma własny adres, więc można się nią dzielić, a lista głosów oddanych przez zalogowanego użytkownika pobierana jest jednym zbiorczym zapytaniem dla wszystkich widocznych ankiet.

Czego brakuje
  • paginacji,
  • GUID-ów dla linków do ankiet zamiast ID,
  • niepublicznych ankiet (dostęp tylko z linkiem),
  • głosowania bez konta,
  • limitu głosów na adres IP.
Auto Parts
(2021)
Python
Django
PostgreSQL
Bootstrap
JavaScript
Leaflet
Heroku
PWA
Agile Scrum

Sklep internetowy z częściami samochodowymi. Aplikację robiliśmy w pięcioosobowej grupie, w metodyce Scrum i we współpracy z grupą z wydziału zarządzania, w ramach zajęć projektowych z przedmiotu „Inżynieria Oprogramowania i Baz Danych".

Sklep zbudowany jest w Django (Python) z bazą PostgreSQL, szablonami po stronie serwera i interfejsem na Bootstrapie. Aplikacja jest przygotowana pod rynek polski, działa jako PWA i została wdrożona na Heroku (gunicorn, whitenoise).

Moja część pracy to katalog produktów i warstwa prezentacji: filtrowanie i sortowanie listy produktów, podział asortymentu na sekcje i kategorie razem z paskiem nawigacji, paginacja, obsługa cen promocyjnych z widokiem wyprzedaży i sekcją ofert specjalnych, a także przeniesienie zapytań widoku głównego do osobnej warstwy serwisowej. Po stronie interfejsu przygotowałem m.in. responsywny pasek nawigacji, pływający koszyk oraz szablony strony produktu, listy produktów, logowania, rejestracji i podsumowania zamówienia.


Katalog produktów
  • Kategorie są hierarchiczne: kategoria może mieć kategorię nadrzędną, przeglądanie działu pokazuje produkty ze wszystkich jego podkategorii, a pasek nawigacji przełącza się na podkategorie wybranego działu.
  • Filtrowanie i sortowanie (django-filter) po nazwie, cenie i promocji, z paginacją po 12 pozycji. Sortowanie po cenie bierze pod uwagę cenę efektywną: wyrażenie SQL wybiera cenę promocyjną, jeśli istnieje, a w przeciwnym razie podstawową.
  • Wyszukiwarka przeszukuje nazwy i opisy produktów.
  • Promocje mają osobny widok wyprzedaży i sekcję ofert specjalnych na stronie głównej. Przy produkcie widać cenę przed i po obniżce oraz kwotę oszczędności.
  • Parametry produktów trzymane są w polu JSON, więc różne kategorie części można opisywać różnymi zestawami atrybutów bez zmiany schematu bazy.
Porównywarka produktów

Oparta na open-source'owym module dla Django (lista porównywanych pozycji trzymana w sesji, dodawanie i usuwanie przez AJAX), który rozszerzyłem o zestawienie parametrów z pola JSON: atrybuty wszystkich porównywanych produktów łączone są w jeden zbiór i układane w tabelę wiersz po wierszu, a brakujące wartości dostają myślnik. Porównywać można produkty z jednej kategorii.

Placówki i geolokalizacja
  • Punkty sprzedaży mają adres, telefon, zdjęcie i współrzędne geograficzne. Można je wyszukiwać po mieście, adresie, kodzie pocztowym i numerze telefonu.
  • Wyszukiwanie najbliższej placówki: przeglądarka pobiera pozycję użytkownika (Geolocation API), a backend znajduje najbliższy punkt zapytaniem SQL ze wzorem haversine, bez potrzeby użycia GeoDjango. Wynik wraca jako JSON i trafia na mapę OpenStreetMap (Leaflet) obok znacznika pozycji użytkownika.
  • Stany magazynowe prowadzone są osobno dla każdej placówki (ilość produktu w danym punkcie).
Konta i zamówienia
  • Rejestracja z aktywacją mailem: konto pozostaje nieaktywne, dopóki użytkownik nie kliknie linku z jednorazowym tokenem. Login i hasło sprawdzają własne walidatory (minimalna długość, cyfra, wielka litera) z komunikatami po polsku.
  • Dwa typy użytkowników: klient indywidualny i warsztat. Typ konta zmienia dostępne metody płatności.
  • Koszykiem jest zamówienie w stanie „nieopłacone", z dodawaniem, zmniejszaniem ilości i usuwaniem pozycji oraz przeliczaniem wartości z uwzględnieniem promocji.
  • Przy składaniu zamówienia wypełnia się formularz adresu dostawy z wyborem kraju i metody płatności (BLIK, karta, Apple Pay, Google Pay, PayPal).
  • Przy płatności fakturą (dostępnej dla warsztatów) generowana jest faktura PDF z pozycjami zamówienia i stawką VAT, zapisywana przy zamówieniu i dostępna z historii zakupów.
  • Profil użytkownika pozwala edytować dane kontaktowe i przeglądać złożone zamówienia.
Pozostałe rozwiązania
  • Obrazy produktów, zdjęcia placówek i pliki faktur trzymane są w bazie danych (osobne tabele na zawartość, nazwę i typ MIME) zamiast na dysku. To wygodne przy wdrożeniu na platformie, która nie zachowuje plików między restartami.
  • Panel administracyjny Django do zarządzania katalogiem, placówkami i zamówieniami, z polskimi nazwami modeli.
  • Polecenie zarządzające, które generuje dane testowe (kategorie, kilkaset produktów, placówki) na potrzeby developmentu.
Push The Box
(2020)
C++
SFML
CMake

Gra logiczna wzorowana na klasycznym Sokobanie: celem jest wepchnięcie wszystkich skrzynek na wyznaczone pola. Napisana w C++17 z biblioteką SFML, bez gotowego silnika. Pętlę gry, system stanów, encje, animacje i kontrolki interfejsu zaimplementowałem od zera. Interfejs gry jest w języku polskim.

Projekt budowany jest przez CMake i przeznaczony na platformę Win32 (x86), zgodnie z dołączoną wersją SFML.


Szkielet aplikacji
  • Pętla gry z pomiarem czasu klatki (delta time) i limitem FPS. Przy starcie wczytywana jest konfiguracja okna i lista zasobów.
  • Stos stanów: menu główne, opcje, wybór poziomu, rozgrywka i edytor to osobne stany. Aktualizowany i rysowany jest tylko stan ze szczytu stosu, a jego zamknięcie zwalnia wszystkie należące do niego encje.
  • Warstwy encji: przynależność do maksymalnie 32 warstw zapisana jest jako maska bitowa, co pozwala pokazywać i ukrywać całe grupy elementów w obrębie jednego stanu (np. w menu edytora: widok główny / nowy poziom / wczytywanie).
  • Encje mają hierarchię rodzic-dziecko, przypinanie pozycji do innej encji, animację ze spritesheetu i płynny ruch do zadanego punktu.
  • Układ jest niezależny od rozdzielczości: pozycje i rozmiary opisywane są we współrzędnych znormalizowanych względem modelu 1920×1080 i przeliczane przez skalę okna.
  • Menedżer zasobów ładuje tekstury i czcionki na podstawie zewnętrznego pliku listy zasobów (nazwa pliku, identyfikator, wygładzanie).
  • Własne kontrolki UI: przycisk, checkbox, lista rozwijana, pole tekstowe (limit znaków, tryb tylko cyfr, migający kursor) i etykieta tekstowa.
Rozgrywka
  • Ruch w czterech kierunkach z animacją postaci. Pchnięcie skrzynki dochodzi do skutku tylko wtedy, gdy pole za nią jest wolne, a kolizje sprawdzane są na siatce identyfikatorów kafli, nie na sprite'ach.
  • Cofanie ruchów: rejestr do 10 ostatnich ruchów (kierunek, animacja i ewentualnie pchnięta skrzynka) działający jak bufor FIFO. Cofnięcie odtwarza ruch gracza razem ze skrzynką i aktualizuje siatkę kolizji.
  • Licznik ruchów i czasu, restart poziomu oraz sterowanie z klawiatury lub przyciskami ekranowymi.
  • Kamera podąża za graczem z marginesem od krawędzi kadru i blokuje daną oś, gdy plansza mieści się w całości na ekranie.
  • Poziom kończy się w momencie zajęcia wszystkich pól docelowych. Wynik trafia do zapisu tylko wtedy, gdy jest lepszy od poprzedniego (mniej ruchów lub krótszy czas).
  • 16 wbudowanych poziomów i osobne menu poziomów własnych.
Binarny format poziomu

Nagłówek (18 bajtów) zawiera wymiary planszy, pozycję startową gracza i liczbę skrzynek. Dalej idzie tablica pozycji pól docelowych (po 8 bajtów) oraz siatka kafli zapisana po jednym bajcie na pole (brak kafla / podłoga / ściana / skrzynka).

Wariant tekstury podłogi i ściany losowany jest z ziarna wyliczonego z wymiarów poziomu. Dzięki temu plansza wygląda różnorodnie, a jednocześnie identycznie przy każdym wczytaniu.

Edytor poziomów
  • Tworzenie nowego poziomu (nazwa, wymiary od 5 do 99 na oś) albo wczytanie istniejącego do dalszej edycji.
  • Narzędzia do stawiania ścian, podłogi, pól docelowych i punktu startowego gracza, gumka oraz regulacja liczby skrzynek. Planszę większą od kadru przewija się strzałkami.
  • Walidacja przy zapisie sprawdza ustawienie punktu startowego, obecność skrzynek i pól docelowych oraz zgodność ich liczby. Komunikat wypisuje wszystkie wykryte problemy naraz.
  • Zapisane poziomy trafiają do katalogu poziomów własnych i są od razu dostępne w menu gry.
Zapis postępów i ustawień
  • Postępy przechowywane są w binarnym pliku z rekordem na każdy poziom (ukończenie, najlepszy czas, najmniejsza liczba ruchów), poprzedzonym sumą kontrolną SHA-256 liczoną z danych i stałego ciągu. Niezgodność sumy oznacza ręczną modyfikację lub uszkodzenie pliku i kończy się zresetowaniem postępu.
  • Ustawienia obrazu (pięć predefiniowanych rozdzielczości, tryb pełnoekranowy) zapisywane są do pliku konfiguracyjnego. Zastosowanie zmian odtwarza okno i przelicza układ interfejsu bez restartu gry.
Console Chat
(2019)
C++
Winsock
Diffie-Hellman Key Exchange
SHA256

Konsolowy komunikator klient-serwer na Windows, napisany w C++ na gniazdach Winsock (TCP), bez zewnętrznych bibliotek. Serwer i klient to dwie osobne aplikacje uruchamiane z linii poleceń. Cała logika (konta, rangi, uprawnienia, bany) siedzi po stronie serwera, a klient tylko wyświetla i przekazuje to, co wpisze użytkownik.

Jeden z moich wczesnych projektów hobbystycznych. Pisałem go, żeby nauczyć się programowania sieciowego i wielowątkowości.


Architektura i protokół
  • Wątek na klienta: wątek główny przyjmuje połączenia, a każde uwierzytelnienie i obsługa sesji dostają własny wątek. Dostęp do plików serwera chroni muteks.
  • Własny protokół pakietowy: przed każdym pakietem wysyłany jest jego typ (wiadomość, komenda, prośba o dane), a struktura pakietu wybierana jest szablonem na podstawie typu przesyłanego obiektu.
  • Prośby o dane sterowane przez serwer: serwer może poprosić klienta o wpisanie tekstu, podając etykietę pola i to, czy znaki mają być maskowane. Na tym mechanizmie działa logowanie hasłem, zakładanie konta i potwierdzenia operacji, więc klient nie musi znać żadnego z tych scenariuszy.
  • Tryb publiczny lub lokalny wybierany przy starcie, razem z portem, liczbą slotów i opcjonalnym hasłem do serwera. Przy pierwszym uruchomieniu tworzony jest katalog z plikami serwera i wiadomością powitalną (MOTD).
Szyfrowanie i uwierzytelnianie
  • Klient i serwer ustalają wspólny klucz wymianą klucza Diffiego-Hellmana (potęgowanie modularne na ustalonej podstawie i module). Z uzgodnionego sekretu i nazwy użytkownika liczony jest skrót SHA-256, z którego brany jest właściwy klucz sesji.
  • Każdy pakiet w obu kierunkach szyfrowany jest strumieniem bajtów generowanym przez srand zainicjowany kolejnymi znakami klucza i mieszanym z danymi operacją XOR.
  • Hasła kont nie są przechowywane jawnie. W pliku serwera zapisywane są ich skróty SHA-256 (implementacja algorytmu jest częścią projektu).
  • Sam szyfr to własna konstrukcja o niskiej sile kryptograficznej. Chodziło o zrozumienie mechaniki wymiany kluczy, a nie o realną ochronę transmisji.
Konta, rangi i moderacja
  • Rangi: GUEST (niezarejestrowany), USER, VIP (kolory w wiadomościach), MOD i ADMIN, porównywane liczbowo. Komenda wymaga rangi nie niższej niż jej próg.
  • Rejestracja to po prostu ustawienie hasła do własnego nicku. Przy kolejnych połączeniach serwer prosi o nie w zamaskowanym polu.
  • Komendy użytkownika: lista dostępnych poleceń, lista osób online, wiadomości prywatne z szybką odpowiedzią do ostatniego rozmówcy, rozłączenie.
  • Komendy moderatora: szczegóły o użytkownikach online, wyrzucenie z serwera, bany na adres IP i przegląd listy banów.
  • Komendy administratora: nadawanie i odbieranie rang, usuwanie hasła z konta, przegląd kont i rang, a także zamknięcie lub restart serwera z możliwością anulowania operacji.
  • Bany czasowe i trwałe: czas trwania podaje się skrótem (np. 7d, 12h, 30m), a termin wygaśnięcia zapisywany jest jako znacznik czasu. Po jego minięciu wpis przestaje obowiązywać, a pozostały czas pokazywany jest w czytelnej postaci.
  • Konta, rangi i bany trzymane są w plikach tekstowych serwera, wczytywanych przy starcie i aktualizowanych na bieżąco.
MTConsole, czyli własna biblioteka konsoli wielowątkowej

Zwykła konsola nie nadaje się do czatu, bo tekst przychodzący z sieci rozbija linię, którą użytkownik właśnie pisze. Na potrzeby projektu napisałem osobny komponent, który rozwiązuje ten problem:

  • Wejście obsługiwane jest w osobnym wątku, znak po znaku, a linia wprowadzania rysowana jest niezależnie od strumienia wiadomości. Przed wypisaniem nowej linii pole wejściowe jest czyszczone i odtwarzane pod spodem, a samo wypisywanie chroni muteks.
  • Kursor można przesuwać w obrębie wpisywanego tekstu (strzałki lewo/prawo), wstawiać i kasować znaki w środku linii, z poprawną obsługą zawijania na krawędzi okna konsoli.
  • Historia wpisanych poleceń dostępna jest strzałkami góra/dół.
  • Wejście może być maskowane gwiazdkami (przy hasłach), a pole wejściowe da się ukryć i zablokować.
  • Kolorowanie tekstu działa znacznikami {numer} osadzonymi w treści. Ten sam ciąg można wypisać z kolorami albo oczyścić ze znaczników, z czego korzystają m.in. wiadomości użytkowników rangi VIP.
Flappy Bird Clone
(2018)
Python
Pygame

Klon gry Flappy Bird napisany w Pythonie z biblioteką Pygame. Pygame odpowiada tylko za okno, rysowanie i zdarzenia wejścia, a warstwę gry (obiekty, sceny, zasoby, pętlę aktualizacji) zbudowałem od zera.


Szkielet gry
  • Pętla główna mierzy czas klatki i ma limit FPS. Cały ruch liczony jest w jednostkach na sekundę i mnożony przez czas klatki, więc gra zachowuje się tak samo niezależnie od wydajności komputera.
  • Bazowy obiekt gry (z metodą aktualizacji i flagą aktywności) jest rozszerzany przez obiekt rysowalny (pozycja, rozmiar, tekstura lub kolor wypełnienia). Ptak, rury, tło, teksty i przyciski dziedziczą po nim.
  • Menedżer zasobów prowadzi centralne rejestry obiektów aktualizowanych i rysowanych oraz słownik zasobów dostępnych po nazwie. Lista rysowania sortowana jest po z_index, co ustala kolejność warstw.
  • Menedżer scen: scena to nazwana grupa obiektów przełączana jednym wywołaniem. Sceny to tap (ekran startowy), playing, died i died_new_best, więc zmiana stanu gry sprowadza się do włączenia jednej sceny i wyłączenia pozostałych, zamiast ręcznego ukrywania pojedynczych elementów.
  • Warstwa graficzna ma globalne współczynniki skali, przez które przechodzą wszystkie współrzędne i rozmiary, oraz funkcję detekcji kolizji AABB.
  • Menedżer czcionek buforuje czcionki według nazwy i rozmiaru, a etykiety tekstowe generują teksturę ponownie tylko wtedy, gdy treść faktycznie się zmieniła.
  • Nakładka debugowania pokazuje w rogu ekranu liczbę klatek, czas klatki, zużycie pamięci procesu i liczbę aktywnych rur.
Generowanie i recykling rur
  • Ptak nie przemieszcza się w poziomie. Gra prowadzi licznik pokonanego dystansu, a pozycje rur wyliczane są z jego wartości i indeksu rury. Dzięki temu świat jest nieskończony, a gra nie musi przechowywać jego historii.
  • Liczba obiektów rur jest stała i dobrana do szerokości ekranu. Zamiast tworzyć i usuwać rury, gra przesuwa okno indeksów i używa tych samych obiektów ponownie, nadając im nowe pozycje i wysokości.
  • Losowanie przerw jest deterministyczne: położenie prześwitu dla rury o danym numerze wyliczane jest z ziarna złożonego z czasu rozpoczęcia rozgrywki i indeksu rury. Ta sama rura zawsze dostaje ten sam prześwit, więc jej parametry można odtworzyć w dowolnym momencie, bez pamiętania wcześniejszych losowań.
  • Punkt przyznawany jest po minięciu przez ptaka połowy szerokości rury, a kolizja sprawdzana jest osobno dla części górnej i dolnej.
Sterowanie lotem

Prędkość opadania narasta do wartości granicznej, a przyspieszenie zależy od fazy lotu: wznoszenie hamowane jest mocniej niż swobodny spadek, co daje charakterystyczny łuk skoku. Po zderzeniu ptak przechodzi w osobny tryb upadku z większym przyspieszeniem i wyższą prędkością graniczną, zatrzymuje się dopiero na poziomie ziemi, a ekran końcowy pojawia się z krótkim opóźnieniem.

Zapis najlepszego wyniku

Rekord przechowywany jest w pliku binarnym poprzedzonym skrótem SHA-256 liczonym z wyniku i stałego ciągu wpisanego w kod. Przy wczytywaniu skrót jest przeliczany ponownie i porównywany z zapisanym. Jeśli się nie zgadza (ktoś ręcznie edytował plik) albo plik jest uszkodzony, rekord jest odrzucany i liczenie zaczyna się od zera.

2D Platformer
(2018)
Kod źródłowy
C++
SDL2

Gra platformowa 2D napisana w C++ z biblioteką SDL2. Biblioteka odpowiada tylko za okno, rysowanie tekstur i odczyt klawiatury, a resztę (pętlę gry, obiekty, fizykę, kamerę i wczytywanie map) napisałem od zera.


Szkielet silnika
  • Pętla gry mierzy czas trwania każdej klatki. Wszystkie prędkości podane są w jednostkach na sekundę i mnożone przez ten czas, więc ruch nie zależy od wydajności komputera.
  • Bazowy obiekt gry (identyfikator, flaga aktywności, metoda aktualizacji) jest rozszerzany przez obiekt dynamiczny o pozycję, prędkość, rozmiar i masę. Gracz i bloki mapy dziedziczą po nim, dzięki czemu fizyka działa na jednym wspólnym typie.
  • Silnik prowadzi centralny rejestr obiektów z pulą odzyskiwanych identyfikatorów: numer usuniętego obiektu wraca do puli i dostaje go kolejny utworzony obiekt.
  • Obiekty można wyszukiwać po typie. Robi to szablon zwracający listę wszystkich obiektów danej klasy i korzysta z niego zarówno fizyka, jak i rysowanie.
  • Świat i interfejs opisane są w stałej rozdzielczości 1920×1080, a przy rysowaniu współrzędne przeliczane są przez współczynniki skali na faktyczny rozmiar okna.
  • Do tego własny szablon wektora dwuwymiarowego z przeciążonymi operatorami arytmetycznymi i menedżer wejścia, który odpytuje stan klawiatury raz na klatkę.
Mapa i renderowanie
  • Poziom wczytywany jest z pliku tekstowego, w którym zapisany jest rozmiar kafla, wymiary planszy w kaflach i lista identyfikatorów. Niezerowy identyfikator tworzy blok kolizyjny. Testowa mapa ma 300×200 kafli po 32 piksele, czyli świat o rozmiarze 9600×6400 pikseli.
  • Kafle rysowane są z atlasu tekstur, a identyfikator z pliku mapy wskazuje wycinek atlasu.
  • Przed rysowaniem sprawdzane jest, czy obiekt mieści się w kadrze kamery. Reszta planszy jest pomijana.
Fizyka i kolizje

Kolizje nie polegają na cofaniu obiektu po wykryciu przenikania. Dla każdej osi z osobna przeglądane są obiekty leżące na drodze ruchu i wyznaczana jest najbliższa blokująca krawędź w kierunku prędkości, a nowa pozycja jest do niej przycinana. Dzięki temu obiekt nie przeskoczy przez przeszkodę nawet przy bardzo dużej prędkości, a granice mapy same zatrzymują ruch.

Grawitacja narasta wraz z masą obiektu aż do ustalonej prędkości granicznej. Kontakt z podłożem lub sufitem zeruje prędkość pionową i aktualizuje flagę przebywania w powietrzu, od której zależy skok (można skoczyć tylko stojąc na ziemi).

Sterowanie postacią

Ruch w poziomie rozpędza się od prędkości początkowej do maksymalnej. W powietrzu przyspieszenie jest niższe niż na ziemi, a prędkość maksymalna trochę wyższa, przez co bieganie i manewrowanie w skoku wyraźnie się od siebie różnią. Zmiana kierunku w locie lekko koryguje prędkość pionową, a osobny klawisz przenosi gracza z powrotem na punkt startowy.

Kamera i tło z paralaksą
  • Kamera podąża za graczem z martwą strefą, czyli przesuwa się dopiero wtedy, gdy postać wyjdzie poza wyznaczony obszar kadru. Jest też przycinana do granic mapy, żeby nie pokazywać obszaru poza planszą.
  • Przy szybkim opadaniu punkt kadrowania płynnie przesuwa się w górę i odsłania więcej przestrzeni pod postacią, a po wyhamowaniu tak samo płynnie wraca do wartości domyślnej.
  • Każda warstwa tła ma ustawioną odległość od kamery, a jej współczynnik przesunięcia wyliczany jest z tej odległości i kąta widzenia, zgodnie z zasadami perspektywy. Nie trzeba więc dobierać go ręcznie dla każdej warstwy. Opcjonalnie z odległości skalowany jest też rozmiar warstwy.
  • Warstwy powtarzają się w poziomie w nieskończoność, z korektą błędów zaokrągleń na styku sąsiednich kopii. Ta sama tekstura użyta kilka razy na różnych odległościach buduje wrażenie głębi dżungli.
Roulette Simulator
(2017)
HTML
CSS
JavaScript
Bootstrap
jQuery
SHA256

Symulator ruletki zainspirowany platformą CSGODouble, działający w całości w przeglądarce: bez backendu, na czystym JavaScripcie z jQuery i Bootstrapem. Wczesny projekt z 2017 roku, z czasów, gdy uczyłem się podstaw JavaScriptu i tworzenia stron internetowych.

Koło ma 15 pól, a zakład stawia się na jeden z trzech kolorów:

  • czerwony (1–7) – wygrana 2X,
  • czarny (8–14) – wygrana 2X,
  • zielony (0) – wygrana 14X.

Generowanie wyniku (provably fair)

Algorytm odtwarza schemat „provably fair" stosowany na platformach tego typu: wynik jest ustalony jeszcze przed rozpoczęciem losowania i można go zweryfikować po fakcie. Wygrywająca liczba wyliczana jest z trzech wartości:

  • seed – sekretny ciąg znaków, udostępniany następnego dnia, żeby można było sprawdzić prawidłowość losowań z dnia poprzedniego (generowany jako skrót losowego ciągu o długości 48–64 znaków),
  • public – wartość publiczna, którą jest data bieżącego dnia w formacie rok-miesiąc-dzień,
  • round – numer rundy w ramach danego dnia.

Wartości łączone są w jeden ciąg i przepuszczane przez funkcję skrótu SHA-256. Pierwsze osiem znaków wyniku odczytywane jest jako liczba szesnastkowa, a reszta z jej dzielenia przez 15 wyznacza wygrywające pole. Z kolejnych znaków tego samego skrótu wyliczane jest też przesunięcie w obrębie pola, dzięki czemu wskaźnik zatrzymuje się za każdym razem w innym miejscu tego samego pola, również w sposób możliwy do zweryfikowania.

Animacja koła

Pola ułożone są na zapętlonej taśmie w kolejności naprzemiennej (czerwone i czarne po obu stronach zera), a losowanie polega na przesuwaniu tła o wyliczony dystans. Ruch wyhamowuje wykładniczo: w każdym kroku pokonywana jest część pozostałej odległości, więc im bliżej celu, tym koło zwalnia łagodniej. Dystans startowy to kilka pełnych obrotów plus droga do wylosowanego pola, a tempo wyhamowania reguluje się suwakiem prędkości.

Rozgrywka
  • Odliczanie do startu rundy z paskiem postępu. Na czas losowania obstawianie jest blokowane, a po ogłoszeniu wyniku numer rundy zwiększa się automatycznie.
  • Limity zakładów: minimalna stawka 10 żetonów i maksymalnie 3 zakłady na rundę.
  • Pole stawki z przyciskami skrótu (+10, +100, +1000, +10000, połowa, podwojenie, wszystko). Salda i pule na kolorach zmieniają się animowanym licznikiem, zielonym przy wygranej i czerwonym przy przegranej.
  • Darmowe żetony do odebrania, gdy saldo spadnie poniżej ustalonego progu.
  • Historia dziesięciu ostatnich wyników, efekty dźwiękowe kręcenia i zatrzymania oraz komunikaty o zdarzeniach.
Panel ustawień

Osobny panel pozwala samemu sprawdzić działanie generatora: ręcznie ustawić lub wylosować ponownie seed, public i round, wyłączyć automatyczne zwiększanie numeru rundy, zmienić długość odliczania i prędkość animacji, znieść limit darmowych żetonów, a także uruchomić losowanie natychmiast, bez czekania na koniec odliczania. Wpisując te same trzy wartości, można odtworzyć dowolne wcześniejsze losowanie i potwierdzić, że wynik nie został po drodze zmieniony.

Damian Żuk — Software Engineer © 2026