Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gordon360/Controllers/RecIM/SeriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public async Task<ActionResult<SeriesAutoSchedulerEstimateViewModel>> GetAutoSch
/// <param name="seriesID"></param>
[HttpGet]
[Route("{seriesID}/bracket")]
public ActionResult<IEnumerable<MatchBracketViewModel>> GetBracket(int seriesID)
public ActionResult<IEnumerable<MatchBracketExportViewModel>> GetBracket(int seriesID)
{
var res = seriesService.GetSeriesBracketInformation(seriesID);
return Ok(res);
Expand Down
22 changes: 22 additions & 0 deletions Gordon360/Models/ViewModels/RecIM/MatchBracketExportViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Gordon360.Models.CCT;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Gordon360.Models.ViewModels.RecIM;

// MatchBracketExportViewModel is essentially a renamed version of MatchBracketExtendedViewModel with name
// changes to mirror exactly that of the UI. As well as a type change from int -> string for tournamentRoundText
// This is purely for ease of access from the UI side and lighter calculations on the UI side.
public class MatchBracketExportViewModel
{
public int id { get; set; }
public string? name { get; set; }
public int? nextMatchId { get; set; }
public string tournamentRoundText { get; set; }
public string? state { get; set; }
public DateTime startTime { get; set;}
public IEnumerable<TeamBracketExportViewModel> participants { get; set; }
public int seedIndex { get; set; } //used to calculate location of match in each round of the bracket
public bool isLosers { get; set; }
}
43 changes: 43 additions & 0 deletions Gordon360/Models/ViewModels/RecIM/MatchBracketExtendedViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Gordon360.Models.CCT;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Gordon360.Models.ViewModels.RecIM;
//UI SHAPE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example doesn't match the definition of MatchBracketExtendedViewModel below. Why is that? It needs an explanation (unless it just needs an update to match the class).

// {
// "id": 260005,
// "name": "Final - Match",
// "nextMatchId": null, // Id for the nextMatch in the bracket, if it's final match it must be null OR undefined
// "tournamentRoundText": "4", // Text for Round Header
// "startTime": "2021-05-30",
// "state": "DONE", // 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | 'DONE' | 'SCORE_DONE' Only needed to decide walkovers and if teamNames are TBD (to be decided)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these values explained somewhere?

I don't know what WALK_OVER and NO_PARTY mean, and a search of the repo doesn't show them elsewhere. And I'm wondering if DONE implies there is no score (since it's different from SCORE_DONE).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are definitions that are explained in the UI package docs. I don't think it is necessary to explain in the API

// "participants": [
// {
// "id": "c016cb2a-fdd9-4c40-a81f-0cc6bdf4b9cc", // Unique identifier of any kind
// "resultText": "WON", // Any string works
// "isWinner": false,
// "status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | null
// "name": "giacomo123"
// },
// {
// "id": "9ea9ce1a-4794-4553-856c-9a3620c0531b",
// "resultText": null,
// "isWinner": true,
// "status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY'
// "name": "Ant"
// }
// }
// API Internal calculation viewmodel
public class MatchBracketExtendedViewModel
{
public int MatchID { get; set; }
public int? NextMatchID { get; set; }
public int RoundNumber { get; set; }
public int RoundOf { get; set; }
public string? State { get; set; }
public DateTime StartTime { get; set;}
public IEnumerable<TeamBracketExtendedViewModel> Team { get; set; }
public int SeedIndex { get; set; }
public bool IsLosers { get; set; }
}
5 changes: 5 additions & 0 deletions Gordon360/Models/ViewModels/RecIM/MatchBracketViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
using Gordon360.Models.CCT;
using System;

namespace Gordon360.Models.ViewModels.RecIM;


// Mirror of Match Bracket DB Model

