Utilizator / Parola:

 

Am uitat parola... | Cont nou!
» Sectiuni
Forum » Articole » Programare » Introducere in programarea aplicatiilor pentru BREW

Articol:
Introducere in programarea aplicatiilor pentru BREW
Autor: Sir Game-a-lot, postat pe 27 Mar 2009 03:11:43
Versiune printabilã
Introducere în programarea jocurilor pentru BREW

1.    Ce este BREW?

Binary Runtime Environment for Wireless este un sistem şi un set de librării care permit unui program compilat pentru procesorul ARM 7 să acceseze diverse funcţii ale telefonului mobil.

Realizat de către gigantul Qualcomm, BREW a fost implementat în celularele a peste 60 de operatori de telefonie din întreaga lume şi în peste 20 de ţări, cu un cumul al veniturilor pentru dezvoltatorii de aplicaţii de peste 1 miliard de dolari (http://brew.qualcomm.com/brew/en/about/brew_today.html).

BREW se vrea o alternativă la J2ME, mai cuprinzător şi mai apropiat de Symbian şi Windows Mobile ca arhitectură şi concepţie.

SDK-ul poate fi downloadat gratis de pe http://brew.qualcomm.com/brew/en şi poate fi folosit pentru creerea de aplicaţii care ruleaza pe emulator, însă pentru a crea aplicaţii pentru telefoane este nevoie de autorizare Qualcomm (http://brew.qualcomm.com/brew/en/developer/getting_started/get_authenticated.html) care costa cel putin 400$. SDK-ul este foarte bine făcut cu excepţia unei structurări oarecum greoaie uneori şi a câtorva explicaţii microsoftice de tipul: „
Cod sursă:
void EnableBlues(void);
This enables the blues.”.

Din punctul de vedere al dezvoltatorilor de aplicaţii mobile, sistemul BREW este mai bine pregatit să facă faţă atât cerinţelor jocurilor complexe (3D) cât şi nevoilor dezvoltatorilor, întrucât poate fi considerat şi un iPhone AppStore 0.01, integrând un sistem foarte bine făcut de plăţi, o piaţă virtuală (B2B) în care dezvoltatorul îşi poate prezenta produsele direct operatorilor şi include şi un sistem de testare obligatoriu al calității prin compania NSTL.

Aplicațiile BREW rulează cod nativ ARM/thumb, direct pe DSP-ul telefonului, neinterpretat, ceea ce asigură performanţe ridicate comparativ cu J2ME. De asemenea, sistemul de downloaduri de aplicaţii la client este securizat în sensul în care se poate executa exclusiv over-the-air, write only către telefon. Mai precis, fişierele cu tip standard de aplicaţie şi cele semnate special de dezvoltator nu pot fi luate/descărcate de pe telefon nici măcar de către dezvoltatorul de aplicaţii sau proprietarul lor. Securitatea e garantată de o semnătură Verisign ce se generează pentru fiecare telefon şi fiecare aplicaţie, în mod unic.

Nu voi descrie sistemul de plată aici decât voi menţiona că e total automat şi dezvoltatorul primeşte banii direct de la Qualcomm.

2.    Etapele dezvoltării de aplicaţii pentru BREW

În principiu, ne putem referi la 3 etape principale: a) dezvoltarea efectivă, b) testul de calitate şi c) acordul de distribuţie.

Dezvoltator autorizat poate încheia acordul de distribuţie cu orice publisher sau direct, folosind sistemul BREW B2B, cu orice operator de telefonie care are implementată soluţia BREW. Pentru ultima variantă, dupa testul de calitate, dezvoltatorul stabileşte un plan tarifar şi trimite aplicaţia diverşilor operatori pentru accept. În cazul în care aceasta este acceptată, ea poate apărea în magazinul virtual al operatorului respectiv în termen de câteva zile.

Testarea este o etapă obligatorie premergătoare lansării de aplicaţii şi ea este efectuată în principal de către compania NSTL sau gratis de către operatorii de telefonie, în cazul în care dezvoltatorul este în aria lor de acoperire şi ei posedă laborator BREW propriu (Zapp făcea în 2007 teste). Testul este destul de scump şi acesta este unul dintre dezavantajele majore ale sistemului BREW față de J2ME.

