円を動かしてビリヤードの玉のように跳ね返らせる

こちらです。遊んでみて下さい。HTML はここのとほぼ同じです(読み込む JavaScript のURL を変えるだけ)。円は完全弾性衝突をします。
canvas_sample3.js

var wd, cvs, ctx, r, num;
var Circle = function () {};     //円クラス
var Vector = function (x, y) {   //ベクトル・クラス
  this.x = x; this.y = y;
};
var cir = [];    //あとで円クラスのインスタンスに使う
wd = 500; r = 20; num = 0;
onload = function() {
  cvs = document.getElementById("Canvas"); ctx = cvs.getContext("2d");
  cvs.height = wd; cvs.width = wd;
  cvs.onclick = setBall;
  setInterval(f, 10);
};
function f() {
  if (num == 0) {return;}
  ctx.clearRect(0, 0, wd, wd);
  var fl = geneArray(num);    //フラグ行列の生成
  for (var i = 0; i < num; i++) {
    ctx.beginPath();
    ctx.fillStyle = cir[i].cl;
    ctx.arc(cir[i].o.x, cir[i].o.y, r, 0, Math.PI * 2);
    ctx.fill();
    cir[i].o = add(cir[i].o, cir[i].v);
    if (cir[i].o.x >= wd - r || cir[i].o.x <= r) {cir[i].v.x = -cir[i].v.x;}
    else if (cir[i].o.y >= wd - r || cir[i].o.y <= r) {
      cir[i].v.y = -cir[i].v.y;
    }
    for (var j = 0; j < num; j++) {
      if (!fl[i][j]) {continue;}
      var c1 = cir[i], c2 = cir[j];
      if (dist(c1, c2) <= r * 2) {
        // 衝突時の速度ベクトルの変換
        var x1 = new Vector(c1.o.x, c1.o.y), x2 = new Vector(c2.o.x, c2.o.y);
        var v1 = new Vector(c1.v.x, c1.v.y), v2 = new Vector(c2.v.x, c2.v.y);
        var ra = sub(x1, x2);
        var tmp = mul(innPro(ra, sub(v1, v2)) / absl2(ra), ra);
        c1.v = sub(c1.v, tmp);
        c2.v = add(c2.v, tmp);
        fl[i][j] = false;
      }
    }
  }
}
function geneArray(n) {
  var a = new Array(n);
  for (var i = 0; i < n; i++) {
    a[i] = new Array(n);
    for (var j = 0; j < n; j++) {
      a[i][j] = true;
      if (i <= j) {a[i][j] = false;}
    }
  }
  return a;
}
//ベクトルの演算(上から定数倍、内積、和、差、絶対値の二乗)
function mul(a, v) {
  return new Vector(a * v.x, a * v.y);
}
function innPro(v1, v2) {
  return v1.x * v2.x + v1.y * v2.y;
}
function add(v1, v2) {
  return new Vector(v1.x + v2.x, v1.y + v2.y);
}
function sub(v1, v2) {
  return new Vector(v1.x - v2.x, v1.y - v2.y);
}
function absl2(v) {
  return Math.pow(v.x, 2) + Math.pow(v.y, 2);
}
//クリック時の円の生成
function setBall(e) {
  var x1, y1;
  x1 = e.pageX - cvs.offsetLeft; y1 = e.pageY - cvs.offsetTop;
  if (x1 <= r || x1 >= wd - r || y1 <= r || y1 >= wd - r) {return;}
  num++;
  //円クラスのインスタンスの生成。cir.o は円の中心の点を指すベクトル、cir.v は速度ベクトル、cir.cl は円の色
  cir[num - 1] = new Circle();
  cir[num - 1].o = new Vector(x1, y1);
  cir[num - 1].v = new Vector((Math.random() * 2 + 2) * (Math.random() - 0.5) * 2,
                (Math.random() * 2 + 2) * (Math.random() - 0.5) * 2);
  cir[num - 1].cl = "rgb(" + rnd() + "," + rnd() + "," + rnd() + ")";
  if (near(num - 1)) {num--; pop(cir); return;}
}
function near(i) {  //円どうしが近すぎて生成できないなら true
  if (i == 0) {return false;}
  for (var j = 0; j < i; j++) {
    if (dist(cir[i], cir[j]) <= r * 2) {return true;}
  }
  return false;
}
function dist(c1, c2) {
  return Math.sqrt(absl2(sub(c1.o, c2.o)));
}
function rnd() {
  return String(Math.floor(Math.random()*256));
}
function cvsClear() {
  ctx.clearRect(0, 0, wd, wd);
  num = 0;
}

