Skip to content

Commit

Permalink
Merge pull request #7 from petzku/master
Browse files Browse the repository at this point in the history
Allow always starting from zero cell if possible
  • Loading branch information
jozsefsallai committed Aug 2, 2020
2 parents c9bb450 + e102874 commit 57d82d1
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 21 deletions.
63 changes: 57 additions & 6 deletions src/Minesweeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @property {number} [mines] - The number of mines in the mine field. Defaults to 10.
* @property {string} [emote] - The emote used as a mine. Defaults to "boom".
* @property {boolean} [revealFirstCell] - Whether or not the first cell should be revealed (like in regular Minesweeper). Defaults to FALSE.
* @property {boolean} [zeroFirstCell] - Whether or not the first cell revealed should always be a zero (and automatically reveal any surrounding safe cells). Does nothing if `revealFirstCell` is false. Defaults to true.
* @property {boolean} [spaces] - Specifies whether or not the emojis should be surrounded by spaces. Defaults to true.
* @property {'emoji' | 'code' | 'matrix'} - The type of the returned data. Defaults to "emoji".
*/
Expand All @@ -15,6 +16,7 @@ interface MinesweeperOpts {
mines?: number;
emote?: string;
revealFirstCell?: boolean;
zeroFirstCell?: boolean;
spaces?: boolean;
returnType?: 'emoji' | 'code' | 'matrix';
}
Expand Down Expand Up @@ -48,6 +50,7 @@ class Minesweeper {
public readonly emote: string;
public readonly spaces: boolean;
public readonly revealFirstCell: boolean;
public readonly zeroFirstCell: boolean;
public readonly safeCells: SafeCell[] = [];
public readonly returnType: 'emoji' | 'code' | 'matrix';
public readonly types: CellTypes;
Expand All @@ -64,6 +67,7 @@ class Minesweeper {
this.mines = (opts && opts.mines) || 10;
this.emote = (opts && opts.emote) || 'boom';
this.revealFirstCell = opts && opts.revealFirstCell !== undefined ? opts.revealFirstCell : false;
this.zeroFirstCell = opts && opts.zeroFirstCell !== undefined ? opts.zeroFirstCell : true;
this.spaces = opts && opts.spaces !== undefined ? opts.spaces : true;
this.returnType = (opts && opts.returnType) || 'emoji';

Expand Down Expand Up @@ -191,16 +195,63 @@ class Minesweeper {
return { x: -1, y: -1 };
}

const safeCell: SafeCell = this.safeCells[Math.floor(Math.random() * this.safeCells.length)];
const zeroCells = this.safeCells.filter(c => this.matrix[c.x][c.y] === this.types.numbers[0]);
if (this.zeroFirstCell && zeroCells.length > 0) {
const safeCell: SafeCell = zeroCells[Math.floor(Math.random() * zeroCells.length)];

const x: number = safeCell.x;
const y: number = safeCell.y;
const x: number = safeCell.x;
const y: number = safeCell.y;

const cell = this.matrix[x][y];
const cell = this.matrix[x][y];

this.matrix[x][y] = cell.slice(2, -2);
this.matrix[x][y] = cell.slice(2, -2);
this.revealSurroundings(safeCell);

return { x, y };
return { x, y };
} else {
const safeCell: SafeCell = this.safeCells[Math.floor(Math.random() * this.safeCells.length)];

const x: number = safeCell.x;
const y: number = safeCell.y;

const cell = this.matrix[x][y];

this.matrix[x][y] = cell.slice(2, -2);

return { x, y };
}
}

/**
* Reveals all cells surrounding a cell. Only meant to be used for zero-cells during initial construction.
* @param {SafeCell} c - A SafeCell to reveal around. This should only be a zero-cell!
* @param {boolean} recurse - Whether to recursively reveal following zero-cells. Defaults to true.
*/
revealSurroundings(c: SafeCell, recurse: boolean = true) {
const isSpoiler = (x: number, y: number) => this.matrix[x][y].includes("||");
const x = c.x;
const y = c.y;

const xLower = Math.max(0, x - 1);
const yLower = Math.max(0, y - 1);
const xUpper = Math.min(this.rows - 1, x + 1);
const yUpper = Math.min(this.columns - 1, y + 1);
let zeroCells: SafeCell[] = [];

for (let i = xLower; i <= xUpper; i++) {
for (let j = yLower; j <= yUpper; j++) {
if (isSpoiler(i, j)) {
if (this.matrix[i][j] === this.types.numbers[0]) {
zeroCells.push({ x: i, y: j });
}
this.matrix[i][j] = this.matrix[i][j].slice(2, -2);
}
}
}

if (recurse) {
zeroCells.forEach(c => this.revealSurroundings(c, true));
}
}

/**
Expand Down
213 changes: 198 additions & 15 deletions test/Minesweeper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('Minesweeper', function () {
expect(minesweeper.emote).to.eql('boom');
expect(minesweeper.spaces).to.eql(true);
expect(minesweeper.revealFirstCell).to.eql(false);
expect(minesweeper.zeroFirstCell).to.eql(true);
expect(minesweeper.returnType).to.eql('emoji');
expect(minesweeper.types.mine).to.eql('|| :boom: ||');
expect(minesweeper.types.numbers[1]).to.eql('|| :one: ||');
Expand Down Expand Up @@ -143,6 +144,20 @@ describe('Minesweeper', function () {
});

describe('#revealFirst', function () {
function countRevealedCells(matrix: string[][]): number {
let counter: number = 0;

matrix.forEach(row => {
row.forEach(column => {
if (!column.includes('||')) {
counter++;
}
});
});

return counter;
}

describe('when revealFirstCell is false', function () {
it('should return null', function () {
const minesweeper = new Minesweeper({ rows: 2, columns: 2, mines: 1 });
Expand All @@ -154,25 +169,193 @@ describe('Minesweeper', function () {
});

describe('when revealFirstCell is true', function () {
it('should change a random field', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 6,
revealFirstCell: true,
spaces: false
describe('when zeroFirstCell is false', function() {
it('should change a random field', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 6,
revealFirstCell: true,
zeroFirstCell: false,
spaces: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

const revealed = minesweeper.revealFirst();
const x: number = revealed.x;
const y: number = revealed.y;

const target: string = minesweeper.matrix[x][y];

return expect(target.startsWith('||')).to.be.false;
});
});

describe('when zeroFirstCell is true', function() {
it('should change a zero field when one exists', function () {
const minesweeper = new Minesweeper({
rows: 4,
columns: 4,
mines: 2,
revealFirstCell: true,
zeroFirstCell: true,
spaces: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

const revealed = minesweeper.revealFirst();
const x: number = revealed.x;
const y: number = revealed.y;

const target: string = minesweeper.matrix[x][y];

expect(target.startsWith('||')).to.be.false;
expect(target).to.eql(':zero:');
});

it('should reveal multiple fields when a zero field exists', function () {
const minesweeper = new Minesweeper({
rows: 4,
columns: 4,
mines: 2,
revealFirstCell: true,
zeroFirstCell: true,
spaces: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

minesweeper.revealFirst();
const revealed: number = countRevealedCells(minesweeper.matrix);

expect(revealed).to.be.greaterThan(1);
});

it('should only change one field when no zero fields exist', function () {
const minesweeper = new Minesweeper({
rows: 2,
columns: 2,
mines: 1,
revealFirstCell: true,
zeroFirstCell: true,
spaces: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

minesweeper.revealFirst();
const revealed: number = countRevealedCells(minesweeper.matrix);

expect(revealed).to.eql(1);
});
});

});
});

describe('#revealSurroundings', function () {
function countHiddenCells(matrix: string[][]): number {
let counter: number = 0;

matrix.forEach(row => {
row.forEach(column => {
if (column.includes('||')) {
counter++;
}
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();
});

return counter;
}

const revealed = minesweeper.revealFirst();
const x: number = revealed.x;
const y: number = revealed.y;
describe('when there are no mines', function() {
describe('when recurse is false', function() {
it('should result in nine revealed cells', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 0,
revealFirstCell: false
});
minesweeper.generateEmptyMatrix();

const target: string = minesweeper.matrix[x][y];
minesweeper.revealSurroundings({x: 2, y: 2}, false);
const totalCells: number = minesweeper.rows * minesweeper.columns;
const hidden: number = countHiddenCells(minesweeper.matrix);

return expect(target.startsWith('||')).to.be.false;
expect(totalCells - hidden).to.eql(9);
});
});

describe('when recurse is true', function() {
it('should result in no hidden cells ', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 0,
revealFirstCell: false
});
minesweeper.generateEmptyMatrix();

minesweeper.revealSurroundings({x: 2, y: 2}, true);
const hidden: number = countHiddenCells(minesweeper.matrix);

expect(hidden).to.eql(0);
});
});
});

describe('when there are mines', function() {
describe('when recurse is true', function() {
function isZeroCell(cell: {x: number, y: number}, minesweeper: Minesweeper): boolean {
if (minesweeper.matrix[cell.x][cell.y] == minesweeper.types.numbers[0]) {
return true;
}
return false;
}

it('should reveal multiple cells', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 1,
revealFirstCell: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

const zeroCell = minesweeper.safeCells.filter(c => isZeroCell(c, minesweeper))[0]
minesweeper.revealSurroundings(zeroCell, true);
const totalCells: number = minesweeper.rows * minesweeper.columns;
const hidden: number = countHiddenCells(minesweeper.matrix);

expect(hidden).to.be.lessThan(totalCells);
})

it('should leave some cells hidden', function () {
const minesweeper = new Minesweeper({
rows: 5,
columns: 5,
mines: 1,
revealFirstCell: false
});
minesweeper.generateEmptyMatrix();
minesweeper.plantMines();
minesweeper.populate();

const zeroCell = minesweeper.safeCells.filter(c => isZeroCell(c, minesweeper))[0]
minesweeper.revealSurroundings(zeroCell, true);
const hidden: number = countHiddenCells(minesweeper.matrix);

expect(hidden).to.be.greaterThan(0);
});
});
});
});
Expand Down

0 comments on commit 57d82d1

Please sign in to comment.