Nel 2018 avevo scritto per mio figlio un piccolo gioco educativo in JavaScript usando Phaser, impiegandoci tre o quattro serate. Otto anni dopo lui lo ha ripreso ed in una frazione di quel tempo ne ha costruito una versione enormemente più ricca usando l'AI come esecutore tecnico. La parte interessante non è che l'AI sappia programmare, ma che il collo di bottiglia si sia spostato dal codice alla capacità di progettare, verificare e correggere.
Nel 2018 avevo messo insieme un giochino per mio figlio in tre, forse quattro dopocena, una di quelle idee vagamente pedagogiche che vengono ai genitori quando i figli vanno alle elementari: una navicella, degli asteroidi da abbattere, numeri ed operazioni infilati dentro al gioco con la speranza abbastanza trasparente di fargli ripassare qualcosa senza fargli sembrare che stesse facendo i compiti. Lo avevo scritto in JavaScript puro appoggiandomi a Phaser, che in quel periodo era grossomodo una delle scelte quasi obbligate se volevate realizzare un gioco 2D nel browser senza reinventarvi da soli ciclo di rendering, sprite, input e collisioni. Funzionava. Non era bello, non aveva nessuna pretesa particolare, ma funzionava ed ha fatto il suo mestiere, poi come succede a moltissimi piccoli programmi domestici è sparito dentro una chiavetta USB e lì è rimasto per anni.
Otto anni dopo mio figlio lo ha ripreso in mano ed è qui che la storia ha cominciato ad interessarmi davvero, perché non riguarda soltanto quel gioco ma secondo me riguarda praticamente tutti quelli che scrivono codice per mestiere. Non ha semplicemente chiesto ad un'AI "fammi un gioco di asteroidi" aspettando che da una frase uscisse per miracolo un prodotto finito, perché se avete mai provato una richiesta del genere saprete certamente cosa succede: arriva una demo, magari anche simpatica, ma quasi sempre mediocre e soprattutto lontanissima da quello che avevate realmente in testa. Lui ha fatto una cosa diversa, ha saputo INSTRADARE il lavoro, decidere cosa voleva ottenere, descrivere il comportamento invece di dettare l'implementazione ed individuare i problemi mentre giocava. Quando, per esempio, trascinando la navicella questa sparava contemporaneamente finendo per colpire a caso anche i bonus negativi, non ha chiesto semplicemente di "sistemare il touch", ha descritto esattamente il comportamento osservato e preteso che venisse eliminata la causa. Sembra una differenza piccola, ma è praticamente il mestiere di un capo progetto tecnico: dire cosa deve succedere, riconoscere quando non succede ed impedire che una toppa nasconda il problema invece di risolverlo.
Il risultato è un gioco che rispetto al mio del 2018 sembra provenire da un altro pianeta, con una colonna sonora, effetti, un abisso cosmico in parallasse, ombre, particelle, esplosioni, due livelli bonus matematici ed una quantità di dettagli che io nelle mie quattro serate non avrei nemmeno cominciato ad implementare. Ma il vecchio programmatore che è in me si è dovuto ricredere soprattutto osservando quello che NON c'è. Non c'è Phaser, non c'è React, non c'è un bundler, non c'è package.json, non c'è la solita foresta di node_modules e non esiste una struttura di progetto costruita per sostenere un'architettura che questo programma semplicemente non necessita. C'è sostanzialmente un documento HTML di poco più di duemilaseicento righe con markup, CSS e JavaScript, qualche asset esterno indispensabile ed un punto di ingresso che si apre con un doppio clic anche da locale o da una chiavetta. Una banalità che oggi sembra quasi rivoluzionaria.
Sotto tutto questo l'impianto è quello vecchissimo, semplice ed ancora imbattibile del game loop, ovvero requestAnimationFrame() che richiama l'aggiornamento dello stato, quindi update(dt), poi il rendering con render(dt) ed infine ricomincia. Nessuna architettura con un nome importante, nessun livello di astrazione costruito perché "si fa così", lo stato del mondo è dentro una manciata di array normalissimi che contengono asteroidi, proiettili, scintille, detriti, fumo ed onde d'urto e ad ogni fotogramma il programma arriva ad aggiornare ciò che si muove, eliminare ciò che non serve più e disegnare quello che deve comparire sul <canvas>. Tutto qui. Ed è proprio questa semplicità che mi piace, perché il codice non cerca di dimostrare quanto sia bravo chi lo ha scritto, cerca soltanto di FAR GIRARE IL GIOCO.
Le parti interessanti arrivano quando si guarda come sono stati risolti i dettagli. Il principio usato praticamente ovunque è uno dei più vecchi dell'informatica grafica, ovvero PRE-CALCOLA QUELLO CHE NON CAMBIA E CALCOLA AD OGNI FOTOGRAMMA SOLTANTO QUELLO CHE SI MUOVE. L'abisso cosmico del fondale, per esempio, non è una gigantesca scena ridisegnata continuamente da zero ma una pila di otto piani a profondità differente con nebulose, polveri, pianeti lontani, relitti sfocati, stelle ed infine un pulviscolo in primo piano che passa davanti alla navicella; i piani continui vengono preparati una volta su canvas fuori schermo e poi fatti scorrere copiandoli con pochissime chiamate a drawImage, quindi il costo per fotogramma resta praticamente costante e non aumenta perché avete deciso di mettere altre cento stelle sullo sfondo. È una di quelle cose che quando la vedete funzionare sembrano ovvie, ma sono proprio le cose ovvie fatte bene a fare la differenza fra un programma che regge ed uno che comincia ad arrancare appena gli aggiungete qualcosa.
Poi ci sono le ombre, ed anche qui la soluzione è quasi irritante per quanto è semplice. Gli oggetti che stanno sul piano di gioco proiettano un'ombra sul fondo dell'abisso seguendo una direzione divergente dal centro dello schermo, quindi basta ricavare un vettore, allungarlo in funzione della posizione e disegnare la sagoma traslata. Tre righe di trigonometria, per così dire, e la scena che prima appariva piatta improvvisamente acquista una profondità che il mio vecchio giochino non aveva nemmeno lontanamente. E poi particelle, fumo, detriti, esplosioni, parallasse ed altri piccoli accorgimenti dei quali, guardando il risultato, mi viene soltanto da dire CHAPEAU, non perché siano invenzioni rivoluzionarie prese una per una ma perché qualcuno ha saputo vedere che mancavano, chiederle, provarle ed eliminare quelle che non funzionavano.
Ed è qui che arriva la parte che secondo me conta veramente. Nel 2018 io avevo bisogno di Phaser perché scrivere personalmente il ciclo di rendering, la gestione degli oggetti, le collisioni, gli input ed il resto mi sarebbe costato più delle tre o quattro serate che avevo a disposizione, quindi il framework era una soluzione perfettamente razionale. Oggi quel codice può essere prodotto direttamente in una frazione del tempo e questo rende nuovamente conveniente scrivere proprio quelle piccole infrastrutture che per anni abbiamo delegato alle librerie. Non significa che i framework non servano più, sarebbe una conclusione abbastanza sciocca, significa che È CAMBIATO IL COSTO DEL CODICE e quindi alcune decisioni architetturali che prima erano economicamente ovvie oggi meritano almeno di essere ricalcolate.
Se una libreria complessa mi evita settimane di lavoro continua ad essere un affare, se invece porto dentro migliaia di funzioni, dipendenze, build system ed astrazioni soltanto per risparmiarmi duecento righe che un'AI può produrre, modificare e testare mentre io descrivo il comportamento che voglio, allora il conto è diventato diverso. La quantità di codice non è nemmeno il punto, perché duemilaseicento righe scritte direttamente possono essere molto più semplici da distribuire di trecento righe che dipendono da un ecosistema intero; quello che conta è quanta complessità dobbiamo capire, mantenere ed attraversare quando qualcosa non funziona.
E naturalmente l'AI sbaglia, anzi proprio questo progetto secondo me dimostra perché l'idea del "programmare senza sapere nulla" è raccontata male. Quando la navicella spara mentre viene trascinata dovete ACCORGERVI che quel comportamento è sbagliato, capire in quale situazione compare, distinguerlo da un problema grafico ed essere abbastanza precisi da far correggere la causa. Quando il parallasse sembra finto dovete riconoscere che la velocità relativa dei piani non restituisce profondità, quando un'esplosione costa troppo dovete capire che qualcosa viene ricostruito ad ogni frame senza motivo, quando la collisione produce un effetto assurdo dovete riuscire almeno a localizzare logicamente il problema. La capacità richiesta si sta quindi spostando dal ricordare la sintassi della riga giusta al SAPER RICONOSCERE QUALE RIGA È SBAGLIATA E PERCHÉ, che poi è sempre stata la parte difficile della programmazione ed è anche quella che non si impara leggendo soltanto manuali.
Mio figlio questa capacità non credo l'abbia imparata da me, o almeno non principalmente. Probabilmente gliel'hanno insegnata molto di più le ore passate a giocare, osservare interfacce, capire perché un gioco risponde bene ed un altro male, smontare mentalmente le regole dei videogiochi degli altri e riconoscere immediatamente quando qualcosa "non torna". È una competenza strana perché fino a pochi anni fa sembrava accessoria alla programmazione, mentre con l'AI rischia di diventare una delle competenze CENTRALI: l'esecutore può scrivere molto velocemente, ma qualcuno deve continuare a sapere che cosa vale la pena scrivere e soprattutto quando quello che è stato scritto è sbagliato.
Alla fine quel piccolo gioco educativo del 2018 ha fatto esattamente il suo dovere, soltanto che la lezione non era quella che pensavo di avergli messo dentro. Io avevo costruito una navicella che sparava agli asteroidi per insegnargli qualcosa di matematica, otto anni dopo lui ha preso la stessa idea e senza quasi toccare direttamente il mestiere che avevo imparato io ha finito per mostrarmi dove sta andando il nostro mestiere: meno tempo passato a produrre meccanicamente codice e molto più tempo a capire, progettare, provare e correggere, USANDO L'AI PER SCRIVERE QUELLO CHE ABBIAMO DAVVERO CAPITO DI VOLERE!
Commenti
Nessun commento, per ora.
Per commentare serve un accesso. Qui siamo tutti tecnici e colleghi!
Accedi per commentare