diff --git a/lib/data_classes/game_session.dart b/lib/data_classes/game_session.dart index a5dbbdc..834e502 100644 --- a/lib/data_classes/game_session.dart +++ b/lib/data_classes/game_session.dart @@ -21,17 +21,28 @@ class GameSession { @override String toString() { - print('GameSession: [gameTitle: $gameTitle, ' + return ('GameSession: [gameTitle: $gameTitle, ' 'players: $players, winner: $winner, ' 'round: $round, gameMode: $gameMode, ' 'playerScores: $playerScores]'); - return super.toString(); + } + + void expandPlayerScoreLists() { + for (int i = 0; i < playerScores.length; i++) { + playerScores[i].add(0); + } + } + + void addRoundScoresToScoreList(List roundScores, int roundNumber) { + for (int i = 0; i < roundScores.length; i++) { + playerScores[i][roundNumber] = (roundScores[i]); + } } void sumPoints() { for (int i = 0; i < playerScores.length; i++) { playerScores[i][0] = 0; - for (int j = 1; j < playerScores[i].length; i++) { + for (int j = 1; j < playerScores[i].length; j++) { playerScores[i][0] += playerScores[i][j]; } } diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index 5215f8b..9dba717 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -15,24 +15,32 @@ class RoundView extends StatefulWidget { } class _RoundViewState extends State { + /// The current game session. + late GameSession gameSession = widget.gameSession; + + /// Index of the player who said CABO. + int _caboPlayerIndex = 0; + + /// List of booleans that represent whether the kamikaze checkbox is checked for a player on that index. late final List _isKamikazeChecked = List.filled(widget.gameSession.players.length, false); + + /// List of text controllers for the point text fields. late final List _pointControllers = List.generate( widget.gameSession.players.length, (index) => TextEditingController(), ); - int _caboPlayerIndex = 0; - late GameSession gameSession = widget.gameSession; + + /// List of focus nodes for the point text fields. late final List _focusNodes = List.generate( widget.gameSession.players.length, (index) => FocusNode(), - ); // Neue FocusNodes-Liste + ); @override void initState() { - super.initState(); - print('Runde ${widget.roundNumber} geöffnet'); + super.initState(); } @override @@ -42,149 +50,291 @@ class _RoundViewState extends State { navigationBar: const CupertinoNavigationBar( transitionBetweenRoutes: true, middle: Text('Ergebnisse'), - previousPageTitle: 'Zurück', + previousPageTitle: 'Übersicht', ), - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + child: Stack( children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 40, 0, 50), - child: Text( - 'Runde ${widget.roundNumber}', - style: Styles.roundTitle, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 10), - child: Text('Wer hat CABO gesagt?', - style: TextStyle(fontWeight: FontWeight.bold)), - ), - Padding( - padding: EdgeInsets.symmetric( - horizontal: gameSession.players.length > 3 ? 5 : 20, - vertical: 10), - child: SizedBox( - height: 40, - child: CupertinoSegmentedControl( - groupValue: _caboPlayerIndex, - children: Map.fromIterable( - widget.gameSession.players.asMap().keys, - value: (index) => Padding( - padding: EdgeInsets.symmetric( - horizontal: gameSession.players.length > 3 ? 7 : 15, - ), - child: Text( - widget.gameSession.players[index], - style: TextStyle( - fontSize: gameSession.players.length > 3 ? 14 : 16), + SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 40, 0, 50), + child: Text( + 'Runde ${widget.roundNumber}', + style: Styles.roundTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 10), + child: Text('Wer hat CABO gesagt?', + style: TextStyle(fontWeight: FontWeight.bold)), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: gameSession.players.length > 3 ? 5 : 20, + vertical: 10), + child: SizedBox( + height: 40, + child: CupertinoSegmentedControl( + groupValue: _caboPlayerIndex, + children: Map.fromIterable( + widget.gameSession.players.asMap().keys, + value: (index) => Padding( + padding: EdgeInsets.symmetric( + horizontal: gameSession.players.length > 3 ? 7 : 15, + ), + child: Text( + widget.gameSession.players[index], + style: TextStyle( + fontSize: + gameSession.players.length > 3 ? 14 : 16), + ), + ), ), + onValueChanged: (int value) { + setState(() { + _caboPlayerIndex = value; + }); + }, ), ), - onValueChanged: (int value) { - setState(() { - _caboPlayerIndex = value; - }); - }, ), - ), - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0), - child: CupertinoListTile( - title: Text('Spieler:in'), - trailing: Row( - children: [ - SizedBox( - width: 100, - child: Center(child: Text('Punkte')), + Padding( + padding: + EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0), + child: CupertinoListTile( + title: Text('Spieler:in'), + trailing: Row( + children: [ + SizedBox( + width: 100, + child: Center(child: Text('Punkte')), + ), + SizedBox(width: 20), + SizedBox( + width: 70, + child: Center(child: Text('Kamikaze')), + ), + ], ), - SizedBox(width: 20), - SizedBox( - width: 70, - child: Center(child: Text('Kamikaze')), - ), - ], + ), ), - ), - ), - Expanded( - child: ListView.builder( - shrinkWrap: true, - itemCount: widget.gameSession.players.length, - itemBuilder: (BuildContext context, int index) { - return Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), - child: ClipRRect( - borderRadius: - BorderRadius.circular(12), // Radius der Ecken - child: CupertinoListTile( - backgroundColor: CupertinoColors.secondaryLabel, - title: Row( - children: [ - Text(widget.gameSession.players[index]), - ], - ), - subtitle: Text( - '${widget.gameSession.playerScores[index][widget.roundNumber - 1]} Punkte', - ), - trailing: Row( - children: [ - SizedBox( - width: 100, - child: CupertinoTextField( - maxLength: 3, - focusNode: _focusNodes[index], - //keyboardType: - // TextInputType.numberWithOptions( - // signed: false, decimal: false), - keyboardType: - TextInputType.numberWithOptions( - signed: true, - decimal: false, - ), - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - textInputAction: index == - widget.gameSession.players.length - - 1 - ? TextInputAction.done - : TextInputAction.next, - controller: _pointControllers[index], - placeholder: 'Punkte', - textAlign: TextAlign.center, - // 👇 Action-Typ explizit setzen + Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.gameSession.players.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, horizontal: 20.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(12), // Radius der Ecken + child: CupertinoListTile( + backgroundColor: CupertinoColors.secondaryLabel, + title: Row( + children: [ + Text(widget.gameSession.players[index]), + ], + ), + subtitle: Text( + '${widget.gameSession.playerScores[index][0]} Punkte', + ), + trailing: Row( + children: [ + SizedBox( + width: 100, + child: CupertinoTextField( + maxLength: 3, + focusNode: _focusNodes[index], + keyboardType: + TextInputType.numberWithOptions( + signed: true, + decimal: false, + ), + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, + ], + textInputAction: index == + widget.gameSession.players + .length - + 1 + ? TextInputAction.done + : TextInputAction.next, + controller: _pointControllers[index], + placeholder: 'Punkte', + textAlign: TextAlign.center, + // 👇 Action-Typ explizit setzen - onSubmitted: (_) => _nextField(index), - ), - ), - SizedBox(width: 20), - SizedBox( - width: 50, - child: CupertinoCheckbox( - activeColor: Styles.primaryColor, - tristate: false, - value: _isKamikazeChecked[index], - onChanged: (bool? value) { - print('value: $value'); - setState(() { - _isKamikazeChecked[index] = value!; - print( - 'Kamikaze checked: ${_isKamikazeChecked[index]}'); - }); - }, - ), - ), - ], - )))); - }, + onSubmitted: (_) => _nextField(index), + ), + ), + SizedBox(width: 20), + SizedBox( + width: 50, + child: CupertinoCheckbox( + activeColor: Styles.primaryColor, + tristate: false, + value: _isKamikazeChecked[index], + onChanged: (bool? value) { + setState(() { + _isKamikazeChecked[index] = value!; + }); + }, + ), + ), + ], + )))); + }, + )), + ], )), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + height: 80, // Höhe der Bar + decoration: const BoxDecoration( + color: CupertinoColors.darkBackgroundGray, + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CupertinoButton( + child: const Text('Fertig'), + onPressed: () => Navigator.pop(context), + ), + CupertinoButton( + child: const Text('Nächste Runde'), + onPressed: () => { + print('===================================='), + print('Runde ${widget.roundNumber} beendet'), + if (widget.roundNumber >= + widget.gameSession.playerScores[0].length) + {gameSession.expandPlayerScoreLists()}, + _checkForWinner(), + widget.gameSession.sumPoints(), + Navigator.pushReplacement(context, + CupertinoPageRoute(builder: (context) { + return RoundView( + gameSession: widget.gameSession, + roundNumber: widget.roundNumber + 1); + })), + }), + ], + )), + ), + ), ], - ))); + )); } + /// Checks the scores of the current round and assigns points to the players. + /// There are two possible outcomes of a round: + /// + /// **Case 1**
+ /// The player who said CABO has the lowest score. They receive 0 points. + /// Every other player gets their round score. + /// + /// **Case 2**
+ /// The player who said CABO does not have the lowest score. + /// They receive 5 extra points added to their round score. + /// Every player with the lowest score gets 0 points. + /// Every other player gets their round score. + void _checkForWinner() { + /// List of the scores of the current round + List roundScores = []; + for (TextEditingController c in _pointControllers) { + roundScores.add(int.parse(c.text)); + } + print('Spieler: ${gameSession.players}'); + print('Punkte: $roundScores'); + print('${gameSession.players[_caboPlayerIndex]} hat CABO gesagt'); + print( + '${gameSession.players[_caboPlayerIndex]} hat ${roundScores[_caboPlayerIndex]} Punkte'); + + /// List of the index of the player(s) with the lowest score + List lowestScoreIndex = _getLowestScoreIndex(roundScores); + // Spieler der CABO gesagt hat, hat am wenigsten Punkte + if (lowestScoreIndex.contains(_caboPlayerIndex)) { + print( + '${widget.gameSession.players[_caboPlayerIndex]} hat CABO gesagt und bekommt 0 Punkte'); + print('Alle anderen Spieler bekommen ihre Punkte'); + + /// + _assignPoints([_caboPlayerIndex], -1, roundScores); + } else { + // Ein anderer Spieler hat weniger Punkte + print( + '${widget.gameSession.players[_caboPlayerIndex]} hat CABO gesagt, jedoch nicht die wenigsten Punkte.'); + + print('Folgende:r Spieler haben die wenigsten Punkte:'); + for (int i in lowestScoreIndex) { + print('${widget.gameSession.players[i]}: ${roundScores[i]} Punkte'); + } + _assignPoints(lowestScoreIndex, _caboPlayerIndex, roundScores); + } + } + + /// Assigns points to the players based on the scores of the current round. + /// [winnerIndex] is the index of the player(s) who receive 0 points + /// [loserIndex] is the index of the player who receives 5 extra points + /// [roundScores] is the raw list of the scores of all players in the current round. + void _assignPoints( + List winnnerIndex, int loserIndex, List roundScores) { + print('Punkte der Spieler'); + for (int i = 0; i < roundScores.length; i++) { + print('${widget.gameSession.players[i]}: ${roundScores[i]}'); + } + for (int i in winnnerIndex) { + print('${widget.gameSession.players[i]} bekommt 0 Punkte'); + roundScores[i] = 0; + } + if (loserIndex != -1) { + print('${widget.gameSession.players[loserIndex]} bekommt 5 Fehlerpunkte'); + roundScores[loserIndex] += 5; + } + print('Aktualisierte Punkte'); + for (int i = 0; i < roundScores.length; i++) { + print('${widget.gameSession.players[i]}: ${roundScores[i]}'); + } + gameSession.addRoundScoresToScoreList(roundScores, widget.roundNumber); + } + + /// Returns the index of the player with the lowest score. If there are + /// multiple players with the same lowest score, all of them are returned. + /// [roundScores] is a list of the scores of all players in the current round. + List _getLowestScoreIndex(List roundScores) { + int lowestScore = roundScores[0]; + List lowestScoreIndex = [0]; + print( + 'Niedrigster Score: ${gameSession.players[lowestScoreIndex[0]]} ($lowestScore Punkte)'); + for (int i = 1; i < roundScores.length; i++) { + if (roundScores[i] < lowestScore) { + print( + 'Neuer niedrigster Score: ${gameSession.players[i]} (${roundScores[i]} Punkte)'); + lowestScore = roundScores[i]; + lowestScoreIndex = [i]; + } else if (roundScores[i] == lowestScore) { + print( + '${gameSession.players[i]} hat ebenfalls am wenigsten Punkte (${roundScores[i]} Punkte)'); + lowestScoreIndex.add(i); + } + } + print('Folgende Spieler haben die niedrigsten Punte:'); + for (int i in lowestScoreIndex) { + print('${widget.gameSession.players[i]} (${roundScores[i]} Punkte)'); + } + return lowestScoreIndex; + } + + /// Focuses the next text field in the list of text fields. + /// [index] is the index of the current text field. void _nextField(int index) { if (index < widget.gameSession.players.length - 1) { FocusScope.of(context).requestFocus(_focusNodes[index + 1]); @@ -192,4 +342,15 @@ class _RoundViewState extends State { _focusNodes[index].unfocus(); } } + + @override + void dispose() { + for (final controller in _pointControllers) { + controller.dispose(); + } + for (final focusNode in _focusNodes) { + focusNode.dispose(); + } + super.dispose(); + } }