Created
December 11, 2023 14:18
-
-
Save bizz84/7a2240cbcffad312e09e9fbcab576542 to your computer and use it in GitHub Desktop.
A game where the player needs to find out under which card the Flutter logo is hiding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
// To use this package, run: | |
// dart pub add page_flip_builder | |
import 'package:page_flip_builder/page_flip_builder.dart'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.indigo, | |
), | |
home: const GamePage(), | |
); | |
} | |
} | |
enum GameStatus { | |
playing, | |
notPlaying, | |
restarting, | |
} | |
class GamePage extends StatefulWidget { | |
const GamePage({super.key}); | |
@override | |
State<GamePage> createState() => _GamePageState(); | |
} | |
class _GamePageState extends State<GamePage> { | |
static final _rng = Random(); | |
static const cardFlipDuration = Duration(milliseconds: 350); | |
int _targetIndex = _rng.nextInt(2); | |
// whether the game is currently playing (both cards are not turned) | |
GameStatus _gameStatus = GameStatus.playing; | |
// whether the player won the game (found the Flutter logo) | |
bool _didWin = false; | |
// flip key for first card | |
final _flipKey0 = GlobalKey<PageFlipBuilderState>(); | |
// flip key for second card | |
final _flipKey1 = GlobalKey<PageFlipBuilderState>(); | |
void _completeGame(bool didWin) { | |
setState(() { | |
_didWin = didWin; | |
_gameStatus = GameStatus.notPlaying; | |
}); | |
} | |
Future<void> _tryAgain() async { | |
setState(() { | |
_gameStatus = GameStatus.restarting; | |
}); | |
// hide again the flipped card | |
// which key to use depends on these variables: | |
if (_didWin && _targetIndex == 0 || !_didWin && _targetIndex == 1) { | |
_flipKey0.currentState?.flip(); | |
} else { | |
_flipKey1.currentState?.flip(); | |
} | |
// await until the card is flipped before changing the state (and revealing the new position) | |
await Future.delayed(cardFlipDuration); | |
setState(() { | |
_targetIndex = _rng.nextInt(2); | |
_gameStatus = GameStatus.playing; | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Find the Flutter Logo'), | |
), | |
body: SafeArea( | |
child: Padding( | |
padding: const EdgeInsets.all(24.0), | |
child: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceAround, | |
children: [ | |
// first card | |
GameCard( | |
flipKey: _flipKey0, | |
canFlip: _gameStatus == GameStatus.playing, | |
hasFlutterLogo: _targetIndex == 0, | |
flipDuration: cardFlipDuration, | |
onFlip: (frontSide) { | |
if (!frontSide) { | |
_completeGame(_targetIndex == 0); | |
} | |
}, | |
), | |
// second card | |
GameCard( | |
flipKey: _flipKey1, | |
canFlip: _gameStatus == GameStatus.playing, | |
hasFlutterLogo: _targetIndex == 1, | |
flipDuration: cardFlipDuration, | |
onFlip: (frontSide) { | |
if (!frontSide) { | |
_completeGame(_targetIndex == 1); | |
} | |
}, | |
), | |
// retry/end game UI | |
RetryGameWidget( | |
gameStatus: _gameStatus, | |
didWin: _didWin, | |
onRetry: _tryAgain, | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class RetryGameWidget extends StatelessWidget { | |
const RetryGameWidget({ | |
super.key, | |
required this.gameStatus, | |
required this.didWin, | |
this.onRetry, | |
}); | |
final GameStatus gameStatus; | |
final bool didWin; | |
final VoidCallback? onRetry; | |
@override | |
Widget build(BuildContext context) { | |
return IgnorePointer( | |
// ignore any clicks if game is playing or restarting | |
ignoring: gameStatus != GameStatus.notPlaying, | |
child: AnimatedOpacity( | |
opacity: gameStatus == GameStatus.playing ? 0.0 : 1.0, | |
duration: const Duration(milliseconds: 200), | |
child: Column( | |
children: [ | |
Text( | |
didWin ? 'You won!' : 'You lost!', | |
style: Theme.of(context).textTheme.headlineSmall!.copyWith( | |
color: didWin ? Colors.green : Colors.red, | |
fontWeight: FontWeight.bold), | |
), | |
const SizedBox(height: 16), | |
OutlinedButton( | |
style: OutlinedButton.styleFrom( | |
shape: const StadiumBorder(), | |
side: const BorderSide(width: 2, color: Colors.black54), | |
), | |
onPressed: onRetry, | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Text('Try Again', | |
style: Theme.of(context).textTheme.headlineSmall), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
/// A game card that can be flipped | |
class GameCard extends StatelessWidget { | |
const GameCard({ | |
super.key, | |
required this.flipKey, | |
required this.canFlip, | |
required this.hasFlutterLogo, | |
this.flipDuration = const Duration(milliseconds: 250), | |
this.onFlip, | |
}); | |
final GlobalKey<PageFlipBuilderState> flipKey; | |
final bool canFlip; | |
final bool hasFlutterLogo; | |
final Duration flipDuration; | |
final ValueChanged<bool>? onFlip; | |
@override | |
Widget build(BuildContext context) { | |
return AspectRatio( | |
aspectRatio: 1.5, | |
child: PageFlipBuilder( | |
key: flipKey, | |
nonInteractiveAnimationDuration: flipDuration, | |
// only enable flip by tapping on the card | |
interactiveFlipEnabled: false, | |
// Show a card with a '?' text inside it | |
frontBuilder: (_) => GameCardSide( | |
color: Colors.yellow, | |
// only flip if canFlip is true | |
onPressed: canFlip ? () => flipKey.currentState?.flip() : null, | |
child: Text( | |
'?', | |
style: Theme.of(context).textTheme.headlineMedium, | |
), | |
), | |
// Show a card revealing the FlutterLogo or an empty SizedBox | |
backBuilder: (_) => GameCardSide( | |
color: Colors.white70, | |
child: | |
hasFlutterLogo ? const FlutterLogo(size: 160) : const SizedBox(), | |
), | |
onFlipComplete: onFlip, | |
), | |
); | |
} | |
} | |
class GameCardSide extends StatelessWidget { | |
const GameCardSide( | |
{super.key, required this.child, this.color, this.onPressed}); | |
final Widget child; | |
final Color? color; | |
final VoidCallback? onPressed; | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: onPressed, | |
child: Card( | |
elevation: 5, | |
color: color, | |
child: Center( | |
child: child, | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment