back
Written by Arthur Jensen — October 30th 2024

Creating an Interactive Simulation

Create a sketch that would simulate an existing natural system - look at physics, biology, and other natural sciences for good examples.
Mice ecosystem and interactive simulation.
Emojis conveying mice ecosystem with cheese and traps.
Interpreting the Brief

Create a sketch that would simulate an existing natural system - look at physics, biology, and other natural sciences for good examples. Start with the environment - where is the system situated? What are the forces the environment might exert on the system? Examine the agents in the system, their relationships to each other and the environment they are in. The look at how this system would develop over time. What are the rules that you are going to come up with, what are the parameters you are going to feed into them and what effect will the changes have on the development of the system. Create classes of entities to represent the components of the system. Use vectors to represent forces existing in the system. Use randomness or noise to generate at least one. Add direct or indirect mouse or keyboard controls

Frog Eating Flies: Utilizing Maxim's Code from Class

In a coding class, my instructor, Maxim, showed an interaction where a frog eats flies and grows larger while the flies reproduce. I'm using this code as a starting point for my own animation project.  Essentially, I'm building off of Maxim's work to learn about generative animation techniques. Seeing how someone else approached this kind of animation helps me understand the concepts and gives me a foundation to build upon.

1let swarm = []
2const swarmSize = 10
3let Hanz
4
5function setup(){
6  createCanvas(800, 800);
7  colorMode(HSB, TWO_PI, 1, 1);
8  for(let i = 0; i < swarmSize; i++){
9    swarm.push(new Fly(createVector(random(width), random(height))))
10  }
11  Hanz = new Frog(createVector(width*0.75, height*0.75))
12}
13
14function draw(){
15  background(3.6, 0.3, 0.7);
16  for(let i = swarm.length - 1; i >= 0; i--){
17    let Daisy = swarm[i];
18    
19    if(Hanz.alive) Hanz.eat(Daisy);
20    
21    if(Daisy.alive){
22      Daisy.move();
23			let baby = Daisy.spawn(swarm)
24      if(baby) {
25				swarm.push(new Fly(createVector(baby.x, baby.y)))
26			}
27      Daisy.display()
28    }
29    else {
30      swarm.splice(i, 1)
31    }
32  }
33  
34  Hanz.display();
35}
Written Code that depicts animation of frog and flies.
Animation of green circle (frog) eating smaller circles (flies) and growing.
Creating Classes of Entities to Represent the Components of the System

I've created three classes; mouse, cheese, and trap. This code simulates a mouse ecosystem where 20 mice wander randomly on a canvas, seeking out 7 cheese circles to eat. As they move, they must avoid 5 red square traps that instantly kill them. Each mouse starts as a small gray circle, but grows larger with every cheese consumed. The mice exhibit simple, random movement, bouncing off the edges of the canvas as they search for food and try to evade the deadly traps.

