Robimy MMOSG I – Wstęp i research.

 

tldr: Nowa seria – robimy ogame.


Ostatnio dla zabawy próbowałem stworzyć lekki webowy frontend do bazy danych (z losowo wygenerowanymi użytkownikami) przy użyciu JQuery i Spring Boota z MongoDB po stronie serwera. Całkiem i się to spodobało i teraz w głowie mam większy pomysł, czyli stworzenie chociaż najbardziej badziewnego, ale działającego massively multiplayer online strategic game (ogame/plemiona/ikariam etc). Bazy danych + technologie webowe wyglądają znacznie lepiej w parze, jeśli patrzeć pod kątem CV, a wydaje mi się że zasadniczo dobrze jest umieć na jakimś poziomie zwizualizować backend. Zacznijmy więc może od przedstawienia kilku problemów które będą po drodze.

Problemy i research

W jaki sposób serwer będzie wiedział, kiedy może odesłać przeglądarce stronę dostępną tylko dla zalogowanych, a kiedy nie, a bardziej ogólnie: skąd wie, czy użytkownik jest zalogowany?

Z tego co wygooglowałem i sam się domyśliłem, wygląda to tak:

Użytkownik loguje się pomyślnie na stronie (hash hasła z jego formularza pokrywa się z tym w bazie danych), na serwerze zostaje wygenerowany unikatowy token i zapisany w bazie danych razem z adresem IP osoby która się zalogowała a także datą wygaśnięcia tokena. Do tej samej osoby, odsyłany jest token, zapisywany jest w formie ciasteczka, a następnie kiedy tylko ta osoba zażąda strony, która dostępna jest tylko dla zalogowanych, serwer porówna token przesłany z ciasteczek oraz adres IP, jeśli wszystko się zgadza, to odsyła stronę, jeśli nie, redirect na stronę logowania.

Na co warto rzucić okiem:

https://pl.wikipedia.org/wiki/HTTP_cookie

https://blog.risingstack.com/web-authentication-methods-explained/

https://security.stackexchange.com/questions/755/how-does-basic-http-auth-work

http://viralpatel.net/blogs/spring-mvc-cookie-example/ 

http://stackoverflow.com/questions/12050059/user-session-tracking-in-javascript

Trzymanie haseł

Minimum bezpieczeństwa, czyli trzymanie hashy. Samo przesyłanie informacji z przeglądarki na serwer również powinno być jakoś zabezpieczane:

https://stackoverflow.com/questions/4101440/jquery-sending-password-via-ajax

https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet

Wygląd strony

Jestem w tym zielony (nie znam niczego poza CSS, HTML), ale póki co nie przejmuję się tym, frontend wyjdzie po drodze i będę wyszukiwał na bieżąco gdy tylko znajdzie się czas. Co do tego, znalazłem całkiem fajną stronę:

https://uptodate.frontendrescue.org/

Materiały do nauki

Okazuje się, że w sieci jest całkiem sporo opensourcowych klonów Ogame, przykładowa lista:

https://freevps.us/thread-7746.html

W razie problemów, będe zaglądał w kod.


Oczywiście wszystko krok po kroku, zanim dojdziemy do pierwszego prototypu minie czas i jestem świadom że nie zrobię wszystkiego powyższego w jeden weekend.

Gdy zaczniemy pisać kod do tej serii, udostępnię go na moim Githubie:

https://github.com/dbeef


dsp2017-1

 

Sound processing IX – Udało się!

tldr: Nadawanie wiadomości z wewnątrz gry i odbieranie jej stamtąd działa pełną parą. Pozostało zaimplementować rozgrywkę z prawdziwego zdarzenia i podsumować serię w okrągłych dziesięciu postach.


Rozwiązanie problemu z głośnikami

Przekopałem całą dokumentację Beads, nie znalazłem niczego co wskazywałoby na problem albo chociaż pozwalało nadać wszystko ciągiem – znalazłem jednak możliwość zapisywania wygenerowanych częstotliwości jako plik .wav i… bingo.

Zapisuję każdą jedną z częstotliwości dotychczas już generowanych na dysku (każda po ~32.kB) , po czym łączę je w jeden duży (5.8 MB) plik i odtwarzam wewnątrz Mad Pirates. Takich mniejszych plików jest 188, wygenerowanie ich zajmuje około 6 sekund.

Screenshot from 2017-05-27 21-27-10.pngWygenerowane pliki.

Odtworzenie całej wiadomości jako plik .wav nie stwarza najmniejszych problemów z głośnikami, więc największy problem transmisji jest już na dobre zamknięty.

Screenshot from 2017-05-27 21-31-57.pngKlasa FileMerger.java którą łączę powstałe pliki .wav.
filerecorder.pngKlasa FileRecorder.java która jest przerobionym tutorialem Beads ze strony:
https://github.com/orsjb/beads/blob/master/packages/Beads/beads_tutorial/Lesson09_RecordToSample.java

Emitowanie i nasłuchiwanie

Komputer musi w jakiś sposób wiedzieć, czy ta część wiadomości, którą do tej pory odebrał, nadaje się do parsowania. Rozwiązałem to, tworząc cyklicznie co sekundę nowy wątek próbujący sparsować obecny bufor z informacjami z głośników. Jeśli się nie powiedzie – trudno, exception, wątek ginie, tworzy się nowy i próbuje to samo. Jeśli się uda – transmisję uznajemy za udaną i podstawiamy wyniki ze sparsowanego obiektu do symulacji. Działa, ale niezbyt eleganckie. Do wymiany w przyszłości.

sounddecoder.png

Jeśli chodzi o emitowanie wiadomości, to skorzystałem ze starej klasy StarterBroadcaster.java, która zamiast odtwarzać dźwięki, to zapisuje je, a po skończeniu tego procesu odczytuje główny plik ze zlepionymi dźwiękami – po wszystkim wątek się zamyka. StarterBroadcaster tworzony jest na żądanie, przyciskiem klawisza G.

Reorganizacja kodu

Do utrzymania porządku dopisałem kilka klas, m.in. FileLoader.java, z którego biorę referencje do tekstur które załadował, a także HUD.java, który trzyma wszystkie możliwe opcje wyświetlania interfejsu użytkownika, tj. tryb w którym wypisuje “Tura planowania”, tryb z wypisywaniem “Tura symulacji” i tak dalej. KeyboardInterpreter.java odpowiada za logikę przy wciskaniu klawiszy a InputModel.java został dodany dla łatwego transportu informacji pomiędzy klasami.

