Skip to content

Instantly share code, notes, and snippets.

@InfiniteXyy
Created July 30, 2024 09:52
Show Gist options
  • Save InfiniteXyy/0a8c9b06e4b612546afffa896b5d955e to your computer and use it in GitHub Desktop.
Save InfiniteXyy/0a8c9b06e4b612546afffa896b5d955e to your computer and use it in GitHub Desktop.
minesweeper example
import { reactive, type Reactive } from '@vue/reactivity';
const width = 10;
const height = 10;
const numMines = 15;
export class Block {
constructor(public isVisible: boolean = false, public isMarked: boolean = false) {}
}
class MineBlock extends Block {
constructor(private unknown?: string) {
super();
}
}
class NumberBlock extends Block {
constructor(public adjacentMines: number = 0) {
super();
}
}
type Board = { value: (MineBlock | NumberBlock)[][] };
export const isMineBlock = (block?: MineBlock | NumberBlock): block is MineBlock => {
return block instanceof MineBlock;
};
export const isNumberBlock = (block?: MineBlock | NumberBlock): block is NumberBlock => {
return block instanceof NumberBlock;
};
export function createGame(callbacks?: { onGameLose?: () => void; onWin?: () => void }) {
const { onGameLose, onWin } = callbacks ?? {};
let board: Reactive<Board> = setupBoard();
let isFirstClick: boolean = true;
function getBlock(x: number, y: number) {
if (x < 0 || x >= width || y < 0 || y >= height) return;
return board.value[y][x];
}
function markBlock(x: number, y: number) {
const block = getBlock(x, y);
if (!block) return;
if (block.isVisible) return;
block.isMarked = !block.isMarked;
// if all mines are marked, win the game
let allMinesMarked = true;
for (const row of board.value) {
for (const block of row) {
if (isMineBlock(block) && !block.isMarked) {
allMinesMarked = false;
}
}
}
if (allMinesMarked) onWin?.();
}
function revealBlock(x: number, y: number) {
if (isFirstClick) {
// Ensure the first click is a number block
while (true) {
const firstClickedBlock = getBlock(x, y);
if (!isNumberBlock(firstClickedBlock) || firstClickedBlock.adjacentMines !== 0) {
board.value = setupBoard().value;
} else {
break;
}
}
isFirstClick = false;
}
const block = getBlock(x, y);
if (!block) {
return;
}
if (isMineBlock(block)) {
block.isVisible = true;
onGameLose?.();
return;
}
if (block.isVisible) {
return;
}
block.isVisible = true;
if (isNumberBlock(block)) {
if (block.adjacentMines === 0) {
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
revealBlock(x + dx, y + dy);
}
}
}
}
return block;
}
function testBlocks(x: number, y: number) {
const block = getBlock(x, y);
if (!block || !block.isVisible || !isNumberBlock(block)) {
return;
}
let isAllMinesMarked = true;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
const currentBlock = getBlock(x + dx, y + dy);
if (isMineBlock(currentBlock) && !currentBlock.isMarked) isAllMinesMarked = false;
}
}
if (isAllMinesMarked) {
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
const currentBlock = getBlock(x + dx, y + dy);
isNumberBlock(currentBlock) && revealBlock(x + dx, y + dy);
}
}
}
}
function printBoard(showAll = false) {
let tableResult = '';
for (const row of board.value) {
const rowResult = row.map((i) => {
if (!showAll && !i.isVisible) {
return '_';
}
if (isNumberBlock(i)) {
return i.adjacentMines;
}
return '*';
});
tableResult += rowResult.join(' ') + '\n';
}
console.clear();
console.log(tableResult);
}
function setupBoard(): Reactive<Board> {
const result: Board['value'] = [];
(function initBoard() {
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) row.push(new NumberBlock());
result.push(row);
}
})();
(function placeMines() {
let minesPlaced = 0;
while (minesPlaced < numMines) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
if (!isMineBlock(result[y][x])) {
result[y][x] = new MineBlock();
minesPlaced++;
}
}
})();
(function calculateNumbers() {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (isMineBlock(result[y][x])) {
continue;
}
let count = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
const ny = y + dy;
const nx = x + dx;
if (ny < 0 || ny >= height || nx < 0 || nx >= width) continue;
if (isMineBlock(result[ny][nx])) count++;
}
}
result[y][x] = new NumberBlock(count);
}
}
})();
return reactive({ value: result });
}
return { board, revealBlock, markBlock, testBlocks, printBoard };
}
export const game = createGame({});
game.revealBlock(3, 4);
game.printBoard();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment