I made a memory game with flutter. I used AnimatedBuilder and just setState() There is a game box model and widget.
In the game there is a hint button to show matching boxes. If user uses hint button half points awarded. Time is also important for points. Best score is stored in shared preferences.
Game animations is staggered animations. (Tweens, Curves, Intervals)
APK File
drive.google.com/file/d/11Wi9i9tC-jftOuxBmq..
Github repository
github.com/canerdemirci/flutter_memory_game
Project Structure
- models
- game_box_model.dart
- pages
- home
- home.dart
- components
- game_box.dart
- hint_button.dart
- results
- results_page.dart
- home
- box_animations.dart
- helpers.dart
- constants.dart
- main.dart
pubspec.yaml
cupertino_icons: ^1.0.2
audioplayers: ^0.20.1
shared_preferences: ^2.0.13
assets:
- audios/flip.mp3
- audios/matched.mp3
- audios/unmatched.mp3
main.dart
import 'package:flutter/material.dart';
import 'pages/home/home.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Memory Game',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(title: 'Memory Game'),
);
}
}
constants.dart
import 'package:flutter/material.dart';
enum GameBoxAnimStatus {
initial,
flip,
waitingToMatch,
matched,
unMatched,
gameOver
}
/* flip tweens */
final flipTween = Tween(begin: 1.0, end: 2.0);
/* waiting to match tweens */
final waitingToMatchColorTweenSequence = TweenSequence([
TweenSequenceItem(
tween: ColorTween(begin: Colors.deepOrange, end: Colors.orange),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.orange, end: Colors.deepOrange),
weight: 1),
]);
final waitingToMatchScaleTweenSequence = TweenSequence([
TweenSequenceItem(tween: Tween(begin: 1.1, end: 0.9), weight: 1),
TweenSequenceItem(tween: Tween(begin: 0.9, end: 1.1), weight: 1),
]);
/* matched tweens */
final matchedColorTweenSequence = TweenSequence([
TweenSequenceItem(
tween: ColorTween(begin: Colors.green[800], end: Colors.green[300]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.green[300], end: Colors.green[800]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.green[800], end: Colors.green[300]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.green[300], end: Colors.green[800]),
weight: 1),
]);
final matchedScaleTweenSequence = TweenSequence([
TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.8), weight: 1),
TweenSequenceItem(tween: Tween(begin: 0.8, end: 1.2), weight: 1),
TweenSequenceItem(tween: Tween(begin: 1.2, end: 1.0), weight: 1),
TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.8), weight: 1),
TweenSequenceItem(tween: Tween(begin: 0.8, end: 1.2), weight: 1),
TweenSequenceItem(tween: Tween(begin: 1.2, end: 1.0), weight: 1),
]);
/* unmatched tweens */
final unMatchedFlipTween = Tween(begin: 2.0, end: 1.0);
final unMatchedColorTweenSequence = TweenSequence([
TweenSequenceItem(
tween: ColorTween(begin: Colors.red[300], end: Colors.red[800]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.red[800], end: Colors.red[300]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.red[300], end: Colors.red[800]),
weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.red[800], end: Colors.red[300]),
weight: 1),
]);
/* initial tweens */
final initialColorTweenSequence = TweenSequence([
TweenSequenceItem(
tween: ColorTween(begin: Colors.blue, end: Colors.blue[800]), weight: 1),
TweenSequenceItem(
tween: ColorTween(begin: Colors.blue[800], end: Colors.blue), weight: 1),
]);
/* game over tweens */
final gameOverOpacityTweenSequence = TweenSequence([
TweenSequenceItem(
tween: Tween(begin: 1.0, end: 0.2),
weight: 1,
),
TweenSequenceItem(
tween: Tween(begin: 0.2, end: 1.0),
weight: 1,
),
]);
/* hint tweens */
final hintTweenSequence = TweenSequence([
TweenSequenceItem(
tween: ColorTween(begin: Colors.deepOrange, end: Colors.yellow),
weight: 1,
),
TweenSequenceItem(
tween: ColorTween(begin: Colors.yellow, end: Colors.deepOrange),
weight: 1,
),
]);
/* Anim durations */
const initialAnimDuration = Duration(milliseconds: 1500);
const flipAnimDuration = Duration(milliseconds: 400);
const waitingToMatchAnimDuration = Duration(milliseconds: 1000);
const unMatchedAnimDuration = Duration(milliseconds: 500);
const matchedAnimDuration = Duration(milliseconds: 1500);
const gameOverAnimDuration = Duration(milliseconds: 1000);
/* Scores */
const int forHintedBoxesPoint = 5;
const int normalPoint = 10;
const int initialHintCount = 5;
box_animations.dart
import 'package:flutter/material.dart';
import 'constants.dart';
import 'models/game_box_model.dart';
Animation initialColorAnim(GameBoxModel gameBoxModel) =>
initialColorTweenSequence.animate(gameBoxModel.animController);
Animation flipAnim(GameBoxModel gameBoxModel) =>
flipTween.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.bounceInOut));
Animation waitingToMatchColorAnim(GameBoxModel gameBoxModel) =>
waitingToMatchColorTweenSequence.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.easeInCubic));
Animation waitingToMatchScaleAnim(GameBoxModel gameBoxModel) =>
waitingToMatchScaleTweenSequence.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.easeInCubic));
Animation unMatchedFlipAnim(GameBoxModel gameBoxModel) =>
unMatchedFlipTween.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: const Interval(0.5, 1.0)));
Animation unMatchedColorAnim(GameBoxModel gameBoxModel) =>
unMatchedColorTweenSequence.animate(gameBoxModel.animController);
Animation matchedColorAnim(GameBoxModel gameBoxModel) =>
matchedColorTweenSequence.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.easeInCubic));
Animation matchedScaleAnim(GameBoxModel gameBoxModel) =>
matchedScaleTweenSequence.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.bounceInOut));
Animation gameOverOpacityAnim(GameBoxModel gameBoxModel) =>
gameOverOpacityTweenSequence.animate(CurvedAnimation(
parent: gameBoxModel.animController, curve: Curves.bounceInOut));
Animation hintAnim(GameBoxModel gameBoxModel) => hintTweenSequence.animate(
CurvedAnimation(parent: gameBoxModel.animController, curve: Curves.ease));
helpers.dart
String timeFormat(int second) {
final dur = Duration(seconds: second);
return dur.inMinutes.remainder(60).toString().padLeft(2, '0') +
':' +
dur.inSeconds.remainder(60).toString().padLeft(2, '0');
}
game_box_model.dart
import 'package:flutter/material.dart';
import 'package:memory/constants.dart';
class GameBoxModel {
IconData icon;
bool open;
bool enabled;
bool hint;
GameBoxAnimStatus animStatus;
AnimationController animController;
GameBoxModel(
{required this.icon,
this.open = false,
this.animStatus = GameBoxAnimStatus.initial,
required this.animController,
this.hint = false,
this.enabled = true});
}
game_box.dart
import 'package:flutter/material.dart';
class GameBox extends StatelessWidget {
final IconData icon;
final bool open;
final Color backgroundColor;
final Color borderColor;
final VoidCallback onTap;
const GameBox(
{Key? key,
required this.icon,
this.open = false,
this.backgroundColor = Colors.blue,
this.borderColor = Colors.white,
required this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap(),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [backgroundColor.withOpacity(.6), backgroundColor],
),
borderRadius: BorderRadius.circular(5),
border: Border.all(color: borderColor, width: 4.0),
boxShadow: const [
BoxShadow(
blurRadius: 5, color: Colors.black26, offset: Offset.zero)
]),
height: double.infinity,
width: double.infinity,
child: open ? Icon(icon, color: Colors.white) : const SizedBox.shrink(),
),
);
}
}
home.dart
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:memory/constants.dart';
import 'package:memory/models/game_box_model.dart';
import 'package:memory/pages/home/components/game_box.dart';
import 'package:memory/pages/home/components/hint_button.dart';
import 'package:memory/pages/results/results_page.dart';
import 'dart:math' as math;
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../../box_animations.dart';
import '../../helpers.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
final gameIcons = const [
Icons.add_alert,
Icons.adb,
Icons.airplanemode_active,
Icons.all_inclusive,
Icons.anchor,
Icons.attach_money,
Icons.attachment,
Icons.audiotrack,
Icons.account_balance,
Icons.bluetooth,
Icons.camera,
Icons.child_care,
Icons.family_restroom,
Icons.fitness_center,
Icons.hardware
];
final boxRowCount = 6;
final boxColumnCount = 5;
final _gameBoxModelMatrix = <List<GameBoxModel>>[];
final _openedCouple = <GameBoxModel>[];
final AudioCache audioCache = AudioCache(prefix: 'audios/');
late final Timer _timer;
final ValueNotifier<int> _elapsedTime = ValueNotifier<int>(0);
int _score = 0;
int _moveCount = 0;
int _hintCount = initialHintCount;
String? localAudioCacheURI;
/*
** Initialize game box model matrix
** with random icons
*/
void _initGameBoxModels() {
final randomIndexes =
List.generate(gameIcons.length * 2, (index) => index ~/ 2)..shuffle();
int counter = -1;
for (int r = 0; r < boxRowCount; r++) {
_gameBoxModelMatrix.add([]);
for (int c = 0; c < boxColumnCount; c++) {
counter++;
final gameBoxModel = GameBoxModel(
icon: gameIcons[randomIndexes[counter]],
animController: AnimationController(
vsync: this, duration: initialAnimDuration));
_gameBoxModelMatrix[r].add(gameBoxModel);
gameBoxModel.animController.repeat();
// Add listeners to game boxes animation controllers
gameBoxModel.animController.addListener(() =>
_gameBoxAnimListen(gameBoxModel.animController, gameBoxModel));
}
}
}
/*
** Change game box animation status and duration and play.HomePage
*/
void _changeBoxAnim(GameBoxModel gameBoxModel, GameBoxAnimStatus status,
Duration duration, bool repeat) {
gameBoxModel.animStatus = status;
gameBoxModel.animController.duration = duration;
gameBoxModel.animController.reset();
if (repeat) {
gameBoxModel.animController.repeat();
} else {
gameBoxModel.animController.forward(from: 0);
}
}
/*
** Do when tapped boxes don't match
*/
void _onUnMatched() {
// Hide icons
_openedCouple[0].open = false;
_openedCouple[1].open = false;
// Box animation: unMatched
_changeBoxAnim(_openedCouple[0], GameBoxAnimStatus.unMatched,
unMatchedAnimDuration, false);
_changeBoxAnim(_openedCouple[1], GameBoxAnimStatus.unMatched,
unMatchedAnimDuration, false);
// Play unmatched sound
_playUnMatchedSound();
// If there are hints then clear them
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
setState(() {
box.hint = false;
});
}
}
}
/*
** Do when tapped boxes match
*/
void _onMatched() {
// Box animation: matched
_changeBoxAnim(_openedCouple[0], GameBoxAnimStatus.matched,
matchedAnimDuration, false);
_changeBoxAnim(_openedCouple[1], GameBoxAnimStatus.matched,
matchedAnimDuration, false);
// Enable all game boxes (they were disabled when tapped)
_enableAllGameBoxes();
// Play matched sound
_playMatchedSound();
// Increase score according to boxes hinted status
setState(() {
if (_openedCouple[0].hint || _openedCouple[1].hint) {
_score += forHintedBoxesPoint;
} else {
_score += normalPoint;
}
_openedCouple.clear();
});
}
void _checkMatches() {
// Unmatched
if (_openedCouple[0].icon != _openedCouple[1].icon) {
_onUnMatched();
}
// Matched
else {
_onMatched();
}
}
/*
** When box flip anim ends if one box is open
** turn its animation to waitingToMatch
** if two box is open check match situation, increase move count
*/
void _whenBoxFlipAnimEnd(
AnimationController controller, GameBoxModel gameBoxModel) {
if (controller.isCompleted &&
gameBoxModel.animStatus == GameBoxAnimStatus.flip) {
if (_openedCouple.length == 1) {
_changeBoxAnim(gameBoxModel, GameBoxAnimStatus.waitingToMatch,
waitingToMatchAnimDuration, true);
} else if (_openedCouple.length == 2 &&
_openedCouple[1].animController.isCompleted &&
_openedCouple[1].animStatus == GameBoxAnimStatus.flip) {
setState(() {
_moveCount++;
});
_checkMatches();
}
}
}
/*
** When unmatched animation ends turn opened couple boxes animation
** status to initial and enable all game boxes
*/
void _whenBoxUnMatchedAnimEnd(
AnimationController controller, GameBoxModel gameBoxModel) {
if (controller.isCompleted &&
_openedCouple.length == 2 &&
gameBoxModel.animStatus == GameBoxAnimStatus.unMatched) {
// Turn opened couple boxes animation status to initial
_changeBoxAnim(_openedCouple[0], GameBoxAnimStatus.initial,
initialAnimDuration, true);
_changeBoxAnim(_openedCouple[1], GameBoxAnimStatus.initial,
initialAnimDuration, true);
// For sync
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
if (box.animStatus == GameBoxAnimStatus.initial) {
box.animController.reset();
box.animController.repeat();
}
}
}
_openedCouple.clear();
_enableAllGameBoxes();
}
}
void _gameBoxAnimListen(
AnimationController controller, GameBoxModel gameBoxModel) {
_whenBoxFlipAnimEnd(controller, gameBoxModel);
_whenBoxUnMatchedAnimEnd(controller, gameBoxModel);
}
double _gameArenaSize() {
if (MediaQuery.of(context).size.width > 320) {
return 320;
}
return MediaQuery.of(context).size.width;
}
/*
** Build animated game box with AnimatedBuilder widget.
*/
Widget _buildAnimatedGameBox(GameBoxModel gameBox) {
return AnimatedBuilder(
animation: gameBox.animController,
builder: (context, child) {
switch (gameBox.animStatus) {
case GameBoxAnimStatus.initial:
return GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
borderColor:
!gameBox.hint ? Colors.white : hintAnim(gameBox).value,
backgroundColor: initialColorAnim(gameBox).value,
icon: gameBox.icon,
open: gameBox.open,
);
case GameBoxAnimStatus.flip:
return Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(flipAnim(gameBox).value * math.pi),
child: GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
icon: gameBox.icon,
open: gameBox.open,
),
);
case GameBoxAnimStatus.waitingToMatch:
return Transform.scale(
scale: waitingToMatchScaleAnim(gameBox).value,
child: GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
backgroundColor: waitingToMatchColorAnim(gameBox).value,
icon: gameBox.icon,
open: gameBox.open,
),
);
case GameBoxAnimStatus.matched:
return Transform.scale(
scale: matchedScaleAnim(gameBox).value,
child: GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
backgroundColor: matchedColorAnim(gameBox).value,
icon: gameBox.icon,
open: gameBox.open,
),
);
case GameBoxAnimStatus.unMatched:
return Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(
unMatchedFlipAnim(gameBox).value * math.pi),
child: GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
backgroundColor: unMatchedColorAnim(gameBox).value,
icon: gameBox.icon,
open: gameBox.open,
));
case GameBoxAnimStatus.gameOver:
return Opacity(
opacity: gameOverOpacityAnim(gameBox).value,
child: GameBox(
onTap: () {
if (gameBox.enabled) {
_onTapGameBox(gameBox);
}
},
backgroundColor: matchedColorAnim(gameBox).value,
icon: gameBox.icon,
open: gameBox.open,
),
);
}
});
}
/*
** Build game boxes
*/
Column _buildGameArena() {
return Column(
children: List.generate(
_gameBoxModelMatrix.length,
(r) => Expanded(
child: Row(
children: List.generate(_gameBoxModelMatrix[r].length, (c) {
final gameBoxModel = _gameBoxModelMatrix[r][c];
return Expanded(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: _buildAnimatedGameBox(gameBoxModel),
),
);
})),
)),
);
}
/*
** When tapped the game box
*/
void _onTapGameBox(GameBoxModel gameBoxModel) {
setState(() {
// Can't tap if the box is open
if (!gameBoxModel.open) {
// Play flip anim
_changeBoxAnim(
gameBoxModel, GameBoxAnimStatus.flip, flipAnimDuration, false);
gameBoxModel.open = true;
// Add the opened box to the opened couple list
_openedCouple.add(gameBoxModel);
// Play flip sound
_playFlipSound();
// If there are two opened boxes then disable all game boxes
if (_openedCouple.length == 2) {
_disableAllGameBoxes();
}
}
// Check wheter game over
_gameOverControl();
});
}
/*
** When tapped hint button
*/
void _onTapHintButton() {
// If there is no opened box
if (_openedCouple.isEmpty) {
var icons = [];
// Add all closed boxes icons to icons list
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
if (!box.open) {
icons.add(box.icon);
}
}
}
// Randomize
icons.shuffle();
// Random icon from icons list
final randomIcon = icons[0];
// Show hints
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
if (!box.open) {
setState(() {
if (box.icon == randomIcon) {
box.hint = true;
} else {
box.hint = false;
}
});
}
}
}
// Decrease hint count
setState(() {
_hintCount--;
});
}
}
/*
** Check game over and show results page
*/
void _gameOverControl() {
// If all game boxes are open then change their animation status to gameOver
final gameOver = _gameBoxModelMatrix
.every((gameBoxes) => gameBoxes.every((box) => box.open));
if (gameOver) {
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
_changeBoxAnim(
box, GameBoxAnimStatus.gameOver, gameOverAnimDuration, true);
}
}
_showResultsPage();
}
}
void _disableAllGameBoxes() {
setState(() {
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
box.enabled = false;
}
}
});
}
void _enableAllGameBoxes() {
setState(() {
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
box.enabled = true;
}
}
});
}
void _resetGame() {
setState(() {
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
box.enabled = true;
box.open = false;
box.hint = false;
_changeBoxAnim(
box, GameBoxAnimStatus.initial, initialAnimDuration, true);
}
}
_openedCouple.clear();
_score = 0;
_moveCount = 0;
_elapsedTime.value = 0;
_hintCount = initialHintCount;
});
}
/*
** Show results page. When user returns to home page
** Reset game.
*/
Future<void> _showResultsPage() async {
await Future.delayed(const Duration(milliseconds: 1300));
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ResultsPage(
score: _score,
totalSeconds: _elapsedTime.value,
moveCount: _moveCount)));
_resetGame();
}
/*
** If platform is Android play sound
*/
Future<void> _playSound(String path) async {
if (defaultTargetPlatform == TargetPlatform.android) {
final file = await audioCache.loadAsFile(path);
final bytes = await file.readAsBytes();
audioCache.playBytes(bytes);
}
}
void _playFlipSound() {
_playSound('flip.mp3');
}
void _playMatchedSound() {
_playSound('matched.mp3');
}
void _playUnMatchedSound() {
_playSound('unmatched.mp3');
}
@override
void initState() {
_initGameBoxModels();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_elapsedTime.value++;
});
super.initState();
}
@override
void dispose() {
// Dispose all game box animation controllers
for (var gameBoxes in _gameBoxModelMatrix) {
for (var box in gameBoxes) {
box.animController.dispose();
}
}
_timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Memory Game',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text('SCORE: $_score'),
],
),
),
Center(
child: SizedBox(
width: _gameArenaSize(),
height: _gameArenaSize(),
child: _buildGameArena()),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ValueListenableBuilder(
valueListenable: _elapsedTime,
builder:
(BuildContext context, int value, Widget? child) {
return Text('Passing Time: ${timeFormat(value)}');
}),
const SizedBox(height: 10),
Text('Moves: $_moveCount'),
if (_hintCount > 0) const SizedBox(height: 10),
if (_hintCount > 0)
HintButton(
hintCount: _hintCount,
onTap: _onTapHintButton,
),
],
),
),
],
),
),
);
}
}
hint_button.dart
import 'package:flutter/material.dart';
class HintButton extends StatelessWidget {
final VoidCallback onTap;
final int hintCount;
const HintButton({Key? key, required this.onTap, required this.hintCount})
: super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
right: 0,
top: 0,
child: Container(
width: 20,
height: 20,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.all(4.0),
child: Text(
'$hintCount',
style: const TextStyle(fontSize: 12, color: Colors.white),
),
)),
IconButton(
icon: const Icon(Icons.tungsten),
onPressed: () => onTap(),
),
],
);
}
}
results_page.dart
import 'package:flutter/material.dart';
import 'package:memory/helpers.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ResultsPage extends StatefulWidget {
final int score;
final int totalSeconds;
final int moveCount;
const ResultsPage(
{Key? key,
required this.score,
required this.totalSeconds,
required this.moveCount})
: super(key: key);
@override
State<ResultsPage> createState() => _ResultsPageState();
}
class _ResultsPageState extends State<ResultsPage> {
static const String bestScoreKey = 'best_score';
double _bestScore = 0;
double _totalScore() {
return widget.score - (widget.moveCount * 0.5 + widget.totalSeconds * 0.1);
}
Future<SharedPreferences> _prefs() async {
return await SharedPreferences.getInstance();
}
Future<void> _setBestScore(double bestScore) async {
final sharedPreferences = await _prefs();
await sharedPreferences.setDouble(bestScoreKey, bestScore);
}
Future<void> _getBestScore() async {
final sharedPreferences = await _prefs();
double bs = sharedPreferences.getDouble(bestScoreKey) ?? _totalScore();
if (bs > _totalScore()) {
setState(() {
_bestScore = bs;
});
} else {
setState(() {
_bestScore = _totalScore();
});
_setBestScore(_bestScore);
}
}
IconButton _playAgainButton() {
return IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
Navigator.pop(context);
},
);
}
@override
void initState() {
super.initState();
_getBestScore();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Text('Score: ${widget.score}'),
const SizedBox(
height: 10,
),
Text('Time: ' + timeFormat(widget.totalSeconds)),
const SizedBox(
height: 10,
),
Text('Moves: ${widget.moveCount}'),
const SizedBox(
height: 10,
),
Text('Total Score: ${_totalScore()}'),
const SizedBox(
height: 10,
),
Text('Best Score: $_bestScore'),
const SizedBox(
height: 50,
),
_playAgainButton(),
const Spacer(),
],
),
),
);
}
}