DEV Community

CBanks901
CBanks901

Posted on • Updated on

Codebreak simulation

I'd like to write about a console application that I wrote in Visual Studio in C++ called CodeBreaker. This is a simple password game application that allows users to guess up to 8 times a 4 code sequence. Besides the code limit, the additional rules are that every unique code will not have repeated numbers (so no 9912), and each code in the list will be different.

The data I used are as follows:

  • Integer numberlist with a size of ten to hold all the numbers
  • A integery pointer array called secretcode that will hold the codes until the programs termination, use new for dynamic memory allocation
  • A char called repeat which holds the users decision upon success or failure of code cracking
  • A custom struct called InfoStruct which holds all the main data necessary for the program, as well as a global instance of it so it can be changed when necessary
  • A function called RandomKeySelection which generates the secret code when called.
  • A function called GuessSequence which is what handles the execution and termination of the programming

I'd first like to review the InfoStruct that was created. It looks like the following:

struct InfoStruct
{
    string codecopy, usercode;                          // Codecopy is intended to be a string copy of the actual code, and usercode a copy of the users guess.
    int userkeycounter, correctdigits, findindex;       // keycounter is for tracking input while entered, correctdigits is for number of correct digits, and findindex is for the 
                                                        // string based on the actual code (will be used to determine if what the user entered is actually in the code)
    char userinput[4];                                  // array of 4 characters that the user will enter to guess the sequence.
    bool inputretry = false;                            // Used at the end of the sequence or chances to determine if the player is going to retry or not
}; InfoStruct userinfo; // Global instance is created here since this variable will be used in different functions
Enter fullscreen mode Exit fullscreen mode

Like I mentioned previously, this simply holds most of the important data of the program. The declaration is right at the end also. This instance is referenced both by the randomkeyselection function and guessingSequences, one to store all the information and the other determining input and data matching.

The next piece I'd like to go over is the randomkeyselection function:

// This function simply generates the random code sequnece that will be determined. At a glance, the arguments (in order) are to the code pointer, the second is to the array of 
// numbers that will be used, and the last argument is for the size of that numberlist. 
void randomKeySelection(int *code, int listRef[], int sizeL)
{
    int selectioncounter = 0;                                                                               // A counter for the current index of the secretcode
    bool usedCounter[10] = { false, false, false, false,false, false, false, false , false, false };        // If a number has been generated, the corresponding bool will be set to true
                                                                                                            // to prevent that same number being used again.
    int randomness;                                                                                         // simply a integer holder that will store the random numbers

    // Loop that will generate the code in response to the selectioncounter variable (Maximum of 4 times).
    do
    {
        randomness = rand() % sizeL;                                // Generate a random number centered around the sizeL argument, between 0 - 9;

        // Based on the randomness variable, if the value inside the usedCounter boolean is false, then proceed. Only inside this sequence will the selectioncounter be increased which
        // will stop the loop.
        // Inside the if statement, set the boolean based on that selected index to true to prevent this digit being used again, set the code based on selectioncounter to the listRef
        // argument that was passed in based on that index, finally increased the counter to proceed to break the loop.
        if (usedCounter[randomness] == false)
        {
            usedCounter[randomness] = true;
            code[selectioncounter] = listRef[randomness];
            ++selectioncounter;
        }

    } while (selectioncounter < 4);

    //Finally simply set the codecopy string inside the userinfo struct to the full combination of our code by converting each digit of the code into a string.
    userinfo.codecopy = to_string(code[0]) + to_string(code[1]) + to_string(code[2]) + to_string(code[3]);

}
Enter fullscreen mode Exit fullscreen mode

The randomkeyselection function uses a dynamic integer pointer, as well as a integer array followed by its size respectively. The reaon I put this into a function is for quick accessibility. Originally I had no intention to do so, but realized I could keep the code a bit cleaner and more organized by seperating it. This code creates a boolean array of ten items that corresponds to the index we pull from the listref parameter value. When that value is pulled this boolean array integer in the same spot is set to true. This makes it so that if the same value is attempted to be pulled again, it is skipped. This repeatedly happens, in a do while loop until the selectioncounter variable is set to four. Selectionvalue only increases when correct values are pulled out. When this is finished, I use the userinfo reference I made to copy the new data (which is the code parameter and is constantly updated through the do loop) into it as a string (codecopy). This function is called in the main branch of the program.

