Jupyter at Bryn Mawr College |
|||
Public notebooks: /services/public/dblank / CS110 Intro to Computing / 2017-Spring / Lectures |
In this exploration, we examine the idea of an object, and begin writing our first object-oriented programming study.
This code is from the discussion on Bouncing Balls. We need to keep track of the ball's velocities in the x
and y
direction, keep track of where it is (x
, y
), and the global time.
float g = 9.8;
float dt;
float t;
float vx1;
float vy1;
float x1;
float y1;
float vx2;
float vy2;
float x2;
float y2;
void setup() {
size(200, 500);
dt = 0.1;
t = 0;
x1 = width/2;
y1 = 50;
vx1 = 50.0;
vy1 = 0.0;
x2 = width/2;
y2 = 25;
vx2 = -50.0;
vy2 = -10.0;
}
void drawBall(float x, float y, int w, int h) {
fill(255, 0, 0);
ellipse(x, y, w, h);
}
void draw() {
background(200);
// gravity
vy1 = vy1 + g * dt;
float dx = vx1 * dt;
if (((x1 + dx) > width) || ((x1 + dx) < 0)) {
vx1 = vx1 * -0.8;
} else {
x1 = x1 + dx;
}
vy2 = vy2 + g * dt;
dx = vx2 * dt;
if (((x2 + dx) > width) || ((x2 + dx) < 0)) {
vx2 = vx2 * -0.8;
} else {
x2 = x2 + dx;
}
float dy = vy1 * dt;
if (((y1 + dy) > height) || ((y1 + dy) < 0)) {
vy1 = vy1 * -0.8;
} else {
y1 = y1 + dy;
}
dy = vy2 * dt;
if (((y2 + dy) > height) || ((y2 + dy) < 0)) {
vy2 = vy2 * -0.8;
} else {
y2 = y2 + dy;
}
drawBall(x1, y1, 10, 10);
drawBall(x2, y2, 15, 15);
t = t + dt;
}
float g = 9.8;
float dt;
float t;
class Ball {
float x;
float y;
float vx;
float vy;
float w;
float h;
Ball() {
x = width/2;
y = 50;
vx = 50.0;
vy = 0.0;
w = 10;
h = 10;
}
void move() {
vy = vy + g * dt;
float dx = vx * dt;
if (((x + dx) > width) || ((x + dx) < 0)) {
vx = vx * -0.8;
} else {
x = x + dx;
}
float dy = vy * dt;
if (((y + dy) > height) || ((y + dy) < 0)) {
vy = vy * -0.8;
} else {
y = y + dy;
}
}
void draw() {
fill(255, 0, 0);
ellipse(x, y, w, h);
}
}
Ball pluto = new Ball();
void setup() {
size(200, 500);
dt = 0.1;
t = 0;
}
void draw() {
background(200);
t = t + dt;
pluto.move();
pluto.draw();
}
What if we wanted to add more balls? We would need to add variables for vx, vy, x, y... for each ball!
A better way is to let each ball keep track of its own properties. We do this by making a class definition, and storing each set of variables inside the Ball object.
Here we will move all of the ball-specific variables "into the Ball class" and leave the others.
Four steps for turning regular code into Object-Oriented code:
class
constructor
this
to represent the current objectnew
We define a class, create an instance of a Ball, and "bounce" it.
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball ball; // the ball
class Ball {
float x;
float y;
int w;
int h;
float vx;
float vy;
// Jargon: the "constructor"
Ball(float x, float y, int w, int h, float vx, float vy) {
// Idea: "this" refers to the current ball
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
// Jargon: an object's function is called a "method"
void moveYourself() {
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
float dy = this.vy * dt;
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void drawYourself() {
fill(255, 0, 0);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
// Jargon: we create an "instance" of the Ball class:
ball = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
t = 0;
}
void draw() {
background(200);
ball.moveYourself();
ball.drawYourself();
t = t + dt;
}
If we wanted to have two (or more) we need only create more instances:
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball ball0; // the balls
Ball ball1;
class Ball {
float x;
float y;
int w;
int h;
float vx;
float vy;
// Jargon: the "constructor"
Ball(float x, float y, int w, int h, float vx, float vy) {
// Idea: "this" refers to the current ball
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
// Jargon: an object's function is called a "method"
void moveYourself() {
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
float dy = this.vy * dt;
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void drawYourself() {
fill(255, 0, 0);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
// Jargon: we create an "instance" of the Ball class:
ball0 = new Ball(width/2, 50, 10, 10, 50.0, 20.0);
ball1 = new Ball(width/4, 50, 10, 10, -29.0, -5.0);
t = 0;
}
void draw() {
background(200);
ball0.moveYourself();
ball0.drawYourself();
ball1.moveYourself();
ball1.drawYourself();
t = t + dt;
}
What if we wanted 10? Or 1,000?
To allow a large number of items, we use arrays.
Using an array takes three steps:
Ball[] balls;
as the type to define the arrayballs = new Ball[1];
new Ball(...);
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball[] balls; // the balls
class Ball {
float x;
float y;
int w;
int h;
float vx;
float vy;
// Jargon: the "constructor"
Ball(float x, float y, int w, int h, float vx, float vy) {
// Idea: "this" refers to the current ball
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
// Jargon: an object's function is called a "method"
void moveYourself() {
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
float dy = this.vy * dt;
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void drawYourself() {
fill(255, 0, 0);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
balls = new Ball[1];
// Jargon: we create an "instance" of the Ball class:
balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
t = 0;
}
void draw() {
background(200);
balls[0].moveYourself();
balls[0].drawYourself();
t = t + dt;
}
It is fairly easy to add more balls:
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball [] balls; // the balls
class Ball {
float x;
float y;
int w;
int h;
float vx;
float vy;
// Jargon: the "constructor"
Ball(float x, float y, int w, int h, float vx, float vy) {
// Idea: "this" refers to the current ball
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
// Jargon: an object's function is called a "method"
void moveYourself() {
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
float dy = this.vy * dt;
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void drawYourself() {
fill(255, 0, 0);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
balls = new Ball[2];
// Jargon: we create an "instance" of the Ball class:
balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
balls[1] = new Ball(width/4, 50, 10, 10, 50.0, 0.0);
t = 0;
}
void draw() {
background(200);
balls[0].moveYourself();
balls[0].drawYourself();
balls[1].moveYourself();
balls[1].drawYourself();
t = t + dt;
}
It can be even easier!
Make code simpler:
.length
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball[] balls; // the balls
class Ball {
float x;
float y;
int w;
int h;
float vx;
float vy;
color c;
// Jargon: the "constructor"
Ball(float x, float y, int w, int h, float vx, float vy) {
// Idea: "this" refers to the current ball
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
this.c = color(random(255), random(255), random(255));
}
// Jargon: an object's function is called a "method"
void moveYourself() {
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
float dy = this.vy * dt;
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void drawYourself() {
fill(this.c);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
balls = new Ball[200];
// Jargon: we create an "instance" of the Ball class:
for (int i = 0; i < balls.length; i++) {
balls[i] = new Ball(width * random(1), height * random(1), 10, 10, random(50) - 25, 0.0);
}
t = 0;
}
void draw() {
background(128);
for (int i = 0; i < balls.length; i++) {
balls[i].moveYourself();
balls[i].drawYourself();
}
t = t + dt;
}
Cut and paste the above code and try some variations:
It would be cool if the balls could detect when they run into each other, just like it detects when it hits a wall. To do that, we go through the other balls, and identify when they "overlap".
Then, we simply swap their velocities.
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball [] balls; // the balls
float distance(float x1, float y1, float x2, float y2) {
return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}
class Ball {
color c;
float x;
float y;
int w;
int h;
float vx;
float vy;
Ball(color c, float x, float y, int w, int h, float vx, float vy) {
this.c = c;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
void move() {
for (int i = 0; i < balls.length; i++) {
if (balls[i] != this) {
if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
float temp = this.vx;
this.vx = balls[i].vx;
balls[i].vx = temp;
temp = this.vy;
this.vy = balls[i].vy;
balls[i].vy = temp;
break;
}
}
}
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
float dy = this.vy * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void draw() {
fill(this.c);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
balls = new Ball[10];
for (int i = 0; i < balls.length; i++) {
balls[i] = new Ball(color(random(255), random(255), random(255)), random(width), random(40), 10, 10, random(50), 0.0);
}
t = 0;
}
void draw() {
background(128);
for (int i = 0; i < balls.length; i++) {
balls[i].move();
balls[i].draw();
}
t = t + dt;
}
Variations:
// Globals:
float g = 9.8; // gravity
float dt = 0.1; // change in time
float t = 0; // current time
Ball [] balls; // the balls
float distance(float x1, float y1, float x2, float y2) {
return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}
class Ball {
color c;
float x;
float y;
int w;
int h;
float vx;
float vy;
Ball(color c, float x, float y, int w, int h, float vx, float vy) {
this.c = c;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
}
void move() {
for (int i = 0; i < COUNT; i++) {
if (balls[i] != this) {
if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
float temp = this.vx;
this.vx = balls[i].vx;
balls[i].vx = temp;
temp = this.vy;
this.vy = balls[i].vy;
balls[i].vy = temp;
break;
}
}
}
this.vy = this.vy + g * dt;
float dx = this.vx * dt;
float dy = this.vy * dt;
if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
this.vx = this.vx * -0.8;
} else {
this.x = this.x + dx;
}
if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
this.vy = this.vy * -0.8;
} else {
this.y = this.y + dy;
}
}
void draw() {
fill(this.c);
ellipse(this.x, this.y, this.w, this.h);
}
}
void setup() {
size(200, 500);
balls = new Ball[100];
COUNT = 0;
t = 0;
}
int COUNT = 0;
void mousePressed() {
balls[COUNT] = new Ball(color(random(255), random(255), random(255)),
mouseX, mouseY, 10, 10, random(50) - 25, 0);
COUNT++;
}
void draw() {
background(128);
for (int i = 0; i < COUNT; i++) {
balls[i].move();
balls[i].draw();
}
t = t + dt;
}