JavaScript で初めてオブジェクト指向プログラミングをやってみたけれど、意外とできるものだな。というか、OOP を使わなければこんなに簡単にできなかったと思う。OOP はむずかしいのではなくて、プログラミングをわかりやすく、容易にするための手法なのだということを実感する。例えば上のプログラムなら、cir[i].v は i番目の円の速度、cir[i].o.x なら i番目の円の中心の x座標、dist(c1, c2) なら円c1(の中心)と円c2(の中心)の距離と、すぐわかるわけだ。

追記

トップレベルの関数をインスタンス・メッソドに書きなおしました。(11/4)
canvas_sample3a.js

var wd, cvs, ctx, r, num;
//円クラス
var Circle = function () {
  this.dist = function (c) {
    return Math.sqrt(this.o.sub(c.o).absl2())
  };
};
//ベクトル・クラス
var Vector = function (x, y) {
  this.x = x; this.y = y;
  //ベクトルの演算(上から定数倍、内積、和、差、絶対値の二乗)
  this.mul = function (a) {
    return new Vector(a * this.x, a * this.y);
  };
  this.innPro = function (v) {
    return this.x * v.x + this.y * v.y;
  };
  this.add = function (v) {
    return new Vector(this.x + v.x, this.y + v.y);
  };
  this.sub = function (v) {
    return new Vector(this.x - v.x, this.y - v.y);
  };
  this.absl2 = function () {
    return Math.pow(this.x, 2) + Math.pow(this.y, 2);
  };
};

var cir = [];    //あとで円クラスのインスタンスに使う
wd = 500; r = 20; num = 0;
onload = function() {
  cvs = document.getElementById("Canvas"); ctx = cvs.getContext("2d");
  cvs.height = wd; cvs.width = wd;
  cvs.onclick = setBall;
  setInterval(f, 10);
}
function f() {
  if (num == 0) {return;}
  ctx.clearRect(0, 0, wd, wd);
  var fl = geneArray(num);    //フラグ行列の生成
  for (var i = 0; i < num; i++) {
    ctx.beginPath();
    ctx.fillStyle = cir[i].cl;
    ctx.arc(cir[i].o.x, cir[i].o.y, r, 0, Math.PI * 2);
    ctx.fill();
    cir[i].o = cir[i].o.add(cir[i].v);
    if (cir[i].o.x >= wd - r || cir[i].o.x <= r) {cir[i].v.x = -cir[i].v.x;}
    else if (cir[i].o.y >= wd - r || cir[i].o.y <= r) {
      cir[i].v.y = -cir[i].v.y;
    }
    for (var j = 0; j < num; j++) {
      if (!fl[i][j]) {continue;}
      var c1 = cir[i], c2 = cir[j];
      if (c1.dist(c2) <= r * 2) {
        // 衝突時の速度ベクトルの変換
        var v1 = c1.v, v2 = c2.v;
        var ra = c1.o.sub(c2.o);
        var tmp = ra.mul(ra.innPro(v1.sub(v2)) / ra.absl2());
        c1.v = v1.sub(tmp);
        c2.v = v2.add(tmp);
        fl[i][j] = false;
      }
    }
  }
}
function geneArray(n) {
  var a = new Array(n);
  for (var i = 0; i < n; i++) {
    a[i] = new Array(n);
    for (var j = 0; j < n; j++) {
      a[i][j] = true;
      if (i <= j) {a[i][j] = false;}
    }
  }
  return a;
}
//クリック時の円の生成
function setBall(e) {
  var x1, y1;
  x1 = e.pageX - cvs.offsetLeft; y1 = e.pageY - cvs.offsetTop;
  if (x1 <= r || x1 >= wd - r || y1 <= r || y1 >= wd - r) {return;}
  num++;
  //円クラスのインスタンスの生成。cir.o は円の中心の点を指すベクトル、cir.v は速度ベクトル、cir.cl は円の色
  cir[num - 1] = new Circle();
  cir[num - 1].o = new Vector(x1, y1);
  cir[num - 1].v = new Vector((Math.random() * 2 + 2) * (Math.random() - 0.5) * 2,
                (Math.random() * 2 + 2) * (Math.random() - 0.5) * 2);
  cir[num - 1].cl = "rgb(" + rnd() + "," + rnd() + "," + rnd() + ")";
  if (near(num - 1)) {num--; pop(cir); return;}
}
function near(i) {  //円どうしが近すぎて生成できないなら true
  if (i == 0) {return false;}
  for (var j = 0; j < i; j++) {
    if (cir[i].dist(cir[j]) <= r * 2) {return true;}
  }
  return false;
}
function rnd() {
  return String(Math.floor(Math.random()*256));
}
function cvsClear() {
  ctx.clearRect(0, 0, wd, wd);
  num = 0;
}