1let mice = [];
2const mouseCount = 20;
3let traps = [];
4const trapCount = 5;
5let cheeses = [];
6const cheeseCount = 7;
7
8function setup(){
9  createCanvas(800, 800);
10  for(let i = 0; i < mouseCount; i++){
11    mice.push(new Mouse(createVector(random(width), random(height))));
12  }
13  
14  for(let i = 0; i < trapCount; i++){
15    traps.push(new Trap(createVector(random(width), random(height))));
16  }
17  
18  for(let i = 0; i < cheeseCount; i++){
19    cheeses.push(new Cheese(createVector(random(width), random(height))));
20  }
21}
22
23function draw(){
24  background(200, 220, 255);
25
26  // Draw and move the mice
27  for(let i = mice.length - 1; i >= 0; i--){
28    let mouse = mice[i];
29    
30    if(mouse.alive){
31      mouse.move();
32      
33      // Check for traps
34      for(let trap of traps){
35        trap.activate(mouse);
36      }
37      
38      // Check for cheese
39      for(let j = cheeses.length - 1; j >= 0; j--){
40        let cheese = cheeses[j];
41        if(mouse.eat(cheese)){
42          cheeses.splice(j, 1);  // Remove cheese when eaten
43        }
44      }
45      
46      mouse.display();
47    } else {
48      mice.splice(i, 1);  // Remove dead mice
49    }
50  }
51
52  // Draw the traps
53  for(let trap of traps){
54    trap.display();
55  }
56
57  // Draw the cheese
58  for(let cheese of cheeses){
59    cheese.display();
60  }
61}
62
63class Mouse {
64  constructor(_pos){
65    this.pos = _pos;
66    this.vel = createVector(0, 0);
67    this.accel = p5.Vector.random2D();
68    this.radius = 10;
69    this.maxSpeed = 3;
70    this.alive = true;
71  }
72  
73  move(){
74    this.accel = p5.Vector.random2D();
75    this.vel.add(this.accel);
76    this.vel.limit(this.maxSpeed);
77    this.pos.add(this.vel);
78    this.borders();
79  }
80  
81  eat(cheese){
82    if(this.pos.dist(cheese.pos) <= this.radius + cheese.radius){
83      this.radius += 5;  // Mouse gets fatter
84      return true;  // Cheese is eaten
85    }
86    return false;
87  }
88  
89  borders() {
90    if (this.pos.x < -this.radius) this.pos.x = width + this.radius;
91    if (this.pos.y < -this.radius) this.pos.y = height + this.radius;
92    if (this.pos.x > width + this.radius) this.pos.x = -this.radius;
93    if (this.pos.y > height + this.radius) this.pos.y = -this.radius;
94  }
95
96  display(){
97    fill(150);
98    noStroke();
99    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);
100  }
101}
102
103class Trap {
104  constructor(_pos){
105    this.pos = _pos;
106    this.radius = 30;
107  }
108  
109  activate(mouse){
110    if(this.pos.dist(mouse.pos) < this.radius){
111      mouse.alive = false;  // Kill the mouse if too close
112    }
113  }
114  
115  display(){
116    fill(255, 0, 0);
117    noStroke();
118    rect(this.pos.x - this.radius, this.pos.y - this.radius, this.radius * 2, this.radius * 2);  // Square trap
119  }
120}
121
122class Cheese {
123  constructor(_pos){
124    this.pos = _pos;
125    this.radius = 10;
126  }
127  
128  display(){
129    fill(255, 255, 0);
130    noStroke();
131    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);  // Circular cheese
132  }
133}
134
Written Code that depicts mice, cheese, and traps.
Animation that depicts mice, cheese, and traps.
Incorporating Dynamic Traps & Mouse Reproduction

This code simulates a mouse ecosystem where mice reproduce and traps respawn. When two mice are close enough together, there's a chance they'll create a new mouse, adding a population growth element. To make the environment more challenging, traps now disappear and reappear in random locations after killing a mouse. The simulation continuously updates the positions of mice, checks for collisions with cheese and traps, and handles mouse reproduction, creating a more complex and engaging visualization of a mouse ecosystem.

