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

var 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) {
    push(this.cell(pos.x, pos.y), entity);
    entity.coord = array(pos);
  },

  // remove an entity from a cell
  remove(entity, pos) {
    this.cells[pos.y][pos.x] = filter(this.cells[pos.y][pos.x], x => x != entity)
  },

  // 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) {
    var list = null;
    var y = 0;
    var x = 0;
    for (y = 0; y < this.height; y++) {
      for (x = 0; x < this.width; x++) {
        list = this.cells[y][x]
        arrfor(list, function(entity) {
          fn(entity, entity.coord);
        })
      }
    }
  },

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

return grid
