diff --git a/rubber.c b/rubber.c
new file mode 100644
index 0000000..2835752
--- /dev/null
+++ b/rubber.c
@@ -0,0 +1,165 @@
+
+#include <math.h>
+#include <stdlib.h>
+#include <helianthus.h>
+
+
+
+#define MAX_BALLS   1000
+#define MAX_LINKS   10000
+#define BALL_RADIUS 1.0
+#define BALL_FADE   0.8
+#define LINK_K      0.05
+#define GRID_SIZE   5
+
+
+
+typedef struct {
+  double x, y;
+  double vx, vy;
+} Ball;
+
+
+typedef struct {
+  double dx, dy;
+  Ball *a, *b;
+} Link;
+
+
+
+Ball balls[MAX_BALLS];
+Link links[MAX_LINKS];
+int ballsCount = 0;
+int linksCount = 0;
+
+double remainDt;
+
+Ball *mball;
+double mdx, mdy;
+double mx, my;
+
+
+
+void ballUpdate(Ball *ball, double dt) {
+  double fade = pow(BALL_FADE, dt);
+  ball->x += ball->vx*dt;
+  ball->y += ball->vy*dt;
+  ball->vx *= fade;
+  ball->vy *= fade;
+}
+
+
+void linkUpdate(Link *link, double dt) {
+  double dx = link->b->x - link->a->x;
+  double dy = link->b->y - link->a->y;
+
+  double dvx = (dx - link->dx)*0.5*LINK_K;
+  double dvy = (dy - link->dy)*0.5*LINK_K;
+
+  link->a->vx += dvx; link->b->vx -= dvx;
+  link->a->vy += dvy; link->b->vy -= dvy;
+}
+
+
+void update(double dt) {
+  remainDt += dt;
+  while(remainDt > 0.001) {
+    remainDt -= 0.001;
+    for(int i = 0; i < linksCount; ++i)
+      linkUpdate(&links[i], dt);
+    for(int i = 0; i < ballsCount; ++i)
+      ballUpdate(&balls[i], dt);
+  }
+}
+
+
+void init() {
+  double dist = 3*BALL_RADIUS;
+  for(int i = 0; i < GRID_SIZE; ++i) {
+    for(int j = 0; j < GRID_SIZE; ++j) {
+      Ball *ball = &balls[ballsCount++];
+      ball->x = (i - 0.5*(GRID_SIZE - 1))*dist;
+      ball->y = (j - 0.5*(GRID_SIZE - 1))*dist;
+      if (i > 0) {
+        Link *link = &links[linksCount++];
+        link->a = ball - GRID_SIZE;
+        link->b = ball;
+        link->dx = dist;
+      }
+      if (j > 0) {
+        Link *link = &links[linksCount++];
+        link->a = ball - 1;
+        link->b = ball;
+        link->dy = dist;
+      }
+    }
+  }
+}
+
+
+void draw() {
+  double dt = windowGetFrameTime();
+  double w = windowGetWidth();
+  double h = windowGetHeight();
+
+  saveState();
+  translate(w/2, h/2);
+  zoom(20);
+
+  mx = mouseTransformedX();
+  my = mouseTransformedY();
+  if (mouseDown("left")) {
+    if (!mball) {
+      for(int i = ballsCount-1; i >= 0; --i) {
+        Ball *ball = &balls[i];
+        double dx = mx - ball->x;
+        double dy = my - ball->y;
+        if (dx*dx + dy*dy <= BALL_RADIUS*BALL_RADIUS) {
+          mball = ball;
+          mdx = dx;
+          mdy = dy;
+          break;
+        }
+      }
+    }
+  } else {
+    mball = NULL;
+  }
+
+
+  update(dt);
+
+  if (mball) {
+    mball->x = mx - mdx;
+    mball->y = my - mdy;
+    mball->vx = 0;
+    mball->vy = 0;
+  }
+
+  stroke(COLOR_BLACK);
+  strokeWidth(BALL_RADIUS*0.1);
+  noFill();
+  for(int i = 0; i < linksCount; ++i) {
+    Link *link = &links[i];
+    line(link->a->x, link->a->y, link->b->x, link->b->y);
+  }
+
+  noStroke();
+  fill(COLOR_BLUE);
+  for(int i = 0; i < ballsCount; ++i) {
+    Ball *ball = &balls[i];
+    circle(ball->x, ball->y, BALL_RADIUS);
+  }
+
+  restoreState();
+}
+
+
+int main() {
+  windowSetResizable(TRUE);
+  windowSetVariableFrameRate();
+  windowSetInit(&init);
+  windowSetDraw(&draw);
+  windowRun();
+  return 0;
+}