I'd like to move on now to the guessingSequence function:

// This function handles the entire sequence of the game including input, retries and termination. Its arugments in order are the code array, the number of chances, and a repeat reference 
// variable which is used inside the main scope. If a repeat is requested, recursion is used, if not then the function simply ends and the main scope terminates the program.
void guessingSequence(int *secretcode, int chances, char &repeat)
{
    // Set the userinfo struct parameter keycounter to 0 so we can correctly track input
    userinfo.userkeycounter = 0;

    // While the previous counter (keycounter) is less than 4, do the following.
    do
    {
        userinfo.userinput[userinfo.userkeycounter] = _getch();             // Ask the the user for a single character. Get the structs userinput character based on the specificed index.
        cout << userinfo.userinput[userinfo.userkeycounter] << endl;        // Simply print so the user can see their key.

        // If the key that the player entered is a valid digit, then increase the keycounter variable so we can be pass through the loop, otherwise continue to ask the player for valid
        // input
        if (isdigit(userinfo.userinput[userinfo.userkeycounter]))
            userinfo.userkeycounter++;
        else
            cout << "Please a enter a number between 0 - 9" << endl;


    } while (userinfo.userkeycounter < 4);

    // Simple for loop that basically replaces the code string (usercode) inside of our userinfo struct to the input based on the index of the loop
    for (int i = 0; i < 4; ++i)
        userinfo.usercode.replace(i, i + 1, 1, userinfo.userinput[i]);

    // Compare the string usercode inside the userinfo struct to the secret code inside the struct. If they are equal (0) then the code has been cracked. If not the following branch will
    // execute.
    if (userinfo.usercode.compare(userinfo.codecopy) == 0)
    {
        cout << "Code cracked!!" << endl;
    }
    else
    {
        // This section is for if 1 or more of the keys that the player entered is wrong. First the find index inside the struct is set to -1, and the correct digits integer is set to 0. 
        userinfo.findindex = -1;
        userinfo.correctdigits = 0;

        // Loop through the four characters that the player entered
        for (int j = 0; j < 4; j++)
        {
            // Set the findindex variable to the codecopy's attempt to find the digit that the user entered based on the for loop (in order).
            userinfo.findindex = userinfo.codecopy.find(userinfo.usercode[j]);

            // If the index is valid and not a no position value (if it has been found) inside the actual code
            if (userinfo.findindex != string::npos)
            {
                // If the findindex is a valid index, but it isn't equal to the current value in the loop, this means the digit exists inside the code but at the wrong place.
                // Simply tell the user that the digit is correct but not placed. The correctdigit value remains the same.
                if (userinfo.findindex != j)
                    cout << "Correct digit " << userinfo.codecopy[userinfo.findindex] << " but at the wrong place." << endl;
                else
                {
                    // If this happens that means that the digit the player entered is correct and it is in the correct spot in response to the actual code. Correct digit is increased.
                    cout << "Successful digit entered!!!\n";
                    userinfo.correctdigits++;
                }
            }
            else
                cout << "Wrong digit " << userinfo.userinput[j] << " entered" << endl;                  // In this case the findindex is a no position which means that number doesn't
                                                                                                        // exist in the code
        }

        cout << "Correct digits overall: " << userinfo.correctdigits << endl;

        // If the number of chances left is greater than 0, we simply use recursion to call this function again but we decrease the number of chances by 1. This will continue either 
        // until the code has been cracked, or the player runs out of chances all together. If they make it here again with no chances, it simply tells them that they have no chances
        // left.
        if (chances > 0)
        {
            cout << "Chance remaining " << chances << endl;
            return guessingSequence(secretcode, chances - 1, repeat);
        }
        else
            cout << "Sorry you are out of chances." << endl;
    }


    cout << "Would you like to continue? Y or N" << endl;

    // Set the input retry boolean inside the struct to false for the following section of the code
    userinfo.inputretry = false;

    // While the retry boolean is false, keep asking the player if they want to continue or not by pressing y, Y, n or N. Regardless set the inputretry bool to true to break this 
    // whil loop. If the input is something else, keep asking for a valid input over and over again.
    while (userinfo.inputretry == false)
    {
        repeat = _getch();
        if (repeat == 'y' || repeat == 'Y')
            userinfo.inputretry = true;
        else if (repeat == 'n' || repeat == 'N')
            userinfo.inputretry = true;
        else
            cout << "Please type y or n. \n";
    }
}
Enter fullscreen mode Exit fullscreen mode

