Neuer Regelsatz: Platzierung #213

Merged
sneeex merged 22 commits from feature/206-Neuer-Regelsatz-Platzierung into development 2026-05-09 21:37:30 +00:00
11 changed files with 176 additions and 6 deletions
Showing only changes of commit bc997633eb - Show all commits

View File

@@ -18,6 +18,8 @@ String translateRulesetToString(Ruleset ruleset, BuildContext context) {
return loc.single_loser;
case Ruleset.multipleWinners:
return loc.multiple_winners;
case Ruleset.placement:
return loc.placement;
}
}

View File

@@ -32,12 +32,14 @@ enum ExportResult { success, canceled, unknownException }
/// - [Ruleset.singleWinner]: The match is won by a single player.
/// - [Ruleset.singleLoser]: The match has a single loser.
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
/// - [Ruleset.placement]: The player with the highest placement wins.
enum Ruleset {
highestScore,
lowestScore,
singleWinner,
singleLoser,
multipleWinners,
placement,
}
/// Different colors available for games

View File

@@ -156,6 +156,9 @@ class Match {
case Ruleset.multipleWinners:
return [];
case Ruleset.placement:
return _getPlayersWithHighestScore().take(1).toList();
}
}

View File

@@ -71,6 +71,7 @@
"none": "Kein",
"none_group": "Keine",
"not_available": "Nicht verfügbar",
"placement": "Platzierung",
"played_matches": "Gespielte Spiele",
"player_name": "Spieler:innenname",
"players": "Spieler:innen",
@@ -85,6 +86,7 @@
"ruleset": "Regelwerk",
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.",
"ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",
"ruleset_placement": "Spieler:innen können in einer Reihenfolge angeordnet werden, die ihre Platzierung reflektiert.",
"ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.",
"ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.",
"save_changes": "Änderungen speichern",

View File

@@ -77,6 +77,9 @@
"@delete_match": {
"description": "Button text to delete a match"
},
"@drag_to_set_placement": {
"description": "Label for dragging to set placement"
},
"@edit_group": {
"description": "Button & Appbar label for editing a group"
},
@@ -218,6 +221,9 @@
"@not_available": {
"description": "Abbreviation for not available"
},
"@placement": {
"description": "Title for placement ruleset"
},
"@played_matches": {
"description": "Label for played matches statistic"
},
@@ -259,6 +265,9 @@
"@ruleset_most_points": {
"description": "Description for most points ruleset"
},
"@ruleset_placement": {
"description": "Description for placement ruleset"
},
"@ruleset_single_loser": {
"description": "Description for single loser ruleset"
},
@@ -358,6 +367,7 @@
"delete_all_data": "Delete all data",
"delete_group": "Delete Group",
"delete_match": "Delete Match",
"drag_to_set_placement": "Drag to set placement",
"edit_group": "Edit Group",
"edit_match": "Edit Match",
"enter_points": "Enter points",
@@ -405,6 +415,7 @@
"none": "None",
"none_group": "None",
"not_available": "Not available",
"placement": "Placement",
"played_matches": "Played Matches",
"player_name": "Player name",
"players": "Players",
@@ -418,6 +429,7 @@
"ruleset": "Ruleset",
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.",
"ruleset_placement": "Players can be arranged in an order, which reflects their placement.",
"ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.",
"ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.",
"save_changes": "Save Changes",

View File