Developerul primeşte un procent destul de ridicat din vânzările aplicaţiei sale (peste 50% din preţul de vânzare) însă nu are flexibilitate în modificarea preţurilor, odată aplicaţia lansată.

3.    Programarea pe BREW

Programarea propriu zisă se efectuează direct din Visual Studio 6.0 - 2008 şi aplicaţia rezultată este un .dll care se poate testa şi debuga pe simulatorul BREW din SDK. Pentru a pune aplicaţia pe telefon aceasta trebuie compilată cu un compilator ARM. Opţiunile sunt: compilatorul ARM oficial care costă 1400$ sau soluţia GNUDE gratis, care are performanţe asemănătoare, dar care uneori genereaza cod mai masiv şi care nu are asamblor. Pentru detalii despre instalarea GNUDE puteţi vizita forumul dezvoltatorilor de BREW (http://brewforums.qualcomm.com/), accesibil şi programatorilor neautorizati. Compilatorul GNUDE C/C++ se poate integra în VStudio prin construcţia unor fisiere de batch (.bat) plasate în PostBuild event, de exemplu. În acest fel avem toate configuraţiile accesibile la un click, fie că e vorba de DebugSim sau ReleasePhoneX. Pe forumul BREW, în threadul care descrie instalarea GNUDE sunt ataşate şi fişierele necesare pt. aceasta.

Putem programa atât în C cât şi în C++. Pentru aplicaţiile dezvoltate de compania mea, Zamolxis Interactive, am preferat C-ul datorită faptului că aveam de a face cu device-uri limitate în ceea ce priveşte capacitatea de procesare. Mai ales că majoritatea telefoanelor din generatia BREW 1.0+ dar şi câteva BREW 2.0 aveau o stivă (stack) foarte limitată, în cazul unor telefoane putând fi chiar şi doar 2000 de bytes! Telefoanele de generaţia a treia sunt mult mai bine dotate, stiva fiind de ordinul sutelor de kilobytes sau chiar de ordinul megabaiţilor.

Pentru a testa o aplicaţie pe telefonul mobil, programatorul autorizat generează o semnatură Verisign de test (are incluse câteva sute în planul tarifar de autorizare, 400$ -> 100 semnături). Utilitarele pentru uploadoarea aplicaţiilor pe telefonul mobil, informaţii detaliate despre telefoanele mobile, operatori, ajutor avansat sunt accesibile doar în urma autorizării care necesită semnarea unui acord de confidențialtate, deci nu voi putea răspunde la întrebări de acest tip.

Simulatorul este însă o reprezentare destul de fidelă a implementării BREW pe deviceuri. Este incomparabil mai elocvent decât cel de Java pentru comportamentul de pe device al aplicaţiilor, în ciuda anumitor funcţiuni care sunt implementate prost sau chiar neimplementate încă. Aplicaţiile de BREW sunt în general mult mai portabile, dacă sunt făcute cu cap, decât cele de java, în ciuda afirmaţiei SUN că J2ME este “Write once, run anywhere”, afirmaţie care a devenit odată cu explozia de device-uri J2ME “write once, ship to Indians, then debug everywhere”.

4.    Despre codare

O aplicaţie BREW trebuie să conţina cel puţin 2 fişiere pentru simulator: 1 dll si un fişier cu extensia .mif care reprezintă descrierea aplicaţiei (nume, GUID clasa, capabilităţi, etc – vezi ustensila Mif Editor din pachetul de SDK Tools gratis, dar care nu mai vine inclus in SDK). BREW ştie să încarce resurse din sistemul său de fişiere din memoria RAM/ROM a celularului (protejat agresiv) şi mai lucrează cu un fişier special de resurse cu extensia .bar care este folosit de diverse clase/interfețe. Pentru device-uri este nevoie de înca un fisier .sig care este generat de pe site-ul Qualcomm (signature generator) pentru fiecare telefon şi care reprezintă o semnatură de test.

Primul pas în dezvoltarea unei aplicaţii este generarea fişierului .mif. Apoi se poate trece direct la codare.

Prima funcţie apelată de către sistemul BREW este o funcţie cu nume standard, AEEClsCreateInstance, obligatoriu a fi definită:

Cod sursă:

//Name: AEEClsCreateInstance
//First called function
int AEEClsCreateInstance(AEECLSID ClsId, IShell *pIShell, IModule *po, void **ppObj)
{
    Jeeky *pMe;
    //LOG_1("AEEClsCreateInstance: IN");

    *ppObj = NULL;
    if(AEECLSID_JEEKY == ClsId)
    {
        if( AEEApplet_New(sizeof(Jeeky), ClsId, pIShell, po, (IApplet**)ppObj,
                        (AEEHANDLER)Jeeky_HandleEvent,
                        (PFNFREEAPPDATA)Jeeky_FreeAppData) )
        {
            pMe = (Jeeky*)*ppObj;
            if(pMe != NULL)
            {    
                pMe->nDebugMessageNr = 1;
                SetGameMode(pMe, GAME_MODE_ZERO);
                if(Jeeky_InitAppData(pMe) == TRUE)
                {
                    //LOG_1("AEEClsCreateInstance:OUT-OK");
                    return AEE_SUCCESS;
                }
            }
            IAPPLET_Release((IApplet*)*ppObj);
        }
    }

    //LOG_1("AEEClsCreateInstance:OUT-NOT OK");

    return EFAILED;
}
 


De remarcat apelul către AEEApplet_New care instanţiază un applet legat de structura descriptivă, în cazul de faţă denumită „Jeeky”. Pointerul cel mai important pe care aplicatia îl primeşte de la sistem este IShell, prin intermediul căruia se obţine accesul la întregul sistem BREW. Acesta împreună cu interfaţa care controlează display-ul vor fi regasite in variabila AEEApplet explicată mai jos.

Programele BREW nu pot avea variabile globale. Acestea trebuie puse în structura aplicaţiei care începe obligatoriu cu o variabilă de tip AEEApplet, variabilă care dă acces către toate zonele necesare.

Cod sursă:

typedef struct _Jeeky
{
    AEEApplet        a;
    AEEDeviceInfo    DeviceInfo;
    IDisplay        *pIDisplay;
    IShell        *pIShell;
    IFileMgr        *pFileMgr;

    GAME_MODE_ENUM    nGameMode, nBackupGameMode;
    boolean        bStartError;
    int            nDebugMessageNr, nSplashStage, nErrorCode;
    // aici urmeaza alte variabile importante, pointeri la
    // structuri și obiecte
} Jeeky;
 


Funcțiile care sunt apelate momentului imediat următor creării și anterior distrugerii appletului sunt: Jeeky_InitAppData și respectiv Jeeky_FreeAppData.

Cod sursă:

boolean Jeeky_InitAppData(Jeeky* pMe)
{
    int            nRez;
    byte            b;
    // […]
    
    //Standard init
    pMe->DeviceInfo.wStructSize    = sizeof(pMe->DeviceInfo);
    ISHELL_GetDeviceInfo(pMe->a.m_pIShell,&pMe->DeviceInfo);
    pMe->pIDisplay            = pMe->a.m_pIDisplay;
    pMe->pIShell            = pMe->a.m_pIShell;

    pMe->nErrorCode            = JEC_NONE;
    pMe->bStartError            = TRUE;
    pMe->bPaused            = FALSE;            //Not paused
    pMe->bSuspended            = FALSE;        //Not suspended
    pMe->nScore                = 0;            //Score is zero

#ifdef _COMPILE_FOR_SLOW_DEVICES_
    pMe->RENDER_RATE            = 200;
#else
    pMe->RENDER_RATE            = 67;
#endif

    //FileManager
    if(ISHELL_CreateInstance(pMe->pIShell, AEECLSID_FILEMGR,
        (void **)&pMe->pFileMgr) != SUCCESS)
    {
        pMe->nErrorCode = JEC_CREATING_INTERFACE;
        return TRUE;
    }

    //MenuControl Main
    if(ISHELL_CreateInstance(pMe->pIShell, AEECLSID_MENUCTL_10,
        (void **)&pMe->pMenuCtlMain) != SUCCESS)
    {
        pMe->nErrorCode = JEC_CREATING_INTERFACE;
        return TRUE;
    }

    // Alte variabile […]

    return TRUE;
}
 


De remarcat că apelurile funcțiilor din API se fac în C de forma:
ISHELL_GetDeviceInfo(pMe->a.m_pIShell, &pMe->DeviceInfo); unde, în cazul de față, m_pIShell este obiectul inițializat de către sistem pt. noi la crearea apletului, iar DeviceInfo este o structură care conține diverse informații, de ex. mărimea ecranului.
În exemplul cu FileManager-ul avem metoda standard de a instanția diverse obiecte oferite de către BREW. Tot ce trebuie să știți este id-ul classei respective (AEECLSID) pe care îl puteți găsi cu ușurință în SDK. Dacă va întrebați de ce toate căile returneaza TRUE, este pentru ca aplicația să nu iasă imediat, în caz de eroare, cu mesajul standard de accident (crash), ci să ne dea posibilitatea de a printa propriul mesaj de eroare, mai prietenos, pentru utilizator. Funcțiile de logging sunt foarte utile datorită faptului ca poți printa către consola stringuri (de sub 128?) caractere în timpul execuției atât în simulator cât și pe device.
Pe device-urile BREW 3.0+ se poate face debugging direct pe device.

Cod sursă:

void Jeeky_FreeAppData(Jeeky* pMe)
{
    UnloadLevel(pMe);
    UnloadSounds(pMe);

    if(pMe->BackgroundCache!=NULL)
    {
        FREE(pMe->BackgroundCache);
        pMe->BackgroundCache = NULL;
    }

    IB_SAFE_RELEASE((IBase *)pMe->pFileMgr);
    // […]   
}
 


În BREW este indicat ca distrugerea instanțelor să se faca astfel încât pointerul să fie încărcat cu valoarea NULL în mod explicit. Aceasta este mai mult un ghid decât o obligativitate, deoarece după FreeAppData nici o altă funcție nu mai este apelată. Este însă o practică bună, deoarece un pointer uitat ne-nulificat poate crea probleme serioase utilizatorului dacă este accesat, soldându-se cel puțin cu resetarea dacă nu chiar cu blocarea telefonului. Pentru variabilele instanță de obiect sistem, acestea trebuie dealocate într-un fel mai special. Pentru instanțele claselor care sunt derivate din IBase se poate face ceva de genul:

Cod sursă:

#define IB_SAFE_RELEASE(_p) if (_p != NULL) {IBASE_Release(_p); _p = NULL;}
 


De asemenea, este preferabil ca întotdeauna să se dealoce în ordinea inversă alocarii pentru a evita device memory fragmentation. BREW nu este la fel de eficient ca Windows în compactarea memoriei disponibile și se poate întâmpla ca un telefon cu jumătate de memorie liberă să nu permită alocarea de memorie datorită faptului că nu mai sunt zone continue de RAM suficient de mari. Problema aceasta era mai serioasă la device-urile de înainte de BREW 3.0, dar ramâne totuși un pricipiu de codare eficient.

Coada de mesaje:

BREW comunică cu aplicația prin intermediul unei cozi de mesaje specifice, asemănătoare cozii de mesaje din Windows.

Cod sursă:

static boolean Jeeky_HandleEvent(Jeeky* pMe, AEEEvent eCode, uint16 wParam,
uint32 dwParam)
{
    //AECHAR    txt[] = {'H', 'e', 'l', 'o', ''};
    int        nrez, n, nr;
    uint32    prop;
    AEERect    rect;

    // Majoritatea tipurilor de variabile se mapeaza direct pe cele standard C.
    // char și AECHAR sunt folosite pt. stringuri, unde AECHAR este standardul UNICODE
    // si char e identic din C.

    switch (eCode)
    {
    case EVT_APP_START:// Primul event trimis de sistem
        pMe->started    = FALSE;
        IDISPLAY_SetColor(pMe->pIDisplay, CLR_USER_BACKGROUND,
            MAKE_RGB(0xFF, 0xFF, 0xFF));
        IDISPLAY_SetColor(pMe->pIDisplay, CLR_USER_TEXT,
            MAKE_RGB(0xfe, 0x99, 0xcc));
        IDISPLAY_ClearScreen(pMe->pIDisplay);
        IDISPLAY_DrawText(pMe->pIDisplay, AEE_FONT_LARGE, txt, -1, 0, 0,
            NULL, IDF_ALIGN_MIDDLE | IDF_ALIGN_CENTER);
        // Desenează un text pe ecran, folosing font mare, textul definit
        // mai sus (“Demo”) și îl poziționează în centrul ecranului.
        // Stringul este în mod obligatoriu terminat cu NULL.
        // Aici am scrie mesajul de eroare și apoi, după ieșirea din funție, am aștepta
        // ca userul să apese o tastă ca să terminăm execuția aplicației.
        IDISPLAY_Update(pMe->pIDisplay);
        // Până la execuția acestei comenzi nu apare nimic pe ecran. Astfel
        // se pot combina mai multe comenzi de desenare pentru a
        // combate efectul de tearing.
        // Pentru desenarea pe ecran se poate folosi și accesul direct la
        // bufferul ecranului, dar pt. jocuri simple e suficienta utilizarea
        // interfețelor de tip IImage/IBitmap împreună cu funcțiile expuse
        // de IDisplay.

        if(pMe->nErrorCode != JEC_NONE)return SpitError(pMe);
            SetGameMode(pMe, GAME_MODE_SPLASH);                
        return(TRUE);
        
    case EVT_APP_STOP:    // Ultimul event trimis de sistem
        SetGameMode(pMe, GAME_MODE_MENU_EXIT);
        return(TRUE);

    case EVT_APP_SUSPEND:    // Când aplicația trebuie să suspende activitatea
        // și să dealoce/deinstanțieze tot ce poate. Cazuri: altă aplicaţie este
        // pornită, utilizatorul a primit un SMS, un apel. etc
        pMe->nBackupGameMode = pMe->nGameMode;
        pMe->nGameMode = GAME_MODE_SUSPENDED;
        pMe->bSuspended = TRUE;
        stop_TAPI(pMe);
        SleepTimers(pMe);
        DeactivateMenus(pMe);
        StopAllSounds(pMe);
        UnloadSounds(pMe);
        ReleaseAllMediaInterfaces(pMe);
             return(TRUE);

    case EVT_APP_RESUME:
        // Restaurăm starea aplicaţiei de la primirea eventului SUSPEND
        pMe->nGameMode = pMe->nBackupGameMode;
        pMe->bSuspended = FALSE;
        pMe->bSunet = pMe->bBackupSunet;
        pMe->bGameOverTimer = FALSE;
        CreateAllMediaInterfaces(pMe);
        LoadSounds(pMe);
        start_TAPI(pMe);
        WakeTimers(pMe);
        ResumeAllSounds(pMe);
        ResumeEvent(pMe);
        IDISPLAY_Update(pMe->pIDisplay);
        return(TRUE);

    case EVT_KEY_PRESS: // Tastă apăsată
        if(pMe->bSuspended == FALSE)
            HandleKeyPress(pMe, eCode, wParam, dwParam);
        return TRUE;

    case EVT_KEY_RELEASE: // Tastă relaxată
        if(pMe->bSuspended == FALSE)
            HandleKeyRelease(pMe, eCode, wParam, dwParam);
        return TRUE;

    case EVT_COMMAND: // Selectat un item dintr-un meniu.
        if(pMe->bSuspended == FALSE) // Nu ne putem aștepta ca BREW să
        // nu ne trimită eventuri cât timp aplicația e suspendată
        {
            switch(wParam)
            {
                case IDS_MENU_EXIT:
                    SetGameMode(pMe, GAME_MODE_MENU_EXIT);
                    break;
                case IDS_MENU_OPTIONS:
                    SetGameMode(pMe, GAME_MODE_MENU_OPTIONS);
                    break;
                case IDS_MENU_LOAD_LEVEL:
                    SetGameMode(pMe, GAME_MODE_MENU_LOAD_LEVEL);
                    break;
            }
        }    
        return TRUE;
    }
    return FALSE; // pt. orice event neprelucrat
}
 


Această coadă de mesaje se deosebește de cea de pe Windows prin faptul că în BREW nu putem folosi funcții care nu returnează imediat. Deci nu putem scrie:

Cod sursă:

boolean pastile_cailor = false;
while(!pastile_cailor)
{
    // do stuff
}
 


Daca aplicația stă prea mult într-o rutina, cățelul de pază a BREW va reseta telefonul.
Timeout-ul e dependent de implementare și telefon, dar nu e greu să-l atingi din greșală. Probabil putem priva sistemul de refresh chiar și mai mult de 1 secundă, însă aceasta va avea efecte de cascadă rezultând doar în utilizatori nervoși. Preferabil este să nu se treaca de cam 250 ms.

Rezolvarea e relativ simplă, dar enervantă pentru programatorul de Windows. Se folosesc one-shot timers, deoarece timerul recurent nu există sub BREW:

Cod sursă:

typedef void (* PFNNOTIFY)(void * pData);

int ISHELL_SetTimer(IShell * pIShell, int32 dwMSecs, PFNNOTIFY pfn, void *pUser)
 


La dvMSecs după apelarea reușită a acestei funcții, sistemul va apela funcția noastră pe care o definim la pfn și îi va transmite valoarea pUser dată de noi.
În marea majoritate a cazurilor pUser va fi pMe, pointerul către structura principală a aplicaţiei (Jeeky *pMe).

Pentru a simula un MainLoop va fi nevoie ca să setăm un timer către o functie de ex. din eventul de EVT_START și să definim:

Cod sursă:

case EVT_START:
ISHELL_SetTimer(pIShell, 100, Render, pMe);
    return;
 


și respectiv

Cod sursă:

void Render(Jeeky *pMe)
{ 
    ISHELL_SetTimer (pMe, Render, pMe->RENDER_RATE);
    []
}
 


5.    Încheiere

Terminalele moderne sunt foarte rapide. De ex. noi am portat un decodor mpeg2 în full software și funcționează foarte bine. Deși BREW este suficient de rapid chiar și pentru portarea unui joc de tip DOOM, trebuie luat în calcul faptul că procesoarele ARM nu au virgulă flotantă. Operațiile matematice în virgula fixă sunt trivial totuși de implementat și permit rularea unui Wolfenstein portat la cel puțin viteza maxima cu care alt joc 2D complex îl afișează. Aici mă refer totuși la puține FPS. Dacă obții 25 FPS ai de a face cu un telefon ultra performant. În general este vorba de limita jucabilității, cel puțin cu terminalele cu care am avut de a face.

BREW are o clasă care implementează OpenGL ES. Atunci când m-am jucat eu cu ea nu existau terminale cu accelerare hardware, însă acum acestea sunt numeroase. Vă puteți uita pe site-ul: http://www.pocketgamer.co.uk/r/Mobile/BREW/feature.asp?c=3477 pt. câteva exemple.

Există soluții automate care porteaza cod J2ME către BREW, Symbian, iPhone și sunt sute de companii, mai ales asiatice sau est-europene, care porteaza aplicații de pe un sistem pe altul. Pentru evitarea unor orori in acest cazuri e bine sa folosiți codarea „băbească” de tipul „if(p == NULL)” și nu if(!p) (în c++ am folosi if(NULL == p)). La capitolul modul de a scrie codul, sfatul meu este să va însușiți modul Paranoid ©™. Nici o alocare nu se lasă fără error check, nici un pointer obținut nu se folosește înainte de a-l verifica pt. validitate. Toate situațiile de eroare trebuie tratate. Procesul de testare NSTL este foarte strict și dacă aplicația crash-ează o singură dată sau nu se comportă conform descrierii date de către dezvoltator, dezvoltatorului pică testul și pierde sumele de bani corespondente. Documentul cu descrierea aplicaţiei conține, între altele, o diagramă cu stările aplicaţiei (state machine) și toate ecranele și toate opțiunile utilizatorului (taste/eventuri), deci e bine să avem clar in mintea structura stărilor mașinii.

În final, as dori să spun pe o notă personală că programarea pe BREW este extrem de interesantă și este ușor de deprins de către un programator de C/C++ bun. BREW este de departe mai dătător de satisfacții decât J2ME, însă are acum concurenți serioși în Android și iPhone.

Aș fi vrut să pot atașa sursele unui proiect de bază și chiar mă gândeam să public Jeeky parțial, însă nu am destul timp ca să curăț codul pt. așa ceva. Vă puteți uita însă aici la câteva demo-uri de aplicații pe BREW rulând pe Zapp i810: http://zamolxisinteractive.com/confidential/. User = guest, Pass = Carmack. Sunt variante mai vechi, nu cele complete, dar vă puteți face o idee despre cum merge BREW.

Comentarii pentru acest articol:



Pagina 1 din 1 [ 1 ]

Autor Mesaj
Overburn

    Postat la 27 Mar 2009 07:23:01    Subiect: < fara subiect >


Status:
Înregistrat pe:
06 Mar 2007 07:36:21
Vârsta: 19 ani
Mesaje: 287
Locatie: Ploiesti
Programator junior
Monkey Crew
chiar ma tenteaza sa ma bag la programarea armurilor... dar cred ca astept sa se mai standardizeze androidu. Very Happy
One Tequilla, Two Tequilla, Three Tequilla, Floor.
__________________________________

ASUS P5M-VM Ultra
Intel Pentium D930 - 3Ghz
4x2048Mb DDR2 PC5300
Leadtek Winfast PX9600GT 512Mb
Maxtor - 200GB
 
Black_Knight

    Postat la 27 Mar 2009 09:13:43    Subiect: < fara subiect >


Status:
Înregistrat pe:
07 May 2007 19:49:43
Vârsta: 28 ani
Mesaje: 652
Locatie: Bucuresti
Programator

felicitari pentru articol...

foarte bun,
cand o sa vreau sa ma apuc de BREW o sa stiu de unde sa incep Wink
 
Dark

    Postat la 27 Mar 2009 14:04:00    Subiect: < fara subiect >


Status:
Înregistrat pe:
12 May 2007 20:12:30
Vârsta: ? ani
Mesaje: 687
Locatie:
Programator

Foarte bun articolul, felicitari.
"Am crezut ca esti ceva mai avansat" - Nekitu, 2008 A.D.
Breviar de personalitati
 
Sir Game-a-lot

    Postat la 27 Mar 2009 17:03:49    Subiect: < fara subiect >


Status:
Înregistrat pe:
25 Aug 2007 18:20:41
Vârsta: 31 ani
Mesaje: 104
Locatie: Cluj-Napoca
Programator
Zamolxis Interactive
Multumesc mult.

Dark ma bucur ca acuma, cu pin46 banat, ai revenit pe gamedev.ro.

ERATA:

Sunt mai multe greseli de exprimare/gramaticale, insa am timp doar pt. cod: initializarea stringul de unicode AECHAR txt[] din Jeeky_HandleMessages se termina in ghilimele_simple_slash_zero_ghilimele_simple, si nu in semnul intrebarii.
Nine women working in perfect harmony can't have a baby in 1 month.
 

Pagina 1 din 1 [ 1 ]


Server time: 21:52:49 10.09.2010
Last full backup: @ 10:43 on 12.09.2009


[ Termeni si conditii | Contact | F.A.Q. | Funny Pictures ]

© 2006-2007 Copyright 7thFACTOR Entertainment - All rights reserved