Nie do końca jestem zadowolony ze zmian (logika działania nadal nie jest do końca czytelna i można się pogubić w boolean-ach), ale na ten moment wystarczy.

Efekty

Oto zakończona sukcesem próba przesłania obecnej pozycji statku dźwiękiem (odbiorcą i nadawcą był ten sam komputer).

Co dalej

Aby uczynić owoce tej pracy użytecznymi, zamiast przesyłać pozycję XY statku będziemy przesyłać pozycję kliknięcia myszką gracza i do tego na start gry przyjmować w argumencie konsoli to, którym graczem gramy (wtedy obie strony wzajemnie znają początkową pozycję początkową siebie i przeciwnika) – tak właściwie, to już to zrobiłem, tym urywkiem kodu:
pozycje

Aby przetestować to rozwiązanie prosto w IDE, wystarczy wyedytować konfigurację startową:

konf

Jesteśmy na skraju celu, którym jest faktycznie móc się poruszać po planszy co turę, grając w dwie osoby, więc to już w następnym, podsumowującym poście.


Jak zawsze, kod dostępny jest na moim GitHubie:

https://github.com/dbeef/soundcoding

dsp2017-1

Sound processing VIII – Shadery i nadawanie pozycji

Cotygodniowy postęp prac stopniowo się zmniejsza ze względu na zbliżającą się maturę. Mam nadzieję, że po mniej więcej połowie maja będę miał więcej czasu na blogowanie.


Problemem z Shaderami który zgłosiłem w ostatnim poście okazało się podlinkowanie modułu libgdx-contribs do projektu core, ale nie do desktop – przez to projekt z kodem rzeczywiście widział bibliotekę i mógł bez problemu korzystać z jej klas, ale projekt który uruchamiał LibGDX i wykorzystywał kod z core już dostępu do nich nie miał:

Screenshot from 2017-04-23 22-04-27.pngPrawidłowo podlinkowane libgdx-contribs

Do tego drobiazgi, takie jak ustawienie prawidłowej ścieżki w klasie ShaderLoader, ze standardowego:

public static String BasePath = "";

Na odpowiadające mojej strukturze:

public static String BasePath = "core/assets/shaders/";

Co wynika z tego całego dobrego podlinkowania? Pozwala dodać mi krzywiznę do dwuwymiarowego obrazu, oto jak się przezentuje:

Jest kilka innych fajnych efektów które można dodać za pośrednictwem tej biblioteki, opisałem je w moim starym poście na ten temat:

https://dbeef.wordpress.com/2016/05/01/easy-shaders-via-libgdx-contribs-postprocessing/

Dodałem również wspominany już wiele razy modem – teraz w momencie zatrzymania symulacji umożliwia się nam wysłanie jsona z naszą pozycją. Na ten moment nie jest to użyteczne i służy bardziej do nauki komputera jak ma debugować wiadomość w której brakuje informacji (powiedzmy, że znak klamry przesłał się jako znak którego nie można przepisać do żadnego z tabeli ASCII bo komputer zamiast usłyszeć 3900 Hz, dostał 4000 Hz, głupio byłoby tylko z tego powodu wysyłać wiadomość drugi raz, skoro można nauczyć go pewnych wzorców).

Do tego pojawia się pewien problem – przy dłuższym czasie nadawania (więcej niż 10 sekund) głośniki potrafią wariować, kaszleć i szumieć zamiast wydawać piski. Mam wrażenie, że próbują wtedy odtwarzać kilka wysokich częstotliwości na raz, co wskazuje na błąd w kodzie. Zobaczymy następnym razem.

Poniżej krótki film z nadawania. Komputer odszyfrował bardziej coś jsono-podobnego, ze względu na powyższe (na filmie słychać te objawy), a także krótkie interwały pomiędzy częstotliwościami, na które naciskam, aby transmisja się nie wlekła. Nadal pracuję nad lepszymi wynikami przesyłu.

Odnośniki:

Kod jak zawsze dostępny jest na moim GitHubie:

https://github.com/dbeef/soundcoding/branches


dsp2017-1

Różne IV – SSTV

W temacie przesyłania danych w nie-internetowy sposób warto wspomnieć o SSTV (przesyłanie obrazów falami krótkimi). Cokolwiek bym tu napisał i tak lepiej wyłoży to ten artykuł, którego nie chcę tu cytować, bo stanowiłby większość posta.

Z racji tego (i chwilowego braku czasu), jest to zbiór odnośników, które wprowadzają w temat.

Jak pogadać z ISS

To zdecydowanie najbardziej ekscytująca sprawa:

Most of the astronauts on the International Space Station are licenced Radio Amateurs and sometimes during their spare time they talk to other Radio Amateurs back on earth. There is a special thrill in talking to an astronaut out in space!

Polecam cały artykuł:
https://amsat-uk.org/beginners/how-to-hear-the-iss/

Nadawanie obrazów z ISS

https://amsat-uk.org/beginners/iss-sstv/

Kod

Na GitHubie znalazłem sporo otwartych programów do nadawania obrazów SSTV:

https://github.com/search?utf8=%E2%9C%93&q=sstv&type=

Inne

Trafiłem w międzyczasie na mikrosatelitę ufundowaną przez stowarzyszenia radioamatorskie, również jej można posłuchać z odpowiednim sprzętem:

http://www.amsat.org/?page_id=2039

Swoją drogą, wysłano ją 34 metrowym przerobionym radzieckim ICBM-em, dla porównania, Saturn V (ten od wysłania ludzi na księżyc) miał 110 metrów.

Tutaj można podsłuchiwać poszczególne długości fali (np. ISS czy wspomniany wcześniej Funcube, jeśli nie zasłania ich aktualnie Ziemia):

http://websdr.suws.org.uk/

Sound Processing VII – Przewidywanie trajektorii

W tej części wprowadzimy przewidywanie ścieżki ruchu i podzielimy ruch na tury. Zapraszam.


Na czym skończyliśmy

Po ostatniej części serii odziedziczyliśmy zaimplementowany silnik fizyczny, sterowanie statkiem i prosty zoom. Do gry dorzucimy teraz przewidywanie ruchu (jak w Angry Birds) – rysowana będzie trajektoria ruchu do wskazanego punktu.

Zajmiemy się również podziałem ruchu na tury.

Przewidywanie ścieżki

