import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:math'; import 'package:flame/game.dart'; import '../landing_page.dart'; import 'level3_hindubuddha.dart'; import '../utils/level_manager.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 Level2HinduBuddha extends StatefulWidget { const Level2HinduBuddha({Key? key}) : super(key: key); @override _Level2HinduBuddhaState createState() => _Level2HinduBuddhaState(); } class _Level2HinduBuddhaState extends State { // Definisikan gridSize sebagai konstanta kelas static const int GRID_SIZE = 15; late Timer timer; int seconds = 0; int score = 0; // Data kata dan pertanyaan terkait dengan urutan final List> orderedQuestions = [ { 'word': 'KUTAI', 'question': 'Kerajaan Hindu tertua di Indonesia yang terletak di Kalimantan Timur', 'image': 'assets/images_hindubuddha/peninggalan_kutai.jpg', 'isFound': false, 'isUnlocked': true, // Pertanyaan pertama sudah unlocked 'isVisible': true, // Pertanyaan pertama sudah visible }, { 'word': 'TARUMANEGARA', 'question': 'Kerajaan Hindu tertua di Pulau Jawa yang terkenal dengan prasasti Tugu', 'image': 'assets/images_hindubuddha/peninggalan_tarumanegara.jpg', 'isFound': false, 'isUnlocked': false, 'isVisible': false, }, { 'word': 'SRIWIJAYA', 'question': 'Kerajaan maritim Buddha terbesar yang berpusat di Palembang', 'image': 'assets/images_hindubuddha/peninggalan_sriwijaya.jpg', 'isFound': false, 'isUnlocked': false, 'isVisible': false, }, { 'word': 'MATARAM', 'question': 'Kerajaan yang membangun Candi Borobudur', 'image': 'assets/images_hindubuddha/peninggalan_mataram.jpg', 'isFound': false, 'isUnlocked': false, 'isVisible': false, }, { 'word': 'MAJAPAHIT', 'question': 'Kerajaan Hindu terbesar di Nusantara yang dipimpin oleh Hayam Wuruk', 'image': 'assets/images_hindubuddha/peninggalan_majapahit.jpg', 'isFound': false, 'isUnlocked': false, 'isVisible': false, } ]; int currentQuestionIndex = 0; late List> grid; String? lastFoundWord; // Untuk seleksi kata Offset? startDrag; Offset? currentDrag; List selectedCells = []; List 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 words = [ 'KUTAI', 'TARUMANEGARA', 'SRIWIJAYA', 'MATARAM', 'MAJAPAHIT' ]; // Bersihkan grid grid = List.generate(GRID_SIZE, (_) => List.filled(GRID_SIZE, '')); // Posisi tetap untuk kata-kata horizontal (baris) final positions = [2, 4, 6, 8]; // Jarak aman antar kata // Acak urutan kata-kata kecuali TARUMANEGARA final random = Random(); words.remove('TARUMANEGARA'); // Hapus sementara TARUMANEGARA for (int i = 0; i < 5; i++) { words.shuffle(random); } // Tempatkan TARUMANEGARA secara vertikal di awal placeWordInGrid(PlacedWord( word: 'TARUMANEGARA', startRow: 0, // Mulai dari baris 0 startCol: 0, // Mulai dari kolom 0 direction: WordDirection.vertical, )); // Tempatkan kata-kata lain secara horizontal for (int i = 0; i < words.length; i++) { String word = words[i]; int row = positions[i]; // Pastikan kata tidak melebihi batas grid if (word.length > GRID_SIZE - 2) { // Jika terlalu panjang, mulai dari kolom 1 placeWordInGrid(PlacedWord( word: word, startRow: row, startCol: 1, direction: WordDirection.horizontal, )); } else { // Jika cukup ruang, acak posisi kolom awal (hindari kolom 0) int maxStartCol = GRID_SIZE - word.length - 1; int startCol = random.nextInt(maxStartCol) + 2; // Mulai dari kolom 2 placeWordInGrid(PlacedWord( word: word, startRow: row, startCol: startCol, 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; for (int i = 0; i < word.length; i++) { // Cek batas grid if (row >= GRID_SIZE || col >= GRID_SIZE) return false; // Cek apakah sel sudah terisi dengan huruf berbeda if (grid[row][col].isNotEmpty && grid[row][col] != word[i]) { return false; } // Pindah ke sel berikutnya sesuai arah switch (direction) { case WordDirection.horizontal: col++; break; case WordDirection.vertical: row++; break; case WordDirection.diagonal: row++; col++; break; } } 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: row++; col++; break; } } } 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 calculateSelectedCells( int startRow, int startCol, int endRow, int endCol) { List 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 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 += 10; // 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() { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text('Jawaban Benar!'), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Anda menemukan kata "${orderedQuestions[currentQuestionIndex - 1]['word']}"'), if (currentQuestionIndex < orderedQuestions.length) Text('\nPertanyaan selanjutnya telah dibuka'), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('OK'), ), ], ), ); } void showCompletionDialog() { // Simpan status level 1 selesai LevelManager.setLevelCompleted(1, true); showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text('Selamat!'), content: const Text('Anda telah menyelesaikan semua jawaban dengan benar.'), actions: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const Level2HinduBuddha(), ), ); }, ), IconButton( icon: const Icon(Icons.menu), onPressed: () { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (context) => LandingPage( onStart: () {}, onStats: () {}, ), ), (route) => false, ); }, ), IconButton( icon: const Icon(Icons.arrow_forward), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const Level3HinduBuddha(), ), ); }, ), ], ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Column( children: [ const SizedBox(height: 40), // Header (Timer, Menu, Score) Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( 'Waktu: ${formatTime(seconds)}', style: const TextStyle(fontSize: 20, color: Colors.black), ), IconButton( icon: const Icon(Icons.menu), onPressed: () { _showMenuDialog(context); }, ), Text( 'Skor: $score', style: const TextStyle(fontSize: 20, color: Colors.black), ), ], ), const SizedBox(height: 20), // 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( border: Border.all(color: Colors.grey), 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: FittedBox( fit: BoxFit.contain, child: Padding( padding: const EdgeInsets.all(2.0), child: Text( grid[row][col], style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ), ); }, ), ), ), ), // Gambar peninggalan Container( height: 200, width: double.infinity, margin: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( orderedQuestions[currentQuestionIndex]['image'], fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: Colors.grey[200], child: const Center( child: Icon( Icons.image_not_supported, size: 50, color: Colors.grey, ), ), ); }, ), ), ), // Pertanyaan yang sedang aktif Container( padding: const EdgeInsets.all(16), child: Text( orderedQuestions[currentQuestionIndex]['question'], style: const TextStyle( fontSize: 16, color: Colors.black, ), 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(() { seconds++; }); }); } @override void dispose() { timer.cancel(); super.dispose(); } void _showMenuDialog(BuildContext context) { showDialog( context: context, barrierDismissible: false, // User harus memilih salah satu opsi builder: (BuildContext context) { return AlertDialog( backgroundColor: Colors.black.withOpacity(0.9), title: const Text( 'Menu Permainan', style: TextStyle(color: Colors.white), textAlign: TextAlign.center, ), content: Column( mainAxisSize: MainAxisSize.min, children: [ // Button Lanjut ElevatedButton( onPressed: () { Navigator.pop(context); // Kembali ke permainan }, style: ElevatedButton.styleFrom( minimumSize: const Size(200, 50), ), child: const Text('Lanjut'), ), const SizedBox(height: 10), // Button Restart ElevatedButton( onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => Level2HinduBuddha(), ), ); }, style: ElevatedButton.styleFrom( minimumSize: const Size(200, 50), ), child: const Text('Restart'), ), const SizedBox(height: 10), // Button Keluar ElevatedButton( onPressed: () { // Kembali ke landing page Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (context) => LandingPage( onStart: () {}, onStats: () {}, ), ), (route) => false, // Hapus semua halaman sebelumnya ); }, style: ElevatedButton.styleFrom( minimumSize: const Size(200, 50), ), child: const Text('Keluar'), ), ], ), ); }, ); } } // Game level 1 class GameLevel2 extends FlameGame { @override Color backgroundColor() => Colors.white; @override Future onLoad() async { // Bisa ditambahkan komponen lain jika diperlukan } }