Negli ultimi anni, l’intelligenza artificiale generativa ha suscitato un forte interesse in diversi ambiti della creatività digitale, con applicazioni che spaziano dalla generazione di immagini e musica alla scrittura di articoli e narrativa. Tuttavia, il potenziale dell’AI nel settore della programmazione rimane spesso sottovalutato da chi non opera direttamente nel campo dello sviluppo software. In molti si trovano spesso di fronte a una scelta: rivolgersi a professionisti per esigenze anche minime (come sistemare il proprio sito) o intraprendere un percorso autodidattico, spesso dispersivo, basato su ricerche online frammentarie. Questa percezione deriva dalla convinzione che la programmazione richieda necessariamente anni di studio approfondito e una conoscenza tecnica avanzata, mentre, in realtà, gli strumenti di AI generativa abbassano notevolmente la soglia d’ingresso, permettendo anche ai non esperti di sviluppare software funzionante. I modelli di AI più adatti per questo tipo di applicazioni sono senza dubbio quelli che seguono un approccio basato sulla ‘catena del pensiero’, particolarmente efficaci per problemi di logica e programmazione. Tra questi rientrano, ad esempio, i modelli o1 e o3 mini high, così come i loro equivalenti nelle piattaforme Gemini di Google e Claude 3.7 di Anthropic.
Intelligenza artificiale e programmazione: un’integrazione naturale
La programmazione si fonda su principi algoritmici e strutture logiche ben definite, caratteristiche che si prestano perfettamente all’assistenza fornita dai moderni modelli di intelligenza artificiale generativa. A differenza di altri settori creativi, dove la soggettività gioca un ruolo predominante, il coding segue regole rigorose che rendono l’AI uno strumento estremamente efficace per l’automazione, la correzione e l’ottimizzazione del codice.
Anche chi possiede solo conoscenze basilari di programmazione può trarre enorme vantaggio dall’uso dell’AI, che può generare frammenti di codice, suggerire soluzioni algoritmiche più efficienti e persino spiegare il funzionamento di un determinato blocco di istruzioni. Questo semplifica notevolmente l’accesso ai neofiti e ai programmatori con poca esperienza, accelerando notevolmente i tempi di sviluppo. Inoltre, l’intelligenza artificiale può essere utilizzata come strumento di apprendimento interattivo: invece di cercare risposte frammentarie su forum o documentazioni tecniche, gli utenti possono interrogare l’AI in tempo reale, ottenendo spiegazioni dettagliate e personalizzate.
JavaScript e l’AI: un ambiente di Sperimentazione facilmente accessibile
JavaScript si configura come uno dei linguaggi più adatti a sperimentare l’integrazione tra intelligenza artificiale e sviluppo software. La sua natura interpretata, la possibilità di eseguire codice direttamente nel browser e il vasto ecosistema di librerie lo rendono ideale per chi vuole testare in tempo reale le capacità generative dell’AI.
Attraverso l’AI è possibile generare e testare script interattivi in JavaScript senza la necessità di ambienti di sviluppo complessi. Questo approccio si rivela particolarmente utile non solo per il web development tradizionale, ma anche per la creazione di interfacce utente dinamiche. L’AI può suggerire miglioramenti al codice, generare interazioni dinamiche basate sugli input dell’utente e proporre soluzioni per migliorare l’accessibilità e le prestazioni di una pagina web.
Un ulteriore vantaggio dell’uso dell’AI con JavaScript è la possibilità di creare piccoli strumenti personalizzati che possono semplificare la vita degli utenti. Ad esempio, script automatici per l’analisi dei dati, strumenti di visualizzazione interattiva e widget personalizzati possono essere generati rapidamente e testati direttamente nel browser senza dover scrivere codice da zero.
Oltre agli impieghi più tecnici, JavaScript e l’intelligenza artificiale offrono un’opportunità perfetta anche per la sperimentazione creativa. Grazie alla possibilità di eseguire codice direttamente nel browser, chiunque può cimentarsi nella realizzazione di demo interattive, visualizzazioni grafiche avanzate, simulazioni dinamiche e persino piccoli giochi. L’AI può assistere nel generare codice ottimizzato per animazioni, effetti visivi e logiche di gameplay, permettendo anche a chi ha conoscenze di programmazione limitate di esplorare nuove idee senza dover affrontare la complessità di ambienti di sviluppo più strutturati. Questo rende il processo non solo più accessibile, ma anche estremamente coinvolgente per chiunque voglia avvicinarsi alla programmazione con un approccio ludico e sperimentale.
Alcuni esempi
Analizziamo ora alcuni script sviluppati attraverso un’interazione diretta tra umano e intelligenza artificiale, come ChatGPT, utilizzando una sequenza di richieste successive tramite normali prompt.
Ricordiamo che il codice JavaScript generato dall’AI può essere copiato e incollato in un semplice editor di testo, come il Blocco Note o, meglio ancora, Notepad++ (gratuito). Una volta salvato con estensione .html, sarà pronto per essere eseguito su qualsiasi browser.
Di seguito, in alto, una simulazione del doppio pendolo, un classico esempio di sistema non lineare che mette in evidenza il comportamento caotico tipico di queste dinamiche. In basso, una semplice animazione 3D dal sapore retrò, anni ’80
Guarda il codice:
<!-- Contenitore per il doppio pendolo -->
<div id="pendoloContainer" style="width: 100%; height: 400px; border: 1px solid #ccc; margin-bottom: 20px;"></div>
<!-- Contenitore per il volo infinito -->
<div id="voloContainer" style="width: 100%; height: 400px; border: 1px solid #ccc;"></div>
<!-- Inclusione di Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// PRIMO SCRIPT: Doppio Pendolo
(function() {
let scene, camera, renderer;
const L1 = 1.0, L2 = 1.0;
const m1 = 1.0, m2 = 1.0;
const g = 9.81;
let theta1 = Math.PI / 2, theta2 = Math.PI / 2;
let omega1 = 0.0, omega2 = 0.0;
let state = [theta1, omega1, theta2, omega2];
const dt = 1.0 / 60;
let trailPointsBlue = [];
let trailPointsRed = [];
const trailDuration = 6.0;
// Selettore del div contenitore
const container = document.getElementById('pendoloContainer');
const width = container.clientWidth;
const height = container.clientHeight;
// Creazione scena, camera e renderer
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.position.set(0, 0, 5);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
container.appendChild(renderer.domElement);
// Ingrandimento della scena e spostamento verso l’alto
scene.scale.set(1.8, 1.8, 1.8);
scene.position.y = 1.8;
// Equazioni del moto e integrazione RK4
function derivatives(s) {
let t1 = s[0], w1 = s[1], t2 = s[2], w2 = s[3];
let dtheta1 = w1;
let dtheta2 = w2;
let delta = t1 - t2;
let denom1 = L1 * (2 * m1 + m2 - m2 * Math.cos(2 * delta));
let domega1 = (
-g * (2 * m1 + m2) * Math.sin(t1)
- m2 * g * Math.sin(t1 - 2 * t2)
- 2 * Math.sin(delta) * m2 * (w2 * w2 * L2 + w1 * w1 * L1 * Math.cos(delta))
) / denom1;
let denom2 = L2 * (2 * m1 + m2 - m2 * Math.cos(2 * delta));
let domega2 = (
2 * Math.sin(delta) * (
w1 * w1 * L1 * (m1 + m2)
+ g * (m1 + m2) * Math.cos(t1)
+ w2 * w2 * L2 * m2 * Math.cos(delta)
)
) / denom2;
return [dtheta1, domega1, dtheta2, domega2];
}
function rk4Step(s, dt) {
let k1 = derivatives(s);
let s2 = s.map((val, i) => val + 0.5 * dt * k1[i]);
let k2 = derivatives(s2);
let s3 = s.map((val, i) => val + 0.5 * dt * k2[i]);
let k3 = derivatives(s3);
let s4 = s.map((val, i) => val + dt * k3[i]);
let k4 = derivatives(s4);
return s.map((val, i) => val + dt * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) / 6);
}
// Funzione per l'aggiornamento di un cilindro fra due punti
function updateCylinder(cyl, start, end) {
let direction = new THREE.Vector3().subVectors(end, start);
let length = direction.length();
let midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
cyl.position.copy(midpoint);
cyl.scale.set(1, length, 1);
let axis = new THREE.Vector3(0, 1, 0);
cyl.quaternion.setFromUnitVectors(axis, direction.normalize());
}
// Creazione dei bob e delle aste
const pivot = new THREE.Vector3(0, 0, 0);
const bobGeometry1 = new THREE.SphereGeometry(0.05, 32, 32);
const bobMaterial1 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const bob1 = new THREE.Mesh(bobGeometry1, bobMaterial1);
scene.add(bob1);
const bobGeometry2 = new THREE.SphereGeometry(0.05, 32, 32);
const bobMaterial2 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const bob2 = new THREE.Mesh(bobGeometry2, bobMaterial2);
scene.add(bob2);
const rodGeometry = new THREE.CylinderGeometry(0.01, 0.01, 1, 8);
const rodMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const rod1 = new THREE.Mesh(rodGeometry, rodMaterial);
const rod2 = new THREE.Mesh(rodGeometry, rodMaterial);
scene.add(rod1);
scene.add(rod2);
// Scie
const trailMaterialBlue = new THREE.LineBasicMaterial({ vertexColors: true, transparent: true });
const trailGeometryBlue = new THREE.BufferGeometry();
const trailLineBlue = new THREE.Line(trailGeometryBlue, trailMaterialBlue);
scene.add(trailLineBlue);
const trailMaterialRed = new THREE.LineBasicMaterial({ vertexColors: true, transparent: true });
const trailGeometryRed = new THREE.BufferGeometry();
const trailLineRed = new THREE.Line(trailGeometryRed, trailMaterialRed);
scene.add(trailLineRed);
// Funzioni di supporto
function computePositions() {
let pos1 = new THREE.Vector3(L1 * Math.sin(state[0]), -L1 * Math.cos(state[0]), 0);
let pos2 = pos1.clone().add(new THREE.Vector3(L2 * Math.sin(state[2]), -L2 * Math.cos(state[2]), 0));
return { pos1, pos2 };
}
function updateTrailGeometry(trailPoints, baseColor, geometry, now) {
const numPoints = trailPoints.length;
const positions = new Float32Array(numPoints * 3);
const colors = new Float32Array(numPoints * 3);
for (let i = 0; i < numPoints; i++) {
const { pos, time } = trailPoints[i];
const age = now - time;
const factor = 1 - (age / trailDuration);
positions[3*i] = pos.x;
positions[3*i+1] = pos.y;
positions[3*i+2] = pos.z;
colors[3*i] = baseColor.r * factor;
colors[3*i+1] = baseColor.g * factor;
colors[3*i+2] = baseColor.b * factor;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
}
// Ciclo di animazione
function animate() {
requestAnimationFrame(animate);
// Integrazione
state = rk4Step(state, dt);
const { pos1, pos2 } = computePositions();
bob1.position.copy(pos1);
bob2.position.copy(pos2);
updateCylinder(rod1, pivot, pos1);
updateCylinder(rod2, pos1, pos2);
// Aggiornamento scie
const now = performance.now() / 1000;
trailPointsBlue.push({ pos: bob2.position.clone(), time: now });
trailPointsRed.push({ pos: bob1.position.clone(), time: now });
while (trailPointsBlue.length > 0 && now - trailPointsBlue[0].time > trailDuration) {
trailPointsBlue.shift();
}
while (trailPointsRed.length > 0 && now - trailPointsRed[0].time > trailDuration) {
trailPointsRed.shift();
}
updateTrailGeometry(trailPointsBlue, new THREE.Color(0x00ffff), trailGeometryBlue, now);
updateTrailGeometry(trailPointsRed, new THREE.Color(0xff3333), trailGeometryRed, now);
renderer.render(scene, camera);
}
animate();
// Resize
window.addEventListener('resize', () => {
const w = container.clientWidth;
const h = container.clientHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
});
})();
</script>
<script>
// SECONDO SCRIPT: Volo infinito (invariato, solo rimosso "Attendere Prego")
(function() {
let scene, camera, renderer;
let terrainGroup, terrainMesh;
let buildingsGroup;
let particleSystem, trails, trailsGeometry;
let particleColors, lastPositions;
let terrainOffset = 0;
const clock = new THREE.Clock();
const particleCount = 1000;
const finalColor = new THREE.Color(0x00ffcc);
let baseCameraPos, baseCameraRotation;
let avoidanceVec = new THREE.Vector3();
let tempVec = new THREE.Vector3();
const raycaster = new THREE.Raycaster();
const down = new THREE.Vector3(0, -1, 0);
const container2 = document.getElementById('voloContainer');
const width2 = container2.clientWidth;
const height2 = container2.clientHeight;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, width2 / height2, 0.1, 1000);
camera.position.set(0, 31, 50);
const deltaY = 50 * Math.tan(THREE.Math.degToRad(5));
camera.lookAt(new THREE.Vector3(0, 31 - deltaY, 0));
baseCameraPos = camera.position.clone();
baseCameraRotation = camera.rotation.clone();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width2, height2);
container2.appendChild(renderer.domElement);
// Terreno
const gridSize = 129;
const size = 400, maxHeight = 30, roughness = 0.6;
const heights = generateDiamondSquare(gridSize, roughness);
for (let i = 0, len = heights.length; i < len; i++) {
heights[i] = Math.pow(heights[i], 0.8);
}
const terrainGeometry = new THREE.PlaneGeometry(size, size, gridSize - 1, gridSize - 1);
terrainGeometry.rotateX(-Math.PI / 2);
const posAttr = terrainGeometry.attributes.position;
const vertCount = posAttr.count;
for (let i = 0; i < vertCount; i++) {
const col = i % gridSize;
const normX = (col / (gridSize - 1)) * 2 - 1;
const canyonFactor = 1 - 0.5 * Math.exp(-(normX * normX) / 0.1);
posAttr.setY(i, heights[i] * maxHeight * canyonFactor);
}
terrainGeometry.computeVertexNormals();
terrainGroup = new THREE.Group();
const fillMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
terrainMesh = new THREE.Mesh(terrainGeometry, fillMaterial);
terrainMesh.renderOrder = 0;
terrainGroup.add(terrainMesh);
const edges = new THREE.EdgesGeometry(terrainGeometry);
const wireframeMaterial = new THREE.LineBasicMaterial({ color: 0x00ffcc });
const wireframe = new THREE.LineSegments(edges, wireframeMaterial);
wireframe.renderOrder = 1;
terrainGroup.add(wireframe);
scene.add(terrainGroup);
// Edifici
buildingsGroup = new THREE.Group();
const numBuildings = 1067;
for (let i = 0; i < numBuildings; i++) {
const w = ((5 + Math.random() * 10) / 10) * 3;
const d = ((5 + Math.random() * 10) / 10) * 3;
const h = 5 + Math.random() * 22.5;
const buildingGeometry = new THREE.BoxGeometry(w, h, d);
const buildingFillMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, flatShading: true });
const buildingFillMesh = new THREE.Mesh(buildingGeometry, buildingFillMaterial);
buildingFillMesh.renderOrder = 0;
const buildingEdges = new THREE.EdgesGeometry(buildingGeometry);
const buildingWireMaterial = new THREE.LineBasicMaterial({ color: 0x000000, flatShading: true });
const buildingWireMesh = new THREE.LineSegments(buildingEdges, buildingWireMaterial);
buildingWireMesh.renderOrder = 1;
const buildingGroup = new THREE.Group();
buildingGroup.add(buildingFillMesh);
buildingGroup.add(buildingWireMesh);
const posX = (Math.random() - 0.5) * size;
const posZ = (Math.random() - 0.5) * size;
raycaster.set(new THREE.Vector3(posX, 1000, posZ), down);
const intersects = raycaster.intersectObject(terrainMesh);
const terrainHeight = (intersects.length > 0) ? intersects[0].point.y : 0;
buildingGroup.position.set(posX, terrainHeight - 0.5 + h / 2, posZ);
buildingsGroup.add(buildingGroup);
}
scene.add(buildingsGroup);
// Particelle
const particlesGeometry = new THREE.BufferGeometry();
const posArr = new Float32Array(particleCount * 3);
particleColors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const base = i * 3;
posArr[base] = (Math.random() - 0.5) * 100;
posArr[base + 1] = (Math.random() - 0.5) * 50 + 20;
posArr[base + 2] = -Math.random() * 300;
particleColors[base] = 0;
particleColors[base + 1] = 0;
particleColors[base + 2] = 0;
}
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(posArr, 3));
particlesGeometry.setAttribute('color', new THREE.BufferAttribute(particleColors, 3));
const particleTexture = createCircleTexture();
const particlesMaterial = new THREE.PointsMaterial({
vertexColors: true,
size: 0.25,
map: particleTexture,
alphaTest: 0.5,
transparent: true,
depthTest: true
});
particleSystem = new THREE.Points(particlesGeometry, particlesMaterial);
scene.add(particleSystem);
lastPositions = new Float32Array(posArr);
// Trail
trailsGeometry = new THREE.BufferGeometry();
const trailsArr = new Float32Array(particleCount * 2 * 3);
trailsGeometry.setAttribute('position', new THREE.BufferAttribute(trailsArr, 3));
const trailsMaterial = new THREE.LineBasicMaterial({ color: 0x00ffcc, transparent: true, opacity: 0.8 });
trails = new THREE.LineSegments(trailsGeometry, trailsMaterial);
scene.add(trails);
window.addEventListener('resize', onWindowResize, false);
}
function createCircleTexture() {
const size = 64;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, size, size);
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "#fff";
ctx.fill();
return new THREE.CanvasTexture(canvas);
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
terrainOffset += delta * 10;
terrainGroup.position.z = (terrainOffset % 200) - 100;
buildingsGroup.position.z = (terrainOffset % 200) - 100;
// Particelle
const posArr = particleSystem.geometry.attributes.position.array;
const colArr = particleSystem.geometry.attributes.color.array;
const trailArr = trailsGeometry.attributes.position.array;
const camZ = camera.position.z;
for (let i = 0; i < particleCount; i++) {
const idx = i * 3;
posArr[idx + 2] += delta * 30;
if (posArr[idx + 2] > camZ) {
posArr[idx] = (Math.random() - 0.5) * 100;
posArr[idx + 1] = (Math.random() - 0.5) * 50 + 20;
posArr[idx + 2] = -300 - Math.random() * 100;
colArr[idx] = colArr[idx + 1] = colArr[idx + 2] = 0;
lastPositions[idx] = posArr[idx];
lastPositions[idx + 1] = posArr[idx + 1];
lastPositions[idx + 2] = posArr[idx + 2];
}
let t = (posArr[idx + 2] + 150) / 190;
t = Math.min(Math.max(t, 0), 1);
colArr[idx] = finalColor.r * t;
colArr[idx + 1] = finalColor.g * t;
colArr[idx + 2] = finalColor.b * t;
const trailIdx = i * 6;
if (posArr[idx + 2] > 40) {
trailArr[trailIdx] = lastPositions[idx];
trailArr[trailIdx + 1] = lastPositions[idx + 1];
trailArr[trailIdx + 2] = lastPositions[idx + 2];
trailArr[trailIdx + 3] = posArr[idx];
trailArr[trailIdx + 4] = posArr[idx + 1];
trailArr[trailIdx + 5] = posArr[idx + 2];
} else {
trailArr[trailIdx] = trailArr[trailIdx + 3] = posArr[idx];
trailArr[trailIdx + 1] = trailArr[trailIdx + 4] = posArr[idx + 1];
trailArr[trailIdx + 2] = trailArr[trailIdx + 5] = posArr[idx + 2];
}
lastPositions[idx] = posArr[idx];
lastPositions[idx + 1] = posArr[idx + 1];
lastPositions[idx + 2] = posArr[idx + 2];
}
particleSystem.geometry.attributes.position.needsUpdate = true;
particleSystem.geometry.attributes.color.needsUpdate = true;
trailsGeometry.attributes.position.needsUpdate = true;
// Evitamento edifici
const tTime = clock.getElapsedTime();
tempVec.set(
2 * Math.sin(tTime * 0.3 + 1.0),
1 * Math.sin(tTime * 0.5 + 0.5),
0
);
avoidanceVec.set(0, 0, 0);
const collisionThresholdSq = 100;
const repulsionStrength = 5;
const bChildren = buildingsGroup.children;
for (let i = 0, len = bChildren.length; i < len; i++) {
const bPos = bChildren[i].position;
const distSq = camera.position.distanceToSquared(bPos);
if (distSq < collisionThresholdSq) {
tempVec.copy(camera.position).sub(bPos).normalize();
const factor = repulsionStrength * (1 - Math.sqrt(distSq) / 10);
avoidanceVec.addScaledVector(tempVec, factor);
}
}
tempVec.add(avoidanceVec);
const avoidanceFactor = 1 + avoidanceVec.length() * 0.2;
tempVec.multiplyScalar(avoidanceFactor);
camera.position.copy(baseCameraPos).add(tempVec);
const roll = 0.05 * Math.sin(tTime * 0.7 * avoidanceFactor + 1.2);
const pitch = 0.05 * Math.sin(tTime * 0.9 * avoidanceFactor + 0.3);
const yaw = 0.05 * Math.sin(tTime * 0.4 * avoidanceFactor + 0.8);
camera.rotation.set(
baseCameraRotation.x + pitch,
baseCameraRotation.y + yaw,
baseCameraRotation.z + roll
);
renderer.render(scene, camera);
}
function onWindowResize() {
const w = container2.clientWidth;
const h = container2.clientHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
// Diamond-Square
function generateDiamondSquare(gridSize, roughness) {
const size = gridSize;
const map = new Float32Array(size * size);
for (let i = 0; i < map.length; i++) map[i] = 0;
function set(x, y, val) { map[y * size + x] = val; }
function get(x, y) { return map[y * size + x]; }
set(0, 0, Math.random());
set(size - 1, 0, Math.random());
set(0, size - 1, Math.random());
set(size - 1, size - 1, Math.random());
let step = size - 1;
let scale = roughness;
while (step > 1) {
const half = step >> 1;
for (let y = 0; y < size - 1; y += step) {
for (let x = 0; x < size - 1; x += step) {
const a = get(x, y);
const b = get(x + step, y);
const c = get(x, y + step);
const d = get(x + step, y + step);
set(x + half, y + half, (a + b + c + d) / 4 + (Math.random() - 0.5) * scale);
}
}
for (let y = 0; y < size; y += half) {
for (let x = (y + half) % step; x < size; x += step) {
let sum = 0, count = 0;
if (x - half >= 0) { sum += get(x - half, y); count++; }
if (x + half < size) { sum += get(x + half, y); count++; }
if (y - half >= 0) { sum += get(x, y - half); count++; }
if (y + half < size) { sum += get(x, y + half); count++; }
set(x, y, sum / count + (Math.random() - 0.5) * scale);
}
}
step = half;
scale *= roughness;
}
let min = Infinity, max = -Infinity;
for (let i = 0, len = map.length; i < len; i++) {
if (map[i] < min) min = map[i];
if (map[i] > max) max = map[i];
}
for (let i = 0, len = map.length; i < len; i++) {
map[i] = (map[i] - min) / (max - min);
}
return map;
}
})();
</script>
Per realizzare questo tipo di script sono stati sufficienti pochi prompt. La chiave è comunicare con l'AI in modo chiaro e diretto, evitando ambiguità che potrebbero generare incomprensioni. Anche chi non ha mai programmato può ottenere il risultato desiderato, sebbene una conoscenza di base della programmazione, una mentalità analitica e una comprensione degli algoritmi possano rendere il processo più efficace e ridurre il numero di tentativi necessari.
L'AI generativa, anche in questo ambito, mostra dinamiche simili a quelle riscontrabili in altri contesti creativi, come il rimanere occasionalmente bloccata in schemi ripetitivi senza reali progressi. In questi casi, può essere utile un reset: ripartire dall'ultima versione funzionante del codice e riformulare il prompt per guidare meglio l'AI verso la soluzione desiderata.
Passiamo ora a uno script più complesso in realtà non proprio adatto a JavaScript a causa della natura interpretata del linguaggio e della sua limitata capacità nel calcolo di precisione. Tuttavia questo linguaggio si è rivelato sorprendentemente efficace.
Lo script in questione realizza un’esplorazione automatizzata del celebre frattale noto come insieme di Mandelbrot (a cui Librologica dedicherà presto un saggio breve). Lo zoom avviene in modo casuale, ma sempre lungo la zona di confine tra le regioni del piano complesso in cui la successione converge e quelle in cui diverge, evitando di disperdersi in aree nere o in zone a tinte piatte.
Inoltre, è stata implementata una funzione che calcola la probabilità che una determinata area sia già stata esplorata, basandosi su un principio elaborato dalla stessa AI e qui di seguito riportato:
Poiché l’insieme di Mandelbrot è un frattale, le sue strutture caratteristiche, come il cardioide principale e le bolle laterali, sono facilmente riconoscibili già a livelli di zoom modesti, poiché occupano porzioni relativamente ampie dell’insieme. Tuttavia, man mano che si aumenta lo zoom, la finestra di osservazione si restringe, rivelando dettagli sempre più fini e complessi. In queste regioni più profonde, emergono strutture intricate, spesso meno note e meno esplorate.
Possiamo definire la probabilità P che l’area attualmente osservata sia già stata "vista" (ovvero, appartenga a zone ben conosciute) nel seguente modo:
P = (Area di zone conosciute) / (Area della finestra corrente)
In un contesto dinamico, dove la "conoscibilità" diminuisce con l'aumentare dello zoom, possiamo modellare questa probabilità attraverso una funzione decrescente del fattore di zoom. Ad esempio, definendo:
\( \style{font-size:1.1em;}{\displaystyle P(z) = \frac{1}{z^\alpha}} \)con un esponente α>0 (ad esempio, α=0.1). In questo modo:
- Quando lo zoom è basso (cioè, zoom ≈ 1), P sarà prossima a 1, indicando che la zona visualizzata è tra quelle più “note”.
- Al crescere dello zoom, P decresce, riflettendo la probabilità minore di trovarsi in una zona già ampiamente esplorata.
Questa definizione è ovviamente una semplificazione euristica: essa non deriva da una misura rigorosa sul frattale, ma serve a dare un’indicazione visiva della “novità” della zona attuale in funzione del livello di zoom e della precisione del calcolo. Tale approccio serve a coinvolgere l'osservatore, invitandolo a seguire lo zoom fino a livelli di dettaglio estremi.
L'insieme di Mandelbrot e l'esplorazione dei frattali hanno vissuto la loro epoca d'oro negli anni '90, un periodo in cui si attribuiva anche una certa valenza artistica a queste immagini, frutto di complessi algoritmi matematici. Un contesto molto diverso da quello attuale, in cui i modelli di intelligenza artificiale generano ormai illustrazioni con un processo che, per molti aspetti, rispecchia la creatività umana.
