Ja bym w sumie bardziej dla tego testu postarał się to jednak przerobić, żeby chociaż nie... BO to w sumie faktycznie może i do implementacji włąsnej. ALe np moznaby obracać samą broń.
Implementacja Sapera w Elten API. Program celowo napisałem na dwóch klasach. Jedna - Program_Saper - odpowiada za mechanikę gry, druga zaś - Struct_Saper_Board - za obsługę planszy. Program wykorzystuje nowy sposób definiowania menu oraz map. Ponownie wdzięczny jestem za wszelkie uwagi i propozycje i chętnie odpowiem na wszystkie pytania. Warto zauważyć, że klasa Struct_Saper_Board może działać dla dowolnej wielkości planszy. Zasięg od A1 do J10 (100 pól) jest wpisany w kodzie na stałe, ale zmiana wartości 10,10 przy generowaniu planszy spowoduje zmianę pola gry.
class Program_Saper < Program Name="Saper" Version="1.0" Author="pajper" def main menu=Menu.new { option("New game") { return start } option("Exit") {} } menu.open finish end def start @mines = input_text("How many mines?","numbers").to_i if @mines > 20 alert("Too many mines, setting 20.") @mines = 20 elsif @mines<3 alert("Too less mines, setting 3.") @mines = 3 end @board=Struct_Saper_Board.new(10,10,@mines) game end def game @lr=("a".."z").to_a @ud=(1..@lr.size).to_a @map=Map.new(@board.width,@board.height) {|m| m.on(:move) { t=@lr[m.x].to_s+@ud[m.y].to_s s=@board.status(m.x,m.y) if s==-1 play("list_checked") elsif s!=nil t+=": "+s.to_s end speak(t) } m.on(:key_escape) { m.dispose } m.on(:key_enter) { if @board.checked?(m.x,m.y)==false o=@board.open(m.x,m.y) if o==-1 play("explosion") m.dispose else speak(o.to_s) if @board.completed? play("applause") m.dispose end end end } m.on(:key_space) { s=@board.status(m.x,m.y) if s==nil play("list_checked") @board.check(m.x,m.y) elsif s==-1 play("list_unchecked") @board.uncheck(m.x,m.y) end } } @map.trigger(:move) @map.show main end end
class Struct_Saper_Board attr_reader :initialized, :mines def initialize(x,y,mines) @board= Array.new(x){Array.new(y)} @tried=[] @checked=[] @mines=mines @initialized=false end def width return @board.size end def height return @board[0].size end def make(startx,starty) return if @initialized @mines.times { x,y=-1,-1 x,y=rand(width),rand(height) while x==-1||y==-1||((x-startx).abs<=1&&(y-starty).abs<=1)||@board[x][y]!=nil @board[x][y]=-1 } for i in 0...width for j in 0...height next if @board[i][j]==-1 c=0 for k in i-1..i+1 for l in j-1..j+1 next if k<0 or k>=width or l<0 or l>=height c+=1 if @board[k][l]==-1 end end @board[i][j]=c end end @initialized=true end def check(x,y) @checked.push([x,y]) if !@checked.include?([x,y]) end def uncheck(x,y) @checked.delete([x,y]) end def open(x,y) @tried.push([x,y]) if !@tried.include?([x,y]) make(x,y) if !@initialized return @board[x][y] end def opened?(x,y) return @tried.include?([x,y]) end def checked?(x,y) return @checked.include?([x,y]) end def status(x,y) return -1 if checked?(x,y) return nil if !opened?(x,y) return @board[x][y] end def completed? if (@tried.size+@mines)==(width*height) return true else return false end end end
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Chcę dodać proste API dla high scores. Problemem jest jednak otwartość Eltena i pisanych do niego programów. Umożliwiłoby to wpisanie dowolnego wyniku każdemu, kto rozczyta kod programu i wystuka odpowiednie polecenia w konsoli. Oczywiście można kompilować odpowiednie rzeczy do bibliotek dll, ale EltenAPI miało być proste, a to na pewno do prostych spraw nie należy. Pomysł jest więc taki, że każda aplikacja byłaby podpisywana odpowiednim certyfikatem uzyskiwanym z serwera. Kod uwierzytelniony takim podpisem może wysyłać dane na serwer. Gdy ktoś zmieni kod źródłowy programu, ten nadal będzie działał, ale jego zapytania dodawane do High Scores będą odrzucane. Kod za to odpowiedzialny będzie kompilowany, ale nie ręcznie do aplikacji, a z góry dla Eltena. W menu narzędzi pojawi się odpowiednia opcja podpisywania programów, która wygeneruje plik umieszczany w folderze aplikacji zawierający odpowiedni skrót i jego klucz. Proces będzie taki: W menu narzędzia mamy opcję generowania klucza dla programów. Wybieramy odpowiedni plik skryptu, na którego podstawie utworzony zostanie plik o rozszerzeniu eac. Plik ten zawiera podpisany cyfrowo skrót kodu źródłowego aplikacji. Jednocześnie wygenerowany klucz RSA wysyłany jest na serwer. Od tej pory pliki skryptów podpisywane tym kluczem mają dostęp do odpowiedniego API.
Interfejs OnlineDB jest bardzo prosty.
OnlineDB.load # zwraca tablicę wszystkich wyników OnlineDB.push # dodaje wynik OnlineDB.delete(index) # usuwa wynik
Baza może służyć nie tylko najlepszym wynikom, ale zapamiętywaniu stanów gry itp. itd. Można do niej zapisywać dowolne tablice i tablice haszowe, max. 8192 znaków na rekord. Dane przechowywane są w formacie JSON.
Ograniczeniem interfejsu jest wymieszanie danych, tj. jeśli gra zawiera kilka różnych tabeli, wszystkie są zachowywane w jednej strukturze OnlineTP i muszą być rozróżnialne odpowiednim polem, np. type. Oznacza to, że przy wczytywaniu listy najlepszych wyników trzeba załadować i tak wszystkie stany gier itp. itd. Celowo jednak nie tworzę funkcji działających na wielu tabelach, bo interfejs miał być z założenia prosty.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Dla sygnałów o niskim priorytecie program może przekazać dane wraz z powiadomieniami "Co nowego". Powinno to dotyczyć sytuacji, gdy przekazanie informacji nie jest natychmiastowo konieczne. Sygnały te są odbierane co jedną do trzech sekund. Jest to zatem metoda wystarczająca dla gier typu Szachy czy kościanych, gdzie oczekujemy na ruch przeciwnika, nie nada się jednak w dynamicznych grach multiplayer. Do tych drugich służy interfejs socketów, o którym później, tj. jak go napiszę. :D Proste wysyłanie danych do agenta odbywa się poprzez funkcję send_packet(username, packet). Pakiet może być napisem, tablicą lub tablicą haszową. Jeśli użytkownik ma otwarty w tym samym momencie ten sam program, w jego sesji wykonana zostanie funkcja on_packetreceived(packet). Jeśli użytkownik nie ma uruchomionego programu, a jest to pierwszy pakiet od jego zamknięcia, pojawi się mu propozycja otwarcia aplikacji. Jeśli to zrobi, pakiet zostanie dostarczony. Sample będzie w nowej implementacji Szachów.
Istnieje także funkcja notify_user(username,notification), która pozwala wysyłać powiadomienia. Powiadomienia będą dostarczane tylko wtedy, gdy użytkownik-odbiorca ma także zainstalowaną daną aplikację.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Gniazda user-user: Za pośrednictwem serwera Eltena można zestawić bezpośrednie połączenie między komputerami użytkowników. Zestawienie to wykorzystuje protokół TCP. Użytkownicy są uwierzytelniani przez serwer, stąd też wiadomo, że do socketu podłączył się na pewno ten zaproszony. Prosty interfejs serwer-klient pozwala na tworzenie zarówno sesji dwóch użytkowników, jak wielu więcej.
Uwaga! Gry, które odbywają się turowo (szachy, karty, kości) lepiej jest wykonać na mechaniźmie OnlineDB, niż z użyciem gniazd. Dzięki temu będą dostępne w każdym momencie i nie będzie potrzeba łączenia się komputerów, a więc możliwe będzie kontynuowanie rozrywki nawet w interwałach czasowych.
Schemat połączenia:
srv = ElServer.new # tworzy instancję serwera Zmienna srv posiada atrybut id (srv.id) oraz key (srv.key). Korzystając z nich, każdy może się do serwera przyłączyć: sock=ElSocket.new sock.connect(id, key) Można jednak także zaprosić użytkownika: srv.invite(username)
Po zaproszeniu użytkownika, pojawi się stosowne powiadomienie, a w Eltenie lub po jego przywróceniu z traya ujawni się zapytanie, czy osoba chce przyjąć zaproszenie do danego programu.
Kiedy użytkownik wybierze opcję Tak, wywołana zostanie funkcja programu def self.event_elserver(sock) # sock to gniazdo klienta, o tym zaraz end
Na serwerze w tym momencie wykonane zostanie zdarzenie srv.on(:connect) {|sock| # sock to gniazdo klienta }
Teraz już wysyłanie jest banalne. Korzystając z gniazd mamy do dyspozycji funkcje: write(text) # wysyła napis read(size) # czyta określoną ilość bajtów avail # zwraca, ile bajtów pozostało do przeczytania
Może się wydawać skomplikowane, dlatego w następnym wpisie przedstawię prosty przykład programu.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Program pozwala na zapraszanie użytkowników. Do każdego dołączonego użytkownika wysyłana jest wiadomość powitalna i informacja o każdym nowym użytkowniku. Po otwarciu programu z zaproszenia, odczytywane są po prostu wszystkie wysyłane wiadomości. Mam nadzieję, że przykład zilustruje sprawę.
class Program_ElSockExample Name="ElSockExample" Author="pajper" Version="1.0" def main @srv=ElServer.new @usersocks=[] @srv.on(:connect) {|sock| a="New user connected: #{sock.user}" alert(a) sock.write("Welcome on our happy server, there are #{@usersocks.size} users already connected.\n") @usersocks.each {|u| u.write(a+"\n")} @usersocks.push(sock) } menu=Menu.new { option("Invite new user") {invite} option("Exixt") {finish} } end def invite user=input_text("User to invite") @srv.invite(user) if user_exists(user) end def self.elserver(sock) alert("Connected.") Timer.new(1,true) { loop { alert(sock.read(sock.avail)) if sock.avail>0 break if sock.closed? } } end end
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Możliwość szyfrowania kodu i plików programu. W menu narzędzia pojawi się odpowiedni kreatorek. Mała uwaga z mojej strony. Pliki te będą deszyfrowane z jednego pliku do pamięci RAM. Jest to o tyle ważne, że gdy na przykład plik programu będzie miał rozmiar 128MB, jego ładowanie będzie po prostu bardzo długo trwało. Dlatego ta funkcja powinna dotyczyć raczej małych dźwięków czy plików, do kilku-kilkunastu megabajtów. Jeśli komuś zależy na zaszyfrowaniu większych danych, liczonych w dziesiątkach lub setkach megabajtów, powinien użyć osobnych plików i stworzyć własny algorytm szyfrujący.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Zkończyłem prace nad szkicem koncepcyjnym dla bazy danych aplikacji i postaram się ją jak najszybciej ukończyć, by pokazać efekt. Zasadniczo jest siedem takich baz pod klasą DB: DB.private, DB.private_readonly, DB.protected, DB.protected_readonly, DB.public, DB.public_readonly i DB.shared . Każda z nich działa podobnie, z jednym wyjątkiem shared, ale o tym za chwilę. Baza danych jest z perspektywy użytkownika tablicą, w której można normalnie zapisywać dane, przy czym przyjmowane są wyłącznie tablice, tablice haszowe i Stringi.
DB.public #=> []
DB.public.push("To jest napis")
DB.public[0] #=> "napis"
DB.public jest współdzielona dla całej aplikacji - każdy użytkownik może ją czytać, pisać do niej i ją edytować.
DB.private to baza prywatna użytkownika. Wpisy zapisywane w tej bazie są dostępne tylko dla niego. Może je usuwać, edytować i dodawać. Pozwala ona zapisywać na serwerze różne postępy, stany czy co tam się chce.
DB.shared to baza współdzielona między użytkownikami. Usunięcie z niej danych usunie je tylko dla danego użytkownika. Domyślnie nowy wpis w tej bazie jest widoczny tylko dla tego, kto go stworzył. Aby go udostępnić, należy wywołać
DB.shared.share(id, user) Od tej pory w bazie DB.shared odpowiedni wpis się pojawi.
Nieco inaczej wygląda sprawa z bazą DB.protected. Ta jest bazą o ograniczonym dostępie. Pisać do niej mogą tylko uwierzytelnieni klienci, innymi słowy baza ta wymaga podpisu cyfrowego programu, o tym pisałem wcześniej. Oznacza to, że baza DB.protected działa jak DB.public z tym wyjątkiem, że jeśli ktoś zmieni coś w kodzie programu, przestanie być dostępna.
Ostatnie, co pozostaje, to bazy readonly. Działają one analogicznie do powyższych, ale po prostu... można do nich zapisać dane, ale nie można ich potem usuwać ani edytować.
Jeśli są pytania lub uwagi, chętnie na nie odpowiem.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Elten 2.4 będzie pozwalał na pisanie programów nie tylko w języku Ruby. Trwają prace nad udostępnieniem frameworków dla języka C# i Python, może w przyszłości pojawi się coś jeszcze. Frameworki te będą dawały dostęp do wszystkich najważniejszych sekcji Eltena, a więc interfejsu użytkownika, baz danych aplikacji, sesji, gniazd P2P między użytkownikami, powiadomień itp. Postaram się w ciągu miesiąca zaprezentować jakieś funkcjonalne demo.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
Przy okazji wprowadzenia programu Youtube, stworzyłem proste API do tłumaczeń programów. Mianowicie folder locale. :) Pliki z tego folderu o nazwie języka: pl.mo, de.mo, fr.mo są automatycznie ładowane. I z tej przyczyny warto używać kontekstu - przy nachodzących na siebie tłumaczeniach, priorytet zawsze ma Elten. Dostępne są standardowe funkcje Gettext, będzie przykład w dokumentacji.
#StandWithUkraine
Shoot for the Moon. Even if you miss, you'll land among the stars.
I to jest bardzo dobra wiadomość! Szczególnie Python... Wielu sie ucieszy myślę... Taki to będzie, no... powiew nowoczesności. :D
--Cytat (pajper): Elten 2.4 będzie pozwalał na pisanie programów nie tylko w języku Ruby. Trwają prace nad udostępnieniem frameworków dla języka C# i Python, może w przyszłości pojawi się coś jeszcze. Frameworki te będą dawały dostęp do wszystkich najważniejszych sekcji Eltena, a więc interfejsu użytkownika, baz danych aplikacji, sesji, gniazd P2P między użytkownikami, powiadomień itp. Postaram się w ciągu miesiąca zaprezentować jakieś funkcjonalne demo.
No to mamy interesujacą rzecz. Chcesz pisac programy w c# jk będzie api do tego a nie chcesz jednocześnie pomagać w rozwoju programu, choć prawdopodobnie będzie mobilk jednak w c#