minesweeper/src/Game.ts

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;
}
}
}
}
}