← Toate articolele

Cum am construit Table Online — un joc de table multiplayer în Flutter pentru web, iOS și Android

Lecții tehnice din construirea unui joc real-time în Flutter, cu același cod care rulează în browser și nativ pe mobil. State management, sincronizare server, performanță 60fps.

Acum câteva luni am lansat Table Online — un joc clasic de table multiplayer pe care îl poți juca direct în browser pe table-online.ro, sau ca aplicație nativă pe iPhone și Android. Tot e construit în Flutter, cu un singur cod sursă pentru toate trei platformele. Acest articol e o privire în culisele inginerești.

De ce Flutter pentru un joc multiplayer

Decizia n-a fost evidentă. Pentru jocuri, mulți recomandă reflex Unity sau un engine custom. Am ales Flutter pentru trei motive concrete:

  1. Animațiile sunt clase de prim rang în Flutter. AnimationController, AnimatedBuilder, Hero, TweenAnimationBuilder — toate datele de mișcare ale pieselor de table și ale zarurilor sunt deja rezolvate de framework. N-a trebuit să scriem un physics engine.
  2. Web-ul ne-a permis adopția fără descărcare. Cea mai mare frecare în adopția unui joc mobil e instalarea. Cu Flutter web, utilizatorul intră, dă două click-uri, joacă o partidă. Ulterior, dacă îi place, instalează app-ul.
  3. Un singur codebase, trei platforme — și asta nu e doar un argument de marketing. Logica de joc, regulile mutărilor și comunicarea cu serverul sunt comune celor trei aplicații și se comportă identic peste tot.

Arhitectura

Pe scurt:

  • Frontend Flutter — UI, animații, input local. State management cu Riverpod.
  • Backend Dart pe Serverpod — autentificare, profiluri, prieteni, turnee, chat, plăți in-app și admin, cu un API type-safe comun cu clientul Flutter.
  • Serviciu real-time în Go — autoritatea jocului în partidele live: conexiunile WebSocket, câte o goroutine pentru fiecare cameră de joc, fiecare mutare validată pe server. Clientul doar propune.
  • PostgreSQL și Redis — profiluri, istoricul partidelor și clasamentul în PostgreSQL; sesiuni, coada de matchmaking și starea partidelor live în Redis.

Actualizare (octombrie 2026): la lansare tot stack-ul era în Dart, iar Serverpod gestiona și partidele. Ulterior am mutat traficul partidelor live într-un serviciu mic în Go, care rulează lângă Serverpod, pe același PostgreSQL și Redis. Restul a rămas în Dart.

Decizia care a contat cel mai mult: serverul e source-of-truth. Clientul propune o mișcare, server-ul decide dacă e validă, apoi broadcast-ează rezultatul ambilor jucători. Asta elimină o clasă întreagă de cheating și face debugging-ul predictibil.

Performanță: cum am ținut 60fps în browser

Web-ul Flutter e mai complicat decât mobile-ul nativ pe partea de performanță. Câteva lucruri care au făcut diferența:

  • RepaintBoundary peste tablă — izolăm repainting-ul tablei de restul UI. Când mișcăm o piesă, nu repaintăm scor-ul.
  • HTML renderer pentru paginile cu mult text, CanvasKit pentru pagina de joc — Flutter web suportă două renderere și poți alege per build. Pentru pagina de joc, CanvasKit câștigă net.
  • Lazy loading al asset-urilor — texturile de piese, sunetele, animațiile de victorie se încarcă doar când sunt necesare.

Lecția cea mai utilă

Dacă aș începe din nou, aș începe cu varianta web înainte de mobile. E mai grea (Flutter web are mai puține tooluri și mai multe edge cases), dar te forțează să iei decizii arhitecturale corecte de la început — bundle size, lazy loading, optimizare imagini. Aceleași decizii fac mobile-ul mai bun ulterior.


Vrei să discutăm un proiect Flutter? Scrie-ne — răspundem în maximum o zi lucrătoare. Și dacă vrei să vezi rezultatul în acțiune, intră pe table-online.ro și joacă o partidă.