236 lines
6.6 KiB
Dart
236 lines
6.6 KiB
Dart
// packages/feature_game_cardflip/lib/controllers/cardflip_controller.dart
|
|
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
import '../models/cardflip_models.dart';
|
|
|
|
class CardFlipController with ChangeNotifier {
|
|
late CardFlipDifficulty difficulty;
|
|
late String userId;
|
|
late String? userName;
|
|
|
|
List<CardItem> _cards = [];
|
|
List<CardItem> get cards => _cards;
|
|
|
|
CardItem? _firstFlippedCard;
|
|
|
|
bool _isProcessing = false;
|
|
bool get isProcessing => _isProcessing;
|
|
|
|
bool _isGameCompleted = false;
|
|
bool get isGameCompleted => _isGameCompleted;
|
|
|
|
bool _isTimeOut = false;
|
|
bool get isTimeOut => _isTimeOut;
|
|
|
|
int _flipCount = 0;
|
|
int get flipCount => _flipCount;
|
|
|
|
Timer? _timer;
|
|
int _secondsElapsed = 0;
|
|
int _remainingTime = 0;
|
|
int get remainingTime => _remainingTime;
|
|
|
|
bool _isGameStarted = false;
|
|
bool get isGameStarted => _isGameStarted;
|
|
|
|
int get matchedPairsCount => _cards.where((c) => c.isMatched).length ~/ 2;
|
|
int get totalPairsCount => _cards.length ~/ 2;
|
|
|
|
void setUserInfo(String userId, String? userName) {
|
|
this.userId = userId;
|
|
this.userName = userName;
|
|
}
|
|
|
|
void startNewGame(CardFlipDifficulty level) {
|
|
difficulty = level;
|
|
_flipCount = 0;
|
|
_secondsElapsed = 0;
|
|
_remainingTime = level.timeLimitSeconds;
|
|
_isGameCompleted = false;
|
|
_isTimeOut = false;
|
|
_isProcessing = false;
|
|
_isGameStarted = false;
|
|
_firstFlippedCard = null;
|
|
|
|
_generateCards();
|
|
notifyListeners();
|
|
}
|
|
|
|
void restartGame() {
|
|
startNewGame(difficulty);
|
|
}
|
|
|
|
void startGameTimer() {
|
|
if (_isGameStarted) return;
|
|
_isGameStarted = true;
|
|
|
|
_timer?.cancel();
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
_secondsElapsed++;
|
|
_remainingTime--;
|
|
|
|
if (_remainingTime <= 0) {
|
|
_timer?.cancel();
|
|
_isTimeOut = true;
|
|
_isGameCompleted = true;
|
|
}
|
|
notifyListeners();
|
|
});
|
|
notifyListeners();
|
|
}
|
|
|
|
void _generateCards() {
|
|
final int totalCards = difficulty.totalCards;
|
|
final int pairsCount = totalCards ~/ 2;
|
|
List<CardItem> deck = [];
|
|
final Random random = Random();
|
|
|
|
if (difficulty.contentType == CardContentType.calculation) {
|
|
// [🔥 수정] 연산 모드
|
|
final Set<int> usedResults = {};
|
|
|
|
// 레벨 4인지 확인 (한 자릿수 덧셈/뺄셈만)
|
|
final bool isLevel4 = difficulty.levelIndex == 4;
|
|
|
|
for (int i = 0; i < pairsCount; i++) {
|
|
String equation;
|
|
String answer;
|
|
int result;
|
|
|
|
while (true) {
|
|
// Lv 4: 0(덧셈), 1(뺄셈)만 선택 / 그 외: 0, 1, 2(곱셈) 선택
|
|
final int opType = isLevel4 ? random.nextInt(2) : random.nextInt(3);
|
|
int a, b;
|
|
|
|
if (opType == 0) { // 덧셈 (+)
|
|
if (isLevel4) {
|
|
// 한 자릿수 (1~9)
|
|
a = random.nextInt(9) + 1;
|
|
b = random.nextInt(9) + 1;
|
|
} else {
|
|
// 두 자릿수 포함 (1~15)
|
|
a = random.nextInt(15) + 1;
|
|
b = random.nextInt(15) + 1;
|
|
}
|
|
result = a + b;
|
|
equation = "$a + $b";
|
|
|
|
} else if (opType == 1) { // 뺄셈 (-)
|
|
if (isLevel4) {
|
|
// 결과가 양수이고 한 자릿수 내에서 처리되도록
|
|
a = random.nextInt(9) + 2; // 2~10
|
|
b = random.nextInt(a - 1) + 1;
|
|
} else {
|
|
a = random.nextInt(20) + 5;
|
|
b = random.nextInt(a - 1) + 1;
|
|
}
|
|
result = a - b;
|
|
equation = "$a - $b";
|
|
|
|
} else { // 곱셈 (*)
|
|
// Lv 4에서는 이 블록에 들어오지 않음
|
|
a = random.nextInt(9) + 2;
|
|
b = random.nextInt(5) + 2;
|
|
result = a * b;
|
|
equation = "$a x $b";
|
|
}
|
|
|
|
// 정답 중복 방지
|
|
if (!usedResults.contains(result)) {
|
|
usedResults.add(result);
|
|
answer = result.toString();
|
|
break;
|
|
}
|
|
}
|
|
|
|
String matchKey = "CALC_$i";
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: equation));
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: answer));
|
|
}
|
|
}
|
|
else if (difficulty.contentType == CardContentType.pairWord) {
|
|
// 연상 모드
|
|
var entries = CardFlipDifficulties.wordPairs.entries.toList()..shuffle();
|
|
for (int i = 0; i < pairsCount; i++) {
|
|
var entry = entries[i % entries.length];
|
|
String matchKey = "PAIR_$i";
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.key));
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.value));
|
|
}
|
|
}
|
|
else {
|
|
// 동일 매칭 모드
|
|
List<String> pool = List.of(CardFlipDifficulties.emojis)..shuffle();
|
|
for (int i = 0; i < pairsCount; i++) {
|
|
String content;
|
|
String matchKey = "SAME_$i";
|
|
|
|
if (difficulty.contentType == CardContentType.number) {
|
|
content = (i + 1).toString();
|
|
} else if (difficulty.contentType == CardContentType.icon) {
|
|
content = "ICON_$i";
|
|
} else {
|
|
content = pool[i % pool.length];
|
|
}
|
|
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: content));
|
|
deck.add(CardItem(id: 0, matchId: matchKey, displayContent: content));
|
|
}
|
|
}
|
|
|
|
deck.shuffle(random);
|
|
for (int i = 0; i < deck.length; i++) {
|
|
deck[i] = CardItem(
|
|
id: i,
|
|
matchId: deck[i].matchId,
|
|
displayContent: deck[i].displayContent
|
|
);
|
|
}
|
|
_cards = deck;
|
|
}
|
|
|
|
void onCardTapped(CardItem card) {
|
|
if (!_isGameStarted || _isGameCompleted || _isProcessing || card.isFaceUp || card.isMatched) return;
|
|
|
|
card.isFaceUp = true;
|
|
_flipCount++;
|
|
notifyListeners();
|
|
|
|
if (_firstFlippedCard == null) {
|
|
_firstFlippedCard = card;
|
|
} else {
|
|
_isProcessing = true;
|
|
_checkMatch(_firstFlippedCard!, card);
|
|
_firstFlippedCard = null;
|
|
}
|
|
}
|
|
|
|
void _checkMatch(CardItem card1, CardItem card2) {
|
|
if (card1.matchId == card2.matchId) {
|
|
card1.isMatched = true;
|
|
card2.isMatched = true;
|
|
_isProcessing = false;
|
|
|
|
if (_cards.every((c) => c.isMatched)) {
|
|
_isGameCompleted = true;
|
|
_timer?.cancel();
|
|
}
|
|
notifyListeners();
|
|
} else {
|
|
Future.delayed(const Duration(milliseconds: 800), () {
|
|
card1.isFaceUp = false;
|
|
card2.isFaceUp = false;
|
|
_isProcessing = false;
|
|
notifyListeners();
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer?.cancel();
|
|
super.dispose();
|
|
}
|
|
} |