down time di findwords
This commit is contained in:
parent
5a9b3ba561
commit
30a600c339
15
package-lock.json
generated
15
package-lock.json
generated
@ -16,7 +16,8 @@
|
|||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-qrcode-reader": "^5.7.3",
|
"vue-qrcode-reader": "^5.7.3",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"vue3-lottie": "^3.3.1"
|
"vue3-lottie": "^3.3.1",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
@ -13099,6 +13100,18 @@
|
|||||||
"integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==",
|
"integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vuex": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.0.0-beta.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.4",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||||
|
|||||||
@ -17,7 +17,8 @@
|
|||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-qrcode-reader": "^5.7.3",
|
"vue-qrcode-reader": "^5.7.3",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1",
|
||||||
"vue3-lottie": "^3.3.1"
|
"vue3-lottie": "^3.3.1",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
|
|||||||
89
src/components/itemPage.vue
Normal file
89
src/components/itemPage.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <div>
|
||||||
|
<h2>Daftar Item</h2>
|
||||||
|
|
||||||
|
<button @click="addItem">Tambah Item</button>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
|
||||||
|
</ul>
|
||||||
|
</div> -->
|
||||||
|
<div
|
||||||
|
class="min-h-screen flex flex-col bg-gradient-to-b from-emerald-100 via-lime-50 to-white
|
||||||
|
text-gray-800 relative overflow-hidden mb-[-10px]"
|
||||||
|
>
|
||||||
|
<div class="w-full px-6 py-4 flex items-center justify-between backdrop-blur-md bg-white/30 border-b border-emerald-100 z-20">
|
||||||
|
<button
|
||||||
|
class="flex items-center gap-2 text-emerald-600 hover:text-emerald-800 transition-colors"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-semibold">Kembali</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Breadcrumb / Path -->
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
<span class="text-emerald-600 font-medium cursor-pointer hover:underline">Beranda</span>
|
||||||
|
<span class="mx-2">›</span>
|
||||||
|
<span class="text-gray-700 font-semibold">Manga</span>
|
||||||
|
<span class="mx-2">›</span>
|
||||||
|
<span class="text-gray-700 font-semibold">Chapters</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-8">
|
||||||
|
<h1 class="text-4xl font-extrabold bg-gradient-to-r from-emerald-500 to-lime-500 bg-clip-text text-transparent">
|
||||||
|
Daftar Manga
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-500 mt-2">Temukan bacaan favoritmu di sini 📖</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 mt-10 px-6 pb-20">
|
||||||
|
<div
|
||||||
|
|
||||||
|
class="group cursor-pointer rounded-2xl bg-white/60 backdrop-blur-lg p-4 shadow-md border border-white/40
|
||||||
|
hover:shadow-2xl hover:-translate-y-2 transition-all duration-300 relative overflow-hidden"
|
||||||
|
>
|
||||||
|
<div class="mt-0 text-center">
|
||||||
|
<h3 class="font-semibold text-lg text-gray-800 truncate">Chapter</h3>
|
||||||
|
</div>
|
||||||
|
<div class="relative" >
|
||||||
|
<img
|
||||||
|
class="w-full h-48 object-cover rounded-xl group-hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent rounded-xl opacity-0 group-hover:opacity-100 transition-opacity"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 text-center">
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
<button
|
||||||
|
class="text-gray-900 bg-white hover:bg-gray-100 border border-gray-200 focus:ring-4 focus:outline-none focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-gray-600 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:bg-gray-700 me-2 mb-2">
|
||||||
|
<h3 class="font-semibold text-lg text-gray-800 truncate">📖 Synopsis</h3>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState, mapActions } from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState("items", ["items"])
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions("items", ["fetchItems", "createItem"]),
|
||||||
|
|
||||||
|
addItem() {
|
||||||
|
const newItem = { id: Date.now(), name: "Item baru" };
|
||||||
|
this.createItem(newItem);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchItems();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -4,7 +4,13 @@ import './assets/tailwind.css'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import Vue3Lottie from 'vue3-lottie'
|
import Vue3Lottie from 'vue3-lottie'
|
||||||
|
import store from '@/stores'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
createApp(App).use(pinia).use(router).use(Vue3Lottie).mount('#app')
|
createApp(App)
|
||||||
|
.use(pinia)
|
||||||
|
.use(router)
|
||||||
|
.use(Vue3Lottie)
|
||||||
|
.use(store)
|
||||||
|
.mount('#app')
|
||||||
|
|
||||||
|
|||||||
240
src/pages/Findwords copy.vue
Normal file
240
src/pages/Findwords copy.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="w-full px-6 py-4 flex items-center justify-between backdrop-blur-md bg-white/30 border-b border-emerald-100 z-20">
|
||||||
|
<!-- Back Button -->
|
||||||
|
<button
|
||||||
|
@click="goBack"
|
||||||
|
class="flex items-center gap-2 text-emerald-600 hover:text-emerald-800 transition-colors"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-semibold">Kembali</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Breadcrumb / Path -->
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
<!-- <span class="text-emerald-600 font-medium cursor-pointer hover:underline" @click="goEntertainment">Entertainment</span>
|
||||||
|
<span class="mx-2">›</span>
|
||||||
|
<span class="text-emerald-600 font-medium cursor-pointer hover:underline" @click="goBack">Mini Games</span>
|
||||||
|
<span class="mx-2">›</span> -->
|
||||||
|
<span class="text-gray-700 font-semibold">Find Words</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col min-h-screen bg-gradient-to-b from-green-200 to-lime-100 text-gray-900 select-none"
|
||||||
|
style="background-image:url('/images/footer.png'); background-size:cover; background-position:center;">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="text-center mt-8">
|
||||||
|
<h1 class="text-4xl font-extrabold bg-gradient-to-r from-emerald-500 to-lime-500 bg-clip-text text-transparent">
|
||||||
|
<span>🌿</span>Temukan Kata Positif
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-500 mt-2">Temukan kata kata positif yang ada di bawah ini 📖</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid Game -->
|
||||||
|
<main class="flex-1 flex flex-col items-center justify-center p-4">
|
||||||
|
<div
|
||||||
|
ref="gridRef"
|
||||||
|
class="grid gap-1"
|
||||||
|
:style="{
|
||||||
|
gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))`,
|
||||||
|
width: '90vw',
|
||||||
|
maxWidth: '500px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<!-- per baris -->
|
||||||
|
<template v-for="(row, rowIndex) in grid" :key="rowIndex">
|
||||||
|
<!-- per sel -->
|
||||||
|
<div
|
||||||
|
v-for="(letter, colIndex) in row"
|
||||||
|
:key="`${rowIndex}-${colIndex}`"
|
||||||
|
@mousedown.prevent="startSelect(rowIndex, colIndex)"
|
||||||
|
@mouseenter="dragSelect(rowIndex, colIndex)"
|
||||||
|
@mouseup="endSelect"
|
||||||
|
@touchstart.prevent="startSelect(rowIndex, colIndex)"
|
||||||
|
@touchmove.prevent="dragSelectTouch($event)"
|
||||||
|
@touchend.prevent="endSelect"
|
||||||
|
class="flex items-center justify-center text-lg sm:text-xl font-bold rounded-xl cursor-pointer
|
||||||
|
transition-all duration-200 ease-out shadow-md"
|
||||||
|
:class="cellClass(rowIndex, colIndex)"
|
||||||
|
>
|
||||||
|
{{ letter }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Words -->
|
||||||
|
<div class="mt-6 bg-white bg-opacity-80 p-4 rounded-3xl shadow-lg w-full max-w-lg border border-lime-200">
|
||||||
|
<h2 class="font-semibold text-green-800 mb-2 text-center">Cari kata berikut:</h2>
|
||||||
|
<div class="flex flex-wrap justify-center gap-2">
|
||||||
|
<span
|
||||||
|
v-for="word in words"
|
||||||
|
:key="word"
|
||||||
|
class="px-3 py-1 rounded-full text-sm font-semibold shadow transition-all duration-200"
|
||||||
|
:class="foundWords.includes(word)
|
||||||
|
? 'bg-green-300 text-green-900 animate-bounce-glow'
|
||||||
|
: 'bg-lime-100 text-green-700 hover:bg-green-200 hover:scale-105'"
|
||||||
|
>
|
||||||
|
{{ word }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-xs text-green-700 italic text-center">
|
||||||
|
Temukan semua kata positif seperti sabar, cinta, syukur, dan gembira untuk hati yang ceria!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer Buttons -->
|
||||||
|
<footer class="p-4 flex justify-center gap-3">
|
||||||
|
<button
|
||||||
|
@click="resetGame"
|
||||||
|
class="px-6 py-2 bg-gradient-to-r from-lime-400 to-green-500 text-white font-bold rounded-full shadow-md hover:shadow-lg hover:scale-105 active:scale-95 transition-all duration-200"
|
||||||
|
>
|
||||||
|
🔄 Main Lagi
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Find-words"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
|
||||||
|
const size = 10;
|
||||||
|
const allWords = [
|
||||||
|
"TENANG", "SABAR", "SYUKUR", "CINTA", "GEMBIRA", "BANGGA",
|
||||||
|
"PERCAYA", "SEMANGAT", "OPTIMIS", "IKHLAS", "BAHAGIA", "PEDULI",
|
||||||
|
"CERIA", "HARMONI", "SETIA", "JUJUR", "RAMAH", "TABAH", "TULUS",
|
||||||
|
"BERANI", "MAJU", "DAMAI", "RIANG"
|
||||||
|
];
|
||||||
|
|
||||||
|
const words = ref([]);
|
||||||
|
const grid = ref([]);
|
||||||
|
const foundWords = ref([]);
|
||||||
|
const selectedCells = ref([]);
|
||||||
|
const isSelecting = ref(false);
|
||||||
|
const wordPositions = {};
|
||||||
|
const gridRef = ref(null);
|
||||||
|
|
||||||
|
function generateGrid() {
|
||||||
|
const rand = (n) => Math.floor(Math.random() * n);
|
||||||
|
const g = Array.from({ length: size }, () => Array(size).fill(""));
|
||||||
|
const chosenWords = [...allWords].sort(() => Math.random() - 0.5).slice(0, 6);
|
||||||
|
words.value = chosenWords;
|
||||||
|
|
||||||
|
chosenWords.forEach((word) => {
|
||||||
|
let placed = false;
|
||||||
|
for (let tries = 0; tries < 100 && !placed; tries++) {
|
||||||
|
const horizontal = Math.random() > 0.5;
|
||||||
|
const row = rand(size - (horizontal ? 1 : word.length));
|
||||||
|
const col = rand(size - (horizontal ? word.length : 1));
|
||||||
|
|
||||||
|
let fits = true;
|
||||||
|
for (let i = 0; i < word.length; i++) {
|
||||||
|
const r = row + (horizontal ? 0 : i);
|
||||||
|
const c = col + (horizontal ? i : 0);
|
||||||
|
if (g[r][c] && g[r][c] !== word[i]) {
|
||||||
|
fits = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fits) {
|
||||||
|
for (let i = 0; i < word.length; i++) {
|
||||||
|
const r = row + (horizontal ? 0 : i);
|
||||||
|
const c = col + (horizontal ? i : 0);
|
||||||
|
g[r][c] = word[i];
|
||||||
|
}
|
||||||
|
placed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
for (let r = 0; r < size; r++) {
|
||||||
|
for (let c = 0; c < size; c++) {
|
||||||
|
if (!g[r][c]) g[r][c] = letters[Math.floor(Math.random() * letters.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.value = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellClass(r, c) {
|
||||||
|
const isSelected = selectedCells.value.some((pos) => pos.r === r && pos.c === c);
|
||||||
|
const isFound = foundWords.value.some((word) =>
|
||||||
|
wordPositions[word]?.some((p) => p.r === r && p.c === c)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isFound) return "bg-green-300 text-green-900 scale-105";
|
||||||
|
if (isSelected) return "bg-yellow-200 text-yellow-900 scale-105";
|
||||||
|
return "bg-white text-green-700 hover:bg-green-100 hover:scale-105";
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSelect(r, c) {
|
||||||
|
isSelecting.value = true;
|
||||||
|
selectedCells.value = [{ r, c }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragSelect(r, c) {
|
||||||
|
if (!isSelecting.value) return;
|
||||||
|
const last = selectedCells.value[selectedCells.value.length - 1];
|
||||||
|
if (last.r !== r || last.c !== c) selectedCells.value.push({ r, c });
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragSelectTouch(e) {
|
||||||
|
if (!isSelecting.value) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const rect = gridRef.value.getBoundingClientRect();
|
||||||
|
const cellSize = rect.width / size;
|
||||||
|
const x = Math.floor((touch.clientX - rect.left) / cellSize);
|
||||||
|
const y = Math.floor((touch.clientY - rect.top) / cellSize);
|
||||||
|
if (x >= 0 && y >= 0 && x < size && y < size) dragSelect(y, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function endSelect() {
|
||||||
|
if (!isSelecting.value) return;
|
||||||
|
const selectedWord = selectedCells.value.map(({ r, c }) => grid.value[r][c]).join("");
|
||||||
|
if (words.value.includes(selectedWord) && !foundWords.value.includes(selectedWord)) {
|
||||||
|
foundWords.value.push(selectedWord);
|
||||||
|
wordPositions[selectedWord] = [...selectedCells.value];
|
||||||
|
if (foundWords.value.length === words.value.length) {
|
||||||
|
setTimeout(() => alert("🎉 Semua kata ditemukan! Hebat sekali! 🌟"), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedCells.value = [];
|
||||||
|
isSelecting.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetGame() {
|
||||||
|
foundWords.value = [];
|
||||||
|
selectedCells.value = [];
|
||||||
|
generateGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// function goEntertainment(){
|
||||||
|
// window.history.back('/entertainment');
|
||||||
|
// }
|
||||||
|
|
||||||
|
function goBack(){
|
||||||
|
window.history.back('/entertainment/mini-games');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
resetGame();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes bounceGlow {
|
||||||
|
0%, 100% { box-shadow: 0 0 8px rgba(72, 255, 140, 0.6); transform: scale(1); }
|
||||||
|
50% { box-shadow: 0 0 20px rgba(72, 255, 140, 1); transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
.animate-bounce-glow {
|
||||||
|
animation: bounceGlow 1s infinite;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,240 +1,339 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Header Section -->
|
|
||||||
<div class="w-full px-6 py-4 flex items-center justify-between backdrop-blur-md bg-white/30 border-b border-emerald-100 z-20">
|
<div class="w-full px-6 py-4 flex items-center justify-between backdrop-blur-md bg-white/30 border-b border-emerald-100 z-20">
|
||||||
<!-- Back Button -->
|
<button @click="goBack" class="flex items-center gap-2 text-emerald-600 hover:text-emerald-800 transition-colors">
|
||||||
<button
|
|
||||||
@click="goBack"
|
|
||||||
class="flex items-center gap-2 text-emerald-600 hover:text-emerald-800 transition-colors"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="font-semibold">Kembali</span>
|
<span class="font-semibold">Kembali</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Breadcrumb / Path -->
|
|
||||||
<div class="text-sm text-gray-600">
|
|
||||||
<!-- <span class="text-emerald-600 font-medium cursor-pointer hover:underline" @click="goEntertainment">Entertainment</span>
|
|
||||||
<span class="mx-2">›</span>
|
|
||||||
<span class="text-emerald-600 font-medium cursor-pointer hover:underline" @click="goBack">Mini Games</span>
|
|
||||||
<span class="mx-2">›</span> -->
|
|
||||||
<span class="text-gray-700 font-semibold">Find Words</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col min-h-screen bg-gradient-to-b from-green-200 to-lime-100 text-gray-900 select-none"
|
<div class="flex flex-col min-h-screen bg-gradient-to-b from-green-200 to-lime-100 text-gray-900 select-none"
|
||||||
style="background-image:url('/images/footer.png'); background-size:cover; background-position:center;">
|
style="background-image:url('/images/footer.png'); background-size:cover; background-position:center;">
|
||||||
<!-- Header -->
|
|
||||||
<div class="text-center mt-8">
|
<div class="text-center mt-8">
|
||||||
<h1 class="text-4xl font-extrabold bg-gradient-to-r from-emerald-500 to-lime-500 bg-clip-text text-transparent">
|
<h1 class="text-4xl font-extrabold bg-gradient-to-r from-emerald-500 to-lime-500 bg-clip-text text-transparent">🌿 Temukan Kata Positif</h1>
|
||||||
<span>🌿</span>Temukan Kata Positif
|
|
||||||
</h1>
|
|
||||||
<p class="text-gray-500 mt-2">Temukan kata kata positif yang ada di bawah ini 📖</p>
|
<p class="text-gray-500 mt-2">Temukan kata kata positif yang ada di bawah ini 📖</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center mt-8">
|
||||||
|
<div class="text-sm text-gray-600">Level: <span class="font-semibold text-gray-800">{{ currLevel + 1 }} / {{ levels.length }}</span></div>
|
||||||
|
<div class="text-sm mt-0.5 text-emerald-700 font-bold">⏳ {{ timeLeft }}s</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Grid Game -->
|
|
||||||
<main class="flex-1 flex flex-col items-center justify-center p-4">
|
<main class="flex-1 flex flex-col items-center justify-center p-4">
|
||||||
<div
|
<div ref="gridRef" class="grid gap-1" :style="gridStyle">
|
||||||
ref="gridRef"
|
<template v-for="(row, r) in grid" :key="r">
|
||||||
class="grid gap-1"
|
<div v-for="(letter, c) in row" :key="`${r}-${c}`"
|
||||||
:style="{
|
@mousedown.prevent="startSelect(r,c)"
|
||||||
gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))`,
|
@mouseenter="dragSelect(r,c)"
|
||||||
width: '90vw',
|
|
||||||
maxWidth: '500px'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- per baris -->
|
|
||||||
<template v-for="(row, rowIndex) in grid" :key="rowIndex">
|
|
||||||
<!-- per sel -->
|
|
||||||
<div
|
|
||||||
v-for="(letter, colIndex) in row"
|
|
||||||
:key="`${rowIndex}-${colIndex}`"
|
|
||||||
@mousedown.prevent="startSelect(rowIndex, colIndex)"
|
|
||||||
@mouseenter="dragSelect(rowIndex, colIndex)"
|
|
||||||
@mouseup="endSelect"
|
@mouseup="endSelect"
|
||||||
@touchstart.prevent="startSelect(rowIndex, colIndex)"
|
@touchstart.prevent="startSelect(r,c)"
|
||||||
@touchmove.prevent="dragSelectTouch($event)"
|
@touchmove.prevent="dragSelectTouch($event)"
|
||||||
@touchend.prevent="endSelect"
|
@touchend.prevent="endSelect"
|
||||||
class="flex items-center justify-center text-lg sm:text-xl font-bold rounded-xl cursor-pointer
|
class="flex items-center justify-center text-lg sm:text-xl font-bold rounded-xl cursor-pointer transition-all duration-200 ease-out shadow-md aspect-square"
|
||||||
transition-all duration-200 ease-out shadow-md"
|
:class="cellClass(r,c)">
|
||||||
:class="cellClass(rowIndex, colIndex)"
|
|
||||||
>
|
|
||||||
{{ letter }}
|
{{ letter }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer Words -->
|
|
||||||
<div class="mt-6 bg-white bg-opacity-80 p-4 rounded-3xl shadow-lg w-full max-w-lg border border-lime-200">
|
<div class="mt-6 bg-white bg-opacity-80 p-4 rounded-3xl shadow-lg w-full max-w-lg border border-lime-200">
|
||||||
<h2 class="font-semibold text-green-800 mb-2 text-center">Cari kata berikut:</h2>
|
<h2 class="font-semibold text-green-800 mb-2 text-center">Cari kata berikut:</h2>
|
||||||
<div class="flex flex-wrap justify-center gap-2">
|
<div class="flex flex-wrap justify-center gap-2">
|
||||||
<span
|
<span v-for="w in words" :key="w" class="px-3 py-1 rounded-full text-sm font-semibold shadow transition-all duration-200"
|
||||||
v-for="word in words"
|
:class="foundWords.includes(w) ? 'bg-green-300 text-green-900 animate-bounce-glow' : 'bg-lime-100 text-green-700 hover:bg-green-200 hover:scale-105'">
|
||||||
:key="word"
|
{{ w }}
|
||||||
class="px-3 py-1 rounded-full text-sm font-semibold shadow transition-all duration-200"
|
|
||||||
:class="foundWords.includes(word)
|
|
||||||
? 'bg-green-300 text-green-900 animate-bounce-glow'
|
|
||||||
: 'bg-lime-100 text-green-700 hover:bg-green-200 hover:scale-105'"
|
|
||||||
>
|
|
||||||
{{ word }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-3 text-xs text-green-700 italic text-center">
|
<p class="mt-3 text-xs text-green-700 italic text-center">Temukan semua kata positif untuk hati yang ceria!</p>
|
||||||
Temukan semua kata positif seperti sabar, cinta, syukur, dan gembira untuk hati yang ceria!
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Footer Buttons -->
|
|
||||||
<footer class="p-4 flex justify-center gap-3">
|
<footer class="p-4 flex justify-center gap-3">
|
||||||
<button
|
<button @click="resetGame" class="px-6 py-2 bg-gradient-to-r from-lime-400 to-green-500 text-white font-bold rounded-full shadow-md hover:shadow-lg hover:scale-105 active:scale-95 transition-all duration-200">
|
||||||
@click="resetGame"
|
|
||||||
class="px-6 py-2 bg-gradient-to-r from-lime-400 to-green-500 text-white font-bold rounded-full shadow-md hover:shadow-lg hover:scale-105 active:scale-95 transition-all duration-200"
|
|
||||||
>
|
|
||||||
🔄 Main Lagi
|
🔄 Main Lagi
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "Find-words"
|
name: "Find-words",
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, computed, watch } from "vue";
|
||||||
|
|
||||||
const size = 10;
|
/* ---------- data kata ---------- */
|
||||||
const allWords = [
|
const allWords = [
|
||||||
"TENANG", "SABAR", "SYUKUR", "CINTA", "GEMBIRA", "BANGGA",
|
"TENANG","SABAR","SYUKUR","CINTA","GEMBIRA","BANGGA","PERCAYA","SEMANGAT","OPTIMIS","IKHLAS",
|
||||||
"PERCAYA", "SEMANGAT", "OPTIMIS", "IKHLAS", "BAHAGIA", "PEDULI",
|
"BAHAGIA","PEDULI","CERIA","HARMONI","SETIA","JUJUR","RAMAH","TABAH","TULUS","BERANI","MAJU","DAMAI","RIANG"
|
||||||
"CERIA", "HARMONI", "SETIA", "JUJUR", "RAMAH", "TABAH", "TULUS",
|
|
||||||
"BERANI", "MAJU", "DAMAI", "RIANG"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const words = ref([]);
|
/* ---------- levels (tambahkan waktu tiap level) ---------- */
|
||||||
|
const levels = [
|
||||||
|
{ name: "Easy", count: 4, time: 60 },
|
||||||
|
{ name: "Medium", count: 6, time: 45 },
|
||||||
|
{ name: "Hard", count: 8, time: 30 },
|
||||||
|
{ name: "Extreme", count: 10, time: 20 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const currLevel = ref(0);
|
||||||
|
const size = computed(() => levels[currLevel.value].count);
|
||||||
|
|
||||||
|
/* ---------- reactive state ---------- */
|
||||||
const grid = ref([]);
|
const grid = ref([]);
|
||||||
|
const words = ref([]);
|
||||||
const foundWords = ref([]);
|
const foundWords = ref([]);
|
||||||
const selectedCells = ref([]);
|
const selectedCells = ref([]);
|
||||||
const isSelecting = ref(false);
|
|
||||||
const wordPositions = {};
|
|
||||||
const gridRef = ref(null);
|
const gridRef = ref(null);
|
||||||
|
const isSelecting = ref(false);
|
||||||
|
const wordPositions = {}; // diisi saat kata ditempatkan
|
||||||
|
// const placedWords = {};
|
||||||
|
|
||||||
|
const gridStyle = computed(() => ({ gridTemplateColumns: `repeat(${size.value}, minmax(0,1fr))`, width: "90vw", maxWidth: "500px" }));
|
||||||
|
|
||||||
|
/* ---------- timer ---------- */
|
||||||
|
let timer = null;
|
||||||
|
const timeLeft = ref(levels[currLevel.value].time);
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
if (timer) clearInterval(timer);
|
||||||
|
timeLeft.value = levels[currLevel.value].time;
|
||||||
|
timer = setInterval(() => {
|
||||||
|
timeLeft.value--;
|
||||||
|
if (timeLeft.value <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
// waktu habis -> reset level (tetap di level sama)
|
||||||
|
resetGame();
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- helper: pilih kata yang muat ---------- */
|
||||||
|
const shuffle = (arr) => arr.slice().sort(() => Math.random() - 0.5);
|
||||||
|
|
||||||
|
function candidatePoolForSize(sz) {
|
||||||
|
return allWords.filter(w => w.length <= sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickWordsForLevel() {
|
||||||
|
const pool = shuffle(candidatePoolForSize(size.value));
|
||||||
|
const desired = levels[currLevel.value].count;
|
||||||
|
if (pool.length <= desired) return pool.slice(0, pool.length);
|
||||||
|
return pool.slice(0, desired);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- placement utils (8 arah) ---------- */
|
||||||
|
const DIRECTIONS = [ [1,0],[-1,0],[0,1],[0,-1], [1,1],[-1,-1],[1,-1],[-1,1] ];
|
||||||
|
|
||||||
|
function canPlace(g, word, r, c, dr, dc) {
|
||||||
|
for (let i = 0; i < word.length; i++) {
|
||||||
|
const nr = r + dr * i;
|
||||||
|
const nc = c + dc * i;
|
||||||
|
if (nr < 0 || nc < 0 || nr >= size.value || nc >= size.value) return false;
|
||||||
|
if (g[nr][nc] !== "" && g[nr][nc] !== word[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeAt(g, word, r, c, dr, dc) {
|
||||||
|
const cells = [];
|
||||||
|
for (let i = 0; i < word.length; i++) {
|
||||||
|
const nr = r + dr * i;
|
||||||
|
const nc = c + dc * i;
|
||||||
|
g[nr][nc] = word[i];
|
||||||
|
cells.push({ r: nr, c: nc });
|
||||||
|
}
|
||||||
|
wordPositions[word] = cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1) coba intersection lalu 2) fallback random */
|
||||||
|
function placeWordWithIntersection(g, word) {
|
||||||
|
// intersection: cari huruf yang sama di grid yg sudah terisi
|
||||||
|
for (let r = 0; r < size.value; r++) {
|
||||||
|
for (let c = 0; c < size.value; c++) {
|
||||||
|
const letterHere = g[r][c];
|
||||||
|
if (!letterHere) continue;
|
||||||
|
for (let idx = 0; idx < word.length; idx++) {
|
||||||
|
if (word[idx] !== letterHere) continue;
|
||||||
|
for (const [dr, dc] of DIRECTIONS) {
|
||||||
|
const startR = r - dr * idx;
|
||||||
|
const startC = c - dc * idx;
|
||||||
|
if (canPlace(g, word, startR, startC, dr, dc)) {
|
||||||
|
placeAt(g, word, startR, startC, dr, dc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback random tries
|
||||||
|
const triesMax = 300;
|
||||||
|
for (let t = 0; t < triesMax; t++) {
|
||||||
|
const [dr, dc] = DIRECTIONS[Math.floor(Math.random() * DIRECTIONS.length)];
|
||||||
|
const r = Math.floor(Math.random() * size.value);
|
||||||
|
const c = Math.floor(Math.random() * size.value);
|
||||||
|
if (canPlace(g, word, r, c, dr, dc)) {
|
||||||
|
placeAt(g, word, r, c, dr, dc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- build grid (re-try strategy) ---------- */
|
||||||
|
function fillEmpty(g) {
|
||||||
|
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
for (let r = 0; r < size.value; r++) {
|
||||||
|
for (let c = 0; c < size.value; c++) {
|
||||||
|
if (!g[r][c]) g[r][c] = abc[Math.floor(Math.random() * abc.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function generateGrid() {
|
function generateGrid() {
|
||||||
const rand = (n) => Math.floor(Math.random() * n);
|
const maxAttempts = 8;
|
||||||
const g = Array.from({ length: size }, () => Array(size).fill(""));
|
const chosenWords = pickWordsForLevel();
|
||||||
const chosenWords = [...allWords].sort(() => Math.random() - 0.5).slice(0, 6);
|
const desiredCount = Math.min(chosenWords.length, levels[currLevel.value].count);
|
||||||
words.value = chosenWords;
|
|
||||||
|
|
||||||
chosenWords.forEach((word) => {
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||||
let placed = false;
|
// reset wordPositions
|
||||||
for (let tries = 0; tries < 100 && !placed; tries++) {
|
for (const k in wordPositions) delete wordPositions[k];
|
||||||
const horizontal = Math.random() > 0.5;
|
|
||||||
const row = rand(size - (horizontal ? 1 : word.length));
|
|
||||||
const col = rand(size - (horizontal ? word.length : 1));
|
|
||||||
|
|
||||||
let fits = true;
|
const g = Array.from({ length: size.value }, () => Array(size.value).fill(""));
|
||||||
for (let i = 0; i < word.length; i++) {
|
// coba tempatkan kata panjang dulu
|
||||||
const r = row + (horizontal ? 0 : i);
|
const wordsToPlace = chosenWords.slice(0, desiredCount).sort((a,b) => b.length - a.length);
|
||||||
const c = col + (horizontal ? i : 0);
|
|
||||||
if (g[r][c] && g[r][c] !== word[i]) {
|
|
||||||
fits = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fits) {
|
|
||||||
for (let i = 0; i < word.length; i++) {
|
|
||||||
const r = row + (horizontal ? 0 : i);
|
|
||||||
const c = col + (horizontal ? i : 0);
|
|
||||||
g[r][c] = word[i];
|
|
||||||
}
|
|
||||||
placed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
let allPlaced = true;
|
||||||
for (let r = 0; r < size; r++) {
|
for (const w of wordsToPlace) {
|
||||||
for (let c = 0; c < size; c++) {
|
const ok = placeWordWithIntersection(g, w);
|
||||||
if (!g[r][c]) g[r][c] = letters[Math.floor(Math.random() * letters.length)];
|
if (!ok) { allPlaced = false; break; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allPlaced) {
|
||||||
|
fillEmpty(g);
|
||||||
grid.value = g;
|
grid.value = g;
|
||||||
|
// words.value = wordsToPlace;
|
||||||
|
words.value = Object.keys(wordPositions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// else ulangi generate
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback (jarang terjadi)
|
||||||
|
const fallbackG = Array.from({ length: size.value }, () => Array(size.value).fill(""));
|
||||||
|
fillEmpty(fallbackG);
|
||||||
|
grid.value = fallbackG;
|
||||||
|
words.value = chosenWords.slice(0, Math.min(chosenWords.length, levels[currLevel.value].count));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- UI selection & validation ---------- */
|
||||||
|
// function cellClass(r, c) {
|
||||||
|
// const sel = selectedCells.value.some(p => p.r === r && p.c === c);
|
||||||
|
// const found = Object.values(wordPositions).flat().some(p => p.r === r && p.c === c);
|
||||||
|
// return found ? "bg-green-300 text-green-900 scale-105" : sel ? "bg-yellow-200 text-yellow-900 scale-105" : "bg-white text-green-700 hover:bg-green-100 hover:scale-105";
|
||||||
|
// }
|
||||||
function cellClass(r, c) {
|
function cellClass(r, c) {
|
||||||
const isSelected = selectedCells.value.some((pos) => pos.r === r && pos.c === c);
|
const sel = selectedCells.value.some(p => p.r === r && p.c === c);
|
||||||
const isFound = foundWords.value.some((word) =>
|
|
||||||
wordPositions[word]?.some((p) => p.r === r && p.c === c)
|
// highlight hanya jika kata ditemukan user (masuk ke foundWords)
|
||||||
|
const found = foundWords.value.some(word =>
|
||||||
|
wordPositions[word]?.some(p => p.r === r && p.c === c)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isFound) return "bg-green-300 text-green-900 scale-105";
|
return found
|
||||||
if (isSelected) return "bg-yellow-200 text-yellow-900 scale-105";
|
? "bg-green-300 text-green-900 scale-105"
|
||||||
return "bg-white text-green-700 hover:bg-green-100 hover:scale-105";
|
: sel
|
||||||
|
? "bg-yellow-200 text-yellow-900 scale-105"
|
||||||
|
: "bg-white text-green-700 hover:bg-green-100 hover:scale-105";
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSelect(r, c) {
|
|
||||||
isSelecting.value = true;
|
|
||||||
selectedCells.value = [{ r, c }];
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragSelect(r, c) {
|
function startSelect(r,c){ isSelecting.value = true; selectedCells.value = [{r,c}]; }
|
||||||
if (!isSelecting.value) return;
|
function dragSelect(r,c){ if (isSelecting.value) { const last = selectedCells.value.at(-1); if(last.r !== r || last.c !== c) selectedCells.value.push({r,c}); } }
|
||||||
const last = selectedCells.value[selectedCells.value.length - 1];
|
function dragSelectTouch(e){ if (!isSelecting.value) return; const t = e.touches[0]; const rect = gridRef.value.getBoundingClientRect(); const s = rect.width / size.value; const x = Math.floor((t.clientX - rect.left) / s); const y = Math.floor((t.clientY - rect.top) / s); if (x >= 0 && y >= 0 && x < size.value && y < size.value) dragSelect(y, x); }
|
||||||
if (last.r !== r || last.c !== c) selectedCells.value.push({ r, c });
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragSelectTouch(e) {
|
/* endSelect: validasi arah lalu cek kata */
|
||||||
if (!isSelecting.value) return;
|
function endSelect(){
|
||||||
const touch = e.touches[0];
|
if(!isSelecting.value) return;
|
||||||
const rect = gridRef.value.getBoundingClientRect();
|
const cells = selectedCells.value;
|
||||||
const cellSize = rect.width / size;
|
if (cells.length < 2) { selectedCells.value = []; isSelecting.value = false; return; }
|
||||||
const x = Math.floor((touch.clientX - rect.left) / cellSize);
|
|
||||||
const y = Math.floor((touch.clientY - rect.top) / cellSize);
|
|
||||||
if (x >= 0 && y >= 0 && x < size && y < size) dragSelect(y, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
function endSelect() {
|
const dx = Math.sign(cells[1].r - cells[0].r);
|
||||||
if (!isSelecting.value) return;
|
const dy = Math.sign(cells[1].c - cells[0].c);
|
||||||
const selectedWord = selectedCells.value.map(({ r, c }) => grid.value[r][c]).join("");
|
|
||||||
if (words.value.includes(selectedWord) && !foundWords.value.includes(selectedWord)) {
|
const valid = cells.every((cell, idx) => {
|
||||||
foundWords.value.push(selectedWord);
|
if (idx === 0) return true;
|
||||||
wordPositions[selectedWord] = [...selectedCells.value];
|
return cell.r === (cells[0].r + dx * idx) && cell.c === (cells[0].c + dy * idx);
|
||||||
if (foundWords.value.length === words.value.length) {
|
});
|
||||||
setTimeout(() => alert("🎉 Semua kata ditemukan! Hebat sekali! 🌟"), 200);
|
|
||||||
}
|
if (!valid) { selectedCells.value = []; isSelecting.value = false; return; }
|
||||||
|
|
||||||
|
const formed = cells.map(p => grid.value[p.r][p.c]).join("");
|
||||||
|
if (words.value.includes(formed) && !foundWords.value.includes(formed)) {
|
||||||
|
foundWords.value.push(formed);
|
||||||
|
// mark positions (already stored when placed)
|
||||||
|
// panggil checkLevelComplete untuk auto next level
|
||||||
|
checkLevelComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCells.value = [];
|
selectedCells.value = [];
|
||||||
isSelecting.value = false;
|
isSelecting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- level completion ---------- */
|
||||||
|
function checkLevelComplete() {
|
||||||
|
if (foundWords.value.length === words.value.length) {
|
||||||
|
if (timer) { clearInterval(timer); timer = null; }
|
||||||
|
// show small delay lalu naik level (jika masih ada)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (currLevel.value < levels.length - 1) {
|
||||||
|
currLevel.value++;
|
||||||
|
resetGame();
|
||||||
|
startTimer();
|
||||||
|
} else {
|
||||||
|
// last level finished: bisa reset ke awal atau tampilkan pesan
|
||||||
|
alert("🌟 Semua level selesai! Selamat :)");
|
||||||
|
// reset kembali ke level 0 atau tetap
|
||||||
|
currLevel.value = 0;
|
||||||
|
resetGame();
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- controls ---------- */
|
||||||
function resetGame() {
|
function resetGame() {
|
||||||
foundWords.value = [];
|
foundWords.value = [];
|
||||||
selectedCells.value = [];
|
selectedCells.value = [];
|
||||||
|
for (const k in wordPositions) delete wordPositions[k];
|
||||||
generateGrid();
|
generateGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// function goEntertainment(){
|
function goBack(){ window.history.back(); }
|
||||||
// window.history.back('/entertainment');
|
|
||||||
// }
|
|
||||||
|
|
||||||
function goBack(){
|
|
||||||
window.history.back('/entertainment/mini-games');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* ---------- init ---------- */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
generateGrid();
|
||||||
|
startTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* optional: kalau level berubah, restart timer dan grid */
|
||||||
|
watch(currLevel, () => {
|
||||||
resetGame();
|
resetGame();
|
||||||
|
startTimer();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@keyframes bounceGlow {
|
/* Pastikan ada aspect-square (Tailwind) — jika tidak, fallback: */
|
||||||
0%, 100% { box-shadow: 0 0 8px rgba(72, 255, 140, 0.6); transform: scale(1); }
|
.grid > * > * { aspect-ratio: 1 / 1; } /* fallback bila Tailwind aspect-square tidak tersedia */
|
||||||
50% { box-shadow: 0 0 20px rgba(72, 255, 140, 1); transform: scale(1.05); }
|
|
||||||
}
|
@keyframes bounceGlow { 0%,100%{box-shadow:0 0 8px rgba(72,255,140,.6);transform:scale(1);} 50%{box-shadow:0 0 20px rgba(72,255,140,1);transform:scale(1.05);} }
|
||||||
.animate-bounce-glow {
|
.animate-bounce-glow{animation:bounceGlow 1s infinite;}
|
||||||
animation: bounceGlow 1s infinite;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import MinigamePage from '@/pages/Minigame.vue'
|
|||||||
import FlipcardPage from '@/pages/Flipcard.vue'
|
import FlipcardPage from '@/pages/Flipcard.vue'
|
||||||
import WordspellsPage from '@/pages/Wordspells.vue'
|
import WordspellsPage from '@/pages/Wordspells.vue'
|
||||||
import PointhistoryPage from '@/pages/Pointhistory.vue'
|
import PointhistoryPage from '@/pages/Pointhistory.vue'
|
||||||
|
import itemPage from '@/components/itemPage.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', name: 'home', component: HomePage , meta:{requiresAuth:true}},
|
{ path: '/', name: 'home', component: HomePage , meta:{requiresAuth:true}},
|
||||||
@ -43,6 +44,7 @@ const routes = [
|
|||||||
{ path: '/entertainment/mini-games/', name:'mini-games', component:MinigamePage, props:true},
|
{ path: '/entertainment/mini-games/', name:'mini-games', component:MinigamePage, props:true},
|
||||||
{ path: '/entertainment/mini-games/flipcard/', name:'flipcard', component:FlipcardPage, props:true},
|
{ path: '/entertainment/mini-games/flipcard/', name:'flipcard', component:FlipcardPage, props:true},
|
||||||
{ path: '/entertainment/mini-games/word-spells/', name:'word-spells', component:WordspellsPage, props:true},
|
{ path: '/entertainment/mini-games/word-spells/', name:'word-spells', component:WordspellsPage, props:true},
|
||||||
|
{ path: '/items/', name:'items', component:itemPage, props:true},
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
8
src/stores/index.js
Normal file
8
src/stores/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createStore } from "vuex";
|
||||||
|
import items from "./items";
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
modules: {
|
||||||
|
items
|
||||||
|
}
|
||||||
|
});
|
||||||
35
src/stores/items.js
Normal file
35
src/stores/items.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
|
||||||
|
// DATA TEMPAT DISIMPAN
|
||||||
|
state: () => ({
|
||||||
|
items: []
|
||||||
|
}),
|
||||||
|
|
||||||
|
// CARA MENGUBAH STATE (HARUS SINKRON)
|
||||||
|
mutations: {
|
||||||
|
SET_ITEMS(state, payload) {
|
||||||
|
state.items = payload;
|
||||||
|
},
|
||||||
|
ADD_ITEM(state, payload) {
|
||||||
|
state.items.push(payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// TEMPAT PANGGIL API / FUNGSI ASYNC
|
||||||
|
actions: {
|
||||||
|
fetchItems({ commit }) {
|
||||||
|
// contoh data dari API
|
||||||
|
const data = [
|
||||||
|
{ id: 1, name: "Apel" },
|
||||||
|
{ id: 2, name: "Mangga" }
|
||||||
|
];
|
||||||
|
|
||||||
|
commit("SET_ITEMS", data); // <--- mutation dipanggil
|
||||||
|
},
|
||||||
|
|
||||||
|
createItem({ commit }, item) {
|
||||||
|
commit("ADD_ITEM", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user