Mój pomysł zamknął się w tym, aby posiadać 2 obiekty symulujące fizykę (Box2D World), mające te same tablicę z ciałami (Array<BodyDef>; statki, wyspy itd.), jeden byłby do symulacji aktualnej pozycji, drugi do przewidywania tej wskazanej (jeśli dałoby się ustawić ujemną deltę czasu, czyli cofnąć symulację po sprawdzeniu trajektorii, to wystarczyłby jeden).

W momencie wskazania punktu do którego statek ma zmierzać, ustawiam obiektom będącym w silniku do trajektorii pozycje i prędkości ciał z właściwego silnika (czyli dokładnie te same prędkości i pozycje ciał co w momencie kliknięcia) oraz ustawiam mu deltę czasu odpowiadającą czasowi jaki upłynie podczas tury symulacji (czyli jakieś 3-4 sekundy, w zależności jak będzie się wygodnie grało):

captureSimulationDot to klasa składająca się wyłącznie z wartości x, y oraz angle. Służy do zapisu kolejnych pozycji trajektorii do późniejszego rysowania.

Metoda captureSimulation() wywoływana jest w momencie kliknięcia.

Dla każdego ciała dodatkowo ustawiam flagi active, ponieważ po zmienieniu pozycji ciała (setTransform()) silnik automatycznie ustawia ją na false, co skutkuje nie sprawdzaniem kolizji dla tego ciała.

Symulacja fizyki w silniku trajektorii, pomijając ustawienie na sztywno delty i powiązanie jej z timerem, następuje dokładnie w ten sam sposób, co w zwykłym (metoda simulateAlternativeWorld() pod wieloma względami pokrywa się z simulate()), dodałem jednak do niej timer i symulację kilku następnych sekund realizuję co 200 ms, zamiast całość symulacji na jeden raz, aby nie skutkowało to krótkim dławieniem gry.

drawingDots.pngRysowanie poszczególnych punktów przewidzianego toru ruchu. Rysuję tylko pierwsze 400 punktów.

Niestety ten sposób z nieznanych mi przyczyn nie uwzględnia kolizji w trakcie ruchu,  więc trajektoria przebiega po obiektach które normalnie wchodzą w kolizję ze statkiem – do naprawienia w przyszłości.

Ruch w turach

Ruch w turach jest bardzo prosty do zrealizowania. Deklaruję timer, powiedzmy, że z wartością 3 – dopóki nie upłyną 3 sekundy, wywołuję za każdą klatką metodę simulate(),jeśli upłyną – symulacja ustaje, obiekty również zamierają w ruchu.

Klawiszem P przechodzę do następnej tury symulacji, znów ustawiając nim timer na 3.

Do tego wszystkiego dorzuciłem również nową mapę i ograniczyłem zasięg kamery do mapy – nie powiązałem nastomiast zasięgu kamery z zoomem, przez co przy krawędziach mapy, duży zoom-in nie centruje kamery na statku.

Wyniki prac wrzuciłem na YT:

Co dalej?

Próbowałem dorzucić shadery, ale muszę źle ustawiać zależności między projektami, bo Java nie jest w stanie znaleźć odpowiednich klas (z którymi IDE nie ma problemu) – również do zrobienia następnym razem.

W następnej części chciałbym dorzucić jakiś zalążek interfejsu i podłączyć modem dla pierwszej próby gry przez dźwięk.

Odnośniki:

Jak zawsze kod dostępny jest na GitHubie:

https://github.com/dbeef/soundcoding/tree/game


dsp2017-1

Różne III – Bot WWW

Wstęp

Kiedy jeszcze w podstawówce/gimnazjum grałem w gry przeglądarkowe (nie robiąc reklamy – Ogame i Imperia Online były najlepsze) zawsze w pewnym momencie kończyłem ze zniszczonym dorobkiem paru miesięcy, bo zapomniałem wysłać FS-a (dla niewtajemniczonych, fleet save to w żargonie przynajmniej gry Ogame – wysłanie floty na inną planetę z najmniejszą możliwą prędkością, robiło się to na noc, żeby w czasie snu ktoś czasem nie zaatakował korzystając z braku czujności, co jasne, kiedy flota gdzieś leciała to nie mogła wziąc udziału w żadnej walce, więc była bezpieczna).

Szkoda, że nie pomyślałem wtedy o napisaniu bota, który robiłby to za mnie. Mało tego, mógłby sam zarządzać surowcami, budować budynki, przeprowadzać badania – człowiek w zasadzie nie byłby potrzebny.

Co więc zrobić?

Piszemy bota

Od razu mówię, że korzystanie z botów może doprowadzić do utraty konta i prawdopodobnie jest to niezgodne z regulaminem większości gier.

Skoro mamy już to za sobą – w naszym bocie najważniejszą częścią jest Selenium, framework do testowania stron www (dostępny dla Pythona i Javy). To co jest w nim najlepsze, to może obsługiwać przeglądarkę, tj. pozwala to na zasymulowanie praktycznie wszystkiego co można zrobić ręcznie na stronie. Do tego można z niego korzystać w tle, nie zakłócając innych zajęć, a w przypadku chociażby Ubuntu można uruchomić go na symulowanym serwerze okienkowym, wtedy nawet przeglądarka nie będzie widoczna (szukałem czegoś podobnego na Windowsa, niby przy odpowiednich wtyczkach jest możliwość uruchomienia IE w wersji headless, ale mi się nie udało).

Od czego zaczniemy nasz projekt w Selenium?

Potrzebujemy AdBlocka. Bez niego Selenium będzie zupełnie ślepe przy popupach wyskakujących w miejscach, gdzie każemy mu szukać jakichkolwiek formularzy.

W przypadku Selenium + Firefox (chociaż wspierana jest większość przeglądarek), ściągnięcie AdBlocka i zainstalowanie go nic nie da – nowe okienko FF jest włączone na świeżym profilu z wyłączonymi wszystkimi wtyczkami. Wtedy pozostaje podanie w kodzie ścieżki do instalatora AdBlocka:

File f = new File("/home/dbeef/addon-1865-latest.xpi");
profile.addExtension(f);

Gdzie addon-1865-latest.xpi to plik instalacyjny Firefoxowej wtyczki AdBlock. Tuż po uruchomieniu FF zostanie tymczasowo zainstalowana dla tego profilu (na czas działania). Skąd wziąć AdBlocka? Na pewno nie Googlując “firefox adblock installer“. Nawet, jeśli traficie na plik .xpi, to prawdopodobnie będzie to jakiś śmieć kradnący hasła. Ja wszedłem na stronę z dodatkami mozilli i wyszukałem AdBlock Plus:

