private void uxDisplayResponse(PuzzleResponse pr) { Color c = this.color_IncorrectAnswer; if (pr.type == PuzzleResponse.ResponseType.Correct) { c = this.color_CorrectAnswer; } else if (pr.type == PuzzleResponse.ResponseType.AskLater) { c = this.color_DelayedAnswer; } this.responseRichTextBox.ForeColor = c; this.responseRichTextBox.Text = pr.response; this.oracleButton.Hide(); this.responsePanel.Show(); }
private void button1_Click(object sender, EventArgs e) { Debug.WriteLine("Verify button clicked!"); uxResetIdleTimer(); String id = this.idTextBox.Text; String answer = this.answerTextBox.Text; // Let's ask the oracle! if (id.Length > 0) { PuzzleResponse pr = oracle.checkSolution(id, answer); try { oracleLogger.logSolveAttempt(id, answer, pr); uxDisplayResponse(pr); } catch (ApplicationException ex) { handleFatalError(ex); } } }
public void logSolveAttempt(String puzzleId, String attemptedSolution, PuzzleResponse response) { // We log the normalized attempt so that it doesn't have extraneous characters. attemptedSolution = PuzzleOracle.normalizeSolution(attemptedSolution); String responseCode = "INVALID"; switch (response.type) { case PuzzleResponse.ResponseType.AskLater: responseCode = "BLACKLISTED"; break; case PuzzleResponse.ResponseType.Correct: responseCode = "CORRECT"; break; case PuzzleResponse.ResponseType.Incorrect: responseCode = "INCORRECT"; // INCORRET means it matched a hint. break; case PuzzleResponse.ResponseType.NotFound: responseCode = "NOTFOUND"; break; default: responseCode = "UNRECOGNIZED_CODE"; break; } // Encrypt... String customizer = teamId + puzzleId; // Important - this is the customizer format used for encryption. responseCode = CryptoHelper.simpleEncryptDecrypt(LOG_PASSWORD, customizer, LOG_ENCRYPT_CHARS, responseCode, true); attemptedSolution = CryptoHelper.simpleEncryptDecrypt(LOG_PASSWORD, customizer, LOG_ENCRYPT_CHARS, attemptedSolution, true); rawLog(puzzleId, responseCode, attemptedSolution); }
/// <summary> /// Adds all puzzles in the specified spreadsheet. Individual puzzles may not /// be added for a variety of reasons including: /// - Duplicate Puzzle ID (only first one gets added) /// - Empty answer field. /// - Invalid REGEX field in answer or hints. /// Errors are reported as Trace output. /// TODO: Have a systematic way to report skipped puzzles. /// </summary> /// <param name="sr"></param> private void addPuzzles(SimpleSpreadsheetReader sr) { // We expect that the first row contains the signature ("POD") followed by "version:1.0" followed by // additional properties (which we ignore for now) const int HEADER_ROWS = 2; const int MIN_COLS = 3; const String FILE_SIGNATURE = "POD"; const String PROP_ENCRYPTED = "encrypted"; int sheet = 0; bool encrypted = false; // whether answers and hints are encrypted or not. int numRows = sr.getNumRows(sheet); int numCols = sr.getNumCols(sheet); if (numRows < HEADER_ROWS || numCols < MIN_COLS) { ErrorReport.logError("Puzzle data spreadsheet is too small!"); throw new ArgumentException(); } String[] propertyRow = sr.getRowCells(0, 0, numCols - 1, sheet); String[] header = sr.getRowCells(1, 0, numCols - 1, sheet); // We expect the first property cell to be POD (all caps) if (!Utils.stripEndBlanks(propertyRow[0]).Equals(FILE_SIGNATURE)) { ErrorReport.logError("Puzzle data spreadsheet has an invalid/missing signature."); throw new ArgumentException(); } // Read rest of properties readProperties(propertyRow); // Check if answer keys are encrypted. if (properties.ContainsKey(PROP_ENCRYPTED)) { encrypted = true; } this.sourceIsEncrypted = encrypted; // We expect the first header cell to be "id" String headerId = Utils.stripEndBlanks(header[0]); if (!headerId.Equals(SPREADSHEET_LABEL_ID, StringComparison.CurrentCultureIgnoreCase)) { ErrorReport.logError("Puzzle data spreadsheet does not have the ID field."); throw new ArgumentException(); } int startRow = HEADER_ROWS; // First row of puzzle data int startCol = 0; // First col of puzzle data //int puzzleCount = numRows - HEADER_ROWS; // could be zero; it's valid to have 0 puzzles. for (int i = startRow; i < numRows; i++) { String[] sRow = sr.getRowCells(i, startCol, numCols - 1, sheet); const String REGEX_ID = @"^[0-9][0-9][0-9]$"; // For now, IDs must be 3-digit numbers. String id = Utils.stripBlanks(sRow[0]); if (!Regex.IsMatch(id, REGEX_ID)) { Trace.WriteLine(String.Format("Skipping row {0}: invalid ID", i)); continue; } // We got the ID, if needed decrypt remaining fields after Name if (encrypted) { decryptCells(id, sRow, 2, numCols - 1); // 2 == skip Id and Name. False == descrypt } // Now let's get the remaining columns. First, get the first two: Name and Answer. String name = Utils.stripEndBlanks(sRow[1]); String answer = Utils.stripEndBlanks(sRow[2]); // Neither should be blank. if (name.Equals("") || answer.Equals("")) { Trace.WriteLine(String.Format("Skipping row {0}: blank Name or Answer", i)); continue; } PuzzleResponse pAnswerResponse = buildPuzzleResponse(answer, PuzzleResponse.ResponseType.Correct); if (pAnswerResponse == null) { Trace.WriteLine(String.Format("Skipping row {0}: Invalid Answer", i)); continue; } PuzzleInfo pi = new PuzzleInfo(id, name); pi.addResponse(pAnswerResponse); // Add hints, if any... for (int j = 3; j < sRow.Length; j++) { String field = Utils.stripEndBlanks(sRow[j]); if (field.Length > 0) { PuzzleResponse pr = buildPuzzleResponse(field, PuzzleResponse.ResponseType.Incorrect); if (pr == null) { Trace.WriteLine(String.Format("Ignoring hint {0} on row {1}: Invalid hint content", j, i)); continue; } pi.addResponse(pr); } } try { puzzles.Add(id, pi); puzzleIDs.Add(id); Trace.WriteLine(String.Format("Adding row {0}: Puzzle ID {1}, Answer {2}", i, id, answer)); } catch (ArgumentException) { Trace.WriteLine(String.Format("Ignoring row {0}: Duplicate ID {1}", i, id)); } } }
/// <summary> /// Constructs a response for the user-generated solution. puzzleId /// MUST reference a valid puzzle else a KeyNotFound exception is /// thrown (call tryGetName first if not sure). /// </summary> /// <param name="puzzleId"></param> /// <param name="solution"></param> /// <returns></returns> public PuzzleResponse checkSolution(string puzzleId, string solution) { // Lookup puzzle... solution = normalizeSolution(solution); PuzzleInfo pi = puzzles[puzzleId]; PuzzleResponse pr = null; // Check blacklist state (<0 means we're ok) int delay = pi.blacklister.submitDelay; // Have we solved this before? Boolean alreadySolved = pi.puzzleSolved; // If we are not already solved, and we are blacklisted, we return a special "try later" message. if (!alreadySolved && delay > 0) { String sResponse; // Blacklisted! Boolean permanentlyBlacklisted = delay == Blacklister.BLACKLIST_FOREVER_TIME; if (permanentlyBlacklisted) { sResponse = PERMANENT_BLACKLISTED_RESPONSE; } else { String sDelay = delay + " seconds"; if (delay > 60) { int minutes = delay / 60; int seconds = delay % 60; sDelay = minutes + " minute" + ((minutes == 1) ? "" : "s"); if (seconds > 0) { sDelay += " and " + seconds + " seconds"; } } sResponse = String.Format(BLACKLISTED_RESPONSE, sDelay, pi.puzzleId); } pr = new PuzzleResponse(solution, PuzzleResponse.ResponseType.AskLater, sResponse); return(pr); // ***************** EARLY RETURN ******************* } pr = pi.matchResponse(solution); if (pr == null) { pr = new PuzzleResponse(solution, PuzzleResponse.ResponseType.NotFound, GENERIC_INCORRECT_RESPONSE); } pi.blacklister.registerSubmission(); // If already solved, but solution is not correct, we put a special message. if (!alreadySolved) { pi.puzzleSolved = (pr.type == PuzzleResponse.ResponseType.Correct); } else if (pr.type != PuzzleResponse.ResponseType.Correct) { // Puzzle has been solved before but there is a new, incorrect submission. We give a helpful message to the user. pr = new PuzzleResponse(solution, pr.type, INCORRECT_BUT_PUZZLE_ANSWERED_BEFORE); } return(pr); }
public void addResponse(PuzzleResponse pr) { responses.Add(pr); }