2025-11-19 17:16:43 +09:00

213 lines
5.7 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 = {};
for (int i = 0; i < pairsCount; i++) {
String equation;
String answer;
int result;
while (true) {
final int opType = random.nextInt(3);
int a, b;
if (opType == 0) { // +
a = random.nextInt(15) + 1;
b = random.nextInt(15) + 1;
result = a + b;
equation = "$a + $b";
} else if (opType == 1) { // -
a = random.nextInt(20) + 5;
b = random.nextInt(a - 1) + 1;
result = a - b;
equation = "$a - $b";
} else { // *
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();
}
}