145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
export class Game {
|
|
mines: boolean[][] = [];
|
|
minesCount: number = 0;
|
|
isRevealed: boolean[][] = [];
|
|
isFlagged: boolean[][] = [];
|
|
isGameOver: boolean = false;
|
|
startTime: number = Date.now();
|
|
|
|
constructor(width: number, height: number, mines: number) {
|
|
if (mines > width * height) {
|
|
throw new Error("Too many mines");
|
|
}
|
|
this.minesCount = mines;
|
|
for (let i = 0; i < width; i++) {
|
|
this.mines.push(new Array(height).fill(false));
|
|
this.isRevealed.push(new Array(height).fill(false));
|
|
this.isFlagged.push(new Array(height).fill(false));
|
|
}
|
|
while (mines > 0) {
|
|
const x = Math.floor(Math.random() * width);
|
|
const y = Math.floor(Math.random() * height);
|
|
if (!this.mines[x][y]) {
|
|
this.mines[x][y] = true;
|
|
mines--;
|
|
}
|
|
}
|
|
}
|
|
|
|
getWidth() {
|
|
return this.mines.length;
|
|
}
|
|
|
|
getHeight() {
|
|
return this.mines[0].length;
|
|
}
|
|
|
|
isMine(x: number, y: number) {
|
|
return this.mines[x][y];
|
|
}
|
|
|
|
flag(x: number, y: number) {
|
|
this.isFlagged[x][y] = !this.isFlagged[x][y];
|
|
}
|
|
|
|
isValid(x: number, y: number) {
|
|
return x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight();
|
|
}
|
|
|
|
reveal(x: number, y: number) {
|
|
if (!this.isValid(x, y)) return;
|
|
this.isRevealed[x][y] = true;
|
|
if (this.isMine(x, y)) {
|
|
this.isGameOver = true;
|
|
return;
|
|
}
|
|
const value = this.getValue(x, y);
|
|
if (value === 0) {
|
|
if (this.isValid(x - 1, y - 1) && !this.isRevealed[x - 1]?.[y - 1])
|
|
this.reveal(x - 1, y - 1);
|
|
if (this.isValid(x, y - 1) && !this.isRevealed[x]?.[y - 1])
|
|
this.reveal(x, y - 1);
|
|
if (this.isValid(x + 1, y - 1) && !this.isRevealed[x + 1]?.[y - 1])
|
|
this.reveal(x + 1, y - 1);
|
|
if (this.isValid(x - 1, y) && !this.isRevealed[x - 1]?.[y])
|
|
this.reveal(x - 1, y);
|
|
if (this.isValid(x + 1, y) && !this.isRevealed[x + 1]?.[y])
|
|
this.reveal(x + 1, y);
|
|
if (this.isValid(x - 1, y + 1) && !this.isRevealed[x - 1]?.[y + 1])
|
|
this.reveal(x - 1, y + 1);
|
|
if (this.isValid(x, y + 1) && !this.isRevealed[x]?.[y + 1])
|
|
this.reveal(x, y + 1);
|
|
if (this.isValid(x + 1, y + 1) && !this.isRevealed[x + 1]?.[y + 1])
|
|
this.reveal(x + 1, y + 1);
|
|
}
|
|
}
|
|
|
|
getHasWon() {
|
|
if (this.isGameOver) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < this.getWidth(); i++) {
|
|
for (let j = 0; j < this.getHeight(); j++) {
|
|
if (!this.isRevealed[i][j] && !this.isFlagged[i][j]) {
|
|
return false;
|
|
}
|
|
if (this.isMine(i, j) && !this.isFlagged[i][j]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
getMinesLeft() {
|
|
return this.minesCount - this.isFlagged.flat().filter((m) => m).length;
|
|
}
|
|
|
|
getNeighborFlags(x: number, y: number) {
|
|
const neighbors = [
|
|
this.isFlagged[x - 1]?.[y - 1],
|
|
this.isFlagged[x]?.[y - 1],
|
|
this.isFlagged[x + 1]?.[y - 1],
|
|
this.isFlagged[x - 1]?.[y],
|
|
this.isFlagged[x + 1]?.[y],
|
|
this.isFlagged[x - 1]?.[y + 1],
|
|
this.isFlagged[x]?.[y + 1],
|
|
this.isFlagged[x + 1]?.[y + 1],
|
|
];
|
|
return neighbors;
|
|
}
|
|
|
|
getNeighborMines(x: number, y: number) {
|
|
const neighbors = [
|
|
this.mines[x - 1]?.[y - 1],
|
|
this.mines[x]?.[y - 1],
|
|
this.mines[x + 1]?.[y - 1],
|
|
this.mines[x - 1]?.[y],
|
|
this.mines[x + 1]?.[y],
|
|
this.mines[x - 1]?.[y + 1],
|
|
this.mines[x]?.[y + 1],
|
|
this.mines[x + 1]?.[y + 1],
|
|
];
|
|
return neighbors;
|
|
}
|
|
|
|
getValue(x: number, y: number) {
|
|
const neighbors = this.getNeighborMines(x, y);
|
|
const mines = neighbors.filter((n) => n).length;
|
|
return mines;
|
|
}
|
|
|
|
quickStart() {
|
|
for (let i = 0; i < this.getWidth(); i++) {
|
|
for (let j = 0; j < this.getHeight(); j++) {
|
|
const value = this.getValue(i, j);
|
|
const isMine = this.isMine(i, j);
|
|
if (value === 0 && !isMine) {
|
|
this.reveal(i, j);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|