1259 lines
44 KiB
Dart
1259 lines
44 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
import 'package:flame/game.dart';
|
|
import '../landing_page.dart';
|
|
import '../utils/level_manager.dart';
|
|
import '../utils/audio_manager.dart';
|
|
import 'package:permainan_kata_anak_sd/services/database_service.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
|
|
// Pindahkan enum ke level atas
|
|
enum WordDirection { horizontal, vertical, diagonal }
|
|
|
|
// Pindahkan class ke level atas
|
|
class PlacedWord {
|
|
final String word;
|
|
final int startRow;
|
|
final int startCol;
|
|
final WordDirection direction;
|
|
|
|
PlacedWord(
|
|
{required this.word,
|
|
required this.startRow,
|
|
required this.startCol,
|
|
required this.direction});
|
|
}
|
|
|
|
class Level3HinduBuddha extends StatefulWidget {
|
|
const Level3HinduBuddha({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
_Level3HinduBuddhaState createState() => _Level3HinduBuddhaState();
|
|
}
|
|
|
|
class _Level3HinduBuddhaState extends State<Level3HinduBuddha> {
|
|
// Definisikan gridSize sebagai konstanta kelas
|
|
static const int GRID_SIZE = 18;
|
|
static const int MAX_TIME = 300; // 5 menit dalam detik
|
|
|
|
late Timer timer;
|
|
int seconds = MAX_TIME;
|
|
int elapsedSeconds =
|
|
0; // Tambah variabel untuk menghitung waktu yang digunakan
|
|
int score = 0;
|
|
|
|
// Data kata dan pertanyaan terkait dengan urutan
|
|
final List<Map<String, dynamic>> orderedQuestions = [
|
|
{
|
|
'word': 'KEDIRI',
|
|
'question': 'Kerajaan Hindu yang yang terletak di tepi Sungai Brantas',
|
|
'image': 'assets/images_wilayah/wilayah_kediri.png',
|
|
'isFound': false,
|
|
'isUnlocked': true,
|
|
'isVisible': true,
|
|
},
|
|
{
|
|
'word': 'PANATARAN',
|
|
'question': 'Salah satu candi peninggalan dari kerajaan Kediri',
|
|
'image': 'assets/images_hindubuddha/peninggalan_kediri.jpg',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'CITARUM',
|
|
'question': 'Sungai letak kerajaan Tarumanegara berdiri',
|
|
'image': 'assets/images_wilayah/wilayah_tarumanegara.png',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'SOJOMERTO',
|
|
'question': 'Salah satu prasasti yang dimiliki kerajaan Kalingga',
|
|
'image': 'assets/images_hindubuddha/peninggalan_kalingga.png',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'TENGAH',
|
|
'question': 'Bagian Pulau Jawa mana Kerajaan Mataram berdiri',
|
|
'image': 'assets/images_wilayah/wilayah_kalingga.png',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
];
|
|
|
|
int currentQuestionIndex = 0;
|
|
|
|
late List<List<String>> grid;
|
|
String? lastFoundWord;
|
|
|
|
// Untuk seleksi kata
|
|
Offset? startDrag;
|
|
Offset? currentDrag;
|
|
List<Offset> selectedCells = [];
|
|
List<Offset> correctCells = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
startTimer();
|
|
initializeGame();
|
|
}
|
|
|
|
void initializeGame() {
|
|
grid = List.generate(GRID_SIZE, (_) => List.filled(GRID_SIZE, ''));
|
|
placeWords();
|
|
fillEmptySpaces();
|
|
}
|
|
|
|
void placeWords() {
|
|
// Definisikan kata-kata yang akan diacak
|
|
List<String> words = [
|
|
'KEDIRI',
|
|
'PANATARAN',
|
|
'CITARUM',
|
|
'SOJOMERTO',
|
|
'TENGAH',
|
|
];
|
|
|
|
// Urutkan kata dari yang terpanjang ke terpendek untuk optimasi penempatan
|
|
words.sort((a, b) => b.length.compareTo(a.length));
|
|
|
|
// Bersihkan grid
|
|
grid = List.generate(GRID_SIZE, (_) => List.filled(GRID_SIZE, ''));
|
|
|
|
// Definisikan zona aman yang lebih tersebar dan offset acak
|
|
final List<List<int>> safeZones = [
|
|
[2, 2],
|
|
[2, 15],
|
|
[10, 10],
|
|
[15, 2],
|
|
[15, 15],
|
|
[6, 14]
|
|
];
|
|
final random = Random();
|
|
|
|
for (int i = 0; i < words.length; i++) {
|
|
String word = words[i];
|
|
bool placed = false;
|
|
List<List<int>> availableZones = List.from(safeZones);
|
|
availableZones.shuffle(random);
|
|
|
|
for (var zone in availableZones) {
|
|
// Offset acak di sekitar zona aman
|
|
List<List<int>> offsets = [
|
|
[-2, -2],
|
|
[-2, 0],
|
|
[-2, 2],
|
|
[0, -2],
|
|
[0, 0],
|
|
[0, 2],
|
|
[2, -2],
|
|
[2, 0],
|
|
[2, 2]
|
|
];
|
|
offsets.shuffle(random);
|
|
|
|
for (var offset in offsets) {
|
|
int startRow = zone[0] + offset[0];
|
|
int startCol = zone[1] + offset[1];
|
|
List<WordDirection> directions = [];
|
|
if (startCol + word.length <= GRID_SIZE - 2)
|
|
directions.add(WordDirection.horizontal);
|
|
if (startRow + word.length <= GRID_SIZE - 2)
|
|
directions.add(WordDirection.vertical);
|
|
directions.shuffle(random);
|
|
|
|
for (var direction in directions) {
|
|
if (canPlaceWord(word, startRow, startCol, direction)) {
|
|
placeWordInGrid(PlacedWord(
|
|
word: word,
|
|
startRow: startRow,
|
|
startCol: startCol,
|
|
direction: direction,
|
|
));
|
|
placed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (placed) break;
|
|
}
|
|
if (placed) break;
|
|
}
|
|
// Fallback jika gagal
|
|
if (!placed) {
|
|
// Cari posisi random di grid
|
|
for (int attempt = 0; attempt < 100 && !placed; attempt++) {
|
|
int row = random.nextInt(GRID_SIZE - word.length);
|
|
int col = random.nextInt(GRID_SIZE - word.length);
|
|
List<WordDirection> directions = [
|
|
WordDirection.horizontal,
|
|
WordDirection.vertical
|
|
];
|
|
directions.shuffle(random);
|
|
for (var direction in directions) {
|
|
if (canPlaceWord(word, row, col, direction)) {
|
|
placeWordInGrid(PlacedWord(
|
|
word: word,
|
|
startRow: row,
|
|
startCol: col,
|
|
direction: direction,
|
|
));
|
|
placed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Jika masih tidak bisa, pakai posisi default
|
|
if (!placed) {
|
|
int fallbackRow = (i * 3) % (GRID_SIZE - word.length);
|
|
placeWordInGrid(PlacedWord(
|
|
word: word,
|
|
startRow: fallbackRow,
|
|
startCol: 1,
|
|
direction: WordDirection.horizontal,
|
|
));
|
|
}
|
|
}
|
|
|
|
fillEmptySpaces();
|
|
}
|
|
|
|
bool canPlaceWord(
|
|
String word, int startRow, int startCol, WordDirection direction) {
|
|
if (startRow < 0 || startCol < 0) return false;
|
|
|
|
int row = startRow;
|
|
int col = startCol;
|
|
|
|
// Cek apakah kata muat dalam grid dengan margin aman yang lebih besar
|
|
if (direction == WordDirection.horizontal) {
|
|
if (col + word.length + 2 > GRID_SIZE) return false;
|
|
} else if (direction == WordDirection.vertical) {
|
|
if (row + word.length + 2 > GRID_SIZE) return false;
|
|
}
|
|
|
|
// Cek area sekitar kata untuk memastikan tidak ada tumpang tindih (margin 2)
|
|
for (int i = -2; i <= word.length + 1; i++) {
|
|
for (int j = -2; j <= 2; j++) {
|
|
int checkRow =
|
|
direction == WordDirection.horizontal ? row + j : row + i;
|
|
int checkCol =
|
|
direction == WordDirection.horizontal ? col + i : col + j;
|
|
|
|
if (checkRow >= 0 &&
|
|
checkRow < GRID_SIZE &&
|
|
checkCol >= 0 &&
|
|
checkCol < GRID_SIZE) {
|
|
if (grid[checkRow][checkCol].isNotEmpty) return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void placeWordInGrid(PlacedWord word) {
|
|
int row = word.startRow;
|
|
int col = word.startCol;
|
|
|
|
for (int i = 0; i < word.word.length; i++) {
|
|
if (!isValidPosition(row, col)) break;
|
|
|
|
grid[row][col] = word.word[i];
|
|
|
|
switch (word.direction) {
|
|
case WordDirection.horizontal:
|
|
col++;
|
|
break;
|
|
case WordDirection.vertical:
|
|
row++;
|
|
break;
|
|
case WordDirection.diagonal:
|
|
break; // Tidak menggunakan diagonal
|
|
}
|
|
}
|
|
}
|
|
|
|
void updateSelectedCells(Offset currentPosition) {
|
|
if (startDrag == null) return;
|
|
|
|
final RenderBox box = context.findRenderObject() as RenderBox;
|
|
final Offset localStart = box.globalToLocal(startDrag!);
|
|
final Offset localCurrent = box.globalToLocal(currentPosition);
|
|
|
|
// Dapatkan ukuran dan posisi grid
|
|
final gridStart =
|
|
Offset(16.0, 120.0); // Sesuaikan dengan padding dan posisi grid
|
|
final gridSize = box.size.width - 32.0; // Kurangi padding kiri dan kanan
|
|
final cellSize = gridSize / GRID_SIZE;
|
|
|
|
// Hitung posisi sel dengan mempertimbangkan offset grid
|
|
int startRow =
|
|
((localStart.dy - gridStart.dy) ~/ cellSize).clamp(0, GRID_SIZE - 1);
|
|
int startCol =
|
|
((localStart.dx - gridStart.dx) ~/ cellSize).clamp(0, GRID_SIZE - 1);
|
|
int currentRow =
|
|
((localCurrent.dy - gridStart.dy) ~/ cellSize).clamp(0, GRID_SIZE - 1);
|
|
int currentCol =
|
|
((localCurrent.dx - gridStart.dx) ~/ cellSize).clamp(0, GRID_SIZE - 1);
|
|
|
|
setState(() {
|
|
selectedCells =
|
|
calculateSelectedCells(startRow, startCol, currentRow, currentCol);
|
|
});
|
|
}
|
|
|
|
bool isValidPosition(int row, int col) {
|
|
return row >= 0 && row < GRID_SIZE && col >= 0 && col < GRID_SIZE;
|
|
}
|
|
|
|
List<Offset> calculateSelectedCells(
|
|
int startRow, int startCol, int endRow, int endCol) {
|
|
List<Offset> cells = [];
|
|
|
|
// Hitung arah
|
|
final int rowStep = startRow == endRow ? 0 : (endRow - startRow).sign;
|
|
final int colStep = startCol == endCol ? 0 : (endCol - startCol).sign;
|
|
|
|
// Hitung jumlah sel
|
|
final int steps = max((endRow - startRow).abs(), (endCol - startCol).abs());
|
|
|
|
// Tambahkan sel ke seleksi
|
|
for (int i = 0; i <= steps; i++) {
|
|
final int row = startRow + (i * rowStep);
|
|
final int col = startCol + (i * colStep);
|
|
if (isValidPosition(row, col)) {
|
|
cells.add(Offset(col.toDouble(), row.toDouble()));
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
void fillEmptySpaces() {
|
|
final random = Random();
|
|
const letters = 'AEIMNRSTUW';
|
|
|
|
for (int i = 0; i < GRID_SIZE; i++) {
|
|
for (int j = 0; j < GRID_SIZE; j++) {
|
|
if (grid[i][j].isEmpty) {
|
|
grid[i][j] = letters[random.nextInt(letters.length)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkSelection() {
|
|
if (selectedCells.length < 2) return;
|
|
|
|
String selectedWord = '';
|
|
List<Offset> orderedCells = List.from(selectedCells);
|
|
|
|
// Urutkan sel berdasarkan arah seleksi
|
|
if (orderedCells.first.dx > orderedCells.last.dx) {
|
|
// Jika seleksi dari kanan ke kiri, balik urutan sel
|
|
orderedCells = orderedCells.reversed.toList();
|
|
}
|
|
|
|
for (var cell in orderedCells) {
|
|
int row = cell.dy.toInt();
|
|
int col = cell.dx.toInt();
|
|
selectedWord += grid[row][col];
|
|
}
|
|
|
|
// Cek kata normal dan terbalik
|
|
if ((selectedWord == orderedQuestions[currentQuestionIndex]['word'] ||
|
|
selectedWord ==
|
|
orderedQuestions[currentQuestionIndex]['word']
|
|
.split('')
|
|
.reversed
|
|
.join()) &&
|
|
!orderedQuestions[currentQuestionIndex]['isFound']) {
|
|
setState(() {
|
|
orderedQuestions[currentQuestionIndex]['isFound'] = true;
|
|
correctCells.addAll(selectedCells);
|
|
score += 20;
|
|
|
|
// Unlock pertanyaan berikutnya jika ada
|
|
if (currentQuestionIndex < orderedQuestions.length - 1) {
|
|
currentQuestionIndex++;
|
|
orderedQuestions[currentQuestionIndex]['isUnlocked'] = true;
|
|
orderedQuestions[currentQuestionIndex]['isVisible'] = true;
|
|
}
|
|
|
|
showCorrectAnswerDialog();
|
|
|
|
// Cek apakah semua pertanyaan sudah ditemukan
|
|
if (orderedQuestions.every((q) => q['isFound'])) {
|
|
showCompletionDialog();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void showCorrectAnswerDialog() {
|
|
AudioManager.successsound();
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => Dialog(
|
|
backgroundColor: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
height: 400,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/dialog_complete.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'Anda menemukan kata "${orderedQuestions[currentQuestionIndex - 1]['word']}"',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 14,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
if (currentQuestionIndex < orderedQuestions.length)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 10),
|
|
child: Text(
|
|
'Pertanyaan selanjutnya telah dibuka',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 14,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
const SizedBox(height: 30),
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pop(context);
|
|
},
|
|
child: Container(
|
|
width: 150,
|
|
height: 50,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'OK',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 16,
|
|
color: Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void showCompletionDialog() async {
|
|
AudioManager.winsound();
|
|
timer.cancel();
|
|
// Simpan status level 1 selesai
|
|
LevelManager.setLevelCompleted(3, true, LevelManager.TYPE_HINDU_BUDDHA);
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user != null) {
|
|
await DatabaseService().saveScore(
|
|
userId: user.uid,
|
|
score: score,
|
|
formattedTime: formatTime(elapsedSeconds),
|
|
levelName: "Level 3",
|
|
category: "Hindu Buddha",
|
|
);
|
|
}
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => Dialog(
|
|
backgroundColor: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
height: 400, // Sesuaikan tinggi background
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/dialog_complete.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
// Isi konten berada tepat di tengah gambar
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Anda telah menyelesaikan semua jawaban dengan benar.',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 14,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Total Skor: $score',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 12,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Total Waktu: ${formatTime(elapsedSeconds)}',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 12,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 25),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const Level3HinduBuddha(),
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/restart.png'),
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 20),
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => LandingPage(
|
|
onStart: () {},
|
|
onStats: () {},
|
|
),
|
|
),
|
|
(route) => false,
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/stats.png'),
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return WillPopScope(
|
|
onWillPop: () async {
|
|
AudioManager.playClickSound();
|
|
_showMenuDialog(context);
|
|
return false;
|
|
},
|
|
child: Scaffold(
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/images/background_permainan.png'),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
|
|
// Header (Timer, Menu, Score)
|
|
Row(
|
|
children: [
|
|
// Kiri (Timer)
|
|
Flexible(
|
|
flex: 2,
|
|
child: Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 22.0),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 5),
|
|
width: 120,
|
|
height: 45,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/grey_button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'${formatTime(seconds)}',
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
color: Colors.black,
|
|
fontFamily: 'Bestime',
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Tengah (Menu Icon)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
_showMenuDialog(context);
|
|
},
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: Image.asset(
|
|
'assets/icons/menu.png',
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Kanan (Skor)
|
|
Flexible(
|
|
flex: 2,
|
|
child: Align(
|
|
alignment: Alignment.centerRight,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: 22.0),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 5),
|
|
width: 120,
|
|
height: 45,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/grey_button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'$score',
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
color: Colors.black,
|
|
fontFamily: 'Bestime',
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 15),
|
|
|
|
// Grid Area
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
physics:
|
|
const NeverScrollableScrollPhysics(), // Prevent scrolling while selecting
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
// Word Search Grid
|
|
AspectRatio(
|
|
aspectRatio: 1, // Make grid square
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: GestureDetector(
|
|
onPanStart: (details) {
|
|
setState(() {
|
|
startDrag = details.globalPosition;
|
|
selectedCells.clear();
|
|
});
|
|
},
|
|
onPanUpdate: (details) {
|
|
updateSelectedCells(details.globalPosition);
|
|
},
|
|
onPanEnd: (details) {
|
|
checkSelection();
|
|
setState(() {
|
|
startDrag = null;
|
|
selectedCells.clear();
|
|
});
|
|
},
|
|
child: GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.all(16.0),
|
|
gridDelegate:
|
|
SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: GRID_SIZE,
|
|
childAspectRatio: 1,
|
|
crossAxisSpacing: 1,
|
|
mainAxisSpacing: 1,
|
|
),
|
|
itemCount: GRID_SIZE * GRID_SIZE,
|
|
itemBuilder: (context, index) {
|
|
final row = index ~/ GRID_SIZE;
|
|
final col = index % GRID_SIZE;
|
|
final isSelected = selectedCells.contains(
|
|
Offset(col.toDouble(), row.toDouble()));
|
|
final isCorrect = correctCells.contains(
|
|
Offset(col.toDouble(), row.toDouble()));
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.grey.withOpacity(0.5)),
|
|
color: isCorrect
|
|
? Colors.green.withOpacity(0.3)
|
|
: isSelected
|
|
? Colors.blue.withOpacity(0.3)
|
|
: Colors.white,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
grid[row][col],
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
fontFamily: 'Bestime',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Gambar peninggalan
|
|
Container(
|
|
height: 200,
|
|
width: double.infinity,
|
|
margin: const EdgeInsets.symmetric(vertical: 16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.asset(
|
|
orderedQuestions[currentQuestionIndex]['image'],
|
|
fit: BoxFit.fill,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
color: Colors.grey[200],
|
|
child: const Center(
|
|
child: Icon(
|
|
Icons.image_not_supported,
|
|
size: 60,
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
// Pertanyaan yang sedang aktif
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Stack(
|
|
children: [
|
|
// Outline text
|
|
Text(
|
|
orderedQuestions[currentQuestionIndex]
|
|
['question'],
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 15,
|
|
foreground: Paint()
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2
|
|
..color =
|
|
const Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
// Fill text
|
|
Text(
|
|
orderedQuestions[currentQuestionIndex]
|
|
['question'],
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 15,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String formatTime(int seconds) {
|
|
int minutes = seconds ~/ 60;
|
|
int remainingSeconds = seconds % 60;
|
|
return '${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
void startTimer() {
|
|
timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
|
setState(() {
|
|
if (seconds > 0) {
|
|
seconds--;
|
|
elapsedSeconds++; // Increment waktu yang telah berlalu
|
|
} else {
|
|
timer.cancel();
|
|
showTimeUpDialog();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void showTimeUpDialog() async {
|
|
AudioManager.losesound();
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user != null) {
|
|
await DatabaseService().saveScore(
|
|
userId: user.uid,
|
|
score: score,
|
|
formattedTime: formatTime(elapsedSeconds),
|
|
levelName: "Level 3",
|
|
category: "Hindu Buddha",
|
|
);
|
|
}
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => Dialog(
|
|
backgroundColor: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
height: 400, // Sesuaikan dengan tinggi gambar background
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/timeout.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Waktu permainan telah habis.',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 14,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Total Skor: $score',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 12,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Total Waktu: ${formatTime(elapsedSeconds)}',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 12,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 30),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const Level3HinduBuddha(),
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/restart.png'),
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 20),
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => LandingPage(
|
|
onStart: () {},
|
|
onStats: () {},
|
|
),
|
|
),
|
|
(route) => false,
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/stats.png'),
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
timer.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _showMenuDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context) {
|
|
return Dialog(
|
|
backgroundColor: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
height: 400,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/cover_menu.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Button Lanjut
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pop(context);
|
|
},
|
|
child: Container(
|
|
width: 170,
|
|
height: 50,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/grey_button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'Lanjut',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 16,
|
|
color: Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 18),
|
|
// Button Restart
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => const Level3HinduBuddha(),
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 170,
|
|
height: 50,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/grey_button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'Restart',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 16,
|
|
color: Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 18),
|
|
// Button Keluar
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => LandingPage(
|
|
onStart: () {},
|
|
onStats: () {},
|
|
),
|
|
),
|
|
(route) => false,
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 170,
|
|
height: 50,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/grey_button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'Keluar',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 16,
|
|
color: Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _showQuestionCountDialog() {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => Dialog(
|
|
backgroundColor: Colors.transparent,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
height: 300,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/covertext.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'Level ini memiliki total ${orderedQuestions.length} pertanyaan.',
|
|
style: const TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 14,
|
|
color: Color.fromARGB(255, 182, 134, 86),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 30),
|
|
GestureDetector(
|
|
onTap: () {
|
|
AudioManager.playClickSound();
|
|
Navigator.pop(context);
|
|
},
|
|
child: Container(
|
|
width: 150,
|
|
height: 50,
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/icons/button.png'),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'Mulai',
|
|
style: TextStyle(
|
|
fontFamily: 'Bestime',
|
|
fontSize: 16,
|
|
color: Color.fromARGB(255, 65, 44, 23),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Game level 3
|
|
class GameLevel3 extends FlameGame {
|
|
@override
|
|
Color backgroundColor() => Colors.white;
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
// Bisa ditambahkan komponen lain jika diperlukan
|
|
}
|
|
}
|