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-qrcode-reader": "^5.7.3",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-lottie": "^3.3.1"
|
||||
"vue3-lottie": "^3.3.1",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
@ -13099,6 +13100,18 @@
|
||||
"integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==",
|
||||
"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": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
"vue": "^3.2.13",
|
||||
"vue-qrcode-reader": "^5.7.3",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-lottie": "^3.3.1"
|
||||
"vue3-lottie": "^3.3.1",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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 { createPinia } from 'pinia'
|
||||
import Vue3Lottie from 'vue3-lottie'
|
||||
import store from '@/stores'
|
||||
|
||||
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>
|
||||
<!-- 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"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
<p class="text-gray-500 mt-2">Temukan kata kata positif yang ada di bawah ini 📖</p>
|
||||
</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">
|
||||
<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)"
|
||||
>
|
||||
<div ref="gridRef" class="grid gap-1" :style="gridStyle">
|
||||
<template v-for="(row, r) in grid" :key="r">
|
||||
<div v-for="(letter, c) in row" :key="`${r}-${c}`"
|
||||
@mousedown.prevent="startSelect(r,c)"
|
||||
@mouseenter="dragSelect(r,c)"
|
||||
@mouseup="endSelect"
|
||||
@touchstart.prevent="startSelect(r,c)"
|
||||
@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 aspect-square"
|
||||
:class="cellClass(r,c)">
|
||||
{{ 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 v-for="w in words" :key="w" class="px-3 py-1 rounded-full text-sm font-semibold shadow transition-all duration-200"
|
||||
: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'">
|
||||
{{ w }}
|
||||
</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>
|
||||
<p class="mt-3 text-xs text-green-700 italic text-center">Temukan semua kata positif 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"
|
||||
>
|
||||
<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"
|
||||
};
|
||||
name: "Find-words",
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, onMounted, computed, watch } from "vue";
|
||||
|
||||
const size = 10;
|
||||
/* ---------- data kata ---------- */
|
||||
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"
|
||||
"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([]);
|
||||
/* ---------- 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 words = ref([]);
|
||||
const foundWords = ref([]);
|
||||
const selectedCells = ref([]);
|
||||
const isSelecting = ref(false);
|
||||
const wordPositions = {};
|
||||
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() {
|
||||
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;
|
||||
const maxAttempts = 8;
|
||||
const chosenWords = pickWordsForLevel();
|
||||
const desiredCount = Math.min(chosenWords.length, levels[currLevel.value].count);
|
||||
|
||||
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));
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
// reset wordPositions
|
||||
for (const k in wordPositions) delete wordPositions[k];
|
||||
|
||||
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 g = Array.from({ length: size.value }, () => Array(size.value).fill(""));
|
||||
// coba tempatkan kata panjang dulu
|
||||
const wordsToPlace = chosenWords.slice(0, desiredCount).sort((a,b) => b.length - a.length);
|
||||
|
||||
let allPlaced = true;
|
||||
for (const w of wordsToPlace) {
|
||||
const ok = placeWordWithIntersection(g, w);
|
||||
if (!ok) { allPlaced = false; break; }
|
||||
}
|
||||
});
|
||||
|
||||
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)];
|
||||
if (allPlaced) {
|
||||
fillEmpty(g);
|
||||
grid.value = g;
|
||||
// words.value = wordsToPlace;
|
||||
words.value = Object.keys(wordPositions);
|
||||
return true;
|
||||
}
|
||||
// else ulangi generate
|
||||
}
|
||||
|
||||
grid.value = g;
|
||||
// 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) {
|
||||
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)
|
||||
const sel = selectedCells.value.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";
|
||||
if (isSelected) return "bg-yellow-200 text-yellow-900 scale-105";
|
||||
return "bg-white text-green-700 hover:bg-green-100 hover:scale-105";
|
||||
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 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 startSelect(r,c){ isSelecting.value = true; selectedCells.value = [{r,c}]; }
|
||||
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}); } }
|
||||
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); }
|
||||
|
||||
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);
|
||||
}
|
||||
/* endSelect: validasi arah lalu cek kata */
|
||||
function endSelect(){
|
||||
if(!isSelecting.value) return;
|
||||
const cells = selectedCells.value;
|
||||
if (cells.length < 2) { selectedCells.value = []; isSelecting.value = false; return; }
|
||||
|
||||
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);
|
||||
}
|
||||
const dx = Math.sign(cells[1].r - cells[0].r);
|
||||
const dy = Math.sign(cells[1].c - cells[0].c);
|
||||
|
||||
const valid = cells.every((cell, idx) => {
|
||||
if (idx === 0) return true;
|
||||
return cell.r === (cells[0].r + dx * idx) && cell.c === (cells[0].c + dy * idx);
|
||||
});
|
||||
|
||||
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 = [];
|
||||
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() {
|
||||
foundWords.value = [];
|
||||
selectedCells.value = [];
|
||||
for (const k in wordPositions) delete wordPositions[k];
|
||||
generateGrid();
|
||||
}
|
||||
|
||||
// function goEntertainment(){
|
||||
// window.history.back('/entertainment');
|
||||
// }
|
||||
|
||||
function goBack(){
|
||||
window.history.back('/entertainment/mini-games');
|
||||
}
|
||||
function goBack(){ window.history.back(); }
|
||||
|
||||
/* ---------- init ---------- */
|
||||
onMounted(() => {
|
||||
generateGrid();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
/* optional: kalau level berubah, restart timer dan grid */
|
||||
watch(currLevel, () => {
|
||||
resetGame();
|
||||
startTimer();
|
||||
});
|
||||
</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;
|
||||
}
|
||||
/* Pastikan ada aspect-square (Tailwind) — jika tidak, fallback: */
|
||||
.grid > * > * { aspect-ratio: 1 / 1; } /* fallback bila Tailwind aspect-square tidak tersedia */
|
||||
|
||||
@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{animation:bounceGlow 1s infinite;}
|
||||
</style>
|
||||
|
||||
@ -17,6 +17,7 @@ import MinigamePage from '@/pages/Minigame.vue'
|
||||
import FlipcardPage from '@/pages/Flipcard.vue'
|
||||
import WordspellsPage from '@/pages/Wordspells.vue'
|
||||
import PointhistoryPage from '@/pages/Pointhistory.vue'
|
||||
import itemPage from '@/components/itemPage.vue'
|
||||
|
||||
const routes = [
|
||||
{ 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/flipcard/', name:'flipcard', component:FlipcardPage, 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({
|
||||
|
||||
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