...
This commit is contained in:
@@ -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/
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user