...
This commit is contained in:
@@ -1,222 +0,0 @@
|
||||
import 'game_difficulty.dart';
|
||||
|
||||
/// 빈칸의 유형을 정의
|
||||
enum MathQuizBlankType {
|
||||
numbersOnly, // 숫자만
|
||||
operatorsOnly, // 연산자만
|
||||
numbersAndOperators, // 둘 다
|
||||
}
|
||||
|
||||
/// 퍼즐의 레이아웃 형태를 정의
|
||||
enum MathQuizLayout {
|
||||
/// A + B = C 또는 A + B * C = D
|
||||
singleLine,
|
||||
/// 'ㄱ', 'ㄴ' 모양 (변수 4개)
|
||||
linkedL,
|
||||
/// 3x3 이상 그리드 (변수 8개 이상)
|
||||
gridSquare,
|
||||
}
|
||||
|
||||
/// 수학 퀴즈 게임의 난이도 정의
|
||||
class MathQuizDifficulty extends GameDifficulty {
|
||||
final int levelIndex;
|
||||
final MathQuizLayout layout;
|
||||
final String operators;
|
||||
final int blankCount;
|
||||
final MathQuizBlankType blankType;
|
||||
|
||||
/// [수정] 연산 복잡도 (예: 2=A+B, 3=A+B*C, 4=2x2그리드)
|
||||
final int operationCount;
|
||||
|
||||
const MathQuizDifficulty({
|
||||
required this.levelIndex,
|
||||
required super.name,
|
||||
required super.contextId,
|
||||
required this.layout,
|
||||
required this.operators,
|
||||
required this.blankCount,
|
||||
required this.blankType,
|
||||
required this.operationCount,
|
||||
});
|
||||
}
|
||||
|
||||
/// [수정됨] 앱 전역에서 사용할 수학 퀴즈 난이도 목록 (15단계)
|
||||
class MathQuizDifficulties {
|
||||
static final List<MathQuizDifficulty> allDifficulties = [
|
||||
// --- 패턴 1: 숫자 2개 (A op B = C) [Lv 1-3] ---
|
||||
// (연산자 우선순위 없음)
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 1,
|
||||
name: 'Lv. 1: 숫자 2개 (숫자 빈칸)',
|
||||
contextId: 'MATH_L1_OP2_NUM',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-',
|
||||
blankCount: 1,
|
||||
blankType: MathQuizBlankType.numbersOnly,
|
||||
operationCount: 2, // A, B
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 2,
|
||||
name: 'Lv. 2: 숫자 2개 (연산자 빈칸)',
|
||||
contextId: 'MATH_L2_OP2_OP',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-',
|
||||
blankCount: 1,
|
||||
blankType: MathQuizBlankType.operatorsOnly,
|
||||
operationCount: 2,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 3,
|
||||
name: 'Lv. 3: 숫자 2개 (사칙연산)',
|
||||
contextId: 'MATH_L3_OP2_ANY',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 2,
|
||||
blankType: MathQuizBlankType.numbersAndOperators,
|
||||
operationCount: 2,
|
||||
),
|
||||
|
||||
// --- 패턴 2: 숫자 3개 (A op1 B op2 C = D) [Lv 4-6] ---
|
||||
// (연산자 우선순위 *적용*)
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 4,
|
||||
name: 'Lv. 4: 숫자 3개 (숫자 빈칸)',
|
||||
contextId: 'MATH_L4_OP3_NUM',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 2,
|
||||
blankType: MathQuizBlankType.numbersOnly,
|
||||
operationCount: 3, // A, B, C
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 5,
|
||||
name: 'Lv. 5: 숫자 3개 (연산자 빈칸)',
|
||||
contextId: 'MATH_L5_OP3_OP',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 2,
|
||||
blankType: MathQuizBlankType.operatorsOnly,
|
||||
operationCount: 3,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 6,
|
||||
name: 'Lv. 6: 숫자 3개 (랜덤 빈칸)',
|
||||
contextId: 'MATH_L6_OP3_ANY',
|
||||
layout: MathQuizLayout.singleLine,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 3,
|
||||
blankType: MathQuizBlankType.numbersAndOperators,
|
||||
operationCount: 3,
|
||||
),
|
||||
|
||||
// --- 패턴 3: 숫자 4개 (2x2 그리드 'ㄱ', 'ㄴ') [Lv 7-9] ---
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 7,
|
||||
name: 'Lv. 7: 숫자 4개 (숫자 빈칸)',
|
||||
contextId: 'MATH_L7_OP4_NUM',
|
||||
layout: MathQuizLayout.linkedL,
|
||||
operators: '+,-',
|
||||
blankCount: 2,
|
||||
blankType: MathQuizBlankType.numbersOnly,
|
||||
operationCount: 4, // A, B, C, D
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 8,
|
||||
name: 'Lv. 8: 숫자 4개 (연산자 빈칸)',
|
||||
contextId: 'MATH_L8_OP4_OP',
|
||||
layout: MathQuizLayout.linkedL,
|
||||
operators: '+,-',
|
||||
blankCount: 2,
|
||||
blankType: MathQuizBlankType.operatorsOnly,
|
||||
operationCount: 4,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 9,
|
||||
name: 'Lv. 9: 숫자 4개 (사칙연산)',
|
||||
contextId: 'MATH_L9_OP4_ANY',
|
||||
layout: MathQuizLayout.linkedL,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 3,
|
||||
blankType: MathQuizBlankType.numbersAndOperators,
|
||||
operationCount: 4,
|
||||
),
|
||||
|
||||
// --- 패턴 4: 3x3 그리드 (숫자 9개) [Lv 10-12] ---
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 10,
|
||||
name: 'Lv. 10: 3x3 그리드 (숫자)',
|
||||
contextId: 'MATH_L10_OP9_NUM',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-',
|
||||
blankCount: 3,
|
||||
blankType: MathQuizBlankType.numbersOnly,
|
||||
operationCount: 9, // 9 Variables
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 11,
|
||||
name: 'Lv. 11: 3x3 그리드 (연산자)',
|
||||
contextId: 'MATH_L11_OP9_OP',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-',
|
||||
blankCount: 3,
|
||||
blankType: MathQuizBlankType.operatorsOnly,
|
||||
operationCount: 9,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 12,
|
||||
name: 'Lv. 12: 3x3 그리드 (사칙연산)',
|
||||
contextId: 'MATH_L12_OP9_ANY',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 4,
|
||||
blankType: MathQuizBlankType.numbersAndOperators,
|
||||
operationCount: 9,
|
||||
),
|
||||
|
||||
// --- 패턴 5: 4x4 그리드 (숫자 16개) [Lv 13-15] ---
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 13,
|
||||
name: 'Lv. 13: 4x4 그리드 (숫자)',
|
||||
contextId: 'MATH_L13_OP16_NUM',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 5,
|
||||
blankType: MathQuizBlankType.numbersOnly,
|
||||
operationCount: 16,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 14,
|
||||
name: 'Lv. 14: 4x4 그리드 (연산자)',
|
||||
contextId: 'MATH_L14_OP16_OP',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 5,
|
||||
blankType: MathQuizBlankType.operatorsOnly,
|
||||
operationCount: 16,
|
||||
),
|
||||
const MathQuizDifficulty(
|
||||
levelIndex: 15,
|
||||
name: 'Lv. 15: 4x4 그리드 (랜덤)',
|
||||
contextId: 'MATH_L15_OP16_ANY',
|
||||
layout: MathQuizLayout.gridSquare,
|
||||
operators: '+,-,*,/',
|
||||
blankCount: 6,
|
||||
blankType: MathQuizBlankType.numbersAndOperators,
|
||||
operationCount: 16,
|
||||
),
|
||||
];
|
||||
|
||||
/// 레벨 인덱스로 레벨 정보 찾기
|
||||
static MathQuizDifficulty getLevel(int levelIndex) {
|
||||
if (levelIndex < 1) levelIndex = 1;
|
||||
if (levelIndex > allDifficulties.length) levelIndex = allDifficulties.length;
|
||||
return allDifficulties.firstWhere((level) => level.levelIndex == levelIndex,
|
||||
orElse: () => allDifficulties[0]
|
||||
);
|
||||
}
|
||||
|
||||
/// 랭킹 화면용 맵 (ContextId -> 이름)
|
||||
static Map<String, String> get contextIdToNameMap {
|
||||
return { for (var level in allDifficulties) level.contextId : level.name };
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@ export 'models/sudoku_game_dto.dart';
|
||||
export 'models/sudoku_theme.dart';
|
||||
export 'models/unified_rank_dto.dart';
|
||||
export 'models/validate_result_dto.dart';
|
||||
export 'models/math_quiz_difficulty.dart'; // 👈 [추가]
|
||||
|
||||
|
||||
// Services
|
||||
export 'services/identity_service.dart';
|
||||
export 'services/puzzle_service.dart';
|
||||
export 'services/theme_notifier.dart';
|
||||
export 'services/session_notifier.dart'; // 👈 [추가]
|
||||
export 'services/session_notifier.dart'; // 👈 [추가]
|
||||
export 'services/lobby_helper_service.dart'; // 👈 [추가]
|
||||
@@ -0,0 +1,70 @@
|
||||
// packages/service_api/lib/services/lobby_helper_service.dart
|
||||
import 'dart:developer';
|
||||
import 'package:service_api/service_api.dart';
|
||||
|
||||
/// [수정됨] 로비 화면의 중복 로직(랭킹, 레벨)을 처리하는 공통 서비스
|
||||
class LobbyHelperService {
|
||||
final IdentityService _identityService;
|
||||
final PuzzleService _puzzleService;
|
||||
|
||||
LobbyHelperService({
|
||||
required IdentityService identityService,
|
||||
required PuzzleService puzzleService,
|
||||
}) : _identityService = identityService,
|
||||
_puzzleService = puzzleService;
|
||||
|
||||
/// (가벼움) 현재 게임의 최대 레벨만 로드
|
||||
Future<int> loadMaxLevel(String gameType) async {
|
||||
return await _identityService.getMaxUnlockedLevel(gameType: gameType);
|
||||
}
|
||||
|
||||
/// [🔥 수정됨] (무거움) 랭킹 변동 이력(▲▼)을 반환 (levelIndex 추출기능 주입)
|
||||
Future<Map<int, (int, int)>> loadRankHistory<T extends GameDifficulty>({
|
||||
required String gameType,
|
||||
required String myName,
|
||||
required List<T> allLevels,
|
||||
required int Function(T level) getLevelIndex, // 👈 [수정] levelIndex 추출 함수
|
||||
}) async {
|
||||
try {
|
||||
final Map<int, int> oldRankMap =
|
||||
await _identityService.getLastSavedRankMap(gameType: gameType);
|
||||
|
||||
List<Future<List<GameRankDto>>> rankFutures = [];
|
||||
for (final level in allLevels) {
|
||||
rankFutures.add(_puzzleService.fetchRanks(gameType, level.contextId));
|
||||
}
|
||||
|
||||
final List<List<GameRankDto>> allRankResults =
|
||||
await Future.wait(rankFutures);
|
||||
Map<int, int> newRankMapForStorage = {};
|
||||
Map<int, (int, int)> newRankHistoryForState = {};
|
||||
|
||||
for (int i = 0; i < allLevels.length; i++) {
|
||||
final level = allLevels[i];
|
||||
final currentRanks = allRankResults[i];
|
||||
|
||||
// [🔥 수정] 주입받은 함수로 levelIndex를 안전하게 추출
|
||||
final int levelIndex = getLevelIndex(level);
|
||||
|
||||
final int oldRank = oldRankMap[levelIndex] ?? 0;
|
||||
int currentRank = 0;
|
||||
int myRankIndex =
|
||||
currentRanks.indexWhere((r) => r.playerName == myName);
|
||||
if (myRankIndex != -1) {
|
||||
currentRank = myRankIndex + 1;
|
||||
}
|
||||
newRankMapForStorage[levelIndex] = currentRank;
|
||||
newRankHistoryForState[levelIndex] = (oldRank, currentRank);
|
||||
}
|
||||
|
||||
await _identityService.saveLastRankMap(newRankMapForStorage,
|
||||
gameType: gameType);
|
||||
|
||||
log("$gameType 랭킹 변동 확인 완료. (유저: $myName)");
|
||||
return newRankHistoryForState;
|
||||
} catch (e) {
|
||||
log("LobbyHelperService($gameType): 랭킹 확인 실패: $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user