Przycisk “Zainstaluj” nie zapisze pliku na dysku, ale od razu zacznie instalację, więc poszedłem inną drogą. Prawy przycisk na zainstaluj odkrył link do pliku .xpi (+ jakieś argumenty dla przeglądarki, które wyciąłem), który wrzuciłem do wgeta:

Screenshot from 2017-04-13 23-51-16.png Mając wtyczkę, podaję ścieżkę do niej w kodzie. Teraz musimy jeszcze pobrać gecko i również do niego podać ścieżkę. Jest to sterownik do Firefoxa dla Selenium.

System.setProperty("webdriver.gecko.driver", "/home/dbeef/Downloads/geckodriver");
profile.setPreference("webdriver.gecko.driver", "/home/dbeef/Downloads/geckodriver");

To wszystko. Jesteśmy gotowi do pisania kodu.

Kod

Wbrew pozorom, więcej będzie tu siedzenia w przeglądarce, niż w IDE. Zaczniemy od wejścia na stronę Ogame i podglądu interesujących nas formularzy i przycisków we wbudowanym w większość przeglądarek inspektorze:

Screenshot from 2017-04-12 20-59-19.png

Jest to ważne, bo teraz chcemy wpisać nasz login i bardzo sekretne hasło. Jak powiedzieć Selenium, żeby wpisało je akurat w drugie pole od prawej, a nie drugie od lewej, przy rejestracji?

Odpowiedni element można w Selenium wyszukać poprzez atrybuty className, tagName czy id. Dlatego musimy znaleźć takie atrybuty odpowiadające interesującym nas polom w inspektorze.

Z powyższego zrzutu ekranu widać, że id pola do nazwy użytkownika to usernameLogin (wystarczy prawy przycisk na interesującym nas elemencie i “zbadaj element” lub coś w tym stylu, w zależności od przeglądarki). Szukamy więc takiego i wysyłamy do niego odpowiedni ciąg znaków:

passes

W zasadzie w tym miejscu mógłbym skończyć. To co dzieje się dalej, to powtarzanie schematu: znajdź element w przeglądarce -> każ Selenium go znaleźć i powiedzieć, jak się z nim obchodzić.

Jedyne, co jeszcze mogłoby być warte dodania jest to, aby pomiędzy przeładowaniami strony (np. po kliknięcu przycisku logowania) uśpić wątek na kilka sekund albo sprawdzać co jakiś czas w Selenium, czy jakiś obiekt (np. przycisk wyloguj) jest już widoczny celem dania czasu przeglądarce na doładowanie nowej strony.

Czasem rozgryzienie jak coś zrobić jest trochę bardziej skomplikowane, np. w poniższym programie, gdzie loguję się i rozpoczynam budowę elektrowni, najpierw szukam przycisku elektrowni po id i sprawdzam czy znaleziony przycisk należy do elektrowni słonecznej:

powerplant

Następnie klikam go, czekam chwilę, wyszukuję przycisk buduj i w końcu i jego klikam:

powerplant2.png

Działanie nagrałem i wrzuciłem na YT:

Przypominam, że przeglądarka wcale nie musi być na wierzchu, aby Selenium wykonywał zlecony program!

To był dość trywialny przykład, ale sam potencjał takiego wykorzystania jest już bardzo duży. Od aplikacji przewidujących wiele działań na bieżąco, działających gdzieś na wirtualnej maszynie albo po prostu włączone w tle na własnym komputerze, po tzw. data mining.

Kod dostępny jest na GitHubie:

https://github.com/dbeef/littlegamebot

Odnośniki:

Polecam sekcję “Legal issues”

https://en.wikipedia.org/wiki/Web_scraping

Obsługa błędu pojawiającego się na stronach z często odświeżanymi elementami w tle:

http://darrellgrainger.blogspot.com/2012/06/staleelementexception

Znajdowanie elementów strony dla Selenium:

http://selenium-python.readthedocs.io/locating-elements.html

http://stackoverflow.com/questions/34115825/when-and-how-i-can-locate-element-by-tagname-using-selenium-webdriver-please-ex


dsp2017-1

Sound Processing VI – Piszemy grę

W ostatnim odcinku skończyliśmy główną część modemu, który będziemy wykorzystywać do przesyłania informacji pomiędzy grami. Czas teraz napisać grę. Zapraszam.


Pomysł

W innym poście wspomniałem, że dobrze byłoby zrobić jakąś sympatyczną gierkę o piratach, z perspektywą migracji na urządzenia mobilne. Usiadłem na chwilę z kartką papieru i zapisałem parę pierwszych ogólników. Będzie to dwuwymiarowa gra z widokiem z góry, polegać będzie na dwuturowym systemie:

Tura ruchu

Najpierw wybierany jest wektor do poruszania się (gracz klika w jakieś miejsce ekranu, tam będzie zmierzał statek), do tego paskami reguluje prędkość statku, przyciskami czy statek ma strzelać, jeśli tak, to znowu odpowiednimi przyciskami wybierana jest lewa lub prawa burta, a także suwakiem – po jakim czasie ma strzelać.

Następnie klikane jest – koniec tury -, odpowiednie informacje przesyłane są naszym modemem do drugiego gracza, drugi gracz robi to samo, dane przesyłane są do pierwszego, następuje tura symulacji.

W turze symulacji przez 4-5 sekund statki robią właśnie to, co de facto zaprogramowało im się w turach ruchu.

Cała gra po przeniesieniu na telefon i pozbyciu się modemu dźwiękowego mogłaby być na prawdę dobra, powiedzmy, że mielibyśmy max. 10 sekund ograniczenia na swoje ruchy i wszyscy gracze (najlepiej kilkunastu na dużej mapie!) robiliby swoje tury asynchronicznie.

Po skończeniu gry działającej z modemem właśnie za tę wersję się zabiorę.

Swoją drogą, znalazłem dobry artykuł na temat różnego podejścia do projektowania gier.

Technologie i narzędzia

Aby całość współgrała z modemem napisanym w Javie, będę korzystał z frameworka LibGDX (możesz go kojarzyć np. z Turn The Snake albo Speechlist).

Realizacją fizyki zajmie się Box2D, a jeśli chodzi o narzędzia to poza edytorem map Tiled, nic innego na razie nie potrzebuję.

Na ten moment większość zasobów z których będę korzystał będzie z tej paczki. Grafiki wyglądają świetnie, a licencja pozwala również na użycie komercyjne.