public class MatchBracketViewModel
{
public int MatchID { get; set; }
public int RoundNumber { get; set; }
public int RoundOf { get; set; }
public int SeedIndex { get; set; }
public bool IsLosers { get; set; }
public DateTime StartTime { get; set; }


public static implicit operator MatchBracketViewModel(MatchBracket m)
Expand Down
16 changes: 16 additions & 0 deletions Gordon360/Models/ViewModels/RecIM/TeamBracketExportViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Gordon360.Models.CCT;
using System.Collections;
using System.Collections.Generic;

namespace Gordon360.Models.ViewModels.RecIM;
// TeamBracketExportViewModel is essentially a renamed version of TeamBracketExtendedViewModel with name
// changes to mirror exactly that of the UI. As well as a type change from int -> string for tournamentRoundText
// This is purely for ease of access from the UI side and lighter calculations on the UI side.
public class TeamBracketExportViewModel
{
public int id { get; set; }
public string? resultText { get; set; }
public bool isWinner { get; set; }
public string? status { get; set; }
public string? name { get; set; }
}
14 changes: 14 additions & 0 deletions Gordon360/Models/ViewModels/RecIM/TeamBracketViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Gordon360.Models.CCT;
using System.Collections;
using System.Collections.Generic;

namespace Gordon360.Models.ViewModels.RecIM;
// API Internal calculation viewmodel
public class TeamBracketExtendedViewModel
{
public int TeamID { get; set; }
public string? Score { get; set; }
public bool IsWinner { get; set; }
public string? Status { get; set; }
public string? TeamName { get; set; }
}
194 changes: 191 additions & 3 deletions Gordon360/Services/RecIM/SeriesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Gordon360.Extensions.System;
using Gordon360.Static.Names;
using Microsoft.Graph;

namespace Gordon360.Services.RecIM;

Expand Down Expand Up @@ -1679,12 +1680,199 @@ private async Task<IEnumerable<MatchBracketViewModel>> CreateEliminationBracket(
return res;
}

public IEnumerable<MatchBracketViewModel> GetSeriesBracketInformation(int seriesID)
public IEnumerable<MatchBracketExportViewModel> GetSeriesBracketInformation(int seriesID)
{
return context.Match
/**
* Match stores StartTime and Series information needed to handle calculations
*/
var match = context.Match
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can we assume about context.Match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this means, context.Match is how we access our Match models in the DB. The code itself should be relatively self explanatory of why I need to contain Match.

.Include(m => m.MatchBracket)
.Where(m => m.SeriesID == seriesID && m.StatusID != 0)
.Select(m => (MatchBracketViewModel) m.MatchBracket);
.Select(m => new MatchBracketViewModel
{
MatchID = m.MatchBracket.MatchID,
RoundNumber = m.MatchBracket.RoundNumber,
RoundOf = m.MatchBracket.RoundOf,
SeedIndex = m.MatchBracket.SeedIndex,
IsLosers = m.MatchBracket.IsLosers,
StartTime = m.StartTime
})
.AsEnumerable()
.OrderBy(mb => mb.RoundNumber)
.ThenBy(mb => mb.SeedIndex);

/*
* fill out each of round level:
* round of 64 needs 32 matches (64 teams), 32/16...
* (empty spots happen when there are bye rounds and are typically an issue in the first 2 rounds)
*/
var combinedList = Enumerable.Empty<MatchBracketExtendedViewModel>().ToList();
var fakeMatchID = -1;
var i = 0;
while (i < match.Count())
{
var j = 0;
var currentRoundOf = match.ElementAt(i).RoundOf;
var currentRoundNum = match.ElementAt(i).RoundNumber;

while (j < currentRoundOf / 2) //roundOf is a term based on number of teams, matches are per 2 teams
{
var m = match.ElementAt(i);
/**
* Round 0 | 1 | 2 | ... n-1 (finals)
* s 0 -
* e \ _ 0
* e / \
* d 1 - \ _ 0
* / \
* i 2 - / \
* n \ _ 1 .
* d / . .
* e n-1 - . .
* x
*
* Each bracket has has an associated RoundOf which declares how many matches should exist in each
* round: roundOf64 = 32 matches, roundOf32 = 16 matches ...
* Each match in the bracket has an associated index. If there are "missing" indicies, we know a bye
* match needs to be filled in.
*
* Check each element (sorted), if the index is incrementing by 1, if not, then we make a pseudo match
* to be displayed on the UI.
*/
if (j == match.ElementAt(i).SeedIndex)
{
var state = "SCHEDULED";
var teams = context.MatchTeam.Where(mt => mt.MatchID == m.MatchID && mt.StatusID != 0);

var teamList = Enumerable.Empty<TeamBracketExtendedViewModel>().ToList();
if (teams.Count() != 0)
{
foreach (var team in teams)
teamList.Add(new TeamBracketExtendedViewModel
{
TeamID = team.TeamID,
Score = team.Score.ToString(),
IsWinner = false,
TeamName = context.Team.Find(team.TeamID)?.Name ?? ""
});

if (teams.Count() == 2)
{
if (teams.ElementAt(0).Score != 0 || teams.ElementAt(1).Score != 0) state = "SCORE_DONE";

if (Convert.ToInt32(teamList.ElementAt(0).Score) > Convert.ToInt32(teamList.ElementAt(1).Score))
teamList.ElementAt(0).IsWinner = true;
if (Convert.ToInt32(teamList.ElementAt(0).Score) < Convert.ToInt32(teamList.ElementAt(1).Score))
teamList.ElementAt(1).IsWinner = true;
}
}

combinedList.Add(new MatchBracketExtendedViewModel
{
MatchID = m.MatchID,
NextMatchID = null,
RoundNumber = m.RoundNumber,
RoundOf = m.RoundOf,
State = state,
SeedIndex = m.SeedIndex,
IsLosers = m.IsLosers,
StartTime = m.StartTime,
Team = teamList
});
i++;
}
else
{
combinedList.Add(new MatchBracketExtendedViewModel
{
MatchID = fakeMatchID--,
NextMatchID = null,
RoundNumber = currentRoundNum,
RoundOf = m.RoundOf,
State = "WALK_OVER",
SeedIndex = j,
IsLosers = m.IsLosers,
StartTime = m.StartTime,
Team = Enumerable.Empty<TeamBracketExtendedViewModel>()
});
}
j++;
}
}

i = 0;
foreach (var _match in combinedList)
{
var nextMatchSeedIndex = _match.SeedIndex >> 1;
var nextMatch = combinedList.FirstOrDefault(m => m.RoundNumber == _match.RoundNumber + 1 && m.SeedIndex == nextMatchSeedIndex);
_match.NextMatchID = nextMatch?.MatchID;

if (_match.MatchID < 0)
{
/**
* 2 conditions
* 1) Match i is the only bye match
* - Inherit the team in the next match that is not i+1
* 2) Both are bye matches
* - Inherit Team at index 0,1 respectively
*/
var bye = nextMatch?.Team;
if (_match.SeedIndex % 2 == 0)
{
bye = nextMatch?.Team
.Where(t => !combinedList[i+1].Team.Any(t_ => t_.TeamID == t.TeamID));
}
else
{
bye = nextMatch?.Team
.Where(t => !combinedList[i-1].Team.Any(t_ => t_.TeamID == t.TeamID));

}
combinedList[i].Team = [new TeamBracketExtendedViewModel()
{
TeamID = bye.First().TeamID,
Score = "BYE",
IsWinner = true,
Status = "WALK_OVER",
TeamName = bye.First().TeamName
}];
}
i++;
}


//final conversion to exact UI shape (more efficient than letting the UI handle the key/pair changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good comment: it explains the goal clearly enough that the code below it makes sense.

The code above could use some similar comment(s).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a comment. All the code above does, is as I described:
It fills out all the invisible bye matches and injects nextMatchID into each bracket object

//concious decision to not make all the conversions in the extended viewmodel as it would disrupt our
//current api styling and naming.
//I want to minimize style disruptions albeit at the cost of a tiny bit of performance
var res = Enumerable.Empty<MatchBracketExportViewModel>().ToList();
foreach( var m in combinedList )
{
var teams = Enumerable.Empty<TeamBracketExportViewModel>().ToList();
foreach (var t in m.Team)
teams.Add(new TeamBracketExportViewModel
{
id = t.TeamID,
resultText = t.Score,
isWinner = t.IsWinner,
status = t.Status,
name = t.TeamName
});
res.Add(new MatchBracketExportViewModel
{
id = m.MatchID,
name = null, //unused currently
nextMatchId = m.NextMatchID,
tournamentRoundText = $"{m.RoundNumber + 1}", //start at round 1 instead of 0 for UI readability
state = m.State,
startTime = m.StartTime,
participants = teams,
seedIndex = m.SeedIndex,
isLosers = m.IsLosers
});
}

return res;
}

public async Task<EliminationRound> ScheduleElimRoundAsync(IEnumerable<Match>? matches)
Expand Down
2 changes: 1 addition & 1 deletion Gordon360/Services/ServiceInterfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public interface ISeriesService
Task<IEnumerable<MatchViewModel>?> ScheduleMatchesAsync(int seriesID, UploadScheduleRequest request);
SeriesAutoSchedulerEstimateViewModel GetScheduleMatchesEstimateAsync(int seriesID, UploadScheduleRequest request);
SeriesScheduleExtendedViewModel GetSeriesScheduleByID(int seriesID);
IEnumerable<MatchBracketViewModel> GetSeriesBracketInformation(int seriesID);
IEnumerable<MatchBracketExportViewModel> GetSeriesBracketInformation(int seriesID);
Task<TeamRecordViewModel> UpdateSeriesTeamRecordAsync(int seriesID, TeamRecordPatchViewModel teamRecord);
}

Expand Down