This commit is contained in:
2025-11-19 17:00:33 +09:00
parent 2008c377f4
commit 09665fa073
442 changed files with 18389 additions and 805 deletions
@@ -0,0 +1,276 @@
// packages/feature_game_sequence/lib/controllers/sequence_game_controller.dart
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import '../models/sequence_models.dart';
enum SequenceGameState {
idle,
presenting,
postDelay,
input,
processing,
completed,
}
class SequenceGameController with ChangeNotifier {
late SequenceDifficulty difficulty;
late String userId;
late String? userName;
SequenceGameState _currentState = SequenceGameState.idle;
SequenceGameState get currentState => _currentState;
List<SequenceButtonId> _currentSequence = [];
List<SequenceButtonId> get currentSequence => _currentSequence;
List<SequenceButtonId> _userInput = [];
List<SequenceButtonId> get userInput => _userInput;
late List<String> _activeSymbolPool;
List<String> get activeSymbolPool => _activeSymbolPool;
int _currentSequenceLength = 0;
int get currentSequenceLength => _currentSequenceLength;
int _maxAchievedLength = 0;
int get maxAchievedLength => _maxAchievedLength;
int _roundsCompleted = 0;
int get roundsCompleted => _roundsCompleted;
int _roundsFailed = 0;
int get roundsFailed => _roundsFailed;
SequenceButtonId? _activePresentationId;
SequenceButtonId? get activePresentationId => _activePresentationId;
bool _isLastInputCorrect = true;
bool get isLastInputCorrect => _isLastInputCorrect;
bool _showFeedback = false;
bool get showFeedback => _showFeedback;
Timer? _scoreTimer;
Timer? _roundTimer;
double _remainingRoundTime = 0.0;
double get remainingRoundTime => _remainingRoundTime;
int _secondsElapsed = 0;
int get secondsElapsed => _secondsElapsed;
int _presentationStep = 0;
int get presentationStep => _presentationStep;
set presentationStep(int step) {
_presentationStep = step;
notifyListeners();
}
final Random _random = Random();
String getSymbolForId(SequenceButtonId id) {
if (id.index >= _activeSymbolPool.length) {
return '?';
}
return _activeSymbolPool[id.index];
}
void setUserInfo(String userId, String? userName) {
this.userId = userId;
this.userName = userName;
}
void startNewGame(SequenceDifficulty level) {
difficulty = level;
_currentSequence.clear();
_userInput.clear();
_currentSequenceLength = level.initialLength;
_maxAchievedLength = level.initialLength - 1;
_currentState = SequenceGameState.idle;
_secondsElapsed = 0;
_roundsCompleted = 0;
_roundsFailed = 0;
_showFeedback = false;
_activePresentationId = null;
_presentationStep = 0;
_initializeSymbolPool();
WidgetsBinding.instance.addPostFrameCallback((_) {
_startPresentationPhase();
});
notifyListeners();
}
void restartGame() {
startNewGame(difficulty);
}
void setActivePresentation(SequenceButtonId? id) {
_activePresentationId = id;
notifyListeners();
}
// 🔽 [🔥 수정] 레벨 10 이상만 믹스, 나머지는 단일 테마
void _initializeSymbolPool() {
if (difficulty.levelIndex >= 10) {
// Lv 10+: 모든 풀을 합치고 섞음 (Mixed)
final List<String> allSymbols = [
...SequenceDifficulties.getSymbolPool(SequenceContentPool.number),
...SequenceDifficulties.getSymbolPool(SequenceContentPool.letter),
...SequenceDifficulties.getSymbolPool(SequenceContentPool.koreanChar),
...SequenceDifficulties.getSymbolPool(SequenceContentPool.emoji),
// (단어는 길이가 길어 혼합 시 시각적 밸런스를 위해 제외하거나 포함 가능. 여기선 제외)
];
_activeSymbolPool = (allSymbols..shuffle()).sublist(0, difficulty.buttonsCount);
} else {
// Lv 1~9: 지정된 단일 풀 사용
_activeSymbolPool = SequenceDifficulties.getSymbolPool(difficulty.contentPoolType);
}
}
void _startPresentationPhase() {
_roundTimer?.cancel();
_currentState = SequenceGameState.presenting;
_presentationStep = 0;
_generateNextSequence();
notifyListeners();
}
void _generateNextSequence() {
_currentSequence.clear();
final int maxIndex = difficulty.buttonsCount;
for (int i = 0; i < _currentSequenceLength; i++) {
final int randIndex = _random.nextInt(maxIndex);
_currentSequence.add(SequenceButtonId.values[randIndex]);
}
}
void runPostPresentationDelay() {
_currentState = SequenceGameState.postDelay;
notifyListeners();
Future.delayed(const Duration(milliseconds: 1000), () {
if (currentState == SequenceGameState.postDelay) {
startInputPhase();
}
});
}
void startInputPhase() {
_startScoreTimer();
_userInput.clear();
_currentState = SequenceGameState.input;
_startRoundTimer();
notifyListeners();
}
void _startRoundTimer() {
_roundTimer?.cancel();
_remainingRoundTime = difficulty.roundTimeLimit;
_roundTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (_remainingRoundTime <= 0) {
_roundTimer?.cancel();
_roundsFailed++;
_handleFeedbackAndTermination();
} else {
_remainingRoundTime -= 0.1;
notifyListeners();
}
});
}
void _startScoreTimer() {
_scoreTimer?.cancel();
_scoreTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
_secondsElapsed++;
});
}
void recordUserInput(SequenceButtonId inputId) {
if (_currentState != SequenceGameState.input || _showFeedback) return;
_userInput.add(inputId);
_currentState = SequenceGameState.processing;
notifyListeners();
Future.delayed(const Duration(milliseconds: 100), () {
_checkUserInput();
});
}
void _checkUserInput() {
if (_currentState != SequenceGameState.processing) return;
final int inputLength = _userInput.length;
final int currentInputIndex = inputLength - 1;
if (_userInput[currentInputIndex] != _currentSequence[currentInputIndex]) {
_roundsFailed++;
_isLastInputCorrect = false;
_handleFeedbackAndTermination();
return;
}
_isLastInputCorrect = true;
if (inputLength == _currentSequenceLength) {
_roundsCompleted++;
_maxAchievedLength = _currentSequenceLength;
_handleFeedbackAndNextRound();
return;
}
_currentState = SequenceGameState.input;
notifyListeners();
}
void _handleFeedbackAndTermination() {
_stopAllTimers();
_showFeedback = true;
notifyListeners();
Future.delayed(const Duration(seconds: 1), () {
_stopAllTimers();
_currentState = SequenceGameState.completed;
_showFeedback = false;
notifyListeners();
});
}
void _handleFeedbackAndNextRound() {
_roundTimer?.cancel();
_showFeedback = true;
notifyListeners();
Future.delayed(const Duration(milliseconds: 1000), () {
_showFeedback = false;
if (_currentSequenceLength < difficulty.maxGameLength) {
_currentSequenceLength++;
} else {
_stopAllTimers();
_currentState = SequenceGameState.completed;
notifyListeners();
return;
}
_startPresentationPhase();
});
}
void _stopAllTimers() {
_scoreTimer?.cancel();
_roundTimer?.cancel();
}
@override
void dispose() {
_stopAllTimers();
super.dispose();
}
}