211 lines
4.6 KiB
JavaScript
211 lines
4.6 KiB
JavaScript
import { ref } from 'vue';
|
|
|
|
const directions = [
|
|
[1, 1],
|
|
[1, 0],
|
|
[1, -1],
|
|
[0, -1],
|
|
[-1, -1],
|
|
[-1, 0],
|
|
[-1, 1],
|
|
[0, 1],
|
|
];
|
|
|
|
export class GamePlay {
|
|
state = ref();
|
|
|
|
constructor(width, height, mines) {
|
|
this.width = width;
|
|
this.height = height;
|
|
this.mines = mines;
|
|
this.reset();
|
|
}
|
|
|
|
get board() {
|
|
return this.state.value.board;
|
|
}
|
|
|
|
get blocks() {
|
|
return this.state.value.board.flat();
|
|
}
|
|
|
|
reset(width = this.width, height = this.height, mines = this.mines) {
|
|
this.width = width;
|
|
this.height = height;
|
|
this.mines = mines;
|
|
|
|
this.state.value = {
|
|
startMS: +Date.now(),
|
|
endMS: undefined, // 确保结束时间戳被重置
|
|
mineGenerated: false,
|
|
status: 'play',
|
|
board: Array.from({ length: this.height }, (_, y) =>
|
|
Array.from({ length: this.width }, (_, x) => ({
|
|
x,
|
|
y,
|
|
mine: false, // 初始化 mine 属性
|
|
flagged: false, // 初始化 flagged 属性
|
|
adjacentMines: 0,
|
|
revealed: false,
|
|
}),
|
|
),
|
|
),
|
|
};
|
|
}
|
|
|
|
randomRange(min, max) {
|
|
return Math.random() * (max - min) + min;
|
|
}
|
|
|
|
randomInt(min, max) {
|
|
return Math.round(this.randomRange(min, max));
|
|
}
|
|
|
|
generateMines(state, initial) {
|
|
const placeRandom = () => {
|
|
const x = this.randomInt(0, this.width - 1);
|
|
const y = this.randomInt(0, this.height - 1);
|
|
const block = state[y][x];
|
|
if (Math.abs(initial.x - block.x) <= 1 && Math.abs(initial.y - block.y) <= 1)
|
|
return false;
|
|
if (block.mine)
|
|
return false;
|
|
block.mine = true;
|
|
return true;
|
|
};
|
|
Array.from({ length: this.mines }, () => null)
|
|
.forEach(() => {
|
|
let placed = false;
|
|
let attempts = 0;
|
|
const maxAttempts = 1000;
|
|
while (!placed) {
|
|
if (attempts++ > maxAttempts) {
|
|
this.reset();
|
|
break;
|
|
}
|
|
placed = placeRandom();
|
|
}
|
|
});
|
|
this.updateNumbers();
|
|
}
|
|
|
|
updateNumbers() {
|
|
this.board.forEach((raw) => {
|
|
raw.forEach((block) => {
|
|
if (block.mine)
|
|
return;
|
|
this.getSiblings(block)
|
|
.forEach((b) => {
|
|
if (b.mine)
|
|
block.adjacentMines += 1;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
expendZero(block) {
|
|
if (block.adjacentMines)
|
|
return;
|
|
|
|
this.getSiblings(block)
|
|
.forEach((s) => {
|
|
if (!s.revealed) {
|
|
s.revealed = true;
|
|
this.expendZero(s);
|
|
}
|
|
});
|
|
}
|
|
|
|
onRightClick(block) {
|
|
if (this.state.value.status !== 'play')
|
|
return;
|
|
|
|
if (block.revealed)
|
|
return;
|
|
block.flagged = !block.flagged;
|
|
}
|
|
|
|
onClick(block) {
|
|
if (this.state.value.status !== 'play')
|
|
return;
|
|
|
|
if (!this.state.value.mineGenerated) {
|
|
this.generateMines(this.board, block);
|
|
this.state.value.mineGenerated = true;
|
|
}
|
|
|
|
block.revealed = true;
|
|
if (block.mine) {
|
|
this.onGameOver('lost');
|
|
return;
|
|
}
|
|
|
|
this.expendZero(block);
|
|
}
|
|
|
|
getSiblings(block) {
|
|
return directions.map(([dx, dy]) => {
|
|
const x2 = block.x + dx;
|
|
const y2 = block.y + dy;
|
|
if (x2 < 0 || x2 >= this.width || y2 < 0 || y2 >= this.height)
|
|
return undefined;
|
|
return this.board[y2][x2];
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
showAllMines() {
|
|
this.board.flat().forEach((i) => {
|
|
if (i.mine)
|
|
i.revealed = true;
|
|
});
|
|
}
|
|
|
|
checkGameState() {
|
|
if (!this.state.value.mineGenerated)
|
|
return;
|
|
const blocks = this.board.flat();
|
|
|
|
if (blocks.every(block => block.revealed || block.flagged || block.mine)) {
|
|
if (blocks.some(block => block.flagged && !block.mine))
|
|
this.onGameOver('lost');
|
|
else
|
|
this.onGameOver('won');
|
|
}
|
|
}
|
|
|
|
autoExpand(block) {
|
|
const siblings = this.getSiblings(block);
|
|
const flags = siblings.reduce((a, b) => a + (b.flagged ? 1 : 0), 0);
|
|
const notRevealed = siblings.reduce((a, b) => a + (!b.revealed && !b.flagged ? 1 : 0), 0);
|
|
if (flags === block.adjacentMines) {
|
|
siblings.forEach((i) => {
|
|
if (i.revealed || i.flagged)
|
|
return;
|
|
i.revealed = true;
|
|
this.expendZero(i);
|
|
if (i.mine)
|
|
this.onGameOver('lost');
|
|
});
|
|
}
|
|
const missingFlags = block.adjacentMines - flags;
|
|
if (notRevealed === missingFlags) {
|
|
siblings.forEach((i) => {
|
|
if (!i.revealed && !i.flagged)
|
|
i.flagged = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
onGameOver(status) {
|
|
this.state.value.status = status;
|
|
this.state.value.endMS = +Date.now();
|
|
if (status === 'lost') {
|
|
this.showAllMines();
|
|
setTimeout(() => {
|
|
alert('lost');
|
|
}, 10);
|
|
}
|
|
}
|
|
}
|