protected static ValueRange CreateUpdateCellsAlongRowRequest <T>( string sheetName, SpreadsheetColumn column, int rowNumber, IEnumerable <T> values) { Verify.IsNotNull(values, nameof(values)); // Subtract one, since the start column already covers the first value SpreadsheetColumn endColumn = column + (values.Count() - 1); List <object> innerList = new List <object>(); foreach (T value in values) { innerList.Add(value); } ValueRange range = new ValueRange() { Range = $"'{sheetName}'!{column}{rowNumber}:{endColumn}{rowNumber}", Values = new List <IList <object> >() { innerList } }; return(range); }
protected override IResult <List <ValueRange> > GetUpdateRangesForRoster( IReadOnlyDictionary <string, string> teamIdToNames, IEnumerable <IGrouping <string, PlayerTeamPair> > groupings) { Verify.IsNotNull(groupings, nameof(groupings)); // Need to put teams in first row List <ValueRange> ranges = new List <ValueRange>(); IEnumerable <string> teamNames = groupings .Select(grouping => teamIdToNames.TryGetValue(grouping.Key, out string name) ? name : grouping.First().PlayerDisplayName); ranges.Add(CreateUpdateCellsAlongRowRequest(RostersSheetName, RostersFirstTeamColumn, 1, teamNames)); SpreadsheetColumn teamColumn = RostersFirstTeamColumn; foreach (IGrouping <string, PlayerTeamPair> grouping in groupings) { IEnumerable <string> playerNames = grouping.Select(pair => pair.PlayerDisplayName); ranges.Add(CreateUpdateCellsAlongColumnRequest( RostersSheetName, teamColumn, RostersFirstPlayerRow, playerNames)); teamColumn = teamColumn + 1; } return(new SuccessResult <List <ValueRange> >(ranges)); }
protected override IResult <IEnumerable <ValueRange> > GetUpdateRangesForBonus( PhaseScore phaseScore, string[] teamIds, string sheetName, int row, int phasesCount) { Verify.IsNotNull(phaseScore, nameof(phaseScore)); List <ValueRange> ranges = new List <ValueRange>(); if (phaseScore.BonusScores?.Any() == true) { int bonusScoresIndex = Array.IndexOf(teamIds, phaseScore.BonusTeamId); if (bonusScoresIndex < 0) { return(new FailureResult <IEnumerable <ValueRange> >( $"Unknown bonus team in phase {row - this.FirstPhaseRow + 1}. Cannot accurately create a scoresheet.")); } int bonusTotal = phaseScore.BonusScores.Sum(); if (bonusTotal != 0 && bonusTotal != 10 && bonusTotal != 20 && bonusTotal != 30) { return(new FailureResult <IEnumerable <ValueRange> >( $"Invalid bonus value in phase {row - this.FirstPhaseRow + 1}. Value must be 0/10/20/30, but it was {bonusTotal}")); } SpreadsheetColumn bonusColumn = this.BonusColumns.Span[bonusScoresIndex]; ranges.Add(CreateUpdateSingleCellRequest(sheetName, bonusColumn, row, bonusTotal)); } else if (row != this.FirstPhaseRow + phasesCount - 1 || phaseScore.ScoringSplitsOnActions.Any()) { // We need to find if anyone got it correct, and if so, what team they are on. Fill that bonus // column with 0. ScoringSplitOnScoreAction split = phaseScore.ScoringSplitsOnActions .FirstOrDefault(split => split.Action.Score > 0); if (split != null) { // TODO: See if there's a better way to get the individual's team (add it when we add the buzz?) // Risk is if there's a team name that is the same as someone's ID // If it's an individual who is a team, then the teamId will be null, but their user ID may be a // team ID. int bonusIndex = Array.IndexOf( teamIds, split.Action.Buzz.TeamId ?? split.Action.Buzz.UserId.ToString(CultureInfo.InvariantCulture)); if (bonusIndex < 0 || bonusIndex >= 2) { return(new FailureResult <IEnumerable <ValueRange> >( $"Unknown bonus team in phase {row - this.FirstPhaseRow + 1}. Cannot accurately create a scoresheet.")); } ranges.Add(CreateUpdateSingleCellRequest(sheetName, this.BonusColumns.Span[bonusIndex], row, 0)); } } return(new SuccessResult <IEnumerable <ValueRange> >(ranges)); }
protected static ValueRange CreateUpdateSingleCellRequest <T>( string sheetName, SpreadsheetColumn column, int rowIndex, T value) { ValueRange range = new ValueRange() { Range = $"'{sheetName}'!{column}{rowIndex}", Values = new List <IList <object> >() { new List <object>() { value } } }; return(range); }
protected override IResult <IEnumerable <ValueRange> > GetUpdateRangesForBonus( PhaseScore phaseScore, string[] teamIds, string sheetName, int row, int phasesCount) { Verify.IsNotNull(phaseScore, nameof(phaseScore)); List <ValueRange> ranges = new List <ValueRange>(); if (phaseScore.BonusScores?.Any() == true) { int bonusPartCount = phaseScore.BonusScores.Count(); if (bonusPartCount != 3) { return(new FailureResult <IEnumerable <ValueRange> >( $"Non-three part bonus in phase {row - this.FirstPhaseRow + 1}. Number of parts: {bonusPartCount}. These aren't supported for the scoresheet.")); } int bonusScoresIndex = Array.IndexOf(teamIds, phaseScore.BonusTeamId); if (bonusScoresIndex < 0) { return(new FailureResult <IEnumerable <ValueRange> >( $"Unknown bonus team in phase {row - this.FirstPhaseRow + 1}. Cannot accurately create a scoresheet.")); } SpreadsheetColumn bonusColumn = this.BonusColumns.Span[bonusScoresIndex]; ranges.Add(CreateUpdateCellsAlongRowRequest( sheetName, bonusColumn, row, phaseScore.BonusScores.Select(score => score > 0).ToArray())); SpreadsheetColumn otherBonusColumn = this.BonusColumns.Span[this.BonusColumns.Length - bonusScoresIndex - 1]; ranges.Add(CreateUpdateCellsAlongRowRequest( sheetName, otherBonusColumn, row, ClearedBonusArray)); } else { // Clear the bonus columns for (int i = 0; i < this.BonusColumns.Span.Length; i++) { ranges.Add(CreateUpdateCellsAlongRowRequest( sheetName, this.BonusColumns.Span[i], row, ClearedBonusArray)); } } return(new SuccessResult <IEnumerable <ValueRange> >(ranges)); }
private IReadOnlyDictionary <ulong, SpreadsheetColumn> CreatePlayerIdToColumnMapping( IEnumerable <IGrouping <string, PlayerTeamPair> > playersByTeam) { Dictionary <ulong, SpreadsheetColumn> playerIdToColumn = new Dictionary <ulong, SpreadsheetColumn>(); int startingColumnIndex = 0; foreach (IGrouping <string, PlayerTeamPair> grouping in playersByTeam) { SpreadsheetColumn startingColumn = this.StartingColumns.Span[startingColumnIndex]; foreach (PlayerTeamPair pair in grouping) { // TODO: If we ever support more than 6 players, we should bump up the next starting column playerIdToColumn[pair.PlayerId] = startingColumn; startingColumn = startingColumn + 1; } startingColumnIndex++; } return(playerIdToColumn); }
public async Task <IResult <string> > TryCreateScoresheet(GameState game, Uri sheetsUri, int roundNumber) { Verify.IsNotNull(game, nameof(game)); Verify.IsNotNull(sheetsUri, nameof(sheetsUri)); IReadOnlyDictionary <string, string> teamIdToNames = await game.TeamManager.GetTeamIdToNames(); if (teamIdToNames.Count == 0 || teamIdToNames.Count > 2) { return(CreateFailureResult("Export only works if there are 1 or 2 teams in the game.")); } // Make it an array so we don't keep re-evaluating the enumerable IReadOnlyDictionary <PlayerTeamPair, LastScoringSplit> players = await game.GetLastScoringSplits(); IEnumerable <IGrouping <string, PlayerTeamPair> > playersByTeam = players.GroupBy( kvp => kvp.Key.TeamId, kvp => kvp.Key); if (playersByTeam.Any(grouping => grouping.Count() > this.PlayersPerTeamLimit)) { return(CreateFailureResult("Export only currently works if there are at most 6 players on a team.")); } string[] teamIds = playersByTeam.Select(grouping => grouping.Key).ToArray(); IEnumerable <PhaseScore> phaseScores = await game.GetPhaseScores(); int phaseScoresCount = phaseScores.Count(); bool trimScoresheet = false; if (phaseScoresCount > this.PhasesLimit + 1 || (phaseScoresCount == this.PhasesLimit + 1 && phaseScores.Last().ScoringSplitsOnActions.Any())) { trimScoresheet = true; phaseScores = phaseScores.Take(this.PhasesLimit); } IReadOnlyDictionary <ulong, SpreadsheetColumn> playerIdToColumn = this.CreatePlayerIdToColumnMapping(playersByTeam); string sheetName = this.GetSheetName(roundNumber); List <ValueRange> ranges = new List <ValueRange>(); // Write the names of the teams int startingColumnIndex = 0; foreach (string teamId in teamIds) { if (!teamIdToNames.TryGetValue(teamId, out string teamName)) { // We know the player exists since the teamIds came from the list of players teamName = players.First(kvp => kvp.Key.TeamId == teamId).Key.PlayerDisplayName; } ranges.Add(CreateUpdateSingleCellRequest( sheetName, this.StartingColumns.Span[startingColumnIndex], this.TeamNameRow, teamName)); startingColumnIndex++; } foreach (PlayerTeamPair pair in players.Select(kvp => kvp.Key)) { // TODO: Make this more efficient by putting all the player names in one update request SpreadsheetColumn column = playerIdToColumn[pair.PlayerId]; ranges.Add(CreateUpdateSingleCellRequest(sheetName, column, this.PlayerNameRow, pair.PlayerDisplayName)); } // Tossup scoring should be similar, but some bonuses are 3 parts, while others are 1 value // We can either let the sheets handle that, or do a switch here for 1 vs 3 part bonuses // but we have the weird DT thing with TJSheets, so let's have abstract classes deal with it int row = this.FirstPhaseRow; int phasesCount = phaseScores.Count(); foreach (PhaseScore phaseScore in phaseScores) { foreach (ScoringSplitOnScoreAction action in phaseScore.ScoringSplitsOnActions) { if (!playerIdToColumn.TryGetValue(action.Action.Buzz.UserId, out SpreadsheetColumn column)) { return(new FailureResult <string>( $"Unknown player {action.Action.Buzz.PlayerDisplayName} (ID {action.Action.Buzz.UserId}). Cannot accurately create a scoresheet. This happens in phase {row - this.FirstPhaseRow + 1}")); } ranges.Add(CreateUpdateSingleCellRequest(sheetName, column, row, action.Action.Score)); } IResult <IEnumerable <ValueRange> > additionalRanges = this.GetAdditionalUpdateRangesForTossup( phaseScore, teamIds, sheetName, row, phasesCount); if (!additionalRanges.Success) { return(new FailureResult <string>(additionalRanges.ErrorMessage)); } ranges.AddRange(additionalRanges.Value); // We need to move this to the UpdateRanges method, since we may need to write other stuff // Include that in the comment if (row <= this.LastBonusRow) { IResult <IEnumerable <ValueRange> > bonusRanges = this.GetUpdateRangesForBonus( phaseScore, teamIds, sheetName, row, phasesCount); if (!bonusRanges.Success) { return(new FailureResult <string>(bonusRanges.ErrorMessage)); } ranges.AddRange(bonusRanges.Value); } row++; } IResult <string> updateResult = await this.SheetsApi.UpdateGoogleSheet( ranges, this.GetClearRanges(sheetName), sheetsUri); if (!updateResult.Success) { return(updateResult); } string message = $"Game written to the scoresheet {sheetName}."; if (trimScoresheet) { message += $" This game had more tossups than space in the scoresheet, so only the first {this.PhasesLimit} tossup/bonus cycles were recorded."; } return(new SuccessResult <string>(message)); }
public static SpreadsheetColumn Subtract(SpreadsheetColumn left, int right) { return(left - right); }
public static SpreadsheetColumn Add(SpreadsheetColumn left, int right) { return(left + right); }