czwartek, 18 sierpnia 2016

Historia Kulki Animowanej

Jest to wpis o historii pewnej Kulki. Nie jest to jednak zwykła kulka bo to historia kulki animowanej i to animowanej javascriptem. I nawet ona spada według jakichś tam praw fizyki i nawet za sobą zostawia ślad (rysowany też pośrednio javascriptem). Jest to kulka w javascripcie ale bez pisania w javascripcie.

Podążając za poprzednim dającym (na opewno mnie) nadzieję na łeba wpisem "Scala.js taka piękna" cały javascript jest wygenerowany przy pomocy Scala.js. I (jak dla mnie) kod jest fajnie. Bo są typy. I Jest fajnie.

Kino 8D

Czcionka na gifach jest mała dlatego trzeba w nie kliknąć albo skrolem powiększyć. Na pierwszym przeźroczu widać, że wielkość kulki można konfigurować i ona tak rośnie i maleje wtedy od razu. Do tego na bieżąco liczona jest masa tejże kulki z założeniem, że gęstość to średnia gęstość meteorytu czyli 3.4 g/cm^3

Jest tam także validacja pola gdzie można wprowadzić promień kulki. A, że rrrr to nie jest najlepsza wartość dla promienia dlatego warto ją sprawdzić. Tutaj dla podkreślenia możliwości Scali.js do validacji została użyta Monada. Jest to tylko Try ale zawsze Monoada®© Zawsze można się (nie)znajomością teorii kategorii popisać przed kolegami od front-endu.

No i w końcu "leć Adam leć". Zwróćcie uwagę, że wraz z czasem odległość pomiędzy czerwonymi kropkami się zwiększa a to dlatego, że działa przyspieszenie i składowa y prędkość się także powiększa z czasem co obrazuje wydłużająca się pionowa strzałka symbolizująca pionowa składową wektora prędkości.Dokładnie tak. W symulacji użyte jest przyśpieszenie księżyca 1.6 m/s aby kulka za szybko nie spadła i aby spektakl się za szybko nie zakończył.

"Z życia programisty front-endu"

Cały eksperyment miał za zadanie sprawdzenie "Jak się w tym pisze". Pierwsza ważna rzecz na jaką należy zwrócić uwagę przy zasiadaniu do nowej technologii to czy na StackOverflow są już odpowiedzi do wszystkich zadań

Generalnie te strzałki w symulacji moje nie są jeśli za "moje" uznać napisane własnoręcznie. Po wpisaniu w google arrow canvas javascript mamy ten link :

I na przykład tak to wygląda w javascript (kim czym - jawaskrypcie?)

//Javascript
function canvas_arrow(context, fromx, fromy, tox, toy){
    var headlen = 10;   // length of head in pixels
    var angle = Math.atan2(toy-fromy,tox-fromx);
    context.moveTo(fromx, fromy);
    context.lineTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
      toy-headlen*Math.sin(angle-Math.PI/6));
    context.moveTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
     toy-headlen*Math.sin(angle+Math.PI/6));
}

i naprawdę niewiele trzeba by ten kod zadziałał w Scala.js

// Scala.js
def canvas_arrow(fromx:Int, fromy:Int, tox:Int, toy:Int){
      val headlen = 10   // length of head in pixels
      val angle = Math.atan2(toy-fromy,tox-fromx)
      ctx.moveTo(fromx, fromy)
      ctx.lineTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
       toy-headlen*Math.sin(angle-Math.PI/6))
      ctx.moveTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
        toy-headlen*Math.sin(angle+Math.PI/6))
    }

Jestem w DOMu

Generalnie jedyna niedogodność do jakiej można się doczepić, to że używając "zwykłego DOM" pobieramy generyczne elementy HTML, które trzeba mapowac na konkretne Typy

//Scala.js
//jest troche więcej rzutowania przy pobieraniu elementów
val canvas=document.getElementById("canvas").asInstanceOf[Canvas]
val context=canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]

//ale dalej jest już w zasadzie to samo
context.fillStyle="#FF0000"
context.fillRect(0,0,150,75)

Ktoś przyzwyczajony do Javascript może narzekać, że to jakiś dodatkowy wysiłek ale to jest inwestycja, która zwraca się w dalszej części pracy z tymi elementami kiedy zabezpieczają nas te właśnie typy typy. (No chyba, że ktoś źle zrzutuje wtedy boli)

//Javascript 

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0,0,150,75);

Generalnie można pisać w Scala.js korzystając z dokumentacji do javascript. Ot choćby mechanizmy z tej stronki działają w Scali jak najbardziej : http://www.w3schools.com/html/html5_canvas.asp

Troski o jednostki