License: (CC0 1.0 Universal) You’re free to use these game assets in any project, personal or commercial. There’s no need to ask permission before using these. Giving attribution is not required, but is greatly appreciated!

Zaczynamy

Grę w LibGDX zaczynam od pobrania i uruchomienia prostego generatora projektów:

generator.png

Z ich strony wynika, że mają wsparcie do Xboxowego pada, stąd zaznaczyłem, by ściągnęły się moduły do jego obsługi – być może później się przydadzą.

Po imporcie świeżo wygenerowanego projektu, początkowy szablon nie wygląda zbyt imponująco:
Screenshot from 2017-04-08 17-27-09

Za podstawę do naszej gry wezmę Mad Pirates – grę, którą zacząłem pisać jakiś rok temu ale szybko porzuciłem. Przerzuciłem całość kodu, do tego zmieniłem tekstury na te z podanej wyżej paczki – są o wiele ładniejsze, a co do licencji starych nie byłem pewien.

W Tiled dodałem kilka obiektów (wyspy), które później można zaimportować w kodzie do silnika symulacji fizycznych:

Screenshot from 2017-04-08 18-49-07.png

Prezentuje się to tak:

Ruchy statków jak na lodowisku są celowo karykaturalne, docelowo nie będą aż tak porywiste, ale pasują do lekkiej, rysunkowej stylistyki.

Co dalej?

Na filmiku widać co jakiś czas białe paski wzdłóż kafelków, na ten moment nie wiem czego wynikiem są takie artefakty, ale na pewno jest to zbyt widoczne, żeby tak zostawić.

Do tego oczywista oczywistość – naprawa ruchu okrętu. Na ten moment, dla przykładu – kiedy statek jest skierowany w dół, lekko w prawo, kiedy kliknie się poniżej statku po lewej, statek wykona praktycznie pełny obrót o ~360 stopni, zamiast obrócić się o te kilka stopni w bardziej optymalną stronę.

Przydałoby się też lepsze zorganizowanie kodu, przemyślane pod komunikację za pomocą dźwięku.

W następnej części dzielimy ruch na tury opisane we wstępie i dorzucamy krzywą ruchu rysowaną tuż po kliknięciu, od statku do punktu.

Odnośniki

Aby odróżnić sam modem od gry wraz z modemem, utworzyłem nowy branch na GitHubie:
https://github.com/dbeef/soundcoding/tree/game


dsp2017-1

Różne II – Glitch-ujemy zdjęcia

Źródło: http://www.glitchet.com/

Skoro już przy okazji pisania modemu jestem w temacie szumów i błędów komunikacji, naturalne jest poruszenie tematu “zepsutych” zdjęć lub filmów.

Swoją drogą, wracając do tematu modemu – Googlowałem trochę informacji i natknąłem się na wtyczkę do Chrome która dźwiękowo rozsyła link do otwartej strony do wszystkich komputerów wokół, które również mają tę wtyczkę.

https://research.googleblog.com/2015/05/tone-experimental-chrome-extension-for.html

Wstęp

Format zdjęcia definiuje jego strukturę (to dlaczego jest wiele formatów zdjęć i czym się różnią możesz przeczytać tutaj), przeglądarka zdjęć musi wiedzieć z jakim formatem ma do czynienia aby wiedzieć jak je wyświetlić; znając już format, wie gdzie szukać poszczególnych sekcji – skanu zdjęcia albo tekstowego komentarza.

tabelaPrzykład – Tabela znaczników dla formatu JPG – Źródło: https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure

Zdjęcia zglitchowane mają celowo, bądź nie, zmienione wartości bajtów z sekcji odpowiadających za obraz (zmiana innych raczej spowoduje, że zdjęcie stanie się niezdatne do odczytu przez galerię, np. zmienimy zadeklarowany rozmiar i nie będzie pasował on do ilości pikseli opisanych w sekcji ze skanem obrazu, przeglądarka zdjęć nie otworzy takiego pliku), co czasem znacznie zmienia sposób w jaki przeglądarka zdjęć rysuje je na ekranie. Wybór bajta któremu zmienia się wartość jak i sama wartość nie muszą być losowe – chociażby poniższy przykład:

Źródło: http://datamoshing.com/category/image/

Program sortował piksele będące w tym samym rzędzie względem ich jasności.

In mode 2, or white mode, the script will begin sorting when it finds a pixel which is not white in the column or row, and will stop sorting when it finds a white pixel.

Piszemy własny glitcher

Korzystając z wklejonej wyżej tabeli odczytałem jakie bajty sygnalizują start i koniec sekcji ze skanem zdjęcia – dla startu jest to “FF DA” a dla końca “FF D9”. Program wczytuje zdjęcie w postaci tablicy bajtów, przeszykuje ją pod względem pierwszego wystąpienia bajtów startu i ostatniego wystąpienia bajtów końca.

Następnie wybiera losowy bajt z pomiędzy nich i losuje dla niego wartość z pomiędzy -128 do 127 – maksymalny decymalny zasięg zmiennej byte. Jeśli podmieniamy tylko jeden bajt, to na tym się kończy – otrzymaną tablicę znów zapisujemy do pliku.

picTo praktycznie cały kod, pominąłem obsługę argumentów z konsoli i wczytanie pliku. Zostawiłem również możliwość włączenia w trybie podmieniającym bajty w dowolnym miejscu tablicy, dla różnorodności (co jasne, wygeneruje to więcej błędnych plików).

Kod razem z gotowym jarem wrzucam na GitHuba:

https://github.com/dbeef/pic-glitcher

Testujemy glitcher

Dla przykładu posłużymy się klasykiem – zdjęciem kaczki. Przed przejściem przez mój pic-glitcher:

zbrodniarz

Po przejściu:

kopia63

Wrzucając to zdjęcie na bloga zauważyłem też jeszcze jedną prawidłowość – z zepsutym jpgiem galerie zdjęć radzą sobie na różne sposoby. Tutaj kaczka wyświetla się zielona (a na niektórych telefonach wyświetla się prawie prawidłowo, pomijając kilka przekłamanych grupek pikseli), ale ta sama kaczka w systemowej przeglądarce wyświetla mi się tak (zrzut ekranu zrobiony zglitchowanemu zdjęciu, tutaj przekłamania już muszą być oddane tak samo na wordpressie):

zepsutyzbrodniarz