@@ -242,6 +242,12 @@ abstract class AppLocalizations {
/// **'Delete Match'**
String get delete_match;
/// Label for dragging to set placement
///
/// In en, this message translates to:
/// **'Drag to set placement'**
String get drag_to_set_placement;
/// Button & Appbar label for editing a group
///
/// In en, this message translates to:
@@ -524,6 +530,12 @@ abstract class AppLocalizations {
/// **'Not available'**
String get not_available;
/// Title for placement ruleset
///
/// In en, this message translates to:
/// **'Placement'**
String get placement;
/// Label for played matches statistic
///
/// In en, this message translates to:
@@ -602,6 +614,12 @@ abstract class AppLocalizations {
/// **'Traditional ruleset: the player with the most points wins.'**
String get ruleset_most_points;
/// Description for placement ruleset
///
/// In en, this message translates to:
/// **'Players can be arranged in an order, which reflects their placement.'**
String get ruleset_placement;
/// Description for single loser ruleset
///
/// In en, this message translates to:

View File

@@ -84,6 +84,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get delete_match => 'Spiel löschen';
@override
String get drag_to_set_placement => 'Ziehen, um die Platzierung zu setzen';
@override
String get edit_group => 'Gruppe bearbeiten';
@@ -229,6 +232,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get not_available => 'Nicht verfügbar';
@override
String get placement => 'Platzierung';
@override
String get played_matches => 'Gespielte Spiele';
@@ -272,6 +278,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get ruleset_most_points =>
'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.';
@override
String get ruleset_placement =>
'Spieler:innen können in einer Reihenfolge angeordnet werden, die ihre Platzierung reflektiert.';
@override
String get ruleset_single_loser =>
'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.';

View File

@@ -84,6 +84,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get delete_match => 'Delete Match';
@override
String get drag_to_set_placement => 'Drag to set placement';
@override
String get edit_group => 'Edit Group';
@@ -229,6 +232,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get not_available => 'Not available';
@override
String get placement => 'Placement';
@override
String get played_matches => 'Played Matches';
@@ -272,6 +278,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get ruleset_most_points =>
'Traditional ruleset: the player with the most points wins.';
@override
String get ruleset_placement =>
'Players can be arranged in an order, which reflects their placement.';
@override
String get ruleset_single_loser =>
'Exactly one loser is determined; last place receives the penalty or consequence.';

View File

@@ -288,34 +288,39 @@ class _MatchDetailViewState extends State<MatchDetailView> {
}
}
/// Returns the result widget for scores
/// Returns the result widget for scores or placement
Widget getScoreResultWidget(AppLocalizations loc) {
List<(String, int)> playerScores = [];
for (var player in match.players) {
int score = match.scores[player.id]?.score ?? 0;
playerScores.add((player.name, score));
}
if (widget.match.game.ruleset == Ruleset.highestScore) {
final ruleset = match.game.ruleset;
if (ruleset == Ruleset.highestScore || ruleset == Ruleset.placement) {
playerScores.sort((a, b) => b.$2.compareTo(a.$2));
} else if (widget.match.game.ruleset == Ruleset.lowestScore) {
} else if (ruleset == Ruleset.lowestScore) {
sneeex marked this conversation as resolved Outdated

Würde glaub ich hierfür Links in Orange Bold Font "1. (2., 3., ...) Platz" und Rechts dann den Namen des Spielers in White Normal Font

Würde glaub ich hierfür Links in Orange Bold Font "1. (2., 3., ...) Platz" und Rechts dann den Namen des Spielers in White Normal Font

ist aber irgendwie bisschen weird dann anders als die anderen oder?1 weil bisher ist ja alle infos immer rechts und links die spielernamen

ist aber irgendwie bisschen weird dann anders als die anderen oder?1 weil bisher ist ja alle infos immer rechts und links die spielernamen

Ja sonst ersetz mal erstmal die #<num> durch <num>. Platz

Ja sonst ersetz mal erstmal die `#<num>` durch `<num>. Platz`
playerScores.sort((a, b) => a.$2.compareTo(b.$2));
}
return Column(
children: [
for (var score in playerScores)
for (var i = 0; i < playerScores.length; i++)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
score.$1,
playerScores[i].$1,
style: const TextStyle(
fontSize: 16,
color: CustomTheme.textColor,
),
),
Text(
getPointLabel(loc, score.$2),
ruleset == Ruleset.placement
? '#${i + 1}'
: getPointLabel(loc, playerScores[i].$2),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,

View File

@@ -10,6 +10,7 @@ import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
import 'package:tallee/presentation/widgets/tiles/score_list_tile.dart';
import 'package:tallee/presentation/widgets/tiles/text_icon_list_tile.dart';
class MatchResultView extends StatefulWidget {
/// A view that allows selecting and saving the winner of a match
@@ -68,6 +69,12 @@ class _MatchResultViewState extends State<MatchResultView> {
final score = scoreList?.score ?? 0;
controller[i].text = score.toString();
}
} else if (rulesetSupportsPlacement()) {
allPlayers.sort((a, b) {
final scoreA = widget.match.scores[a.id]?.score ?? 0;
final scoreB = widget.match.scores[b.id]?.score ?? 0;
return scoreB.compareTo(scoreA);
});
}
super.initState();
}
@@ -177,6 +184,70 @@ class _MatchResultViewState extends State<MatchResultView> {
},
),
),
if (rulesetSupportsPlacement())
Expanded(
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Column(
children: [
for (int i = 0; i < allPlayers.length; i++)
Container(
alignment: Alignment.center,
height: 60,
child: Container(
decoration:
CustomTheme.standardBoxDecoration,
flixcoo marked this conversation as resolved Outdated

Vielleicht andere box decoration, maybe keine border und kompletter container in der border color

Vielleicht andere box decoration, maybe keine border und kompletter container in der border color

tote nummer
grafik.png

tote nummer ![grafik.png](/attachments/a399a850-1b2f-4afe-8f74-e4c28f80941a)

ja fair, aber irgendwie würd ich die gern anders darstellen. Nicht genau so wie die list tiles. Und macht die mal quadratisch

ja fair, aber irgendwie würd ich die gern anders darstellen. Nicht genau so wie die list tiles. Und macht die mal quadratisch

so?
grafik.png

so? ![grafik.png](/attachments/41e1acb6-75c3-4487-86a6-fa77c0b1ff90)

Mach mal color: orange, fontcolor: weiß

Mach mal color: orange, fontcolor: weiß

meinst du so?
sonst probier selber mal rum, du weißt ja besser was du willst

grafik.png

meinst du so? sonst probier selber mal rum, du weißt ja besser was du willst ![grafik.png](/attachments/f4030533-907c-4c68-b5ff-f90cc676859e)
alignment: Alignment.center,
height: 50,
width: 40,
child: Text(
" #${i + 1} ",
style: const TextStyle(
color: CustomTheme.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
],
),
),
Expanded(
child: ReorderableListView.builder(
padding: EdgeInsets.zero,
proxyDecorator: (child, index, animation) {
return Material(
color: Colors.transparent,
child: child,
flixcoo marked this conversation as resolved Outdated

Gerne hier noch ein kleines Highlighting implementieren, wenn ein Tile gedraggt wird

Gerne hier noch ein kleines Highlighting implementieren, wenn ein Tile gedraggt wird

so nen highlighting?

so nen highlighting?

Das mag ich garnicht. Lieber was simples, z.B. BG color etwas heller, ne weiße border oder so

Das mag ich garnicht. Lieber was simples, z.B. BG color etwas heller, ne weiße border oder so

dann schau mal selber ich habe wirklich keine ahnung

dann schau mal selber ich habe wirklich keine ahnung
);
},
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final Player item = allPlayers.removeAt(
oldIndex,
);
allPlayers.insert(newIndex, item);
});
},
itemCount: allPlayers.length,
itemBuilder: (context, index) {
return TextIconListTile(
key: ValueKey(allPlayers[index].id),
text: allPlayers[index].name,
iconEnabled: false,
);
},
),
),
],
),
),
],
),
),
@@ -222,6 +293,8 @@ class _MatchResultViewState extends State<MatchResultView> {
} else if (ruleset == Ruleset.lowestScore ||
ruleset == Ruleset.highestScore) {
await _handleScores();
} else if (ruleset == Ruleset.placement) {
await _handlePlacement();
}
widget.onWinnerChanged?.call();
@@ -267,12 +340,29 @@ class _MatchResultViewState extends State<MatchResultView> {
}
}
/// Handles saving the placement for each player in the database.
Future<void> _handlePlacement() async {
for (int i = 0; i < allPlayers.length; i++) {
await db.scoreEntryDao.addScore(
matchId: widget.match.id,
playerId: allPlayers[i].id,
sneeex marked this conversation as resolved Outdated

Bitte neue Methode setPlacements(List<Player> player) bei der die Platzierung anhand des Index gesetzt wird.

Bitte neue Methode `setPlacements(List<Player> player)` bei der die Platzierung anhand des Index gesetzt wird.

du bist weird, warum soll ich das auslagern, die methode hat doch nur die einzige funktion mit handlePlacement hä

du bist weird, warum soll ich das auslagern, die methode hat doch nur die einzige funktion mit handlePlacement hä

Die Methode soll in die score_entry_dao.dart zu den anderen Methoden wie setWinner(), addScore() etc

Die Methode soll in die `score_entry_dao.dart ` zu den anderen Methoden wie `setWinner()`, `addScore()` etc
entry: ScoreEntry(
roundNumber: 0,
score: allPlayers.length - i,
change: 0,
),
);
}
}
String getTitleForRuleset(AppLocalizations loc) {
switch (ruleset) {
case Ruleset.singleWinner:
return loc.select_winner;
case Ruleset.singleLoser:
return loc.select_loser;
case Ruleset.placement:
return loc.drag_to_set_placement;
default:
return loc.enter_points;
}
@@ -285,4 +375,8 @@ class _MatchResultViewState extends State<MatchResultView> {
bool rulesetSupportsScoreEntry() {
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
}
bool rulesetSupportsPlacement() {
return ruleset == Ruleset.placement;
}
}

View File

@@ -10,6 +10,7 @@ class TextIconListTile extends StatelessWidget {
super.key,
required this.text,
this.suffixText = '',
this.prefixText = '',
this.iconEnabled = true,
this.onPressed,
});
@@ -20,6 +21,9 @@ class TextIconListTile extends StatelessWidget {
/// An optional suffix text to display after the main text.
final String suffixText;
/// An optional prefix text to display before the main text.
final String prefixText;
/// A boolean to determine if the icon should be displayed.
final bool iconEnabled;
@@ -44,6 +48,14 @@ class TextIconListTile extends StatelessWidget {
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: [
TextSpan(
text: prefixText,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: CustomTheme.primaryColor,
),
),
TextSpan(
text: text,
style: const TextStyle(