JavaScript – Lineart

Just re-implemented the lineart for learning JavaScript and Jasmine. Though I’m not yet feeling right on the slew of “function()” and semicolons, JavaScript’s versatality these days is tempting enough to try. Maybe I need more practice. Should try CoffeeScript? hmm, not sure.

The following is working example and codes.

http://jsdo.it/parroty/bf5T?lang=en

HTML

<!DOCTYPE html>
<head>
  <script type="text/javascript" charset="utf-8" src="src/Line.js"></script>
</head>
<body onLoad="init()" style="background-color:#000000">
  <canvas id="sample1" width="100%" height="100%" style="border:solid 1px;background-color:#000000"></canvas>
</body>
</html>

JavaScript

var canvas;
var ctx;
var points;
var colors;

//------- Random Class -------
var Random = function() {};
Random.generate = function(range) {
  return Math.floor(Math.random() * range);
}

//------- Color Class -------
var Color = function(r, g, b) {
  this.r  = (r === undefined ? 0 : r);
  this.g  = (g === undefined ? 0 : g);
  this.b  = (b === undefined ? 0 : b);
  this.sr = 0;
  this.sg = 0;
  this.sb = 0;
}

Color.SPEED_BASE    = 2;
Color.SPEED_VARIANT = 4;

Color.prototype = {
  clone: function() {
    var c = new Color(this.r, this.g, this.b);
    c.setSpeeds(this.sr, this.sg, this.sb);
    return c;
  },

  rgb: function() {
    return [this.r, this.g, this.b];
  },

  speeds: function() {
    return [this.sr, this.sg, this.sb];
  },

  setSpeeds: function(sr, sg, sb) {
    this.sr = sr;
    this.sg = sg;
    this.sb = sb;
  },

  move: function() {
    var r = Mover.update(this.r, this.sr, 256);
    var g = Mover.update(this.g, this.sg, 256);
    var b = Mover.update(this.b, this.sb, 256);

    this.r = r[0]; this.sr = r[1];
    this.g = g[0]; this.sg = g[1];
    this.b = b[0]; this.sb = b[1];
  },

  to_s: function() {
    var r = Math.floor(this.r);
    var g = Math.floor(this.g);
    var b = Math.floor(this.b);
    return "rgb(" + r + "," + g + "," + b + ")";
  }
}

//------- Point Class -------
var Point = function(x, y, dx, dy) {
  this.x  = (x  === undefined ? 0 : x);
  this.y  = (y  === undefined ? 0 : y);
  this.dx = (dx === undefined ? 0 : dx);
  this.dy = (dy === undefined ? 0 : dy);
};

Point.SPEED_BASE    = 10;
Point.SPEED_VARIANT = 10;

Point.prototype = {
  nextPosition: function() {
    var x = Mover.update(this.x, this.dx, Lineart.screenWidth);
    var y = Mover.update(this.y, this.dy, Lineart.screenHeight);

    return new Point(x[0], y[0], x[1], y[1]);
  },

  setX: function(val) {
    if(val >= 0 && val <= Lineart.screenWidth) {
      this.x = val;
    }
    else {
      var msg = "invalid x value : " + val + ". It should be between 0 and " + Lineart.screenWidth;
      throw new Error(msg);
    }
  },

  setY: function(val) {
    if(val >= 0 && val <= Lineart.screenHeight) {
      this.y = val;
    }
    else {
      var msg = "invalid y value : " + val + ". It should be between 0 and " + Lineart.screenHeight;
      throw new Error(msg);
    }
  }
}

//------- Polygon Class -------
var Polygon = function(points, color) {
  if(color === undefined) {
    this.color = Generator.generateColor();
  }
  else {
    this.color = color;
  }

  if(points === undefined) {
    this.points = [];
    for(var i = 0; i < Lineart.POINTS_NUM; i++) {
      this.points.push(new Point());
    }
  }
  else {
    this.points = points;
  }
};

Polygon.prototype = {
  move: function() {
    this.points = this.points.map(function(item){
      return item.nextPosition();
    });

    this.color.move();
  }
}

//------- Mover Class -------
var Mover = function() {};
Mover.update = function(pos, diff, border) {
  pos += diff

  // when bounce at upper side
  if(pos > border) {
    pos = (border * 2) - pos;
    diff *= -1;
  }

  // when bounce at lower side
  if(pos < 0) {
    pos *= -1;
    diff *= -1;
  }

  return [pos, diff];
}

//------- Generator Class -------
var Generator = function() {};

Generator.generatePolygons = function(points, num) {
  var polygons = [];
  var color = Generator.generateColor();
  for(var i = 0; i < num; i++) {
    polygons.push(new Polygon(points, color));

    points = points.map(function(item) {
      return item.nextPosition();
    });
    color = Generator.generateNextColor(color);
  }
  return polygons;
}

