Updated folder structure
This commit is contained in:
78
lib/data/dto/game_manager.dart
Normal file
78
lib/data/dto/game_manager.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:cabo_counter/data/dto/game_session.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class GameManager extends ChangeNotifier {
|
||||
List<GameSession> gameList = [];
|
||||
|
||||
/// Adds a new game session to the list and sorts it by creation date.
|
||||
/// Takes a [GameSession] object as input. It then adds the session to the `gameList`,
|
||||
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
|
||||
/// It also saves the updated game sessions to local storage.
|
||||
/// Returns the index of the newly added session in the sorted list.
|
||||
int addGameSession(GameSession session) {
|
||||
session.addListener(() {
|
||||
notifyListeners(); // Propagate session changes
|
||||
});
|
||||
gameList.add(session);
|
||||
gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
notifyListeners();
|
||||
LocalStorageService.saveGameSessions();
|
||||
return gameList.indexOf(session);
|
||||
}
|
||||
|
||||
/// Retrieves a game session by its id.
|
||||
/// Takes a String [id] as input. It searches the `gameList` for a session
|
||||
/// with a matching id and returns it if found.
|
||||
/// If no session is found, it returns null.
|
||||
GameSession? getGameSessionById(String id) {
|
||||
return gameList.firstWhereOrNull((session) => session.id == id);
|
||||
}
|
||||
|
||||
/// Removes a game session from the list and sorts it by creation date.
|
||||
/// Takes a [index] as input. It then removes the session at the specified index from the `gameList`,
|
||||
/// sorts the list in descending order based on the creation date, and notifies listeners of the change.
|
||||
/// It also saves the updated game sessions to local storage.
|
||||
void removeGameSessionByIndex(int index) {
|
||||
gameList[index].removeListener(notifyListeners);
|
||||
gameList.removeAt(index);
|
||||
notifyListeners();
|
||||
LocalStorageService.saveGameSessions();
|
||||
}
|
||||
|
||||
/// Removes a game session by its ID.
|
||||
/// Takes a String [id] as input. It finds the index of the game session with the matching ID
|
||||
/// in the `gameList`, and then calls `removeGameSessionByIndex` with that index.
|
||||
void removeGameSessionById(String id) {
|
||||
final int index =
|
||||
gameList.indexWhere((session) => session.id.toString() == id);
|
||||
if (index == -1) return;
|
||||
removeGameSessionByIndex(index);
|
||||
}
|
||||
|
||||
/// Retrieves a game session by its ID.
|
||||
/// Takes a String [id] as input. It finds the game session with the matching id
|
||||
bool gameExistsInGameList(String id) {
|
||||
return gameList.any((session) => session.id.toString() == id);
|
||||
}
|
||||
|
||||
/// Ends a game session if its in unlimited mode.
|
||||
/// Takes a String [id] as input. It finds the index of the game
|
||||
/// session with the matching ID marks it as finished,
|
||||
void endGame(String id) {
|
||||
final int index =
|
||||
gameList.indexWhere((session) => session.id.toString() == id);
|
||||
|
||||
// Game session not found or not in unlimited mode
|
||||
if (index == -1 || gameList[index].isPointsLimitEnabled == true) return;
|
||||
|
||||
gameList[index].roundNumber--;
|
||||
gameList[index].isGameFinished = true;
|
||||
gameList[index].setWinner();
|
||||
notifyListeners();
|
||||
LocalStorageService.saveGameSessions();
|
||||
}
|
||||
}
|
||||
|
||||
final gameManager = GameManager();
|
||||
320
lib/data/dto/game_session.dart
Normal file
320
lib/data/dto/game_session.dart
Normal file
@@ -0,0 +1,320 @@
|
||||
import 'package:cabo_counter/data/dto/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();
|
||||
|
||||
/// 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 (bonus player)
|
||||
/// in the current round for the [RoundView] to show a popup
|
||||
List<int> updatePoints() {
|
||||
List<int> bonusPlayers = [];
|
||||
_sumPoints();
|
||||
|
||||
if (isPointsLimitEnabled) {
|
||||
bonusPlayers = _checkHundredPointsReached();
|
||||
bool limitExceeded = false;
|
||||
|
||||
for (int i = 0; i < playerScores.length; i++) {
|
||||
if (playerScores[i] > pointLimit) {
|
||||
isGameFinished = true;
|
||||
limitExceeded = true;
|
||||
print('${players[i]} hat die 100 Punkte ueberschritten, '
|
||||
'deswegen wurde das Spiel beendet');
|
||||
setWinner();
|
||||
}
|
||||
}
|
||||
if (!limitExceeded) {
|
||||
isGameFinished = false;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
46
lib/data/dto/round.dart
Normal file
46
lib/data/dto/round.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
/// This class represents a single round in the game.
|
||||
/// It is stored within the [GameSession] class.
|
||||
/// [roundNum] is the number of the round its reppresenting.
|
||||
/// [scores] is a list of the actual scores the players got.
|
||||
/// [scoreUpdates] is a list of how the players scores updated this round.
|
||||
/// [kamikazePlayerIndex] is the index of the player who got kamikaze. If no one got
|
||||
/// kamikaze, this value is null.
|
||||
class Round {
|
||||
final int roundNum;
|
||||
final int caboPlayerIndex;
|
||||
final int? kamikazePlayerIndex;
|
||||
final List<int> scores;
|
||||
final List<int> scoreUpdates;
|
||||
|
||||
Round({
|
||||
required this.roundNum,
|
||||
required this.caboPlayerIndex,
|
||||
this.kamikazePlayerIndex,
|
||||
required this.scores,
|
||||
required this.scoreUpdates,
|
||||
});
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return 'Round $roundNum, caboPlayerIndex: $caboPlayerIndex, '
|
||||
'kamikazePlayerIndex: $kamikazePlayerIndex, scores: $scores, '
|
||||
'scoreUpdates: $scoreUpdates, ';
|
||||
}
|
||||
|
||||
/// Converts the Round object to a JSON map.
|
||||
Map<String, dynamic> toJson() => {
|
||||
'roundNum': roundNum,
|
||||
'caboPlayerIndex': caboPlayerIndex,
|
||||
'kamikazePlayerIndex': kamikazePlayerIndex,
|
||||
'scores': scores,
|
||||
'scoreUpdates': scoreUpdates,
|
||||
};
|
||||
|
||||
/// Creates a Round object from a JSON map.
|
||||
Round.fromJson(Map<String, dynamic> json)
|
||||
: roundNum = json['roundNum'],
|
||||
caboPlayerIndex = json['caboPlayerIndex'],
|
||||
kamikazePlayerIndex = json['kamikazePlayerIndex'],
|
||||
scores = List<int>.from(json['scores']),
|
||||
scoreUpdates = List<int>.from(json['scoreUpdates']);
|
||||
}
|
||||
Reference in New Issue
Block a user