This commit is contained in:
2025-12-15 18:18:17 +09:00
parent 03a7ed2ef2
commit 4c2c98de8a
216 changed files with 9831 additions and 725 deletions
+31
View File
@@ -0,0 +1,31 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: package
@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.
+1
View File
@@ -0,0 +1 @@
TODO: Add your license here.
+39
View File
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/to/develop-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -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),
],
),
),
);
}
}
@@ -0,0 +1,22 @@
name: feature_game_schulte
description: Schulte table game for attention training.
version: 0.0.1
publish_to: 'none'
resolution: workspace
environment:
sdk: '^3.9.2'
flutter: '>=3.10.0'
dependencies:
flutter:
sdk: flutter
# 공통 모듈
feature_common:
path: ../feature_common
service_api:
path: ../service_api
dev_dependencies:
flutter_test:
sdk: flutter
@@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:feature_game_schulte/feature_game_schulte.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}