Generator.generatePoints = function(xrange, yrange, num) {
  var points = [];
  for(var i = 0; i < num; i++) {
    points.push(
      new Point(
        Random.generate(xrange),
        Random.generate(yrange),
        Random.generate(Point.SPEED_VARIANT) + Point.SPEED_BASE,
        Random.generate(Point.SPEED_VARIANT) + Point.SPEED_BASE
      )
    );
  }

  return points;
}

Generator.generateColor = function() {
  var c = new Color(
    Random.generate(256),
    Random.generate(256),
    Random.generate(256));

  c.setSpeeds(
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE,
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE,
    Random.generate(Color.SPEED_VARIANT) + Color.SPEED_BASE);

  return c;
}

Generator.generateNextColor = function(color) {
  var newColor = color.clone();
  newColor.move();
  return newColor;
}

//------- Lineart Class -------
var Lineart = function(polygons) {
  var points = Generator.generatePoints(Lineart.SCREEN_WIDTH, Lineart.SCREEN_HEIGHT, Lineart.POINTS_NUM);
  this.polygons = Generator.generatePolygons(points, Lineart.POLYGON_NUM);
};

Lineart.POLYGON_NUM = 10;
Lineart.SCREEN_WIDTH = 320;
Lineart.SCREEN_HEIGHT = 240;
Lineart.POINTS_NUM = 4;

Lineart.prototype = {
  move: function() {
    for(var i = 0; i < this.polygons.length; i++) {
      this.polygons[i].move();
    }
  }
}

Lineart.reset = function() {
  this.screenWidth = Lineart.SCREEN_WIDTH;
  this.screenHeight = Lineart.SCREEN_HEIGHT;
  this.initialized = false;
}

Lineart.initScreenSize = function(width, height) {
  if(!this.initialized) {
    this.screenWidth  = width;
    this.screenHeight = height;
    this.initialized = true;
  }
  else {
      throw new UserException("it doesn't support changing screen size yet");
  }
}

//------- Logics -------
var lineart;
function init() {
  canvas = document.getElementById('sample1');
  if (canvas.getContext) {
    ctx = canvas.getContext('2d');
    ctx.canvas.width = window.innerWidth;
    ctx.canvas.height = window.innerHeight;

    Lineart.initScreenSize(
      window.innerWidth,
      window.innerHeight
    );
  }
  else {
    alert("failed to initialize canvas");
  }

  // initialize defaults points
  lineart = new Lineart();
  repeat_drawing();
}

function draw() {
  var polygons = lineart.polygons;
  for(var i = 0; i < polygons.length; i++) {
    var polygon = polygons[i];
    var color   = polygon.color.to_s();
    var points  = polygon.points;

    for(var j = 0; j < points.length; j++) {
      ctx.strokeStyle = color;
      ctx.beginPath();
      ctx.moveTo(points[j].x, points[j].y);
      ctx.lineTo(points[(j + 1) % Lineart.POINTS_NUM].x, points[(j + 1) % Lineart.POINTS_NUM].y);
      ctx.stroke();
    }
  }
}

function repeat_drawing() {
  ctx.clearRect(0, 0, Lineart.screenWidth, Lineart.screenHeight);
  draw();
  lineart.move();
  setTimeout(repeat_drawing, 50);
}

Jasime

var TEST_RANDOM_VALUES = 5;

describe("Lineart", function() {
  describe("Polygons", function() {
    describe("default behavior", function() {
      it("generates predefined number of polygon by default", function () {
        var la = new Lineart();
        expect(la.polygons.length).toEqual(Lineart.POLYGON_NUM);
      });

      it("sets screen width and height", function() {
        Lineart.initScreenSize(1280, 768);

        expect(Lineart.screenWidth).toEqual(1280);
        expect(Lineart.screenHeight).toEqual(768);
      });
    });

    describe("drawing", function() {
      beforeEach(function() {
        spyOn(Random, 'generate').andReturn(TEST_RANDOM_VALUES);
      });

      it("prepares for drawing", function() {
        var la = new Lineart();
        la.move();
        expect(la.polygons.length).toEqual(Lineart.POLYGON_NUM);
      });

      it("moves points", function() {
        var la = new Lineart();
        var beforePoints = la.polygons[0].points;
        la.move();
        var afterPoints = la.polygons[0].points;

        expect(beforePoints.length).toEqual(Lineart.POINTS_NUM);
        for(i in beforePoints) {
          expect(afterPoints[i].x).toEqual(
            beforePoints[i].x + TEST_RANDOM_VALUES + Point.SPEED_BASE);
        }
      });
    });

    describe("polygon", function() {
      it("has a color", function() {
        var polygons = new Lineart().polygons;
        for(i in polygons) {
          expect(polygons[i].color).toBeDefined();
        }
      });

      it("comprises of predefined points", function(){
        var polygons = new Lineart().polygons;
        for(i in polygons) {
          expect(polygons[i].points.length).toEqual(Lineart.POINTS_NUM);
        }
      });
    });
  });
});

