Skip to content

Commit

Permalink
Merge pull request #421 from softwaremagico/418-kachinuki-generate-or…
Browse files Browse the repository at this point in the history
…der-depending-on-the-competitors-statistics

Added wizard to king of the m and loop
  • Loading branch information
softwaremagico authored May 11, 2024
2 parents d68b74d + c4aecef commit 52e7393
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager)
[![Time](https://img.shields.io/badge/development-614h-blueviolet.svg)]()
[![Time](https://img.shields.io/badge/development-615h-blueviolet.svg)]()

[![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]()
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -267,7 +268,8 @@ public List<ScoreOfCompetitorDTO> getCompetitorsGlobalScoreRankingByClub(Integer
}

public List<ScoreOfCompetitorDTO> getCompetitorsGlobalScoreRanking(Collection<ParticipantDTO> competitors, Integer fromNumberOfDays) {
return getCompetitorsGlobalScoreRanking(competitors, ScoreType.DEFAULT, fromNumberOfDays);
return getCompetitorsGlobalScoreRanking(competitors.stream().filter(Objects::nonNull).collect(Collectors.toCollection(ArrayList::new)),
ScoreType.DEFAULT, fromNumberOfDays);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export class TeamListData {
teams: Team[];
filteredTeams: Team[];

filter(filter: string) {
filter(filter: string): void {
filter = filter.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, "");
this.filteredTeams = this.teams?.filter(team => team.name.normalize('NFD').replace(/\p{Diacritic}/gu, "").toLowerCase().includes(filter) ||
team.members.some(user => user !== undefined && (user.lastname.normalize('NFD').replace(/\p{Diacritic}/gu, "").toLowerCase().includes(filter) ||
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/services/ranking.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class RankingService {
);
}

getCompetitorsGlobalScoreRanking(participants: Participant[] | undefined, fromDays: number | undefined): Observable<ScoreOfCompetitor[]> {
getCompetitorsGlobalScoreRanking(participants: (Participant | undefined)[] | undefined, fromDays: number | undefined): Observable<ScoreOfCompetitor[]> {
this.systemOverloadService.isBusy.next(true);
const url: string = `${this.baseUrl}` + '/competitors';
return this.http.post<ScoreOfCompetitor[]>(url, participants, {
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/app/utils/teams/members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Participant} from "../../models/participant";
import {random} from "../random/random";

export function getBalancedMember(_participants: (Participant | undefined)[], selectFromSector: number, availableSectors: number): Participant {
const participants: (Participant | undefined)[] = _participants.filter((item: Participant | undefined) => !!item);
let selected: number = Math.floor(random() * (participants.length / availableSectors));
let participant: Participant;
if (selectFromSector == 0) {
participant = participants[selected]!;
participants.splice(selected, 1);
} else if (selectFromSector == availableSectors - 1) {
selected = participants.length - selected - 1;
participant = participants[selected]!;
participants.splice(selected, 1);
} else {
selected = Math.floor((participants.length / availableSectors)) * selectFromSector + selected;
participant = participants[selected]!;
participants.splice(selected, 1);
}
return participant;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ <h2>{{'teamsOrder' | translate}}</h2>
<app-team-card *ngFor="let team of teamsOrder" [team]="team"></app-team-card>
</div>

<div *ngIf="canMaximizeFights || needsDrawResolution || needsFifoWinner || canAvoidDuplicatedFights" class="extra-properties">
<div *ngIf="canMaximizeFights || needsDrawResolution || needsFifoWinner || canAvoidDuplicatedFights"
class="extra-properties">
<mat-slide-toggle *ngIf="canMaximizeFights" [checked]="areFightsMaximized" (change)="maxFightsToggle($event)"
[matTooltipShowDelay]="500" matTooltip="{{'maximizeFightsHint' | translate}}">
{{'maximizeFights' | translate }}
</mat-slide-toggle>

<mat-slide-toggle *ngIf="canAvoidDuplicatedFights" [checked]="avoidDuplicatedFights" (change)="avoidDuplicatesToggle($event)">
<mat-slide-toggle *ngIf="canAvoidDuplicatedFights" [checked]="avoidDuplicatedFights"
(change)="avoidDuplicatesToggle($event)">
{{'avoidDuplicateFights' | translate }}
</mat-slide-toggle>

Expand All @@ -41,6 +43,15 @@ <h2>{{'teamsOrder' | translate}}</h2>
</div>
</div>
<div class="button-container" mat-dialog-actions>
<button
(click)="balancedTeams()"
*ngIf="(tournament.type ==TournamentType.KING_OF_THE_MOUNTAIN || tournament.type ==TournamentType.LOOP)
&& (RbacActivity.CREATE_FIGHT | rbac : this.rbacService.getActivities())"
mat-flat-button
mat-raised-button>
<mat-icon>auto_fix_high</mat-icon>
{{'wizard' | translate}}
</button>
<button (click)="sortedTeams()" *ngIf="(RbacActivity.CREATE_FIGHT | rbac : this.rbacService.getActivities())"
class="fight-button" mat-flat-button
mat-raised-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {MessageService} from "../../../services/message.service";
import {DrawResolution} from "../../../models/draw-resolution";
import {MatSlideToggleChange} from "@angular/material/slide-toggle";
import {LeagueFightsOrder} from "../../../models/league-fights-order";
import {Participant} from "../../../models/participant";
import {ScoreOfCompetitor} from "../../../models/score-of-competitor";
import {RankingService} from "../../../services/ranking.service";

@Component({
selector: 'app-league-generator',
Expand All @@ -27,6 +30,7 @@ import {LeagueFightsOrder} from "../../../models/league-fights-order";
export class LeagueGeneratorComponent extends RbacBasedComponent implements OnInit {

teamListData: TeamListData = new TeamListData();
teams: Team[];
title: string;
action: Action;
actionName: string;
Expand All @@ -50,7 +54,7 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
constructor(public dialogRef: MatDialogRef<LeagueGeneratorComponent>,
private teamService: TeamService, rbacService: RbacService, private tournamentService: TournamentService,
private tournamentExtendedPropertiesService: TournamentExtendedPropertiesService,
private messageService: MessageService,
private messageService: MessageService, private rankingService: RankingService,
@Optional() @Inject(MAT_DIALOG_DATA) public data: {
title: string,
action: Action,
Expand Down Expand Up @@ -90,14 +94,15 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
});
}

this.teamService.getFromTournament(this.tournament).subscribe((teams: Team[]): void => {
if (teams) {
teams.sort(function (a: Team, b: Team) {
this.teamService.getFromTournament(this.tournament).subscribe((_teams: Team[]): void => {
if (_teams) {
_teams.sort(function (a: Team, b: Team) {
return a.name.localeCompare(b.name);
});
}
this.teamListData.teams = teams;
this.teamListData.filteredTeams = teams;
this.teams = _teams;
this.teamListData.teams = _teams;
this.teamListData.filteredTeams = _teams;
});
this.avoidDuplicates.valueChanges.subscribe(avoidDuplicates => {
const tournamentProperty: TournamentExtendedProperty = new TournamentExtendedProperty();
Expand Down Expand Up @@ -156,7 +161,7 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
}
}

sortedTeams() {
sortedTeams(): void {
this.teamsOrder.push(...this.teamListData.teams);
this.teamsOrder.sort(function (a: Team, b: Team) {
return a.name.localeCompare(b.name);
Expand All @@ -165,7 +170,7 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
this.teamListData.teams.splice(0, this.teamListData.teams.length);
}

randomTeams() {
randomTeams(): void {
this.teamListData.teams.push(...this.teamsOrder);
this.teamsOrder = [];
while (this.teamListData.teams.length > 0) {
Expand All @@ -181,6 +186,41 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
return team;
}

balancedTeams(): void {
let participants: (Participant | undefined)[] = this.teams.flatMap((team: Team) => team.members);

this.rankingService.getCompetitorsGlobalScoreRanking(participants, undefined).subscribe((_scoreRanking: ScoreOfCompetitor[]): void => {
const sortedParticipants: Participant[] = _scoreRanking.map((scoreOfCompetitor: ScoreOfCompetitor) => scoreOfCompetitor.competitor);
//Get Teams classification by members index.
const teamsScore: Map<number, Team> = new Map();
for (let team of this.teams) {
//Accumulate the index of each member. Lower member means better participant statistics.
let score: number = 0;
for (let member of team.members) {
if (member != undefined) {
score += sortedParticipants.indexOf(<Participant>sortedParticipants.find(p => p.id === member?.id));
}
}
//Ensure not collides.
while (teamsScore.get(score)) {
score++;
}
teamsScore.set(score, team);
}

//Sort map and assign:
this.teamsOrder = [];
const sortedTeams: Map<number, Team> = new Map([...teamsScore.entries()].sort());
this.teamsOrder.push(...sortedTeams.values());
this.teamListData.teams = [];
this.teamListData.filteredTeams = [];
if (this.tournament.type == TournamentType.LOOP) {
this.teamsOrder = this.teamsOrder.reverse();
}
});
}


getDrawResolutionTranslationTag(drawResolution: DrawResolution): string {
if (!drawResolution) {
return "";
Expand Down Expand Up @@ -235,4 +275,6 @@ export class LeagueGeneratorComponent extends RbacBasedComponent implements OnIn
this.messageService.infoMessage('infoTournamentUpdated');
});
}

protected readonly TournamentType = TournamentType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {Fight} from "../../../models/fight";
import {Role} from "../../../models/role";
import {ScoreOfCompetitor} from "../../../models/score-of-competitor";
import {TournamentType} from "../../../models/tournament-type";
import {getBalancedMember} from "../../../utils/teams/members";

@Component({
selector: 'app-tournament-teams',
Expand Down Expand Up @@ -382,10 +383,11 @@ export class TournamentTeamsComponent extends RbacBasedComponent implements OnIn
participants = [...Array.prototype.concat.apply([], [...this.members.values()]), ...this.userListData.participants];

this.rankingService.getCompetitorsGlobalScoreRanking(participants, undefined).subscribe((_scoreRanking: ScoreOfCompetitor[]): void => {
const sortedParticipants: Participant[] = _scoreRanking.map((scoreOfCompetitor: ScoreOfCompetitor) => scoreOfCompetitor.competitor);
for (let team of this.teams) {
team.members = [];
for (let i = 0; i < (this.tournament.teamSize ? this.tournament.teamSize : 1); i++) {
const participant: Participant = this.getBalancedMember(participants, team.members.length,
const participant: Participant = getBalancedMember(sortedParticipants, team.members.length,
(this.tournament.teamSize ? this.tournament.teamSize : 1));
if (participant) {
team.members[i] = participant;
Expand All @@ -400,7 +402,7 @@ export class TournamentTeamsComponent extends RbacBasedComponent implements OnIn
).subscribe(() => this.statisticsChangedService.areStatisticsChanged.next(true));
}
//Remaining one on left column.
this.userListData.participants = participants;
this.userListData.participants = sortedParticipants;
this.userListData.filteredParticipants = this.userListData.participants;
});
}
Expand Down Expand Up @@ -436,24 +438,6 @@ export class TournamentTeamsComponent extends RbacBasedComponent implements OnIn
return participant;
}

getBalancedMember(participants: Participant[], selectFromSector: number, availableSectors: number): Participant {
let selected: number = Math.floor(random() * (participants.length / availableSectors));
let participant: Participant;
if (selectFromSector == 0) {
participant = participants[selected];
participants.splice(selected, 1);
} else if (selectFromSector == availableSectors - 1) {
selected = participants.length - selected - 1;
participant = participants[selected];
participants.splice(selected, 1);
} else {
selected = Math.floor((participants.length / availableSectors)) * selectFromSector + selected;
participant = participants[selected];
participants.splice(selected, 1);
}
return participant;
}

generateTeams(): void {
if (this.tournament.teamSize === 1) {
this.assignTeamByParticipant();
Expand Down

0 comments on commit 52e7393

Please sign in to comment.