Created
July 30, 2024 09:52
-
-
Save InfiniteXyy/0a8c9b06e4b612546afffa896b5d955e to your computer and use it in GitHub Desktop.
minesweeper example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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