Search code examples
flutterdartstate-management

How to check game state globals for values instantly?


I am messing with a game in Flutter. I have a few global variables that keep track of if the player clicked some dots or not. But I realized that it only updates the global values in tandem with the global timer and doesn't trigger the win condition instantly or register a tap instantly.

Is there a way for the global tap variable to update and check at time of tapping so that it triggers the win condition right away? I also know my timer is a little wonky but it works for now.

Player Grid

import 'dart:math';

import 'package:flutter/material.dart';

import 'global.dart' as global;

class Dot extends StatefulWidget {
  final String itemText;

  const Dot({super.key, required this.itemText});

  @override
  State<Dot> createState() => _DotState();
}

class _DotState extends State<Dot> {
  bool tapped = false;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: increaseTap,
      child: tapped == false
          ? CircleAvatar(
              backgroundColor: randomColor(),
              radius: 10,
              child: Text(widget.itemText),
            )
          : const SizedBox(
              width: 20,
              height: 20,
            ),
    );
  }

  Color randomColor() {
    return Colors.primaries[Random().nextInt(Colors.primaries.length)];
  }

  void increaseTap() {
    global.tapCount++;
    setState(() {
      tapped = true;
    });
  }
}

class GameGrid extends StatelessWidget {
  const GameGrid({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '1'),
            Dot(itemText: '2'),
            Dot(itemText: '3'),
            Dot(itemText: '4'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '5'),
            Dot(itemText: '6'),
            Dot(itemText: '7'),
            Dot(itemText: '8'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '9'),
            Dot(itemText: '10'),
            Dot(itemText: '11'),
            Dot(itemText: '12'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '13'),
            Dot(itemText: '14'),
            Dot(itemText: '15'),
            Dot(itemText: '16'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '17'),
            Dot(itemText: '18'),
            Dot(itemText: '19'),
            Dot(itemText: '20'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '21'),
            Dot(itemText: '22'),
            Dot(itemText: '23'),
            Dot(itemText: '24'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: const [
            Dot(itemText: '25'),
            Dot(itemText: '26'),
            Dot(itemText: '27'),
            Dot(itemText: '28'),
          ],
        ),
      ],
    );
  }
}

This is where I started building the game state management

Game State

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'global.dart' as global;

import 'package:flutter/services.dart';
import 'gamegrid.dart';

class GameState extends StatefulWidget {
  const GameState({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _GameStateState createState() => _GameStateState();
}

class _GameStateState extends State<GameState> {
  //Game Loop Variables
  bool gameRunning = false;
  bool timerRunning = false;

  @override
  void initState() {
    Timer.periodic(const Duration(seconds: 1), (Timer time) {
      setState(() {
        if (gameRunning == true && timerRunning == true) {
          global.timer--;
        }
      });
    });

    //Hide android status bar
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);

    super.initState();
  }

  void setGameVarables() {
    setState(() {
      global.timer = 5;
      global.tapCount = 0;
      gameRunning = true;
      timerRunning = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          body: Container(
        color: Colors.black,
        child: Stack(
          children: [
            getScreen(global.timer, gameRunning, global.tapCount),
            debugWidget(),
          ],
        ),
      )),
    );
  }

  //Get Screen
  Widget getScreen(int timer, bool gameRunning, int tapCount) {
    if (timer > 0 && gameRunning == true && tapCount == 5) {
      return nextLevel();
    } else if (timer > 0 && gameRunning == true) {
      return const GameGrid();
    } else if (gameRunning == true && timer < 0 || timer == 0) {
      return gameover();
    } else {
      return menu();
    }
  }

  //Menu
  Widget menu() {
    return Center(
      child: TextButton(
        style: TextButton.styleFrom(
          textStyle: const TextStyle(fontSize: 20),
        ),
        onPressed: setGameVarables,
        child: const Text('Play Glo'),
      ),
    );
  }

  //Next Level Screen
  Widget nextLevel() {
    setState(() {
      timerRunning = false;
    });

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextButton(
            style: TextButton.styleFrom(
              textStyle: const TextStyle(fontSize: 20),
            ),
            onPressed: setGameVarables,
            child: const Text('Next Level'),
          ),
        ],
      ),
    );
  }

//GAME OVER SCREEN
  Widget gameover() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text(
            'GAME OVER',
            style: TextStyle(fontSize: 20),
          ),
          TextButton(
            style: TextButton.styleFrom(
              textStyle: const TextStyle(fontSize: 20),
            ),
            onPressed: setGameVarables,
            child: const Text('Play Again'),
          ),
          TextButton(
            style: TextButton.styleFrom(
              textStyle: const TextStyle(fontSize: 20),
            ),
            onPressed: () {
              setState(() {
                gameRunning = false;
                timerRunning = false;
              });
            },
            child: const Text('Main Menu'),
          ),
        ],
      ),
    );
  }

  Widget debugWidget() {
    if (kDebugMode) {
      return Positioned(
        left: 0,
        top: 50,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Row(
              children: [
                Text(
                  'Timer: ${global.timer}',
                  style: const TextStyle(fontSize: 32, color: Colors.white),
                ),
                Text(
                  'Taps: ${global.tapCount}',
                  style: const TextStyle(fontSize: 32, color: Colors.white),
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(
                  'GR: $gameRunning',
                  style: const TextStyle(fontSize: 32, color: Colors.white),
                ),
                Text(
                  'TR: $timerRunning',
                  style: const TextStyle(fontSize: 32, color: Colors.white),
                ),
              ],
            ),
          ],
        ),
      );
    } else {
      return Container();
    }
  }
}

I was expecting the state to update and check for update instantly.


Solution

  • For some reason I didn't realize/think that using a double for the timer and updating milliseconds would work. Facepalm moment

    Timer.periodic(const Duration(milliseconds: 100), (Timer time) {
      setState(() {
        if (gameRunning == true && timerRunning == true) {
          global.timer -= .1;
        }
      });
    });