public async Task <IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return(Page()); } _context.Attach(PuzzleResponse).State = EntityState.Modified; PuzzleId = PuzzleResponse.PuzzleID; try { await _context.SaveChangesAsync(); await PuzzleStateHelper.UpdateTeamsWhoSentResponse(_context, PuzzleResponse); } catch (DbUpdateConcurrencyException) { if (!ResponseExists(PuzzleResponse.ID)) { return(NotFound()); } else { throw; } } return(RedirectToPage("./Index", new { puzzleId = PuzzleId })); }
public async Task OnGetAsync() { // get the page data: puzzle, solve count, top three fastest var puzzlesData = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(s => s.SolvedTime != null && s.Puzzle.IsPuzzle) .GroupBy(state => state.Puzzle) .Select(g => new { Puzzle = g.Key, SolveCount = g.Count(), Fastest = g.OrderBy(s => s.SolvedTime - s.UnlockedTime).Take(3).Select(s => new { s.Team.ID, s.Team.Name, Time = s.SolvedTime - s.UnlockedTime }) }) .OrderByDescending(p => p.SolveCount).ThenBy(p => p.Puzzle.Name) .ToListAsync(); var puzzles = new List <PuzzleStats>(puzzlesData.Count); for (int i = 0; i < puzzlesData.Count; i++) { var data = puzzlesData[i]; var stats = new PuzzleStats() { Puzzle = data.Puzzle, SolveCount = data.SolveCount, SortOrder = i, Fastest = data.Fastest.Select(f => new FastRecord() { ID = f.ID, Name = f.Name, Time = f.Time }).ToArray() }; puzzles.Add(stats); } this.Puzzles = puzzles; }
public async Task <IActionResult> OnPostAsync(int puzzleId) { if (!ModelState.IsValid) { return(Page()); } PuzzleResponse.PuzzleID = puzzleId; // Ensure that the response text is unique across all responses for this puzzle. bool duplicateResponse = await(from Response r in _context.Responses where r.PuzzleID == puzzleId && r.SubmittedText == PuzzleResponse.SubmittedText select r).AnyAsync(); if (duplicateResponse) { ModelState.AddModelError("PuzzleResponse.SubmittedText", "Submission text is not unique"); return(await OnGetAsync(puzzleId)); } _context.Responses.Add(PuzzleResponse); await _context.SaveChangesAsync(); await PuzzleStateHelper.UpdateTeamsWhoSentResponse(_context, PuzzleResponse); return(RedirectToPage("./Index", new { puzzleid = puzzleId })); }
public async Task <IActionResult> InitializeModelAsync(Puzzle puzzle, Team team, SortOrder?sort) { if (puzzle == null && team == null) { return(NotFound()); } IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery( _context, Event, puzzle, team, EventRole == EventRole.admin ? null : LoggedInUser); Sort = sort; switch (sort ?? DefaultSort) { case SortOrder.PuzzleAscending: statesQ = statesQ.OrderBy(state => state.Puzzle.Name); break; case SortOrder.PuzzleDescending: statesQ = statesQ.OrderByDescending(state => state.Puzzle.Name); break; case SortOrder.TeamAscending: statesQ = statesQ.OrderBy(state => state.Team.Name); break; case SortOrder.TeamDescending: statesQ = statesQ.OrderByDescending(state => state.Team.Name); break; case SortOrder.UnlockAscending: statesQ = statesQ.OrderBy(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.UnlockDescending: statesQ = statesQ.OrderByDescending(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.SolveAscending: statesQ = statesQ.OrderBy(state => state.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: statesQ = statesQ.OrderByDescending(state => state.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } PuzzleStatePerTeam = await statesQ.Include(pspt => pspt.Team).Include(pspt => pspt.Puzzle).ToListAsync(); return(Page()); }
public async Task SetSolveStateAsync(Puzzle puzzle, Team team, bool value) { await PuzzleStateHelper.SetSolveStateAsync( _context, Event, puzzle, team, value?(DateTime?)DateTime.UtcNow : null, EventRole == EventRole.admin?null : LoggedInUser); }
public async Task SetEmailModeAsync(Puzzle puzzle, Team team, bool value) { await PuzzleStateHelper.SetEmailOnlyModeAsync( _context, Event, puzzle, team, value, EventRole == EventRole.admin?null : LoggedInUser); }
public async Task <IActionResult> OnPostAsync(int teamId) { Team = await _context.Teams.FindAsync(teamId); var mergeIntoTeam = await _context.Teams.FindAsync(MergeIntoID); if (Team == null || mergeIntoTeam == null || Team.Event != Event || mergeIntoTeam.Event != Event) { return(NotFound()); } List <string> memberEmails = null; List <string> mergeIntoMemberEmails = null; using (var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable)) { var members = await _context.TeamMembers.Where(tm => tm.Team.ID == teamId).ToListAsync(); memberEmails = await _context.TeamMembers.Where(tm => tm.Team.ID == teamId).Select(tm => tm.Member.Email).ToListAsync(); mergeIntoMemberEmails = await _context.TeamMembers.Where(tm => tm.Team.ID == MergeIntoID).Select(m => m.Member.Email).ToListAsync(); var states = await PuzzleStateHelper.GetSparseQuery(_context, Team.Event, null, Team).ToListAsync(); var mergeIntoStates = await PuzzleStateHelper.GetSparseQuery(_context, Team.Event, null, mergeIntoTeam).ToDictionaryAsync(s => s.PuzzleID); // copy all the team members over foreach (var member in members) { member.Team = mergeIntoTeam; } // also copy puzzle solves over foreach (var state in states) { if (state.SolvedTime != null && mergeIntoStates.TryGetValue(state.PuzzleID, out var mergeIntoState) && mergeIntoState.SolvedTime == null) { await PuzzleStateHelper.SetSolveStateAsync(_context, Team.Event, mergeIntoState.Puzzle, mergeIntoTeam, state.UnlockedTime); } } await TeamHelper.DeleteTeamAsync(_context, Team, sendEmail : false); await _context.SaveChangesAsync(); transaction.Commit(); } MailHelper.Singleton.SendPlaintextWithoutBcc(memberEmails.Union(mergeIntoMemberEmails), $"{Event.Name}: Team '{Team.Name}' has been merged into '{mergeIntoTeam.Name}'", $"These two teams have been merged into one superteam. Please welcome your new teammates!"); return(RedirectToPage("./Index")); }
public async Task SetUnlockStateAsync(Puzzle puzzle, Team team, bool value) { var statesQ = await PuzzleStateHelper.GetFullReadWriteQueryAsync(this.Context, this.Event, puzzle, team); var states = await statesQ.ToListAsync(); for (int i = 0; i < states.Count; i++) { states[i].IsUnlocked = value; } await Context.SaveChangesAsync(); }
public async Task <IActionResult> OnPostAsync(int?puzzleId) { // Handle no radio button selected if (Result == FreeformResult.None) { return(RedirectToPage()); } bool accepted = (Result == FreeformResult.Accepted); if (String.IsNullOrWhiteSpace(FreeformResponse)) { FreeformResponse = Result.ToString(); } Submission submission; using (var transaction = _context.Database.BeginTransaction()) { bool isAdmin = (EventRole == EventRole.admin); submission = await(from Submission sub in _context.Submissions join PuzzleAuthors author in _context.PuzzleAuthors on sub.PuzzleID equals author.PuzzleID where sub.ID == SubmissionID && sub.FreeformAccepted == null && sub.Puzzle.EventID == Event.ID && (isAdmin || author.Author == LoggedInUser) select sub).FirstOrDefaultAsync(); if (submission != null) { submission.FreeformAccepted = accepted; submission.FreeformResponse = FreeformResponse; submission.FreeformJudge = LoggedInUser; submission.FreeformFavorited = Favorite; if (accepted) { await PuzzleStateHelper.SetSolveStateAsync(_context, Event, submission.Puzzle, submission.Team, DateTime.UtcNow); } await _context.SaveChangesAsync(); await transaction.CommitAsync(); } } if (submission != null) { MailHelper.Singleton.SendPlaintextOneAddress(submission.Submitter.Email, $"{submission.Puzzle.Name} Submission {Result}", $"Your submission for {submission.Puzzle.Name} has been {Result} with the response: {FreeformResponse}"); } return(RedirectToPage()); }
private async Task SetupContext(int puzzleId) { Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); Puzzle = await _context.Puzzles.Where( (p) => p.ID == puzzleId).FirstOrDefaultAsync(); PuzzleState = await(PuzzleStateHelper .GetFullReadOnlyQuery( _context, Event, Puzzle, Team)) .FirstAsync(); SubmissionViews = await(from submission in _context.Submissions join user in _context.PuzzleUsers on submission.Submitter equals user join r in _context.Responses on submission.Response equals r into responses from response in responses.DefaultIfEmpty() where submission.Team == Team && submission.Puzzle == Puzzle orderby submission.TimeSubmitted select new SubmissionView() { Submission = submission, Response = response, SubmitterName = user.Name, FreeformReponse = submission.FreeformResponse, IsFreeform = Puzzle.IsFreeform }).ToListAsync(); Submissions = new List <Submission>(SubmissionViews.Count); foreach (SubmissionView submissionView in SubmissionViews) { Submissions.Add(submissionView.Submission); } PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync(); if (PuzzleState.SolvedTime != null) { if (!Puzzle.IsFreeform && Submissions?.Count > 0) { AnswerToken = Submissions.Last().SubmissionText; } else { AnswerToken = "(marked as solved by admin or author)"; } } }
public async Task <IActionResult> OnGet(int puzzleId) { PrereqPuzzleId = puzzleId; Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict this page to whistle stop non-puzzles if (puzzle.MinutesOfEventLockout == 0 || puzzle.IsPuzzle) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); var puzzleStateQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team); PuzzleStatePerTeam state = await(from pspt in puzzleStateQuery where pspt.PuzzleID == puzzle.ID select pspt).FirstOrDefaultAsync(); if (state == null) { return(NotFound()); } // Only move forward if the puzzle is open and unsolved if (state.UnlockedTime == null || state.SolvedTime != null) { return(NotFound()); } CurrentPuzzle = puzzle; // Treat all locked puzzles where this is a prerequisite as puzzles that can be unlocked PuzzleOptions = await(from pspt in puzzleStateQuery join prereq in _context.Prerequisites on pspt.PuzzleID equals prereq.PuzzleID where prereq.PrerequisiteID == puzzle.ID && pspt.SolvedTime == null && pspt.UnlockedTime == null select prereq.Puzzle).ToListAsync(); return(Page()); }
public async Task InitializeModelAsync(Puzzle puzzle, Team team, SortOrder?sort) { IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(this.Context, this.Event, puzzle, team); this.Sort = sort; switch (sort ?? this.DefaultSort) { case SortOrder.PuzzleAscending: statesQ = statesQ.OrderBy(state => state.Puzzle.Name); break; case SortOrder.PuzzleDescending: statesQ = statesQ.OrderByDescending(state => state.Puzzle.Name); break; case SortOrder.TeamAscending: statesQ = statesQ.OrderBy(state => state.Team.Name); break; case SortOrder.TeamDescending: statesQ = statesQ.OrderByDescending(state => state.Team.Name); break; case SortOrder.UnlockAscending: statesQ = statesQ.OrderBy(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.UnlockDescending: statesQ = statesQ.OrderByDescending(state => state.UnlockedTime ?? DateTime.MaxValue); break; case SortOrder.SolveAscending: statesQ = statesQ.OrderBy(state => state.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: statesQ = statesQ.OrderByDescending(state => state.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } PuzzleStatePerTeam = await statesQ.ToListAsync(); }
public async Task OnGetAsync() { Dictionary <int, string> teamNameLookup = new Dictionary <int, string>(); // build an ID-to-name mapping to improve perf var names = await _context.Teams.Where(t => t.Event == Event) .Select(t => new { t.ID, t.Name }) .ToListAsync(); names.ForEach(t => teamNameLookup[t.ID] = t.Name); // get the page data: puzzle, solve count, top three fastest var puzzlesData = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(s => s.SolvedTime != null && s.Puzzle.IsPuzzle) .GroupBy(state => state.Puzzle) .Select(g => new { Puzzle = g.Key, SolveCount = g.Count(), Fastest = g.OrderBy(s => s.SolvedTime - s.UnlockedTime).Take(3).Select(s => new { s.Team.ID, Time = s.SolvedTime - s.UnlockedTime }) }) .OrderByDescending(p => p.SolveCount).ThenBy(p => p.Puzzle.Name) .ToListAsync(); var puzzles = new List <PuzzleStats>(puzzlesData.Count); for (int i = 0; i < puzzlesData.Count; i++) { var data = puzzlesData[i]; var stats = new PuzzleStats() { Puzzle = data.Puzzle, SolveCount = data.SolveCount, SortOrder = i, Fastest = data.Fastest.Select(f => new FastRecord() { ID = f.ID, Name = teamNameLookup[f.ID], Time = f.Time }).ToArray() }; puzzles.Add(stats); } this.Puzzles = puzzles; }
private async Task <IList <HintWithState> > GetAllHints(int puzzleID, int teamID) { Hints = await(from Hint hint in _context.Hints join HintStatePerTeam state in _context.HintStatePerTeam on hint.Id equals state.HintID where state.TeamID == teamID && hint.Puzzle.ID == puzzleID orderby hint.DisplayOrder, hint.Description select new HintWithState { Hint = hint, IsUnlocked = state.IsUnlocked }).ToListAsync(); bool solved = await PuzzleStateHelper.IsPuzzleSolved(_context, puzzleID, teamID); if (Hints.Count > 0) { int discount = Hints.Min(hws => (hws.IsUnlocked && hws.Hint.Cost < 0) ? hws.Hint.Cost : 0); // During a beta, once a puzzle is solved, all other hints become free. // There's no IsBeta flag on an event, so check the name. // We can change this in the unlikely event there's a beta-themed hunt. bool allHintsFree = solved && Event.Name.ToLower().Contains("beta"); foreach (HintWithState hint in Hints) { if (allHintsFree) { hint.Discount = -hint.BaseCost; } else if (hint.Hint.Cost < 0) { hint.Discount = discount; } } // if the event is over, show all hints if (Event.AreAnswersAvailableNow && Team.Name.Contains("Archive")) { foreach (HintWithState hint in Hints) { hint.IsUnlocked = true; } } } return(Hints); }
public async Task <IActionResult> OnGet(int puzzleId) { Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict to the Backstage metapuzzle if (puzzle.Group != "Backstage") { return(NotFound()); } if (puzzle.SolveValue != 50) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); var puzzleQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team, null); var solvedBackstagePuzzles = await(from PuzzleStatePerTeam pspt in puzzleQuery where pspt.SolvedTime != null && pspt.UnlockedTime != null && pspt.Puzzle.IsPuzzle && pspt.Puzzle.Group == "Backstage" select new BackstageSolveStatus() { PuzzleName = pspt.Puzzle.Name, SolveTime = (int)(pspt.SolvedTime.Value - pspt.UnlockedTime.Value).TotalSeconds }).ToListAsync(); string json = JsonConvert.SerializeObject(solvedBackstagePuzzles); string escapedJson = Uri.EscapeDataString(json); byte[] macKey = Encoding.ASCII.GetBytes(puzzle.Description); HMACSHA1 hasher = new HMACSHA1(macKey); byte[] jsonBytes = Encoding.ASCII.GetBytes(escapedJson); byte[] mac = hasher.ComputeHash(jsonBytes); string macString = Convert.ToBase64String(mac).Replace('/', '_').Replace('+', '.'); return(Redirect($"https://ph20backstage.azurewebsites.net/openingnight/{macString}/{escapedJson}")); }
public async Task OnGetAsync() { var teamsData = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(s => s.SolvedTime != null) .GroupBy(state => state.Team) .Select(g => new { Team = g.Key, SolveCount = g.Count(), Score = g.Sum(s => s.Puzzle.SolveValue), FinalMetaSolveTime = g.Where(s => s.Puzzle.IsFinalPuzzle).Select(s => s.SolvedTime).FirstOrDefault() }) .OrderBy(t => t.FinalMetaSolveTime).ThenByDescending(t => t.Score).ThenBy(t => t.Team.Name) .ToListAsync(); var teams = new List <TeamStats>(teamsData.Count); TeamStats prevStats = null; for (int i = 0; i < teamsData.Count; i++) { var data = teamsData[i]; var stats = new TeamStats() { Team = data.Team, SolveCount = data.SolveCount, Score = data.Score, FinalMetaSolveTime = data.FinalMetaSolveTime ?? DateTime.MaxValue }; if (prevStats == null || stats.FinalMetaSolveTime != prevStats.FinalMetaSolveTime || stats.Score != prevStats.Score) { stats.Rank = i + 1; } teams.Add(stats); prevStats = stats; } this.Teams = teams; }
private async Task SetupContext(int puzzleId) { Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); Puzzle = await _context.Puzzles.Where( (p) => p.ID == puzzleId).FirstOrDefaultAsync(); PuzzleState = await(PuzzleStateHelper .GetFullReadOnlyQuery( _context, Event, Puzzle, Team)) .FirstOrDefaultAsync(); Submissions = await _context.Submissions.Where( (s) => s.Team == Team && s.Puzzle == Puzzle) .OrderBy(submission => submission.TimeSubmitted) .ToListAsync(); PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync(); }
public async Task PlayerCanSeePuzzleCheck(AuthorizationHandlerContext authContext, IAuthorizationRequirement requirement) { PuzzleUser puzzleUser = await PuzzleUser.GetPuzzleUserForCurrentUser(dbContext, authContext.User, userManager); Puzzle puzzle = await GetPuzzleFromRoute(); Event thisEvent = await GetEventFromRoute(); if (thisEvent != null && puzzle != null) { Team team = await UserEventHelper.GetTeamForPlayer(dbContext, thisEvent, puzzleUser); if (team != null) { IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadOnlyQuery(dbContext, thisEvent, puzzle, team); if (statesQ.FirstOrDefault().UnlockedTime != null || thisEvent.AreAnswersAvailableNow) { authContext.Succeed(requirement); } } } }
public async Task <IActionResult> OnPostAsync(int puzzleId) { if (!this.Event.IsAnswerSubmissionActive) { return(RedirectToPage("/Submissions/Index", new { puzzleid = puzzleId })); } await SetupContext(puzzleId); if (!ModelState.IsValid) { return(Page()); } // Don't allow submissions after the answer has been found. if (PuzzleState.SolvedTime != null) { return(Page()); } // Create submission and add it to list Submission submission = new Submission { SubmissionText = SubmissionText, TimeSubmitted = DateTime.UtcNow, Puzzle = PuzzleState.Puzzle, Team = PuzzleState.Team, Submitter = LoggedInUser, }; submission.Response = await _context.Responses.Where( r => r.Puzzle.ID == puzzleId && submission.SubmissionText == r.SubmittedText) .FirstOrDefaultAsync(); Submissions.Add(submission); // Update puzzle state if submission was correct if (submission.Response != null && submission.Response.IsSolution) { await PuzzleStateHelper.SetSolveStateAsync(_context, Event, submission.Puzzle, submission.Team, submission.TimeSubmitted); } else if (submission.Response == null) { // We also determine if the puzzle should be set to email-only mode. if (IsPuzzleSubmissionLimitReached( Event, Submissions, PuzzleState)) { await PuzzleStateHelper.SetEmailOnlyModeAsync(_context, Event, submission.Puzzle, submission.Team, true); } else { // If the submission was incorrect and not a partial solution, // we will do the lockout computations now. DateTime?lockoutExpiryTime = ComputeLockoutExpiryTime( Event, Submissions, PuzzleState); if (lockoutExpiryTime != null) { await PuzzleStateHelper.SetLockoutExpiryTimeAsync(_context, Event, submission.Puzzle, submission.Team, lockoutExpiryTime); } } } _context.Submissions.Add(submission); await _context.SaveChangesAsync(); return(RedirectToPage( "/Submissions/Index", new { puzzleid = puzzleId })); }
public async Task OnGetAsync(SortOrder?sort, PuzzleStateFilter?stateFilter) { this.Sort = sort; this.StateFilter = stateFilter; this.CurrentTeam = (await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser)); Dictionary <int, string> teamNameLookup = new Dictionary <int, string>(); // build an ID-to-name mapping to improve perf var names = await _context.Teams.Where(t => t.Event == Event) .Select(t => new { t.ID, t.Name }) .ToListAsync(); names.ForEach(t => teamNameLookup[t.ID] = t.Name); DateTime submissionEnd = Event.AnswerSubmissionEnd; // Get the page data: puzzle, solve count, top three fastest // Puzzle solve counts var solveCounts = await(from pspt in _context.PuzzleStatePerTeam where pspt.SolvedTime != null && pspt.Puzzle.IsPuzzle && pspt.UnlockedTime != null && pspt.SolvedTime <= submissionEnd && !pspt.Team.IsDisqualified && pspt.Puzzle.Event == Event let puzzleToGroup = new { PuzzleID = pspt.Puzzle.ID, PuzzleName = pspt.Puzzle.Name } // Using 'let' to work around EF grouping limitations (https://www.codeproject.com/Questions/5266406/Invalidoperationexception-the-LINQ-expression-for) group puzzleToGroup by puzzleToGroup.PuzzleID into puzzleGroup orderby puzzleGroup.Count() descending, puzzleGroup.Max(puzzleGroup => puzzleGroup.PuzzleName) // Using Max(PuzzleName) because only aggregate operators are allowed select new { PuzzleID = puzzleGroup.Key, PuzzleName = puzzleGroup.Max(puzzleGroup => puzzleGroup.PuzzleName), SolveCount = puzzleGroup.Count() }).ToListAsync(); // Getting the top 3 requires working around EF limitations translating sorting results within a group to SQL. Workaround from https://github.com/dotnet/efcore/issues/13805 // Filter to solved puzzles var psptToQuery = _context.PuzzleStatePerTeam.Where(pspt => pspt.Puzzle.EventID == Event.ID && pspt.Puzzle.IsPuzzle && pspt.UnlockedTime != null && pspt.SolvedTime != null && pspt.SolvedTime < submissionEnd && !pspt.Team.IsDisqualified ); // Sort by time and get the top 3 var fastestResults = _context.PuzzleStatePerTeam .Select(pspt => pspt.PuzzleID).Distinct() .SelectMany(puzzleId => psptToQuery .Where(pspt => pspt.PuzzleID == puzzleId) .OrderBy(pspt => EF.Functions.DateDiffSecond(pspt.UnlockedTime, pspt.SolvedTime)) .Take(3), (puzzleId, pspt) => pspt) .ToLookup(pspt => pspt.PuzzleID, pspt => new { pspt.TeamID, pspt.SolvedTime, pspt.UnlockedTime }); var unlockedData = this.CurrentTeam == null ? null : ( await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(state => state.Team == this.CurrentTeam && state.UnlockedTime != null) .Select(s => new { s.PuzzleID, IsSolvedByUserTeam = (s.SolvedTime < submissionEnd) }) .ToDictionaryAsync(s => s.PuzzleID)); var puzzles = new List <PuzzleStats>(solveCounts.Count); for (int i = 0; i < solveCounts.Count; i++) { var data = solveCounts[i]; // For players, we will hide puzzles they have not unlocked yet. if (EventRole == EventRole.play && (unlockedData == null || !unlockedData.ContainsKey(data.PuzzleID))) { continue; } FastRecord[] fastest; if (fastestResults.Contains(data.PuzzleID)) { fastest = fastestResults[data.PuzzleID].Select(f => new FastRecord() { ID = f.TeamID, Name = teamNameLookup[f.TeamID], Time = f.SolvedTime - f.UnlockedTime }).ToArray(); } else { fastest = Array.Empty <FastRecord>(); } bool isSolved = false; if (unlockedData != null) { isSolved = unlockedData[data.PuzzleID].IsSolvedByUserTeam; } var stats = new PuzzleStats() { PuzzleName = data.PuzzleName, SolveCount = data.SolveCount, SortOrder = i, Fastest = fastest, IsSolved = isSolved }; puzzles.Add(stats); } if (this.StateFilter == PuzzleStateFilter.Unsolved) { puzzles = puzzles.Where(stats => !stats.IsSolved).ToList(); } switch (sort ?? DefaultSort) { case SortOrder.RankAscending: puzzles.Sort((rhs, lhs) => (rhs.SortOrder - lhs.SortOrder)); break; case SortOrder.RankDescending: puzzles.Sort((rhs, lhs) => (lhs.SortOrder - rhs.SortOrder)); break; case SortOrder.CountAscending: puzzles.Sort((rhs, lhs) => (rhs.SolveCount - lhs.SolveCount)); break; case SortOrder.CountDescending: puzzles.Sort((rhs, lhs) => (lhs.SolveCount - rhs.SolveCount)); break; case SortOrder.PuzzleAscending: puzzles.Sort((rhs, lhs) => (String.Compare(rhs.PuzzleName, lhs.PuzzleName, StringComparison.OrdinalIgnoreCase))); break; case SortOrder.PuzzleDescending: puzzles.Sort((rhs, lhs) => (String.Compare(lhs.PuzzleName, rhs.PuzzleName, StringComparison.OrdinalIgnoreCase))); break; default: throw new ArgumentException($"unknown sort: {sort}"); } this.Puzzles = puzzles; }
public async Task <IActionResult> OnPostAsync(int puzzleId, string submissionText) { if (String.IsNullOrWhiteSpace(submissionText)) { ModelState.AddModelError("submissionText", "Your answer cannot be empty"); } SubmissionText = submissionText; if (DateTime.UtcNow < Event.EventBegin) { return(NotFound("The event hasn't started yet!")); } await SetupContext(puzzleId); if (!ModelState.IsValid) { return(Page()); } // Don't allow submissions if the team is locked out. if (PuzzleState.IsTeamLockedOut) { return(Page()); } // Don't allow submissions if team is in email only mode. if (PuzzleState.IsEmailOnlyMode) { return(Page()); } // Don't allow submissions after the answer has been found. if (PuzzleState.SolvedTime != null) { return(Page()); } // Don't accept posted submissions when a puzzle is causing lockout if (PuzzlesCausingGlobalLockout.Count != 0 && !PuzzlesCausingGlobalLockout.Contains(Puzzle)) { return(Page()); } // Soft enforcement of duplicates to give a friendly message in most cases DuplicateSubmission = (from sub in Submissions where sub.SubmissionText == ServerCore.DataModel.Response.FormatSubmission(submissionText) select sub).Any(); if (DuplicateSubmission) { return(Page()); } // Create submission and add it to list Submission submission = new Submission { TimeSubmitted = DateTime.UtcNow, Puzzle = PuzzleState.Puzzle, Team = PuzzleState.Team, Submitter = LoggedInUser, AllowFreeformSharing = AllowFreeformSharing }; string submissionTextToCheck = ServerCore.DataModel.Response.FormatSubmission(submissionText); if (Puzzle.IsFreeform) { submission.UnformattedSubmissionText = submissionText; } else { submission.SubmissionText = submissionText; } submission.Response = await _context.Responses.Where( r => r.Puzzle.ID == puzzleId && submissionTextToCheck == r.SubmittedText) .FirstOrDefaultAsync(); Submissions.Add(submission); // Update puzzle state if submission was correct if (submission.Response != null && submission.Response.IsSolution) { await PuzzleStateHelper.SetSolveStateAsync(_context, Event, submission.Puzzle, submission.Team, submission.TimeSubmitted); AnswerToken = submission.SubmissionText; } else if (!Puzzle.IsFreeform && submission.Response == null && Event.IsAnswerSubmissionActive) { // We also determine if the puzzle should be set to email-only mode. if (IsPuzzleSubmissionLimitReached( Event, Submissions, PuzzleState)) { await PuzzleStateHelper.SetEmailOnlyModeAsync(_context, Event, submission.Puzzle, submission.Team, true); var authors = await _context.PuzzleAuthors.Where((pa) => pa.Puzzle == submission.Puzzle).Select((pa) => pa.Author.Email).ToListAsync(); MailHelper.Singleton.SendPlaintextBcc(authors, $"{Event.Name}: Team {submission.Team.Name} is in email mode for {submission.Puzzle.Name}", ""); } else { // If the submission was incorrect and not a partial solution, // we will do the lockout computations now. DateTime?lockoutExpiryTime = ComputeLockoutExpiryTime( Event, Submissions, PuzzleState); if (lockoutExpiryTime != null) { await PuzzleStateHelper.SetLockoutExpiryTimeAsync(_context, Event, submission.Puzzle, submission.Team, lockoutExpiryTime); } } } _context.Submissions.Add(submission); await _context.SaveChangesAsync(); SubmissionViews.Add(new SubmissionView() { Submission = submission, Response = submission.Response, SubmitterName = LoggedInUser.Name, IsFreeform = Puzzle.IsFreeform }); return(Page()); }
public async Task <IActionResult> OnPostUnlock(int puzzleId, int unlockId) { Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict this page to whistle stop non-puzzles if (puzzle.MinutesOfEventLockout == 0 || puzzle.IsPuzzle) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); using (var transaction = _context.Database.BeginTransaction()) { var puzzleStateQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team); PuzzleStatePerTeam prereqState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == puzzle.ID select pspt).FirstOrDefaultAsync(); if (prereqState == null) { return(NotFound()); } // Only move forward if the prereq is open and unsolved if (prereqState.UnlockedTime == null || prereqState.SolvedTime != null) { return(NotFound("Your team has already chosen and can't choose again")); } PuzzleStatePerTeam unlockState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == unlockId select pspt).FirstOrDefaultAsync(); if (unlockState == null) { return(NotFound()); } // The chosen puzzle must be locked (and unsolved) if (unlockState.UnlockedTime != null || unlockState.SolvedTime != null) { return(NotFound("You've already chosen this puzzle")); } // Ensure the puzzle is actually one of the unlock options Prerequisites prereq = await(from pre in _context.Prerequisites where pre.PrerequisiteID == puzzleId && pre.PuzzleID == unlockId select pre).FirstOrDefaultAsync(); if (prereq == null) { return(NotFound()); } await PuzzleStateHelper.SetSolveStateAsync(_context, Event, puzzle, team, DateTime.UtcNow); await PuzzleStateHelper.SetUnlockStateAsync(_context, Event, unlockState.Puzzle, team, DateTime.UtcNow); transaction.Commit(); } Puzzle puzzleToUnlock = await(from p in _context.Puzzles where p.ID == unlockId select p).FirstOrDefaultAsync(); string puzzleUrl; if (puzzleToUnlock.CustomURL != null) { puzzleUrl = PuzzleHelper.GetFormattedUrl(puzzleToUnlock, Event.ID); } else { puzzleUrl = puzzleToUnlock.PuzzleFile.UrlString; } return(Redirect(puzzleUrl)); }
public async Task OnGetAsync() { // get the puzzles and teams // TODO: Filter puzzles if an author; no need to filter teams. Revisit when authors exist. var puzzles = await _context.Puzzles.Where(p => p.Event == this.Event).Select(p => new PuzzleStats() { Puzzle = p }).ToListAsync(); var teams = await _context.Teams.Where(t => t.Event == this.Event).Select(t => new TeamStats() { Team = t }).ToListAsync(); // build an ID-based lookup for puzzles and teams var puzzleLookup = new Dictionary <int, PuzzleStats>(); puzzles.ForEach(p => puzzleLookup[p.Puzzle.ID] = p); var teamLookup = new Dictionary <int, TeamStats>(); teams.ForEach(t => teamLookup[t.Team.ID] = t); // tabulate solve counts and team scores var states = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null).ToListAsync(); var stateList = new List <StateStats>(states.Count); foreach (var state in states) { // TODO: Is it more performant to prefilter the states if an author, or is this sufficient? if (!puzzleLookup.TryGetValue(state.PuzzleID, out PuzzleStats puzzle) || !teamLookup.TryGetValue(state.TeamID, out TeamStats team)) { continue; } stateList.Add(new StateStats() { Puzzle = puzzle, Team = team, UnlockedTime = state.UnlockedTime, SolvedTime = state.SolvedTime }); if (state.IsSolved) { puzzle.SolveCount++; team.SolveCount++; team.Score += puzzle.Puzzle.SolveValue; if (puzzle.Puzzle.IsFinalPuzzle) { team.FinalMetaSolveTime = state.SolvedTime.Value; } } } // sort puzzles by solve count, add the sort index to the lookup puzzles = puzzles.OrderByDescending(p => p.SolveCount).ThenBy(p => p.Puzzle.Name).ToList(); for (int i = 0; i < puzzles.Count; i++) { puzzles[i].SortOrder = i; } // sort teams by metameta/score, add the sort index to the lookup teams = teams.OrderBy(t => t.FinalMetaSolveTime).ThenByDescending(t => t.Score).ThenBy(t => t.Team.Name).ToList(); for (int i = 0; i < teams.Count; i++) { if (i == 0 || teams[i].FinalMetaSolveTime != teams[i - 1].FinalMetaSolveTime || teams[i].Score != teams[i - 1].Score) { teams[i].Rank = i + 1; } teams[i].SortOrder = i; } // Build the map var stateMap = new StateStats[puzzles.Count, teams.Count]; stateList.ForEach(state => stateMap[state.Puzzle.SortOrder, state.Team.SortOrder] = state); this.Puzzles = puzzles; this.Teams = teams; this.StateMap = stateMap; }
public async Task OnGetAsync(SortOrder?sort, PuzzleStateFilter?stateFilter) { this.Sort = sort; this.StateFilter = stateFilter; this.CurrentTeam = (await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser)); Dictionary <int, string> teamNameLookup = new Dictionary <int, string>(); // build an ID-to-name mapping to improve perf var names = await _context.Teams.Where(t => t.Event == Event) .Select(t => new { t.ID, t.Name }) .ToListAsync(); names.ForEach(t => teamNameLookup[t.ID] = t.Name); // get the page data: puzzle, solve count, top three fastest var puzzlesData = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(s => s.SolvedTime != null && s.Puzzle.IsPuzzle) .GroupBy(state => state.Puzzle) .Select(g => new { Puzzle = g.Key, SolveCount = g.Count(), Fastest = g.OrderBy(s => s.SolvedTime - s.UnlockedTime).Take(3).Select(s => new { s.Team.ID, Time = s.SolvedTime - s.UnlockedTime }), IsSolvedByUserTeam = g.Where(s => s.Team == this.CurrentTeam).Any() }) .OrderByDescending(p => p.SolveCount).ThenBy(p => p.Puzzle.Name) .ToListAsync(); var unlockedData = this.CurrentTeam == null ? null : (new HashSet <int>( await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(state => state.Team == this.CurrentTeam && state.UnlockedTime != null) .Select(s => s.PuzzleID) .ToListAsync())); var puzzles = new List <PuzzleStats>(puzzlesData.Count); for (int i = 0; i < puzzlesData.Count; i++) { var data = puzzlesData[i]; // For players, we will hide puzzles they have not unlocked yet. if (EventRole == EventRole.play && (unlockedData == null || !unlockedData.Contains(data.Puzzle.ID))) { continue; } var stats = new PuzzleStats() { Puzzle = data.Puzzle, SolveCount = data.SolveCount, SortOrder = i, Fastest = data.Fastest.Select(f => new FastRecord() { ID = f.ID, Name = teamNameLookup[f.ID], Time = f.Time }).ToArray(), IsSolved = data.IsSolvedByUserTeam }; puzzles.Add(stats); } if (this.StateFilter == PuzzleStateFilter.Unsolved) { puzzles = puzzles.Where(stats => !stats.IsSolved).ToList(); } switch (sort ?? DefaultSort) { case SortOrder.RankAscending: puzzles.Sort((rhs, lhs) => (rhs.SortOrder - lhs.SortOrder)); break; case SortOrder.RankDescending: puzzles.Sort((rhs, lhs) => (lhs.SortOrder - rhs.SortOrder)); break; case SortOrder.CountAscending: puzzles.Sort((rhs, lhs) => (rhs.SolveCount - lhs.SolveCount)); break; case SortOrder.CountDescending: puzzles.Sort((rhs, lhs) => (lhs.SolveCount - rhs.SolveCount)); break; case SortOrder.PuzzleAscending: puzzles.Sort((rhs, lhs) => (String.Compare(rhs.Puzzle.Name, lhs.Puzzle.Name, StringComparison.OrdinalIgnoreCase))); break; case SortOrder.PuzzleDescending: puzzles.Sort((rhs, lhs) => (String.Compare(lhs.Puzzle.Name, rhs.Puzzle.Name, StringComparison.OrdinalIgnoreCase))); break; default: throw new ArgumentException($"unknown sort: {sort}"); } this.Puzzles = puzzles; }
public async Task OnGetAsync(SortOrder?sort, int teamId) { TeamID = teamId; Team myTeam = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); if (myTeam != null) { this.TeamID = myTeam.ID; await PuzzleStateHelper.CheckForTimedUnlocksAsync(_context, Event, myTeam); } else { throw new Exception("Not currently registered for a team"); } this.Sort = sort; // all puzzles for this event that are real puzzles var puzzlesInEventQ = _context.Puzzles.Where(puzzle => puzzle.Event.ID == this.Event.ID && puzzle.IsPuzzle); // unless we're in a global lockout, then filter to those! var puzzlesCausingGlobalLockoutQ = PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, myTeam); if (await puzzlesCausingGlobalLockoutQ.AnyAsync()) { puzzlesInEventQ = puzzlesCausingGlobalLockoutQ; } // all puzzle states for this team that are unlocked (note: IsUnlocked bool is going to harm perf, just null check the time here) // Note that it's OK if some puzzles do not yet have a state record; those puzzles are clearly still locked and hence invisible. var stateForTeamQ = _context.PuzzleStatePerTeam.Where(state => state.TeamID == this.TeamID && state.UnlockedTime != null); // join 'em (note: just getting all properties for max flexibility, can pick and choose columns for perf later) // Note: EF gotcha is that you have to join into anonymous types in order to not lose valuable stuff var visiblePuzzlesQ = puzzlesInEventQ.Join(stateForTeamQ, (puzzle => puzzle.ID), (state => state.PuzzleID), (Puzzle, State) => new { Puzzle, State }); switch (sort ?? DefaultSort) { case SortOrder.PuzzleAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.Puzzle.Name); break; case SortOrder.PuzzleDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.Puzzle.Name); break; case SortOrder.GroupAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.Puzzle.Group).ThenBy(puzzleWithState => puzzleWithState.Puzzle.OrderInGroup); break; case SortOrder.GroupDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.Puzzle.Group).ThenByDescending(puzzleWithState => puzzleWithState.Puzzle.OrderInGroup); break; case SortOrder.SolveAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.State.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.State.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } PuzzlesWithState = (await visiblePuzzlesQ.ToListAsync()).Select(x => new PuzzleWithState(x.Puzzle, x.State)).ToList(); }
public async Task OnGetAsync(SortOrder?sort, int teamId, PuzzleStateFilter?stateFilter) { TeamID = teamId; Team myTeam = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); if (myTeam != null) { this.TeamID = myTeam.ID; await PuzzleStateHelper.CheckForTimedUnlocksAsync(_context, Event, myTeam); } else { throw new Exception("Not currently registered for a team"); } this.Sort = sort; this.StateFilter = stateFilter; ShowAnswers = Event.AnswersAvailableBegin <= DateTime.UtcNow; AllowFeedback = Event.AllowFeedback; // all puzzles for this event that are real puzzles var puzzlesInEventQ = _context.Puzzles.Where(puzzle => puzzle.Event.ID == this.Event.ID && puzzle.IsPuzzle); // unless we're in a global lockout, then filter to those! var puzzlesCausingGlobalLockoutQ = PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, myTeam); if (await puzzlesCausingGlobalLockoutQ.AnyAsync()) { puzzlesInEventQ = puzzlesCausingGlobalLockoutQ; } // all puzzle states for this team that are unlocked (note: IsUnlocked bool is going to harm perf, just null check the time here) // Note that it's OK if some puzzles do not yet have a state record; those puzzles are clearly still locked and hence invisible. // All puzzles will show if all answers have been released) var stateForTeamQ = _context.PuzzleStatePerTeam.Where(state => state.TeamID == this.TeamID && (ShowAnswers || state.UnlockedTime != null)); // join 'em (note: just getting all properties for max flexibility, can pick and choose columns for perf later) // Note: EF gotcha is that you have to join into anonymous types in order to not lose valuable stuff var visiblePuzzlesQ = from Puzzle puzzle in puzzlesInEventQ join PuzzleStatePerTeam pspt in stateForTeamQ on puzzle.ID equals pspt.PuzzleID select new PuzzleView { ID = puzzle.ID, Group = puzzle.Group, OrderInGroup = puzzle.OrderInGroup, Name = puzzle.Name, CustomUrl = puzzle.CustomURL, Errata = puzzle.Errata, SolvedTime = pspt.SolvedTime, PieceMetaUsage = puzzle.PieceMetaUsage }; switch (sort ?? DefaultSort) { case SortOrder.PuzzleAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.Name); break; case SortOrder.PuzzleDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.Name); break; case SortOrder.GroupAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.Group).ThenBy(pv => pv.OrderInGroup).ThenBy(pv => pv.Name); break; case SortOrder.GroupDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.Group).ThenByDescending(pv => pv.OrderInGroup).ThenByDescending(pv => pv.Name); break; case SortOrder.SolveAscending: visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.SolvedTime ?? DateTime.MaxValue); break; case SortOrder.SolveDescending: visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.SolvedTime ?? DateTime.MaxValue); break; default: throw new ArgumentException($"unknown sort: {sort}"); } if (this.StateFilter == PuzzleStateFilter.Unsolved) { visiblePuzzlesQ = visiblePuzzlesQ.Where(puzzles => puzzles.SolvedTime == null); } PuzzleViews = await visiblePuzzlesQ.ToListAsync(); Dictionary <int, ContentFile> files = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Puzzle select file).ToDictionaryAsync(file => file.PuzzleID); foreach (var puzzleView in PuzzleViews) { files.TryGetValue(puzzleView.ID, out ContentFile content); puzzleView.Content = content; } if (ShowAnswers) { Dictionary <int, ContentFile> answers = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Answer select file).ToDictionaryAsync(file => file.PuzzleID); foreach (var puzzleView in PuzzleViews) { answers.TryGetValue(puzzleView.ID, out ContentFile answer); puzzleView.Answer = answer; } } }
public async Task <IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return(Page()); } using (var transaction = _context.Database.BeginTransaction()) { // // Add the event and save, so the event gets an ID. // Event.TeamRegistrationBegin = DateTime.UtcNow; Event.TeamRegistrationEnd = Event.AnswerSubmissionEnd; Event.TeamNameChangeEnd = Event.AnswerSubmissionEnd; Event.TeamMembershipChangeEnd = Event.AnswerSubmissionEnd; Event.TeamMiscDataChangeEnd = Event.AnswerSubmissionEnd; Event.TeamDeleteEnd = Event.AnswerSubmissionEnd; Event.AnswersAvailableBegin = Event.AnswerSubmissionEnd; Event.StandingsAvailableBegin = DateTime.UtcNow; Event.LockoutIncorrectGuessLimit = 5; Event.LockoutIncorrectGuessPeriod = 1; Event.LockoutDurationMultiplier = 2; Event.MaxSubmissionCount = 50; Event.MaxNumberOfTeams = 120; Event.MaxExternalsPerTeam = 9; Event.MaxTeamSize = 12; _context.Events.Add(Event); await _context.SaveChangesAsync(); // // Add start puzzle, three module puzzles, and one module meta (marked as the final event puzzle for this demo) // Puzzle start = new Puzzle { Name = "!!!Get Hopping!!!", Event = Event, IsPuzzle = false, IsGloballyVisiblePrerequisite = true, Description = "Start the event", }; _context.Puzzles.Add(start); Puzzle easy = new Puzzle { Name = "Bunny Slope", Event = Event, IsPuzzle = true, SolveValue = 10, HintCoinsForSolve = 1, Group = "Thumper's Stumpers", OrderInGroup = 1, MinPrerequisiteCount = 1, Description = "Bunsweeper", }; _context.Puzzles.Add(easy); Puzzle intermediate = new Puzzle { Name = "Rabbit Run (automatically solves in ~3 mins)", Event = Event, IsPuzzle = true, SolveValue = 10, HintCoinsForSolve = 2, Group = "Thumper's Stumpers", OrderInGroup = 2, MinPrerequisiteCount = 1, MinutesToAutomaticallySolve = 3, Description = "Rabbit's Cube", }; _context.Puzzles.Add(intermediate); Puzzle hard = new Puzzle { Name = "Hare-Raising", Event = Event, IsPuzzle = true, SolveValue = 10, HintCoinsForSolve = 3, Group = "Thumper's Stumpers", OrderInGroup = 3, MinPrerequisiteCount = 1, Description = "Lateral Leaping", }; _context.Puzzles.Add(hard); Puzzle meta = new Puzzle { Name = "Lagomorph Meta", Event = Event, IsPuzzle = true, IsMetaPuzzle = true, IsFinalPuzzle = true, SolveValue = 100, Group = "Thumper's Stumpers", OrderInGroup = 99, MinPrerequisiteCount = 2, Description = "Word Hutch", }; _context.Puzzles.Add(meta); Puzzle other = new Puzzle { Name = "Rabbit Season", Event = Event, IsPuzzle = true, SolveValue = 10, Group = "Daffy's Delights", OrderInGroup = 1, MinPrerequisiteCount = 1, Description = "Hip Hop Identification", CustomURL = "https://www.bing.com/images/search?q=%22rabbit%22", }; _context.Puzzles.Add(other); Puzzle cheat = new Puzzle { Name = "You're Despicable (cheat code)", Event = Event, IsPuzzle = true, IsCheatCode = true, SolveValue = -1, Group = "Daffy's Delights", OrderInGroup = 2, MinPrerequisiteCount = 1, Description = "Duck Konundrum", }; _context.Puzzles.Add(cheat); Puzzle lockIntro = new Puzzle { Name = "Wouldn't you know... (whistle stop intro)", Event = Event, IsPuzzle = true, SolveValue = 0, Group = "Roger's Railway", OrderInGroup = 1, MinPrerequisiteCount = 1, Description = "Whistle Hop Intro", }; _context.Puzzles.Add(lockIntro); Puzzle lockPuzzle = new Puzzle { Name = "...Locked! (whistle stop, lasts 5 minutes)", Event = Event, IsPuzzle = true, SolveValue = 0, Group = "Roger's Railway", OrderInGroup = 2, MinPrerequisiteCount = 1, MinutesOfEventLockout = 5, Description = "Whistle Hop", }; _context.Puzzles.Add(lockPuzzle); Puzzle kitchenSyncPuzzle = new Puzzle { Name = "Kitchen Sync", Event = Event, IsPuzzle = true, SolveValue = 10, Group = "Sync Test", OrderInGroup = 1, MinPrerequisiteCount = 1 }; _context.Puzzles.Add(kitchenSyncPuzzle); Puzzle heatSyncPuzzle = new Puzzle { Name = "Heat Sync", Event = Event, IsPuzzle = true, SolveValue = 10, Group = "Sync Test", OrderInGroup = 2, MinPrerequisiteCount = 1 }; _context.Puzzles.Add(heatSyncPuzzle); Puzzle lipSyncPuzzle = new Puzzle { Name = "Lip Sync", Event = Event, IsPuzzle = true, SolveValue = 10, Group = "Sync Test", OrderInGroup = 3, MinPrerequisiteCount = 1 }; _context.Puzzles.Add(lipSyncPuzzle); Puzzle syncTestMetapuzzle = new Puzzle { Name = "Sync Test", Event = Event, IsPuzzle = true, IsMetaPuzzle = true, SolveValue = 50, Group = "Sync Test", OrderInGroup = 99, MinPrerequisiteCount = 1, MaxAnnotationKey = 400 }; _context.Puzzles.Add(syncTestMetapuzzle); await _context.SaveChangesAsync(); // // Add responses, PARTIAL is a partial, ANSWER is the answer. // _context.Responses.Add(new Response() { Puzzle = easy, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = easy, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = intermediate, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = intermediate, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = hard, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = hard, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = meta, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = meta, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = other, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = other, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = cheat, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = cheat, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = lockIntro, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = lockIntro, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = lockPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = lockPuzzle, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = kitchenSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = kitchenSyncPuzzle, SubmittedText = "SYNC", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = heatSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = heatSyncPuzzle, SubmittedText = "OR", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = lipSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = lipSyncPuzzle, SubmittedText = "SWIM", ResponseText = "Correct!", IsSolution = true }); _context.Responses.Add(new Response() { Puzzle = syncTestMetapuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..." }); _context.Responses.Add(new Response() { Puzzle = syncTestMetapuzzle, SubmittedText = "SYNCORSWIM", ResponseText = "Correct!", IsSolution = true }); string hint1Description = "Tell me about the rabbits, George."; string hint1Content = "O.K. Some day – we’re gonna get the jack together and we’re gonna have a little house and a couple of acres an’ a cow and some pigs and..."; string hint2Description = "Go on... George. How I get to tend the rabbits."; string hint2Content = "Well, we’ll have a big vegetable patch and a rabbit-hutch and chickens."; _context.Hints.Add(new Hint() { Puzzle = easy, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content }); _context.Hints.Add(new Hint() { Puzzle = easy, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content }); _context.Hints.Add(new Hint() { Puzzle = intermediate, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content }); _context.Hints.Add(new Hint() { Puzzle = intermediate, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content }); _context.Hints.Add(new Hint() { Puzzle = hard, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content }); _context.Hints.Add(new Hint() { Puzzle = hard, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content }); _context.Hints.Add(new Hint() { Puzzle = meta, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content }); _context.Hints.Add(new Hint() { Puzzle = meta, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content }); await _context.SaveChangesAsync(); // // Set up prequisite links. // The first two depend on start puzzle, then the third depends on one of the first two, then the meta depends on two of the first three. // _context.Prerequisites.Add(new Prerequisites() { Puzzle = easy, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = intermediate, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = hard, Prerequisite = easy }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = hard, Prerequisite = intermediate }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = easy }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = intermediate }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = meta, Prerequisite = hard }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = other, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = cheat, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = lockIntro, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = lockPuzzle, Prerequisite = lockIntro }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = kitchenSyncPuzzle, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = heatSyncPuzzle, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = lipSyncPuzzle, Prerequisite = start }); _context.Prerequisites.Add(new Prerequisites() { Puzzle = syncTestMetapuzzle, Prerequisite = start }); await _context.SaveChangesAsync(); // // Create puzzle pieces. // _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 0, 1, "xxx xxxx'x x xXxxx!", 3, "What Crocodile Dundee might say")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 1, 2, "xxx xxxx xxxx xxx Xxxxx", 1, "In <i>Hey Diddle Diddle</i>, what the dish did")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 1, 3, "xxXx", 1, "It smells")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 4, "xxX", 4, "You reach things by extending it")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 5, "Xxx xxxxx", 1, "Result of the <i>Exxon Valdez</i> crash")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 6, "xxxxX", 2, "What you make out of turkey drippings")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 3, 7, "xxxXx xxx", 4, "Another name for a slow cooker")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 8, "xXxxxx", 3, "The index is one of them")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 9, "xxxxX", 2, "It can suffer when you play tennis")); _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 10, "xxxxxxX xxxxxxx", 2, "What Chekov asked strangers the location of in Star Trek IV, garnering suspicion due to his Russian accent")); await _context.SaveChangesAsync(); // // Create teams. Can we add players to these? // Team team1 = new Team { Name = "Team Bugs", Event = Event }; _context.Teams.Add(team1); Team team2 = new Team { Name = "Team Babs", Event = Event }; _context.Teams.Add(team2); Team team3 = new Team { Name = "Team Buster", Event = Event }; _context.Teams.Add(team3); Team teamLoneWolf = null; if (AddCreatorToLoneWolfTeam) { teamLoneWolf = new Team { Name = "Lone Wolf", Event = Event }; _context.Teams.Add(teamLoneWolf); } var demoCreatorUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager); if (demoCreatorUser != null) { // // Event admin/author // _context.EventAdmins.Add(new EventAdmins() { Event = Event, Admin = demoCreatorUser }); _context.EventAuthors.Add(new EventAuthors() { Event = Event, Author = demoCreatorUser }); // // Puzzle author (for Thumper module only) // _context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = easy, Author = demoCreatorUser }); _context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = intermediate, Author = demoCreatorUser }); _context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = hard, Author = demoCreatorUser }); _context.PuzzleAuthors.Add(new PuzzleAuthors() { Puzzle = meta, Author = demoCreatorUser }); } // TODO: Files (need to know how to detect whether local blob storage is configured) // Is there a point to adding Feedback or is that quick/easy enough to demo by hand? await _context.SaveChangesAsync(); if (teamLoneWolf != null) { _context.TeamMembers.Add(new TeamMembers() { Team = teamLoneWolf, Member = demoCreatorUser }); } // line up all hints var teams = await _context.Teams.Where((t) => t.Event == Event).ToListAsync(); var hints = await _context.Hints.Where((h) => h.Puzzle.Event == Event).ToListAsync(); var puzzles = await _context.Puzzles.Where(p => p.Event == Event).ToListAsync(); foreach (Team team in teams) { foreach (Hint hint in hints) { _context.HintStatePerTeam.Add(new HintStatePerTeam() { Hint = hint, Team = team }); } foreach (Puzzle puzzle in puzzles) { _context.PuzzleStatePerTeam.Add(new PuzzleStatePerTeam() { PuzzleID = puzzle.ID, TeamID = team.ID }); } } await _context.SaveChangesAsync(); // // Mark the start puzzle as solved if we were asked to. // if (StartTheEvent) { await PuzzleStateHelper.SetSolveStateAsync(_context, Event, start, null, DateTime.UtcNow); } transaction.Commit(); } return(RedirectToPage("./Index")); }
public async Task OnGetAsync(SortOrder?sort) { Sort = sort; var puzzleData = await _context.Puzzles .Where(p => p.Event == Event && p.IsPuzzle) .ToDictionaryAsync(p => p.ID, p => new { p.SolveValue, p.IsCheatCode, p.IsFinalPuzzle }); DateTime submissionEnd = Event.AnswerSubmissionEnd; var stateData = await PuzzleStateHelper.GetSparseQuery(_context, this.Event, null, null) .Where(pspt => pspt.SolvedTime != null && pspt.SolvedTime <= submissionEnd) .Select(pspt => new { pspt.PuzzleID, pspt.TeamID, pspt.SolvedTime }) .ToListAsync(); // Hide disqualified teams from the standings page. var teams = await _context.Teams .Where(t => t.Event == Event && t.IsDisqualified == false) .ToListAsync(); Dictionary <int, TeamStats> teamStats = new Dictionary <int, TeamStats>(teams.Count); foreach (var t in teams) { teamStats[t.ID] = new TeamStats { Team = t, FinalMetaSolveTime = DateTime.MaxValue }; } foreach (var s in stateData) { if (!puzzleData.TryGetValue(s.PuzzleID, out var p) || !teamStats.TryGetValue(s.TeamID, out var ts)) { continue; } ts.Score += p.SolveValue; ts.SolveCount++; if (p.IsCheatCode) { ts.Cheated = true; ts.FinalMetaSolveTime = DateTime.MaxValue; } if (p.IsFinalPuzzle && !ts.Cheated) { ts.FinalMetaSolveTime = s.SolvedTime.Value; } } var teamsFinal = teamStats.Values.OrderBy(t => t.FinalMetaSolveTime).ThenByDescending(t => t.Score).ThenBy(t => t.Team.Name).ToList(); TeamStats prevStats = null; for (int i = 0; i < teamsFinal.Count; i++) { var stats = teamsFinal[i]; if (prevStats == null || stats.FinalMetaSolveTime != prevStats.FinalMetaSolveTime || stats.Score != prevStats.Score) { stats.Rank = i + 1; } else { stats.Rank = prevStats.Rank; } prevStats = stats; } switch (sort) { case SortOrder.RankAscending: break; case SortOrder.RankDescending: teamsFinal.Reverse(); break; case SortOrder.NameAscending: teamsFinal = teamsFinal.OrderBy(ts => ts.Team.Name).ToList(); break; case SortOrder.NameDescending: teamsFinal = teamsFinal.OrderByDescending(ts => ts.Team.Name).ToList(); break; case SortOrder.PuzzlesAscending: teamsFinal = teamsFinal.OrderBy(ts => ts.SolveCount).ThenBy(ts => ts.Rank).ThenBy(ts => ts.Team.Name).ToList(); break; case SortOrder.PuzzlesDescending: teamsFinal = teamsFinal.OrderByDescending(ts => ts.SolveCount).ThenByDescending(ts => ts.Rank).ThenByDescending(ts => ts.Team.Name).ToList(); break; case SortOrder.ScoreAscending: teamsFinal = teamsFinal.OrderBy(ts => ts.Score).ThenBy(ts => ts.Rank).ThenBy(ts => ts.Team.Name).ToList(); break; case SortOrder.ScoreDescending: teamsFinal = teamsFinal.OrderByDescending(ts => ts.Score).ThenByDescending(ts => ts.Rank).ThenByDescending(ts => ts.Team.Name).ToList(); break; case SortOrder.HintsUsedAscending: teamsFinal = teamsFinal.OrderBy(ts => ts.Team.HintCoinsUsed).ThenBy(ts => ts.Rank).ThenBy(ts => ts.Team.Name).ToList(); break; case SortOrder.HintsUsedDescending: teamsFinal = teamsFinal.OrderByDescending(ts => ts.Team.HintCoinsUsed).ThenByDescending(ts => ts.Rank).ThenByDescending(ts => ts.Team.Name).ToList(); break; } this.Teams = teamsFinal; }
public async Task <IActionResult> OnGetAsync(int?refresh) { if (refresh != null) { Refresh = refresh; } // get the puzzles and teams List <PuzzleStats> puzzles; if (EventRole == EventRole.admin) { puzzles = await _context.Puzzles.Where(p => p.Event == Event) .Select(p => new PuzzleStats() { Puzzle = p }) .ToListAsync(); } else { puzzles = await UserEventHelper.GetPuzzlesForAuthorAndEvent(_context, Event, LoggedInUser) .Select(p => new PuzzleStats() { Puzzle = p }) .ToListAsync(); } List <TeamStats> teams = await _context.Teams.Where(t => t.Event == Event) .Select(t => new TeamStats() { Team = t }) .ToListAsync(); // build an ID-based lookup for puzzles and teams Dictionary <int, PuzzleStats> puzzleLookup = new Dictionary <int, PuzzleStats>(); puzzles.ForEach(p => puzzleLookup[p.Puzzle.ID] = p); Dictionary <int, TeamStats> teamLookup = new Dictionary <int, TeamStats>(); teams.ForEach(t => teamLookup[t.Team.ID] = t); // tabulate solve counts and team scores List <PuzzleStatePerTeam> states = await PuzzleStateHelper.GetSparseQuery( _context, Event, null, null, EventRole == EventRole.admin?null : LoggedInUser).ToListAsync(); List <StateStats> stateList = new List <StateStats>(states.Count); foreach (PuzzleStatePerTeam state in states) { // TODO: Is it more performant to prefilter the states if an author, or is this sufficient? if (!puzzleLookup.TryGetValue(state.PuzzleID, out PuzzleStats puzzle) || !teamLookup.TryGetValue(state.TeamID, out TeamStats team)) { continue; } stateList.Add(new StateStats() { Puzzle = puzzle, Team = team, UnlockedTime = state.UnlockedTime, SolvedTime = state.SolvedTime, LockedOut = state.IsEmailOnlyMode }); if (state.SolvedTime != null) { puzzle.SolveCount++; team.SolveCount++; team.Score += puzzle.Puzzle.SolveValue; if (puzzle.Puzzle.IsCheatCode) { team.CheatCodeUsed = true; team.FinalMetaSolveTime = DateTime.MaxValue; } if (puzzle.Puzzle.IsFinalPuzzle && !team.CheatCodeUsed) { team.FinalMetaSolveTime = state.SolvedTime.Value; } } } // sort puzzles by group, then solve count, add the sort index to the lookup // but put non-puzzles to the end puzzles = puzzles.OrderByDescending(p => p.Puzzle.IsPuzzle) .ThenByDescending(p => p.Puzzle.Group) .ThenBy(p => p.SolveCount) .ThenBy(p => p.Puzzle.Name) .ToList(); for (int i = 0; i < puzzles.Count; i++) { puzzles[i].SortOrder = i; } // sort teams by metameta/score, add the sort index to the lookup teams = teams.OrderBy(t => t.FinalMetaSolveTime) .ThenByDescending(t => t.Score) .ThenBy(t => t.Team.Name) .ToList(); for (int i = 0; i < teams.Count; i++) { if (i == 0 || teams[i].FinalMetaSolveTime != teams[i - 1].FinalMetaSolveTime || teams[i].Score != teams[i - 1].Score) { teams[i].Rank = i + 1; } teams[i].SortOrder = i; } // Build the map var stateMap = new StateStats[puzzles.Count, teams.Count]; stateList.ForEach(state => stateMap[state.Puzzle.SortOrder, state.Team.SortOrder] = state); Puzzles = puzzles; Teams = teams; StateMap = stateMap; return(Page()); }
public async Task <IActionResult> OnPostAsync(int puzzleId) { if (!ModelState.IsValid) { return(Page()); } HashSet <string> submissions = new HashSet <string>(); if (DeleteExisting) { Response[] responsesToRemove = await(from Response r in _context.Responses where r.PuzzleID == puzzleId select r).ToArrayAsync(); _context.Responses.RemoveRange(responsesToRemove); } else { string[] responses = await(from Response r in _context.Responses where r.PuzzleID == puzzleId select r.SubmittedText).ToArrayAsync(); foreach (string r in responses) { submissions.Add(r); } } using StringReader isSolutionReader = new StringReader(IsSolution ?? string.Empty); using StringReader submittedTextReader = new StringReader(SubmittedText ?? string.Empty); using StringReader responseTextReader = new StringReader(ResponseText ?? string.Empty); using StringReader noteReader = new StringReader(Note ?? string.Empty); List <Response> newResponses = new List <Response>(); while (true) { string isSolution = isSolutionReader.ReadLine(); string submittedText = submittedTextReader.ReadLine(); string responseText = responseTextReader.ReadLine(); string note = noteReader.ReadLine(); // TODO probably clearer ways to validate but I honestly do not understand how validation works if (submittedText == null) { if (responseText != null) { ModelState.AddModelError("ResponseText", "Unmatched Response without Submission"); } if (isSolution != null) { ModelState.AddModelError("IsSolution", "Unmatched IsSolution without Submission"); } if (note != null) { ModelState.AddModelError("Note", "Unmatched Note without Submission"); } // we're done break; } string submittedTextFormatted = ServerCore.DataModel.Response.FormatSubmission(submittedText); // Ensure that the submission text is unique for this puzzle. if (!submissions.Add(submittedTextFormatted)) { ModelState.AddModelError("SubmittedText", "Submission text is not unique"); break; } if (responseText == null) { ModelState.AddModelError("SubmittedText", "Unmatched Submission without Response"); break; } isSolution = isSolution == null ? string.Empty : isSolution.ToLower(); Response response = new Response() { PuzzleID = puzzleId, SubmittedText = submittedText, ResponseText = responseText, Note = note, IsSolution = isSolution == "y" || isSolution == "yes" || isSolution == "t" || isSolution == "true" || isSolution == "1" }; _context.Responses.Add(response); newResponses.Add(response); } if (!ModelState.IsValid) { return(await OnGetAsync(puzzleId)); } await _context.SaveChangesAsync(); foreach (var response in newResponses) { await PuzzleStateHelper.UpdateTeamsWhoSentResponse(_context, response); } return(RedirectToPage("./Index", new { puzzleid = puzzleId })); }