How I built a math game using Flutter & Firebase

offlineprogrammer

Offline Programmer

Posted on March 14, 2021

How I built a math game using Flutter & Firebase

You might know that I am currently learning Flutter. I watched few YouTube tutorials, wrote few posts about my learnings, and signed up for the #30DaysOfFlutter. I decided to take my learning journey to the next level by actually implementing something

BrainTrainer is a math game where you have 30 seconds to solve as many questions as possible. Your highest score will be displayed on the app bar and saved in Firebase.

BrainTrainer

The game has two screens in total. On main.dart we watch the firebase user to determine which screen to display.


class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final firebaseUser = context.watch<User>();
    return Scaffold(
        appBar: AppBar(
          title: Text('BrainTrainer'),
        ),
        body: firebaseUser != null ? loadGameScreen(context) : LoginPage());
  }

  Future<Object> loadGameScreen(BuildContext context) {
    Future.delayed(Duration.zero, () {
      return Navigator.of(context).pushNamedAndRemoveUntil(
          GameScreen.routeName, (Route<dynamic> route) => false);
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

We are using a provider to enable (Apple Sign In) using Firebase. check my previous post on that

We are using few widgets in the game:

ActionButtons Widget: This is where we show the play button to start the game. we also use to indicate to the player if their selected answer is correct or now


import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ActionButtons extends StatefulWidget {
  const ActionButtons({
    Key key,
  }) : super(key: key);

  @override
  _ActionButtonsState createState() => _ActionButtonsState();
}

class _ActionButtonsState extends State<ActionButtons> {
  @override
  Widget build(BuildContext context) {
    final _game = Provider.of<Game>(context);
    return SizedBox(
      height: 100,
      child: GestureDetector(
        onTap: () {
          _game.playTheGame();
        },
        child: Card(
          margin: EdgeInsets.all(10),
          elevation: 8,
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
              boxShadow: [
                BoxShadow(color: Colors.white, spreadRadius: 3),
              ],
            ),
            padding: EdgeInsets.all(10),
            child: Consumer<Game>(
              builder: (context, game, child) {
                return Image.asset(
                  _game.actionButtonImage,
                  fit: BoxFit.cover,
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

AnswerItem Widget: This represents an item on a grid to display a number for the player to select. One of those items will be the correct answer.


import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class AnswerItem extends StatelessWidget {
  final Answer answer;
  final int index;

  static const _answercolot = [
    Color.fromRGBO(224, 81, 98, 1),
    Color.fromRGBO(84, 160, 86, 1),
    Color.fromRGBO(68, 150, 224, 1),
    Color.fromRGBO(111, 64, 222, 1),
  ];

  const AnswerItem({Key key, this.answer, this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _game = Provider.of<Game>(context);
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: GridTile(
        child: GestureDetector(
          onTap: () {
            _game.answerSelected(this.answer);
          },
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
              color: _answercolot[this.index],
              boxShadow: [
                BoxShadow(color: Colors.white, spreadRadius: 3),
              ],
            ),
            padding: EdgeInsets.all(10),
            child: Center(
                child: Text(
              answer.value.toString(),
              style: TextStyle(
                  fontSize: 24,
                  fontStyle: FontStyle.italic,
                  fontWeight: FontWeight.bold),
            )),
          ),
        ),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

AnswersGrid Widget: A grid of 4 AnswerItem widgets.


import 'package:brain_trainer_app/models/game.dart';
import 'package:brain_trainer_app/widgets/answer_item.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class AnswersGrid extends StatelessWidget {
  @override
  @override
  Widget build(BuildContext context) {
    final gameData = Provider.of<Game>(context);
    final answers = gameData.answers;
    return GridView.builder(
        padding: const EdgeInsets.all(10.0),
        itemCount: answers.length,
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            childAspectRatio: 3 / 2,
            crossAxisSpacing: 10,
            mainAxisSpacing: 10),
        itemBuilder: (BuildContext context, int index) {
          return AnswerItem(
            answer: answers[index],
            index: index,
          );
        });
  }
}


Enter fullscreen mode Exit fullscreen mode

GameAds Widget: This is where we display Google AdMob Ads


import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

class GameAds extends StatefulWidget {
  @override
  _GameAdsState createState() => _GameAdsState();
}

class _GameAdsState extends State<GameAds> {
  BannerAd _bannerAd;

  @override
  void initState() {
    super.initState();
    MobileAds.instance.initialize();
    _bannerAd = BannerAd(
      size: AdSize.banner,
      adUnitId: BannerAd.testAdUnitId,
      listener: AdListener(),
      request: AdRequest(),
    );
    _bannerAd..load();
  }

  @override
  void dispose() {
    super.dispose();
    _bannerAd.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(height: 50, child: AdWidget(ad: _bannerAd));
  }
}


Enter fullscreen mode Exit fullscreen mode

GameConfetti Widget: We use this fun widget to display a celebration animation when the play finish the game



import 'package:brain_trainer_app/models/game.dart';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rflutter_alert/rflutter_alert.dart';

class GameConfetti extends StatefulWidget {
  @override
  _GameConfettiState createState() => _GameConfettiState();
}

class _GameConfettiState extends State<GameConfetti> {
  ConfettiController _controllerCenter;

  @override
  void initState() {
    _controllerCenter =
        ConfettiController(duration: const Duration(seconds: 10));
    _controllerCenter.play();
    super.initState();
  }

  @override
  void dispose() {
    _controllerCenter.dispose();
    super.dispose();
  }

  _showAlert(context) {
    Future.delayed(Duration.zero, () async {
      final String _gameMsg =
          Provider.of<Game>(context, listen: false).completionMsg;
      var alertStyle = AlertStyle(
        animationType: AnimationType.fromTop,
        isCloseButton: false,
        isOverlayTapDismiss: false,
        descStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        descTextAlign: TextAlign.start,
        animationDuration: Duration(milliseconds: 400),
        overlayColor: Colors.transparent,
        alertBorder: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(0.0),
          side: BorderSide(
            color: Colors.grey,
          ),
        ),
        titleStyle: TextStyle(
          color: Colors.green,
        ),
        alertAlignment: Alignment.center,
      );

      Alert(
        style: alertStyle,
        context: context,
        type: AlertType.success,
        title: "Well done",
        desc: _gameMsg,
        buttons: [
          DialogButton(
            child: Text(
              "Brilliant!!!",
              style: TextStyle(color: Colors.white, fontSize: 16),
            ),
            onPressed: () => Navigator.pop(context),
            width: 120,
          )
        ],
      ).show();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topCenter,
      child: ConfettiWidget(
          confettiController: _controllerCenter,
          blastDirectionality: BlastDirectionality
              .explosive, // don't specify a direction, blast randomly
          shouldLoop: false,
          gravity: 0.5,
          emissionFrequency: 0.05,
          numberOfParticles:
              20, // start again as soon as the animation is finished
          colors: const [
            Colors.green,
            Colors.blue,
            Colors.pink,
            Colors.orange,
            Colors.purple
          ], // manually specify the colors to be used
          child: _showAlert(context)
          ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

TimerQuestionScoreRow Widget: Here, we show the 30s timer, The math question, and we keep track of the player score/


import 'package:brain_trainer_app/models/game.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class TimerQuestionScoreRow extends StatefulWidget {
  const TimerQuestionScoreRow({
    Key key,
  }) : super(key: key);

  @override
  _TimerQuestionScoreRowState createState() => _TimerQuestionScoreRowState();
}

class _TimerQuestionScoreRowState extends State<TimerQuestionScoreRow> {
  @override
  Widget build(BuildContext context) {
    final _game = Provider.of<Game>(context);
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Expanded(
          flex: 3,
          child: SizedBox(
            height: 100,
            child: Card(
              margin: EdgeInsets.all(10),
              elevation: 8,
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Color.fromRGBO(255, 152, 0, 1),
                  boxShadow: [
                    BoxShadow(color: Colors.white, spreadRadius: 3),
                  ],
                ),
                padding: EdgeInsets.all(10),
                child: Consumer<Game>(
                  builder: (context, game, child) {
                    return Center(
                        child: Text(
                      '${game.timer}s',
                      style:
                          TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                    ));
                  },
                ),
              ),
            ),
          ),
        ),
        Expanded(
          flex: 4,
          child: SizedBox(
            height: 100,
            child: Card(
              margin: EdgeInsets.all(10),
              elevation: 8,
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  boxShadow: [
                    BoxShadow(color: Colors.white, spreadRadius: 3),
                  ],
                ),
                padding: EdgeInsets.all(10),
                child: Consumer<Game>(
                  builder: (context, game, child) {
                    return Center(
                        child: Text(
                      '${game.question}',
                      style:
                          TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                    ));
                  },
                ),
              ),
            ),
          ),
        ),
        Expanded(
          flex: 3,
          child: SizedBox(
            height: 100,
            child: Card(
              margin: EdgeInsets.all(10),
              elevation: 8,
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Color.fromRGBO(3, 169, 244, 1),
                  boxShadow: [
                    BoxShadow(color: Colors.white, spreadRadius: 3),
                  ],
                ),
                padding: EdgeInsets.all(10),
                child: Consumer<Game>(
                  builder: (context, game, child) {
                    return Center(
                        child: Text(
                      '${game.score}',
                      style:
                          TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                    ));
                  },
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}



Enter fullscreen mode Exit fullscreen mode

The game use three models:

Game: This is the primary model where we set up the game. Here we also verify the selected answer and keep track of the timer.

Answer: This is a simple model to represent the answer value

Player: This is where we have the player information we get from Firebase, and we also use it to track the high score.

We are using DataRepository class to retrieves and saves the data.


import 'package:brain_trainer_app/models/player.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class DataRepository {
  final CollectionReference collection =
      FirebaseFirestore.instance.collection('players');

  Future<void> addPlayer(Player player) {
    return collection.doc(player.uid).set(player.toJson());
  }

  updatePlayer(Player player) async {
    await collection.doc(player.uid).update(player.toJson());
  }

  Future<Player> getPlayer(String uid) async {
    var doc = await collection.doc(uid).get();
    if (doc.data() == null) {
      return null;
    }
    return Player.fromJson(doc.data());
  }
}


Enter fullscreen mode Exit fullscreen mode

The game is on Apple Store here and Google Play here

Check the code here

brain_trainer_app

Do you enjoy math? Do you enjoy games? Want to relax and train your brain? Try this amazing app for some fun – Brain Trainer.

Brain Trainer consists of simple math-based games designed to exercise memory, speed, and attention. Take a break from your busy schedule and use Brain Trainer to relax and practice some math.






Follow me on Twitter for more tips about #coding, #learning, #technology...etc.

Check my Apps on Google Play

Cover image Amol Tyagi on Unsplash

💖 💪 🙅 🚩
offlineprogrammer
Offline Programmer

Posted on March 14, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related