-
Intro
În 2011 am fost la primul meu hackathon. Am fost foarte entuziasmat, compania asta mare și faimoasă venea în România pentru a organiza un hackathon! Compania respectivă era Yahoo!, și da, știu că acum nu pare ceva spectaculos, dar pe atunci era impresionant.
În timpul evenimentului am dezvoltat (împreună cu câțiva prieteni) un mic joc amuzant de snake multiplayer, care, ca orice proiect de hackathon, a fost abandonat în ziua următoare.
Jocul a fost scris în Node.js și a fost primul meu proiect de backend în JS, chiar dacă eram deja un adevărat entuziast al limbajului.
Câțiva ani mai târziu am vrut să revizuiesc proiectul și să văd cam cât de groaznic era scris de fapt. Din păcate m-am lovit de o problema: nu am versionat pachetele așa că nu mai știam versiunea lor și nimic nu mai funcționa. Pe atunci lucrurile erau destul de la început cu Node.js, așa că schimbările de la o versiune la altă aveau tendința să fie substanțiale. Prin urmare, am decis că era timpul să-l refac, doar pentru amuzamentul propriu.
Refactorizarea
Era finele anului 2015, acordul de la Paris urma să puna stop poluării, Volkswagen a fost găsit vinovat de manipularea rezultatelor testelor pentru emisiile de poluare, iar eu refăceam "Tequila worms".
Node.js nu mai era o noutate, devenise o tehnologie consacrată și începuse deja să suporte alternative la JS, sau mai bine spus la EcmaScript. Unele dintre cele mai importante opțiuni erau: TypeScript și CoffeeScript.
Dacă TypeScript oferea tipuri de date statice, lucru care ajută la găsirea unor probleme la compilare înainte de runtime, CoffeeScript avea o abordare mult mai orientată către "cod". CoffeeScript permitea o sintaxa mult mai agreabilă. Recunosc, avea tendința să fie un pic mai dificilă la început, dar arăta super!
Decizia a fost luată, backend-ul urma să fie scris în CoffeeScript, era în mod evident o alternativă mai bună decât TypeScript.
Pentru frontend erau câteva opțiuni, dar era cert că nu voi folosi jQuery, pentru că erau deja disponibile multe frameworkuri interesante. Întrebarea era de fapt: să folosesc React sau Angular?
Credeam cu îndârjire că o librărie de frontend nu ar trebui să folosească backend pentru compilare. Angular era deja la versiunea 1, lucru care denotă stabilitate, un favorit! Avea o versiune stabilă (deci nu avea să se schimbe prea curând), o comunitate importantă în spate și, în plus, era dezvoltată de Google!
React, pe de altă parte, nu era la fel de popular. Avea nevoie de backend pentru compilare, lucru care e un pic ciudat dacă te gândești, era făcută de Facebook, și mai avea și o abordare diferită față de mai popularul MVC.
Angular (versiunea 1) și CoffeeScript au fost câștigătorii! Pot spune că a fost o plăcere să reconstruiesc acest joculeț și, pe lângă asta, am avut ocazia să lucrez cu tehnologii de ultima oră, lucru care a făcut proiectul să fie pregătit să reziste probei timpului!
Rezultatul este aici: https://github.com/claudiu-persoiu/tequila-worms/
8 ani mai târziu...
Acordul de la Paris nu a schimbat lumea așa de radical cum aveam nevoie, Volkswagen a început să facă mașini electrice și hibride, iar aplicația mea nu este tocmai pregătită pentru viitor, nici măcar pentru prezent.
O dată ce Node.js s-a maturizat, dependințele lui au devenit și ele mai complexe și au nevoie din ce în ce mai multă atenție pentru tot felul de actualizări.
Pe de altă parte, Angular a fost complet regândit în versiunea 2, iar visul meu de a face frontend independent de backend a rămas doar un vis.
CoffeeScript a fost înlocuit ușor, ușor de TypeScript și mai există acum ca o bucățică de istorie pentru entuziaști decât ca un limbaj folosit uzual.
Pe pagina de Wikipedia a CoffeeScript, la secțiunea "Adoption", este o frază care descrie foarte bine la ce s-a ajuns:
In data de 13 septembrie 2012, Dropbox a anunțat că au rescris din JavaScript in CoffeeScript tot codul lor care rulează în browser, dar au migrat apoi către TypeScript în 2017.
Concluzii
Chiar dacă Node.js se pare că a fost o decizie bună pentru stack, restul alegerilor făcute demonstrează că nu ar trebui să urmezi sfaturi de la oameni de pe Internet, mai ales nu pe ale mele, pentru că nu am fost deloc pe aproape...
Pe de altă parte, niciuna dintre tehnologii nu a fost o alegere greșită la momentul respectiv, iar să vrei să construiesti frontend fără librării de backend nu era o dorință atât de excentrică pe cât pare astăzi.
CoffeeScript a fost doar ghinionist, deoarece, chiar dacă sintaxa arată mai bine, făcea codul să arate un pic mai greu de citit pentru programatorii proveniți din limbaje cu sintaxa din familia C.
Chiar dacă sintaxa TypeScript nu este mai plăcută, ajută totuși cu verificarea tipurilor de date și rezolvă o problemă reală.
Concluzia poveștii cred că este că nu ai cum să faci o aplicație rezistentă la trecerea timpului dacă te bazezi pe un framework și, mai ales, să nu ai încredere în predicțiile celorlalți, în special nu în ale mele.
--
-
Nota: Titanium si PhoneGap sunt doua platforme pentru dezvoltarea de aplicatii hibride pentru mobil. Aceste aplicatii nu sunt scrise in limbajul de programare nativ telefonului, dar pot fi distribuite pe mai multe platforme mobile.
Recent, am publicat pe Github cea mai noua si extraordinara platforma de teste, Just-Quizzing pe numele ei. Am facut-o ca sa o folosesc eu personal, dar pentru ca am gasit cativa colegi care au considerat-o utila, am facut-o publica.
In versiunea initiala foloseam SL4A pentru a publica aplicatia pe telefonul meu mobil cu Android. Aveam nevoie de un mod in care sa folosesc aplicatia cand sunt in miscare si nu am acces la Intenet. Din pacate nu exista actualizare automata pentru teste. Am sperat ca SL4A va avea o directie mai clara in timp, dar platforma si-a pierdut din elanul initial, iar eu am decis sa scriu o aplicatie mobila.
Am avut de ales intre PhoneGap si Titanium. In trecut am folosit PhoneGap pentru ca marimea aplicatiei era prea mare in Titanium, dar am zis sa ii mai dau o sansa. In 2011, cand am mai scris despre Titanium, marimea unei aplicatii de baza era aproximativ 4M. In ziua de azi nu mi se mai pare chiar atat de mare, desi este maricica.
Perspectiva va fi in special pentru Android, desi aplicatia a fost facuta sa fie compatibila si cu iPhone.
Am folosit SDK 3.1.3, fara framework-ul Alloy.
Alloy are si el curba lui de invatare si am vrut sa o evit. Scopul aplicatiei era sa fie o versiune portabila a testelor de pe server si nu am vrut sa fac o investitie foarte mare de timp.
Timpul total de dezvoltare a fost putin mai mult de o saptamana.
Prima parere
Lucrurile nu merg chiar asa cum te astepti, nu ai foarte multa flexibilitate. Din timpul total alocat, mai mult de un sfert am incercat sa fac un “flyout menu”, asa cum au aplicatiile ca: Facebook, Google Plus si Youtube. A fost un dezastru total! Singurele variante care chiar pareau sa mearga au fost folosirea unui modul extern sau folosirea widgetul-ui din Alloy.
Se pare ca la conversia dintre coordonatele relative, unde utilizatorul a atins elementul curent, la punctul efectiv, unde esti pe ecran, exista o discrepanta mare si este imposibil sa faci un efect decent de deschidere a meniului. Intr-un final am renuntat total la acest efect.
Viteza nu este chiar atat de buna cand vine vorba de randare de elemente. Sincer, ma asteptam la o viteza mai buna. Exista mici trucuri care se pot face, de exemplu sa preincarci view-urile inainte sa le afisezi, dar pentru asta trebuie sa interpretezi cam ce va face utilizatorul in continuare, lucru care nu este posibil mereu.
Un alt elemente care m-a deranjat este ca la apasarea butonului de meniu pe Android nu poti pune o actiune anume, sa zicem sa deschida meniul lateral de care vorbeam anterior. In PhoneGap de exemplu, nu se va deschide nici un meniu, acesta trebuie construit din HTML de la zero.
Cand au aparut, Titanium se laudau ca poti dezolta o data si distribui pe mai multe platforme. Acum am avut surpriza ca nu mai au aceasta abordare. In Kitchen Sink, aplicatia care demonstreaza facilitatile platformei, fiecare tip de platforma initializeaza aplicatia in fisierul ei. Unul dintre motive este ca design-ul se face dinamic daca nu vrei sa folosesti Alloy, nu prin fisiere de layout.
Printre primele lucruri pe care le-am incercat cu Titanium a fost sa vad cat este marimea unei aplicatii simple de “Hello World”. Impachetata, aplicatia ocupa ~8.5M, instalata pe telefon ~16M. Nu stiu ce parere aveti voi, dar mie mi se pare foarte mult! Ciudat este ca aplicatia mea finala ocupa ~8.4M impachetata si ~16M instalata, aproape identic cu aplicatia originala. Motivul este simplu, imaginile puse de mine ocupa cu cativa kb mai putin, rezultand in final o aplicatie putin mai mica decat aceea de la care am pornit.
Dar de ce ocupa o aplicatie Titanium atat de mult?
Am avut de cautat pana am gasit raspunsul. In 2011, cand am incercat sa ma documentez, nu am gasit decat ca sunt impachetate toate librariile chiar daca sunt necesare sau nu si din acest motiv este atat de mare. Nu asta era cauza! Intre timp, am aflat adevaratul motiv. Aplicatia ruleaza folosind un “bridge” intre Android si JavaScript. Codul JavaScript nu este “compilat”, asa cum reiese din prezentarile initiale, ci pur si simplu interpretat.
Codul JavaScript este interpretat folosind unul din motoarele disponibile in aplicatie (V8 si Rhino) si apeleaza componentele care construiesc elementele grafice native.
Problema este la interpretoare (engine). Acestea sunt destul de mari si sunt practic prezente in fiecare aplicatie. Si, de parca nu erau destul de mari, sunt si 3, fiecare cu scopul lui:
- V8 – pentru versiuni de Android noi (dupa Gingerbread 2.3);
- Rhino – pentru versiuni vechi de Android (inainte de Gingerbread 2.3);
- x86 – pentru procesoare de tip Intel.
Aceste 3 engine-uri ocupa in total, dezarhivate, 19M pe calculatorul meu. Deci, fara sa eliminam o parte din ele nu avem cum sa scadem dimensiunea.
O metoda prin care cei de la Appcelerator vor sa scada dimensiunea apk-ului este sa renunte la Rhino, dar in acest mod se va pierde si compatibilitatea cu versiuni mai vechi de Android.
Comunitate
Exista o comunitate foarte activa. Pe forumuri si platforme Q/A este plin de intrebari si raspunsuri! Trebuie sa recunosc ca am fost impresionat din acest punct de vedere. Unele intrebari sunt relativ vechi, chiar si de 3 ani de zile.
Curba de invatare
Pentru o saptamana si aproape jumatate, din care mai bine de un sfert am cautat cum sa fac un efect de miscare al unui view, care pana la final nu a functionat, eu zic ca a mers destul de repede!
Recunosc, m-am mai jucat acum 3-4 ani cu el pentru cateva zile si chiar mi-am amintit anumite lucruri, cum ar fi ca erau elemente de tip window, view si label.
Faptul ca gasesti usor raspunsuri la intebarile de pe Stackoverflow, adunate in cativa ani, dar si ca exista o documentatie destul de bine organizata a ajutat foarte mult.
De asemenea si pe Youtube exista cateva tutoriale utile.
Mediul de dezoltare
Pentru primele versiuni nu exista un IDE, aplicatiile erau scrise cu un editor la alegere, apoi impachetate si testate folosind platforma.
Acum exista un IDE care are la baza Eclipse. Numele acestuia este Titanium Studio. Acesta are un autocomplete destul de reusit, care ajuta in cele mai multe cazuri. Are si cateva lucruri care nu merg prea bine, cum ar fi faptul ca mie uneori nu mi-a mai mers syntax highlight, fara sa-mi indice unde este problema. De asemenea, administrarea proiectelor mi se pare un pic ciudata si am avut cateva probleme la importul si exportul de proiecte.
Nu exista implicit o unealta vizuala de editare a elementelor din fereastra. Atat Xcode cat si ADT permit editarea de elemente cu drag & drop. Se pare ca exista si unelte de acest tip, dar nu sunt gratuite.
Emulatoarele nu sunt foarte configurabile. Partea buna este ca nu trebuie sa-ti bati capul, selectezi sistemul de operare si tipul de display si Titanium va configura automat un emulator. Partea proasta este ca poate mi-ar placea sa am un management al resurselor mai clar.
Un alt dezavantaj este ca uneori aplicatia pur si simplu moare pana porneste emulatorul, iar daca incerci din nou va incerca sa mai porneasca un emulator fara succes, caz in care trebuie inchis emulatorul, rulat Project clean si apoi incercat din nou, cu grija sa nu ratam momentul cand se poate debloca ecranul, sau scenariul de la inceput se va repeta.
Pentru iPhone nu merg log-urile cand vine vorba de OS X Mavericks.
Parti bune
Se invata relativ usor.
Documentatia este destul de bine realizata, iar acolo unde ceva nu este clar, sigur a mai intebat altcineva aceeasi intrebare.
Exista o comunitate destul de mare.
IDE-ul este usor de folosit.
Este usor de vazut cam ce este posibil in aplicatia Kitchen Sink.
Aplicatia nu arata ca o pagina web intr-un WebView!
Parti rele
Aplicatia ocupa foarte mult spatiu (16M pe telefon).
Elementele grafice sunt doar niste subseturi din platformele suportate.
Nu este foarte rapid, din cauza faptului ca avem codul intepretat si nu compilat, ca sa poata interactiona direct cu OS-ul.
Nu exista un editor vizual.
Exista probleme de integrare cu emulatoarele, atat pentru Android cat si pentru iPhone.
Concluzie
Daca esti un cunoscator al limbajului JavaScript, ai nevoie repede de o aplicatie si vrei sa foloseasca elemente grafice native, atunci probabil Titanium este pentru tine.
Daca vrei performanta, probabil trebuie sa te orientezi spre altceva.
Eu nu am suficienta experienta cu SDK-ul Android cat sa pot construi aceasta aplicatie in timpul care l-am avut, acesta a fost compromisul meu.
Sincer, nu sunt sigur daca PhoneGap era o optiune mai buna sau nu, vorbind de cazul meu paticular. Astazi exista multe librarii de JavaScript facute special sa emuleze elementele grafice native intr-o aplicatie PhoneGap. Diferenta nu este neaparat vizibila si, pana la urma, uneori poate aplicatia chiar nu trebuie sa arate ca o aplicatie standard, poate o interfata complet diferita este mai potrivita.
-
Notiunea de closure in PHP, desi a aparut in PHP 5.3, a fost realizata intr-un mod adecvat abia in 5.4, asa cum am mai spus-o si pe blogul meu.
Wikipedia ne spune:
In computer science, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables) of that function.
In PHP nu este un concept foarte popular sau foarte cunoscut. De multe ori acesta este confundat cu Anonymous Functions. In limbajele functionale totusi, acest concept este foarte popular, pentru ca acolo este cu adevarat nevoie de el!
Scheme
Cand Brendan Eich a conceput JavaScript, s-a bazat pe limbajul Scheme si a ajuns sa faca o implementare a acestuia cu o sintaxa de C. Sintaxa C era si este in continuare mult mai populara, iar atunci (1995) limbajul Java era foarte “la moda”.
Sintaxa Scheme este similara cu sintaxa Lisp, in sensul ca se folosesc paranteze in jurul expresiilor pentru a le rula. Operatorii sunt definiti ca si functii si la fel ca o si in cazul functiilor, se pun in partea stanga a parantezei.
Sa luam un exemplu de closure in Scheme:
1(define (make-counter) 2 (let ((count (begin 3 (display "run parent function and return 0") 4 ))) 5 (lambda () 6 (set! count (+ count 1)) 7 (begin 8 (display "inside child function ") 9 count))))
Functia principala seteaza o variabila “count”, cu valoarea 0 si afisaza “run parent function and return 0”, apoi intoarce o alta functie lambda, care incrementeaza variabila definita in functia principala si apoi afisaza “inside child function”.
Functia rezultata din executia functiei principale o stochez intr-o variabila pentru a o putea rula ulterior de mai multe ori:
1> (define counter (make-counter)) 2run parent function and return 0 3> (counter) 4inside child function 1 5> (counter) 6inside child function 2
Cu alte cuvinte, de fiecare data cand apelez (make-couter), acesta va intoarce o functie noua care are acces la mediul in care a fost creata. Daca pare ciudat din pricina sintaxei, promit ca in JavaScript va parea mult mai natural.
Acest concept este foarte interesant pentru incapsulare. Mediul la tipul cand functia parinte este executata se poate incapsula, iar ulterior se va folosi de accest mediu fara grija ca acesta se poate schimba din cauze exterioare.
Pentru limbajele functionale acesta este un concept foarte interesant. Cand vine vorba de limbaje obiectuale totusi, conceptul aproape inutil, pentru ca obiectele au si ele rolul de incapsulare.
JavaScript
JavaScript a fost de la inceput un hibrid, un limbaj functional, orientat obiect, cu mostenire bazata pe prototype. Iar daca acestea nu erau suficiente, sintaxa a fost preluata din Java (C).
JavaScript nu a mostenit multe de la Scheme, dar a mostenit conceptul de closure.
Un motiv pentru care era nevoie de closure in Scheme este acela ca daca o functie nu gaseste o variabila in mediul in care se afla, o va cauta in mediul superior. Sa luam un exemplu:
1(define x 1) 2(define (add-in-env y) (+ x y))
Daca apelam add-in-env cu 2:
1(add-in-env 2) -> 3
Pare la fel de ambiguu ca si in JavaScript, dar nu este tocmai asa. In Scheme sa faci mutatie nu e la fel de usor, simplu si transparent, deci o operatie ulterioara de:
1(define x 2)
va rezulta intr-o eroare.
In JavaScript a rezultat un hibrid. Mutatia este permisa, dar notiunea de a cauta o variabila in mediul in care te afli a ramas:
1var x = 1; 2var add_in_env = function (y) { 3 return x + y; 4} 5 6add_in_env(2); // rezulta 3
Pana aici e ok, dar pentru:
1x = 2; 2add_in_env(2); // rezulta 4
In acest caz, lucrurile scapa foarte usor de sub control.
Dar, ca sa rezolvam problema, putem pur si simplu sa definim variabila in mediul care isi va termina executia (se va inchide = will close):
1var make_counter = function () { 2 console.log("run parent function and set counter to 0") 3 var count = 0; 4 5 return function () { 6 count = count + 1; 7 console.log("inside child function"); 8 return count; 9 } 10} 11 12var counter = make_counter(); 13console.log(counter()); 14console.log(counter()); 15 16var counter2 = make_counter(); 17console.log(counter2()); 18console.log(counter()); 19console.log(counter2());
Outputul va fi:
1run parent function and set counter to 0 2inside child function 31 4inside child function 52 6run parent function and set counter to 0 7inside child function 81 9inside child function 103 11inside child function 122
Chiar daca functia principala si-a terminat executia, mediul din interiorul ei este pastrat ca un closure pentru functia care a fost intoarsa. Doar in momentul in care si subfunctia nu mai are referinte catre ea memoria alocata pentru closure va fi dezalocata.
Chiar daca JavaScript are obiecte, acestea nu au metode private. O abordare este sa pui un “_” (underscore) in fata numelui functiei si sa o consideri privata. Din punctul meu de vedere asta este ca si cum ii rogi pe cei care vin dupa tine sa o considere o functie privata. Evident acest lucru nu este tocmai consistent.
Sa luam un exemplu:
1var obj = { 2 _secretFunction : function (key) { console.log(‘do secret ’ + key) }, 3 doStuff : function (key) { this._secretFunction(key) } 4} 5 6obj.doStuff(‘stuff’); // do secret stuff
Aparent avem o metoda publica “doStuff” si una privata “_secretFunction”. Totusi nu poti preveni un utilizator sa apeleze “_secretFunction”, sau mai rau, sa o modifice:
1obj._secretFunction = function (key) { console.log('new secret ' + key); } 2 3obj.doStuff('stuff'); // new secret stuff
Daca vrem ca functia sa fie ascunsa, iar acest lucru sa fie evident pentru toata lumea, din nou putem folosi un closure:
1var obj = (function () { 2 var secretFunction = function (key) { console.log(‘do secret ’ + key) } 3 4 return { 5 doStuff : function (key) { 6 secretFunction(key) 7 } 8 } 9})(); 10 11obj.doStuff(‘stuff’); // do secret stuff
Pentru ca functia parinte se va executa la inceput, practic spatiul in care a fost definit secretFunction si-a terminat deja executia, incapsuland logica. Obiectul intors poate sa apeleze functia pentru ca este definit in acelasi mediu ca si obiectul.
Pare complicat prima data, dar de fapt este foarte simplu cand intelegi conceptul.
Si apoi a fost… PHP
PHP inglobeaza multe optiuni diferite. PHP s-a dezvoltat initial ca un framework Perl, ulterior engine-ul fiind scris in C.
PHP este un limbaj dinamic care inglobeaza foarte multe concepte, de la obiecte, interfete si functii anonime, pana la goto labels. Nu este foarte clara directia in care ar trebui sa se dezolte limbajul, mai degraba ofera posibilitatea pentru abordari diferite.
In istoria ciudata a PHP, undeva in versiunea 4 a fost introdusa o sintaxa pentru Anonymous Functions, dar abia in PHP 5.3 a aparut o versiune mai “normala“.
Tot in versiunea 5.3 a fost introdusa si prima varianta de closures:
1$scalar = 5; 2 3$closure = function () use ($scalar) { 4 return 'Scalar: ' . $scalar . PHP_EOL; 5}; 6 7echo $closure(); // Scalar: 5 8 9$scalar = 7; 10 11echo $closure(); // Scalar: 5
Versiunea functioneaza in mare parte, dar trebuie sa specifici ce vei trimite catre closure.
Si mai exista cateva inconveniente:
1<?php 2class Foo { 3 private function privateMethod() { 4 return 'Inside private method'; 5 } 6 7 public function bar() { 8 $obj = $this; 9 return function () use ($obj) { 10 return $obj->privateMethod(); 11 }; 12 } 13} 14 15$obj = new Foo(); 16$closure = $obj->bar(); 17echo $closure(); 18 19Fatal error: Call to private method Foo::privateMethod() from context '' in [...][...] on line 10
Nu functioneaza pentru ca nu poti trimite $this ca parametru la closure, iar daca faci artificiul de mai sus tot nu vei putea accesa metodele private. Nu uitati, asta se intampla in PHP 5.3.
Ideea de a introduce acest tip de closure mi se pare bizara. Nu este prima daca cand in PHP se introduce un feature “bizar”, dupa cum vorbeam mai sus si de Anonymous Function. Pare work in progress.
Cred ca toata lumea se astepta ca acest feature sa functioneze la fel ca in JavaScript. Cred ca doar datorita JavaScript conceptul de closure a devenit atat de popular.
In versiunea PHP 5.4 lucrurile s-au mai schimbat, avem in sfarsit closures asa cum ne asteptam:
1class Foo { 2 private function privateMethod() { 3 return 'Inside private method'; 4 } 5 6 public function bar() { 7 return function () { 8 return $this->privateMethod(); 9 }; 10 } 11} 12 13$obj = new Foo(); 14$closure = $obj->bar(); 15echo $closure(); // Inside private method
Functioneaza!
Poti chiar sa spui:
1unset($obj); 2echo $closure();
si va functiona, pentru ca obiectul in interiorul caruia a fost definit closure-ul a ramas in memorie pana cand se va termina executia scriptului, sau se va apela:
1unset($closure);
Pentru mai multe detalii despre cum functioneaza closure in PHP 5.4, puteti citi acest blog.
-
Acesta este un mic proiect destinat utilizatorilor de Microsoft Windows!
Este un utilitar pentru Text To Speech scris in HTML si JavaScript. Aplicatia ruleaza ca HTML Application.
Am scris aceasta aplicatie de 3 ori pana acum, pentru ca am pierdut primele variante. Intr-un final m-am gandit ca poate si altii pot profita de pe urma lui.
Pentru utlizatorii Linux o alternativa este: eSpeak.
Mai multe detalii pe pagina proiectului.
-
A venit vremea sa mai public inca un joc facut in JavaScript, de data asta este vorba de Minesweeper.
Prima versiune a jocului a fost facuta initial acum aproximativ un an jumatate, dar intre timp l-am rescris total din pricina problemelor de performanta.
Acesta este cred si ultimul joc care nu il fac folosind canvas din motive de compatibilitate.
Fata de majoritatea jocurilor care au fost facute literalmente peste week-end (exceptia este Puzzle Gd) acesta s-a dovedit un joc putin mai complicat.
Pentru grafica vreau sa ii multumesc (din nou) Cătălinei Radu.
Enjoy!