· informatica · 9 min read

Falling Sand Simulation in C++ e raylib con automi cellulari

In questo tutorial, vedremo come creare una simulazione di sabbia cadente in C++ utilizzando la libreria grafica raylib. La simulazione di sabbia cadente è un esempio classico di simulazione basata su celle, in cui le particelle di sabbia cadono e interagiscono con l'ambiente circostante.

In questo tutorial, vedremo come creare una simulazione di sabbia cadente in C++ utilizzando la libreria grafica raylib. La simulazione di sabbia cadente è un esempio classico di simulazione basata su celle, in cui le particelle di sabbia cadono e interagiscono con l'ambiente circostante.

In questo articolo vediamo come realizzare un esempio classico di simulazione basata su automi cellulari: la simulazione di sabbia cadente. Utilizzeremo il linguaggio di programmazione C++ e la libreria grafica raylib per creare un’applicazione che simula il comportamento di particelle di sabbia che cadono e interagiscono con l’ambiente circostante.

Introduzione alla simulazione di sabbia cadente (falling sand simulation)

La simulazione di sabbia cadente è un esempio classico di simulazione basata su celle, in cui le particelle di sabbia cadono verso il basso e interagiscono con l’ambiente circostante. Le particelle di sabbia possono accumularsi, formare pile e interagire con altri materiali, come l’acqua o il fuoco.

In questa simulazione, utilizzeremo un’implementazione semplificata di un automa cellulare per modellare il comportamento delle particelle di sabbia. Ogni cella della griglia rappresenterà uno spazio in cui può trovarsi una particella di sabbia. Le particelle di sabbia cadono verso il basso e possono interagire con altre particelle o con l’ambiente circostante.

Requisiti

Per seguire questo tutorial, è necessario avere installato il compilatore C++ e la libreria grafica raylib sul proprio sistema. Se non hai ancora installato raylib, puoi seguire la guida Installare raylib su CLion (Windows) per configurare l’ambiente di sviluppo.

Per questo tutorial, utilizzeremo CLion come IDE per lo sviluppo in C++. Il codice da cui partiremo è invece il mio raylib starter project su GitHub.

Per utilizzarlo come template per il tuo progetto, puoi scaricarlo come file ZIP o selezionare “Use this template” per crearne una copia sul tuo account GitHub. A quel punto, puoi clonare il repository sul tuo computer e importarlo in CLion per iniziare a lavorare.

Implementazione della simulazione di sabbia cadente

Per prima cosa dovremo impostare le costantti per la simulazione, in particolare la larghezza e l’altezza della finestra di visualizzazione e la dimensione delle celle della griglia.

#include "raylib.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 450
#define CELL_SIZE 5
#define GRID_WIDTH (SCREEN_WIDTH / CELL_SIZE)
#define GRID_HEIGHT (SCREEN_HEIGHT / CELL_SIZE)

La griglia verrà rappresentata da un vettore di booleani, in cui ogni cella indica se è presente una particella di sabbia o meno. Inizializzeremo la griglia con tutte le celle vuote. dopo aver incluso vector dalla libreria standard C++.

#include <vector>
std::vector<bool> world(GRID_WIDTH * GRID_HEIGHT,false);

Per disegnare la griglia, creeremo una funzione DrawWorld che disegnerà un rettangolo per ogni cella della griglia in base al valore del vettore world.

void DrawWorld(const std::vector<bool>& world)
{
for (int y = 0; y < GRID_HEIGHT; y++)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
DrawRectangle(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, WHITE);
}
}
}
}

Per simulare il movimento delle particelle di sabbia, creeremo una funzione UpdateWorld che sposterà le particelle verso il basso se possibile. Se una particella è già presente nella cella sottostante, la particella corrente rimarrà ferma.

void UpdateWorld(std::vector<bool>& world)
{
for (int y = GRID_HEIGHT - 1; y >= 0; y--)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
if (y < GRID_HEIGHT - 1 && !world[(y + 1) * GRID_WIDTH + x])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x] = true;
}
}
}
}
}

Per avere una caduta più realistica, possiamo aggiungere un po’ di casualità al movimento delle particelle di sabbia. Modificheremo la funzione UpdateWorld per spostare le particelle in una direzione casuale se possibile. Questo renderà la simulazione più interessante e naturale, ma l’effetto non è troppo “sabbioso”.

void UpdateWorld(std::vector<bool>& world)
{
for (int y = GRID_HEIGHT - 1; y >= 0; y--)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
if (y < GRID_HEIGHT - 1)
{
int dx = GetRandomValue(-1, 1);
if (x + dx >= 0 && x + dx < GRID_WIDTH && !world[(y + 1) * GRID_WIDTH + x + dx])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x + dx] = true;
}
}
}
}
}
}

Per rendere l’effetto più sabbioso è necessarrio prevedere che i granelli di sabbia non possano accumularsi troppo uno soprra l’altro. Modificheremo la funzione UpdateWorld per evitare che le particelle di sabbia si sovrappongano se possibile. Se una particella di sabbia è già presente nella cella sottostante, la particella corrente si sposterà in una direzione casuale.

void UpdateWorld(std::vector<bool>& world)
{
for (int y = GRID_HEIGHT - 1; y >= 0; y--)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
if (y < GRID_HEIGHT - 1)
{
if (!world[(y + 1) * GRID_WIDTH + x])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x] = true;
}
else
{
int dx = GetRandomValue(-1, 1);
if (x + dx >= 0 && x + dx < GRID_WIDTH && !world[(y + 1) * GRID_WIDTH + x + dx])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x + dx] = true;
}
}
}
}
}
}
}

