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 }));
        }
Example #2
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());
        }
Example #3
0
        /// <summary>
        /// Evaulates player submissions then either saves them to the database or returns an error to the caller
        /// </summary>
        public static async Task <SubmissionResponse> EvaluateSubmission(PuzzleServerContext context, PuzzleUser loggedInUser, Event thisEvent, int puzzleId, string submissionText, bool allowFreeformSharing)
        {
            //Query data needed to process submission
            Team team = await UserEventHelper.GetTeamForPlayer(context, thisEvent, loggedInUser);

            Puzzle puzzle = await context.Puzzles.Where(
                (p) => p.ID == puzzleId).FirstOrDefaultAsync();

            PuzzleStatePerTeam puzzleState = await(PuzzleStateHelper
                                                   .GetFullReadOnlyQuery(
                                                       context,
                                                       thisEvent,
                                                       puzzle,
                                                       team))
                                             .FirstAsync();

            List <Puzzle> puzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(context, thisEvent, team).ToListAsync();

            // Return early for cases when there's obviously nothing we can do with the submission
            // The submission text is empty
            if (String.IsNullOrWhiteSpace(submissionText))
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.EmptySubmission
                });
            }

            // The puzzle is locked
            if (puzzle == null || puzzleState.UnlockedTime == null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.PuzzleLocked
                });
            }

            // The user or team isn't known
            if (loggedInUser == null || team == null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Unauthorized
                });
            }

            // The event hasn't started yet
            if (DateTime.UtcNow < thisEvent.EventBegin)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Unauthorized
                });
            }

            // The team is locked out
            if (puzzleState.IsTeamLockedOut || puzzleState.IsEmailOnlyMode)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.TeamLockedOut
                });
            }

            // The puzzle has already been solved
            if (puzzleState.SolvedTime != null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.AlreadySolved
                });
            }

            // The team is under a global lockout
            if (puzzlesCausingGlobalLockout.Count != 0 && !puzzlesCausingGlobalLockout.Contains(puzzle))
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.TeamLockedOut
                });
            }

            List <SubmissionView> submissionViews = await(from sub in context.Submissions
                                                          join user in context.PuzzleUsers on sub.Submitter equals user
                                                          join r in context.Responses on sub.Response equals r into responses
                                                          from response in responses.DefaultIfEmpty()
                                                          where sub.Team == team &&
                                                          sub.Puzzle == puzzle
                                                          orderby sub.TimeSubmitted
                                                          select new SubmissionView()
            {
                Submission      = sub,
                Response        = response,
                SubmitterName   = user.Name,
                FreeformReponse = sub.FreeformResponse,
                IsFreeform      = puzzle.IsFreeform
            }).ToListAsync();

            List <Submission> submissions = new List <Submission>(submissionViews.Count);

            foreach (SubmissionView submissionView in submissionViews)
            {
                submissions.Add(submissionView.Submission);
            }

            // The submission is a duplicate
            bool duplicateSubmission = (from sub in submissions
                                        where sub.SubmissionText == Response.FormatSubmission(submissionText)
                                        select sub).Any();

            if (duplicateSubmission)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.DuplicateSubmission
                });
            }

            // 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 = 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,
                                                           thisEvent,
                                                           submission.Puzzle,
                                                           submission.Team,
                                                           submission.TimeSubmitted);
            }
            else if (!puzzle.IsFreeform && submission.Response == null && thisEvent.IsAnswerSubmissionActive)
            {
                // We also determine if the puzzle should be set to email-only mode.
                if (IsPuzzleSubmissionLimitReached(
                        thisEvent,
                        submissions,
                        puzzleState))
                {
                    await PuzzleStateHelper.SetEmailOnlyModeAsync(context,
                                                                  thisEvent,
                                                                  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,
                                                          $"{thisEvent.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(
                        thisEvent,
                        submissions,
                        puzzleState);

                    if (lockoutExpiryTime != null)
                    {
                        await PuzzleStateHelper.SetLockoutExpiryTimeAsync(context,
                                                                          thisEvent,
                                                                          submission.Puzzle,
                                                                          submission.Team,
                                                                          lockoutExpiryTime);
                    }
                }
            }

            context.Submissions.Add(submission);
            await context.SaveChangesAsync();

            // Send back responses for cases where the database has been updated
            // Correct response
            if (submission.Response != null && submission.Response.IsSolution)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Correct, CompleteResponse = submission.Response
                });
            }

            // Freeform response
            if (puzzle.IsFreeform)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Freeform, FreeformResponse = submission.FreeformResponse
                });
            }

            // Partial response
            if (submission.Response != null && !submission.Response.IsSolution)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Partial, CompleteResponse = submission.Response
                });
            }

            // Default to incorrect
            return(new SubmissionResponse()
            {
                ResponseCode = SubmissionResponseCode.Incorrect
            });
        }