diff --git a/CHANGELOG.md b/CHANGELOG.md index cc42de95..c32f46d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Count badges for all leg types - WLED support for venues - TTS voice selection per venue +- Improved Tournament Generation - Lots of new badges #### Changed diff --git a/controllers/tournament_controller.go b/controllers/tournament_controller.go index ac51bffa..c2b57e64 100644 --- a/controllers/tournament_controller.go +++ b/controllers/tournament_controller.go @@ -363,7 +363,7 @@ func NewTournament(w http.ResponseWriter, r *http.Request) { // GenerateTournament will generate a new tournament func GenerateTournament(w http.ResponseWriter, r *http.Request) { SetHeaders(w) - var input models.Tournament + var input models.GenerateTournamentInput err := json.NewDecoder(r.Body).Decode(&input) if err != nil { log.Println("Unable to deserialize body", err) @@ -392,7 +392,15 @@ func GeneratePlayoffsTournament(w http.ResponseWriter, r *http.Request) { return } - tournament, err := data.GeneratePlayoffsTournament(id) + var input models.GeneratePlayoffsInput + err = json.NewDecoder(r.Body).Decode(&input) + if err != nil { + log.Println("Unable to deserialize body", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + tournament, err := data.GeneratePlayoffsTournament(id, input) if err != nil { log.Println("Unable to create new tournament", err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/data/match.go b/data/match.go index 0086d1c4..729d8837 100644 --- a/data/match.go +++ b/data/match.go @@ -472,8 +472,8 @@ func SetScore(matchID int, result models.MatchResult) (*models.Match, error) { // TODO Improve by only inserting legs where there is no score? for i := 0; i < result.LooserScore; i++ { - res, err := tx.Exec(`INSERT INTO leg (end_time, starting_score, current_player_id, match_id, created_at, is_finished, winner_id, has_scores) VALUES - (NOW(), ?, ?, ?, NOW(), 1, ?, 0)`, match.Legs[0].StartingScore, result.LooserID, matchID, result.LooserID) + res, err := tx.Exec(`INSERT INTO leg (end_time, starting_score, current_player_id, match_id, created_at, is_finished, winner_id, has_scores, num_players) VALUES + (NOW(), ?, ?, ?, NOW(), 1, ?, 0, 2)`, match.Legs[0].StartingScore, result.LooserID, matchID, result.LooserID) if err != nil { tx.Rollback() return nil, err @@ -495,8 +495,8 @@ func SetScore(matchID int, result models.MatchResult) (*models.Match, error) { } var legID int64 for i := 0; i < result.WinnerScore; i++ { - res, err := tx.Exec(`INSERT INTO leg (end_time, starting_score, current_player_id, match_id, created_at, is_finished, winner_id, has_scores) VALUES - (NOW(), ?, ?, ?, NOW(), 1, ?, 0)`, match.Legs[0].StartingScore, result.WinnerID, matchID, result.WinnerID) + res, err := tx.Exec(`INSERT INTO leg (end_time, starting_score, current_player_id, match_id, created_at, is_finished, winner_id, has_scores, num_players) VALUES + (NOW(), ?, ?, ?, NOW(), 1, ?, 0, 2)`, match.Legs[0].StartingScore, result.WinnerID, matchID, result.WinnerID) if err != nil { tx.Rollback() return nil, err diff --git a/data/player.go b/data/player.go index 681fe4cd..f94c2e04 100644 --- a/data/player.go +++ b/data/player.go @@ -1384,3 +1384,34 @@ func GetPlayersMatchTypes() (map[int]int, error) { } return m, nil } + +// GetPlaceholderPlayers returns all placeholder players +func GetPlaceholderPlayers() ([]*models.Player, error) { + rows, err := models.DB.Query(` + SELECT + p.id, p.first_name, p.last_name, p.vocal_name, p.nickname, p.slack_handle, p.color, p.profile_pic_url, p.smartcard_uid, + p.board_stream_url, p.board_stream_css, p.active, p.office_id, p.is_bot, p.is_placeholder, p.is_supporter, p.created_at, + p.updated_at + FROM player p WHERE is_placeholder = 1`) + if err != nil { + return nil, err + } + defer rows.Close() + + players := make([]*models.Player, 0) + for rows.Next() { + p := new(models.Player) + err := rows.Scan(&p.ID, &p.FirstName, &p.LastName, &p.VocalName, &p.Nickname, &p.SlackHandle, &p.Color, &p.ProfilePicURL, + &p.SmartcardUID, &p.BoardStreamURL, &p.BoardStreamCSS, &p.IsActive, &p.OfficeID, &p.IsBot, &p.IsPlaceholder, &p.IsSupporter, + &p.CreatedAt, &p.UpdatedAt) + if err != nil { + return nil, err + } + players = append(players, p) + } + if err = rows.Err(); err != nil { + return nil, err + } + + return players, nil +} diff --git a/data/tournament.go b/data/tournament.go index 511491cc..76b3e170 100644 --- a/data/tournament.go +++ b/data/tournament.go @@ -2,6 +2,7 @@ package data import ( "database/sql" + "errors" "log" "math" "sort" @@ -68,7 +69,7 @@ func AddTournamentGroup(group models.TournamentGroup) error { // GetTournamentGroups will return all tournament groups func GetTournamentGroups() (map[int]*models.TournamentGroup, error) { - rows, err := models.DB.Query("SELECT id, name, division FROM tournament_group") + rows, err := models.DB.Query("SELECT id, name, is_generated, is_playoffs, division FROM tournament_group") if err != nil { return nil, err } @@ -77,7 +78,7 @@ func GetTournamentGroups() (map[int]*models.TournamentGroup, error) { groups := make(map[int]*models.TournamentGroup) for rows.Next() { group := new(models.TournamentGroup) - err := rows.Scan(&group.ID, &group.Name, &group.Division) + err := rows.Scan(&group.ID, &group.Name, &group.IsGenerated, &group.IsPlayoffs, &group.Division) if err != nil { return nil, err } @@ -827,19 +828,13 @@ func NewTournament(tournament models.Tournament) (*models.Tournament, error) { } // GenerateTournament generates a new tournament -func GenerateTournament(input models.Tournament) (*models.Tournament, error) { - preset, err := GetTournamentPreset(int(input.PresetID.Int64)) - if err != nil { - return nil, err - } - +func GenerateTournament(input models.GenerateTournamentInput) (*models.Tournament, error) { officeID := input.OfficeID tournament, err := NewTournament(models.Tournament{ Name: input.Name, ShortName: input.ShortName, - IsPlayoffs: false, + IsPlayoffs: input.IsPlayoffs, OfficeID: officeID, - PresetID: input.PresetID, ManualAdmin: input.ManualAdmin, Players: input.Players, StartTime: null.TimeFrom(time.Now()), @@ -849,6 +844,9 @@ func GenerateTournament(input models.Tournament) (*models.Tournament, error) { return nil, err } + matchType := models.MatchType{ID: input.MatchTypeID} + matchMode := models.MatchMode{ID: input.MatchModeID} + players := input.Players for i := 0; i < len(players); i++ { for j := i + 1; j < len(players); j++ { @@ -858,15 +856,15 @@ func GenerateTournament(input models.Tournament) (*models.Tournament, error) { } match, err := NewMatch(models.Match{ - MatchType: preset.MatchType, - MatchMode: preset.MatchMode, + MatchType: &matchType, + MatchMode: &matchMode, //VenueID: 1, OfficeID: null.IntFrom(int64(officeID)), IsPractice: false, TournamentID: null.IntFrom(int64(tournament.ID)), Players: []int{players[i].PlayerID, players[j].PlayerID}, Legs: []*models.Leg{{ - StartingScore: preset.StartingScore, + StartingScore: input.StartingScore, Parameters: &models.LegParameters{OutshotType: &models.OutshotType{ID: models.OUTSHOTDOUBLE}}}}, }) if err != nil { @@ -879,7 +877,7 @@ func GenerateTournament(input models.Tournament) (*models.Tournament, error) { } // GeneratePlayoffsTournament generates playoffs matches for the given tournament -func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { +func GeneratePlayoffsTournament(tournamentID int, input models.GeneratePlayoffsInput) (*models.Tournament, error) { tournament, err := GetTournament(tournamentID) if err != nil { return nil, err @@ -902,13 +900,49 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { group2 = overview[keys[1]] } - preset := tournament.Preset - playoffsGroupID := preset.PlayoffsTournamentGroup.ID - mt := preset.MatchType - walkoverPlayerID := preset.PlayerIDWalkover - placeholderHomeID := preset.PlayerIDPlaceholderHome - placeholderAwayID := preset.PlayerIDPlaceholderAway - startingScore := preset.StartingScore + // Get the playoff tournament group + groups, err := GetTournamentGroups() + if err != nil { + return nil, err + } + var playoffsGroup *models.TournamentGroup + for _, group := range groups { + if group.IsPlayoffs { + playoffsGroup = group + break + } + } + playoffsGroupID := playoffsGroup.ID + + // Get type and starting score from the regular season matches + regularSeasonMatches, err := GetTournamentMatches(tournamentID) + if err != nil { + return nil, err + } + var regularSeasonMatch models.Match + var startingScore int + for _, value := range regularSeasonMatches { + regularSeasonMatch = *value[0] + legs, err := GetLegsForMatch(regularSeasonMatch.ID) + if err != nil { + return nil, err + } + startingScore = legs[0].StartingScore + break + } + matchType := regularSeasonMatch.MatchType + + // Get placeholder players + placeholders, err := GetPlaceholderPlayers() + if err != nil { + return nil, err + } + if len(placeholders) < 3 { + return nil, errors.New("missing 3 placeholder players from database") + } + placeholderHomeID := placeholders[0].ID + placeholderAwayID := placeholders[1].ID + walkoverPlayerID := placeholders[2].ID players := make([]*models.Player2Tournament, 0) for _, groupPlayer := range append(group1, group2...) { @@ -928,7 +962,6 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { IsPlayoffs: true, OfficeID: tournament.OfficeID, Players: players, - PresetID: tournament.PresetID, StartTime: null.TimeFrom(time.Now()), EndTime: null.TimeFrom(time.Now()), ManualAdmin: tournament.ManualAdmin, @@ -944,8 +977,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { matches := make([]*models.Match, 0) // Create Grand Final - match, err := createTournamentMatch(playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeGrandFinal) + match, err := createTournamentMatch(playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeGFID) if err != nil { return nil, err } @@ -953,8 +986,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { // Create Semi Final Matches if numPlayers > 4 { - semis, err := createTournamentMatches(2, playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeSemiFinal) + semis, err := createTournamentMatches(2, playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeSFID) if err != nil { return nil, err } @@ -979,8 +1012,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { // Walkover, so use placeholder away = walkoverPlayerID } - match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeSemiFinal) + match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeSFID) if err != nil { return nil, err } @@ -990,8 +1023,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { // Create Quarter Final Matches if numPlayers > 8 { - quarters, err := createTournamentMatches(4, playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeQuarterFinal) + quarters, err := createTournamentMatches(4, playoffs.ID, []int{placeholderHomeID, placeholderAwayID}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeQFID) if err != nil { return nil, err } @@ -1010,8 +1043,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { // Walkover, so use placeholder away = walkoverPlayerID } - match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeLast16) + match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeLast16ID) if err != nil { return nil, err } @@ -1037,8 +1070,8 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { // Walkover, so use placeholder away = walkoverPlayerID } - match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, models.X01, - tournament.OfficeID, mt, preset.MatchModeQuarterFinal) + match, err := createTournamentMatch(playoffs.ID, []int{home, away}, startingScore, -1, + tournament.OfficeID, matchType, input.MatchModeQFID) if err != nil { return nil, err } @@ -1125,10 +1158,10 @@ func GeneratePlayoffsTournament(tournamentID int) (*models.Tournament, error) { return GetTournament(playoffs.ID) } -func createTournamentMatches(num int, tournamentID int, players []int, startingScore int, venueID int, officeID int, matchType *models.MatchType, matchMode *models.MatchMode) ([]*models.Match, error) { +func createTournamentMatches(num int, tournamentID int, players []int, startingScore int, venueID int, officeID int, matchType *models.MatchType, matchModeID int) ([]*models.Match, error) { matches := make([]*models.Match, 0) for i := 0; i < num; i++ { - match, err := createTournamentMatch(tournamentID, players, startingScore, venueID, officeID, matchType, matchMode) + match, err := createTournamentMatch(tournamentID, players, startingScore, venueID, officeID, matchType, matchModeID) if err != nil { return nil, err } @@ -1137,10 +1170,10 @@ func createTournamentMatches(num int, tournamentID int, players []int, startingS return matches, nil } -func createTournamentMatch(tournamentID int, players []int, startingScore int, venueID int, officeID int, matchType *models.MatchType, matchMode *models.MatchMode) (*models.Match, error) { +func createTournamentMatch(tournamentID int, players []int, startingScore int, venueID int, officeID int, matchType *models.MatchType, matchModeID int) (*models.Match, error) { match, err := NewMatch(models.Match{ MatchType: matchType, - MatchMode: matchMode, + MatchMode: &models.MatchMode{ID: matchModeID}, //VenueID: null.IntFrom(int64(venueID)), OfficeID: null.IntFrom(int64(officeID)), IsPractice: false, diff --git a/models/tournament.go b/models/tournament.go index 94f56864..63ea0c03 100644 --- a/models/tournament.go +++ b/models/tournament.go @@ -60,9 +60,11 @@ type Tournament struct { // TournamentGroup struct for storing tournament groups type TournamentGroup struct { - ID int `json:"id"` - Name string `json:"name"` - Division null.Int `json:"division,omitempty"` + ID int `json:"id"` + Name string `json:"name"` + IsGenerated bool `json:"is_generated"` + IsPlayoffs bool `json:"is_playoffs"` + Division null.Int `json:"division,omitempty"` } // Player2Tournament struct for storing player to tounament links @@ -119,3 +121,25 @@ type TournamentPreset struct { PlayerIDPlaceholderAway int `json:"player_id_placeholder_away"` Description null.String `json:"description"` } + +// GenerateTournamentInput struct for storing generate tournament inputs +type GenerateTournamentInput struct { + Name string `json:"name"` + ShortName string `json:"short_name"` + IsPlayoffs bool `json:"is_playoffs"` + ManualAdmin bool `json:"manual_admin"` + OfficeID int `json:"office_id"` + MatchModeID int `json:"match_mode_id"` + MatchTypeID int `json:"match_type_id"` + StartingScore int `json:"starting_score"` + Players []*Player2Tournament `json:"players,omitempty"` +} + +// GeneratePlayoffsInput struct for storing generate playoffs inputs +type GeneratePlayoffsInput struct { + MatchModeLast32ID int `json:"match_mode_last32"` + MatchModeLast16ID int `json:"match_mode_last16"` + MatchModeQFID int `json:"match_mode_quarterFinals"` + MatchModeSFID int `json:"match_mode_semiFinals"` + MatchModeGFID int `json:"match_mode_grandFinals"` +}