...
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
library feature_game_schulte;
|
||||
export 'screens/schulte_game_screen.dart';
|
||||
export 'screens/schulte_lobby_screen.dart'; // 추가
|
||||
@@ -0,0 +1,247 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:feature_common/feature_common.dart';
|
||||
|
||||
class SchulteGameScreen extends BaseGameScreen {
|
||||
final int levelIndex;
|
||||
|
||||
const SchulteGameScreen({
|
||||
super.key,
|
||||
super.onNextGame,
|
||||
this.levelIndex = 1,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SchulteGameScreen> createState() => _SchulteGameScreenState();
|
||||
}
|
||||
|
||||
class _SchulteGameScreenState extends BaseGameScreenState<SchulteGameScreen> with TickerProviderStateMixin {
|
||||
// 게임 설정
|
||||
int _gridSize = 3; // 3x3, 4x4, 5x5
|
||||
List<int> _numbers = [];
|
||||
|
||||
// 진행 상태
|
||||
int _targetNumber = 1; // 현재 찾아야 할 숫자
|
||||
DateTime? _startTime;
|
||||
Timer? _hintTimer;
|
||||
|
||||
// 힌트 애니메이션
|
||||
AnimationController? _hintController;
|
||||
int? _hintIndex; // 힌트를 보여줄 그리드 인덱스
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initLevel();
|
||||
_startNewGame();
|
||||
}
|
||||
|
||||
void _initLevel() {
|
||||
// 난이도별 그리드 크기 설정
|
||||
// Lv 1~3: 3x3
|
||||
// Lv 4~6: 4x4
|
||||
// Lv 7~: 5x5
|
||||
if (widget.levelIndex <= 3) {
|
||||
_gridSize = 3;
|
||||
} else if (widget.levelIndex <= 6) {
|
||||
_gridSize = 4;
|
||||
} else {
|
||||
_gridSize = 5;
|
||||
}
|
||||
|
||||
// 힌트 애니메이션 컨트롤러
|
||||
_hintController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
)..repeat(reverse: true);
|
||||
}
|
||||
|
||||
void _startNewGame() {
|
||||
// 1~N 까지 숫자 생성 후 섞기
|
||||
int totalCount = _gridSize * _gridSize;
|
||||
_numbers = List.generate(totalCount, (index) => index + 1);
|
||||
_numbers.shuffle();
|
||||
|
||||
setState(() {
|
||||
_targetNumber = 1;
|
||||
_startTime = DateTime.now();
|
||||
_hintIndex = null;
|
||||
});
|
||||
|
||||
_resetHintTimer();
|
||||
}
|
||||
|
||||
// 힌트 타이머 (3초간 입력 없으면 작동)
|
||||
void _resetHintTimer() {
|
||||
_hintTimer?.cancel();
|
||||
setState(() => _hintIndex = null);
|
||||
|
||||
_hintTimer = Timer(const Duration(seconds: 3), () {
|
||||
// 현재 찾아야 할 숫자의 위치를 찾음
|
||||
int index = _numbers.indexOf(_targetNumber);
|
||||
if (index != -1 && mounted) {
|
||||
setState(() => _hintIndex = index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onNumberTap(int number) {
|
||||
if (number == _targetNumber) {
|
||||
// 정답!
|
||||
// 효과음 재생 가능
|
||||
|
||||
if (_targetNumber == _gridSize * _gridSize) {
|
||||
// 게임 클리어
|
||||
_finishGame();
|
||||
} else {
|
||||
// 다음 숫자로 이동
|
||||
setState(() {
|
||||
_targetNumber++;
|
||||
});
|
||||
_resetHintTimer();
|
||||
}
|
||||
} else {
|
||||
// 오답 (흔들기 효과 등을 넣을 수 있음)
|
||||
// 여기서는 간단히 스낵바
|
||||
/*
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$_targetNumber을(를) 누르세요!'), duration: Duration(milliseconds: 500)),
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
void _finishGame() {
|
||||
_hintTimer?.cancel();
|
||||
final duration = DateTime.now().difference(_startTime!);
|
||||
|
||||
showCommonGameCompletion(
|
||||
GameResultArgs(
|
||||
gameType: 'SCHULTE',
|
||||
contextId: 'Lv${widget.levelIndex}',
|
||||
primaryScore: duration.inSeconds,
|
||||
scoreFormatter: (s, _) => "$s초 걸림",
|
||||
|
||||
levelIndex: widget.levelIndex,
|
||||
stars: duration.inSeconds < (_gridSize * _gridSize) ? 3 : 2,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hintTimer?.cancel();
|
||||
_hintController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('숫자 순서 찾기 (Lv.${widget.levelIndex})')),
|
||||
body: Column(
|
||||
children: [
|
||||
// 상단 안내
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"1부터 순서대로 빠르게 누르세요!",
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueAccent,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Text(
|
||||
"찾을 숫자: $_targetNumber",
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: GridView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: _gridSize,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
itemCount: _numbers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final number = _numbers[index];
|
||||
final isFound = number < _targetNumber; // 이미 찾은 숫자
|
||||
final isHint = index == _hintIndex; // 힌트 대상
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isFound ? null : () => _onNumberTap(number),
|
||||
child: AnimatedBuilder(
|
||||
animation: _hintController!,
|
||||
builder: (context, child) {
|
||||
// 힌트일 때 깜빡임 효과
|
||||
double opacity = 1.0;
|
||||
if (isHint) {
|
||||
opacity = 0.5 + (_hintController!.value * 0.5);
|
||||
}
|
||||
return Opacity(opacity: opacity, child: child);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isFound
|
||||
? Colors.grey.shade200 // 찾은건 흐리게
|
||||
: (isHint ? Colors.orange.shade100 : Colors.white),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isFound
|
||||
? Colors.transparent
|
||||
: (isHint ? Colors.orange : Colors.blue.shade200),
|
||||
width: 2
|
||||
),
|
||||
boxShadow: isFound ? [] : [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
)
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
isFound ? "" : "$number", // 찾은건 숫자 숨김 (또는 흐리게)
|
||||
style: TextStyle(
|
||||
fontSize: _gridSize == 3 ? 40 : (_gridSize == 4 ? 32 : 24),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isFound ? Colors.grey : Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'schulte_game_screen.dart';
|
||||
|
||||
class SchulteLobbyScreen extends StatelessWidget {
|
||||
const SchulteLobbyScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('숫자 순서 찾기')),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.looks_one, size: 80, color: Colors.indigo),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"1부터 순서대로 숫자를 빠르게 찾으세요.\n주의력과 탐색 속도를 높여줍니다.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// 난이도 카드 (3개)
|
||||
_buildLevelCard(context, 1, "초급 (3x3)", Colors.green),
|
||||
const SizedBox(height: 16),
|
||||
_buildLevelCard(context, 4, "중급 (4x4)", Colors.blue),
|
||||
const SizedBox(height: 16),
|
||||
_buildLevelCard(context, 7, "고급 (5x5)", Colors.red),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLevelCard(BuildContext context, int level, String title, Color color) {
|
||||
return InkWell(
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => SchulteGameScreen(levelIndex: level))),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withOpacity(0.5), width: 2),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.grid_on, color: color, size: 32),
|
||||
const SizedBox(width: 16),
|
||||
Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
const Icon(Icons.play_circle_fill, color: Colors.grey),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user