Таймер обратного отсчета Flutter для шахматного приложения (застрял в логике)

Итак, последние несколько дней я пытался обойти этот логический цикл, перепробовал все, что мог, а затем, наконец, решил опубликовать его здесь (отсюда и мой дебют здесь). Я застрял, вся помощь приветствуется. Спасибо!

Логика: В приложении их 2container который отображает время белых и черных, которое выбирается из заданных пяти вариантов -> 5,10,15,30,60, которые затем обновляют время для отображения в этих контейнерах, использовали provider пакет для этого и всего остального.

Теперь я также добавил Raised button с именем 'switch', который при нажатии должен был:

  1. Старт таймера обратного отсчета белых
  2. Затем, если нажать снова, таймер белых остановится, а затем начнется обратный отсчет черных.
  3. Затем, если нажать еще раз, должен остановить таймер черных, а затем запустить таймер белых.
  4. И повторяйте, пока не истечет время, и один из двух не будет объявлен победителем.

Проблема: Итак, с тем, что я закодировал до сих пор, при нажатии "переключателя" запускается таймер белого, а при повторном нажатии он останавливает таймер белого, но не запускает таймер черного. Я знаю это из-за того, как я сформулировал условия if(), а также потому, что не знаю, как остановить таймер извне. Что я сделал, так это использовать - bool checkTimerW и checkTimerB для каждого белого и черного, которые я проверяю в условии if() для отмены таймера, основанного на нем.

Код:

Провайдер -

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'),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

2 ответа

Я кодировал это в прошлом. Это может вам помочь.

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 ?? '??');
        });
  }
}

Я закодировал его с помощью провайдера (используя только ValueNotifier), чтобы показать вам логику

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(),
              )
            ],
          ),
        )
      ),
    );
  }
}

Идея та же самая, но я хочу показать вам, что вы можете использовать только один таймер для выполнения всей логики и с некоторым изменением значения перечисления (белого и черного) между обеими минутами.

Кнопка изменяет ход игрока (метод switchPlayer) и внутри таймера вы видите, что в зависимости от хода игрока он сокращает свое время _isWhiteTurn ? --_whiteTime : --_blackTime;. Как ValueNotifier он обновляется только при изменении значения, но вы можете использовать своего Provider с ChangeNotifier и обновлять, когда хотите (и это лучше, потому что при смене игрока в моем примере мне все равно нужно ждать окончания второго, поэтому таймер обновляет текст).

Вы можете попробовать изменить что-то подобное с помощью перечисления, чтобы упростить логику таймера

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();
}

Таким образом, у вас есть только один таймер на весь матч, который будет отменен, когда один из таймеров достигнет 0 (или если кто-то потеряет, но это другая логика в каком-то другом провайдере, я думаю)

ОБНОВИТЬ

Вы можете изменить геттер timeLeft на что-то вроде этого

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
    }
}

Таким образом, он вернет String с обоими временами, и только таймер игрока, в свою очередь, будет меняться каждую секунду. Но, поскольку я говорю, попробуйте эту логику с ChangeNotifierProvider, и она тоже должна работать, и вы можете использовать ее в разных частях своего дерева виджетов.

Другие вопросы по тегам