forked from moyin/fzu-product
feat: 加了扫雷小游戏
This commit is contained in:
65
.vitepress/components/Confetti.vue
Normal file
65
.vitepress/components/Confetti.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup>
|
||||||
|
import confetti from 'canvas-confetti'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps(
|
||||||
|
{
|
||||||
|
passed: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
['passed'],
|
||||||
|
)
|
||||||
|
|
||||||
|
function congrats() {
|
||||||
|
const defaults = {
|
||||||
|
colors: [
|
||||||
|
'#5D8C7B',
|
||||||
|
'#F2D091',
|
||||||
|
'#F2A679',
|
||||||
|
'#D9695F',
|
||||||
|
'#8C4646',
|
||||||
|
],
|
||||||
|
shapes: ['square'],
|
||||||
|
ticks: 500,
|
||||||
|
}
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount: 80,
|
||||||
|
spread: 100,
|
||||||
|
origin: { y: 0 },
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount: 50,
|
||||||
|
angle: 60,
|
||||||
|
spread: 80,
|
||||||
|
origin: { x: 0 },
|
||||||
|
})
|
||||||
|
}, 250)
|
||||||
|
setTimeout(() => {
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount: 50,
|
||||||
|
angle: 120,
|
||||||
|
spread: 80,
|
||||||
|
origin: { x: 1 },
|
||||||
|
})
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.passed,
|
||||||
|
(v) => {
|
||||||
|
if (v)
|
||||||
|
setTimeout(congrats, 300)
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div />
|
||||||
|
</template>
|
||||||
111
.vitepress/components/CustomNotFound.vue
Normal file
111
.vitepress/components/CustomNotFound.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { withBase } from 'vitepress'
|
||||||
|
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||||
|
import { useLangs } from 'vitepress/dist/client/theme-default/composables/langs'
|
||||||
|
import Sweep from './sweep.vue';
|
||||||
|
|
||||||
|
const { site, theme } = useData()
|
||||||
|
const { localeLinks } = useLangs({ removeCurrent: false })
|
||||||
|
|
||||||
|
const root = ref('/')
|
||||||
|
onMounted(() => {
|
||||||
|
const path = window.location.pathname
|
||||||
|
.replace(site.value.base, '')
|
||||||
|
.replace(/(^.*?\/).*$/, '/$1')
|
||||||
|
if (localeLinks.value.length) {
|
||||||
|
root.value =
|
||||||
|
localeLinks.value.find(({ link }) => link.startsWith(path))?.link ||
|
||||||
|
localeLinks.value[0].link
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="NotFound">
|
||||||
|
<p class="code">{{ theme.notFound?.code ?? '404' }}</p>
|
||||||
|
<h1 class="title">{{ theme.notFound?.title ?? 'PAGE NOT FOUND' }}</h1>
|
||||||
|
<div class="divider" />
|
||||||
|
<blockquote class="quote">
|
||||||
|
{{
|
||||||
|
theme.notFound?.quote ??
|
||||||
|
"这是一个扫雷小游戏"
|
||||||
|
}}
|
||||||
|
</blockquote>
|
||||||
|
<Sweep />
|
||||||
|
|
||||||
|
<div class="action">
|
||||||
|
<a
|
||||||
|
class="link"
|
||||||
|
:href="withBase(root)"
|
||||||
|
:aria-label="theme.notFound?.linkLabel ?? 'go to home'"
|
||||||
|
>
|
||||||
|
{{ theme.notFound?.linkText ?? 'Take me home' }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.NotFound {
|
||||||
|
padding: 64px 24px 96px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.NotFound {
|
||||||
|
padding: 96px 32px 168px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
line-height: 64px;
|
||||||
|
font-size: 64px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding-top: 12px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin: 24px auto 18px;
|
||||||
|
width: 64px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 256px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid var(--vp-c-brand);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 3px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
transition:
|
||||||
|
border-color 0.25s,
|
||||||
|
color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
border-color: var(--vp-c-brand-dark);
|
||||||
|
color: var(--vp-c-brand-dark);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
72
.vitepress/components/MineBlock.vue
Normal file
72
.vitepress/components/MineBlock.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps(
|
||||||
|
{
|
||||||
|
block: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
['block']
|
||||||
|
)
|
||||||
|
|
||||||
|
const numberColors = [
|
||||||
|
'text-transparent',
|
||||||
|
'text-blue-500',
|
||||||
|
'text-green-500',
|
||||||
|
'text-yellow-500',
|
||||||
|
'text-orange-500',
|
||||||
|
'text-red-500',
|
||||||
|
'text-purple-500',
|
||||||
|
'text-pink-500',
|
||||||
|
'text-teal-500',
|
||||||
|
]
|
||||||
|
|
||||||
|
function getBlockClass(block) {
|
||||||
|
if (block.flagged)
|
||||||
|
return 'bg-gray-500-10'
|
||||||
|
if (!block.revealed)
|
||||||
|
return 'bg-gray-500-10 hover:bg-gray-500-20'
|
||||||
|
|
||||||
|
return block.mine
|
||||||
|
? 'bg-red-500-50'
|
||||||
|
: numberColors[block.adjacentMines]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.text-transparent { color: transparent; }
|
||||||
|
.text-blue-500 { color: #3b82f6; }
|
||||||
|
.text-green-500 { color: #10b981; }
|
||||||
|
.text-yellow-500 { color: #f59e0b; }
|
||||||
|
.text-orange-500 { color: #f97316; }
|
||||||
|
.text-red-500 { color: #ef4444; }
|
||||||
|
.text-red { color: rgba(248, 113, 113); }
|
||||||
|
.text-purple-500 { color: #8b5cf6; }
|
||||||
|
.text-pink-500 { color: #ec4899; }
|
||||||
|
.text-teal-500 { color: #14b8a6; }
|
||||||
|
.bg-gray-500-10 { background-color: rgba(107, 114, 128, 0.1); }
|
||||||
|
.bg-gray-500-20 { background-color: rgba(107, 114, 128, 0.2); }
|
||||||
|
.bg-red-500-50 { background-color: rgba(239, 68, 68, 0.5); }
|
||||||
|
.font-600 { font-weight: 600; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
style="display: flex; align-items: center; justify-content: center; min-width: 2rem; min-height: 2rem; margin: 1px; border: 0.5px solid rgba(166, 166, 166, 0.1);"
|
||||||
|
:class="getBlockClass(block)"
|
||||||
|
>
|
||||||
|
<template v-if="block.flagged">
|
||||||
|
<div class="text-red">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6Z"/></svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="block.revealed">
|
||||||
|
<div v-if="block.mine">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M23 13v-2h-3.07a7.988 7.988 0 0 0-1.62-3.9l2.19-2.17l-1.43-1.43l-2.17 2.19A7.988 7.988 0 0 0 13 4.07V1h-2v3.07c-1.42.18-2.77.74-3.9 1.62L4.93 3.5L3.5 4.93L5.69 7.1A7.988 7.988 0 0 0 4.07 11H1v2h3.07c.18 1.42.74 2.77 1.62 3.9L3.5 19.07l1.43 1.43l2.17-2.19c1.13.88 2.48 1.44 3.9 1.62V23h2v-3.07c1.42-.18 2.77-.74 3.9-1.62l2.17 2.19l1.43-1.43l-2.19-2.17a7.988 7.988 0 0 0 1.62-3.9H23M12 8a4 4 0 0 0-4 4H6a6 6 0 0 1 6-6v2Z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div v-else class="font-600">
|
||||||
|
{{ block.adjacentMines }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
156
.vitepress/components/sweep.vue
Normal file
156
.vitepress/components/sweep.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup>
|
||||||
|
import { GamePlay } from '../composables/logic.js'
|
||||||
|
import { useNow, useStorage } from '@vueuse/core'
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import MineBlock from './MineBlock.vue'
|
||||||
|
import Confetti from './Confetti.vue'
|
||||||
|
const play = new GamePlay(6, 6, 3)
|
||||||
|
|
||||||
|
const now = $(useNow())
|
||||||
|
const timerMS = $computed(() => Math.round(((play.state.value.endMS || +now) - play.state.value.startMS) / 1000))
|
||||||
|
|
||||||
|
useStorage('vuesweeper-state', play.state)
|
||||||
|
const state = $computed(() => play.board)
|
||||||
|
|
||||||
|
const mineRest = $computed(() => {
|
||||||
|
if (!play.state.value.mineGenerated)
|
||||||
|
return play.mines
|
||||||
|
return play.blocks.reduce((a, b) => a + (b.mine ? 1 : 0) - (b.flagged ? 1 : 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
function newGame(difficulty) {
|
||||||
|
switch (difficulty) {
|
||||||
|
case 'very easy':
|
||||||
|
play.reset(3, 3, 1)
|
||||||
|
break
|
||||||
|
case 'easy':
|
||||||
|
play.reset(9, 9, 10)
|
||||||
|
break
|
||||||
|
case 'medium':
|
||||||
|
play.reset(16, 16, 40)
|
||||||
|
break
|
||||||
|
case 'hard':
|
||||||
|
play.reset(16, 30, 99)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
play.checkGameState()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex gap1 justify-center p4">
|
||||||
|
<button class="btn" @click="play.reset()">
|
||||||
|
New Game
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click="newGame('very easy')">
|
||||||
|
Very Easy
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click="newGame('easy')">
|
||||||
|
Easy
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click="newGame('medium')">
|
||||||
|
Medium
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click="newGame('hard')">
|
||||||
|
Hard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-10 justify-center">
|
||||||
|
<div class="text-2xl flex gap-1 items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 32 32"><path fill="currentColor" d="M15 11h2v9h-2zm-2-9h6v2h-6z"/><path fill="currentColor" d="m28 9l-1.42-1.41l-2.25 2.25a10.94 10.94 0 1 0 1.18 1.65ZM16 26a9 9 0 1 1 9-9a9 9 0 0 1-9 9Z"/></svg>
|
||||||
|
{{ timerMS }}
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl flex gap-1 items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M23 13v-2h-3.07a7.988 7.988 0 0 0-1.62-3.9l2.19-2.17l-1.43-1.43l-2.17 2.19A7.988 7.988 0 0 0 13 4.07V1h-2v3.07c-1.42.18-2.77.74-3.9 1.62L4.93 3.5L3.5 4.93L5.69 7.1A7.988 7.988 0 0 0 4.07 11H1v2h3.07c.18 1.42.74 2.77 1.62 3.9L3.5 19.07l1.43 1.43l2.17-2.19c1.13.88 2.48 1.44 3.9 1.62V23h2v-3.07c1.42-.18 2.77-.74 3.9-1.62l2.17 2.19l1.43-1.43l-2.19-2.17a7.988 7.988 0 0 0 1.62-3.9H23M12 8a4 4 0 0 0-4 4H6a6 6 0 0 1 6-6v2Z"/></svg>
|
||||||
|
{{ mineRest }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p5 w-full overflow-auto">
|
||||||
|
<div
|
||||||
|
v-for="row, y in state"
|
||||||
|
:key="y"
|
||||||
|
class="flex items-center justify-center w-max ma"
|
||||||
|
>
|
||||||
|
<MineBlock
|
||||||
|
v-for="block, x in row" :key="x"
|
||||||
|
:block="block"
|
||||||
|
@click="play.onClick(block)"
|
||||||
|
@dblclick="play.autoExpand(block)"
|
||||||
|
@contextmenu.prevent="play.onRightClick(block)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div flex="~ gap-1" justify-center>
|
||||||
|
<button btn @click="toggleDev()">
|
||||||
|
{{ isDev ? 'DEV' : 'NORMAL' }}
|
||||||
|
</button>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<Confetti :passed="play.state.value.status === 'won'" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.gap1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.gap-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.gap-10 {
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
background-color: var(--primary);
|
||||||
|
border: 1px solid var(--vp-c-brand-light);
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background-color: var(--vp-c-brand-light);
|
||||||
|
border-color: var(--vp-c-brand-light);
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
.p4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.p5 {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.w-max {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
.overflow-auto {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.ma {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
210
.vitepress/composables/logic.js
Normal file
210
.vitepress/composables/logic.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import { main_sidebar, chapter2, chapter3, chapter4, chapter5, chapter6, chapter
|
|||||||
import { nav } from './nav.js';
|
import { nav } from './nav.js';
|
||||||
import PanguPlugin from 'markdown-it-pangu'
|
import PanguPlugin from 'markdown-it-pangu'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
import VueMacros from 'unplugin-vue-macros/vite'
|
||||||
|
import Vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
const customElements = [
|
const customElements = [
|
||||||
'mjx-container',
|
'mjx-container',
|
||||||
@@ -152,11 +154,15 @@ export default withMermaid({
|
|||||||
isCustomElement: (tag) => customElements.includes(tag),
|
isCustomElement: (tag) => customElements.includes(tag),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
sitemap: {
|
sitemap: {
|
||||||
hostname: 'https://hdu-cs.wiki'
|
hostname: 'https://hdu-cs.wiki'
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
VueMacros(),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
@@ -164,6 +170,12 @@ export default withMermaid({
|
|||||||
replacement: fileURLToPath(
|
replacement: fileURLToPath(
|
||||||
new URL('./components/CustomSwitchAppearance.vue', import.meta.url)
|
new URL('./components/CustomSwitchAppearance.vue', import.meta.url)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: /^.*\/NotFound\.vue$/,
|
||||||
|
replacement: fileURLToPath(
|
||||||
|
new URL('./components/CustomNotFound.vue', import.meta.url)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1430
package-lock.json
generated
1430
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
|||||||
"@jupyterlab/theme-light-extension": "^4.0.4",
|
"@jupyterlab/theme-light-extension": "^4.0.4",
|
||||||
"@vercel/analytics": "^1.0.2",
|
"@vercel/analytics": "^1.0.2",
|
||||||
"@vueuse/core": "^10.3.0",
|
"@vueuse/core": "^10.3.0",
|
||||||
|
"canvas-confetti": "^1.6.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"markdown-it-pangu": "^1.0.2",
|
"markdown-it-pangu": "^1.0.2",
|
||||||
"sitemap": "^7.1.1"
|
"sitemap": "^7.1.1"
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"markdown-it-mathjax3": "^4.3.2",
|
"markdown-it-mathjax3": "^4.3.2",
|
||||||
"mermaid": "^10.3.0",
|
"mermaid": "^10.3.0",
|
||||||
|
"unplugin-vue-macros": "^2.4.7",
|
||||||
"vitepress": "^1.0.0-rc.4",
|
"vitepress": "^1.0.0-rc.4",
|
||||||
"vitepress-plugin-mermaid-xyxsw": "^2.12.13"
|
"vitepress-plugin-mermaid-xyxsw": "^2.12.13"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user