Machine Learning (pol. uczenie maszynowe, samouczenie się maszyn, systemy uczące się) jest jedną z aktualnie najlepiej prosperujących dziedzin w świecie IT.
W 1959 Alan Turing i Arthur Samuel amerykańscy pionierzy w dziedzinie gier komputerowych i sztucznej inteligencji ukształtowali współczesny termin machine learning .
The field of study that gives computers the ability to learn without being explicitly programmed.
~ Arthur Samuel
Oznacza to możliwość nauki programu komputerowego na podstawie wcześniej przygotowanych danych w celu rozwiązania konkretnego problemu bez znania sposobu na osiągnięcie go, czyli wcześniejszej listy kroków którą ma wykonać program. Oparte jest to o zestaw gotowych algorytmów z podanych dziedzin matematyki:
Machine Learning działa w oparciu o sieci neuronowe wzorowane na ludzkich. Powyższy schemat sieci neuronowej pokazuje jej uproszczony sposób działania. Składa się ona z neuronów . Każdy neuron jest wprowadzany przez dendryty od innych neuronów przenosząc sygnały otrzymywane z innych neuronów przez synapsy . Na grafice powyżej czerwone neurony stanowią warstwę wejściową, niebieskie – warstwę ukrytą, a zielone – warstwę wyjściową. Ilość ukrytych warstw jest zależna od tzw. głębokości , im głębsza tym bardziej skomplikowane są połączenia między nimi.
Podczas trenowania własnej sieci neuronowej dane są przekazywane od neuronu do neuronu, każdy kolejny może zapamiętywać (Recurrent Neural Network) wynik poprzedniego i przekazywać coraz to lepsze dane. Im więcej razy będziemy trenować taką sieć tym mniej błędów i lepszy wynik.
Ta sieć bardzo dobrze klasyfikuje proste rzeczy, ale nie pamięta o poprzednich czynnościach/zmianach oraz ma nieskończone warianty wyników.
W tym przypadku sieć pamięta poprzednie zmiany i ma skończony wariant wyników.
Ten sposób uczenia polega na stworzeniu modelu danych, wejściowych czyli próbek (samples) wraz z modelem wyjściowym na przykład etykiet. Gdy wytrenujemy naszą sieć będziemy mogli prawidłowo przypisać wyjście dla obiektu którego dotychczas nie było na wejściu. Nasza sieć uczy się w oparciu o nasz model tzw. dataset .
Tym razem sieć nie otrzymuje danych wyjściowych (etykiet), więc sama musi znaleźć odpowiedni sposób, aby uzyskać dane wejściowe.
W uczenie przez wzmacnianie sieć działa bez określonych danych wejściowych i wyjściowych. Jedyne informacje jakie otrzymuje to tzw sygnał wzmocnienia, który może być pozytywny (nagroda) w przypadku podejmowania trafnych decyzji lub negatywny w przypadku mylenia się (kara). Jest to całkiem niezły sposób na naukę naszej AI grania w proste gry bez pomocy człowieka czy nawet zostania mistrzem świata w Go i tworzenia własnych skomplikowanych strategii. Jest to niestety najbardziej wymagający czasu jak i mocy obliczeniowej sposób nauki.
Dwa najpopularniejsze rodzaje problemów to klasyfikacja oraz regresja.
Nasze wejściowe dane łączone są w model na podstawie którego jest określane czy spełnione są odpowiednie warunki czy też nie. Wynik z największym prawdopodobieństwem zostanie nam zwrócony. Na przykład rozpoznawanie ręcznie narysowanych cyfr, w tym przypadku mamy dataset w którym są próbki z zdjęciami narysowanych cyfr oraz etykiety dla każdego z nich. Podajemy nasze dane (zdjęcie) oraz trenujemy sieć używając naszego datasetu i otrzymujemy wynik.
Działa to tak jak wyżej, lecz z jedną różnicą, a mianowicie tutaj otrzymujemy ciąg wyników/tablice z wynikami, a nie tylko jeden wynik. Przykładem problemu regresji może być przewidywanie ceny bitcoina w zależności od jego aktualnego kursu oraz w oparciu o dane z poprzednich miesięcy.
Podsumowując, Machine Learning ma wielki potencjał niemal w każdej dziedzinie, myślę, że ten wstęp powinien przybliżyć Ci jak mniej więcej to działa.
Źródła:
Niektóre z tytułów wręcz są popularne ze swojej prymitywnej S.I.:
Policjanci w GTA.
Wbrew popularnej opinii sztuczna inteligencja policjantów w San Andreas nie jest aż taka prosta, jednak można by było ją trochę doszlifować. W tym artykule rozpoczniemy tworzenie podstawowego zmysłu dla przeciwników – zmysłu wzroku i wprowadzimy sobie prosty interfejs do debuggingu.
Zaczniemy od kodu finalnego z mojego poprzedniego artykułu: Jak programować gry? Najważniejsze elementy w grach 2D. Kod źródłowy z tamtego artykułu znajdziecie tutaj: GitHub
Rozpoczniemy od stworzenia klasy gracza oraz klasy przeciwnika, które nazwiemy odpowiednio
CPlayer
oraz
CEnemy
. Następnie stworzymy prosty kontroler dla obydwu, a dodatkowo w kontrolerze gracza dodamy możliwość poruszania się.
Podobny schemat realizowałem już w filmie o tworzeniu bombermana, jednak przewiduję, że prawdopodobnie go nie obejrzałeś (bo komu chciałoby się oglądać w całości film o tworzeniu gry trwający 2 godziny). Z tego względu jeszcze raz wytłumaczę, co zrobiliśmy:
I) Utworzyliśmy klasę gracza i jego kontrolera:
CPlayer
– klasa gracza
CPlayerController
– klasa kontrolera gracza zawierająca sterowanie
Opisałem kod tak, by było dokładnie widać co krok po kroku trzeba wykonać.
Plik nagłówkowy
Player.hpp
:
Plik źródłowy
Player.cpp
:
II) Utworzyliśmy klasę wroga i jego kontrolera. Póki co nasze A.I. zostawimy w spokoju. Zajmiemy się nim w następnej części tego artykułu.
CEnemy
– klasa wroga
CEnemyAIController
– klasa kontrolera S.I. wroga
Plik nagłówkowy
Enemy.hpp
:
Plik źródłowy
Enemy.cpp
:
III) Wczytaliśmy potrzebne tekstury przy uruchamianiu gry:
IV) Stworzyliśmy gracza i jednego wroga, a następnie dodaliśmy ich do poziomu:
Sztuczna inteligencja w grach jest oparta o różne zmysły np.: zmysł wzroku, zmysł słuchu czy nawet czasem zmysł węchu. Najprostszym do zaimplementowania jest zmysł wzroku, który jak zapewne łatwo jest się domyślić, będzie odpowiadał za widzenie. Będzie on działał w ten sposób, że będzie sprawdzał, czy jest coś interesującego w zasięgu wzroku oraz w obszarze widoku (kąt widzenia zrobimy łatwy do dostosowania) i jeśli coś wykryje, to zwróci to w wyniku.
Na początku, jako że wszystkie zmysły będą miały kilka wspólnych metod i własności, utworzymy sobie klasę bazową. Nazwałem ją IAISense (w swoich projektach przedrostek „I” dodaje do klas bazowych, dostarczających swego rodzaju interfejs do późniejszego rozbudowywania, jednak takich, które nie mogą być bezpośrednio używane). Do implementacji stworzymy sobie osobne pliki –
AISense.hpp
i
AISense.cpp
.
Teraz, zanim przejdziemy do kodu, zastanówmy się, co będzie musiał mieć każdy zmysł. Po chwili rozmyślań doszedłem do wniosku, że:
Będąc świadomym tego, do czego dążymy, utworzyłem następującą klasę bazową:
Warto bardzo dokładnie przeanalizować komentarze, szczególnie ten przy metodzie QueryActors, bo jest to kluczowa metoda tej klasy. Teraz do zaimplementowania został nam tak naprawdę tylko konstruktor (zwróć uwagę, że
QueryActors
jest metodą czysto wirtualną,
GetSensedActors
jest zdefiniowana jako
inline
).
Kod konstruktora jest bardzo prosty:
Pokusiłem się tutaj o referencje na klasę
IPawn
(lub bazową) właściciela. Takie rozwiązanie wymusza podanie go (gdybyśmy użyli wskaźników, ktoś mógłby podać np. pusty wskaźnik), przez co reszta kodu, która się do niego odnosi, zadziała poprawnie.
Następnym etapem będzie stworzenie zmysłu wzroku. Aby mieć jakiś wgląd w sposób jego działania, spójrz na poniższy obraz:
Zatem mamy do stworzenia kolejną klasę, która będzie zawierała kąt i zasięg widzenia oraz implementacje metody
QueryActors
, która sprawdzi, czy aktorzy są polu widzenia. Zdecydowałem, że umieszczę tę klasę również w plikach
AISense.hpp
i
AISense.cpp
by nasz projekt nie miał za chwile 40 plików.
Tak oto wygląda ta klasa:
Tym razem do zaimplementowania mamy konstruktor oraz trzy metody. Może zajmijmy się najpierw konstruktorem:
Nie wrzuciłem ustawienia
m_sightDistance
i
m_sightAngle
do listy inicjalizacyjnej, ze względu na to, że mamy od tego odpowiednie funkcje, które zapobiegają wprowadzeniu nieodpowiednich danych.
Teraz spójrzmy dalej do metod
SetSightDistance
i
SetSightAngle
. Odpowiednie środki bezpieczeństwa są tutaj wymagane. Nie chcemy przecież mieć ujemnego zasięgu lub kąta widzenia > 180 stopni. Warto również zauważyć, że podane funkcje zwracają
true
,
jeśli poprawnie ustawiono wartość a
false
,
jeśli wartość była niepoprawna. Nie będziemy póki co z tego korzystać, ale warto mieć coś takiego zaimplementowanego – może niedługo się przyda
Przyszedł czas na kluczowy moment. Teraz zajmiemy się całą logiką zmysłu. Pomyślmy, co powinien on krok po kroku zrobić:
Problemem jednak jest to, że w poprzednim artykule nie stworzyliśmy sobie metody, dzięki której zyskamy dostęp do listy aktorów ze sceny. Dlatego właśnie potrzebna jest nam poniższa funkcja, którą dodałem do klasy
CLevel
:
Na tych założeniach zbudowałem taką funkcję. Polecam przeanalizować po kolei każdy etap jej działania, gdyż właśnie jesteśmy w punkcie kulminacyjnym tego artykułu.
Uff… już najtrudniejsze za nami. Teraz możemy dodać już zmysł wzroku do wroga.
CSightSense m_sightSense; // Zmysl wzroku wroga
Należy również pamiętać o prawidłowym wywołaniu konstruktora zmysłu z poziomu listy inicjalizacyjnej. Następnie, aby nasz zmysł „działał” musimy go ciągle uaktualniać. Do tego posłuży nam metoda
IActor::Update
, którą sobie przeładujemy. Musimy jednak pamiętać, że metoda ta również ma swoją implementację w klasie bazowej
IPawn
, więc musimy umieścić też odwołanie do implementacji bazowej:
Co nam jednak z tego, że tak się narobiliśmy, a wciąż nie widzimy efektów? Właśnie dlatego teraz zaimplementujemy…
Wyszukiwanie błędów w grach jest bardzo uciążliwe, jeśli przed sobą mamy same cyferki i nic konkretnego. Dlatego właśnie wiele gier dodaje sobie prosty panel debugowania „in-game”. Tym właśnie się teraz zajmiemy.
Na samym początku już wiemy, że potrzebna będzie nam funkcja generująca kształt wycinka koła. Jako że SFML sam jej nie dostarcza, postanowiłem napisać ją sam. Stworzyłem więc plik z deklaracją
SFMLShapes.hpp
oraz plik źródłowy
SFMLShapes.cpp
. Możliwe, że w przyszłości potrzebne będzie nam więcej własnych kształtów i wtedy umieścimy ich generowanie również w tych plikach.
Nazwałem ten kształt Pie (odnosi się do angielskiej nazwy ciasta, ponieważ nasz kształt to jakby wycinek ciasta ).
Powyższa funkcja nie jest w pełni funkcjonalna, jednak wystarcza do podstawowych zastosowań. Jej mankamentem jest to, że używa
sf::ConvexShape
(convex – wypukły). Może to spowodować niechciany efekt przy wyświetlaniu wycinka o kącie > 90 stopni. Jeśli ktoś ma na tyle ochoty, żeby się z tym bawić, to polecam do tego użyć
sf::VertexArray
.
Teraz już możemy wykorzystać ten generator. Pamiętamy jeszcze klasę
IAISense
? Każdy zmysł będzie mógł się popisać jakimś fajnym symbolem przy debuggingu, dlatego też utworzymy metodę dla tej klasy, która będzie rysowała takie symbole na ekranie.
Metoda ta, nie jest metodą czysto wirtualną, bo zmysł ma mieć możliwość wyświetlenia debug info, ale nie jest do tego zmuszany.
Teraz czym do diabła jest to
static bool DebugMode;
?
Dobrym pomysłem jest posiadanie jakiegoś przełącznika, którym będziemy sterowali, by albo włączyć tryb testowy, albo go wyłączyć. Statyczna zmienna
DebugMode
jest właśnie takim przełącznikiem. Oczywiście pamiętamy o tym, że taką zmienną statyczną trzeba też zainicjalizować. Najprościej będzie zrobić to na początku pliku źródłowego:
bool IAISense::DebugMode = true;
Mamy już bazę, to teraz trzeba zaimplementować wyświetlanie informacji trybu testowego dla zmysłu wzroku. Zrobiłem to tak:
Weźmy głęboki oddech… w tym miejscu artykuł ten możemy zakończyć jedną linijką, którą dodamy, by nasz debug info mógł się w ogóle wyświetlić. Jak zapewne się już domyśliłeś, umieścimy ją w metodzie wyświetlającej wroga na ekranie.
m_sightSense.DrawDebug();
W tym artykule zbudowaliśmy sobie podstawę pod dalsze rozwijanie modułu sztucznej inteligencji. Dodaliśmy również możliwość wyświetlania podstawowych informacji debuggingu. Kod z tego artykułu znajdziesz tutaj: GitHub . Teksturki można pobrać tutaj: Mega .
]]>
Uwaga, aby artykuł nie miał setek stron, pominąłem dogłębne tłumaczenie kodu Javy/PHP/SQL. Co nie znaczy, że jeśli jesteś zaciekawionym tematem to nic nie zrozumiesz. Postarał się w miarę możliwości dobrze okomentować kod. W każdym razie czujcie się ostrzeżeni!
Z nielegalnym oprogramowaniem spotykamy się na co dzień – w Polsce jest ono dosyć popularne . Tworząc aplikację, którą chcemy sprzedać komuś, chcielibyśmy zadbać o to, żeby inni nie mieli do niej dostępu. Dlatego programiści starają się wymyślać coraz to nowsze zabezpieczenia przeciw piratom. Chciałbym pokazać jedną z prostych metod zabezpieczenia aplikacji.
Typowe zabezpieczenia:
Do każdego z tych punktów można dopisać jeszcze jedną wadę:
Żadne zabezpieczenie nie jest całkiem bezpieczne
Żadne z tych zabezpieczeń nie odeprze jednego: zmiany kodu programu. Dlatego warto jest korzystać z różnych zabezpieczeń, które modyfikują nam nazwy zmiennych/funkcji w kodzie, aby potem kod był trudny do rozczytania po dekompilacji.
Przed zastosowaniem zabezpieczenia warto przemyśleć, które warto wybrać. Jak każdy wie, stały wymóg połączenia internetowego może być denerwujący dla użytkownika. Jeżeli nie robicie aplikacji, która operuje na połączeniu internetowym, to nie zawsze warto wybierać to zabezpieczenie. Na dzień dzisiejszy najlepszym sposobem wydawać się może jednorazowa aktywacja – ma ją np. Windows, Steam. W tym artykule chciałbym zaprezentować mój sposób na system aktywacji, który przedstawiłem jako nr. 3 w liście wyżej; czyli – aktywacja urządzenia online.
Dlaczego akurat to wybrałem? Gdy myślałem o zastosowaniu mojej aplikacji, stwierdziłem, że zależy mi, aby nie była ona wykorzystywana przez jedną osobę na kilku urządzeniach. Dlatego aktywacja urządzenia wydaje się być rozsądnym wyborem.
Jak to wygląda?
Przy aktywacji urządzenia pierwszą rzeczą, jaka jest nam potrzebna, to pobranie jakiegoś identyfikatora urządzenia. I to jest drobny problem – co można wziąć? W większości przypadków znajdziecie na internecie wykorzystanie adresu MAC karty sieciowej, ale to rozwiązanie jest dosyć słabe – ponieważ w przypadku laptopów jest możliwość, iż aplikacja była instalowana gdy interfejs sieciowy WLAN był włączony, a potem wyłączony. Adres MAC w aplikacji zostanie zwrócony inny i aplikacja
przestanie
działać
. Dlatego polecam np. wykorzystać identyfikator BIOS’u. W Windowsie pobiera się go wpisując komendę w konsoli:
wmic bios get serialnumber
, w przypadku Linuxa sprawa jest nieco trudniejsza, ale i tak są pewne rozwiązania.
Okej – mamy już identyfikator sprzętowy – teraz przydałoby się go jakoś wysłać do serwera i odebrać plik weryfikacyjny. Tak to będzie wyglądać:
Ogólny schemat pierwszej aktywacji prezentuje się tak:
Schemat będzie się nieco
różnić
w przypadku, gdy klient aktywuje drugi raz aplikację na tym samym urządzeniu. Wtedy gdy serwer wykryje że klucz jest już użyty (czyli
used_num>=1
) to będzie sprawdzać tabelę
activations
i będzie próbował znaleźć
hardware_id
taki, jaki wysłał klient. Jeśli znajdzie takowy w tabeli, to oznacza, że klucz jest poprawny i odsyła klientowi klucz weryfikacyjny z kolumny
response_key
.
Przejdźmy do implementacji. Potrzebny nam serwer + baza danych na tym serwerze. Ja wybrałem bazę danych MySQL. Stworzyłem bazę danych o nazwie
Activation
, a w środku dwie tabele:
activation_keys
– w tej tabeli zawarte są wszystkie zaszyfrowane klucze programu i ich liczba użyć
CREATE TABLE activation_keys (id int PRIMARY KEY NOT NULL AUTO_INCREMENT, activation_key varchar(72) NOT NULL, used_num int);
activations
– w tej tabeli zwarte są wszystkie aktywacje, identyfikator sprzętowy i identyfikator klucza z tabeli activation_keys
CREATE TABLE activations (id int PRIMARY KEY NOT NULL AUTO_INCREMENT, id_key int NOT NULL, hardware_id varchar(64), response_key TEXT);
Identyfikator urządzenia będziemy pobierać, korzystając z wyżej wymienionego kodu + hashowania SHA-256.
Klucze będziemy szyfrować algorytmem BCrypt , który generuje maksymalnie 72-znakowy kod. Na obrazku obok jest pokazany schemat BCrypt. Jest to bardzo zalecane , gdyż mając atak hakerów na bazę danych, będą oni w stanie sprawdzić wszystkie dostępne klucze . Ogólnie, BCrypt zamienia klucz do formy zhashowanej. Ten proces trudno odwrócić, tzn.posiadając hash, trudno odnaleźć taki klucz, który po zhashowaniu będzie mieć taką samą formę jak zhashowany. BCrypt często wykorzystuje się do zapisywania haseł użytkowników na wielu stronach internetowych. Klient wysyła do serwera hasło w formie zahashowanej , a następnie jest sprawdzana jego poprawność. W przypadku ataku na bazę danych z hasłami, haker będzie mieć tylko tabelę z hasłami zahashowanymi oraz będzie mieć problem z odwróceniem procesu hashowania. Biblioteki BCrypt znajdziecie w Google.
Uwaga! Np. SHA-1, SHA-256 przestał być bezpiecznym algorytmem i dziś z dobrym komputerem nie ma większych problemów z inwersją. Jeżeli zależy wam na zabezpieczeniu przeciw dobrym hakerom, to polecam poszukać w Google nowych, skuteczniejszych zabezpieczeń. Jednak jeśli chcecie się obronić przed „Script kiddie”, to wystarczy nawet MD5. (nie warto też wrzucać tysięcy kluczy do tabeli, lecz dodawać nowe przy zakupie przez daną osobę)
Do tworzenia kluczy polecam stworzyć program. Ja uznałem że moje klucze będą 16 znakowe. Tak prezentuje się kod w Javie do wygenerowania losowego klucza i zhashowania go. Potrzebna jest też biblioteka BCrypt dla Javy: link .
BCrypt.gensalt(10)
– decyduje o złożoności hasła. Czym większa liczba w argumencie, tym trudniej będzie odszyfrować. Ale też sprawdzanie za pomocą serwera będzie dłuższe.
BCrypt.gensalt()
generuje optymalną wartość, wybraną przez twórcę biblioteki. Warto zauważyć, że duża wartość może być też problemem dla serwera przy sprawdzaniu poprawności klucza.
String AB
– w tym łańcuchu znaków znajdują się wszystkie znaki, które będą mogły znaleźć się w kluczu niezaszyfrowanym.
funkcja
randomString
– zwraca losowy łańcuch znaków o długości
len
, z wykorzystaniem znaków z łańcucha znaków
String AB
.
Dodajmy od razu testowy klucz
1234567890123456.
Aby taki zrobić wystarczy w kodzie zamiast
String key = randomString(16);
dać
String key = "1234567890123456";
Klucze w takiej formie będą przechowywane w tabeli do której nikt nie ma prawa mieć dostępu. Możecie zrobić bazę danych albo po prostu wklejać je do arkusza kalkulacyjnego. Otrzymujemy zaszyfrowany klucz –
$2a$10$SjT/4zh1LwNrsiKCvhUlzec4bAilAsvhw3xb2vpJkRK6eTacCTJFy.
Teraz wpisujemy go do tabeli na serwerze korzystając z komendy SQL
INSERT INTO
.
INSERT INTO `activation_keys`(`activation_key`,`used_num`) VALUES ("
$2a$10$SjT/4zh1LwNrsiKCvhUlzec4bAilAsvhw3xb2vpJkRK6eTacCTJFy
",0);
Czyli zhashowane klucze dodajemy do tabeli SQL, a klucz niezhashowany wysyłamy klientowi. Aplikacja będzie hashować klucz wpisany przez klienta, następnie wysyła go do serwera, który będzie porównywać wszystkie zhashowane klucze.
Okej, więc mamy bazę danych z kluczami gotową. Przejdźmy teraz do pliku weryfikującego licencję. Aby program zadziałał, w swojej lokalizacji musi mieć plik
activation.key
, który po odszyfrowaniu kluczem publicznym RSA wskaże
SHA-256 identyfikatora sprzętowego
urządzenia, na którym jest odpalany właśnie ten program. Ten plik
activation.key
będzie tworzony podczas pierwszej aktywacji (serwer wyśle odpowiedni kod dla klienta, który następnie zostanie zapisany w owym pliku). Trudno stworzyć taki klucz, żeby po odszyfrowaniu kluczem publicznych, dał SHA-256 identyfikatora, dlatego to zabezpieczenie jest w miarę bezpieczne. Serwer, posiadając klucz prywatny RSA może z łatwością stworzyć klucz weryfikacyjny. Rysunek pomoże dobrze zinterpretować to co napisałem:
Dla przypomnienia – schemat szyfrowania RSA . Wszystko pokazane na obrazku wyżej: kluczem publicznym szyfrujemy wiadomość; zaszyfrowanej wiadomości nie można rozszyfrować kluczem publicznym , potrzebny jest do tego klucz prywatny . Szyfrowanie to może też działać w drugą stronę – można szyfrować kluczem prywatnym, a odszyfrowywać publicznym, co dziś wykorzystamy. W naszym przypadku klucz weryfikacyjny będzie szyfrowany kluczem prywatnym na serwerze , a potem klucz będzie odszyfrowywany w aplikacji kluczem publicznym.
Jak stworzyć klucz
RSA
? Do jego stworzenia wykorzystam narzędzie
OpenSSL
, które można pobrać tu dla Windowsa:
http://gnuwins32.sourceforge.net/packages/openssl.htm
Program ten odpala się w trybie konsolowym. Potrzebne nam będzie parę komend:
openssl genrsa -out private_key.pem 2048
– tworzy 2048-bitowy klucz prywatny, o rozszerzeniu *.pem, które dobrze odczyta PHP. Jeśli chcecie lepszy poziom zabezpieczeń, to możecie użyć 3072 bitów.
openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt
– tworzy plik private_key.der w formacie PKCS#8, który lepiej jest odczytywany w Javie (bez kombinowania)
openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der
– tworzy plik public_key.der, który łatwiej odczytać w Javie (bez tworzenia przeróżnych funkcji które zmieniają format klucza)
Są klucze, więc przyszła część na PHP, do którego będzie się zwracał klient. PHP będzie wypisywać proste komunikaty typu „ERROR”, „OK”, „WRONGKEY”, które następnie program wczyta i wskaże odpowiedni komunikat.
Klucz i identyfikator urządzenia będą wysyłane metodą POST. Wynik będzie wypisany funkcją echo, a oto lista możliwych wyników:
Po co te średniki w odpowiedziach? Są one po to aby wydzielić granice klucza weryfikacyjnego. Głębsze wyjaśnienie tego znajdziecie w dalszej części artykułu.
Program desktopowy będzie sprawdzać, jaki kod został otrzymany i wyświetli odpowiedni komunikat.
Na początku stworzyłem sobie dwie funkcje, które kończą wykonywanie pliku PHP i zwracają błąd:
Jeśli jakimś cudem nie ma parametrów HTTP POST lub identyfikator sprzętowy jest pusty to należy zakończyć działanie programu:
Klucz prywatny (private_key.pem) wrzucamy do poprzedniego katalogu niż jest plik PHP. Upewnijmy się, że prawa do pliku są tylko dla serwera i że nie można tego pliku otworzyć bezpośrednio z innego urządzenia.
Teraz należy pobrać klucz prywatny RSA z poprzedniego katalogu, który nie jest dostępny dla nikogo:
Połączenie się z bazą danych i ewentualne zwrócenie błędu:
Pobieramy rekordy z tabeli activation_keys i sprawdzamy, czy klucz jest dobry.
password_verify()
– sprawdza czy klucz w formie niezaszyfrowanej, jest odpowiednikiem zaszyfrowanego podanego w drugim argumencie (BCrypt)
trim()
– usuwa spacje na początku i końcu klucza. (zabezpieczenie, jeśli do bazy danych daliśmy zaszyfrowany klucz z niepotrzebną spacją – mi się to zdarzyło)
Jeżeli klucz nie został jeszcze użyty – czyli
$used
jest równe 0, to przygotujemy klucz weryfikacyjny dla klienta metodą
openssl_private_encrypt($informacja_do_zaszyfrowania, $zmienna_gdzie_zapisze_sie_zaszyfrowany_tekst, $klucz_prywatny_RSA);
Następnie należy powiększyć licznik użycia klucza i dodać identyfikator sprzętowy do tabeli
activations
. Na sam koniec wystarczy zwrócić klientowi klucz weryfikacyjny funkcją
echo
, który następnie klient zapisze do pliku.
Użyte jest to proste zabezpieczenie przed SQL Injection, więcej o tym możecie poczytać w innym artykule na tej stronie: Zabezpieczenia baz danych
Jeżeli klucz jest użyty to należy sprawdzić, czy urządzenie o identyfikatorze wysłanym w HTTP POST występuje w tabeli
activations
. Jeśli występuje to należy klucz weryfikujący wysłać klientowi.
Jeśli po zakończeniu pętli nie odnaleziono klucza, to należy zwrócić błąd – zły klucz.
Cały plik prezentuje się tak:
Możecie stworzyć też prosty plik HTML i porobić parę testów:
W tym fragmencie artykułu będę opisywał stworzenie aplikacji desktopowej Javie, co nie zmienia faktu, że tak samo da się zrobić w innych językach programowania.
Zacznijmy od stworzenia projektu i dodania biblioteki BCrypt. Link do biblioteki: http://www.mindrot.org/projects/jBCrypt/
Stwórzmy najpierw klasę pomocniczą
Activation.java
. Będzie ona pomagać w tworzeniu klucza identyfikatora urządzenia i do odszyfrowania klucza weryfikacyjnego:
Jak pewnie zauważyliście, do pobrania identyfikatora sprzętowego potrzebna jest magiczna klasa WindowsID. Wyżej napisałem, jak w przypadku systemu Windows pobierać identyfikator. Oto ta klasa:
W przypadku innych systemów też znajdziecie różne rozwiązania na pobranie identyfikatora sprzętowego.
Przejdźmy teraz do GUI aplikacji. Na początku można zrobić prosty layout w JavaFX (Java Scene Builder):
A tutaj przegląd obiektów, dla których przypisałem jakieś id. Jest to potrzebne po to, aby już w kodzie Javy móc pobrać te obiekty:
Do katalogu
resources
(jeśli nie wiecie jak dodawać to zapraszam tutaj:
link
) wrzucamy plik
public_key.der
. Startowy plik Javy wygląda tak:
OnActivationListener
zawiera funkcję, która zostanie aktywowana, jeśli aktywacja się powiedzie oraz funkcję pobrania klucza publicznego.
Teraz część kontrolera Aktywacji. W JavaFX, korzystając z kontrolera scen, można łatwo pobierać obiekty GUI dla których przypisaliśmy jakiś identyfikator. Wystarczy podać adnotację
@FXML
a następnie klasę obiektu i jego identyfikator. Bardzo ułatwia to pracę.
Po kliknięciu przycisku submitButton jest on wyłączany (na czas próby połączenia z serwerem) i są wykonywane czynności łączenia. Do funkcji
connectToServer
dodałem 3 argumenty:
keyInput.getText()
– klucz produktu
id
– identyfikator urządzenia
ActivationListener
– informuje o rezultacie połączenia / zwraca wynik wykonania PHP
Interfejs
ActivationListener
wygląda tak:
W odpowiedzi serwera musimy sprawdzić wszystkie przypadki, które wymieniłem wcześniej, czyli:
OK
,
OK_USED
,
WRONGKEY
,
WRONGHARDWARE
,
USED
,
ERROR
i jeszcze przypadek, jak coś innego zostało zwrócone (nieznany błąd). Gdy wystąpi błąd połączenia z serwerm (brak internetu lub serwer jest wyłączony) to zostanie wywołana funkcja z interfejsu
ActivationListener void error();
Błąd połączania wyskoczy jako wyjątek
IOException
, który łatwo wyłapać.
A tak wygląda sama funkcja łączenia z serwerem:
Warto zaznaczyć, że koniecznie trzeba tu użyć wątków. Bez nich aplikacja zacięłaby się na czas łączenia z serwerem.
Tak nie może być!
Dlatego też skorzystałem z prostego wątku
Thread.
Wracamy do interfejsu
ActivationListener
. Co jeśli jest błąd połączenia? Należy wyświetlić odpowiedni komunikat i ponownie włączyć przycisk
submitButton
(bo użytkownik ma prawo ponowić próbę).
Warto zauważyć, iż aktualizując elementy interfejsu z innego wątku, należy skorzystać z funkcji
Platform.runLater
, która aktualizuje GUI z wątku interfejsu graficznego. Bez tego nie da się aktualizować interfejsu.
Przejdźmy teraz do drugiej funkcji interfejsu
ActivationListener.
Serwer zwraca pewien ciąg bajtów, które można zamienić na łańcuch znaków:
String
responseString
=
new
String
(response);
Problem polega na tym, że PHP zwraca niepotrzebne spacje i nowe linie na końcu pliku, dlatego też użyłem średników do wyznaczania początku i końca klucza weryfikacyjnego. Do tego klucz weryfikacyjny jest zapisany w
formie bitów
i zawiera różne
znaki, które po przekształceniu do String’a,
zmienią
swój format. Dlatego, w przypadku zwróconego klucza weryfikacyjnego przez serwer, trzeba na nim operować jako na
tablicy bajtów
. Funkcja
trimKey
obcina tablicę bajtów do pierwszej znalezionej liczby podanej w argumencie 2 oraz obcina tablicę od końca to ostatniej znalezionej liczby podanej w argumencie 2. Średnik to w kodzie ASCII numer 59, dlatego też taki wpisałem do funkcji
trimKey
. Dobry klucz jest pobierany do zmiennej
goodKey
, a do zmiennej
keyFromServerDecrypted
przypisywany jest odszyfrowany klucz weryfikacyjny. Jeśli oba te zmienne mają takie same wartości (
contentEquals
), to należy zapisać klucz zaszyfrowany w pliku
activation.key
. Starałem się okomentować kod, aby był w miarę możliwości zrozumiały:
A funkcja
trimKey
prezentuje się tak:
Na sam koniec wystarczy jeszcze dopieszczyć plik startowy, aby program nie wyświetlał okna z aktywacją przy każdym uruchomieniu. Klucz będzie sprawdzany tak samo jak w przypadku aktywacji, więc nie tłumaczę już kodu. Poniżej fragment kodu z funkcji startowej JavaFX:
Nareszcie koniec ! Cieszę się, jeśli dotrwaliście do tego momentu. Od teraz możecie powiedzieć, że algorytm RSA nie taki straszny, a nasza aplikacja stała się w miarę bezpieczna! Jakbyście czegoś nie rozumieli to zapraszam do komentowania !
Tworząc aplikacje, które dotrą do wielkiej liczby użytkowników, nie jesteśmy w stanie całkiem przeciwdziałać ich niecnym zamiarom okradania nas. Jednakże systemy antypirackie takie jak aktywacja online pozwalają pozbyć się tego problemu nawet w większości przypadków. Warto łączyć różne systemy aktywacji, tworzyć własne, aby były trudniejsze do ogarnięcia .
W tym artykule przekazałem także informacje na temat szyfrowania – jest ono używane wszędzie: na stronach banku , sklepach, mediach społecznościowych. Dlatego kryptografia (utajnianie wiadomości) jest taka ważna. Korzystamy z niej na co dzień! Tworząc stronę internetową warto zadbać o to, aby hasła użytkowników nie były narażone na proste ataki hakerów. Zabezpieczenie haseł nie jest trudnym zajęciem, jednakże nadal sporo osób omija to szerokim łukiem lub używa przestarzałych algorytmów.
]]>
std::array
.
Zakładam, że wiesz co to tablica, prawda? Ot, zbiór elementów jednego typu poukładanych w pamięci jeden za drugim. Nic strasznego, nic trudnego. Jest to bardzo prosta rzecz, można by rzec nawet, że aż za prosta – standardowy C-style array nie posiada w sobie nic ciekawego, żadnych iteratorów, żadnych fajnych metod dostępu do niego, wszystko trzeba robić „ręcznie”.
I tutaj na scenie pojawia się
std::array
z biblioteki
array
, dodany w standardzie
C++11
Ważne – (zazwyczaj) domyślnie każdy kompilator korzysta ze standardu C++03. Żeby skorzystać z nowszych, należy to sprecyzować przy użyciu flagi -std=c++11/14/17 (zależnie od standardu), albo w opcjach kompilatora – ale to zostawiam już do samodzielnego ogarnięcia).
Jest to bardzo przyjemna alternatywa, posiadająca dużą wartość użytkową – po prostu ułatwia programiście życie. No dobra, ale co w tym takiego fajnego? Dlaczego miałbym się przestawić nagle na
std::array
, zamiast używać starego dobrego c-style arraya?
Używanie
std::array
jest prawie takie samo jak używanie zwykłego c-style arraya – losowy dostęp do elementów umożliwia standardowy operator
[]
, ale oprócz tego ten kontener posiada kilka bajerów:
Po pierwsze –
std::array
posiada tak zwany
iterator
. Iterator to obiekt wskazujący na jakiś element z zakresu elementów, który potrafi iterować (czyli, potocznie mówiąc,
„przeskakiwać”
) między elementami przy pomocy na przykład operatorów inkrementacji czy dekrementacji (ale częściej iteratorów używa się ich w range-based for’ach na przykład, o czym za chwilę). Jest to duże ułatwienie w sytuacjach gdzie musimy na przykład przelecieć całą tablicę od początku do końca (lub też od końca do początku, bo
std::array
posiada także reverse iterator!)
Po drugie – posiada przeładowane operatory porównania
==
,
!=
,
<
,
<=
,
>
,
>=
. Czyli możemy w łatwy sposób porównywać dwa std::array’e. Dodatkowo, ma przeładowania dla
std::swap
i
std::get
, gdybyśmy potrzebowali w fikuśny sposób dostać się do danego elementu tablicy lub zamienić jej zawartość z zawartością innej.
Po trzecie – posiada kilka użytkowych metod, takich jak
fill
(wypełnienie tablicy pewną wartością),
front
/
back
(dostęp do pierwszego/ostatniego elementu),
empty
(sprawdzenie czy tablica jest pusta),
size
/
max_size
(sprawdzenie aktualnej i maksymalnej ilości elementów). Jest to też typ agregowalny, czyli przykładowo można go inicjalizować poprzez klamry
{}
, tak jak zwykłą tablicę.
Podsumowując –
std::array
jest prawie taki sam w użytkowaniu jak zwykły c-style array, tylko fajniejszy oraz często wygodniejszy.
Jak już wcześniej mówiłem, można używać
std::array
tak samo jak zwykłej tablicy. Inaczej wygląda deklaracja, ponieważ jest to zwyczajna struktura z szablonowymi typami w konstruktorze:
template<class T, std::size_t N> struct array;
Gdzie T to typ danych przechowywanych w naszym kontenerze, a N to jego początkowa wielkość.
std::size_t
to ogólnie mówiąc typ danych dla liczb całkowitych bez znaku.
Warto o tym pamiętać, polecam przy odnoszeniu się do wielkości kontenerów używać
std::size_t
zamiast int’a czy jakiegokolwiek innego typu danych, oszczędzi nam to ostrzeżeń kompilatora mówiących o porównywaniu typów ze znakiem i bez niego.
Przykładowe deklaracje takich kontenerów wyglądają następująco
No i fajnie, mamy jakieśtam tablice. Co teraz z nimi zrobić? Wykorzystajmy dla przykładu iteratory.
Ten kod chyba wymaga tłumaczenia, szczególnie jeśli ktoś wcześniej nie miał kontaktu z nowszymi standardami C++.
Więc tak – pierwszy przykład wyświetla nam zawartość
std::array
przy użyciu range-based for’a.
const auto &v
to stała referencja do kolejnych wartości kontenera, iterator automatycznie inkrementuje się co iterację pętli. Dlaczego odnoszę się do elementów w ten sposób? Mógłbym to zrobić klasycznie, poprzez
int v
, ale w ten sposób każdy z elementów byłby kopiowany do tymczasowej zmiennej
v
, co w przypadku większych kontenerów stwarzało by duży problem wydajnościowy. Odniesienie się poprzez referencję pozwala na kopiowanie jedynie adresu do elementu, unikając kopiowania jego samego, a
const
uniemożliwia zmianę danego elementu (bo nie chcemy go przecież zmieniać). W tym wypadku jest to na tyle mała skala że w praktyce nie zauważymy żadnych wzrostów wydajności, ale warto wyrobić sobie praktykę używania referencji. Jeśli chodzi o
auto
, to jest to tylko kwestia wygody – ten keyword i tak zostanie zamieniony przez kompilator na odpowiedni typ danych.
W następnym kawałku kodu wypełniamy sobie tablicę bezpośrednio odnosząc się do iteratorów –
b.begin()
wskazuje na początek,
b.end()
na koniec naszego kontenera (analogicznie,
b.rbegin()
wskazuje na koniec, a
b.rend()
na początek). Jak wcześniej mówiłem, iteratory mają przeładowane operatory – inkrementacja, dekrementacja i tego typu rzeczy, które są bardzo wygodne.
Dalej, modyfikacja elementów przy użyciu range-based for’a, tutaj używam referencji bez
const
ponieważ modyfikuję elementy (i w tym wypadku nie mogę odnieść się bez referencji, ponieważ bez niej operował bym na kopiach elementów tablicy)
I na koniec smaczek, przelatuję sobie tablicę od końca co drugi element.
Oczywiście to tylko kilka przykładowych porównań, ale widzimy, że
std::array
jest o wiele bardziej czytelniejszy niż standardowa tablica.
Przyszedł czas na krótkie podsumowanie naszego kontenera
std::vector
I w sumie tyle. Jeśli masz jakieś wątpliwości, nadal czegoś nie rozumiesz, zapomniałem o czymś, masz jakąś sugestię czy cokolwiek – pisz śmiało w komentarzu.
A po pełną referencję i więcej przykładów dla tego kontenera zapraszam tutaj: http://en.cppreference.com/w/cpp/container/array .
Następny rozdział będzie poświęcony kontenerowi
std::vector.
Jako nastoletni programiści nieraz mamy taki moment, że nie wiemy co mamy robić. Kolejna strona/program?
Brak pomysłów
! Więc czemu by nie sprawdzić siebie w zleceniach? W tym artykule chciałbym omówić tę kwestię.
Zlecenia to dobry sposób na nabycie doświadczenia.
W Polsce najpopularniejszym serwisem od zleceń dla programistów z pewnością jest Oferia.pl . Na tej stronie znajdziecie pełno zleceń – od budownictwa, grafik, transportu, aż po właśnie programowanie. Aktualnie jest 1300+ zleceń, które możemy wykonywać. Myślę, że każdy może tu znaleźć coś dla siebie – najwięcej zleceń jest z PHP, C++, C#, HTML i Javy.
– jest dwuetapowa. Najpierw należy zrobić konto, podając adres email i hasło, a następnie po aktywacji przystąpić do konfiguracji konta. Co ważne – nie musicie mieć 18 lat ! W całym regulaminie Oferii nie ma wzmianki o wieku. Co więcej, nie musicie go nawet podawać! Z rejestracją jest tylko jeden mały problem – nasze dane osobowe będą widoczne w Google . Niektórym będzie to przeszkadzać, ale cóż, z tym nie da się nic zrobić. Podczas rejestracji należy podać imię, nazwisko, telefon i adres zamieszkania.
Jak to z legalnością? – tutaj w większości zależy od zleceniodawcy. Zdecydowana większość zleceniodawców woli nie mieć żadnych umów . Większość tutaj kontaktuje się przez waszego maila, całkowicie omijając potem serwis Oferia – co najwyżej potem mogą wystawić wam ocenę, jeśli poprosicie. Zrobiłem już parę zleceń i ani razu nie miałem dalszego kontaktu przez ten serwis, nawet ocen nie dostałem.
„Wielu spotkałem takich ludzi, którzy chcieliby oszukiwać, ale takiego, który by chciał być oszukiwany, nie spotkałem.” – Aureliusz Augustyn z Hippony
Skoro bez umowy, to łatwo nas okraść? – tak, to niestety jest prawda . Oczywiście, możesz wymagać takowej umowy, ale szczerze mówiąc nie trafiłem jeszcze na kogoś, kto by mnie oszukał. A nawet jakby ktoś oszukał, to chciałoby wam się „gonić” za tą osobą? Oczywiście to zależy od ceny zlecenia, lecz na sam początek proponuję nie brać tych droższych – lepiej oswoić się z zleceniami, robiąc coś prostego.
Trafiają się też osoby, które najpierw wymagały mniej, a potem okazało się, że chcą jeszcze „to i tamto”. Wtedy musicie zadecydować – albo robicie w tej cenie, albo cena wzrośnie.
Wracając trochę do wstępu – jak nie macie co robić i robicie w miarę tanie zlecenia, to nawet jeśli dalibyście się zrobić w konia (dość rzadko się to zdarza), to zawsze macie kolejny projekt do portfolio ! Nie każdy ma motywację, żeby robić sobie samemu takie projekty do portfolio, a dzięki takim zleceniom macie ciekawe pomysły + uczycie się nowych technologii! Jest wiele prostych aplikacji, których nie chciałoby wam się robić, bo wydają się wam niepotrzebne, a jest wiele osób, które potrzebują takie aplikacje. Np. studenci często potrzebują prostych zadań – wyświetlanie figur 3D, prosty odtwarzacz muzyki, liczenie kredytów – to dobra okazja, aby nauczyć się jakiegoś API!
Pewnie teraz zapytacie – czyli jak składam ofertę, to muszę umieć perfekcyjnie to, co jest w zleceniu ? – odpowiedź brzmi: nie ! Jeżeli w ofercie jest wymagane używanie jakiegoś frameworka, to przejrzyj go z grubsza, oceń czy dasz radę i po prostu napisz ofertę. Zleceń nie trzeba wykonywać w 1 dzień – nieraz firmy robią dłużej projekty niż osoby prywatne. Skoro się jednocześnie uczysz i robisz zlecenia, to możesz dodać konkurencyjną cenę!
– to jest najważniejsze. Po przeczytaniu waszej oferty zleceniodawcy często patrzą na portfolio. Na Oferii
macie możliwość tworzenia portfolio, jednakże ja polecam
własną stronę
– bo to po prostu ładniej wygląda. Kolejne projekty będziecie tam wrzucać i będzie to wszystko ładnie rosło. Ja jeszcze nie mam, ale musicie uwierzyć mi na słowo.
Bez portfolio trudno dostać dobrą ofertę. Wyobraźcie siebie na miejscu zleceniodawcy – chcecie mieć dobry program/stronkę, a nie wykonaną przez gimnazjalistę w Scratchu. W opisie oferty zawsze odpowiadaj dokładnie na to, co prosi zleceniodawca – tzn. jeśli chce konkretną kwotę, to musisz ją podać! Uwzględnij swoje kompetencje, np. jeśli robiłeś coś podobnego w przeszłości, to wspomnij o tym. Możesz powiedzieć coś o sobie, przytoczyć inne projekty (portfolio). Dodaj, że bardzo chętnie podejmiesz się tego zlecenia i na sam koniec koniecznie podaj adres e-mail – bo, jak mówiłem, większość zleceniodawców woli kontakt przez e-mail niż przez prywatne wiadomości serwisu Oferia. Ogólnie jest dużo osób, które nawet nie czytają zlecenia, kopiują swoją ofertę i wklejają do wielu ofert (z ceną „do negocjacji”) – Ty już jesteś od nich lepszy !
Jak pewnie zauważysz, niektóre oferty są podświetlone – jest to pakiet MAX na Oferii. To już do was należy przemyśleć, czy chcecie taki kupić – z doświadczenia powiem, że bez tego z pewnością znajdziecie zlecenia. Opis i tak jest ważniejszy!
Gdy już znajdziesz odpowiednie zlecenie, to często inni wykonawcy będą podbijać i edytować swoje oferty. Możesz tutaj troszkę oszukać system i napisać coś więcej – ale wtedy licz się z tym, że zleceniodawca będzie wymagał od Ciebie profesjonalizmu. Pamiętaj też, że najniższa cena niczego nie gwarantuje. Jeśli nie masz portfolio, a ofertę masz kiepską, to najniższa cena nic nie pomoże.
Wysyłajcie wiele ofert – z doświadczenia wiem, że niektórzy zleceniodawcy rezygnują z projektu i do nikogo nie piszą. Jeśli jest jakiekolwiek nowe zlecenie, które Ci się podoba – pisz od razu . Jeśli nawet trafi Ci się potem tak, że napisze do Ciebie paru gości na maila, to po prostu powiesz im, że aktualnie nie dasz rady tego wykonać.
Podczas wykonywania zleceń możecie trafić na osobę, która w przyszłości będzie wam dostarczać zlecenia. Wasze dane osobowe są teraz publiczne, więc może się zdarzyć, że zleceniodawcy do was zadzwonią: ja na przykład otrzymałem ofertę stworzenia systemu dla biblioteki (spis książek, aplikacja webowa i Androidowa). Co więcej – nawet dzwoniła do mnie firma, która szuka programistę Java. Niestety jako iż jeszcze się uczę, a praca miała być na cały etat, to musiałem odmówić.
Inne strony ( żeby nie było, że to reklama Oferii ) – Z innych stron korzystałem też z freelancer.com . Tam jest baaaardzo dużo ofert (w końcu nie tylko polskie), jednak jakoś nie miałem tam powodzenia. Jest tam też dużo większy wybór pakietów (i dużo droższych) + większy limit ofert na miesiąc. Ale spróbować na pewno warto .
Przywołując słowa Alberta Einsteina:
„Każda praca jest dobra, o ile jest dobrze wykonywana.”
starajmy się, aby każde nasze zlecenie było dobrze wykonywane. Czym więcej zadowolonych klientów, to tym więcej doświadczenia i szansa na zauważenie takiej osoby przez znane firmy. Wykonując dobrą robotę, dostaniecie dobrą pracę, więc jeśli jeszcze nie wykonywałeś żadnej aplikacji na zlecenie – spróbuj !
]]>
Całość znajduje się na repo /Vunnysher/GodotBattleCity – ( 01_Movement )
Postaram się odwzorować sterowanie czołgiem używając do tego godotowej fizyki.
Zaczynając, oczywiście tworzę czysty projekt.
Oraz skorzystam z oryginalnych assetów z Battle City. Poniżej źródła:
Z tekstur usunę niepotrzebne tło w gimpie i zostawię przeźroczyste. Dodaję nową warstwę przeźroczystą, ustawiam ją na samym dole. Kopiuję całą zawartość na przeźroczystą warstwę, a starą usuwam. Następnie zaznaczam Różdżką wybrany kolor (w tym przypadku turkusowy), który chcę usunąć i naciskam Delete.
Tworzę hierarchię KinematicBody2D – Sprite2D
Wchodząc w Inspector Sprite’a, wybieram dla niego teksturę, a następnie używając strzałeczki na prawo od tekstury przechodzę do jej właściwości. Odznaczam wszystkie flagi aby piksele się nie rozmywały.
Wracam do Inspectora Sprite’a i zaznaczam flagę Region . Umożliwia ona wycięcie z tekstury wybranego fragmentu. Niżej w Region Rect ustawiam koordynaty czołgu oraz jego szerokość, która wynosi 13 x 13px. I oto widać pojazd:
Pora zrobić sterowanie. W tym celu najzwyczajniej klikam prawym na KInematicBody2D oraz wybierając z menu Add Script. Tworzę nowy plik i wpisuję podany niżej kod.
set_fixed_process(true)
Uruchamia funkcję _fixed_process , która powtarza się 60 razy na sekundę.
if Input.is_action_pressed("akcja"):
Zwraca
true
lub
false
w zależności od tego czy dany klawisz akcji jest wciśnięty.
self.move(Vector(x,y))
Jest to funkcja przeznaczona dla klasy
KinematicBody
( do siebie odnosimy się poprzez
self
),
przesuwa ona ciało obiektu o daną ilość pikseli na klatkę.
Teraz tylko odrobinę powiększę Sprite z czołgiem, ustawiając mu w Inspector Scale na 4,4.
Jeździć jeździ. Ale jakoś tak… bokiem. Zmienię trochę skrypt, alby wycięty był inny obszar tekstury w zależności od kierunku.
onready var
to zmienna której przypisujemy wartość jeszcze przed rozpoczęciem programu.
get_node("scieżka")
pobiera Node do zmiennej.
set_region_rect(Rect2(x,y,width,height))
to funkcja dla klasy Sprite, która zmienia położenie wycinka z tekstury.
Teraz wygląda to tak:
Przydało by się zanimować poruszanie. Zmieniam więc teksturę na tą drugą ponieważ zawiera ona także klatki poruszania się gąsienic. Zaraz po tym aktualizuję pozycje dla Region Rect w kodzie, oraz dodaję do skryptu kilka nowych rzeczy.
Na sam początek zmienna
frameTime
będzie przechowywała czas dla klatek.
Jeżeli jest mniejszy niż 0.1s to jest jedna klatka, jeżeli większy to druga. Gdy
frameTime
osiągnie 0, nadaję mu wartość 0.2s aby go „zresetować” (ostatnie 2 linijki).
set_rot(rad)
to funkcja dla klasy Node2D która ustawia obiekt pod podanym kątem w radianach. Dlatego tez użyłem funkcji
deg2rad(deg)
która zamienia podane stopnie (degrees) na radiany.
set_flip_h(boolean)
odwraca sprite względem osi poziomej ( horizontal ) – użyłem aby odwrócić czołg odpowiednią stroną naświetlenia.
I dzięki tej aktualizacji kodu używamy tak naprawdę tylko dwóch klatek z tekstury.
Tak to teraz wygląda:
Na razie tyle, możliwe że niedługo będzie dalsza część tworzenia klona Battle City.
]]>
Moimi ulubionymi modyfikacjami okazały się dodatki przemysłowe, dodające kolejne stopnie przerabiania surowców oraz maszyny ułatwiające ich przetwarzanie. Każdy, kto chociaż trochę zainteresuje się uprzemysłowieniem produkcji zawsze dobrnie do momentu, w którym ręczne operowanie całą fabryką straci sens, ponieważ nie jest to już takie efektywne jak to, czego się oczekiwało. W takim przypadku z pomocą przychodzą nam komputery.
W tym poradniku przedstawię rozwiązanie architektury klient – serwer na jednej z dwóch najpopularniejszych modyfikacji dodającej maszyny liczące. Będzie to ComputerCraft . W kolejnych poradnikach skupię się na drugiej modyfikacji tego typu – OpenComputers . Ten poradnik skierowany jest do osób, które miały już styczność z językiem Lua oraz tą modyfikacją.
Poradnik rozpocznę od cytatu z portalu Wikipedia.com:
Klient-serwer (ang. client/server, client-server model ) – architektura systemu komputerowego, w szczególności oprogramowania, umożliwiająca podział zadań (ról). Polega to na ustaleniu, że serwer zapewnia usługi dla klientów , zgłaszających do serwera żądania obsługi (ang. service request ).
Najlepszym (moim zdaniem) przykładem takiej architektury jest relacja przeglądarka – serwer WWW . Na jakiej zasadzie działa takie połączenie? Przeglądarka wysyła do serwera żądanie (ang. request ) i oczekuje od serwera odpowiedzi (ang. response ). Kilka lat temu natknąłem się na określenie połączenia typu ping-pong , czyli połączenie polegające na przekazaniu pakietu danych razem z wirtualną „piłeczką”. Określenie osobliwe, jednakże nie odbiega znacznie od głównej idei a może ułatwić początkującym zrozumienie tego typu relacji.
Na obecnym etapie ujawniają się kolejne właściwości tego typu połączenia. Jedną z głównych jest zależność, z której wynika, że jeden serwer może przyjmować żądania od wielu klientów . Co jednak w momencie, w którym serwer otrzyma w tym samym momencie kilka żądań? Jest na to kilka rozwiązań, nie tylko sprzętowych ale również programowych. Czasem jest to dodatkowy serwer kolejki (ang. queue server ), który zostaje umieszczony pomiędzy klientem a serwerem i wysyła żądania serwerowi głównemu w kolejce, w której je otrzymał, bez jego przeciążania, a czasem program uruchomiony w tle , który przetrzymuje żądania w kolejce, a program serwera po kolei może je z niego wybierać. W tym przykładzie pominę to zagadnienie i skupię się głównie na samym połączeniu, bez rozwiązywania problemów z możliwymi przeciążeniami.
Połączenie obu komputerów musi być w jakiś sposób ujednolicone , dlatego utworzyłem protokół (w ogromnym uproszczeniu), który korzysta z API Modemu ( link ). Sama modyfikacja udostępnia jeszcze API nazwane „Rednet” , jednakże pozwala ono tylko na połączenie po modemie bezprzewodowym, a sporządzając swój własny protokół można korzystać nie tylko z bezprzewodowych, ale i przewodowych modemów.
Identyfikacja komputerów w sieci w tej modyfikacji znacznie odbiega od właściwości protokołu TCP/IP . Każdy komputer posiada swój unikalny identyfikator i jest on niezmienny, niezależny od sieci, do której jesteśmy połączeni.
Sam projekt rozpocznę od ramki , czyli „piłeczki”, którą będziemy „rzucali” między komputerami. Obiekt ten musi zawierać wszystkie potrzebne informacje do działania protokołu jak i dane, które będziemy chcieli przekazać dalej. Oto lista:
ID adresata
, czyli identyfikator komputera, z którego wysyłamy ramkę
ID odbiorcy
, czyli identyfikator komputera, do którego docelowo ma dotrzeć ramka
Typ wiadomości
, czyli ciąg znakowy identyfikujący typ zawartości ramki (np. obiekt, ciąg znakowy, liczba, typ logiczny…)
Zawartość
, czyli faktyczna wiadomość wysyłana w ramce
API Modemu pozwala na operowanie na kanałach – odpowiednikach portów w protokole TCP/IP. Chcąc uzyskać podobieństwo do protokołu HTTP , skorzystam z kanału 80 .
Oto konstrukcja obiektu ramki, którym będą wymieniały się komputery:
Cały protokół oparty jest na 2 funkcjach – odbierającej i wysyłającej. Funkcja odbiorcza przyjmuje 3 argumenty. Pierwszy,
wymagany
argument to kanał, na którym program ma nasłuchiwać. Pozostałe dwa argumenty są
opcjonalne
i nie trzeba ich wpisywać. Pierwszy z opcjonalnych to czas, przez który program ma nasłuchiwać do czasu zwrócenia błędu o braku odpowiedzi (ang.
timeout
). Domyślną wartością w tym przypadku będzie
10 sekund
, a maksymalną możliwą do wpisania będzie
120 sekund
. W argumencie tym można wpisać również wartość logiczną
false
, która zablokuje wywołanie timera, a co za tym idzie, nie pozwoli na zwrócenie błędu o braku odpowiedzi. Ta właściwość przyda się podczas pisania programu serwera. Drugi zaś to typ wiadomości, którego program oczekuje. Jeżeli ciąg znakowy wpisany w tym parametrze nie będzie zgodny z ciągiem podanym w ramce, program przystąpi do dalszego nasłuchu. Funkcje te są przystosowane do wykrycia modemu, tak więc w celu uniknięcia błędów/niepożądanego działania lepiej nie podłączać do komputera większej ich ilości.
Teraz można wywołać funkcję odbiorczą wpisująć
receive(kanał[, czas, typ])
. Funkcja
waitForMessage()
jest funkcją wewnętrzną wykorzystywaną przez funkcję
receive()
.
Funkcja nadawcza przyjmuje 2 argumenty. Oba są wymagane do jej działania. Pierwszy argument to obiekt ramki, który program chce przesłać. Drugi argument to kanał, po którym ramka ma zostać przesłana.
Aby wysłać wiadomość, wystarczy sporządzić odpowiedni obiekt żądania według wzoru ramki i wywołać funkcję
send(żądanie, kanał)
.
Obie funkcje można umieścić w jednym pliku i wywoływać je w innym (np. w programie serwera) poprzez załączenie ich jako API. Aby to zrobić, trzeba zapisać je w pliku o nazwie np.
protocol
i załadować je do programu jak w poniższym przykładzie. Potem będzie można odwoływać się do nich jak do metod obiektu.
Modyfikacja udostępnia API serwisu
Pastebin.com
do pobierania skryptów ze strony na komputery w grze. Aby umożliwić jednak korzystanie z tej funkcji, trzeba odnaleźć plik
ComputerCraft.cfg
i zmienić wartość linii
B:enableAPI_http=
z
false
na
true
. Wtedy będzie można skorzystać z tego udogodnienia. W celu pobrania protokołu wystarczy wpisać
pastebin get DfjzLK6R protocol
.
Poradnik przewiduje 2 programy. Pierwszy program ( klient ) będzie przyjmował od użytkownika komendę, po czym nastąpi jej przekazanie do serwera i oczekiwanie na odpowiedź. W przypadku otrzymania odpowiedzi nastąpi jej wyświetlenie, a w przypadku braku odpowiedzi w wyznaczonym czasie zostanie wyświetlony błąd o braku odpowiedzi z serwera. Po wyświetleniu błędu program zada pytanie użytkownikowi, czy ten życzy sobie ponownego wysłania żądania do serwera. Odpowiedź twierdząca powtórzy procedurę wysłania żądania, a przecząca zakończy program. Drugi program ( serwer ) będzie działał w wiecznej pętli, podczas której będzie oczekiwał zdarzenia (ang. event ) otrzymania żądania od klienta, po której nastąpi przetworzenie ów żądania i wystosowanie odpowiedniej odpowiedzi. Po wysłaniu wiadomości zwrotnej program przystąpi od początku do nasłuchiwania.
Klient będzie prostym programem konsolowym, który będzie wysyłał komendy wprowadzone przez użytkownika do serwera i zwracał użytkownikowi odpowiedź serwera. W tym przypadku będą to komendy włączenia/wyłączenia lampki oraz zwrócenia stanu lampki (czy włączona). Do programu przyda się funkcja wysyłająca żądanie i zwracająca odpowiedź. Oto ona:
W funkcji tej utworzyłem obiekt żądania i wypełniłem go potrzebnymi danymi. Adres serwera oraz komendę wpisuje użytkownik podczas działania programu. Następuje teraz
bezpieczne wywołanie
funkcji wysłania żądania funkcją
pcall()
. W przypadku wystąpienia błędu, program nie zatrzyma się, a przekaże błąd do wyniku działania funkcji. Wtedy warunek w linii 10 wykryje takowy, jeżeli tylko się pojawi i wyświetli stosowny komunikat. Analogiczna sytuacja w przypadku funkcji odbierania odpowiedzi. Gdy wszystko będzie w porządku, funkcja zwróci wiadomość w postaci obiektu odpowiedzi.
Zgodnie z algorytmem, w pierwszej kolejności użytkownik musi podać wymagane informacje dotyczące identyfikatora serwera oraz komendy, którą zamierza przesłać. Pętlę rozwiązałem otaczając cały blok wprowadzania argumentu pętlą
repeat ... until
. Jest to pętla będąca odpowiednikiem
do ... while
w językach C-podobnych. W każdym bloku sprawdzam również zgodność typu danych oraz czy nie zwróciło fałszu lub pustej wartości (
nil
). Oto moje rozwiązanie:
Skoro jest już rozwiązane wprowadzanie i wysył/odbiór danych to pozostało oprogramować całość do końca. Trzeba zaimplementować mechanizm wyświetlania odpowiedzi i ponowienia wysłania ramki. Tutaj również użyłem pętli
repeat ... until
. Gdy program wykryje brak odpowiedzi, zaproponuje użytkownikowi ponowienie operacji. W tym przypadku użytkownik ma do wyboru naciśnięcie dwóch przycisków. Naciśnięcie przycisku
T
na klawiaturze spowoduje powtórzenie pętli, zaś naciśnięcie
N
zakończy działanie programu. Otrzymanie odpowiedzi spowoduje jej wyświetlenie i również zakończenie dalszych działań.
Teraz pozostało dołączyć API naszego protokołu i połączyć całą resztę ze sobą. Oto efekt:
Program klienta możemy pobrać na komputer w grze wpisując komendę
pastebin get 04ckzKNf client
Zadaniem serwera będzie kontrola lampki, która poleceniem klienta będzie mogła zostać zapalona lub zgaszona. Klient będzie mógł również poprosić serwer o podanie stanu lampki (czyli czy jest zapalona czy zgaszona). Program serwera rozpocznę od napisania paru funkcji do sterowania lampką:
Jak widać są to dwie funkcje – jedna do włączania lampki i druga do jej wyłączania. Każda z nich przyjmuje argument w postaci ciągu znakowego oznaczającego stronę komputera, po której został umieszczony przewód prowadzący do lampki. Obie funkcje mają ten sam mechanizm działania. W przypadku, gdy wykonywana przez nie operacja powiedzie się, zwracają logiczną prawdę. Każdy inny wynik operacji spowoduje zwrócenie logicznego fałszu. Tak więc jeżeli będziemy chcieli zapalić lampkę a ta będzie już zapalona, otrzymamy
false
.
Ciągi znakowe oznaczające strony to: top, bottom, left, right, front, back . Tłumacząc na język polski w kolejności: góra, dół, lewo, prawo, przód, tył .
Postępując zgodnie ze schematem algorytmu do napisania pozostały jeszcze 2 funkcje – przetwarzania żądania i odsyłania odpowiedzi.
Aby użyć funkcji odbiorczej w programie serwera wystarczy wpisać w argumencie funkcji odpowiadającym za czas oczekiwania wartość
false
. Nastąpi wtedy wyłączenie timer’a, który odpowiada za wywołanie błędu braku odpowiedzi. Pozwoli to programowi na działanie w wiecznej pętli nasłuchu bez konieczności napisania nowej funkcji.
Oto kod pętli programowej serwera. Tak jak napisałem wcześniej – wieczna pętla, w której program nasłuchuje wiadomości i odpowiada na żądania. Ale jak wygląda odpowiadanie i przetwarzanie tych żądań?
callback()
to prosta funkcja przyjmująca jako jedyny argument obiekt odpowiedzi serwera i wysyłający go zgodnie z danymi w nim zawartymi. Zadaniem funkcji
process()
jest przetwarzanie żądania, które otrzymał serwer, wykonywanie polecenia w nim zawartego i wystosowanie obiektu odpowiedzi.
Ułatwiłem sobie trochę zadanie poprzez utworzenie funkcji
prepareResponse()
, która zwraca mi kompletną konstrukcję obiektu, który potem zostanie użyty w funkcji
callback()
. Kod wygląda w ten sposób znacznie schludniej. Pokusiłem się również o dodanie prostej informacji wstępnej z ID komputera oraz stroną z kablem prowadzącym do lampki. Oczywiście stronę można sobie ustawić samemu edytując zmienną
side
.
Tak samo jak w przypadku kodu klienta, kod serwera również można pobrać w grze komendą
pastebin get q88vs6TJ server
Do działania potrzeba oczywiście dwóch komputerów. Każdy z nich powinien być wyposażony w modem. W przypadku modemów bezprzewodowych trzeba pamiętać o ograniczeniu odległości. Moja platforma testowa wygląda tak:
Komputer po lewej ma zainstalowany program klienta, ten po prawej zaś działa już jako serwer, z lampką podłączoną do tylnej ścianki. Oba łączą się za pomocą modemów bezprzewodowych. Najlepiej zacząć od wgrania protokołu oraz programu na komputer, który ma pełnić rolę serwera. Po uruchomieniu serwera trzeba zwrócić uwagę na dwie rzeczy:
Pierwsza rzecz to ID , czyli identyfikator serwera, który potem będzie trzeba wpisać w programie klienta. Druga rzecz to informacja po której stronie powinien znajdować się kabel prowadzący do lampki . Specjalnie dla początkujących spolszczyłem nazwy w programie. Jeżeli ta wartość jest inna niż zamierzona, trzeba zmienić ją w programie.
Podczas instalacji programu klienta jak i serwera należy pamiętać o równoczesnej instalacji protokołu . Bez tego program nie będzie w stanie zadziałać.
Teraz skupię się na programie klienta. Po wgraniu programu razem z protokołem można go uruchomić.
W tym momencie program wymaga wprowadzenia komendy. Jeżeli nie zostanie rozpoznana przez serwer, prześle on odpowiedź zwrotną z informacją o nierozpoznanej komendzie. Dla testu wprowadzę komendę
is_on
, która powinna zwrócić stan lampki.
Tutaj należy wpisać ID komputera, na którym zainstalowany jest program serwera. W moim przypadku ID wynosi 0. Po tym kroku nastąpi wysłanie informacji na serwer i oczekiwanie odpowiedzi. Kolejne okno pojawi się, gdy klient otrzyma wiadomość lub minie czas oczekiwania na wiadomość. W moim przypadku czas ten w protokole ustawiłem na 3 sekundy .
Po lewej stronie klient odebrał wiadomość od serwera, po stronie prawej natomiast tej odpowiedzi nie otrzymał i dał nam wybór na powtórzenie wysłania wiadomości.
Do czego można użyć takiej konfiguracji? Sterowanie jedną maszyną/fabryką z wielu miejsc, system wewnętrznych wiadomości, system kontroli dostępu… Możliwości jest naprawdę wiele. Sam system można naprawdę mocno rozbudować, na pewno w wielu miejscach również poprawić lub napisać na nowo – lepiej, wydajniej. Mam nadzieję, że ten poradnik będzie dla wielu przydatny i że chociaż odrobinę przybliżyłem piękno leżące w komunikacji sieciowej i jej możliwościach. Odpowiem na wszelkie pytania w komentarzach w ramach mojej najlepszej wiedzy. Nie bójcie się pytać. Każdy kiedyś zaczynał, a przykład takiej gry jak Minecraft , gdzie zastosowanie znajdują ogromne ilości nie tylko informatycznych zagadnień, tylko potwierdza regułę.
]]>
Zajmę się tematem języka Go. Czym on jest, dlaczego warto się go nauczyć, jak stworzyć w nim prosty program „Hello World”, jak stworzyć sobie środowisko do pracy z nim i zamieszczę parę skryptów do zautomatyzowania sobie pracy.
Pragnę jeszcze tylko nadmienić, że ten wpis jest skierowany do użytkowników, którzy już potrafią programować w jakimś języku, ale jeśli nie potrafisz, to nie martw się. Do wszystkiego zrobiłem odnośniki, więc nawet jeżeli nie zrozumiesz tego wpisu, to i tak coś stąd wyniesiesz.
Go (często nazywany Golang’iem dla odróżnienia od słowa „go”) jest językiem programowania opracowanym przez programistów z Google: Roberta Griesemera, Roba Pikea oraz Kena Thompsona.
Łączy w sobie łatwość pisania aplikacji charakterystyczną dla języków dynamicznych (np. Pythona, Lispa, C#’a oraz JavaScriptu) z wydajnością języków kompilowanych (np. C, C++).
Kompilacja w Go po pierwszym razie jest bardzo szybka w porównaniu do np. kompilacji w C++. Język ten posiada również wbudowany
Garbage Collector
, który
bardzo
ułatwia zarządzanie zasobami. Składnia Golang’a i budowa kodu nad wyraz przypomina to co znamy z JavaScriptu. Nie ma tu klas, ponieważ byty, które przyjdzie nam zadeklarować, to
instancje
struktur z przypisanymi do ich typów metodami – czyli deklarujemy typ jakiejś struktury, a następnie przypisujemy do jej typu różne metody. Przypomina to trochę tworzenie klasy ze zmiennymi, ale bez metod w C#, a następnie dodanie do tej klasy
metod rozszerzeń
. W Go można także tworzyć
literały struktur
, które bardzo przypominają
literały obiektów
z JavaScriptu. Konstruktorów i destruktorów tu nie ma, ale możliwa jest ich sztuczna implementacja poprzez funkcje czy też, jak już wspomniałem, metody przypisane do typów struktur z wykorzystaniem instrukcji
defer
. Obiektowość w Golangu realizowana jest za pomocą interfejsów.
Go do bólu ułatwia również zrównoleglanie wywołań. Nie trzeba zajmować czasu na wątki, rezerwacje zasobów, mutexy i resztę tego tałatajstwa. Zamiast tego Golang oferuje nam tzw. „goroutines” (które pozwalają na asynchroniczne uruchomienie danej funkcji poprzez dodanie przed jej wywołaniem instrukcji
go
) oraz daje nam kanały, które w bardzo dużym uproszczeniu działają jak wskaźniki. Możesz coś do nich wrzucić w jednym procesie i odczytać w innym. Go wspiera UTF-8, więc nie ma żadnych problemów z polskimi znakami.
Jeżeli doszedłeś/aś do tego etapu, to prawdopodobnie przekonał Cię mój wstęp. Bardzo mnie to cieszy. Teraz pokażę Ci jak zainstalować Golanga. Zrealizuję to w formie instrukcji.
cmd
i wciśnij enter) i wpisz
go version
. Jeżeli pokazuje Ci się podobna informacja do mojej, oznacza to, że wszystko jest okej.
Wiesz już co to Go i potrafisz go poprawnie zainstalować. Czas napisać pierwszy program!
Pamiętasz jak napisałem, że zamieszczę parę skryptów do zautomatyzowania sobie pracy? Oto one:
goCreate.bat nazwaProjektu
– służy do tworzenia nowych projektów
goPackage.bat nazwaPaczki
– tworzy nowe paczki
goHere.bat
– kiedy jesteś w folderze projektu i uruchomisz ten skrypt, projekt będzie gotowy do pracy, a kompilator będzie widział wszystkie twoje pliki.
createGoFile.bat
– goPackage i goCreate wykorzystują go do tworzenia potrzebnych plików.
Wrzuć te pliki do folderu z twoimi skryptami (jeżeli takiego nie masz, stwórz go), a następnie dodaj ścieżkę do tego folderu do zmiennych środowiskowych („PATH”).
Projekty w Golangu należy zapisywać projekty w specjalnej strukturze z folderów (więcej tutaj ). Ręczne tworzenie owej struktury za każdym razem jest niewygodne i może sprawiać problemy początkującym w Go.
Aby stworzyć projekt otwórz konsolę i wpisz
goCreate "tutaj nazwa projektu"
(możesz to zrobić poprzez przytrzymanie shiftu i wciśnięcie prawego klawisza myszy, a następnie wybranie opcji „Otwórz okno polecenia tutaj”). Gdy to zrobisz, wpisz
go install
i voilà, napisałeś Hello World w golang. Czy napewno? No nie do końca. Zrobiły to za Ciebie moje skrypty, jednak zapewne widzisz, że w folderze /src/ powstał folder główny projektu wraz z plikiem main.go. Tak jak w innych językach programowania ten plik steruje całym programem. Zawarta jest w nim funkcja main, która wywoływana jest jako pierwsza.
Przyjrzyjmy się strukturze naszego pliku. Na początku pliku zawsze opisane jest, do jakiego pakietu należy ten plik. Pakietów może być wiele. To coś jak przestrzenie nazw w C++ i C#. Deklaruje je się po prostu pisząc nazwę pakietu. Należy zapamiętać jedynie by zawsze trzymać pliki z danego pakietu w folderze takim jaka jest nazwa jego pakietu. Czyli, gdy dla pliku X.go zadeklarowany jest pakiet Y, to należy umieścić pliki, które mają zadeklarowany ten pakiet w folderze o nazwie Y. W przeciwnym wypadku można napotkać na problemy.
W środku zapisane są importy. W naszym pliku jest tylko jeden import –
import fmt
. Import to instrukcja
preprocesora
, która wkleja nam po prostu kod z danego pakietu (tak samo jak słówko „include” w C++ albo „using” w C#). Pakiet „fmt” to główny pakiet Input/Output języka Go. Coś jak
iostream
.
Na końcu jest już tylko sama funkcja main. Wygląda na bardzo prostą. Nie ma zapisanego typu, który zwraca. Między klamrami jest jedynie instrukcja
fmt.Println("Hello World")
. Ma ona za zadanie wypisać tekst na ekranie. Zawsze, gdy chcemy wywołać funkcje z danego pakietu, to nazwę funkcji należy poprzedzić nazwą pakietu i operatorem wyłuskania (kropki). Po skompilowaniu i uruchomieniu program wypisuje na ekranie „Hello World”.
Jak widać Golang to bardzo prosty i intuicyjny język. Nie miałem zamiaru nauczyć Cię go tu całego, a jedynie pokazać Ci namiastkę tego jak on wygląda w praktyce i co on potrafi. Moim zdaniem jest to świetny język nawet na start i warto się go nauczyć. Nie jest trudny do opanowania i do użytkowania. Warto też się go nauczyć chociażby dlatego, że na każdej platformie ten sam kod Golanga kompiluje się i działa tak samo, a tych platform jest sporo. Posiada on ogrom własnych bibliotek, a w dodatku społeczność, która go rozwija tworzy cały czas nowe.
Jeżeli chciałbyś/chciałabyś dowiedzieć się więcej lub nauczyć się tegoż wspaniałego języka programowania, to polecam Ci tą stronę.
]]>
Dobra, jak się za to zabrać? Zróbmy niebieski ekran, w tym celu do
ApplicationWindow
podpinamy właściwość, jaką jest kolor i ustawiamy na „blue”, teraz trzeba zrobić, aby nasza aplikacja nie wyglądała jak aplikacja, w tym celu dodajemy właściwość
flags
i wartość
Qt.FramelessWindowHint
, kod powinien wyglądać tak:
Teraz wypadałoby zmusić aplikację, aby była na pełnym ekranie. Jest parę sposobów, moim zdaniem najprościej będzie zaimportować moduł od okien
import QtQuick.Window 2.0
, następnie ustalić
width
okna na
Screen.width
i analogicznie
height
.
Teraz potrzebny nam jakiś tekst, w tym celu do
ApplicationWindow
„podłączamy” komponent Text, ustalamy kolor, wielkość, font i inne ficzery.
Całość powinna wyglądać tak:
Nasz program właściwie jest już gotowy, można jeszcze dostosować font, wielkość itp. Co do tzw. deploymentu , czyli doprowadzania aplikacji do stanu, w którym można ją uruchomić na innym komputerze – opiszę ten proces w kolejnym poradniku, jest to głębszy temat.
]]>Przytoczę pewne bardzo obrazowe porównanie. Wyobraź sobie, że jesteś w restauracji. Podnosisz menu, wybierasz potrawę i czekasz na kelnera. Widzisz, że tymczasowo obsługuje innego klienta. Mija kilka, kilkanaście minut, a kelner wciąż stoi przy stoliku innego klienta, obsługuje tylko jego. Dopiero, gdy klient płaci i wychodzi z restauracji, kelner podchodzi do Ciebie i zaczyna cię obsługiwać. W tym czasie przychodzi inny klient, ale kelner jest zajęty tylko tobą. Mniej więcej tak działa aplikacja jednowątkowa (nasz kelner). W oczywisty sposób jest to nieefektywne.
Wtedy niczym superbohater, przybywa wielowątkowość.
Super! A więc zyskujemy trochę wydajności. Jednak rozważmy ten kod w języku C# i przeanalizujmy go.
Co tu się dzieje? Program sprawdza czy plik examplefile.txt istnieje, jeżeli nie, tworzy go. Teraz przejdźmy do tego, dlaczego kod ten nie jest bezpieczny. Jako, że dziś mamy do czynienia z wieloma procesorami (rdzeniami), to w tle mogą działać równocześnie różne programy. Nie ma więc żadnej gwarancji, że jeden z nich nie utworzy pliku o nazwie examplefile.txt właśnie w tym katalogu. Rozchodzi się więc o moment pomiędzy otrzymaniem informacji, że plik nie istnieje, a utworzeniem go. Klasa tego błędu to tzw. Race Condition . Aby uniknąć takich sytuacji, wymagana jest synchronizacja.
Najprostszym używanym niskopoziomowym mechanizmem do synchronizacji są tzw. Spinlocki (w luźnym tłumaczeniu – kręcące się blokady). Jest on w zasadzie aktywną pętlą, która czeka, aż dany obiekt (blokada) zostanie zwolniony. Dziś w zasadzie się z nich nie korzysta, gdyż nowoczesne języki takie jak C# mają wbudowane mechanizmy synchronizujące, takie jak
lock
. Lock działa w ten sposób:
i jest równoważny temu:
gdyż
lock
jest w zasadzie aliasem tego drugiego. Teraz, zanim przejdę do spinlocków, wytłumaczę z pomocą Wikipedii czym jest owa sekcja krytyczna.
Sekcja krytyczna – fragment kodu programu, w którym korzysta się z zasobu dzielonego, a co za tym idzie w danej chwili może być wykorzystywany przez co najwyżej jeden wątek.
Myślę, że ta definicja spokojnie wystarczy. Przejdźmy teraz do Spinlocków.
W C++ można podejść do tego na prawdę prosto, jest to zwyczajnie (jak już pisałem) aktywna pętla. Wygląda to mniej więcej tak (korzystając z biblioteki <atomic> służącej właśnie do synchronizacji, i jak sama nazwa mówi do operacji atomowych):
Dzięki Spinlockom możemy także zsynchronizować uruchomienie wątków, aby wystartowały w mniej więcej tym samym czasie, co można zaimplementować bardzo prosto:
Wówczas nasza blokada może blokować(?) wykonanie wątku tak długo, jak programista sobie zażyczy. Słówka
volatile
użyłem, gdyż nie chce, by kompilator potraktował tę pętle tak:
if (slock != THREAD_COUNT) while(1);
a tego oczywiście nie chcemy.
C# również udostępnia nam mechanizm spinlocków, ba, w zasadzie całą strukturę! Napisałem tu krótki program implementujący Spinlock:
Przeanalizujmy, co się w nim dzieje.
static void Main(string[] args)
. Tworzymy w nim instancję struktury SpinLock, w bardzo standardowy sposób, oraz prywatne pole
int
o nazwie
_count
, którego zadaniem będzie przechowywanie pewnej wartości. Wewnątrz funkcji głównej tworzę prostą pętlę, w niej tworze natomiast nowy obiekt
Task
, który będzie asynchronicznie wykonywał naszą funkcję
vInc()
. Tam widzimy standardowy blok
try finally
, w którym to rozpoczynamy spin SpinLocka. Zauważ, że zanim wszedłem w aktywną pętlę, flagę
lockTaken
ustawiłem na
false
i jest przekazywana przez referencję, nie wartość.
To by było na tyle, jeżeli czegoś wystarczająco nie rozwinąłem, albo macie jakieś pytania, zachęcam do komentowania!
]]>