// 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 _cards = []; List 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; 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(); // [๐Ÿ”ฅ ์ˆ˜์ •] startNewGame์—์„œ๋Š” ํƒ€์ด๋จธ๋ฅผ ์‹œ์ž‘ํ•˜์ง€ ์•Š์Œ (๊ฐ€์ด๋“œ ํ™•์ธ ํ›„ ์‹œ์ž‘) notifyListeners(); } void restartGame() { startNewGame(difficulty); // ์žฌ์‹œ์ž‘ ์‹œ์—๋Š” ๊ฐ€์ด๋“œ ์—†์ด ๋ฐ”๋กœ ์‹œ์ž‘ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์—ฌ๊ธฐ์„œ startTimer ํ˜ธ์ถœ // ํ•˜์ง€๋งŒ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด UI์—์„œ ๋‹ค์‹œ ๊ฐ€์ด๋“œ๋ฅผ ๋„์šฐ๋„๋ก ์œ ๋„ } // [๐Ÿ”ฅ ์ˆ˜์ •] Public์œผ๋กœ ๋ณ€๊ฒฝ (UI์—์„œ ํ˜ธ์ถœ) 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 deck = []; if (difficulty.contentType == CardContentType.calculation) { // 1. ์—ฐ์‚ฐ ๋ชจ๋“œ (์‹ โ†” ๋‹ต) var entries = CardFlipDifficulties.calculationPairs.entries.toList()..shuffle(); for (int i = 0; i < pairsCount; i++) { var entry = entries[i % entries.length]; String matchKey = "CALC_$i"; // ๋…ผ๋ฆฌ์  ID // ์‹ ์นด๋“œ deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.key)); // ๋‹ต ์นด๋“œ deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.value)); } } else if (difficulty.contentType == CardContentType.pairWord) { // 2. ์—ฐ์ƒ ๋ชจ๋“œ (A โ†” B) var entries = CardFlipDifficulties.wordPairs.entries.toList()..shuffle(); for (int i = 0; i < pairsCount; i++) { var entry = entries[i % entries.length]; String matchKey = "PAIR_$i"; // ๋‹จ์–ด A deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.key)); // ๋‹จ์–ด B deck.add(CardItem(id: 0, matchId: matchKey, displayContent: entry.value)); } } else { // 3. ๋™์ผ ๋งค์นญ ๋ชจ๋“œ (์ด๋ชจ์ง€, ์•„์ด์ฝ˜, ์ˆซ์ž) List 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]; } // ๋˜‘๊ฐ™์€ ์นด๋“œ 2์žฅ deck.add(CardItem(id: 0, matchId: matchKey, displayContent: content)); deck.add(CardItem(id: 0, matchId: matchKey, displayContent: content)); } } // 4. ์ „์ฒด ์„ž๊ธฐ ๋ฐ ID ๋ถ€์—ฌ deck.shuffle(Random()); for (int i = 0; i < deck.length; i++) { // ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌํ•˜๋ฉฐ ๊ณ ์œ  ID ๋ถ€์—ฌ 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; } } // ๐Ÿ”ฝ [๐Ÿ”ฅ ์ˆ˜์ •] ๋งค์นญ ๋กœ์ง: matchId๋กœ ๋น„๊ต 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(); } }