1let mice = [];
2const mouseCount = 20;
3let traps = [];
4const trapCount = 5;
5let cheeses = [];
6const cheeseCount = 7;
7
8function setup(){
9  createCanvas(800, 800);
10  for(let i = 0; i < mouseCount; i++){
11    mice.push(new Mouse(createVector(random(width), random(height))));
12  }
13  
14  for(let i = 0; i < trapCount; i++){
15    traps.push(new Trap(createVector(random(width), random(height))));
16  }
17  
18  for(let i = 0; i < cheeseCount; i++){
19    cheeses.push(new Cheese(createVector(random(width), random(height))));
20  }
21}
22
23function draw(){
24  background(200, 220, 255);
25
26  // Draw and move the mice
27  for(let i = mice.length - 1; i >= 0; i--){
28    let mouse = mice[i];
29    
30    if(mouse.alive){
31      mouse.move();
32      
33      // Check for traps
34      for(let trap of traps){
35        trap.activate(mouse);
36      }
37      
38      // Check for cheese
39      for(let j = cheeses.length - 1; j >= 0; j--){
40        let cheese = cheeses[j];
41        if(mouse.eat(cheese)){
42          cheeses.splice(j, 1);  // Remove cheese when eaten
43        }
44      }
45      
46      // Check for reproduction
47      mouse.reproduce(mice);
48
49      mouse.display();
50    } else {
51      mice.splice(i, 1);  // Remove dead mice
52    }
53  }
54
55  // Draw the traps
56  for(let trap of traps){
57    trap.display();
58  }
59
60  // Draw the cheese
61  for(let cheese of cheeses){
62    cheese.display();
63  }
64}
65
66class Mouse {
67  constructor(_pos){
68    this.pos = _pos;
69    this.vel = createVector(0, 0);
70    this.accel = p5.Vector.random2D();
71    this.radius = 10;
72    this.maxSpeed = 3;
73    this.alive = true;
74    this.canReproduce = false;
75    
76    // Mouse can reproduce after 3 seconds
77    setTimeout(() => {
78      this.canReproduce = true;
79    }, 3000);
80  }
81  
82  move(){
83    this.accel = p5.Vector.random2D();
84    this.vel.add(this.accel);
85    this.vel.limit(this.maxSpeed);
86    this.pos.add(this.vel);
87    this.borders();
88  }
89  
90  eat(cheese){
91    if(this.pos.dist(cheese.pos) <= this.radius + cheese.radius){
92      this.radius += 5;  // Mouse gets fatter
93      return true;  // Cheese is eaten
94    }
95    return false;
96  }
97  
98  reproduce(mice){
99    if(this.canReproduce){
100      for(let otherMouse of mice){
101        if(otherMouse !== this && otherMouse.alive){
102          let distance = this.pos.dist(otherMouse.pos);
103          if(distance < 50 && random(1) < 0.005){  // Small chance to reproduce
104            let babyPos = p5.Vector.lerp(this.pos, otherMouse.pos, 0.5);  // New baby position
105            mice.push(new Mouse(babyPos));  // Add new mouse to the swarm
106            this.canReproduce = false;  // Reset reproduction timer
107            setTimeout(() => {
108              this.canReproduce = true;
109            }, 5000);
110          }
111        }
112      }
113    }
114  }
115  
116  borders() {
117    if (this.pos.x < -this.radius) this.pos.x = width + this.radius;
118    if (this.pos.y < -this.radius) this.pos.y = height + this.radius;
119    if (this.pos.x > width + this.radius) this.pos.x = -this.radius;
120    if (this.pos.y > height + this.radius) this.pos.y = -this.radius;
121  }
122
123  display(){
124    fill(150);
125    noStroke();
126    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);
127  }
128}
129
130class Trap {
131  constructor(_pos){
132    this.pos = _pos;
133    this.radius = 30;
134  }
135  
136  activate(mouse){
137    if(this.pos.dist(mouse.pos) < this.radius){
138      mouse.alive = false;  // Kill the mouse if too close
139      this.respawn();  // Respawn the trap in a new location
140    }
141  }
142  
143  respawn(){
144    // Move trap to a new random location
145    this.pos = createVector(random(width), random(height));
146  }
147
148  display(){
149    fill(255, 0, 0);
150    noStroke();
151    rect(this.pos.x - this.radius, this.pos.y - this.radius, this.radius * 2, this.radius * 2);  // Square trap
152  }
153}
154
155class Cheese {
156  constructor(_pos){
157    this.pos = _pos;
158    this.radius = 10;
159  }
160  
161  display(){
162    fill(255, 255, 0);
163    noStroke();
164    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);  // Circular cheese
165  }
166}
167
Written Code that depicts traps reappearing after the mice encounter them.
Animation that depicts traps reappearing after the mice encounter them.
Renewing Cheese: A Resource in the Ecosystem

In this enhanced mouse ecosystem simulation, cheese becomes a renewable resource. Instead of disappearing permanently when eaten, each piece of cheese is immediately replaced by a new one in a random location. This ensures that the mice always have access to food, preventing starvation and allowing the population to thrive. This constant regeneration of cheese adds another layer of dynamism to the simulation, highlighting the cyclical nature of resource availability in a balanced ecosystem.

