중급
미니게임 (카드 기억)
4x4 카드판에서 두 장씩 뒤집어 같은 그림을 찾고, 완료 시간을 기록하는 기억력 게임을 만듭니다.
동작
미니게임 (카드 기억) 실행 화면
카드 기억 게임
시도 0
짝 0/8
시간 0초
4x4 카드판에서 카드를 두 장씩 열어 같은 그림을 찾습니다. 다른 그림을 고르면 두 번째 카드까지 잠시 보여준 뒤 1초 후 다시 뒤집습니다. 게임 시작과 함께 초가 올라가고, 모든 짝을 찾으면 중앙에 완료 시간이 표시됩니다.
예제 코드
카드 기억 게임
import 'dart:async';
import 'package:flutter/material.dart';
class MemoryCardGame extends StatefulWidget {
const MemoryCardGame({super.key});
@override
State<MemoryCardGame> createState() => _MemoryCardGameState();
}
class _MemoryCardGameState extends State<MemoryCardGame> {
final icons = ['★', '●', '■', '◆', '▲', '✦', '✚', '☘', '★', '●', '■', '◆', '▲', '✦', '✚', '☘'];
final opened = <int>{};
final previewing = <int>{};
int? first;
int moves = 0;
int seconds = 0;
bool locked = false;
bool finished = false;
Timer? timer;
void startGame() {
timer?.cancel();
setState(() {
opened.clear();
previewing.clear();
first = null;
moves = 0;
seconds = 0;
finished = false;
locked = false;
});
timer = Timer.periodic(const Duration(seconds: 1), (_) {
setState(() => seconds += 1);
});
}
Future<void> flip(int index) async {
if (locked || opened.contains(index) || previewing.contains(index)) return;
setState(() => previewing.add(index));
if (first == null) {
first = index;
return;
}
locked = true;
moves += 1;
if (icons[first!] == icons[index]) {
opened.add(first!);
opened.add(index);
if (opened.length == icons.length) {
timer?.cancel();
finished = true;
}
} else {
await Future.delayed(const Duration(seconds: 1));
}
setState(() {
previewing.remove(first);
previewing.remove(index);
first = null;
locked = false;
});
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('시도 $moves · 시간 $seconds초'),
ElevatedButton(onPressed: startGame, child: const Text('게임 시작')),
Stack(
alignment: Alignment.center,
children: [
GridView.count(
shrinkWrap: true,
crossAxisCount: 4,
children: List.generate(icons.length, (index) {
final visible = opened.contains(index) || previewing.contains(index);
return AnimatedSwitcher(
duration: const Duration(milliseconds: 220),
child: TextButton(
key: ValueKey(visible),
onPressed: () => flip(index),
child: Text(visible ? icons[index] : '?'),
),
);
}),
),
if (finished)
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text('$seconds초 기록'),
),
),
],
),
],
);
}
}
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xFFDCE6EF)),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'카드 기억 게임',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w900),
),
const SizedBox(height: 14),
Wrap(
spacing: 8,
children: const [Chip(label: Text('시도 0')), Chip(label: Text('짝 0/3'))],
),
const SizedBox(height: 14),
SizedBox(
width: 320,
height: 220,
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
children: List.generate(icons.length, (index) {
final visible = opened.contains(index) || first == index;
return FilledButton.tonal(
onPressed: () => flip(index),
child: Text(visible ? icons[index] : '?'),
);
}),
),
),
],
),
)
카드 기억 게임
시도 0
짝 0/8
시간 0초
예제 코드 기능별 설명
코드를 나누어 읽기
import
dart:async은 초 기록 타이머에 필요합니다. material.dart는 GridView, TextButton, AnimatedSwitcher, Card 같은 화면 구성 요소를 제공합니다.
import 'dart:async';
import 'package:flutter/material.dart';
카드 상태 변수
opened는 이미 맞힌 카드, previewing은 잠깐 보여주는 카드, first는 첫 번째로 고른 카드입니다. locked는 두 장 비교 중에 추가 클릭을 막습니다.
final opened = <int>{};
final previewing = <int>{};
int? first;
bool locked = false;
시간 기록 변수
seconds는 게임 시작 후 흐른 시간이고 finished는 모든 짝을 찾았는지 나타냅니다. timer는 시작 버튼을 눌렀을 때만 만들어지고 완료 시 멈춥니다.
int seconds = 0;
bool finished = false;
Timer? timer;
4x4 카드 데이터
16장의 카드가 필요하므로 8종의 그림을 두 장씩 넣습니다. opened는 맞힌 카드, previewing은 잠깐 뒤집어 보여줄 카드를 저장합니다.
final icons = ['★', '●', '■', '◆', '▲', '✦', '✚', '☘', '★', '●', '■', '◆', '▲', '✦', '✚', '☘'];
final opened = <int>{};
final previewing = <int>{};
1초 뒤 다시 뒤집기
두 카드가 다르면 바로 닫지 않고 1초 동안 보여줍니다. 사용자가 어떤 그림을 잘못 골랐는지 확인할 수 있게 하기 위한 수정입니다.
if (icons[first!] == icons[index]) {
opened.add(first!);
opened.add(index);
} else {
await Future.delayed(const Duration(seconds: 1));
}
뒤집기 모션
AnimatedSwitcher를 사용해 카드 앞면과 뒷면이 바뀔 때 짧은 전환이 생기게 합니다. 실제 앱에서는 Transform으로 3D 회전까지 확장할 수 있습니다.
AnimatedSwitcher(
duration: const Duration(milliseconds: 220),
child: Text(visible ? icons[index] : '?'),
)