简体   繁体   中英

Flutter Countdown Timer for Chess App ( stuck in the logic)

So from past few day I have been trying to get around this logical loop, and have tried everything I could and then finally decided to post it here (hence made my debut here). I am stuck, all the help is appreciated. Thanks !

Logic : In the app there are 2 container which displays White's and black's time, which is selected from a given five options -> 5,10,15,30,60, which then update's the time to display in those containers, used a provider package for this and everything else.

Now I have also added a Raised button named 'switch', which when pressed was supposed to:

  1. Start's the white's countdown timer
  2. Then if pressed again should stops white's timer and then start Black's countdown timer
  3. Then if pressed again should stop the black's timer and then starts white's timer.
  4. And repeat till the timer runs out and one out of the two is declared a winner.

Problem: So with what I coded so far, when 'switch' is pressed it starts white's timer and if pressed again stops white's timer but it doesn't begins black's timer. I know its because of the way I have framed the if() conditions and also because I don't know how to stop the timer from outside. What I have done is to use a - bool checkTimerW and checkTimerB for each white and black, which I check in the if() condition to cancel the timer is based on it.

Code:

Provider -

import 'dart:async';

import 'package:flutter/foundation.dart';

class SettingsProvider extends ChangeNotifier {
  int valueW = 0;
  int valueB = 0;

// * with these booleans we will stop the timer.
  bool checkTimerW = true;
  bool checkTimerB = true;


  String timeToDisplayW = ""; // for white
  String timeToDisplayB = ""; // for black

  bool switchT = false;

   // this is called in the settings Modal Bottom Sheet
  void changeValue(int valW, int valB) {
    
   //? Changing the value in seconds
    valueW = valW * 60;
    valueB = valB * 60;

    print(valueW);

    timeToDisplayW = valueW.toString();
    timeToDisplayB = valueB.toString();
    notifyListeners();
  }

  void reset() {
    started = true;
    stopped = true;
    checkTimerW = false;
    checkTimerB = false;
    notifyListeners();
  }

  void toggleSwitch() {
    if (switchT == false) {
      switchT = true;
      print('true');
    } else if (switchT == true) {
      switchT = false;
      print('false');
    }
  }

  void switchTimer() {
    if (switchT == false) {

      // Starts white's timer

      Timer.periodic(
        Duration(seconds: 1),
        (Timer t) {
          if (valueW <= 1 || checkTimerW == false) {
            t.cancel();
            checkTimerW = true;

            // TODO : Black Won

            notifyListeners();
          } else {
            valueW = valueW - 1;
            notifyListeners();
          }

          timeToDisplayW = valueW.toString();
          notifyListeners();
        },
      );

      // stops black's timer

      checkTimerB = false;
      toggleSwitch();
      notifyListeners();

    } else {

      // Starts black's timer

      Timer.periodic(
        Duration(seconds: 1),
        (Timer t) {
          if (valueB <= 1 || checkTimerB == false) {
            t.cancel();
            checkTimerB = true;

            // TODO : White won

            notifyListeners();
          } else {
            valueB = valueB - 1;
            notifyListeners();
          }

          timeToDisplayB = valueB.toString();
          notifyListeners();
        },
      );

      // stops white's timer
      checkTimerW = false;
      toggleSwitch();
      notifyListeners();
      
  }
 }
}

Main.dart -

import 'dart:math';