1let mice = [];
2const mouseCount = 20;
3let traps = [];
4const trapCount = 5;
5let cheeses = [];
6const cheeseCount = 7;
7
8function setup(){
9  createCanvas(800, 800);
10  for(let i = 0; i < mouseCount; i++){
11    mice.push(new Mouse(createVector(random(width), random(height))));
12  }
13  
14  for(let i = 0; i < trapCount; i++){
15    traps.push(new Trap(createVector(random(width), random(height))));
16  }
17  
18  for(let i = 0; i < cheeseCount; i++){
19    cheeses.push(new Cheese(createVector(random(width), random(height))));
20  }
21}
22
23function draw(){
24  background(200, 220, 255);
25
26  // Draw and move the mice
27  for(let i = mice.length - 1; i >= 0; i--){
28    let mouse = mice[i];
29    
30    if(mouse.alive){
31      mouse.move();
32      
33      // Check for traps
34      for(let trap of traps){
35        trap.activate(mouse);
36      }
37      
38      // Check for cheese
39      for(let j = cheeses.length - 1; j >= 0; j--){
40        let cheese = cheeses[j];
41        if(mouse.eat(cheese)){
42          cheeses.splice(j, 1);  // Remove cheese when eaten
43          cheeses.push(new Cheese(createVector(random(width), random(height))));  // Spawn new cheese
44        }
45      }
46      
47      // Check for reproduction
48      mouse.reproduce(mice);
49
50      mouse.display();
51    } else {
52      mice.splice(i, 1);  // Remove dead mice
53    }
54  }
55
56  // Draw the traps
57  for(let trap of traps){
58    trap.display();
59  }
60
61  // Draw the cheese
62  for(let cheese of cheeses){
63    cheese.display();
64  }
65}
66
67class Mouse {
68  constructor(_pos){
69    this.pos = _pos;
70    this.vel = createVector(0, 0);
71    this.accel = p5.Vector.random2D();
72    this.radius = 10;
73    this.maxSpeed = 3;
74    this.alive = true;
75    this.canReproduce = false;
76    
77    // Mouse can reproduce after 3 seconds
78    setTimeout(() => {
79      this.canReproduce = true;
80    }, 3000);
81  }
82  
83  move(){
84    this.accel = p5.Vector.random2D();
85    this.vel.add(this.accel);
86    this.vel.limit(this.maxSpeed);
87    this.pos.add(this.vel);
88    this.borders();
89  }
90  
91  eat(cheese){
92    if(this.pos.dist(cheese.pos) <= this.radius + cheese.radius){
93      this.radius += 5;  // Mouse gets fatter
94      return true;  // Cheese is eaten
95    }
96    return false;
97  }
98  
99  reproduce(mice){
100    if(this.canReproduce){
101      for(let otherMouse of mice){
102        if(otherMouse !== this && otherMouse.alive){
103          let distance = this.pos.dist(otherMouse.pos);
104          if(distance < 50 && random(1) < 0.005){  // Small chance to reproduce
105            let babyPos = p5.Vector.lerp(this.pos, otherMouse.pos, 0.5);  // New baby position
106            mice.push(new Mouse(babyPos));  // Add new mouse to the swarm
107            this.canReproduce = false;  // Reset reproduction timer
108            setTimeout(() => {
109              this.canReproduce = true;
110            }, 5000);
111          }
112        }
113      }
114    }
115  }
116  
117  borders() {
118    if (this.pos.x < -this.radius) this.pos.x = width + this.radius;
119    if (this.pos.y < -this.radius) this.pos.y = height + this.radius;
120    if (this.pos.x > width + this.radius) this.pos.x = -this.radius;
121    if (this.pos.y > height + this.radius) this.pos.y = -this.radius;
122  }
123
124  display(){
125    fill(150);
126    noStroke();
127    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);
128  }
129}
130
131class Trap {
132  constructor(_pos){
133    this.pos = _pos;
134    this.radius = 30;
135  }
136  
137  activate(mouse){
138    if(this.pos.dist(mouse.pos) < this.radius){
139      mouse.alive = false;  // Kill the mouse if too close
140      this.respawn();  // Respawn the trap in a new location
141    }
142  }
143  
144  respawn(){
145    // Move trap to a new random location
146    this.pos = createVector(random(width), random(height));
147  }
148
149  display(){
150    fill(255, 0, 0);
151    noStroke();
152    rect(this.pos.x - this.radius, this.pos.y - this.radius, this.radius * 2, this.radius * 2);  // Square trap
153  }
154}
155
156class Cheese {
157  constructor(_pos){
158    this.pos = _pos;
159    this.radius = 10;
160  }
161  
162  display(){
163    fill(255, 255, 0);
164    noStroke();
165    ellipse(this.pos.x, this.pos.y, this.radius * 2, this.radius * 2);  // Circular cheese
166  }
167}
168
Written Code that depicts renewing cheese in the mice ecosystem.
Animation that depicts renewing cheese in the mice ecosystem.
Reflection

This project was a great introduction to building simulations with code. I started with simple mice, traps, and cheese, and gradually added more complex behaviors like mouse reproduction, trap respawning, and cheese regeneration. It was fascinating to see how these simple rules created a dynamic and self-sustaining ecosystem.I learned a lot about object-oriented programming and iterative development. This experience has definitely sparked my interest in exploring more advanced simulation features and creative coding techniques in the future.