1
0

Add Group Bracket Generator

Dieser Commit ist enthalten in:
Chaoscaot 2023-02-10 17:34:53 +01:00
Ursprung 6b8f631235
Commit 1a515603b0
6 geänderte Dateien mit 370 neuen und 105 gelöschten Zeilen

Datei anzeigen

@ -22,6 +22,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/components/date_time_editor.dart';
@ -204,7 +205,7 @@ class EventFightList extends HookConsumerWidget {
update();
}
},
icon: const Icon(Icons.person)),
icon: const Icon(Icons.gavel)),
),
Tooltip(
message: "Change Group",
@ -302,6 +303,13 @@ class EventFightList extends HookConsumerWidget {
icon: const Icon(Icons.add),
),
const SizedBox(width: 8),
FilledButton.icon(
onPressed: () {
context.go("/event/${event.id}/generator");
},
label: const Text("Generate Fight"),
icon: const Icon(Icons.shuffle)),
const SizedBox(width: 8),
PopupMenuButton(
itemBuilder: (context) {
return const [

Datei anzeigen

@ -118,7 +118,7 @@ class LoadedEventScreen extends HookConsumerWidget {
: null,
icon: const Icon(Icons.save)),
],
leading: IconButton(
leading: BackButton(
onPressed: () async {
var canPop = false;
if (changed.value) {
@ -147,7 +147,6 @@ class LoadedEventScreen extends HookConsumerWidget {
}
if (canPop) context.go("/");
},
icon: const Icon(Icons.arrow_back),
),
),
body: Padding(
@ -283,7 +282,7 @@ class LoadedEventScreen extends HookConsumerWidget {
side: const BorderSide(color: Colors.transparent),
label: Text(team.name,
style: TextStyle(
color: team.color.computeLuminance() > 0.5
color: team.color.computeLuminance() > 0.4
? Colors.black
: Colors.white)),
backgroundColor: team.color,

Datei anzeigen

@ -17,10 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:steamwar_multitool/src/components/components.dart';
import 'package:steamwar_multitool/src/delegates/delegates.dart';
import 'package:steamwar_multitool/src/provider/data.dart';
import 'package:steamwar_multitool/src/provider/events.dart';
import 'package:steamwar_multitool/src/types/types.dart';
import 'package:steamwar_multitool/src/util/constants.dart';
class GroupBracketGenerator extends HookConsumerWidget {
final EventExtended event;
@ -47,49 +55,233 @@ class GroupBracketGenerator extends HookConsumerWidget {
}
}, [groups.value]);
Team getTeam(int id) =>
event.teams.firstWhere((element) => element.id == id);
final startTime = useState(event.event.start);
final roundTime = useState(const Duration(minutes: 30));
final startDelay = useState(const Duration(seconds: 30));
final gamemode = useState<String?>(null);
final map = useState<String?>(null);
final canProceed = useMemoized(
() =>
groups.value.every((element) => element.length >= 2) &&
groups.value.reduce((value, element) => value + element).length ==
event.teams.length,
[groups.value, notInGroupTeams]);
groups.value.isNotEmpty &&
gamemode.value != null &&
map.value != null,
[groups.value, notInGroupTeams, gamemode.value, map.value]);
final fights = useMemoized(() {
List<List<List<List<int>>>> groupFights = [];
final random = Random();
for (final group in groups.value) {
int rounds = group.length - 1;
List<List<List<int>>> groupFight = [];
for (int i = 0; i < rounds; i++) {
final availableTeams = group.toList();
if (group.length % 2 == 1) availableTeams.removeAt(i);
final List<List<int>> roundFights = [];
while (availableTeams.isNotEmpty) {
final team1 = availableTeams.removeAt(0);
final team2 = availableTeams.removeAt(i % availableTeams.length);
final fight = [team1, team2];
fight.shuffle(random);
roundFights.add(fight);
}
groupFight.add(roundFights);
}
groupFights.add(groupFight);
}
return groupFights;
}, [groups.value]);
return Scaffold(
appBar: AppBar(
title: Text('Group Bracket Generator'),
),
floatingActionButton: FloatingActionButton(
onPressed: !canProceed
? null
: () {
groups.value.add([]);
},
child: Icon(Icons.navigate_next),
),
body: Column(
mainAxisSize: MainAxisSize.max,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.start,
alignment: WrapAlignment.start,
children: [
for (final team in notInGroupTeams)
Padding(
padding: const EdgeInsets.all(8.0),
child: Draggable<Team>(
data: team,
feedback: Material(
color: Colors.transparent, child: _TeamChip(team)),
child: _TeamChip(team),
),
),
],
title: const Text('Group Bracket Generator'),
actions: [
IconButton(
icon: const Icon(Icons.sync),
tooltip: "Auto Generate",
onPressed: () async {
final size = await showDialog(
context: context,
builder: (context) {
return HookBuilder(builder: (context) {
final groupsCount = useState(event.teams.length ~/ 2);
return AlertDialog(
title: const Text("Groups"),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Groups: ${groupsCount.value}"),
Slider(
value: groupsCount.value.toDouble(),
min: 1,
max: event.teams.length / 2,
divisions: (event.teams.length ~/ 2) - 1,
label: groupsCount.value.toString(),
onChanged: (value) {
groupsCount.value = value.toInt();
},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(0),
child: const Text("Cancel"),
),
FilledButton(
onPressed: () =>
Navigator.of(context).pop(groupsCount.value),
child: const Text("Ok"),
),
],
);
});
});
if (size == 0) return;
final random = Random();
final teams = event.teams.toList();
teams.shuffle(random);
final groupsNew = List.generate(size, (index) => <int>[]);
int k = 0;
for (int i = 0; i < teams.length; i++) {
groupsNew[k].add(teams[i].id);
k++;
if (k >= size) k = 0;
}
groups.value = groupsNew;
},
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.start,
alignment: WrapAlignment.start,
children: [
for (final group in groups.value)
],
leading: BackButton(
onPressed: () => context.go('/event/${event.event.id}'),
),
),
floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.check),
onPressed: canProceed
? () async {
final repo = await ref.read(eventRepositoryProvider.future);
for (final group in fights) {
for (final round in group) {
for (final fight in round) {
final blue = getTeam(fight[0]);
final red = getTeam(fight[1]);
final start = startTime.value.add(
roundTime.value * group.indexOf(round) +
startDelay.value *
(round.indexOf(fight) +
(fights.indexOf(group) * round.length)),
);
await repo.createFight(
event.event.id,
start,
gamemode.value!,
map.value!,
blue,
red,
0,
"Gruppe ${fights.indexOf(group) + 1}");
}
}
}
context.go("/events/${event.event.id}");
}
: null,
label: const Text("Generate")),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.start,
alignment: WrapAlignment.start,
children: [
for (final team in notInGroupTeams)
Padding(
padding: const EdgeInsets.all(8.0),
child: Draggable<Team>(
data: team,
feedback: Material(
color: Colors.transparent, child: _TeamChip(team)),
child: _TeamChip(team),
),
),
],
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.start,
alignment: WrapAlignment.start,
children: [
for (final group in groups.value)
Padding(
padding: const EdgeInsets.all(8.0),
child: DragTarget<Team>(
builder: (context, candidateData, rejectedData) {
return ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 200,
maxWidth: 200,
minHeight: 200,
),
child: Card(
child: Column(
children: [
Text(
'Group ${groups.value.indexOf(group) + 1}'),
Wrap(
children: [
for (final teamId in group)
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
child: _TeamChip(
event.teams.firstWhere((element) =>
element.id == teamId),
),
onTap: () {
groups.value = [
for (final addGroup
in groups.value)
if (addGroup.contains(teamId))
addGroup
.where((element) =>
element != teamId)
.toList()
else
addGroup
];
},
),
),
],
),
],
),
),
);
},
onWillAccept: (data) => true,
onAccept: (data) {
groups.value = [
for (final addGroup in groups.value)
if (addGroup.contains(data.id))
addGroup
.where((element) => element != data.id)
.toList()
else if (addGroup == group)
[...addGroup, data.id]
else
addGroup
];
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: DragTarget<Team>(
@ -99,34 +291,8 @@ class GroupBracketGenerator extends HookConsumerWidget {
height: 200,
child: Card(
child: Column(
children: [
Text('Group ${groups.value.indexOf(group) + 1}'),
Wrap(
children: [
for (final teamId in group)
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
child: _TeamChip(
event.teams.firstWhere((element) =>
element.id == teamId),
),
onTap: () {
groups.value = [
for (final addGroup in groups.value)
if (addGroup.contains(teamId))
addGroup
.where((element) =>
element != teamId)
.toList()
else
addGroup
];
},
),
),
],
),
children: const [
Text('New Group'),
],
),
),
@ -135,47 +301,114 @@ class GroupBracketGenerator extends HookConsumerWidget {
onWillAccept: (data) => true,
onAccept: (data) {
groups.value = [
for (final addGroup in groups.value)
if (addGroup.contains(data.id))
addGroup
.where((element) => element != data.id)
.toList()
else if (addGroup == group)
[...addGroup, data.id]
else
addGroup
...groups.value,
[data.id],
];
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: DragTarget<Team>(
builder: (context, candidateData, rejectedData) {
return SizedBox(
width: 200,
height: 200,
child: Card(
child: Column(
children: const [
Text('New Group'),
],
),
Divider(),
Column(
children: [
Row(
children: [
const Text('Start Time:'),
DateTimeEditor((p0) {
startTime.value = p0;
}, startTime.value, true),
],
),
Text('Round Time: ${roundTime.value.inMinutes} min'),
Slider(
value: roundTime.value.inMinutes.toDouble(),
onChanged: (t) =>
roundTime.value = Duration(minutes: t.round()),
min: 5,
max: 60,
divisions: 60 - 5,
label: '${roundTime.value.inMinutes} min'),
Text('Start Delay: ${startDelay.value.inSeconds} sec'),
Slider(
value: startDelay.value.inSeconds.toDouble(),
onChanged: (t) =>
startDelay.value = Duration(seconds: t.round()),
min: 0,
max: 30,
divisions: 30,
label: '${startDelay.value.inSeconds} sec'),
Row(
children: [
const Text('Gamemode:'),
TextButton(
onPressed: () async {
final newmode = await showSearch(
context: context,
delegate: GamemodeSearchDelegate(
await ref.read(fightServersProvider.future)));
gamemode.value = newmode;
},
child: Text(gamemode.value ?? 'None')),
],
),
Row(
children: [
const Text('Map:'),
TextButton(
onPressed: gamemode.value != null
? () async {
final newmap = await showSearch(
context: context,
delegate: MapSearchDelegate(await ref
.read(mapsProvider.future)
.then((value) =>
value[gamemode.value]!)));
map.value = newmap;
}
: null,
child: Text(map.value ?? 'None')),
],
),
const Divider(),
for (final group in fights)
Column(
children: [
Text('Group ${fights.indexOf(group) + 1}',
style: Theme.of(context).textTheme.headlineMedium),
for (final round in group)
Column(
children: [
Text('Round ${group.indexOf(round) + 1}',
style:
Theme.of(context).textTheme.headlineSmall),
const Divider(),
for (final fight in round)
ListTile(
leading: Chip(
label: Text(
kDateFormat.format(
startTime.value.add(
roundTime.value * group.indexOf(round) +
startDelay.value *
(round.indexOf(fight) +
(fights.indexOf(group) *
round.length)),
),
),
),
),
title: Text(
'${getTeam(fight[0]).name} vs ${getTeam(fight[1]).name}'),
),
],
),
),
);
},
onWillAccept: (data) => true,
onAccept: (data) {
groups.value = [
...groups.value,
[data.id],
];
},
),
),
],
),
],
],
)
],
),
],
),
),
);
}

Datei anzeigen

@ -24,7 +24,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:steamwar_multitool/src/provider/mods.dart';
import 'package:steamwar_multitool/src/provider/provider.dart';
import 'package:steamwar_multitool/src/screens/home/lists/events_list.dart';

Datei anzeigen

@ -19,4 +19,4 @@
import 'package:intl/intl.dart';
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm");
final kDateFormat = DateFormat("dd.MM.yyyy HH:mm:ss");

26
lib/src/util/functions.dart Normale Datei
Datei anzeigen

@ -0,0 +1,26 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2023 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
int wrapAround(int value, int max) {
if (value < max) {
return value;
} else {
return value % max;
}
}