Zapowiedzianego w ostatnim poście pierwszego próbnego gameplayu nie będzie:
Pomimu kilku prób, mój ThinkPad nie potrafił wydusić z siebie wystarczająco głośnego dźwięku, aby drugi komputer mógł go z powodzeniem odczytać, kończyło się tak, że transmisja była dobra, pomijając w kilku miejsach źle przesłaną cyfrę, co jeśli chodzi o znaki ASCII kończyło się wstawieniem np. znaku pi zamiast dwukropka i w efekcie złym sparsowaniem jsona (modem jest na tyle dobry, że praktycznie zawsze nadawca podsłuchując to co wysyła potrafi to z powrotem sparsować, więc kwestią zostaje głośność).
Prawdopodobnie mógłbym to rozwiązać mikrofonami kierunkowymi, chociaż pozostałby niesmak, że to już nie jest tym, czym było w zamierzeniach (transmisja z urządzenia na urządzenie, bez internetu i specjalnych przyrządów).
Tak czy inaczej, projekt będzie kontynuowany, mam zamiar zakupić pierwszy lepszy kierunkowy mikrofon x2 i nagrać jakiś film, a na ten moment szukam innego wyjścia, w końcu ktoś już napisał podobne aplikacje:
Możliwe, że to kwestia zejścia poziom niżej i napisania własnego wykrywania częstotliwości na podstawie DFT, co nie byłoby takie złe.
W weekend powinien pojawić się nowy post, bo nie sądzę, żebym trafił na ścianę nie do przejścia.
Jak już wspominałem przy okazji posta z serii Pokaż kod, pisząc Speechlist, na początku byłem nastawiony na rozwiązywanie testów głosem. Częściowo mi się to udało, chociaż przeniesienie tego na telefon nie było łatwe, żeby nie powiedzieć – sprawiało kłopoty, dlatego finalnie je porzuciłem na rzecz zwykłego wstawiania tekstu dotykiem. Zostałem z częściowo już napisanym frontendem pod rozpoznawanie głosu, możecie go zobaczyć w działaniu tutaj:
Nie rozpisałem się wtedy co do samego Sphinxa, a to jest temat na większy post.
Czym jest CMUSphinx
CMUSphinx jest opensourcowym narzędziem do rozpoznawania mowy, wspierające C, C++, C#, Python, Ruby, Java, Javascript i rozpoznające m.in. angielski, francuski i niemiecki (chociaż społeczność zbudowała modele rozpoznające także chociażby rosyjski, lista modeli do pobrania jest tutaj).
Wielkim plusem w stosunku chociażby do androidowych usług rozpoznawnia głosu (komunikujących się z serwerami Google) z których można korzystać pisząc aplikacje jest to, że Sphinx działa całkowicie offline.
Do jego działania potrzebne są 3 pliki: acoustic model, dictionary i language model.
Acoustic model zależy od języka i sami go raczej nie stworzymy, pobieramy gotowy z listy którą zamieściłem wyżej. Przykładowo, dla języka angielskiego pobieramy model en-us.
Language model i dictionary generujemy sami za pomocą narzędzi do których odniosę się później, ale można je także pobrać.
Teraz najważniejsze: aby uzyskać niemal 100% skuteczność rozpoznawania, jak na filmiku wyżej, nie używałem gotowego słownika i modelu językowego z ich strony (zawierającego wszystkie słowa). CMUSphinx udostępnia narzędzie, pozwalające zbudować słownik tylko konkretnych słów:
Dzięki temu, że zamieściłem tam tylko te słowa, które muszą być rozpoznane w Speechlistowym teście, poprawność była tak dobra, że trudno uwierzyć.
Przy tym wszystkim warto też dodać, jak niewiele zasobów wymaga Sphinx. Słowniki i modele ważą tyle co nic, a działanie nie wymaga dużej mocy od komputera.
PocketSphinx z którego korzystam na filmiku i w kodzie poniżej, waży 6.7 kB!
Wykorzystanie Sphinxa w przykładzie z filmiku wygląda następująco:
Tworzymy obiekt edu.cmu.sphinx.api.Configuration, aby załadować z plików modele i słowniki.
Tworzymy obiekt edu.cmu.sphinx.api.LiveSpeechRecognizer, dostarczamy mu konfigurację w konstruktorze i stawiamy warunek który skończy rozpoznawanie mowy (np. while(!lastRecognizedWord.equals(“apple“)), w poniższym przykładzie rozpoznawanie trwa przez cały czas (while(true)).
To wszystko. Teraz, we frontendowej części kodu odczytujemy zapisane przez Sphinx słowa i wyświetlamy na ekranie:
There is no need to remove unused words from the dictionary unless you want to save memory, extra words in the dictionary do not affect accuracy.
Z moich doświadczeń wynika, że jest zupełnie odwrotnie. Przekonajcie się sami, zapiszcie sobie kilka słów na kartce, które chcecie wypróbować na Sphinxie, pobierzcie cały słownik z ich strony, wypróbujcie dokładność, a potem zróbcie to samo generując własny za pomocą ich narzędzia, ale wstawiając do niego tylko te słowa, które potem będziecie wymawiać.
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.
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ę:
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:
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.
Wygenerowane 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.
Klasa FileMerger.java którą łączę powstałe pliki .wav.
Klasa FileRecorder.java która jest przerobionym tutorialem Beads ze strony:
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.
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:
Aby przetestować to rozwiązanie prosto w IDE, wystarczy wyedytować konfigurację startową:
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.
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ł:
Prawidł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:
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.
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!
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):
Dot 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.
Rysowanie 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.
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:
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.
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:
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:
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:
Następnie klikam go, czekam chwilę, wyszukuję przycisk buduj i w końcu i jego klikam:
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.
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ę.
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!
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:
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:
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.
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ę.
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.
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:
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.
To 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).
Dla przykładu posłużymy się klasykiem – zdjęciem kaczki. Przed przejściem przez mój pic-glitcher:
Po przejściu:
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):
Tu również psychodelicznie (oryginał glitcha, również może wyświetlać się różnie w zależności od twojej przeglądarki zdjęć):
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.