Różne V – Sphinx i rozpoznawanie mowy

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:

Kod Speechlist udostępniłem za darmo pod adresem: https://bitbucket.org/dbeef/speechlist

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:

http://www.speech.cs.cmu.edu/tools/lextool.html

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!

https://bitbucket.org/dbeef/speechlist/src/990c18c459d5f6b89971fc6b93d24fa4730ba07f/android/libs/pocketsphinx.jar?at=master&fileviewer=file-view-default

Praktyka

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.

configuration

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)).

sphinx2

To wszystko. Teraz, we frontendowej części kodu odczytujemy zapisane przez Sphinx słowa i wyświetlamy na ekranie:

frontend

Cały plik znajduje się pod adresem:
 https://bitbucket.org/dbeef/speechlist/src/990c18c459d5f6b89971fc6b93d24fa4730ba07f/core/src/com/dbeef/speechlist/recognition/SpeechRecognizer.java?at=master&fileviewer=file-view-default

 

Linki:

Repozytorium Speechlist (branch master jest pod rozpoznawanie mowy):

https://bitbucket.org/dbeef/speechlist/src/990c18c459d5?at=master

Artykuł opisujący dostosowywanie CMU pod język polski:

 https://www.researchgate.net/publication/282323232_Tuning_a_CMU_Sphinx-III_speech_recognition_system_for_Polish_language

Dla szukających więcej informacji na temat słowników:

https://cmusphinx.github.io/wiki/tutorialdict/

Mam jednak zastrzeżenie co do ich słów:

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ć.


dsp2017-1

Różne IV – SSTV

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

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

Jak pogadać z ISS

To zdecydowanie najbardziej ekscytująca sprawa:

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

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

Nadawanie obrazów z ISS

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

Kod

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

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

Inne

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

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

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

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

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

Różne III – Bot WWW

Wstęp

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

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

Co więc zrobić?

Piszemy bota

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

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

Od czego zaczniemy nasz projekt w Selenium?

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

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

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

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

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

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

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

To wszystko. Jesteśmy gotowi do pisania kodu.

Kod

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

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

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

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

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

passes

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

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

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

powerplant

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

powerplant2.png

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

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

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

Kod dostępny jest na GitHubie:

https://github.com/dbeef/littlegamebot

Odnośniki:

Polecam sekcję “Legal issues”

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

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

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

Znajdowanie elementów strony dla Selenium:

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

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


dsp2017-1

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

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

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

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

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

Wstęp

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

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

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

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

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

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

Piszemy własny glitcher

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

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

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

Kod razem z gotowym jarem wrzucam na GitHuba:

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

Testujemy glitcher

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

zbrodniarz

Po przejściu:

kopia63

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

zepsutyzbrodniarz

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

zbrodniarz2

copy-436

copy-464

copy-497

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

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

Odnośniki

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

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

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

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

dsp2017-1

Różne I – Java na olimpiadzie fizycznej

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

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

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


Wstęp do problemu

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

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

Braquistócrona.gif

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

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

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

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

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

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

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

Kod programu

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

program1

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

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

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

a także do:

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

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

petla whileWspomniana pętla while.

iszero

Wspomniana funkcja isZero.

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

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

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

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

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

Odnośniki:

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

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

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

https://bitbucket.org/dbeef/66-olimpiada-fizyczna

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

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

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


 

dsp2017-1