Beispiel #1
0
        // Go through list of puzzles and compute the max number of hints addded to any particular puzzle.
        // Used to write out the CSV file with enough commas.
        private int computeMaxHints()
        {
            int max = 0;

            foreach (var kpr in puzzles)
            {
                PuzzleInfo pi = kpr.Value;
                int        n  = pi.responses.Count;
                if (n > max)
                {
                    max = n;
                }
            }
            return(max);
        }
Beispiel #2
0
        /// <summary>
        /// This is used for debugging purposes - be sure NOT to write out the decrypted
        /// data file in the production version of the app! The directory is the same as where the
        /// data file was loaded, and the name is hardcoded.
        /// </summary>
        /// <param name="encrypted"></param>
        public void writeCsvFile(String basePath, Boolean encrypted)
        {
            const string ENCRYPTED_PROPERTY = "encrypted";
            String       fileName           = "data-out-FREETEXT.csv";

            if (encrypted)
            {
                fileName = "data-out-ENCRYPTED.csv";
            }
            else
            {
                Trace.WriteLine("WARNING: Writing UNENCRYPTED data to file!");
            }
            String pathName = basePath + @"\" + fileName;

            using (TextWriter tw = new StreamWriter(pathName))
            {
                // Write header properties
                tw.Write("POD,Version:1.0");
                foreach (KeyValuePair <String, String> kvp in properties)
                {
                    String k = kvp.Key;
                    if (!k.Equals("version") && !k.Equals("pod"))
                    {
                        if (!k.Equals(ENCRYPTED_PROPERTY)) // we selectively add it later
                        {
                            String s = (kvp.Value.Length == 0) ? "" : ":" + kvp.Value;
                            appendCell(tw, k + s);
                        }
                    }
                }
                int nProps = properties.Count;

                // Add the "encrypted" property if we need to...
                if (encrypted)
                {
                    appendCell(tw, ENCRYPTED_PROPERTY);
                    nProps++;
                }

                int maxHints = computeMaxHints();
                int maxCols  = maxHints + 3; // 3 for ID, Answer and Name
                if (maxCols < nProps)
                {
                    maxCols = nProps;
                }
                else
                {
                    writeCommas(tw, maxCols - nProps);
                }
                tw.WriteLine("");


                // Write Table headers
                tw.Write("Id,Name,Answer");
                for (int i = 0; i < maxHints; i++)
                {
                    tw.Write(",Hint" + (i + 1));
                }
                int colsWritten = maxHints + 3; // 3 for ID, Name and Answer
                if (colsWritten < maxCols)
                {
                    writeCommas(tw, maxCols - colsWritten);
                }
                tw.WriteLine("");

                // Write Puzzle rows
                foreach (String id in puzzleIDs)
                {
                    PuzzleInfo pi = puzzles[id];

                    // Write ID and Name
                    tw.Write(id);
                    appendCell(tw, pi.puzzleName);

                    // Write responses... (Answer comes first
                    foreach (var pr in pi.responses)
                    {
                        String s = pr.pattern + (pr.response.Length == 0 ? "" : ":" + compressAliases(pr.response));
                        if (encrypted)
                        {
                            s = "[" + endecrypt(id, s, true) + "]"; // true == encrypt
                        }
                        appendCell(tw, s);
                    }
                    colsWritten = 2 + pi.responses.Count; // 2 for ID and Name
                    if (colsWritten < maxCols)
                    {
                        writeCommas(tw, maxCols - colsWritten);
                    }
                    tw.WriteLine("");
                }
                tw.Flush();
                tw.Close();
            }
        }
Beispiel #3
0
        /// <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));
                }
            }
        }
Beispiel #4
0
        /// <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);
        }