Пример #1
0
        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;
        }
Пример #3
0
        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);
 }
Пример #7
0
        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());
        }
Пример #10
0
        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)";
                }
            }
        }
Пример #11
0
        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();
        }
Пример #13
0
        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;
        }
Пример #14
0
        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);
        }
Пример #15
0
        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;
        }
Пример #17
0
        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);
                    }
                }
            }
        }
Пример #19
0
        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 }));
        }
Пример #20
0
        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;
        }
Пример #21
0
        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());
        }
Пример #22
0
        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));
        }
Пример #23
0
        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;
        }
Пример #25
0
        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();
        }
Пример #26
0
        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;
                }
            }
        }
Пример #27
0
        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"));
        }
Пример #28
0
        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;
        }
Пример #29
0
        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 }));
        }