Tu również psychodelicznie (oryginał glitcha, również może wyświetlać się różnie w zależności od twojej przeglądarki zdjęć):

zbrodniarz2

copy-436

copy-464

copy-497

Ogółem na 500 generowanych w ciągu paru sekund zdjęć, jakieś 100 było nie do ponownego otwarcia w galerii, prawdopodobnie nadpisuję bajty odpowiedzialne za coś odpowiedzialnego za chociażby kompresję a nie właściwy skan zdjęcia.

Myślę, że temat zdjęć podejmę jeszcze przynajmniej raz, następnym razem już z innej strony.

Odnośniki

https://github.com/dbeef/pic-glitcher – GitHub z pic-glitcherem

http://www.glitchet.com/resources – Sporo odnośników do artykułów w temacie

http://jameshconnolly.com/glitchtalk.html – Glitchowanie jako sztuka

http://users.wfu.edu/matthews/misc/graphics/formats/formats.html – O formatach zdjęć

dsp2017-1

Sound Processing V – Synchronizacja wątków, suma kontrolna, odchudzony json

Ostatnia część, w której będę skupiał się na modemie. Następne części będą już skupiały się na praktycznym zastosowaniu, tj. będziemy pisać turową grę multiplayer, którą obsłuży nasz modem. Jakiekolwiek zmiany w backendzie (modem) będą pisane adhoc, bez specjalnych postów. Zapraszam.


Na czym skończyliśmy

Ostatni post zakończyliśmy 3 problemami do rozwiązania:

  • Dostęp do głośników realizowany w jak najkrótszym czasie, bez błędu dostępu który pojawia się co jakiś czas (java.lang.IllegalStateException: Mixer is already open, tj. program próbuje odtworzyć nowy dźwięk, zanim obiekt zakończy odtwarzanie poprzedniego).
  • Weryfikacja poprawności przesyłu danych – metoda która pozwoli stwierdzić, czy pakiet informacji przesłano bez przekłamań
  • Opracowanie sposobu na szybkie przesyłanie jsonów, tj. odchudzenie go o informacje, których obecności program może się sam domyślić.

Zacznijmy od pierwszego.

Usunięcie buga dostępu

Trochę pomyślałem i stwierdziłem, że błędem było nie korzystanie z metody synchronized klasy Thread, ze względu na modyfikowanie obiektu z zewnątrz klasy przez inny wątek (np. zatrzymywanie generowania dźwięku z wątku Broadcaster). Doprowadziło to do paru zmian:

SoundWaveGenerator teraz rozszerza klasę Thread, zamiast jak wcześniej implementować klasę Runnable (aby móc korzystać z metody join).

Jeśli kogoś zastanawiało, kiedy tworzyć obiekty thread-safe, a kiedy odpuścić sobie synchronizację, odsyłam:

http://stackoverflow.com/questions/234341/should-i-always-make-my-java-code-thread-safe-or-for-performance-reasons-do-it
https://www.tutorialspoint.com/java/java_thread_synchronization.htm
http://stackoverflow.com/questions/15956231/what-does-this-thread-join-code-mean
http://www.tutorialspoint.com/java/java_multithreading.htm

Do tego w Broadcaster.java już nie korzystam z:

while (!soundWaveGenerator.getAudioContext().isRunning()) {…czekaj 500 ms….}

tylko z:

soundWaveGenerator.join();

Czym różnią się obie metody?

Poza tym, że druga to wygodny one-liner, a pierwsza jest nieczytelna, bo polega na pętli sprawdzającej czy jakiś wątek nadal pracuje, a jeśli tak, to idzie na chwilę spać, to w zasadzie niczym (jeśli się mylę, to proszę o informacje).

Obie sprawiają, że wątek w którym zostały wywołane czeka, aż drugi wątek skończy pracę, a dopiero potem przechodzą do dalszego kodu. Było to potrzebne, bo kiedy chciałem wyłączyć generator dźwięku, to wyłaczenie nie działo się dokładnie w momencie, w którym o to poprosiłem. Musiałem więc poczekać, aż wątek na pewno skończy działanie, zanim znowu będę chciał skorzystać z dźwięku (co jak widać po błędach “Mixer already opened” nadal nie zawsze przed tym chroniło).

Do tego usunąłem:

audioContext.stop();

Które o ile było potrzebne wcześniej, bo zatrzymywało odgrywany dźwięk, to teraz zwyczajnie było źródłem błędów przy generowaniu dźwięku z bardzo krótkimi odstępami.

Sam nie wiem które dokładnie z powyższych naprawiło sprawę, ale teraz byłem w stanie przesłać pomyślnie literę w ciągu 750ms! W dodatku bez żadnego błędu dostępu, również przy przesyłaniu całych zdań i wyrazów.

W porównaniu do ostatniej wersji, gdzie “napis ćwiczebny” przesyłał się 26 sekund, czas przesyłania skrócił się do 12 sekund, w dodatku z mniejszą liczbą błędów transmisji.

napisc

Polecam sklonować z gita i sprawdzić samemu.

Suma kontrolna

Przejdźmy teraz do problemu weryfikacji przesłanych danych.

Skąd odbiorca może wiedzieć, czy nadawca wysłał pakiet informacji i np. po drodze przez zakłócenia nie dotarł kompletny lub przekłamany?

Na przykład, można przesłać takie informacje dwukrotnie, by porównać je po odebraniu. Ale przesyłanie informacji dwukrotnie to spore marnotrawienie czasu.

Co innego można więc zrobić?

Dołączać do wiadomości sumę kontrolną.

Za Wikipedią:

Suma kontrolna (ang. checksum) – liczba uzyskana w wyniku sumowania lub wykonania innych operacji matematycznych na przesyłanych danych, przesłana razem z danymi i służąca do sprawdzania poprawności przetwarzanych danych.

Wspomniane działania matematyczne mało nas interesują, sami lepszego algorytmu niż już stworzone nie wymyślimy, a sam fakt, że to po prostu nasz pakiet danych (w postaci zer i jedynek, bitów) po działaniach matematycznych, które mają na celu jak najmniejsze ryzyko kolizji powinien nam wystarczyć. Czym jest kolizja i jak długa ma być suma kontrolna?

Im większa suma kontrolna tym mniejsze prawdopodobieństwo kolizji, tj. sytuacji, kiedy ta sama suma kontrolna pasuje do więcej niż jednego układu bitów z których jest obliczana. Powiedzmy, że w trakcie przesyłu kilka zer zostanie odebranych jako jedynki i akurat dla takiego układu suma kontrolna przesłana przez nadawcę również będzie pasowała, wtedy odbiorca po obliczeniu swojej sumy kontrolnej i porównaniu z otrzymaną pomyśli, że dostał prawidłowy pakiet.

