Files
nl-game/src/games/components/MazeGame.vue

161 lines
3.8 KiB
Vue
Raw Normal View History

<script setup>
// 迷宫探险:随机生成迷宫(深度优先挖墙),限时走到终点,越快分越高
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Keys, palette, randInt } from '../engine'
const emit = defineEmits(['score', 'end'])
const canvas = ref(null)
const N = 21 // 迷宫尺寸(奇数)
const CELL = 26
const SIZE = N * CELL
let ctx
let keys
let maze = [] // 1=墙 0=路
let player = { x: 1, y: 1 }
let level = 1
let score = 0
let timeLeft = 60
let countTimer = 0
let playing = false
// 深度优先挖墙生成迷宫
function generate() {
maze = Array.from({ length: N }, () => Array(N).fill(1))
const stack = [[1, 1]]
maze[1][1] = 0
while (stack.length) {
const [cx, cy] = stack[stack.length - 1]
// 随机方向找未访问的相邻格(步长2)
const dirs = [[2, 0], [-2, 0], [0, 2], [0, -2]].sort(() => Math.random() - 0.5)
let moved = false
for (const [dx, dy] of dirs) {
const nx = cx + dx
const ny = cy + dy
if (nx > 0 && nx < N - 1 && ny > 0 && ny < N - 1 && maze[ny][nx] === 1) {
maze[ny][nx] = 0
maze[cy + dy / 2][cx + dx / 2] = 0 // 打通中间墙
stack.push([nx, ny])
moved = true
break
}
}
if (!moved) stack.pop()
}
// 终点保证可达(右下角)
maze[N - 2][N - 2] = 0
}
function move(dx, dy) {
if (!playing) return
const nx = player.x + dx
const ny = player.y + dy
if (maze[ny]?.[nx] === 0) {
player.x = nx
player.y = ny
draw()
// 到达终点:加分并进入下一关(迷宫重新生成,时间+20)
if (nx === N - 2 && ny === N - 2) {
score += 100 + timeLeft
timeLeft = Math.min(90, timeLeft + 20)
level++
emit('score', score)
generate()
player = { x: 1, y: 1 }
draw()
}
}
}
function tick() {
timeLeft--
draw()
if (timeLeft <= 0) {
playing = false
clearInterval(countTimer)
emit('end', { score })
}
}
function draw() {
const p = palette()
ctx.fillStyle = p.bg
ctx.fillRect(0, 0, SIZE, SIZE)
for (let y = 0; y < N; y++) {
for (let x = 0; x < N; x++) {
if (maze[y][x] === 1) {
ctx.fillStyle = p.panel
ctx.fillRect(x * CELL, y * CELL, CELL, CELL)
ctx.strokeStyle = p.border
ctx.strokeRect(x * CELL + 0.5, y * CELL + 0.5, CELL - 1, CELL - 1)
}
}
}
// 终点
ctx.fillStyle = '#4ade80'
ctx.beginPath()
ctx.roundRect((N - 2) * CELL + 4, (N - 2) * CELL + 4, CELL - 8, CELL - 8, 6)
ctx.fill()
// 玩家
ctx.font = `${CELL - 4}px serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('🐭', player.x * CELL + CELL / 2, player.y * CELL + CELL / 2 + 1)
// 顶部信息
ctx.fillStyle = p.text
ctx.font = 'bold 14px sans-serif'
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
ctx.fillText(`第 ${level} 关 ⏱ ${timeLeft}s`, 6, 4)
}
// ---- 对外接口 ----
function start() {
generate()
player = { x: 1, y: 1 }
level = 1
score = 0
timeLeft = 60
playing = true
emit('score', 0)
clearInterval(countTimer)
countTimer = setInterval(tick, 1000)
draw()
}
function stop() {
clearInterval(countTimer)
keys?.detach()
}
// 时间延长卡:+30 秒
function useProp(code) {
if (code === 'time_extend' && playing) {
timeLeft += 30
draw()
return true
}
return false
}
defineExpose({ start, stop, useProp })
onMounted(() => {
ctx = canvas.value.getContext('2d')
keys = new Keys()
keys.attach()
keys.onPress((k) => {
const map = {
ArrowUp: [0, -1], w: [0, -1],
ArrowDown: [0, 1], s: [0, 1],
ArrowLeft: [-1, 0], a: [-1, 0],
ArrowRight: [1, 0], d: [1, 0],
}
if (map[k]) move(...map[k])
})
generate()
draw()
})
onBeforeUnmount(stop)
</script>
<template>
<canvas ref="canvas" :width="SIZE" :height="SIZE"></canvas>
</template>