import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import 'controller/countdown_controller.dart';
import 'widgets/blackButton.dart';
import 'widgets/bottom_sheet_design.dart';
import 'widgets/whiteButton.dart';
import 'providers/settings.dart';


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    return ChangeNotifierProvider(
      create: (ctx) => SettingsProvider(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.amber,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
      
  void settings(BuildContext ctx) {
    showModalBottomSheet(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(10),
          topRight: Radius.circular(10),
        ),
      ),
      context: ctx,
      builder: (_) => BottomSheetDesign(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        backgroundColor: Colors.grey[350],
        body: Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Transform.rotate(
                angle: pi / 1,
                child: GestureDetector(
                  onTap: () {
                    Provider.of<SettingsProvider>(context, listen: false)
                        .switchTimer();
                  },
                  child: Container(
                    width: 80.0,
                    height: 500,
                    child: Center(
                      child: Transform.rotate(
                        angle: pi / 2,
                        child: Text('Switch',
                            style: Theme.of(context).textTheme.bodyText2),
                      ),
                    ),
                    decoration: BoxDecoration(
                      color: Colors.blueGrey,
                    ),
                  ),
                ),
              ),
            ),
            VerticalDivider(),
            Expanded(
              flex: 4,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Row(
                    children: <Widget>[

                      // container that displays black's timer
                      BlackButton(),

                      Expanded(
                        child: Transform.rotate(
                          angle: pi / 2,
                          child: RaisedButton(
                            onPressed: () {
                              settings(context);
                            },
                            color: Colors.blue[300],
                            child: Text('Settings'),
                          ),
                        ),
                      ),
                    ],
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  Row(
                    children: <Widget>[

                      // container that displays white's timer
                      WhiteButton(),

                      Expanded(
                        child: Transform.rotate(
                          angle: pi / 2,
                          child: RaisedButton(
                            onPressed: () {
                              Provider.of<SettingsProvider>(context, listen: false).reset();
                            },
                            color: Colors.red[600],
                            child: Text('Reset'),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

I have coded this in the past. It might help you.

class DoubleTimer extends StatefulWidget {
  @override
  _DoubleTimerState createState() => _DoubleTimerState();
}

class _DoubleTimerState extends State<DoubleTimer> {
  int timeToGoA = 50000;
  int timeToGoB = 50000;

  int state = 0; //0: waiting, 1: counting A, 2: counting B

  DateTime timeStamp;

  _DoubleTimerState() {
    print("init");
  }

  @override
  Widget build(BuildContext context) {
    print(
        "${DateTime.now().compareTo(DateTime.now().add(Duration(seconds: 1)))}");
    return Row(
      children: <Widget>[
        if (state == 1)
          ToTime(timeStamp.add(Duration(milliseconds: timeToGoA))),
        FlatButton(
          onPressed: () {
            setState(() {
              switch (state) {
                case 0:
                  state = 1;
                  timeStamp = DateTime.now();
                  print("Running A");
                  break;
                case 1:
                  state = -1;
                  timeToGoA -=
                      DateTime.now().difference(timeStamp).inMilliseconds;
                  timeStamp = DateTime.now();
                  print("A: $timeToGoA\nRunning B");
                  break;
                case -1:
                  state = 1;
                  timeToGoB -=
                      DateTime.now().difference(timeStamp).inMilliseconds;
                  timeStamp = DateTime.now();
                  print("B: $timeToGoB\nRunning A");
                  break;
              }
            });
          },
          child: Text("switch"),
        ),
        if (state == -1)
          ToTime(timeStamp.add(Duration(milliseconds: timeToGoB))),
      ],
    );
  }
}

class ToTime extends StatelessWidget {
  final DateTime timeStamp;

  const ToTime(this.timeStamp, {Key key}) : super(key: key);

  static final Map<String, int> _times = <String, int>{
    'y': -const Duration(days: 365).inMilliseconds,
    'm': -const Duration(days: 30).inMilliseconds,
    'w': -const Duration(days: 7).inMilliseconds,
    'd': -const Duration(days: 1).inMilliseconds,
    'h': -const Duration(hours: 1).inMilliseconds,
    '\'': -const Duration(minutes: 1).inMilliseconds,
    '"': -const Duration(seconds: 1).inMilliseconds,
    "ms": -1,
  };

  Stream<String> get relativeStream async* {
    while (true) {
      int duration = DateTime.now().difference(timeStamp).inMilliseconds;
      String res = '';
      int level = 0;
      int levelSize;
      for (MapEntry<String, int> time in _times.entries) {
        int timeDelta = (duration / time.value).floor();
        if (timeDelta > 0) {
          levelSize = time.value;
          res += '$timeDelta${time.key} ';
          duration -= time.value * timeDelta;
          level++;
        }
        if (level == 2) {
          break;
        }
      }
      levelSize ??= _times.values.reduce(min);
      if (level > 0 && level < 2) {
        List<int> _tempList =
            _times.values.where((element) => (element < levelSize)).toList();

        if (_tempList.isNotEmpty) levelSize = _tempList.reduce(max);
      }
      if (res.isEmpty) {
        yield 'now';
      } else {
        res.substring(0, res.length - 2);
        yield res;
      }
//      print('levelsize $levelSize sleep ${levelSize - duration}ms');
      await Future.delayed(Duration(milliseconds: levelSize - duration));
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<String>(
        stream: relativeStream,
        builder: (context, snapshot) {
          return Text(snapshot.data ?? '??');
        });
  }
}

I coded it withoud provider (Using only ValueNotifier) just to show you the logic

enum Player{White, Black}

class MyTimer extends ValueNotifier<int>{
  Player _turn; //White starts
  int _minutes;
  int _whiteTime;
  int _blackTime;
  
  MyTimer(int time) : 
       _minutes = time * 60,
       _whiteTime = time * 60,
       _blackTime = time * 60,
       _turn = Player.White, //White starts
       super(time * 60 * 2);
  
  bool get _isWhiteTurn => Player.White == _turn;
  
  String get timeLeft{
    if(value != 0){
      //int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
      Duration left = Duration(seconds: _isWhiteTurn ? _whiteTime : _blackTime);
      String playerTime = left.toString();
      playerTime = playerTime.substring(0, playerTime.lastIndexOf('.'));
      return '${describeEnum(_turn)} turn time left : $playerTime';
    } 
    else{
      return '${describeEnum(_turn)} wins!'; //We have a winner
    }
  }
  
  void switchPlayer() => _turn = _isWhiteTurn ? Player.Black : Player.White;
  void reset([int time]){
    if(time != null) _minutes = time * 60; //if you want to start with a different  value
    _turn = Player.White; //White starts
    _whiteTime = _minutes; //reset time
    _blackTime = _minutes; //reset time
    value = 2*_minutes; //reset time
    //twice as long because it counts the whole time of the match (the time of the 2 players)
  }
  void start(){
    _initilizeTimer();
  }
  void _initilizeTimer(){
    Timer.periodic(
      Duration(seconds: 1),
      (Timer t) {
        if(_whiteTime == 0 || _blackTime == 0){
          t.cancel();
          switchPlayer(); //the time of one player ends, so it switch to the winner player
          value = 0; //end the game
        }
        else{
          _isWhiteTurn ? --_whiteTime : --_blackTime;
          --value;
        }
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final MyTimer clock = MyTimer(1);
  
  @override
  void initState(){
    super.initState();
    clock.start();
  }
  
  @override
  void dispose(){
    clock.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        backgroundColor: Colors.grey[350],
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ValueListenableBuilder<int>(
                valueListenable: clock,
                 builder: (context, unit, _) =>
                   Text(clock.timeLeft ,style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))
              ),
              RaisedButton(
                child: Text('Switch'),
                onPressed: () => clock.switchPlayer(),
              )
            ],
          ),
        )
      ),
    );
  }
}

The idea is the same but what I want to show you is that you can use only one timer to do the whole logic and with some enum value (White and Black) change between both minutes.

The button change the turn of the player (switchPlayer method) and inside the timer you see that depending the turn of the player it reduces its time _isWhiteTurn? --_whiteTime: --_blackTime; _isWhiteTurn? --_whiteTime: --_blackTime; . As a ValueNotifier it updates only when value changes, but you can use your Provider with ChangeNotifier and update when you want (and it's better, because when changing player in my example I still have to wait for the second to end so the timer updates the text).

You can try change something like this with an enum to simplify the timer logic

bool get _isWhiteTurn => Player.White == _turn;

void startMatch() {
      Timer.periodic(
        Duration(seconds: 1),
        (Timer t) {
          if (valueW == 0 || valueB == 0) {
            t.cancel();
            if(valueW == 0) checkTimerB = true;
            else checkTimerW = true
            //it won the one whose time didn't end
          } else {
            _isWhiteTurn ? --valueW : --valueB;
          }

          timeToDisplayW = valueW.toString();
          timeToDisplayB = valueB.toString();
          //only one of them will change
          notifyListeners();
        },
      );
}

void switchTimer(){
  _turn = _isWhiteTurn ? Player.Black : Player.White;
  notifyListeners();
}

That way you have only one timer the whole match that will cancel when one of the timer gets to 0 (or if someone loese, but thats other logic in some other Provider I guess)

UPDATE

You can change the timeLeft getter to something like this

String get timeLeft{
    if(value != 0){
      //int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
      Duration white = Duration(seconds: _whiteTime);
      Duration black = Duration(seconds: _blackTime);
      String whiteTime = white.toString();
      String blackTime = black.toString();
      whiteTime = whiteTime.substring(0, whiteTime.lastIndexOf('.'));
      blackTime = blackTime.substring(0, blackTime.lastIndexOf('.'));
      return '''
                ${describeEnum(Player.White)} time left : $whiteTime
                ${describeEnum(Player.Black)} time left : $blackTime
      ''';
    } 
    else{
      return '${describeEnum(_turn)} wins!'; //We have a winner
    }
}

That way it will return a String with both times and only the timer of the player in turn will change each second. But as I saig try this logic with ChangeNotifierProvider and it should work too, and you can consume it in differents parts of your widget tree

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM