Updated score and winner handling

This commit is contained in:
2026-04-21 18:38:00 +02:00
parent 522441b0ca
commit 9364f0d9d6
19 changed files with 286 additions and 179 deletions

View File

@@ -2,11 +2,12 @@ import 'package:drift/drift.dart';
import 'package:tallee/core/enums.dart';
import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/db/tables/game_table.dart';
import 'package:tallee/data/db/tables/match_table.dart';
import 'package:tallee/data/models/game.dart';
part 'game_dao.g.dart';
@DriftAccessor(tables: [GameTable])
@DriftAccessor(tables: [MatchTable, GameTable])
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
GameDao(super.db);
@@ -44,6 +45,25 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
);
}
Future<Game> getGameByMatchId({required String matchId}) async {
final query = select(gameTable).join([
innerJoin(matchTable, matchTable.gameId.equalsExp(gameTable.id)),
])..where(matchTable.id.equals(matchId));
final result = await query.getSingle();
final gameRow = result.readTable(gameTable);
return Game(
id: gameRow.id,
name: gameRow.name,
ruleset: Ruleset.values.firstWhere((e) => e.name == gameRow.ruleset),
description: gameRow.description,
color: GameColor.values.firstWhere((e) => e.name == gameRow.color),
icon: gameRow.icon,
createdAt: gameRow.createdAt,
);
}
/// Adds a new [game] to the database.
/// If a game with the same ID already exists, no action is taken.
/// Returns `true` if the game was added, `false` otherwise.

View File

@@ -5,6 +5,8 @@ part of 'game_dao.dart';
// ignore_for_file: type=lint
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
GameDaoManager get managers => GameDaoManager(this);
}
@@ -13,4 +15,8 @@ class GameDaoManager {
GameDaoManager(this._db);
$$GameTableTableTableManager get gameTable =>
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
$$GroupTableTableTableManager get groupTable =>
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
$$MatchTableTableTableManager get matchTable =>
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
}

View File

@@ -34,7 +34,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
matchId: row.id,
);
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
return Match(
id: row.id,
name: row.name,
@@ -45,7 +44,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
createdAt: row.createdAt,
endedAt: row.endedAt,
scores: scores,
winner: winner,
);
}),
);
@@ -68,8 +66,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
final winner = await db.scoreEntryDao.getWinner(matchId: matchId);
return Match(
id: result.id,
name: result.name,
@@ -80,7 +76,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
createdAt: result.createdAt,
endedAt: result.endedAt,
scores: scores,
winner: winner,
);
}
@@ -110,19 +105,14 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
}
for (final pid in match.scores.keys) {
final playerScores = match.scores[pid]!;
await db.scoreEntryDao.addScoresAsList(
entrys: playerScores,
playerId: pid,
matchId: match.id,
);
}
if (match.winner != null) {
await db.scoreEntryDao.setWinner(
matchId: match.id,
playerId: match.winner!.id,
);
final playerScores = match.scores[pid];
if (playerScores != null) {
await db.scoreEntryDao.addScore(
entry: playerScores,
playerId: pid,
matchId: match.id,
);
}
}
});
}
@@ -300,7 +290,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final group = await db.groupDao.getGroupById(groupId: groupId);
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
return Match(
id: row.id,
name: row.name,
@@ -310,7 +299,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
notes: row.notes ?? '',
createdAt: row.createdAt,
endedAt: row.endedAt,
winner: winner,
);
}),
);

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:tallee/core/enums.dart';
import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/db/tables/score_entry_table.dart';
import 'package:tallee/data/models/player.dart';
@@ -83,21 +84,21 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
}
/// Retrieves all scores for a specific match.
Future<Map<String, List<ScoreEntry>>> getAllMatchScores({
Future<Map<String, ScoreEntry?>> getAllMatchScores({
required String matchId,
}) async {
final query = select(scoreEntryTable)
..where((s) => s.matchId.equals(matchId));
final result = await query.get();
final Map<String, List<ScoreEntry>> scoresByPlayer = {};
final Map<String, ScoreEntry?> scoresByPlayer = {};
for (final row in result) {
final score = ScoreEntry(
roundNumber: row.roundNumber,
score: row.score,
change: row.change,
);
scoresByPlayer.putIfAbsent(row.playerId, () => []).add(score);
scoresByPlayer[row.playerId] = score;
}
return scoresByPlayer;
@@ -237,10 +238,25 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
// Retrieves the winner of a match based on the highest score.
Future<Player?> getWinner({required String matchId}) async {
// Check the ruleset of the match
final ruleset = await db.gameDao
.getGameByMatchId(matchId: matchId)
.then((game) => game.ruleset);
final query = select(scoreEntryTable)
..where((s) => s.matchId.equals(matchId))
..orderBy([(s) => OrderingTerm.desc(s.score)])
..limit(1);
..where((s) => s.matchId.equals(matchId));
// If the ruleset is lowestScore, the winner is the player with the lowest
// score so we order by ascending score.
if (ruleset == Ruleset.lowestScore) {
query
..orderBy([(s) => OrderingTerm.asc(s.score)])
..limit(1);
} else {
query
..orderBy([(s) => OrderingTerm.desc(s.score)])
..limit(1);
}
final result = await query.getSingleOrNull();
if (result == null) return null;

View File

@@ -15,27 +15,25 @@ class Match {
final Group? group;
final List<Player> players;
final String notes;
Map<String, List<ScoreEntry>> scores;
Player? winner;
Map<String, ScoreEntry?> scores;
Match({
String? id,
DateTime? createdAt,
this.endedAt,
required this.name,
required this.game,
required this.players,
this.endedAt,
this.group,
this.players = const [],
this.notes = '',
Map<String, List<ScoreEntry>>? scores,
this.winner,
String? id,
DateTime? createdAt,
Map<String, ScoreEntry?>? scores,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(),
scores = scores ?? {for (var player in players) player.id: []};
scores = scores ?? {for (Player p in players) p.id: null};
@override
String toString() {
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, winner: $winner}';
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}';
}
/// Creates a Match instance from a JSON object where related objects are
@@ -71,10 +69,60 @@ class Match {
'gameId': game.id,
'groupId': group?.id,
'playerIds': players.map((player) => player.id).toList(),
'scores': scores.map(
(playerId, scoreList) =>
MapEntry(playerId, scoreList.map((score) => score.toJson()).toList()),
),
'scores': scores,
'notes': notes,
};
List<Player> get mvp {
if (players.isEmpty || scores.isEmpty) return [];
switch (game.ruleset) {
case Ruleset.highestScore:
return _getPlayersWithHighestScore();
case Ruleset.lowestScore:
return _getPlayersWithLowestScore();
case Ruleset.singleWinner:
return [_getPlayersWithHighestScore().first];
case Ruleset.singleLoser:
return [_getPlayersWithLowestScore().first];
case Ruleset.multipleWinners:
return [];
}
}
List<Player> _getPlayersWithHighestScore() {
if (players.isEmpty || scores.isEmpty) return [];
final int highestScore = players
.map((player) => scores[player.id]?.score)
.whereType<int>()
.reduce((max, score) => score > max ? score : max);
return players.where((player) {
final playerScores = scores[player.id];
if (playerScores == null) return false;
return playerScores.score == highestScore;
}).toList();
}
List<Player> _getPlayersWithLowestScore() {
if (players.isEmpty || scores.values.every((score) => score == null)) {
return [];
}
final int lowestScore = players
.map((player) => scores[player.id]?.score)
.whereType<int>()
.reduce((min, score) => score < min ? score : min);
return players.where((player) {
final playerScore = scores[player.id];
if (playerScore == null) return false;
return playerScore.score == lowestScore;
}).toList();
}
}