@@ -1,6 +1,6 @@
|
|||||||
# CABO Counter
|
# CABO Counter
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@@ -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 50 Punkte abgezogen');
|
'deswegen ${(pointLimit / 2).round()} Punkte abgezogen');
|
||||||
roundList[roundNumber - 1].scoreUpdates[i] -= 50;
|
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.
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class ActiveGameView extends StatefulWidget {
|
|||||||
|
|
||||||
class _ActiveGameViewState extends State<ActiveGameView> {
|
class _ActiveGameViewState extends State<ActiveGameView> {
|
||||||
late final GameSession gameSession;
|
late final GameSession gameSession;
|
||||||
|
late List<int> denseRanks;
|
||||||
|
late List<int> sortedPlayerIndices;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -32,7 +34,9 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: gameSession,
|
listenable: gameSession,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
List<int> sortedPlayerIndices = _getSortedPlayerIndices();
|
sortedPlayerIndices = _getSortedPlayerIndices();
|
||||||
|
denseRanks = _calculateDenseRank(
|
||||||
|
gameSession.playerScores, sortedPlayerIndices);
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: Text(gameSession.gameTitle),
|
middle: Text(gameSession.gameTitle),
|
||||||
@@ -58,7 +62,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
return CupertinoListTile(
|
return CupertinoListTile(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
_getPlacementPrefix(index),
|
_getPlacementTextWidget(index),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
gameSession.players[playerIndex],
|
gameSession.players[playerIndex],
|
||||||
@@ -266,39 +270,50 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
playerIndices.sort((a, b) {
|
playerIndices.sort((a, b) {
|
||||||
int scoreA = gameSession.playerScores[a];
|
int scoreA = gameSession.playerScores[a];
|
||||||
int scoreB = gameSession.playerScores[b];
|
int scoreB = gameSession.playerScores[b];
|
||||||
return scoreA.compareTo(scoreB);
|
if (scoreA != scoreB) {
|
||||||
|
return scoreA.compareTo(scoreB);
|
||||||
|
}
|
||||||
|
return a.compareTo(b);
|
||||||
});
|
});
|
||||||
return playerIndices;
|
return playerIndices;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a widget that displays the placement prefix based on the index.
|
/// Calculates the dense rank for a player based on their index in the sorted list of players.
|
||||||
/// First three places are represented by medals, and the rest are numbered.
|
List<int> _calculateDenseRank(
|
||||||
/// [index] is the index of the player in the descending sorted list.
|
List<int> playerScores, List<int> sortedIndices) {
|
||||||
Widget _getPlacementPrefix(int index) {
|
List<int> denseRanks = [];
|
||||||
switch (index) {
|
int rank = 1;
|
||||||
case 0:
|
for (int i = 0; i < sortedIndices.length; i++) {
|
||||||
return const Text(
|
if (i > 0) {
|
||||||
'\u{1F947}',
|
int prevScore = playerScores[sortedIndices[i - 1]];
|
||||||
style: TextStyle(fontSize: 22),
|
int currScore = playerScores[sortedIndices[i]];
|
||||||
);
|
if (currScore != prevScore) {
|
||||||
|
rank++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
denseRanks.add(rank);
|
||||||
|
}
|
||||||
|
return denseRanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a text widget representing the placement text based on the given placement number.
|
||||||
|
/// [index] is the index of the player in [players] list,
|
||||||
|
Text _getPlacementTextWidget(int index) {
|
||||||
|
int placement = denseRanks[index];
|
||||||
|
switch (placement) {
|
||||||
case 1:
|
case 1:
|
||||||
return const Text(
|
return const Text('\u{1F947}', style: TextStyle(fontSize: 22)); // 🥇
|
||||||
'\u{1F948}',
|
|
||||||
style: TextStyle(fontSize: 22),
|
|
||||||
);
|
|
||||||
case 2:
|
case 2:
|
||||||
return const Text(
|
return const Text('\u{1F948}', style: TextStyle(fontSize: 22)); // 🥈
|
||||||
'\u{1F949}',
|
case 3:
|
||||||
style: TextStyle(fontSize: 22),
|
return const Text('\u{1F949}', style: TextStyle(fontSize: 22)); // 🥉
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return Text(
|
return Text(' $placement.',
|
||||||
' ${index + 1}.',
|
style: const TextStyle(fontWeight: FontWeight.bold));
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a dialog to confirm deleting the game session.
|
||||||
Future<bool> _showDeleteGameDialog() async {
|
Future<bool> _showDeleteGameDialog() async {
|
||||||
return await showCupertinoDialog<bool>(
|
return await showCupertinoDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -331,6 +346,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
|
|||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the game session in the game manager and navigates back to the previous screen.
|
||||||
|
/// If the game session does not exist in the game list, it shows an error dialog.
|
||||||
Future<void> _removeGameSession(GameSession gameSession) async {
|
Future<void> _removeGameSession(GameSession gameSession) async {
|
||||||
if (gameManager.gameExistsInGameList(gameSession.id)) {
|
if (gameManager.gameExistsInGameList(gameSession.id)) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:cabo_counter/services/local_storage_service.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
class RoundView extends StatefulWidget {
|
class RoundView extends StatefulWidget {
|
||||||
final GameSession gameSession;
|
final GameSession gameSession;
|
||||||
@@ -67,6 +68,8 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||||
|
final rotatedPlayers = _getRotatedPlayers();
|
||||||
|
final originalIndices = _getOriginalIndices();
|
||||||
|
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -175,9 +178,10 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: widget.gameSession.players.length,
|
itemCount: rotatedPlayers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final name = widget.gameSession.players[index];
|
final originalIndex = originalIndices[index];
|
||||||
|
final name = rotatedPlayers[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 10, horizontal: 20),
|
vertical: 10, horizontal: 20),
|
||||||
@@ -187,13 +191,23 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
backgroundColor: CupertinoColors.secondaryLabel,
|
backgroundColor: CupertinoColors.secondaryLabel,
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Row(children: [
|
||||||
name,
|
Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
name,
|
||||||
))
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: index == 0,
|
||||||
|
child: const SizedBox(width: 10),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: index == 0,
|
||||||
|
child: const Icon(FontAwesomeIcons.medal,
|
||||||
|
size: 15))
|
||||||
|
]))
|
||||||
]),
|
]),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${widget.gameSession.playerScores[index]}'
|
'${widget.gameSession.playerScores[originalIndex]}'
|
||||||
' ${AppLocalizations.of(context).points}'),
|
' ${AppLocalizations.of(context).points}'),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -201,7 +215,7 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
width: 100,
|
width: 100,
|
||||||
child: CupertinoTextField(
|
child: CupertinoTextField(
|
||||||
maxLength: 3,
|
maxLength: 3,
|
||||||
focusNode: _focusNodeList[index],
|
focusNode: _focusNodeList[originalIndex],
|
||||||
keyboardType:
|
keyboardType:
|
||||||
const TextInputType.numberWithOptions(
|
const TextInputType.numberWithOptions(
|
||||||
signed: true,
|
signed: true,
|
||||||
@@ -216,12 +230,13 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
1
|
1
|
||||||
? TextInputAction.done
|
? TextInputAction.done
|
||||||
: TextInputAction.next,
|
: TextInputAction.next,
|
||||||
controller: _scoreControllerList[index],
|
controller:
|
||||||
|
_scoreControllerList[originalIndex],
|
||||||
placeholder:
|
placeholder:
|
||||||
AppLocalizations.of(context).points,
|
AppLocalizations.of(context).points,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
onSubmitted: (_) =>
|
onSubmitted: (_) =>
|
||||||
_focusNextTextfield(index),
|
_focusNextTextfield(originalIndex),
|
||||||
onChanged: (_) => setState(() {}),
|
onChanged: (_) => setState(() {}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -230,9 +245,10 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_kamikazePlayerIndex =
|
_kamikazePlayerIndex =
|
||||||
(_kamikazePlayerIndex == index)
|
(_kamikazePlayerIndex ==
|
||||||
|
originalIndex)
|
||||||
? null
|
? null
|
||||||
: index;
|
: originalIndex;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -240,17 +256,20 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
height: 24,
|
height: 24,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: _kamikazePlayerIndex == index
|
color: _kamikazePlayerIndex ==
|
||||||
|
originalIndex
|
||||||
? CupertinoColors.systemRed
|
? CupertinoColors.systemRed
|
||||||
: CupertinoColors
|
: CupertinoColors
|
||||||
.tertiarySystemFill,
|
.tertiarySystemFill,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _kamikazePlayerIndex == index
|
color: _kamikazePlayerIndex ==
|
||||||
|
originalIndex
|
||||||
? CupertinoColors.systemRed
|
? CupertinoColors.systemRed
|
||||||
: CupertinoColors.systemGrey,
|
: CupertinoColors.systemGrey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: _kamikazePlayerIndex == index
|
child: _kamikazePlayerIndex ==
|
||||||
|
originalIndex
|
||||||
? const Icon(
|
? const Icon(
|
||||||
CupertinoIcons.exclamationmark,
|
CupertinoIcons.exclamationmark,
|
||||||
size: 16,
|
size: 16,
|
||||||
@@ -287,9 +306,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 +322,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);
|
||||||
}
|
}
|
||||||
@@ -324,11 +354,60 @@ class _RoundViewState extends State<RoundView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the index of the player who won the previous round.
|
||||||
|
int _getPreviousRoundWinnerIndex() {
|
||||||
|
if (widget.roundNumber == 1) {
|
||||||
|
return 0; // If it's the first round, there's no previous round, so return 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
final previousRound = widget.gameSession.roundList[widget.roundNumber - 2];
|
||||||
|
final scores = previousRound.scoreUpdates;
|
||||||
|
|
||||||
|
// Find the index of the player with the minimum score
|
||||||
|
int minScore = scores[0];
|
||||||
|
int winnerIndex = 0;
|
||||||
|
|
||||||
|
// Iterate through the scores to find the player with the minimum score
|
||||||
|
for (int i = 1; i < scores.length; i++) {
|
||||||
|
if (scores[i] < minScore) {
|
||||||
|
minScore = scores[i];
|
||||||
|
winnerIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return winnerIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rotates the players list based on the previous round's winner.
|
||||||
|
List<String> _getRotatedPlayers() {
|
||||||
|
final winnerIndex = _getPreviousRoundWinnerIndex();
|
||||||
|
return [
|
||||||
|
widget.gameSession.players[winnerIndex],
|
||||||
|
...widget.gameSession.players.sublist(winnerIndex + 1),
|
||||||
|
...widget.gameSession.players.sublist(0, winnerIndex)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the original indices of the players by recalculating it from the rotated list.
|
||||||
|
List<int> _getOriginalIndices() {
|
||||||
|
final winnerIndex = _getPreviousRoundWinnerIndex();
|
||||||
|
return [
|
||||||
|
winnerIndex,
|
||||||
|
...List.generate(widget.gameSession.players.length - winnerIndex - 1,
|
||||||
|
(i) => winnerIndex + i + 1),
|
||||||
|
...List.generate(winnerIndex, (i) => i)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/// Focuses the next text field in the list of text fields.
|
/// Focuses the next text field in the list of text fields.
|
||||||
/// [index] is the index of the current text field.
|
/// [index] is the index of the current text field.
|
||||||
void _focusNextTextfield(int index) {
|
void _focusNextTextfield(int index) {
|
||||||
if (index < widget.gameSession.players.length - 1) {
|
final originalIndices = _getOriginalIndices();
|
||||||
FocusScope.of(context).requestFocus(_focusNodeList[index + 1]);
|
final currentPos = originalIndices.indexOf(index);
|
||||||
|
|
||||||
|
if (currentPos < originalIndices.length - 1) {
|
||||||
|
FocusScope.of(context)
|
||||||
|
.requestFocus(_focusNodeList[originalIndices[currentPos + 1]]);
|
||||||
} else {
|
} else {
|
||||||
_focusNodeList[index].unfocus();
|
_focusNodeList[index].unfocus();
|
||||||
}
|
}
|
||||||
@@ -359,7 +438,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 +460,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
|
||||||
|
|||||||
@@ -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+485
|
version: 0.4.7+506
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|||||||
@@ -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)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user