No dobrze, jak więc obliczyć tę sumę kontrolną? Szczegóły tego również nas nie obchodzą – Java udostępnia klasę która z podanej na wejściu tablicy bajtów utworzy 32 bitowej długości sumę kontrolną (algorytmem CRC, bo trzeba wiedzieć, że są też inne, nawet bardzo prymitywne sumy kontrolne jak bit parzystości).Tę sumę możemy dołączyć do naszej wiadomości.

crcWykonanie powyższego kodu wygeneruje na każdym komputerze ciąg 101110010001101101010101011110

Tu pojawia się kolejny problem:

Dla przykładu, 32 jedynki i zera – 10001111110100001001111011100111 – to trochę sporo do przesłania dźwiękiem. Rozwiążemy to znowu zmieniając system liczbowy – tym razem na szesnastkowy.  Podana wyżej liczba jest zapisana binarnie, natomiast zapisana hexadecymalnie będzie stanowić już tylko 8FD09EE7. Skrócenie 32 znaków do 8 to całkiem dobry wynik. W zasadzie, to skoro już dodajemy 6 liter alfabetu aby przesyłać szesnastkowo, to może dodać całą resztę liter i nie przesyłać ich żadnymi kodami ASCII? Dobry pomysł do wypróbowania, częstotliwości w naszym paśmie nadającym się do nadawania i odczytu powinno wystarczyć.

Checksum od właściwej części wiadomości będziemy oddzielać dodatkową częstotliwością nadawaną tak jak częstotliwość startu i końca wiadomości przez 3 * interwał pomiędzy znakami wiadomości, dla pewności, że odbiorca na pewno dostanie ten znak, bo inaczej źle zrozumie całą wiadomość.

Odchudzamy json

Jak już wspomniałem w poprzednim poście, struktura jsonu wygląda w ten sposób:
json

Dobre pytanie brzmi więc: jeśli oba komputery wiedzą jak wygląda przesyłany model i że pojawiają się w nich ciągi takie jak “jabłko” albo “miecz”, to czy opłacalne jest przesyłanie za każdym razem tych wyrazów? Nie lepiej dla nich zarezerwować jakiejś częstotliwości? (robi się trochę ciasno, jeśli mowa o wykorzystaniu kolejnych częstotliwości w naszym paśmie)

Oczywiście, że tak.

W jaki sposób to zrealizujemy?

Utworzymy tablicę z powiązanymi ze sobą nazwami zmiennych (np. “jabłko”) i odpowiadającymi im częstotliwościami. Następnie, po zamianie obiektu który ją zawiera na ciąg znaków (json), poszukamy w nim jakichś pod-ciągów które będą się pokrywać z tymi z tablicy, jeśli tak, to zamieniamy je na “$” + częstotliwość + “$”. Znak dolara jest po to, aby przesyłając ten ciąg do klasy Broadcaster, klasa wiedziała kiedy ma przesyłać znak jako znak ASCII a kiedy ma do czynienia ze zmienną i ma ją przesłać jako pojedyńczą częstotliwość.

Zrealizowałem to w ten sposób:

model1

Powyżej jest klasa, z której będziemy generować jsona. Jak widać, dużo w niej nie ma, jeśli chcemy dorzucić jakiś klucz z wartością, to dodajemy go metodą putVariable.

Prawdziwa praca znajduje się niżej:

gameinformationf

Tutaj deklaruję odpowiednie nazwy zmiennych wraz z odpowiadającymi im częstotliwościami.

gameinformation2

Jeśli w metodzie translateJSONToSound natrafię na jakąś zmienną zadeklarowaną wcześniej w tablicy, to zamieniam ją na odpowiadającą jej częstotliwość.

Jak to działa w praktyce?

Oto kod, który wrzuca do obiektu z którego generujemy jsona miecz, jabłko, 50 złota i liczbę punktów życia. Do tego generuję z niego sumę kontrolną.

code2

Wynikiem takiego programu jest:

console1

Ale niekoniecznie musimy mieć akurat takie przedmioty w ekwipunku. Json jest bardzo elastyczny, mogę mieć np. tylko jabłko. Wtedy zwrócone zostanie:

console3

Litery składające się z 2-3 znaków decymalnych przesyłam 750-850ms, więc mając do przesłania 7 znaków (zakładam, że mam oddzielną częstotliwość dla ‘{‘, ‘:’,’}’ i cudzysłów włączam do wiadomości już po stronie odbiorcy, bo wiadomo, że nazwa zmiennej będzie objęta cudzysłowiem) plus do tego 8 znaków sumy kontrolnej, powyższy json powinienem przesłać w około 10 sekund. Spora różnica, w porównaniu do pierwszej wersji modemu, gdzie sam pojedyńczy znak przesyłałem ponad 4 sekundy.

Zostało dopisać interpretację kodów pomiędzy znakami dolara przez Broadcaster i Sniffer, ale nie jest to nic specjalnie skomplikowanego a ten post staje się już na to za długi. Zrobimy to przy okazji dorzucania modemu do gry.


W następnej części piszemy grę, zaczynając od pomysłu, wykonania prototypu, a dopiero na koniec połączymy to z modemem, aby można było grać w dwie osoby (chociaż w zasadzie, to gdyby się uprzeć, to tak jak w ramce sieci ethernet można dołączać nagłówek z informacją o tym kto nadaje pakiet i do kogo, grając w ten sposób w kilka osób, chociaż to mocno przedłużyłoby czas wysyłania).


 

Odnośniki:

Jak zawsze kod dostępny jest na GitHubie

https://github.com/dbeef/soundcoding


dsp2017-1

Różne I – Java na olimpiadzie fizycznej

Programowanie jest chyba najbardziej uniwersalnym narzędziem do rozwiązywania problemów naukowych. Od data processingu, gdzie mamy duże ilości próbek pomiarowych po niemożliwe (ze względu na czasochłonność) do zrealizowana ręcznie obliczenia. Umiejętność zrzucenia pracy na komputer daje sporo możliwości.

W tegorocznej olimpiadzie fizycznej w której brałem udział, w drugiej części zadań pierwszego etapu pojawiło się zadanie, co do którego sami autorzy zachęcali do napisania programu w arkuszu kalkulacyjnym lub dowolnym języku programowania i dostarczenie działającej, opisanej wersji.

