632 lines
19 KiB
Dart
632 lines
19 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';
|
|
|
|
// 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 = 17;
|
|
|
|
late Timer timer;
|
|
int seconds = 0;
|
|
int score = 0;
|
|
|
|
// Data kata dan pertanyaan terkait dengan urutan
|
|
final List<Map<String, dynamic>> orderedQuestions = [
|
|
{
|
|
'word': 'KUTAI',
|
|
'question':
|
|
'Kerajaan Hindu tertua di Indonesia yang terletak di Kalimantan Timur',
|
|
'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',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'SRIWIJAYA',
|
|
'question': 'Kerajaan maritim Buddha terbesar yang berpusat di Palembang',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'MATARAM',
|
|
'question': 'Kerajaan yang membangun Candi Borobudur',
|
|
'isFound': false,
|
|
'isUnlocked': false,
|
|
'isVisible': false,
|
|
},
|
|
{
|
|
'word': 'MAJAPAHIT',
|
|
'question':
|
|
'Kerajaan Hindu terbesar di Nusantara yang dipimpin oleh Hayam Wuruk',
|
|
'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 = [
|
|
'KUTAI',
|
|
'TARUMANEGARA',
|
|
'SRIWIJAYA',
|
|
'MATARAM',
|
|
'MAJAPAHIT'
|
|
];
|
|
|
|
// Bersihkan grid
|
|
grid = List.generate(GRID_SIZE, (_) => List.filled(GRID_SIZE, ''));
|
|
|
|
// Acak urutan kata
|
|
final random = Random();
|
|
words.shuffle(random);
|
|
|
|
// Urutkan kata dari yang terpanjang ke terpendek
|
|
words.sort((a, b) => b.length.compareTo(a.length));
|
|
|
|
// Definisikan zona aman untuk setiap kata
|
|
final List<PlacedWord> placements = [];
|
|
int verticalPosition = 1; // Mulai dari baris 1
|
|
int horizontalPosition = 1; // Mulai dari kolom 1
|
|
|
|
// Tempatkan kata-kata dengan zona aman
|
|
for (String word in words) {
|
|
// Pilih arah penempatan secara acak (horizontal/vertical)
|
|
bool isHorizontal = random.nextBool();
|
|
|
|
if (word.length > GRID_SIZE - 2) {
|
|
// Kata terlalu panjang, selalu horizontal
|
|
placements.add(PlacedWord(
|
|
word: word,
|
|
startRow: verticalPosition,
|
|
startCol: 1,
|
|
direction: WordDirection.horizontal,
|
|
));
|
|
verticalPosition += 3;
|
|
} else if (isHorizontal &&
|
|
horizontalPosition + word.length < GRID_SIZE - 1) {
|
|
// Coba horizontal jika muat
|
|
placements.add(PlacedWord(
|
|
word: word,
|
|
startRow: verticalPosition,
|
|
startCol: horizontalPosition,
|
|
direction: WordDirection.horizontal,
|
|
));
|
|
verticalPosition += 3;
|
|
horizontalPosition = 1;
|
|
} else if (verticalPosition + word.length < GRID_SIZE - 1) {
|
|
// Coba vertikal jika muat
|
|
placements.add(PlacedWord(
|
|
word: word,
|
|
startRow: verticalPosition,
|
|
startCol: horizontalPosition,
|
|
direction: WordDirection.vertical,
|
|
));
|
|
horizontalPosition += 3;
|
|
if (horizontalPosition > GRID_SIZE - word.length) {
|
|
horizontalPosition = 1;
|
|
verticalPosition += 2;
|
|
}
|
|
} else {
|
|
// Fallback ke horizontal di baris baru
|
|
verticalPosition += 3;
|
|
horizontalPosition = 1;
|
|
placements.add(PlacedWord(
|
|
word: word,
|
|
startRow: verticalPosition,
|
|
startCol: horizontalPosition,
|
|
direction: WordDirection.horizontal,
|
|
));
|
|
}
|
|
}
|
|
|
|
// Tempatkan kata-kata ke dalam grid
|
|
for (var placement in placements) {
|
|
placeWordInGrid(placement);
|
|
}
|
|
|
|
fillEmptySpaces();
|
|
}
|
|
|
|
void placeWordInGrid(PlacedWord word) {
|
|
int row = word.startRow;
|
|
int col = word.startCol;
|
|
|
|
for (int i = 0; i < word.word.length; i++) {
|
|
if (row >= GRID_SIZE || col >= GRID_SIZE) break;
|
|
|
|
switch (word.direction) {
|
|
case WordDirection.horizontal:
|
|
if (col + i < GRID_SIZE) {
|
|
grid[row][col + i] = word.word[i];
|
|
}
|
|
break;
|
|
case WordDirection.vertical:
|
|
if (row + i < GRID_SIZE) {
|
|
grid[row + i][col] = word.word[i];
|
|
}
|
|
break;
|
|
case WordDirection.diagonal:
|
|
if (row + i < GRID_SIZE && col + i < GRID_SIZE) {
|
|
grid[row + i][col + i] = word.word[i];
|
|
}
|
|
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<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 += 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 3 selesai
|
|
LevelManager.setLevelCompleted(3, 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 Level3HinduBuddha(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
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: () {
|
|
// Implementasi untuk level berikutnya
|
|
// Navigator.push(context, MaterialPageRoute(builder: (context) => NextLevelPage()));
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
|
|
// Timer, Menu, dan Score dalam satu baris
|
|
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),
|
|
|
|
// Word Search Grid dengan container yang jelas
|
|
Expanded(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: GestureDetector(
|
|
onPanStart: (details) {
|
|
final RenderBox box =
|
|
context.findRenderObject() as RenderBox;
|
|
setState(() {
|
|
startDrag = box.globalToLocal(details.globalPosition);
|
|
selectedCells.clear();
|
|
});
|
|
},
|
|
onPanUpdate: (details) {
|
|
updateSelectedCells(details.globalPosition);
|
|
},
|
|
onPanEnd: (details) {
|
|
checkSelection();
|
|
setState(() {
|
|
startDrag = null;
|
|
selectedCells.clear();
|
|
});
|
|
},
|
|
child: GridView.builder(
|
|
padding: const EdgeInsets.all(8),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: GRID_SIZE,
|
|
childAspectRatio: 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: 20,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// 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) => Level3HinduBuddha(),
|
|
),
|
|
);
|
|
},
|
|
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 GameLevel3 extends FlameGame {
|
|
@override
|
|
Color backgroundColor() => Colors.white;
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
// Bisa ditambahkan komponen lain jika diperlukan
|
|
}
|
|
}
|