Blogs

Try it Out: Math Riddle Game in Flutter

Category
Software development
Try it Out: Math Riddle Game in Flutter

It’s time to play and build a simple math riddle game using the Flutter framework.

The game consists of five equations. Four display correct results and in the fifth one, the result will be overridden with an X.

Below the equations, we’ll build the input field for the result (X). So, when the player enters a correct answer in this input, a success toast will be shown and the player will be redirected to the next level. 

Prerequisites for the Flutter game

If you would like to follow this blog, you should install Flutter SDK and choose the code editor. I will be using Visual studio code and the iOS simulator.

If you are using Windows or Linux, you can use the android emulator coming from Android studio. Also, before you start, you should know Dart basics and be familiar with Scaffold, Column, Row, Center, Padding, Text, TextField, Card, and AlertDialog widgets. You can find them all in the Flutter widget catalog.

Getting started with a math riddle game

We are going to start with creating a new Flutter project using the Flutter command in the terminal: 

flutter create math_riddle

Inside the lib folder in the main.dart file default Counter app will be created. We don’t need that code. Delete all excess code and comments in MyHomePage stateful widget. Keep the Scaffold widget which has the Column widget as a child, and wrap the Column with the Center widget.

math riddle game
import 'package:flutter/material.dart';
 
void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(),
      home: const MyHomePage(),
    );
  }
}
 
class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);
 
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const <Widget>[],
        ),),
);}}

Adding assets

After we’ve cleaned the code, we can add assets to our project. At the root of the project, we are going to create an assets folder, and inside we’ll add folders for icons and gifs. Like this:

Also, we’ll register a path to assets in our pubspec.yaml file. 

flutter:
  uses-material-design: true
  assets:
    - assets/icons/
    - assets/gifs/

In the lib folder, create assets.dart file.

Generate a list that contains fruit icons and a map that contains mathematical operators.

List<String> fruitIconsList = [
  'assets/icons/apple.svg',
  'assets/icons/citrus.svg',
  'assets/icons/tomato.svg',
];
 
Map<String, String> operationIconsMap = {
  'addIcon': 'assets/icons/add.svg',
  'substractIcon': 'assets/icons/substract.svg',
  'equalIcon': 'assets/icons/equal.svg',
};

Adding packages

Now, let’s install the packages which we’ll need for this project.

Add packages with these commands:

  • flutter pub add google_fonts
  • flutter pub add flutter_svg

These commands will install google_fonts and flutter_svg packages in the project. You should be able to find them in pubspec.lock file, that is if the installation went successfully.

We’ll be using the google_fonts package inside the Text widget for styling purposes, and the flutter_svg package for displaying SVG images.

Coding

Inside main.dart file create Fruit class:

class Fruit {
  final String icon;
  final int value;
  Fruit({
    required this.icon,
    required this.value,
  });
}

Inside the build method create a fruits variable which will be a List of Fruit objects. Map through the fruitIconsList and for every item return a Fruit object with an icon from fruitIconsList, and a random value from 0 to 5.

List<Fruit> fruits = fruitIconsList.map(
      (fruit) {
        return Fruit(
          icon: fruit,
          value: Random().nextInt(5),
        );
      },
    ).toList();

We’ll be using this list for adding fruit icons to our equations widgets.

Also, we need a function for operands so let’s create a function that returns a list of operations. 

 List<Operation> asignOperationValues() {
    List<Operation> operations = [
      Operation(icon: operationIconsMap['addIcon']!, value: '+'),
Operation(icon: operationIconsMap['substractIcon']!, value: '-'),
    ]..shuffle();
    return operations.map(
      (operation) {
        return Operation(
          icon: operation.icon,
          value: operation.value,
        );
      },
    ).toList();
  }

And, we need to create a function that will calculate the equation result.

 String findResult(
    Operation leadOperation,
    Operation sufixOperation,
    Fruit leadFruit,
    Fruit midFruit,
    Fruit sufixFruit,
  ) {
    {
      if (leadOperation.value == '+' && sufixOperation.value == '+') {
        return (leadFruit.value + midFruit.value + sufixFruit.value).toString();
      } else if (leadOperation.value == '+' && sufixOperation.value == '-') {
        return (leadFruit.value + midFruit.value - sufixFruit.value).toString();
      } else if (leadOperation.value == '-' && sufixOperation.value == '+') {
        return (leadFruit.value - midFruit.value + sufixFruit.value).toString();
      } else {
        return (leadFruit.value - midFruit.value - sufixFruit.value).toString();
      }
    }
  }

Now, we are ready to create _buildRow() function which will return a Row widget with our equation. _buildRow() function takes a list of Fruit objects, a list of operations, and isQuestion nullable boolean variable.

Widget _buildRow({
    required List<Fruit> fruitValues,
    required List<Operation> operations,
    bool? isQuestion = false,
  }) {
    Fruit leadFruit = fruitValues[0];
    Fruit midFruit = fruitValues[1];
    Fruit sufixFruit = fruitValues[2];
    Operation leadOperation = operations[0];
    Operation sufixOperation = operations[1];
    if (isQuestion!) {
      result = findResult(
        leadOperation,
        sufixOperation,
        leadFruit,
        midFruit,
        sufixFruit,
      );
    }
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        SvgPicture.asset(
          leadFruit.icon,
          height: 45,
        ),
        SvgPicture.asset(
          leadOperation.icon,
          height: 25,
        ),
        SvgPicture.asset(
          midFruit.icon,
          height: 45,
        ),
        SvgPicture.asset(
          sufixOperation.icon,
          height: 25,
        ),
        SvgPicture.asset(
          sufixFruit.icon,
          height: 45,
        ),
        SvgPicture.asset(
          operationIconsMap['equalIcon']!,
          height: 25,
        ),
        Text(
          isQuestion
              ? 'X'
              : findResult(
                  leadOperation,
                  sufixOperation,
                  leadFruit,
                  midFruit,
                  sufixFruit,
                ),
          style: GoogleFonts.play(
              textStyle: const TextStyle(
            fontSize: 46,
            fontWeight: FontWeight.w800,
            color: Colors.green,
          )),
        ),
      ],
    );  }