Za to zadanie dostałem 15/20 punktów, więc ten post raczej mógłby być przykładem dla przyszłych uczestników, jak mniej więcej rozwiązywać zadania numeryczne OF.


Wstęp do problemu

zadanieCałość dostępna jest na stronie KGOF.

Aby uniknąć nieporozumień, oto ilustracja brachistochrony (czerwony tor) znaleziona na Wikipedii:

Braquistócrona.gif

Zacznijmy od krótkiego wstępu fizycznego, dla osób głebiej zainteresowanych tematem, niżej w odnośnikach załączam omówienie problemu i wyników z programu które wysłałem na OF.

Mamy koralik, wiemy że będzie poruszał się po torze opisanym parabolą. Tor z definicji jest zbiorem punktów odwiedzonych przez ciało w czasie ruchu, co oznacza, że do jakiejś chwili t można przypisać punkt na tej paraboli (i na odwrót).

Z twierdzenia o pracy i energii (w układzie mamy siłę tarcia, więc musimy uwzględnić jej pracę w równaniu energetycznym) możemy obliczyć ile energii straciło/zyskało ciało przemieszczając się z jednego punktu toru do drugiego i przeliczyć tę energię na prędkość  ciała.

W podpowiedzi od autora otrzymaliśmy formułę na długość bardzo małego odcinka paraboli, co jest nam potrzebne, bo w przybliżeniu ten odcinek jest linią prostą – a jak liczymy czas poruszania się po prostej, w przybliżeniu ruchem jednostajnym? Zwykłym t = S / V.

Teraz co nam zostało – przeiterować po współczynnikach ‘a’ funkcji kwadratowej i dobrać taki, przy którym suma czasów na wszystkich małych odcinkach będzie jak najmniejsza (i zrobić to dla każdego z podanych współczynników tarcia).

Zostało nam jedno do spostrzeżenia – koralik startuje na pozycji (0,0) a musi dolecieć do (100,-1). Co oczywiste, jeśli nie ma prędkości początkowej, to musi po prostu spaść metr w dół, aby nabrać prędkość do przemieszczenia się 100 metrów dalej. Dlatego odrzucam ujemne współczynniki, bo wtedy koralik musiałby już na początku wzlecieć w górę, co jest niemożliwe.

Teraz, skoro już wiemy to wszystko, przejdźmy do programu.

Kod programu

Program zaczynam od zapisania stałych podanych w zadaniu, dla czytelności kodu, a także zainicjalizowania co ważniejszych zmiennych:

program1

Warto zwrócić uwagę, że w funkcji zapisującej najkrótszy czas, przyrównuję wartość aktualnego czasu z wartością najmniejszego czasu odnalezionego do tej pory. Jeśli aktualny czas jest mniejszy niż najmniejszy zapamiętany do tej pory czas, to zapisuję go do zmiennej najmniejszy czas. Dlatego też na sam start programu zapisuję do zmiennej najmniejszy_czas maksymalną wartość jaką można zapisać w typie double – aby warunek ten był spełniony przynajmniej raz.

Następnie przyjmuję kilka kluczowych stałych od użytkownika. Nie ma tu zbyt wiele finezji:
inputPo przyjęciu danych, przechodzę do pętli która iteruje od współczynnika początkowego do końcowego, dodając przy każdym przejściu bardzo małą deltę – 0.001. Dobieram następnie do otrzymanego tak współczynnika ‘a’ pozostałe współczynniki (współczynnik c jest zawsze zerowy).  Warte podkreślenia jest, że sprawdzam, czy mam do czynienia z funkcją liniową (a = 0) nie poprzez operator porównania, a dopisaną przeze mnie funkcją – robię to, ze względu na to, że komputery mają kłopoty z reprezentowaniem ułamków binarnie (binarny to w ogóle słaby system na ułamki), odsyłam zaciekawionych poniżej:

https://docs.python.org/3/tutorial/floatingpoint.html

a także do:

http://fulmanski.pl/news/materials/fpn.pdf

Za każdym przejściem pętli uruchamiam również funkcję obliczającą czas przejścia dla zadanej paraboli.

petla whileWspomniana pętla while.

iszero

Wspomniana funkcja isZero.

W tym momencie przechodzimy do wisienki na torcie – funkcji obliczającej czas przejścia. Jest tu dużo matematyki, a w zasadzie zero wartych odnotowania sztuczek programistycznych. Zainteresowani mogą zajrzeć w kod (jest w odnośnikach) i rozwiązanie które wysłałem.

Warte odnotowania jest natomiast to, jak dużo obliczeń wykonuje program, aby znaleźć ten najlepszy wynik.

Powiedzmy, że na start programu podaliśmy współczynnik początkowy a = 0 i końcowy a = 2. Ponieważ nasza delta a jest równa 0.001, to do zbadania mamy 2000 funkcji kwadratowych. Musimy więc 2000 razy obliczyć współczynniki funkcji kwadratowej i pochodną. Dla każdej z tych 2000 funkcji, mamy do przejścia od x początkowego = 0 do x końcowego = 1, ale delta x jest równa z kolei 0.0001f, czyli do przejścia mamy 10000 punktów, a dla każdego z nich liczymy wartość funkcji, równanie energetyczne, prędkość, odcinek drogi i na końcu – czas przejścia przez tę drogę.

Po włączeniu programu, podaniu ‘a’ początkowego = 0 i ‘a’ końcowego = 1 oraz także tarcia beta 1, obliczenie współczynnika ‘a’ paraboli najkrótszego czasu zajęło prawie 60 sekund (przy podanych wcześniej w poście deltach).

Teraz wyobraźcie sobie zrobienie tego wszystkiego ręcznie, na papierze.

Odnośniki:

PDF z fizycznym spojrzeniem na problem i omówieniem wyników programu, który załączyłem do zgłoszenia do OF:

T4-Daniel-Zalega-OF66-I-283-nowe

Repozytorium z kodem, instrukcją uruchomienia i jarem, który wysłałem do OF:

<redacted>

Moje rozwiązania zadań doświadczalnych dla drugiego zestawu zadań wraz z punktacją (żeby wiedzieć na ile można się sugerować moją odpowiedzią), być może komuś się przyda:

D3-Daniel-Zalega-OF66-I-283   10/40

Zadanie D1 Daniel Zalega OF66-I-283   24/40


dsp2017-1