From a306f2f48c59efa792646f97859c049cac646dae Mon Sep 17 00:00:00 2001 From: Martin Raymond Date: Fri, 26 Jan 2024 21:50:30 -0800 Subject: [PATCH] adding tournaments --- apps/api/Dockerfile | 11 +- .../tournament-list.component.html | 5 +- .../tournament-list/tournament-list.store.ts | 9 +- .../tournament-setup.component.html | 32 +- .../tournament-setup.component.ts | 4 + .../tournament-setup.store.ts | 15 +- .../toggle/src/lib/toggle.component.ts | 1 - libs/go/api/tournaments.go | 3 +- libs/go/challonge/challonge.go | 275 +++++++++--------- libs/go/challonge/match.go | 15 +- libs/go/challonge/tournament.go | 119 +++++--- .../src/lib/tournament-settings.model.ts | 1 + .../scoreboard/scoreboard.component.scss | 2 +- 13 files changed, 284 insertions(+), 208 deletions(-) diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 4c18b56..c498bfa 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,17 +1,18 @@ # Use nxgo/cli as the base image to do the build -FROM nxgo/cli as builder +# FROM nxgo/cli as builder +FROM golang:1.20-alpine as builder # Create app directory WORKDIR /workspace # Build argument for fontawesome npm token -ARG FONTAWESOME_NPM_AUTH_TOKEN +# ARG FONTAWESOME_NPM_AUTH_TOKEN # Copy package.json and the lock file -COPY package.json package-lock.json .npmrc ./ +# COPY package.json package-lock.json .npmrc ./ # Install app dependencies -RUN npm ci +# RUN npm ci # Copy go mod files COPY go.mod go.sum ./ @@ -29,7 +30,7 @@ COPY apps/seed apps/seed COPY libs/go libs/go # Copy Nx files -COPY nx.json workspace.json tsconfig.base.json ./ +# COPY nx.json workspace.json tsconfig.base.json ./ # Build api app # RUN nx build api diff --git a/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.component.html b/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.component.html index d4560e6..c758e45 100644 --- a/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.component.html +++ b/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.component.html @@ -1,5 +1,5 @@
-
+

Name

@@ -17,6 +17,9 @@
+ +

Please create a new tournament on Challonge to continue.