Infine, creeremo il ciclo principale del gioco in cui disegneremo la griglia e aggiorneremo lo stato del mondo ad ogni frame.

int main()
{
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Falling Sand Simulation");
SetTargetFPS(60);
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(BLACK);
UpdateWorld(world);
DrawWorld(world);
EndDrawing();
}
CloseWindow();
return 0;
}

Interazione con il mondo attraverso il mouse

Per rendere la simulazione più interattiva, possiamo consentire all’utente di aggiungere particelle di sabbia al mondo facendo clic con il mouse. Aggiungeremo una funzione AddSand che imposterà una particella di sabbia nella cella corrispondente alla posizione del mouse.

void AddSand(std::vector<bool>& world, int mouseX, int mouseY) {
int x = mouseX / CELL_SIZE;
int y = mouseY / CELL_SIZE;
if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
world[y * GRID_WIDTH + x] = true;
}
}

Falling Sand Simulation

Quello che otterremo sarà una simulazione personaalizzabile con comportamenti diversi a seconda delle scelte fatte e ulteriormente arricchibile con nuove funzionalità, ad esempio potresti:

  • Aggiungere nuovi materiali come acqua o fuoco e definire le loro interazioni con la sabbia.
  • Implementare regole più complesse per il movimento delle particelle di sabbia, come la formazione di pile o la creazione di strutture complesse.
  • Aggiungere controlli per modificare le impostazioni della simulazione, come la velocità di aggiornamento o la densità delle particelle di sabbia.
  • Creare un’interfaccia utente grafica per modificare le impostazioni della simulazione in tempo reale.
  • Salvare e caricare lo stato del mondo per riprendere la simulazione da un punto specifico.

Con un po’ di creatività e sforzo, puoi creare una simulazione di sabbia cadente molto interessante e coinvolgente che ti permetterà di esplorare i concetti di automi cellulari e simulazioni basate su celle.

Buona simulazione!

Codice

Il codice completo per la simulazione di sabbia cadente in C++ con raylib è disponibile su GitHub: raylib-falling-sand-simulation

Ecco per ogni evenienza il codice completo:

#include <vector>
#include "raylib.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 450
#define CELL_SIZE 5
#define GRID_WIDTH (SCREEN_WIDTH / CELL_SIZE)
#define GRID_HEIGHT (SCREEN_HEIGHT / CELL_SIZE)
void DrawWorld(const std::vector<bool> &world) {
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
if (world[y * GRID_WIDTH + x]) {
DrawRectangle(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, LIGHTGRAY);
}
}
}
}
void UpdateWorld(std::vector<bool>& world)
{
for (int y = GRID_HEIGHT - 1; y >= 0; y--)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
if (y < GRID_HEIGHT - 1)
{
if (!world[(y + 1) * GRID_WIDTH + x])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x] = true;
}
else
{
int dx = GetRandomValue(-1, 1);
if (x + dx >= 0 && x + dx < GRID_WIDTH && !world[(y + 1) * GRID_WIDTH + x + dx])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x + dx] = true;
}
}
}
}
}
}
}
void AddSand(std::vector<bool> &world, int mouseX, int mouseY) {
int x = mouseX / CELL_SIZE;
int y = mouseY / CELL_SIZE;
if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
world[y * GRID_WIDTH + x] = true;
}
}
int main() {
std::vector<bool> world(GRID_WIDTH * GRID_HEIGHT, false);
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Falling Sand Simulation");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
AddSand(world, GetMouseX(), GetMouseY());
}
UpdateWorld(world);
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
DrawWorld(world);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
#include <vector>
#include "raylib.h"
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 450
#define CELL_SIZE 5
#define GRID_WIDTH (SCREEN_WIDTH / CELL_SIZE)
#define GRID_HEIGHT (SCREEN_HEIGHT / CELL_SIZE)
void DrawWorld(const std::vector<bool> &world) {
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
if (world[y * GRID_WIDTH + x]) {
DrawRectangle(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, LIGHTGRAY);
}
}
}
}
void UpdateWorld(std::vector<bool>& world)
{
for (int y = GRID_HEIGHT - 1; y >= 0; y--)
{
for (int x = 0; x < GRID_WIDTH; x++)
{
if (world[y * GRID_WIDTH + x])
{
if (y < GRID_HEIGHT - 1)
{
if (!world[(y + 1) * GRID_WIDTH + x])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x] = true;
}
else
{
int dx = GetRandomValue(-1, 1);
if (x + dx >= 0 && x + dx < GRID_WIDTH && !world[(y + 1) * GRID_WIDTH + x + dx])
{
world[y * GRID_WIDTH + x] = false;
world[(y + 1) * GRID_WIDTH + x + dx] = true;
}
}
}
}
}
}
}
void AddSand(std::vector<bool> &world, int mouseX, int mouseY) {
int x = mouseX / CELL_SIZE;
int y = mouseY / CELL_SIZE;
if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
world[y * GRID_WIDTH + x] = true;
}
}
int main() {
std::vector<bool> world(GRID_WIDTH * GRID_HEIGHT, false);
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Falling Sand Simulation");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
AddSand(world, GetMouseX(), GetMouseY());
}
UpdateWorld(world);
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
DrawWorld(world);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
    Back to Blog

    Related Posts

    View All Posts »
    Struct in C++

    Struct in C++

    Le struct rappresentano un elemento fondamentale del linguaggio C++, offrendo un modo flessibile e strutturato per gestire dati complessi. La loro semplicità d'uso e i numerosi vantaggi le rendono uno strumento prezioso per qualsiasi programmatore C++.