250 lines
6.2 KiB
Vue
250 lines
6.2 KiB
Vue
<template>
|
|
<div class="container">
|
|
<div class="content">
|
|
<h1 class="title">Generator Labiryntu</h1>
|
|
|
|
<div class="input-group">
|
|
<input v-model="seed" @keyup.enter="generateMaze" type="text" class="input"
|
|
placeholder="Wprowadź seed (ziarno)">
|
|
<button @click="generateMaze" class="button" type="button">Generuj</button>
|
|
</div>
|
|
|
|
<div v-if="maze.length" class="maze-container">
|
|
<div v-for="(row, y) in maze" :key="y" class="maze-row">
|
|
<div v-for="(cell, x) in row" :key="x" :class="['maze-cell',
|
|
{
|
|
'wall': cell === 1,
|
|
'visited': isVisited(x, y),
|
|
'start': x === 1 && y === 1,
|
|
'end': x === maze[0].length - 3 && y === maze.length - 3,
|
|
'player': x === playerX && y === playerY
|
|
}]">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mobile-controls" v-if="isMobile">
|
|
<button @click="moveUp" class="control-button">↑</button>
|
|
<div class="horizontal-controls">
|
|
<button @click="moveLeft" class="control-button">←</button>
|
|
<button @click="moveDown" class="control-button">↓</button>
|
|
<button @click="moveRight" class="control-button">→</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="step-counter">
|
|
Liczba kroków: {{ stepCount }}
|
|
</div>
|
|
|
|
<div v-if="completed">
|
|
<p class="completion-message mt-2">Gratulacje! Przeszedłeś labirynt.</p>
|
|
<p class="completion-message">Chcesz spróbować jeszcze raz?</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, Ref, onMounted, watch } from 'vue';
|
|
|
|
const seed = ref('');
|
|
const maze = ref<number[][]>([]);
|
|
|
|
const playerX = ref(1);
|
|
const playerY = ref(1);
|
|
const stepCount = ref(0);
|
|
|
|
const completed = ref(false);
|
|
const visitedCells: Ref<Set<string>> = ref(new Set<string>());
|
|
|
|
const isMobile = ref(window.innerWidth < 640);
|
|
|
|
const updateIsMobile = () => {
|
|
isMobile.value = window.innerWidth < 640;
|
|
};
|
|
|
|
window.addEventListener('resize', updateIsMobile);
|
|
|
|
const generateMaze = () => {
|
|
const seedValue = seed.value || Math.random().toString(36).substring(7);
|
|
const rows = 50;
|
|
const cols = 50;
|
|
|
|
visitedCells.value = new Set();
|
|
stepCount.value = 0;
|
|
|
|
const random = (() => {
|
|
let seed = hash(seedValue);
|
|
return () => {
|
|
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
|
|
return seed / 0x7fffffff;
|
|
};
|
|
})();
|
|
|
|
maze.value = Array(rows).fill(0).map(() => Array(cols).fill(1));
|
|
|
|
const stack: [number, number][] = [[1, 1]];
|
|
maze.value[1][1] = 0;
|
|
|
|
while (stack.length > 0) {
|
|
const [x, y] = stack[stack.length - 1];
|
|
const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]].sort(() => random() - 0.5);
|
|
|
|
let moved = false;
|
|
for (const [dx, dy] of directions) {
|
|
const nx = x + dx * 2;
|
|
const ny = y + dy * 2;
|
|
if (nx > 0 && nx < cols - 1 && ny > 0 && ny < rows - 1 && maze.value[ny][nx] === 1) {
|
|
maze.value[ny][nx] = 0;
|
|
maze.value[y + dy][x + dx] = 0;
|
|
stack.push([nx, ny]);
|
|
moved = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!moved)
|
|
stack.pop();
|
|
}
|
|
|
|
playerX.value = 1;
|
|
playerY.value = 1;
|
|
completed.value = false;
|
|
};
|
|
|
|
const movePlayer = (e: KeyboardEvent) => {
|
|
if (completed.value) return;
|
|
|
|
const key = e.key;
|
|
let newX = playerX.value;
|
|
let newY = playerY.value;
|
|
|
|
if (key === 'ArrowUp') newY--;
|
|
else if (key === 'ArrowDown') newY++;
|
|
else if (key === 'ArrowLeft') newX--;
|
|
else if (key === 'ArrowRight') newX++;
|
|
|
|
moveTo(newX, newY);
|
|
};
|
|
|
|
const moveTo = (newX: number, newY: number) => {
|
|
if (newX >= 0 && newX < maze.value[0].length && newY >= 0 && newY < maze.value.length && maze.value[newY][newX] === 0) {
|
|
playerX.value = newX;
|
|
playerY.value = newY;
|
|
stepCount.value++;
|
|
visitedCells.value.add(`${newX},${newY}`);
|
|
|
|
if (newX === maze.value[0].length - 3 && newY === maze.value.length - 3)
|
|
completed.value = true;
|
|
}
|
|
};
|
|
|
|
const moveUp = () => moveTo(playerX.value, playerY.value - 1);
|
|
const moveDown = () => moveTo(playerX.value, playerY.value + 1);
|
|
const moveLeft = () => moveTo(playerX.value - 1, playerY.value);
|
|
const moveRight = () => moveTo(playerX.value + 1, playerY.value);
|
|
|
|
const hash = (s: string) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0);
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('keydown', movePlayer);
|
|
generateMaze();
|
|
});
|
|
|
|
watch(completed, (newValue) => {
|
|
if (newValue)
|
|
window.removeEventListener('keydown', movePlayer);
|
|
else
|
|
window.addEventListener('keydown', movePlayer);
|
|
});
|
|
|
|
const isVisited = (x: number, y: number) => {
|
|
if (
|
|
(x == playerX.value && y == playerY.value) ||
|
|
(x == 1 && y == 1) ||
|
|
(x == maze.value[0].length - 3 && y == maze.value.length - 3)
|
|
)
|
|
return false;
|
|
return visitedCells.value.has(`${x},${y}`);
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.container {
|
|
@apply flex justify-center items-center min-h-screen bg-gray-100 min-w-full p-4;
|
|
}
|
|
|
|
.content {
|
|
@apply bg-white p-4 sm:p-8 rounded-lg shadow-lg max-w-full;
|
|
}
|
|
|
|
.title {
|
|
@apply text-2xl sm:text-3xl font-bold text-center mb-4 sm:mb-6;
|
|
}
|
|
|
|
.input-group {
|
|
@apply flex mb-4;
|
|
}
|
|
|
|
.input {
|
|
@apply flex-grow px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500;
|
|
}
|
|
|
|
.button {
|
|
@apply px-4 py-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500;
|
|
}
|
|
|
|
.maze-container {
|
|
@apply inline-block border-2 border-black max-w-full overflow-auto;
|
|
}
|
|
|
|
.maze-row {
|
|
@apply flex;
|
|
}
|
|
|
|
.maze-cell {
|
|
@apply w-2 h-2 min-w-0.5 min-h-0.5;
|
|
}
|
|
|
|
.wall {
|
|
@apply bg-black;
|
|
}
|
|
|
|
.player {
|
|
@apply bg-red-500;
|
|
}
|
|
|
|
.start {
|
|
@apply bg-green-500;
|
|
}
|
|
|
|
.end {
|
|
@apply bg-yellow-500;
|
|
}
|
|
|
|
.visited {
|
|
@apply bg-blue-200;
|
|
}
|
|
|
|
.step-counter {
|
|
@apply mt-4 text-center text-base sm:text-lg font-semibold;
|
|
}
|
|
|
|
.completion-message {
|
|
@apply text-center text-base sm:text-lg font-semibold text-green-600;
|
|
}
|
|
|
|
/* Nowe style dla przycisków na urządzeniach mobilnych */
|
|
.mobile-controls {
|
|
@apply mt-4 flex flex-col items-center;
|
|
}
|
|
|
|
.horizontal-controls {
|
|
@apply flex mt-2 mb-2;
|
|
}
|
|
|
|
.control-button {
|
|
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded-full m-1;
|
|
@apply hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500;
|
|
}
|
|
</style> |