Na razie Scala.js goni javascript ale czy może zaoferować coś czego tam nie ma? (ewentualnie jest ale ja nie wiem, że jest). Otóż jak już ogarniemy przyciski i inputy to jest pewien problem poniżej bo trzeba nam np. wyliczyć masę ze wzoru m=ρ * (4/3) * π * r3 gdzie ρ - to gęstość. I teraz weź to wszystko licz przy pomocy double gdy gęstość to g/cm3 a promień jest w metrach. Czy też licz drogę w kilometrach gdy prędkość metr/sekunda a czas symulacji jest w milisekundach by się to jakoś odświeżało.

I w scali np moge stworzyć sobie odpowiednie typy, które będą kontrolowały jednostki. To poniżej nie jest jakimś szczytem modelowania domenowego bo Centy,Mili,Kilo trzeba powtarzać dla każdej jednostki ale generalnie chodzi o eksperyment jak to się sprawdzi w Scala.js (No i dla porównania zmienię formatowanie kodu bo nie mogę się zdecydować czy lepsze jest jaśniejsze czy ciemniejsze.)

object Distance{

  case class Centimeter(v:Double) extends AnyVal {
    def meters = Meter(v/100)
  }

  case class Meter(v:Double) extends AnyVal{
    def centimeters=Centimeter(v*100.0)
  }
  case class KiloMeter(v:Double) extends AnyVal{
    def meters=Meter(v*1000.0)
  }

  implicit def meterToDouble(m:Meter):Double=m.v
}

Pierwsza rzecz to kontrola przy liczeniu objętości kuli. Chcemy utrzymać czyste jednostki bez żadnego przedrostka.

case class Volume(v:Double) extends AnyVal

def sphereVolume(r:Meter):Volume=Volume(4.0/3.0 * PI * pow(r,3.0))

No i drogi czytelniku/czytelniczko mam nadzieję, że zauważyłeś/aś tam ciekawą rzecz, że podajemy bezpośrednio promień który przecież jest typu "Meter" do Math.pow. A to dlatego, że zaimportowaliśmy konwersję z modułu Distance

implicit def meterToDouble(m:Meter):Double=m.v

No i jeszcze jedna rzecz - AnyVal. W Scali kompilator rozpakuje prymitywa i w runtime nie ma żadnego narzutu a w trakcie kompilacji typy zabezpieczają. (to tak jak wypić wódkę i mieć wódkę czy coś takiego)

No i z tego kodu już po kompilacji do js wynika, że to też działa bo bez AnyVal jest wersja gdzie wartość z promienia trzeba dopiero wydobyć (czyli to tak jak mieć ciastko i zjeść ciastko albo tak jak nie płacić abonamentu a i tak oglądać telewizję).

//Z valueclass
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__D__D = (function(r) {
  return (4.1887902047863905 * $uD($g.Math.pow(r, 3.0)))
});

//bez value class
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__Lcom_wlodar_physics_Distance$Meter__D = (function(r) {
  var a = r.v$1;
  return (4.1887902047863905 * $uD($g.Math.pow(a, 3.0)))
});

Rzeczy nowe

Przy okazji takiego programu pojawia się kilka ciekawych wyzwań, których w zwykłych CRUDach nie ma. No bo tam jest taki stan, który wydaje sie wygodny czyli np. aktualne położenie kulki i jakoś monady mnie się tu nie widzą. Można by zawczasu "obliczyć" sumylację i później ją odpalić wtedy niejako kolejne klatki byłyby kolejną wersją niemutowalnego stanu na stosie czy czymś takim i w sumie tak historia ruchu (te czerwone kropki) jest wyliczana.

Ale z drugiej strony na courserze przy okazji zeszłorocznego kursu o Reactive Application było coś o sygnałach i "Functional Reactive Programming". Także też ciekawe.

Testowanie

Tę część z jednostkami to nawet i zwykłym ScalaTestem da się pojechać bo to zwykła taka Scala-Scala. Można Scalachecka użyć do Property Based Testing no i są specjalne narzędzia które testy tłumacza na javascript i odpalają je już na takim "RuntimeDom"

Kulka

Jest pod adresem :

adres kulki.

Niestety jak wstawiłem iframe to blogger mi powiedział, że nie ma tego ficzeru. Generalnie chyba najfajniej wygląda jak wpisze się 100 w promień i 150 w prędkość poziomą. Mozna tez sobie potestować podawanie złych wartości - Try daje radę przy podawaniu liter zamiast cyfr a dalej już nie testowałem.

Jak na kogoś kto ostatni raz javascripta robił w 2013 i teraz taka kulka... no czuję się taki szcześliwy...

Brak komentarzy:

Prześlij komentarz