describe("Point", function() {
  beforeEach(function() {
    Lineart.reset();
  });

  it("has location (x, y) and speed (dx, dy)", function(){
    var p = new Point(100, 200, 3, 4);
    expect([p.x, p.y, p.dx, p.dy]).toEqual([100, 200, 3, 4]);
  });

  describe("nextPosition", function(){
    it("returns normal next position", function(){
      var p = new Point(100, 200, 3, 4);
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([103, 204, 3, 4]); // return next;
      expect([p.x, p.y, p.dx, p.dy]).toEqual([100, 200, 3, 4]); // keep original
    });

    it("returns bounced max-x position", function(){
      var p = new Point(Lineart.SCREEN_WIDTH - 7, 0, 10, 5)
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([Lineart.SCREEN_WIDTH - 3, 5, -10, 5]);
    });

    it("returns bounced min-x position", function(){
      var p = new Point(3, 0, -10, 5)
      var n = p.nextPosition();

      expect([n.x, n.y, n.dx, n.dy]).toEqual([7, 5, 10, 5]);
    });
  });

  describe("validations", function() {
    it("should raise error if x value is invalid", function() {
      var point = new Point();
      expect(function() {
        point.setX(-1);
      }).toThrow();

      expect(function() {
        point.setX(Lineart.SCREEN_WIDTH + 1);
      }).toThrow();
    });

    it("should raise error if y value is invalid", function() {
      var point = new Point();
      expect(function() {
        point.setY(-1);
      }).toThrow();

      expect(function() {
        point.setY(Lineart.SCREEN_HEIGHT + 1);
      }).toThrow();
    });
  });
});

describe("Generator", function() {
  beforeEach(function() {
    spyOn(Random, 'generate').andReturn(TEST_RANDOM_VALUES);
  });

  describe("polygon and point", function(){
    var points;
    var polygons;
    beforeEach(function() {
      points = Generator.generatePoints(Lineart.SCREEN_WIDTH, Lineart.SCREEN_HEIGHT, Lineart.POINTS_NUM);
      polygons = Generator.generatePolygons(points, Lineart.POLYGON_NUM);
    });

    it("generates point locations", function() {
      expect(points.length).toEqual(Lineart.POINTS_NUM);
      for(i in points) {
        expect(points[i].x).toEqual(TEST_RANDOM_VALUES);
        expect(points[i].y).toEqual(TEST_RANDOM_VALUES);
        expect(points[i].dx).toEqual(TEST_RANDOM_VALUES + Point.SPEED_BASE);
        expect(points[i].dy).toEqual(TEST_RANDOM_VALUES + Point.SPEED_BASE);
      }
    });

    it("genetates polygon from points", function() {
      expect(polygons.length).toEqual(Lineart.POLYGON_NUM);
    });
  });

  describe("color", function() {
    it("generates random color", function() {
      var c = Generator.generateColor();
      expect(c.rgb()).toEqual([TEST_RANDOM_VALUES, TEST_RANDOM_VALUES, TEST_RANDOM_VALUES]);
    });

    it("generates next color", function() {
      var c1 = Generator.generateColor();
      var c2 = Generator.generateNextColor(c1);

      expect([c2.r, c2.g, c2.b]).toEqual([c1.r, c1.g, c1.b].map(function(item) {
        return item + TEST_RANDOM_VALUES + Color.SPEED_BASE;
      }));
    });

    it("has a change speed", function() {
      var c = Generator.generateColor();
      var val = TEST_RANDOM_VALUES + Color.SPEED_BASE;

      expect(c.speeds()).toEqual([val, val, val]);
    });
  })
});

describe("Color", function() {
  var c;
  beforeEach(function() {
    c = new Color(0, 100, 200);
  });

  it("has RGB value", function() {
    expect([c.r, c.g, c.b]).toEqual([0, 100, 200]);
  });

  it("can set speeds", function() {
    c.setSpeeds(1, 2, 3);

    expect([c.sr, c.sg, c.sb]).toEqual([1, 2, 3]);
  });

  it("transits based on its speed", function() {
    c.setSpeeds(1, 2, 3)
    c.move();

    expect(c.rgb()).toEqual([1, 102, 203]);
  });

  it("converts to string", function() {
    expect(c.to_s()).toEqual('rgb(0,100,200)');
  });
});

describe("Mover", function() {
  it("updates without bounce", function() {
    var ret = Mover.update(0, 3, 100);
    expect(ret).toEqual([3, 3]);
  });

  it("bounce at upper side", function() {
    var ret = Mover.update(99, 5, 100);
    expect(ret).toEqual([96, -5]);
  });

  it("bounce at lower side", function() {
    var ret = Mover.update(1, -5, 100);
    expect(ret).toEqual([4, 5]);
  });
});
Advertisements

Posted on March 27, 2013, in JavaScript. Bookmark the permalink. 1 Comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: