public async Task <IActionResult> OnGetAsync() { List <Puzzle> puzzles = await PuzzleHelper.GetPuzzles(_context, Event, LoggedInUser, EventRole); Dictionary <int, List <string> > puzzleAuthors = await(from author in _context.PuzzleAuthors where author.Puzzle.Event == Event group author by author.PuzzleID into authorList select new { Puzzle = authorList.Key, Authors = (from a in authorList select a.Author.Name).ToList() }).ToDictionaryAsync(x => x.Puzzle, x => x.Authors); Dictionary <int, ContentFile> puzzleFiles = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Puzzle select file).ToDictionaryAsync(file => file.PuzzleID); Dictionary <int, ContentFile> puzzleAnswers = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Answer select file).ToDictionaryAsync(file => file.PuzzleID); Dictionary <int, List <string> > puzzlePrereqs = await(from prerequisite in _context.Prerequisites where prerequisite.Puzzle.Event == Event group prerequisite by prerequisite.PuzzleID into prereqs select new { Puzzle = prereqs.Key, Prereqs = (from p in prereqs orderby p.Prerequisite.Name select p.Prerequisite.Name).ToList() }).ToDictionaryAsync(x => x.Puzzle, x => x.Prereqs); Dictionary <int, ResponseData> puzzleResponses = await(from response in _context.Responses where response.Puzzle.Event == Event group response by response.PuzzleID into responseList select new { Puzzle = responseList.Key, Responses = new ResponseData { ResponseCount = responseList.Count(), HasAnswer = (from r in responseList where r.IsSolution select r).Count() > 0 } }).ToDictionaryAsync(x => x.Puzzle, x => x.Responses); Dictionary <int, IEnumerable <int> > puzzleHints = await(from hint in _context.Hints where hint.Puzzle.Event == Event group hint by hint.Puzzle.ID into hints select new { Puzzle = hints.Key, Hints = (from h in hints select h.Cost) }).ToDictionaryAsync(x => x.Puzzle, x => x.Hints); PuzzleData = new List <PuzzleView>(); foreach (Puzzle puzzle in puzzles) { puzzleAuthors.TryGetValue(puzzle.ID, out List <string> authors); puzzleFiles.TryGetValue(puzzle.ID, out ContentFile puzzleFile); puzzleAnswers.TryGetValue(puzzle.ID, out ContentFile puzzleAnswer); puzzlePrereqs.TryGetValue(puzzle.ID, out List <string> prereqs); puzzleResponses.TryGetValue(puzzle.ID, out ResponseData responses); puzzleHints.TryGetValue(puzzle.ID, out IEnumerable <int> hints); int totalHintCostThisPuzzle = 0; int hintsCountThisPuzzle = 0; if (hints != null) { int totalDiscount = 0; hintsCountThisPuzzle = hints.Count(); foreach (int cost in hints) { totalDiscount = Math.Min(totalDiscount, cost); } // totalDiscount is 0 or negative. Start with that cost (flipped to positive // as it must be paid in order to reduce the cost of the others. totalHintCostThisPuzzle = -totalDiscount; foreach (int cost in hints) { // Negative cost hints will be ignored because Max(0,negative) is 0. // Positive cost hints will only be counted for the cost above the discount. // (The discount is counted against each other hint.) totalHintCostThisPuzzle += Math.Max(0, cost + totalDiscount); } } PuzzleData.Add(new PuzzleView() { Puzzle = puzzle, Authors = authors != null ? string.Join(", ", authors) : "", PuzzleFile = puzzleFile, AnswerFile = puzzleAnswer, Prerequisites = prereqs != null ? string.Join(", ", prereqs) : "", PrerequisitesCount = prereqs != null ? prereqs.Count() : 0, Responses = responses != null ? responses : new ResponseData { ResponseCount = 0, HasAnswer = false }, Hints = hintsCountThisPuzzle, TotalHintCost = totalHintCostThisPuzzle }); TotalHints += hintsCountThisPuzzle; TotalHintCost += totalHintCostThisPuzzle; } return(Page()); }
public async Task <IActionResult> OnPostUnlock(int puzzleId, int unlockId) { Puzzle puzzle = await(from puzz in _context.Puzzles where puzz.ID == puzzleId select puzz).FirstOrDefaultAsync(); if (puzzle == null) { return(NotFound()); } // Restrict this page to whistle stop non-puzzles if (puzzle.MinutesOfEventLockout == 0 || puzzle.IsPuzzle) { return(NotFound()); } Team team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser); using (var transaction = _context.Database.BeginTransaction()) { var puzzleStateQuery = PuzzleStateHelper.GetFullReadOnlyQuery(_context, Event, null, team); PuzzleStatePerTeam prereqState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == puzzle.ID select pspt).FirstOrDefaultAsync(); if (prereqState == null) { return(NotFound()); } // Only move forward if the prereq is open and unsolved if (prereqState.UnlockedTime == null || prereqState.SolvedTime != null) { return(NotFound("Your team has already chosen and can't choose again")); } PuzzleStatePerTeam unlockState = await(from pspt in puzzleStateQuery where pspt.PuzzleID == unlockId select pspt).FirstOrDefaultAsync(); if (unlockState == null) { return(NotFound()); } // The chosen puzzle must be locked (and unsolved) if (unlockState.UnlockedTime != null || unlockState.SolvedTime != null) { return(NotFound("You've already chosen this puzzle")); } // Ensure the puzzle is actually one of the unlock options Prerequisites prereq = await(from pre in _context.Prerequisites where pre.PrerequisiteID == puzzleId && pre.PuzzleID == unlockId select pre).FirstOrDefaultAsync(); if (prereq == null) { return(NotFound()); } await PuzzleStateHelper.SetSolveStateAsync(_context, Event, puzzle, team, DateTime.UtcNow); await PuzzleStateHelper.SetUnlockStateAsync(_context, Event, unlockState.Puzzle, team, DateTime.UtcNow); transaction.Commit(); } Puzzle puzzleToUnlock = await(from p in _context.Puzzles where p.ID == unlockId select p).FirstOrDefaultAsync(); string puzzleUrl; if (puzzleToUnlock.CustomURL != null) { puzzleUrl = PuzzleHelper.GetFormattedUrl(puzzleToUnlock, Event.ID); } else { puzzleUrl = puzzleToUnlock.PuzzleFile.UrlString; } return(Redirect(puzzleUrl)); }
public async Task <IActionResult> OnPostImportAsync() { // the BindProperty only binds the event ID, let's get the rest if (await _context.Events.Where((e) => e.ID == ImportEventID).FirstOrDefaultAsync() == null) { return(NotFound()); } // verify that we're an admin of the import event. current event administratorship is already validated. if (!await _context.EventAdmins.Where(ea => ea.Event.ID == ImportEventID && ea.Admin == LoggedInUser).AnyAsync()) { return(Forbid()); } var sourceEventAuthors = await _context.EventAuthors.Where((a) => a.Event.ID == ImportEventID).ToListAsync(); var sourcePuzzles = await _context.Puzzles.Where((p) => p.Event.ID == ImportEventID).ToListAsync(); // TODO: replace this with a checkbox and sufficient danger warnings about duplicate titles bool deletePuzzleIfPresent = true; using (var transaction = _context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable)) { // Step 1: Make sure all authors exist foreach (var sourceEventAuthor in sourceEventAuthors) { var destEventAuthor = await _context.EventAuthors.Where((e) => e.Event == Event && e.Author == sourceEventAuthor.Author).FirstOrDefaultAsync(); if (destEventAuthor == null) { destEventAuthor = new EventAuthors(sourceEventAuthor); destEventAuthor.Event = Event; _context.EventAuthors.Add(destEventAuthor); } } // Step 2: Make sure all puzzles exist Dictionary <int, Puzzle> puzzleCloneMap = new Dictionary <int, Puzzle>(); foreach (var sourcePuzzle in sourcePuzzles) { // delete the puzzle if it exists if (deletePuzzleIfPresent) { foreach (Puzzle p in _context.Puzzles.Where((p) => p.Event == Event && p.Name == sourcePuzzle.Name)) { await PuzzleHelper.DeletePuzzleAsync(_context, p); } } var destPuzzle = new Puzzle(sourcePuzzle); destPuzzle.Event = Event; puzzleCloneMap[sourcePuzzle.ID] = destPuzzle; _context.Puzzles.Add(destPuzzle); } // Step 3: Save so that all our new objects have valid IDs await _context.SaveChangesAsync(); // Step 4: Ancillary tables referring to puzzles foreach (var sourcePuzzle in sourcePuzzles) { // PuzzleAuthors foreach (PuzzleAuthors sourcePuzzleAuthor in _context.PuzzleAuthors.Where((p) => p.Puzzle == sourcePuzzle)) { var destPuzzleAuthor = new PuzzleAuthors(sourcePuzzleAuthor); destPuzzleAuthor.Puzzle = puzzleCloneMap[sourcePuzzleAuthor.Puzzle.ID]; _context.PuzzleAuthors.Add(destPuzzleAuthor); } // Responses foreach (Response sourceResponse in _context.Responses.Where((r) => r.Puzzle == sourcePuzzle)) { var destResponse = new Response(sourceResponse); destResponse.Puzzle = puzzleCloneMap[sourceResponse.Puzzle.ID]; _context.Responses.Add(destResponse); } // Prerequisites foreach (Prerequisites sourcePrerequisite in _context.Prerequisites.Where((r) => r.Puzzle == sourcePuzzle)) { var destPrerequisite = new Prerequisites(sourcePrerequisite); destPrerequisite.Puzzle = puzzleCloneMap[sourcePrerequisite.Puzzle.ID]; destPrerequisite.Prerequisite = puzzleCloneMap[sourcePrerequisite.Prerequisite.ID]; _context.Prerequisites.Add(destPrerequisite); } // Hints foreach (Hint sourceHint in _context.Hints.Where((h) => h.Puzzle == sourcePuzzle)) { var destHint = new Hint(sourceHint); destHint.Puzzle = puzzleCloneMap[sourceHint.Puzzle.ID]; _context.Hints.Add(destHint); foreach (Team team in _context.Teams.Where(t => t.Event == Event)) { _context.HintStatePerTeam.Add(new HintStatePerTeam() { Hint = destHint, TeamID = team.ID }); } } // PuzzleStatePerTeam foreach (Team team in _context.Teams.Where(t => t.Event == Event)) { int newPuzzleId = puzzleCloneMap[sourcePuzzle.ID].ID; bool hasPuzzleStatePerTeam = await(from pspt in _context.PuzzleStatePerTeam where pspt.PuzzleID == newPuzzleId && pspt.TeamID == team.ID select pspt).AnyAsync(); if (!hasPuzzleStatePerTeam) { PuzzleStatePerTeam newPspt = new PuzzleStatePerTeam() { TeamID = team.ID, PuzzleID = newPuzzleId }; _context.PuzzleStatePerTeam.Add(newPspt); } } // ContentFiles foreach (ContentFile contentFile in _context.ContentFiles.Where((c) => c.Puzzle == sourcePuzzle)) { ContentFile newFile = new ContentFile(contentFile); newFile.Event = Event; newFile.Puzzle = puzzleCloneMap[contentFile.Puzzle.ID]; newFile.Url = await FileManager.CloneBlobAsync(contentFile.ShortName, Event.ID, contentFile.Url); _context.ContentFiles.Add(newFile); } // Pieces foreach (Piece piece in _context.Pieces.Where((p) => p.Puzzle == sourcePuzzle)) { Piece newPiece = new Piece(piece); newPiece.Puzzle = puzzleCloneMap[piece.PuzzleID]; newPiece.PuzzleID = puzzleCloneMap[piece.PuzzleID].ID; // unsure why I need this line for pieces but not others <shrug/> _context.Pieces.Add(newPiece); } } // Step 5: Final save and commit await _context.SaveChangesAsync(); transaction.Commit(); } return(RedirectToPage("./Details")); }
public async Task <IActionResult> OnPostImportAsync() { // the BindProperty only binds the event ID, let's get the rest if (await _context.Events.Where((e) => e.ID == ImportEventID).FirstOrDefaultAsync() == null) { return(NotFound()); } // verify that we're an admin of the import event. current event administratorship is already validated. if (!await _context.EventAdmins.Where(ea => ea.Event.ID == ImportEventID && ea.Admin == LoggedInUser).AnyAsync()) { return(Forbid()); } var sourceEventAuthors = await _context.EventAuthors.Where((a) => a.Event.ID == ImportEventID).ToListAsync(); var sourcePuzzles = await _context.Puzzles.Where((p) => p.Event.ID == ImportEventID).ToListAsync(); // TODO: replace this with a checkbox and sufficient danger warnings about duplicate titles bool deletePuzzleIfPresent = true; using (var transaction = _context.Database.BeginTransaction()) { // Step 1: Make sure all authors exist foreach (var sourceEventAuthor in sourceEventAuthors) { var destEventAuthor = await _context.EventAuthors.Where((e) => e.Event == Event && e.Author == sourceEventAuthor.Author).FirstOrDefaultAsync(); if (destEventAuthor == null) { destEventAuthor = new EventAuthors(sourceEventAuthor); destEventAuthor.Event = Event; _context.EventAuthors.Add(destEventAuthor); } } // Step 2: Make sure all puzzles exist Dictionary <int, Puzzle> puzzleCloneMap = new Dictionary <int, Puzzle>(); foreach (var sourcePuzzle in sourcePuzzles) { // delete the puzzle if it exists if (deletePuzzleIfPresent) { foreach (Puzzle p in _context.Puzzles.Where((p) => p.Event == Event && p.Name == sourcePuzzle.Name)) { await PuzzleHelper.DeletePuzzleAsync(_context, p); } } var destPuzzle = new Puzzle(sourcePuzzle); destPuzzle.Event = Event; puzzleCloneMap[sourcePuzzle.ID] = destPuzzle; _context.Puzzles.Add(destPuzzle); } // Step 3: Save so that all our new objects have valid IDs await _context.SaveChangesAsync(); // Step 4: Ancillary tables referring to puzzles foreach (var sourcePuzzle in sourcePuzzles) { // PuzzleAuthors foreach (PuzzleAuthors sourcePuzzleAuthor in _context.PuzzleAuthors.Where((p) => p.Puzzle == sourcePuzzle)) { var destPuzzleAuthor = new PuzzleAuthors(sourcePuzzleAuthor); destPuzzleAuthor.Puzzle = puzzleCloneMap[sourcePuzzleAuthor.Puzzle.ID]; _context.PuzzleAuthors.Add(destPuzzleAuthor); } // Responses foreach (Response sourceResponse in _context.Responses.Where((r) => r.Puzzle == sourcePuzzle)) { var destResponse = new Response(sourceResponse); destResponse.Puzzle = puzzleCloneMap[sourceResponse.Puzzle.ID]; _context.Responses.Add(destResponse); } // Prerequisites foreach (Prerequisites sourcePrerequisite in _context.Prerequisites.Where((r) => r.Puzzle == sourcePuzzle)) { var destPrerequisite = new Prerequisites(sourcePrerequisite); destPrerequisite.Puzzle = puzzleCloneMap[sourcePrerequisite.Puzzle.ID]; destPrerequisite.Prerequisite = puzzleCloneMap[sourcePrerequisite.Prerequisite.ID]; _context.Prerequisites.Add(destPrerequisite); } // Hints foreach (Hint sourceHint in _context.Hints.Where((h) => h.Puzzle == sourcePuzzle)) { var destHint = new Hint(sourceHint); destHint.Puzzle = puzzleCloneMap[sourceHint.Puzzle.ID]; _context.Hints.Add(destHint); } // TODO: ContentFiles } // Step 5: Final save and commit await _context.SaveChangesAsync(); transaction.Commit(); } return(RedirectToPage("./Index")); }
public async Task <IActionResult> OnGetAsync() { List <Puzzle> puzzles = await PuzzleHelper.GetPuzzles(_context, Event, LoggedInUser, EventRole); ILookup <int, string> puzzleAuthors = (await(from author in _context.PuzzleAuthors where author.Puzzle.Event == Event select author).ToListAsync()).ToLookup(author => author.PuzzleID, author => author.Author.Name); Dictionary <int, ContentFile> puzzleFiles = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Puzzle select file).ToDictionaryAsync(file => file.PuzzleID); Dictionary <int, ContentFile> puzzleAnswers = await(from file in _context.ContentFiles where file.Event == Event && file.FileType == ContentFileType.Answer select file).ToDictionaryAsync(file => file.PuzzleID); ILookup <int, string> puzzlePrereqs = (await(from prerequisite in _context.Prerequisites where prerequisite.Puzzle.Event == Event select prerequisite).ToListAsync()).ToLookup(prerequisite => prerequisite.PuzzleID, prerequisite => prerequisite.Prerequisite.Name); HashSet <int> puzzlesWithSolutions = (await(from response in _context.Responses where response.Puzzle.Event == Event && response.IsSolution select response.PuzzleID).ToListAsync()).ToHashSet(); Dictionary <int, int> puzzleResponses = await(from response in _context.Responses where response.Puzzle.Event == Event group response by response.PuzzleID into responseList select new { Puzzle = responseList.Key, ResponseCount = responseList.Count() }).ToDictionaryAsync(x => x.Puzzle, x => x.ResponseCount); ILookup <int, int> puzzleHints = (await(from hint in _context.Hints where hint.Puzzle.Event == Event select new { PuzzleId = hint.PuzzleID, Cost = hint.Cost }).ToListAsync()).ToLookup(hint => hint.PuzzleId, hint => hint.Cost); PuzzleData = new List <PuzzleView>(); foreach (Puzzle puzzle in puzzles) { IEnumerable <string> authors = puzzleAuthors[puzzle.ID]; puzzleFiles.TryGetValue(puzzle.ID, out ContentFile puzzleFile); puzzleAnswers.TryGetValue(puzzle.ID, out ContentFile puzzleAnswer); IEnumerable <string> prereqs = puzzlePrereqs[puzzle.ID]; puzzleResponses.TryGetValue(puzzle.ID, out int responses); int totalHintCostThisPuzzle = 0; int hintsCountThisPuzzle = 0; if (puzzleHints.Contains(puzzle.ID)) { IEnumerable <int> hints = puzzleHints[puzzle.ID]; int totalDiscount = 0; hintsCountThisPuzzle = hints.Count(); // positive hints cost what they say they cost. // negative hints apply discounts to each other, find the most negative hint. foreach (int cost in hints) { totalDiscount = Math.Min(totalDiscount, cost); totalHintCostThisPuzzle += Math.Max(0, cost); } // add the total available discount to the cost. totalHintCostThisPuzzle += Math.Abs(totalDiscount); } PuzzleData.Add(new PuzzleView() { Puzzle = puzzle, Authors = authors != null ? string.Join(", ", authors) : "", PuzzleFile = puzzleFile, AnswerFile = puzzleAnswer, Prerequisites = prereqs != null ? string.Join(", ", prereqs.OrderBy(prereq => prereq)) : "", PrerequisitesCount = prereqs != null ? prereqs.Count() : 0, Responses = new ResponseData { ResponseCount = responses, HasAnswer = puzzlesWithSolutions.Contains(puzzle.ID) }, Hints = hintsCountThisPuzzle, TotalHintCost = totalHintCostThisPuzzle }); TotalHints += hintsCountThisPuzzle; TotalHintCost += totalHintCostThisPuzzle; } return(Page()); }