Files
cabo-counter/lib/data/game_session.dart
Felix Kirchner d627f33579 Beta-Version 0.5.3 (#136)
* Updated createGameView ListBuilder

* Added ReorderableListView

* Increment build no

* Fixed bug with wrong medal icon

* change not equal to greater than

* Updated bool var

* Fixed deletion error

* Small translation improvements

* Implemented first version of point overview

* Visual improvements on table

* Added details and sum row

* Updated strings

* Implemented new strings

* Refactoring

* Updated graph displayment

* Moved new views to statistics section

* Added seperator in main menu

* Renaming

* Updated sign

* Updated colors & class name

* Removed empty line

* Updated round index

* Updated types

* Added new kamikaze button and bundles navigation functionality

* Updated lock icon

* Updated button position and design

* Removed title row and changed segmendetControl Padding

* Refactored logic and added comments

* Updated comment

* Chaned icon

* Added comment

* Removed print

* Updated colors

* Changed var name

* Removed unused strings

* Added gameMode

* Changed creation variable

* Updated mode selection

* Updated strings

* Changed mode order

* Implemented default mode selection

* Updated initState

* Removed print

* Removed print

* Removed comments

* Updated config service

* Changed create game view

* Changed icon

* Updated strings

* Updated config

* Updated mode selection logic

* Deleted getter

* Removed not used code

* Implemented reset logic for default game mode

* Updated to 0.5.0

* Hotfix: Pixel Overflow

* Changed the overall return type for gamemodes

* Updated documentation

* Fixed merge issues

* Added Custom button

* Updated strings

* Updated buttons, implemented animatedOpacity

* Keyboard still doesnt works

* Fixed keyboard behaviour

* Changed keyboard height

* Added method getGameSessionById()

* Updated gameSession class

* id gets added to gameSession class at creation

* Cleaned up file

* Added docs and dependency

* Removed toString

* Implemented null safety

* Added named parameter

* Replaced button with custom button

* Updated key

* Updated addGameSessionMethod

* Update README.md

* Added Strings for popup

* Implemented popup & confetti

* Extracted code to method _playFinishAnimation()

* Replaced tenary operator with Visibility Widget

* Replaced tenary operator with Visibility Widget

* Used variable again

* Added delays in constants.dart

* Removed confetti button

* Updated strings

* Removed print

* Added dispose for confettiController

* Implemented missing constant in code

* Updated gameSession logic so more than one player can be winner

* Updated strings

* Updated winner popup

* game names now can have up to 20 chars

* Updated strings

* Added sized box for visual enhancement

* Centered the add player button and made it wider

* New created player textfields get automatically focused

* Added focus nodes for autofocus and navigation between textfields

* Updated version number

* Updated game title textfield with focus node and textaction

* Added focusnodes to dispose

* Update README.md

* Fixed bug with no popup shown

* Fixed bug with out of range error

* Updated listener notification
2025-07-21 13:29:25 +02:00

326 lines
12 KiB
Dart

import 'package:cabo_counter/data/round.dart';
import 'package:flutter/cupertino.dart';
import 'package:uuid/uuid.dart';
/// This class represents a game session for Cabo game.
/// [createdAt] is the timestamp of when the game session was created.
/// [gameTitle] is the title of the game.
/// [isPointsLimitEnabled] is a boolean indicating if the game has the default
/// point limit of 101 points or not.
/// [players] is a string list of player names.
/// [playerScores] is a list of the summed scores of all players.
/// [roundNumber] is the current round number.
/// [isGameFinished] is a boolean indicating if the game has ended yet.
/// [winner] is the name of the player who won the game.
class GameSession extends ChangeNotifier {
final String id;
final DateTime createdAt;
final String gameTitle;
final List<String> players;
final int pointLimit;
final int caboPenalty;
final bool isPointsLimitEnabled;
bool isGameFinished = false;
String winner = '';
int roundNumber = 1;
late List<int> playerScores;
List<Round> roundList = [];
GameSession({
required this.id,
required this.createdAt,
required this.gameTitle,
required this.players,
required this.pointLimit,
required this.caboPenalty,
required this.isPointsLimitEnabled,
}) {
playerScores = List.filled(players.length, 0);
}
@override
toString() {
return ('GameSession: [id: $id, createdAt: $createdAt, gameTitle: $gameTitle, '
'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,'
' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]');
}
/// Converts the GameSession object to a JSON map.
Map<String, dynamic> toJson() => {
'id': id,
'createdAt': createdAt.toIso8601String(),
'gameTitle': gameTitle,
'players': players,
'pointLimit': pointLimit,
'caboPenalty': caboPenalty,
'isPointsLimitEnabled': isPointsLimitEnabled,
'isGameFinished': isGameFinished,
'winner': winner,
'roundNumber': roundNumber,
'playerScores': playerScores,
'roundList': roundList.map((e) => e.toJson()).toList()
};
/// Creates a GameSession object from a JSON map.
GameSession.fromJson(Map<String, dynamic> json)
: id = json['id'] ?? const Uuid().v1(),
createdAt = DateTime.parse(json['createdAt']),
gameTitle = json['gameTitle'],
players = List<String>.from(json['players']),
pointLimit = json['pointLimit'],
caboPenalty = json['caboPenalty'],
isPointsLimitEnabled = json['isPointsLimitEnabled'],
isGameFinished = json['isGameFinished'],
winner = json['winner'],
roundNumber = json['roundNumber'],
playerScores = List<int>.from(json['playerScores']),
roundList =
(json['roundList'] as List).map((e) => Round.fromJson(e)).toList();
/// Returns the length of the longest player name.
int getMaxLengthOfPlayerNames() {
int length = 0;
for (String player in players) {
if (player.length >= length) {
length = player.length;
}
}
return length;
}
/// Assigns 50 points to all players except the kamikaze player.
/// [kamikazePlayerIndex] is the index of the kamikaze player.
void applyKamikaze(int roundNum, int kamikazePlayerIndex) {
List<int> roundScores = List.generate(players.length, (_) => 0);
List<int> scoreUpdates = List.generate(players.length, (_) => 0);
for (int i = 0; i < scoreUpdates.length; i++) {
if (i != kamikazePlayerIndex) {
scoreUpdates[i] += 50;
}
}
addRoundScoresToList(
roundNum, roundScores, scoreUpdates, 0, kamikazePlayerIndex);
}
/// Checks the scores of the current round and assigns points to the players.
/// There are three possible outcomes of a round:
///
/// **Case 1**<br>
/// The player who said CABO has the lowest score. They receive 0 points.
/// Every other player gets their round score.
///
/// **Case 2**<br>
/// 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 calculateScoredPoints(
int roundNum, List<int> roundScores, int caboPlayerIndex) {
print('Spieler: $players');
print('Punkte: $roundScores');
print('${players[caboPlayerIndex]} hat mit ${roundScores[caboPlayerIndex]} '
'Punkten CABO gesagt');
/// List of the index of the player(s) with the lowest score
List<int> lowestScoreIndex = _getLowestScoreIndex(roundScores);
print('Folgende Spieler haben die niedrigsten Punte:');
for (int i in lowestScoreIndex) {
print('${players[i]} (${roundScores[i]} Punkte)');
}
// The player who said CABO is one of the players which have the
// fewest points.
if (lowestScoreIndex.contains(caboPlayerIndex)) {
print('${players[caboPlayerIndex]} hat CABO gesagt '
'und bekommt 0 Punkte');
print('Alle anderen Spieler bekommen ihre Punkte');
_assignPoints(roundNum, roundScores, caboPlayerIndex, [caboPlayerIndex]);
} else {
// A player other than the one who said CABO has the fewest points.
print('${players[caboPlayerIndex]} hat CABO gesagt, '
'jedoch nicht die wenigsten Punkte.');
print('Folgende:r Spieler haben die wenigsten Punkte:');
for (int i in lowestScoreIndex) {
print('${players[i]}: ${roundScores[i]} Punkte');
}
_assignPoints(roundNum, roundScores, caboPlayerIndex, lowestScoreIndex,
caboPlayerIndex);
}
}
/// The _getLowestScoreIndex method but forwarded for testing purposes.
@visibleForTesting
List<int> testingGetLowestScoreIndex(List<int> roundScores) =>
_getLowestScoreIndex(roundScores);
/// 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<int> _getLowestScoreIndex(List<int> roundScores) {
int lowestScore = roundScores[0];
List<int> lowestScoreIndex = [0];
for (int i = 1; i < roundScores.length; i++) {
if (roundScores[i] < lowestScore) {
lowestScore = roundScores[i];
lowestScoreIndex = [i];
} else if (roundScores[i] == lowestScore) {
lowestScoreIndex.add(i);
}
}
return lowestScoreIndex;
}
@visibleForTesting
void testingAssignPoints(int roundNum, List<int> roundScores,
int caboPlayerIndex, List<int> winnerIndex, [int? loserIndex]) =>
_assignPoints(
roundNum, roundScores, caboPlayerIndex, winnerIndex, loserIndex);
/// Assigns points to the players based on the scores of the current round.
/// [roundNum] is the number of the current round.
/// [roundScores] is the raw list of the scores of all players in the current round.
/// [winnerIndex] is the index of the player who receives 5 extra points
void _assignPoints(int roundNum, List<int> roundScores, int caboPlayerIndex,
List<int> winnerIndex,
[int? loserIndex]) {
/// List of the updates for every player score
List<int> scoreUpdates = [...roundScores];
print('Folgende Punkte wurden aus der Runde übernommen:');
for (int i = 0; i < scoreUpdates.length; i++) {
print('${players[i]}: ${scoreUpdates[i]}');
}
for (int i in winnerIndex) {
print('${players[i]} hat gewonnen und bekommt 0 Punkte');
scoreUpdates[i] = 0;
}
if (loserIndex != null) {
print('${players[loserIndex]} bekommt 5 Fehlerpunkte');
scoreUpdates[loserIndex] += 5;
}
print('Aktualisierte Punkte:');
for (int i = 0; i < scoreUpdates.length; i++) {
print('${players[i]}: ${scoreUpdates[i]}');
}
print('scoreUpdates: $scoreUpdates, roundScores: $roundScores');
addRoundScoresToList(roundNum, roundScores, scoreUpdates, caboPlayerIndex);
}
/// Sets the scores of the players for a specific round.
/// This method takes a list of round scores and a round number as parameters.
/// It then replaces the values for the given [roundNum] in the
/// playerScores. Its important that each index of the [roundScores] list
/// corresponds to the index of the player in the [playerScores] list.
void addRoundScoresToList(
int roundNum,
List<int> roundScores,
List<int> scoreUpdates,
int caboPlayerIndex, [
int? kamikazePlayerIndex,
]) {
Round newRound = Round(
roundNum: roundNum,
caboPlayerIndex: caboPlayerIndex,
kamikazePlayerIndex: kamikazePlayerIndex,
scores: roundScores,
scoreUpdates: scoreUpdates,
);
if (roundNum > roundList.length) {
roundList.add(newRound);
} else {
roundList[roundNum - 1] = newRound;
}
notifyListeners();
}
/// 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.
/// 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.
/// If the game has the point limit activated, it first applies the
/// _subtractPointsForReachingHundred() method to subtract 50 points
/// for every time a player reached 100 points in the game.
/// It then checks if any player has exceeded 100 points. If so, it sets
/// isGameFinished to true and calls the _setWinner() method to determine
/// the winner.
/// 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();
if (isPointsLimitEnabled) {
bonusPlayers = _checkHundredPointsReached();
for (int i = 0; i < playerScores.length; i++) {
if (playerScores[i] > pointLimit) {
isGameFinished = true;
print('${players[i]} hat die 100 Punkte ueberschritten, '
'deswegen wurde das Spiel beendet');
setWinner();
}
}
}
notifyListeners();
return bonusPlayers;
}
@visibleForTesting
void testingSumPoints() => _sumPoints();
/// Sums up the points of all players and stores the result in the
/// playerScores list.
void _sumPoints() {
for (int i = 0; i < players.length; i++) {
playerScores[i] = 0;
for (int j = 0; j < roundList.length; j++) {
playerScores[i] += roundList[j].scoreUpdates[i];
}
}
notifyListeners();
}
/// Checks if a player has reached 100 points in the current round.
/// If so, it updates the [scoreUpdate] List by subtracting 50 points from
/// the corresponding round update.
List<int> _checkHundredPointsReached() {
List<int> bonusPlayers = [];
for (int i = 0; i < players.length; i++) {
if (playerScores[i] == pointLimit) {
bonusPlayers.add(i);
print('${players[i]} hat genau 100 Punkte erreicht und bekommt '
'deswegen ${(pointLimit / 2).round()} Punkte abgezogen');
roundList[roundNumber - 1].scoreUpdates[i] -= (pointLimit / 2).round();
}
}
_sumPoints();
return bonusPlayers;
}
/// Determines the winner of the game session.
/// It iterates through the player scores and finds the player
/// with the lowest score.
void setWinner() {
int minScore = playerScores.reduce((a, b) => a < b ? a : b);
List<String> lowestPlayers = [];
for (int i = 0; i < players.length; i++) {
if (playerScores[i] == minScore) {
lowestPlayers.add(players[i]);
}
}
if (lowestPlayers.length > 1) {
winner =
'${lowestPlayers.sublist(0, lowestPlayers.length - 1).join(', ')} & ${lowestPlayers.last}';
} else {
winner = lowestPlayers.first;
}
notifyListeners();
}
/// Increases the round number by 1.
void increaseRound() {
roundNumber++;
print('roundNumber erhöht: $roundNumber — Hash: ${identityHashCode(this)}');
notifyListeners();
}
}