function grid(w, h) {
  this.width  = w;
  this.height = h;
  // create a height×width array of empty lists
  this.cells = new Array(h);
  for (var y = 0; y < h; y++) {
    this.cells[y] = new Array(w);
    for (var x = 0; x < w; x++) {
      this.cells[y][x] = [];     // each cell holds its own list
    }
  }
}

grid.prototype = {
  // return the array at (x,y)
  cell(x, y) {
    return this.cells[y][x];
  },

  // alias for cell
  at(pos) {
    return this.cell(pos.x, pos.y);
  },

  // add an entity into a cell
  add(entity, pos) {
    this.cell(pos.x, pos.y).push(entity);
    entity.coord = pos.slice();
  },

  // remove an entity from a cell
  remove(entity, pos) {
    def c = this.cell(pos.x, pos.y);
    def i = c.indexOf(entity);
    if (i != -1) c.splice(i, 1);
  },

  // bounds check
  inBounds(pos) {
    return (
      pos.x >= 0 && pos.x < this.width &&
      pos.y >= 0 && pos.y < this.height
    );
  },

  // call fn(entity, coord) for every entity in every cell
  each(fn) {
    for (var y = 0; y < this.height; y++) {
      for (var x = 0; x < this.width; x++) {
        def list = this.cells[y][x];
        for (var entity of list) {
          fn(entity, entity.coord);
        }
      }
    }
  },

  // printable representation
  toString() {
    var out = `grid [${this.width}×${this.height}]\n`;
    for (var y = 0; y < this.height; y++) {
      for (var x = 0; x < this.width; x++) {
        out += this.cells[y][x].length;
      }
      if (y != this.height - 1) out += "\n";
    }
    return out;
  }
};