We’ll call _buildRow() function inside the build method, five times.

 _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: true,
                    ),

“.. ” is known as cascade notation.

This often saves you the step of creating a temporary variable, and allows you to write more fluid code. and get this output:

Now it’s time to create _buildResultInput() function which will check that the entered value is correct. If the entered value is correct, _buildResultInput() function will show a dialog with a success message and lead you to the next level.

Widget _buildResultInput() {
    return Column(
      children: [
        Text(
          'Guess X:',
          style: GoogleFonts.play(
              textStyle: const TextStyle(
            fontSize: 46,
            fontWeight: FontWeight.w800,
            color: Colors.black,
          )),
        ),
        const SizedBox(height: 10),
        TextField(
          controller: textEditingController,
          onChanged: (String value) {
            if (value == result) {
              showDialog(
                  context: context,
                  builder: (BuildContext context) {
                    return AlertDialog(
                      actionsAlignment: MainAxisAlignment.center,
                      content: Image.asset('assets/gifs/completed.gif'),
                      actions: <Widget>[
                        ElevatedButton(
                          child: Text('Start level ${level + 1}'),
                          onPressed: () {
                            textEditingController.clear();
                            setState(() {
                              level++;
                            });
                            Navigator.pop(context);
                          },
                        ),
                      ],
                    );
                  });
            }
          },
          cursorColor: Colors.green,
          decoration: const InputDecoration(
            fillColor: Colors.white,
            filled: true,
            contentPadding: EdgeInsets.all(12),
            enabledBorder: OutlineInputBorder(
              borderSide: BorderSide(color: Colors.green),
            ),
            focusedBorder: OutlineInputBorder(
              borderSide: BorderSide(color: Colors.green),
            ),
          ),
          keyboardType: TextInputType.number,
          style: GoogleFonts.play(
              textStyle: const TextStyle(
            fontSize: 46,
            fontWeight: FontWeight.w800,
            color: Colors.black,
          )),
          textAlign: TextAlign.center,
        )
      ],
    );
  }

Also, call this function inside the build method.

Notch Math Riddle game in Flutter

Finishing layout

Now it’s time to add some layout details. Let’s add two texts that will be displaying the current game level and the “Can you solve this riddle?” question. We also want to add some padding and background color.

In the build method, we want this:

Scaffold(
      backgroundColor: Colors.blueGrey[100],
      body: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Level: $level',
              style: GoogleFonts.play(
                textStyle: const TextStyle(
                  fontSize: 46,
                  fontWeight: FontWeight.w800,
                  color: Colors.black,
                ),
              ),
              textAlign: TextAlign.center,
            ),
            Text(
              'Can you solve this riddle ?',
              style: GoogleFonts.play(
                textStyle: const TextStyle(
                  fontSize: 46,
                  fontWeight: FontWeight.w800,
                  color: Colors.green,
                ),
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 24),
            Card(
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(vertical: 16, horizontal: 4),
                child: Column(
                  children: [
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: false,
                    ),
                    _buildRow(
                      fruitValues: fruits..shuffle(),
                      operations: asignOperationValues(),
                      isQuestion: true,
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 12),
            _buildResultInput()
          ],
        ),
      ),
    );

Finally, the riddle game is done!

Starting the game

If you want to start your application, you can run the flutter devices command in the terminal, this command will display a list of devices available for starting your flutter project.

You can pick one and type in terminal: flutter run -d iPhone, and your application will start.

Or you can create a launch.json file as shown below and run the simulator on the green play button.

{
   "version": "0.2.0",
   "configurations": [
       {
           "name": "iPhone Simulator DEV",
           "request": "launch",
           "type": "dart",
           "args": [ "iPhone", "--dart-define", "ENVIRONMENT=dev"]
       }
   ]
}

The application will start and you will see the game screens as shown here: 

Math riddle game in Flutter Notch

Working example

Link to the repository with the fully working example. Check it out and hope you had fun creating this little math riddle in Flutter!

Next

Blog

Way to Go: Combining Go and Gradle Plugins

Combining Go and Gradle Notch

Company

Our people really love it here

Evolution of expertise

The agency was founded in 2014 by seasoned industry veterans with experience from large enterprises. A group of Java engineers then evolved into a full-service company that builds complex web and mobile applications. 

Flexibility is our strong suit — both large enterprises and startups use our services. We are now widely recognized as the leading regional experts in the Java platform and Agile approach.

We make big things happen and we are proud of that.

Personal development

If you join our ranks you’ll be able hone your coding skills on relevant projects for international clients.

Our diverse client portfolio enables our engineers to work on complex long term projects like core technologies for Delivery Hero, Blockchain and NFTs for Fantasy Football or cockpit interface for giants like Strabag. 

Constant education and knowledge sharing are part of our company culture. Highly experienced mentors will help you out and provide real-time feedback and support.

Contact

We’d love to hear from you