Files
2024-08-28 15:35:35 +02:00

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>