Files
nl-game/src/games/components/MazeGame.vue
李琦 cd1ab90013 初始化
需要注意的是饥荒不够完善:动作、手持工具、细节交互、系统逻辑
2026-08-15 07:24:34 +08:00

161 lines
3.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>