Merge pull request #110 from flixcoo/enhance/106-pop-up-for-reaching-exactly-100-points

Pop-Up for reaching exactly 100 points
This commit is contained in:
2025-07-14 10:21:06 +02:00
committed by GitHub
9 changed files with 173 additions and 19 deletions

View File

@@ -235,7 +235,7 @@ class GameSession extends ChangeNotifier {
/// This method updates the points of each player after a round. /// This method updates the points of each player after a round.
/// It first uses the _sumPoints() method to calculate the total points of each player. /// It first uses the _sumPoints() method to calculate the total points of each player.
/// Then, it checks if any player has reached 100 points. If so, it marks /// Then, it checks if any player has reached 100 points. If so, saves their indices and marks
/// that player as having reached 100 points in that corresponding [Round] object. /// that player as having reached 100 points in that corresponding [Round] object.
/// If the game has the point limit activated, it first applies the /// If the game has the point limit activated, it first applies the
/// _subtractPointsForReachingHundred() method to subtract 50 points /// _subtractPointsForReachingHundred() method to subtract 50 points
@@ -243,10 +243,13 @@ class GameSession extends ChangeNotifier {
/// It then checks if any player has exceeded 100 points. If so, it sets /// It then checks if any player has exceeded 100 points. If so, it sets
/// isGameFinished to true and calls the _setWinner() method to determine /// isGameFinished to true and calls the _setWinner() method to determine
/// the winner. /// the winner.
Future<void> updatePoints() async { /// It returns a list of players indices who reached 100 points in the current
/// round for the [RoundView] to show a popup
List<int> updatePoints() {
List<int> bonusPlayers = [];
_sumPoints(); _sumPoints();
if (isPointsLimitEnabled) { if (isPointsLimitEnabled) {
_checkHundredPointsReached(); bonusPlayers = _checkHundredPointsReached();
for (int i = 0; i < playerScores.length; i++) { for (int i = 0; i < playerScores.length; i++) {
if (playerScores[i] > pointLimit) { if (playerScores[i] > pointLimit) {
@@ -258,6 +261,7 @@ class GameSession extends ChangeNotifier {
} }
} }
notifyListeners(); notifyListeners();
return bonusPlayers;
} }
@visibleForTesting @visibleForTesting
@@ -278,15 +282,18 @@ class GameSession extends ChangeNotifier {
/// Checks if a player has reached 100 points in the current round. /// Checks if a player has reached 100 points in the current round.
/// If so, it updates the [scoreUpdate] List by subtracting 50 points from /// If so, it updates the [scoreUpdate] List by subtracting 50 points from
/// the corresponding round update. /// the corresponding round update.
void _checkHundredPointsReached() { List<int> _checkHundredPointsReached() {
List<int> bonusPlayers = [];
for (int i = 0; i < players.length; i++) { for (int i = 0; i < players.length; i++) {
if (playerScores[i] == pointLimit) { if (playerScores[i] == pointLimit) {
bonusPlayers.add(i);
print('${players[i]} hat genau 100 Punkte erreicht und bekommt ' print('${players[i]} hat genau 100 Punkte erreicht und bekommt '
'deswegen ${(pointLimit / 2).round()} Punkte abgezogen'); 'deswegen ${(pointLimit / 2).round()} Punkte abgezogen');
roundList[roundNumber - 1].scoreUpdates[i] -= (pointLimit / 2).round(); roundList[roundNumber - 1].scoreUpdates[i] -= (pointLimit / 2).round();
} }
} }
_sumPoints(); _sumPoints();
return bonusPlayers;
} }
/// Determines the winner of the game session. /// Determines the winner of the game session.

View File

@@ -73,6 +73,25 @@
"kamikaze": "Kamikaze", "kamikaze": "Kamikaze",
"done": "Fertig", "done": "Fertig",
"next_round": "Nächste Runde", "next_round": "Nächste Runde",
"bonus_points_title": "Bonus-Punkte!",
"bonus_points_message": "{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}",
"@bonus_points_message": {
"placeholders": {
"playerCount": {
"type": "int"
},
"names": {
"type": "String"
},
"pointLimit": {
"type": "int"
},
"bonusPoints": {
"type": "int"
}
}
},
"end_game": "Spiel beenden", "end_game": "Spiel beenden",
"delete_game": "Spiel löschen", "delete_game": "Spiel löschen",

View File

@@ -73,6 +73,25 @@
"kamikaze": "Kamikaze", "kamikaze": "Kamikaze",
"done": "Done", "done": "Done",
"next_round": "Next Round", "next_round": "Next Round",
"bonus_points_title": "Bonus-Points!",
"bonus_points_message": "{playerCount, plural, =1{{names} has reached exactly the point limit of {pointLimit} points and therefore gets {bonusPoints} points deducted!} other{{names} have reached exactly the point limit of {pointLimit} points and therefore get {bonusPoints} points deducted!}}",
"@bonus_points_message": {
"placeholders": {
"playerCount": {
"type": "int"
},
"names": {
"type": "String"
},
"pointLimit": {
"type": "int"
},
"bonusPoints": {
"type": "int"
}
}
},
"end_game": "End Game", "end_game": "End Game",
"delete_game": "Delete Game", "delete_game": "Delete Game",

View File

@@ -416,6 +416,19 @@ abstract class AppLocalizations {
/// **'Nächste Runde'** /// **'Nächste Runde'**
String get next_round; String get next_round;
/// No description provided for @bonus_points_title.
///
/// In de, this message translates to:
/// **'Bonus-Punkte!'**
String get bonus_points_title;
/// No description provided for @bonus_points_message.
///
/// In de, this message translates to:
/// **'{playerCount, plural, =1{{names} hat exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommt deshalb {bonusPoints} Punkte abgezogen!} other{{names} haben exakt das Punktelimit von {pointLimit} Punkten erreicht und bekommen deshalb jeweils {bonusPoints} Punkte abgezogen!}}'**
String bonus_points_message(
int playerCount, String names, int pointLimit, int bonusPoints);
/// No description provided for @end_game. /// No description provided for @end_game.
/// ///
/// In de, this message translates to: /// In de, this message translates to:

View File

@@ -178,6 +178,23 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get next_round => 'Nächste Runde'; String get next_round => 'Nächste Runde';
@override
String get bonus_points_title => 'Bonus-Punkte!';
@override
String bonus_points_message(
int playerCount, String names, int pointLimit, int bonusPoints) {
String _temp0 = intl.Intl.pluralLogic(
playerCount,
locale: localeName,
other:
'$names haben exakt das Punktelimit von $pointLimit Punkten erreicht und bekommen deshalb jeweils $bonusPoints Punkte abgezogen!',
one:
'$names hat exakt das Punktelimit von $pointLimit Punkten erreicht und bekommt deshalb $bonusPoints Punkte abgezogen!',
);
return '$_temp0';
}
@override @override
String get end_game => 'Spiel beenden'; String get end_game => 'Spiel beenden';

View File

@@ -175,6 +175,23 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get next_round => 'Next Round'; String get next_round => 'Next Round';
@override
String get bonus_points_title => 'Bonus-Points!';
@override
String bonus_points_message(
int playerCount, String names, int pointLimit, int bonusPoints) {
String _temp0 = intl.Intl.pluralLogic(
playerCount,
locale: localeName,
other:
'$names have reached exactly the point limit of $pointLimit points and therefore get $bonusPoints points deducted!',
one:
'$names has reached exactly the point limit of $pointLimit points and therefore gets $bonusPoints points deducted!',
);
return '$_temp0';
}
@override @override
String get end_game => 'End Game'; String get end_game => 'End Game';

View File

@@ -287,9 +287,14 @@ class _RoundViewState extends State<RoundView> {
children: [ children: [
CupertinoButton( CupertinoButton(
onPressed: _areRoundInputsValid() onPressed: _areRoundInputsValid()
? () { ? () async {
_finishRound(); List<int> bonusPlayersIndices = _finishRound();
if (bonusPlayersIndices.isNotEmpty) {
await _showBonusPopup(
context, bonusPlayersIndices);
}
LocalStorageService.saveGameSessions(); LocalStorageService.saveGameSessions();
if (!context.mounted) return;
Navigator.pop(context); Navigator.pop(context);
} }
: null, : null,
@@ -298,12 +303,18 @@ class _RoundViewState extends State<RoundView> {
if (!widget.gameSession.isGameFinished) if (!widget.gameSession.isGameFinished)
CupertinoButton( CupertinoButton(
onPressed: _areRoundInputsValid() onPressed: _areRoundInputsValid()
? () { ? () async {
_finishRound(); List<int> bonusPlayersIndices =
_finishRound();
if (bonusPlayersIndices.isNotEmpty) {
await _showBonusPopup(
context, bonusPlayersIndices);
}
LocalStorageService.saveGameSessions(); LocalStorageService.saveGameSessions();
if (widget.gameSession.isGameFinished) { if (widget.gameSession.isGameFinished &&
context.mounted) {
Navigator.pop(context); Navigator.pop(context);
} else { } else if (context.mounted) {
Navigator.pop( Navigator.pop(
context, widget.roundNumber + 1); context, widget.roundNumber + 1);
} }
@@ -359,7 +370,7 @@ class _RoundViewState extends State<RoundView> {
/// every player. If the round is the highest round played in this game, /// every player. If the round is the highest round played in this game,
/// it expands the player score lists. At the end it updates the score /// it expands the player score lists. At the end it updates the score
/// array for the game. /// array for the game.
void _finishRound() { List<int> _finishRound() {
print('===================================='); print('====================================');
print('Runde ${widget.roundNumber} beendet'); print('Runde ${widget.roundNumber} beendet');
// The shown round is smaller than the newest round // The shown round is smaller than the newest round
@@ -381,12 +392,63 @@ class _RoundViewState extends State<RoundView> {
widget.gameSession.calculateScoredPoints( widget.gameSession.calculateScoredPoints(
widget.roundNumber, roundScores, _caboPlayerIndex); widget.roundNumber, roundScores, _caboPlayerIndex);
} }
widget.gameSession.updatePoints(); List<int> bonusPlayers = widget.gameSession.updatePoints();
if (widget.gameSession.isGameFinished == true) { if (widget.gameSession.isGameFinished == true) {
print('Das Spiel ist beendet'); print('Das Spiel ist beendet');
} else if (widget.roundNumber == widget.gameSession.roundNumber) { } else if (widget.roundNumber == widget.gameSession.roundNumber) {
widget.gameSession.increaseRound(); widget.gameSession.increaseRound();
} }
return bonusPlayers;
}
/// Shows a popup dialog with the bonus information.
Future<void> _showBonusPopup(
BuildContext context, List<int> bonusPlayers) async {
print('Bonus Popup wird angezeigt');
int pointLimit = widget.gameSession.pointLimit;
int bonusPoints = (pointLimit / 2).round();
String resultText =
_getBonusPopupMessageString(pointLimit, bonusPoints, bonusPlayers);
await showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(AppLocalizations.of(context).bonus_points_title),
content: Text(resultText),
actions: [
CupertinoDialogAction(
child: Text(AppLocalizations.of(context).ok),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
/// Generates the message string for the bonus popup.
/// It takes the [pointLimit], [bonusPoints] and the list of [bonusPlayers]
/// and returns a formatted string.
String _getBonusPopupMessageString(
int pointLimit, int bonusPoints, List<int> bonusPlayers) {
List<String> nameList =
bonusPlayers.map((i) => widget.gameSession.players[i]).toList();
String resultText = '';
if (nameList.length == 1) {
resultText = AppLocalizations.of(context).bonus_points_message(
nameList.length, nameList.first, pointLimit, bonusPoints);
} else {
resultText = nameList.length == 2
? '${nameList[0]} & ${nameList[1]}'
: '${nameList.sublist(0, nameList.length - 1).join(', ')} & ${nameList.last}';
resultText = AppLocalizations.of(context).bonus_points_message(
nameList.length,
resultText,
pointLimit,
bonusPoints,
);
}
return resultText;
} }
@override @override

View File

@@ -2,7 +2,7 @@ name: cabo_counter
description: "Mobile app for the card game Cabo" description: "Mobile app for the card game Cabo"
publish_to: 'none' publish_to: 'none'
version: 0.4.4+486 version: 0.4.5+494
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4

View File

@@ -114,15 +114,15 @@ void main() {
expect(session.roundList[0].caboPlayerIndex, 0); expect(session.roundList[0].caboPlayerIndex, 0);
}); });
test('updatePoints - game not finished', () async { test('updatePoints - game not finished', () {
session.addRoundScoresToList(1, [10, 20, 30], [10, 20, 30], 0); session.addRoundScoresToList(1, [10, 20, 30], [10, 20, 30], 0);
await session.updatePoints(); session.updatePoints();
expect(session.isGameFinished, isFalse); expect(session.isGameFinished, isFalse);
}); });
test('updatePoints - game finished', () async { test('updatePoints - game finished', () {
session.addRoundScoresToList(1, [101, 20, 30], [101, 20, 30], 0); session.addRoundScoresToList(1, [101, 20, 30], [101, 20, 30], 0);
await session.updatePoints(); session.updatePoints();
expect(session.isGameFinished, isTrue); expect(session.isGameFinished, isTrue);
}); });
@@ -154,9 +154,9 @@ void main() {
expect(session.playerScores, equals([50, 0, 30])); expect(session.playerScores, equals([50, 0, 30]));
}); });
test('_setWinner via updatePoints', () async { test('_setWinner via updatePoints', () {
session.addRoundScoresToList(1, [101, 20, 30], [101, 0, 30], 1); session.addRoundScoresToList(1, [101, 20, 30], [101, 0, 30], 1);
await session.updatePoints(); session.updatePoints();
expect(session.winner, 'Bob'); // Bob has lowest score (20) expect(session.winner, 'Bob'); // Bob has lowest score (20)
}); });
}); });