+
diff --git a/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.store.ts b/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.store.ts index 9b5c5d0..0e1c484 100644 --- a/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.store.ts +++ b/apps/dashboard/src/app/tournament/components/tournament-list/tournament-list.store.ts @@ -43,12 +43,19 @@ export class TournamentListStore extends ComponentStore { // selectors private isLoaded$ = this.select((state) => state.callState === LoadingState.LOADED); private tournaments$ = this.select((state) => state.tournaments); + private hasTournaments$ = this.select( + this.isLoaded$, + this.tournaments$, + (loaded, tournaments) => loaded && !!tournaments.length + ); readonly vm$ = this.select( this.isLoaded$, this.tournaments$, - (isLoaded, tournaments) => ({ + this.hasTournaments$, + (isLoaded, tournaments, hasTournaments) => ({ isLoaded, tournaments, + hasTournaments, }) ); diff --git a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.html b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.html index b126ca1..cc7e40e 100644 --- a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.html +++ b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.html @@ -10,10 +10,22 @@

{{ vm.tournament?.name }}

-
+
Global Settings
+
+ + + + + +
+
+
+
+
Game Type
+
- - - - - +
+
+
+
+
Max Tables
+
+
+
+ + + + +
diff --git a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.ts b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.ts index 11ba2bb..5098d6c 100644 --- a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.ts +++ b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.component.ts @@ -20,6 +20,10 @@ export class TournamentSetupComponent { this.store.getTournamentById(tournamentId); } + public updateMaxTables(maxTables: number): void { + this.store.updateMaxTables(maxTables); + } + public updateGameType(gameType: GameType): void { this.store.updateGameType(gameType); } diff --git a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.store.ts b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.store.ts index 0490bfe..33be6c8 100644 --- a/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.store.ts +++ b/apps/dashboard/src/app/tournament/components/tournament-setup/tournament-setup.store.ts @@ -14,6 +14,7 @@ export enum LoadingState { interface TournamentSetupState { callState: LoadingState; tournament: Tournament | null; + maxTables: number; isHandicapped: boolean; showOverlay: boolean; showFlags: boolean; @@ -27,6 +28,7 @@ interface TournamentSetupState { export const initialState: TournamentSetupState = { callState: LoadingState.INIT, tournament: null, + maxTables: 3, isHandicapped: true, showOverlay: true, showFlags: false, @@ -58,6 +60,10 @@ export class TournamentSetupStore extends ComponentStore { callState: LoadingState.LOADED, })); + readonly updateMaxTables = this.updater((state, maxTables) => ({ + ...state, + maxTables, + })); readonly updateGameType = this.updater((state, gameType) => ({ ...state, @@ -102,6 +108,7 @@ export class TournamentSetupStore extends ComponentStore { // selectors private isLoaded$ = this.select((state) => state.callState === LoadingState.LOADED); private tournament$ = this.select((state) => state.tournament); + private maxTables$ = this.select((state) => state.maxTables); private isHandicapped$ = this.select((state) => state.isHandicapped); private showOverlay$ = this.select((state) => state.showOverlay); private showFlags$ = this.select((state) => state.showFlags); @@ -113,6 +120,7 @@ export class TournamentSetupStore extends ComponentStore { readonly vm$ = this.select( this.isLoaded$, this.tournament$, + this.maxTables$, this.isHandicapped$, this.showOverlay$, this.showFlags$, @@ -121,9 +129,10 @@ export class TournamentSetupStore extends ComponentStore { this.gameType$, this.aSideRaceTo$, this.bSideRaceTo$, - (isLoaded, tournament, isHandicapped, showOverlay, showFlags, showFargo, showScore, gameType, aSideRaceTo, bSideRaceTo) => ({ + (isLoaded, tournament, maxTables, isHandicapped, showOverlay, showFlags, showFargo, showScore, gameType, aSideRaceTo, bSideRaceTo) => ({ isLoaded, tournament, + maxTables, isHandicapped, showOverlay, showFlags, @@ -151,6 +160,7 @@ export class TournamentSetupStore extends ComponentStore { readonly loadTournament = this.effect((trigger$) => trigger$.pipe( withLatestFrom( this.tournament$, + this.maxTables$, this.gameType$, this.showOverlay$, this.showFlags$, @@ -160,7 +170,8 @@ export class TournamentSetupStore extends ComponentStore { this.aSideRaceTo$, this.bSideRaceTo$, ), - switchMap(([, tournament, game_type, show_overlay, show_flags, show_fargo, show_score, is_handicapped, a_side_race_to, b_side_race_to]) => this.tournamentsService.load(tournament!.id, { + switchMap(([, tournament, max_tables, game_type, show_overlay, show_flags, show_fargo, show_score, is_handicapped, a_side_race_to, b_side_race_to]) => this.tournamentsService.load(tournament!.id, { + max_tables, game_type, show_overlay, show_flags, diff --git a/libs/dashboard/components/toggle/src/lib/toggle.component.ts b/libs/dashboard/components/toggle/src/lib/toggle.component.ts index f221822..fff098f 100644 --- a/libs/dashboard/components/toggle/src/lib/toggle.component.ts +++ b/libs/dashboard/components/toggle/src/lib/toggle.component.ts @@ -16,7 +16,6 @@ export class ToggleComponent { @Input() public set checked(value: boolean) { - console.log(`input: ${value}`); this.store.setChecked(value); } diff --git a/libs/go/api/tournaments.go b/libs/go/api/tournaments.go index da944a8..b362bac 100644 --- a/libs/go/api/tournaments.go +++ b/libs/go/api/tournaments.go @@ -16,6 +16,7 @@ type TournamentLoadBody struct { } type TournamentLoadBodySettings struct { + MaxTables int `json:"max_tables"` GameType models.GameType `json:"game_type"` ShowOverlay bool `json:"show_overlay"` ShowFlags bool `json:"show_flags"` @@ -147,7 +148,7 @@ func (server *Server) handleTournamentLoadPost(w http.ResponseWriter, r *http.Re // make settings settings := &challonge.Settings{ - MaxTables: 3, + MaxTables: body.Settings.MaxTables, GameType: body.Settings.GameType, ShowOverlay: body.Settings.ShowOverlay, ShowFlags: body.Settings.ShowFlags, diff --git a/libs/go/challonge/challonge.go b/libs/go/challonge/challonge.go index 4c41e87..b89be84 100644 --- a/libs/go/challonge/challonge.go +++ b/libs/go/challonge/challonge.go @@ -129,132 +129,40 @@ func (c *Challonge) Continue(table int) error { return err } - // update all other matches not on tables - for _, match := range c.Tournament.Matches { - if c.CurrentMatches[1] != nil && match.ID == c.CurrentMatches[1].ID { - continue - } - - if c.CurrentMatches[2] != nil && match.ID == c.CurrentMatches[2].ID { - continue - } - - if c.CurrentMatches[3] != nil && match.ID == c.CurrentMatches[3].ID { - continue - } - - if err := match.Refresh(c.config.Username, c.config.APIKey); err != nil { - log.Printf("unable to refresh match: %s", err) - } + // update all matches + if err := c.Tournament.updateMatches(c.config.Username, c.config.APIKey); err != nil { + return err } // should we drop max tables // TODO: REMOVE THIS HACKY SHIT - if c.CurrentMatches[table].Round == -3 && c.Settings.MaxTables == 3 { + if c.CurrentMatches[table].Round == -4 && c.Settings.MaxTables == 3 { c.Settings.MaxTables = 2 } - if c.CurrentMatches[table].Round == -4 && c.Settings.MaxTables == 2 { + if c.CurrentMatches[table].Round == -5 && c.Settings.MaxTables == 2 { c.Settings.MaxTables = 1 } // unset the current table c.CurrentMatches[table] = nil - // check if current table is now outside of max tables bounds - if table > c.Settings.MaxTables { - c.tables[table].Overlay.TableNoLongerInUse = true - - // Unset waiting for players since this will never happen now - c.tables[table].Overlay.WaitingForPlayers = false - - // Generate overlay state message to broadcast to overlay. - overlayMessage, err := overlayPkg.NewEvent( - events.OverlayStateEventType, - c.tables[table].Overlay, - ).ToBytes() - if err != nil { - return err - } - - // Broadcast new overlay state to overlay. - c.overlay.Broadcast <- overlayMessage + // flag all tables over max tables as unused + if err := c.flagUnusedTables(); err != nil { + return err } + // loop over tables still in tournament for i := 1; i <= c.Settings.MaxTables; i++ { + // skip tables with games already in progress if c.CurrentMatches[i] != nil { continue } + // check if tournament has more matches if c.Tournament.HasMoreMatches() { - match := c.Tournament.GetNextMatch() - - if match != nil { - c.CurrentMatches[i] = match - - // set game data - // load players to the overlay for that table - c.tables[i].Game.SetPlayer(1, c.PlayersMap[*match.Player1ID]) - c.tables[i].Game.SetPlayer(2, c.PlayersMap[*match.Player2ID]) - c.tables[i].Game.ResetScore() - - // set race for a/b side - if match.IsOnASide() { - c.tables[i].Game.SetRaceTo(c.Settings.ASideRaceTo) - } else { - c.tables[i].Game.SetRaceTo(c.Settings.BSideRaceTo) - } - - // mark match as in progress on challonge if possible - if err := match.SetInProgress(c.config.Username, c.config.APIKey); err != nil { - return err - } - - // Generate game message to broadcast to overlay. - gameMessage, err := overlayPkg.NewEvent( - events.GameEventType, - events.NewGameEventPayload(c.tables[i].Game), - ).ToBytes() - if err != nil { - return err - } - - // Broadcast new game state to overlay. - c.overlay.Broadcast <- gameMessage - - // flag overlay as hidden - c.tables[i].Overlay.SetHidden(false) - c.tables[i].Overlay.WaitingForPlayers = false - - // Generate overlay state message to broadcast to overlay. - overlayMessage, err := overlayPkg.NewEvent( - events.OverlayStateEventType, - c.tables[i].Overlay, - ).ToBytes() - if err != nil { - return err - } - - // Broadcast new overlay state to overlay. - c.overlay.Broadcast <- overlayMessage - } else { - // flag overlay as hidden - c.tables[i].Overlay.SetHidden(true) - c.tables[i].Overlay.WaitingForPlayers = true - - // Generate overlay state message to broadcast to overlay. - overlayMessage, err := overlayPkg.NewEvent( - events.OverlayStateEventType, - c.tables[i].Overlay, - ).ToBytes() - if err != nil { - return err - } - - // Broadcast new overlay state to overlay. - c.overlay.Broadcast <- overlayMessage - } - } else { - if err := c.Tournament.CompleteIfPossible(c.config.Username, c.config.APIKey); err != nil { + // get next match for table + _, err := c.getNextMatchForTable(i) + if err != nil { return err } } @@ -293,7 +201,7 @@ func (c *Challonge) UnloadTournament() { // Initializes all the settings for the tables and overlays. func (c *Challonge) initializeTournament() error { - for i := 1; i <= c.Settings.MaxTables; i++ { + for i := 1; i <= len(c.tables); i++ { // ********** // ** GAME ** // ********** @@ -321,6 +229,11 @@ func (c *Challonge) initializeTournament() error { c.tables[i].Overlay.WaitingForPlayers = false c.tables[i].Overlay.TableNoLongerInUse = false + if i <= c.Settings.MaxTables { + c.tables[i].Overlay.TableNoLongerInUse = false + } else { + c.tables[i].Overlay.TableNoLongerInUse = true + } } return nil @@ -331,7 +244,8 @@ func (c *Challonge) mapPlayers() error { for _, participant := range c.Tournament.Participants { fargoObservableID, err := getFargoObservableIDFromParticpantName(participant.Name) if err != nil { - return err + log.Printf("Error: unable to get fargo observable id for participant: %s", participant.Name) + continue } var player models.Player @@ -368,54 +282,149 @@ func (c *Challonge) fillTables() error { for i := 1; i <= c.Settings.MaxTables; i++ { log.Printf("filling table: %d", i) - match := c.Tournament.GetNextMatch() + if _, err := c.getNextMatchForTable(i); err != nil { + return err + } + } + + return nil +} + +// Fills a table number with the next available match. +func (c *Challonge) getNextMatchForTable(table int) (*Match, error) { + match := c.Tournament.GetNextMatch() + + if match != nil { + // check match for byes + byes, err := c.checkMatchForByes(match) + if err != nil { + return nil, err + } + if byes { + if err := c.Tournament.updateMatches(c.config.Username, c.config.APIKey); err != nil { + return nil, err + } - if match == nil { - return fmt.Errorf("no next match found") + return c.getNextMatchForTable(table) } // add that match to the current matches for that table - c.CurrentMatches[i] = match + c.CurrentMatches[table] = match // load players to the overlay for that table - c.tables[i].Game.SetPlayer(1, c.PlayersMap[*match.Player1ID]) - c.tables[i].Game.SetPlayer(2, c.PlayersMap[*match.Player2ID]) + c.tables[table].Game.SetPlayer(1, c.PlayersMap[*match.Player1ID]) + c.tables[table].Game.SetPlayer(2, c.PlayersMap[*match.Player2ID]) + c.tables[table].Game.ResetScore() + + // set race for a/b side + if match.IsOnASide() { + c.tables[table].Game.SetRaceTo(c.Settings.ASideRaceTo) + } else { + c.tables[table].Game.SetRaceTo(c.Settings.BSideRaceTo) + } // mark match as in progress on challonge if possible if err := match.SetInProgress(c.config.Username, c.config.APIKey); err != nil { - return err + return nil, err + } + + // broadcast game for table + if err := c.broadcastGame(table); err != nil { + return nil, err + } + + // make sure overlay is visible + c.tables[table].Overlay.SetHidden(false) + c.tables[table].Overlay.WaitingForPlayers = false + } else { + // flag overlay as hidden + c.tables[table].Overlay.SetHidden(true) + c.tables[table].Overlay.WaitingForPlayers = true + } + + // broadcast overlay for table + if err := c.broadcastOverlay(table); err != nil { + return nil, err + } + + return match, nil +} + +func (c *Challonge) checkMatchForByes(match *Match) (bool, error) { + _, player1Exists := c.PlayersMap[*match.Player1ID] + _, player2Exists := c.PlayersMap[*match.Player2ID] + + if !player2Exists { + return true, match.ReportWinner(c.config.Username, c.config.APIKey, *match.Player1ID, "0-0") + } else if !player1Exists { + return true, match.ReportWinner(c.config.Username, c.config.APIKey, *match.Player2ID, "0-0") + } + + return false, nil +} + +// flagUnusedTables flags all tables above max tables as unused. +func (c *Challonge) flagUnusedTables() error { + for i := 1; i <= len(c.tables); i++ { + // check if current table is now outside of max tables bounds + if i > c.Settings.MaxTables { + c.tables[i].Overlay.TableNoLongerInUse = true + + // unset waiting for players since this will never happen now + c.tables[i].Overlay.WaitingForPlayers = false + + // broadcast overlay updates for table + if err := c.broadcastOverlay(i); err != nil { + return err + } } } return nil } +func (c *Challonge) broadcastGame(table int) error { + // Generate game message to broadcast to overlay. + gameMessage, err := overlayPkg.NewEvent( + events.GameEventType, + events.NewGameEventPayload(c.tables[table].Game), + ).ToBytes() + if err != nil { + return err + } + + // Broadcast new game state to overlay. + c.overlay.Broadcast <- gameMessage + + return nil +} + +func (c *Challonge) broadcastOverlay(table int) error { + // Generate overlay state message to broadcast to overlay. + overlayMessage, err := overlayPkg.NewEvent( + events.OverlayStateEventType, + c.tables[table].Overlay, + ).ToBytes() + if err != nil { + return err + } + + // Broadcast new overlay state to overlay. + c.overlay.Broadcast <- overlayMessage + + return nil +} + // Broadcasts game and overlay data to all tables after tournament load. func (c *Challonge) broadcastAllTables() error { for i := 1; i <= c.Settings.MaxTables; i++ { - // Generate game message to broadcast to overlay. - gameMessage, err := overlayPkg.NewEvent( - events.GameEventType, - events.NewGameEventPayload(c.tables[i].Game), - ).ToBytes() - if err != nil { + if err := c.broadcastGame(i); err != nil { return err } - // Broadcast new game state to overlay. - c.overlay.Broadcast <- gameMessage - - // Generate overlay state message to broadcast to overlay. - overlayMessage, err := overlayPkg.NewEvent( - events.OverlayStateEventType, - c.tables[i].Overlay, - ).ToBytes() - if err != nil { + if err := c.broadcastOverlay(i); err != nil { return err } - - // Broadcast new overlay state to overlay. - c.overlay.Broadcast <- overlayMessage } return nil diff --git a/libs/go/challonge/match.go b/libs/go/challonge/match.go index 22a39df..33dd350 100644 --- a/libs/go/challonge/match.go +++ b/libs/go/challonge/match.go @@ -3,7 +3,7 @@ package challonge import ( "encoding/json" "fmt" - "io/ioutil" + "io" "log" "net/http" "net/url" @@ -69,7 +69,7 @@ func (m *Match) UpdateScore(username string, apiKey string, scoresCsv string) er params := fmt.Sprintf("match[scores_csv]=%s", scoresCsv) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Body = ioutil.NopCloser(strings.NewReader(params)) + req.Body = io.NopCloser(strings.NewReader(params)) client := &http.Client{} resp, err := client.Do(req) @@ -94,7 +94,7 @@ func (m *Match) ReportWinner(username string, apiKey string, playerID int, score params := fmt.Sprintf("match[winner_id]=%d&match[scores_csv]=%s", playerID, scoresCsv) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Body = ioutil.NopCloser(strings.NewReader(params)) + req.Body = io.NopCloser(strings.NewReader(params)) client := &http.Client{} resp, err := client.Do(req) @@ -110,15 +110,6 @@ func (m *Match) ReportWinner(username string, apiKey string, playerID int, score // IsOnASide returns if the round is on the A side or not. func (m *Match) IsOnASide() bool { return m.Round > 0 - // aSideRounds := []int{1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 20, 22} - - // for _, round := range aSideRounds { - // if round == m.Round { - // return true - // } - // } - - // return false } // Refreshes a match with new data from Challonge. diff --git a/libs/go/challonge/tournament.go b/libs/go/challonge/tournament.go index d1ef82e..eeddeaa 100644 --- a/libs/go/challonge/tournament.go +++ b/libs/go/challonge/tournament.go @@ -2,7 +2,6 @@ package challonge import ( "encoding/json" - "errors" "fmt" "log" "net/http" @@ -41,16 +40,11 @@ type Tournament struct { Participants []*Participant `json:"participants"` } -var ( - // ErrNoIncompleteTournaments - No incomplete tournaments. - ErrNoIncompleteTournaments = errors.New("no incomplete tournaments") -) - // Validate checks to make sure the tournament is in a valid format. func (t *Tournament) Validate() error { err := t.verifyPlayerNames() if err != nil { - return err + log.Printf("Error: %s", err) } return nil @@ -69,8 +63,6 @@ func (t *Tournament) GetNextMatch() *Match { } } - log.Print("didn't find a match") - return nil } @@ -84,6 +76,30 @@ func (t *Tournament) HasMoreMatches() bool { return false } +// CountParallelMatches returns a count of the maximum number of matches that could be played +// in parallel. +func (t *Tournament) CountParallelMatches() int { + sortedMatches := t.getMatchesByPlayOrder() + activePlayers := make(map[int]bool) + parallelCount := 0 + + for i := range sortedMatches { + match := sortedMatches[i] + if match.State != "complete" { + // Check if players are already in a match + if _, exists := activePlayers[*match.Player1ID]; !exists { + if _, exists := activePlayers[*match.Player2ID]; !exists { + // Mark players as active + activePlayers[*match.Player1ID] = true + activePlayers[*match.Player2ID] = true + parallelCount++ + } + } + } + } + return parallelCount +} + // CompleteIfPossible attempts to complete the tournament. func (t *Tournament) CompleteIfPossible(username, apiKey string) error { for i := range t.Matches { @@ -127,8 +143,7 @@ func (t *Tournament) getMatches(username, apiKey string) error { defer resp.Body.Close() var matchesResp MatchesResp - err = json.NewDecoder(resp.Body).Decode(&matchesResp) - if err != nil { + if err := json.NewDecoder(resp.Body).Decode(&matchesResp); err != nil { return err } @@ -143,12 +158,55 @@ func (t *Tournament) getMatches(username, apiKey string) error { return nil } +// updateMatches updates all the matches for the tournament. +func (t *Tournament) updateMatches(username, apiKey string) error { + url := fmt.Sprintf("https://api.challonge.com/v1/tournaments/%d/matches.json", t.ID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.SetBasicAuth(username, apiKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var matchesResp MatchesResp + if err := json.NewDecoder(resp.Body).Decode(&matchesResp); err != nil { + return err + } + + var matches []*Match + for i := range matchesResp { + match := matchesResp[i] + matches = append(matches, &match.Match) + } + + for _, match := range t.Matches { + for _, updatedMatch := range matches { + if match.ID == updatedMatch.ID { + match.State = updatedMatch.State + match.Player1ID = updatedMatch.Player1ID + match.Player2ID = updatedMatch.Player2ID + match.ScoresCsv = updatedMatch.ScoresCsv + break + } + } + } + + return nil +} + // verifyPlayerNames makes sure all the player names in Challonge are in the correct format. func (t *Tournament) verifyPlayerNames() error { playerNames := t.playerNames() // This regex assumes names do not contain '-' or '(' and the Fargo ID is numeric - re := regexp.MustCompile(`^[^-]+ - [^(-]+ \(\d+\)$`) + // re := regexp.MustCompile(`^[^-]+ - [^(-]+ \(\d+\)$`) + re := regexp.MustCompile(`^(.+) - (\d+) \((\d+)\)$`) var invalidNames []string for _, name := range playerNames { @@ -218,32 +276,6 @@ func (t *Tournament) getMatchesByPlayOrder() []*Match { return sortedMatches } -// getMatchesByRound returns all the matches in a tournament, sorted in order of appearance on the bracket. -// func (t *Tournament) getMatchesByRound() []*Match { -// matchesByRound := make(map[int][]*Match) - -// // Group matches by round -// for i := range t.Matches { -// match := t.Matches[i] -// matchesByRound[match.Round] = append(matchesByRound[match.Round], match) -// } - -// // Sort matches within each round -// for _, matches := range matchesByRound { -// sort.Slice(matches, func(i, j int) bool { -// return matches[i].Identifier < matches[j].Identifier -// }) -// } - -// // Flatten matches into a single slice, sorted by round and position -// var sortedMatches []*Match -// for i := 1; i <= t.Rounds; i++ { -// sortedMatches = append(sortedMatches, matchesByRound[i]...) -// } - -// return sortedMatches -// } - // getLatestTournaments returns the latest tournaments for the specified Challonge account. func getLatestTournaments(username, apiKey string) ([]*Tournament, error) { url := "https://api.challonge.com/v1/tournaments.json?state=all&created_after=2022-01-01" @@ -270,14 +302,11 @@ func getLatestTournaments(username, apiKey string) ([]*Tournament, error) { // Filter off completed tournaments for _, tournament := range tournamentsResp { - if tournament.Tournament.CompletedAt == nil { - tournaments = append(tournaments, &tournament.Tournament) - } - } + currentTournament := tournament.Tournament - // Return error if no incomplete tournaments - if len(tournaments) == 0 { - return nil, ErrNoIncompleteTournaments + if currentTournament.CompletedAt == nil { + tournaments = append(tournaments, ¤tTournament) + } } // Sort tournaments by created date (most recent first) diff --git a/libs/models/src/lib/tournament-settings.model.ts b/libs/models/src/lib/tournament-settings.model.ts index d7af457..314284e 100644 --- a/libs/models/src/lib/tournament-settings.model.ts +++ b/libs/models/src/lib/tournament-settings.model.ts @@ -1,6 +1,7 @@ import { GameType } from './game-type.enum'; export interface TournamentSettings { + max_tables: number; game_type: GameType; show_overlay: boolean; show_flags: boolean; diff --git a/libs/scoreboard/src/lib/components/scoreboard/scoreboard.component.scss b/libs/scoreboard/src/lib/components/scoreboard/scoreboard.component.scss index 33c5a86..2c6c973 100644 --- a/libs/scoreboard/src/lib/components/scoreboard/scoreboard.component.scss +++ b/libs/scoreboard/src/lib/components/scoreboard/scoreboard.component.scss @@ -54,7 +54,7 @@ $game-height: 40px; :host { flex: none; - margin: 0 0 50px 0; + margin: 0; .scoreboard-wrapper { .scoreboard {