I have written Minesweeper in JavaScript. Using OO-Approach.
Cell
class describes a cell in the grid.
Cell.t
property tells whether the cell is a mine or not.
Cell.count
property stores the number of mines in the cell's vicinity.
Cell.state
property stores whether the cell is used / unused / flagged.
Grid
class provides an abstraction from the game's flow.
My main concerns are Grid.init
and Grid.click
functions.
Grid.init
assigns the number of mines in a cell's vicinity to Cell.count
.
Grid.click
recursively clears out the neighbor cells (without mines) when a cell is clicked.
The conditions in Grid.init
and Grid.click
seem quite redundant. Is there any way to fix that? Is there any other change that I should make to improve the code.
function Cell(type) {
if (!(this instanceof Cell)) {
throw new Error("Invalid call to constructor");
}
this.t = type;
this.count = 0;
this.state = 1;
}
function Grid(length, width, mines) {
var i, j, rc, rr;
if (!(this instanceof Grid)) {
throw new Error("Invalid call to constructor");
}
this.l = length;
this.w = width;
this.m = mines;
this.grid = [];
for (i = 0; i < this.l; i += 1) {
this.grid.push([]);
for (j = 0; j < this.w; j += 1) {
this.grid[i].push(new Cell("n"));
}
}
for (i = 0; i < this.m; i += 1) {
rr = parseInt(Math.random() * this.l);
rc = parseInt(Math.random() * this.w);
if (this.grid[rr][rc].t === "n") {
this.grid[rr][rc].t = "m";
} else {
i -= 1;
}
}
}
Grid.prototype.init = function() {
var i, j, n;
for (i = 0; i < this.l; i += 1) {
for (j = 0; j < this.w; j += 1) {
n = 0;
if (i - 1 >= 0 && j - 1 >= 0 && this.grid[i - 1][j - 1].t === "m") {
n += 1;
}
if (j - 1 >= 0 && this.grid[i][j - 1].t === "m") {
n += 1;
}
if (i + 1 < this.l && j - 1 >= 0 && this.grid[i + 1][j - 1].t === "m") {
n += 1;
}
if (i - 1 >= 0 && this.grid[i - 1][j].t === "m") {
n += 1;
}
if (i + 1 < this.l && this.grid[i + 1][j].t === "m") {
n += 1;
}
if (i - 1 >= 0 && j + 1 < this.w && this.grid[i - 1][j + 1].t === "m") {
n += 1;
}
if (j + 1 < this.w && this.grid[i][j + 1].t === "m") {
n += 1;
}
if (i + 1 < this.l && j + 1 < this.w && this.grid[i + 1][j + 1].t === "m") {
n += 1;
}
this.grid[i][j].count = n;
}
}
};
Grid.prototype.click = function(i, j) {
this.grid[i][j].state = 0;
if (this.grid[i][j].t === "m") {
return 0;
}
if (this.grid[i][j].count === 0) {
if (i - 1 >= 0 && j - 1 >= 0 && this.grid[i - 1][j - 1].state === 1) {
this.click(i - 1, j - 1);
}
if (j - 1 >= 0 && this.grid[i][j - 1].state === 1) {
this.click(i, j - 1);
}
if (i + 1 < this.l && j - 1 >= 0 && this.grid[i + 1][j - 1].state === 1) {
this.click(i + 1, j - 1);
}
if (i - 1 >= 0 && this.grid[i - 1][j].state === 1) {
this.click(i - 1, j);
}
if (i + 1 < this.l && this.grid[i + 1][j].state === 1) {
this.click(i + 1, j);
}
if (i - 1 >= 0 && j + 1 < this.w && this.grid[i - 1][j + 1].state === 1) {
this.click(i - 1, j + 1);
}
if (j + 1 < this.w && this.grid[i][j + 1].state === 1) {
this.click(i, j + 1);
}
if (i + 1 < this.l && j + 1 < this.w && this.grid[i + 1][j + 1].state === 1) {
this.click(i + 1, j + 1);
}
}
return this.check();
};
Grid.prototype.check = function() {
var i, j;
for (i = 0; i < this.l; i += 1) {
for (j = 0; j < this.w; j += 1) {
if (this.grid[i][j].t === "n" && this.grid[i][j].state) {
return 1;
}
}
}
return 2;
};
Note: I haven't provided the code which links these functions as event handlers to forms controls.
1 Answer 1
I think your algorithms are OK. But there is room for improvement:
Naming
One letter variable names are seldom useful. If you have to explain what Cell.t
means, it isn't clear enough, it should be Cell.type
. The same goes for Grid.l, Grid.w and Grid.m
.
Also, the limited values for the variables should also be meaningful:
- I'm guessing "n" is "nothing" Or you could pass and "m" is "mine" but is a lot better to spell it out.
- The same goes for the states
- And I still don't understand what the
2
returned bycheck()
means.
JavaScript doesn't have enums, you can use constants, but a full string (like "mine" instead of "m") should improve the code a lot (it still will be difficult to change in the future).
Setting the mines
The current algorithm has an infinite worst case: if the cell randomly chosen keeps hitting a mine, it wouldn't end.
I think a good alternative could be to generate an array of length * height
with as many true
s (or another meaningful value) as mines
, shuffle that array, and then use that as the grid.
I don't know if you can shuffle a bidimensional array, but shuffling a one-dimension array and then converting to 2-dimension should be easy.
Neighbors
In order to simplify the init()
and click()
functions, you could make a neighbors()
function in Grid
and then iterate over these.
Something like this:
Grid.prototype.neighbors = function(i, j) {
neighbors = []
if (i - 1 >= 0 && j -1 >= 0) {
neighbors.push(this.grid[i - 1][j - 1]);
}
...
return neighbors;
}
And then, in init()
:
n = 0;
for (neighbor in this.neighbors(i, j)) {
if (neighbor.type == "mine") {
n++;
}
}
this.grid[i][j].count = n;
And in click()
:
for (neighbor in this.neighbors(i, j)) {
if (neighbor.state === 1) {
this.click(neighbor.x, neighbor.y);
}
}
-
\$\begingroup\$
Grid.click()
returns 0 if the user has clicked on a mine, otherwise, it returnsGrid.check()
which returns 1 if user has not clicked on all the non-mine cells or 2 if user has clicked on the non-mine cells (has won the game). \$\endgroup\$ShuklaSannidhya– ShuklaSannidhya2015年06月17日 07:59:27 +00:00Commented Jun 17, 2015 at 7:59 -
\$\begingroup\$ How do I implement
Grid.click()
usingGrid.neighbors()
(the one you mentioned)? Since,Grid.click()
needs to be called with the index of the cell in the grid, the cell needs to be aware of its index. I have implemented it here. Note that, now theCell
class has stores the index inCell.x
andCell.y
. \$\endgroup\$ShuklaSannidhya– ShuklaSannidhya2015年06月17日 08:07:09 +00:00Commented Jun 17, 2015 at 8:07 -
\$\begingroup\$ @ShuklaSannidhya I've added an example for
click()
. \$\endgroup\$Pablo– Pablo2015年06月17日 15:34:00 +00:00Commented Jun 17, 2015 at 15:34
Explore related questions
See similar questions with these tags.