This function is the bread and butter of the application. The first thing that it does is initialize the counter inisde the userinfo struct and then repeatedly asks for user input until the key counter inside the struct reaches 4. It uses the isdigit function to check if the players input is actually a digit and limits input to a single key press.

The next thing that the function does is update the usercode variable inside the userinfostruct because I used something different for input so it needs to be transfered over.

The next thing that happens is the code is compared to the codecopy variable inside the userinfostruct. You're probably wondering why I did this? It's because in the randomkeygenerate function I copied the final code into this value so thats why userinfo is used twice but with different parameters. Now if the code isn't correct, the program basically goes through a sequence of events which tells you if you entered a correct digit inside the code as well as if the number is correct but at the wrong spot. For example a code 1256 with user input 1465 will tell you that 1 is correct, 4 is completely wrong, and 5 and 6 are correct but at the wrong spot. At the end of that it will also display how many digits you had correct, overall, using another parameter inside the userinfo struct. At the end of this branch it references the number of chances you have left to either repeat the function or tell the user they failed and have no chances left.

Regardless of whether the player succeeded or failed, then it asks the user if they'd like to try again. It simply asks for a y or n character and if the character is y, it calls the function again to repeat the entire process, if the key is not n it repeats endless, and if it is no, then it ends the process. In both yes and no it sets the inputretry to true. The reason for this is because the repeat parameter (pass by reference) is changed in both cases to one or the other which is referenced by the main function. Anything other than y or Y will terminate the program. This will be explained next.

This is what the main section looks like that starts the process:

int main()
{
    srand(time(NULL));      // random time seed for randomness
    int numberlist[10] = { 0, 1, 2, 3, 4, 5, 6, 7 ,8 , 9 };     // array of numbers that will be used for references later
    int *secretcode = new int[4];                               // dynamic pointer that will be used for secrete code until the end of the programs life cycle
    char repeat('y');                                           // a repeat character that is initialized to Y initially

    // Perform this loop while the repeat variable is equal to Y or y for yes. Repeat variable is changed inside the guessingSequence function
    do
    {
        randomKeySelection(secretcode, numberlist, size(numberlist));       // custom function that sets the secret code based on the numberlist array

        cout << "Welcome. You have 8 chances to guess the correct 4 digit code sequence." << endl;
        guessingSequence(secretcode, 7, repeat);                            // This function asks the user to guess the code and handles all important brances such as continuations and failures
    } while (repeat == 'y' || repeat == 'Y');

    // This means the end. If the secretcode is not equal to NULL, destroy it and release the memory
    if (secretcode != NULL)
        delete secretcode;
}
Enter fullscreen mode Exit fullscreen mode

As you can see here the main function intializes the basic functions and checks for input only when the guessingSequence function is finished. When it's yes everything is repeated, when no this loop stops and finally the secretcode pointer is dereferenced and released.

These are the header files if you'd like to build the application and test it for yourself:

#include <iostream>
#include <random>
#include <ostream>
#include <string>
#include <sstream>
#include <istream>
Enter fullscreen mode Exit fullscreen mode

No external software, toolkits and frameworks were used here other than visual studio (2017) itself.

Postmortem:
So there are a few things I learned in this project before it reached its conclusion. The first was input. Originally it wasn't only a simple character input, but I opted for an entire sequence of character and the approach was different. While I was able to limit it to about four characters I determined that my current approach ultimately suited my needs better and makes it easy to prevent errors, something I initially did not notice until I started stress testing the application. The second thing I opted for was the InfoStruct to hold all my information. This turned out to be a good decision as intially the program was initially more clunky and messier than it is now. I failed to fully plan out the scope and depth of this project which is something I'll keep in mind for my projects going forward.

If you made it this far thank you so much for reading. I hope you were able to learn a little bit not only about the application but my thought process as well. Till next time,

Christian

Top comments (0)