Search code examples
c#arraysformswinformslistbox

Attempting to 'permanetly' save two user inputs into two seperate arrays, and display the array data in a listbox on a different form


Apologies I am a newbie, ill try to explain as best as I can. Im creating a game for a university assignment. My game is a maths game that asks the user maths questions, they get a point for answering correctly and lose a life for answering incorrectly. I have created the game over multiple forms namely: Main Menu, Game (actual game), Rules and Leaderboard. My question focuses on the game and leaderboard forms. The two inputs the user give are their 'usernames' as a string input and the amount of points they get once they finish a round of answering questions as a interger input.

I am attempting to display the 'Username' and 'Score' on a listbox (leaderboard) permanetly so that if multiple players play, they can compare scores when opening the leaderboard at any time (leaderboard does not need to keep its data if the program is stopped and started again, only the data it acquires while its running). At the moment, once a player has finished their round, the leaderboard pops up and displays the information correctly, but when the player goes back to main menu and reopens the leaderboard or plays another round, the leaderboard is empty again, as if the array had cleaned itself.

I managed to succesfully keep the data in the listbox when I use one form only for the inputs, array and listbox, the issue comes in when trying to do it over two different forms, in my case, the inputs (name and score) are inputted on the game form, then are transferred over to the leaderboard form and stored in an array. The array is then displayed to the listbox output for the leaderboard.

Example of what I would like: Player 1 plays 1 round and gets a score of 7. This is outputted to the listbox leaderboard as follows:

Username: Player 1 | Score: 7

Player 2 proceeds to play 1 round and gets a score of 9. Then the listbox leaderboard is as follows:

Username: Player 2 | Score: 9

Username: Player 1 | Score: 7

etc. At the moment, the usernames and scores are displayed on the leaderboard the first time the player views it which is right after their round. Then when the player closes the leaderboard it gets deleted/refreshed off the leaderboard so that the leaderboard is empty which is not what I want.

Things to note:

  • I am required to use an array, cannot use List
  • Id prefer not to write to a textfile but will if its my last resort
  • Some of the comments may not make sense, but it is required for marks

Hope that was clear enough. Any help would be greatly appreciated :)

My code sofar is as follows: Leaderboard code:

namespace Educational_Boardgame_Group_Assignment

{ public partial class Leaderboard : Form { //Variables string userName; int userScore;

    public Leaderboard()
    {
        InitializeComponent();
    }

    public Leaderboard(string nameValue, int numValue)
    {
        InitializeComponent();
        this.nameValue = nameValue;
        this.scoreValue = numValue;
    }

    public string nameValue { get; set; }
    public int scoreValue { get; set; }

    //Back to main menu method
    private void btnMainMenu_Click(object sender, EventArgs e)
    {
        //Closes Leaderboard Form
        this.Close();
    }


    //Loads users name and score from the game they played
    private void Leaderboard_Load(object sender, EventArgs e)
    {
        //Variables
        string[] Username = new string[20];
        int[] Score = new int[20];
        int index = 0;

        //Input
        userName = nameValue;
        userScore = scoreValue;

        //Process | Displays user's name and score from the array within it is stored.
        if (index < Username.Length && index < Score.Length)
        {
            Username[index] = userName;
            Score[index] = userScore;
            lstHighScoreOutput.Items.Add("Username: " + Username[index] + "                             Score: " + Score[index]);
        }
    }
}

}

Game Code (Acquiring Username):

//Variable
        bool boolTryAgain = false;

        //Process | Dialog Pop-Up Box for username entry by user in order to save their highscore
        do
        {
            //Variable
            userName = UserPopUpBox.GetUserInput("Enter your name below:", "Username Entry");
            //Process
            if (userName == "")
            {
                DialogResult dialogResult = MessageBox.Show("You did not enter anything. Try again?", "Error", MessageBoxButtons.YesNo);
                if (dialogResult == DialogResult.Yes)
                {
                    boolTryAgain = true; //will reopen the dialog for user to input text again
                }
                else if (dialogResult == DialogResult.No)
                {
                    //exit/cancel
                    MessageBox.Show("Your highscore will not be saved.");
                    boolTryAgain = false;
                }//end if
            }
            else
            {
                if (userName == "cancel")
                {
                    MessageBox.Show("Your highscore will not be saved.");
                }
                else
                {
                    MessageBox.Show("Your username is: '" + userName + "'");
                }

            }
        } while (boolTryAgain == true);

Game Code (Acquiring User Score):

//Process | Determines whether user input is correct. Displays specific output based on user input.
        int userEntered = Convert.ToInt32(txtAnswer.Text);
        if (userEntered == total)
        {
            lblStatus.Text = "Correct!";
            lblStatus.ForeColor = Color.Lime;
            score += 1;
            lblScore.Text = "Score: " + score;
            SetUpGame();
        }
        else
        {
            lblStatus.Text = "Incorrect!";
            lblStatus.ForeColor = Color.Red;
            lives -= 1;
            lblLives.Text = "Lives: " + lives;
            SetUpGame();
        }

Game Code (Sending data to the leaderboard form):

//Process | If user answers incorrectly 3 times, game ends and output displayed.
        if (lives == 0)
        {
            this.Close();
            MessageBox.Show("You have run out of lives! \nYour final score is: " + score);

            //Shows user score on leaderboard
            Leaderboard frm3 = new Leaderboard(userName, score);
            frm3.ShowDialog();
        }

Solution

  • Welcome to managing state in an application!

    A couple things to point out first (some you already know):

    • Your Leaderboard has nameValue and scoreValue as public properties so that they are accessible outside of the Leaderboard class and are available to the rest of the class (like it's methods etc).
    • Your string[] Username and int[] Score are scoped to the Leaderboard_Load method. That is, these variable exist during the method's execution and no where else. You can't access these variables in btnMainMenu_Click, for example.
    • The actual leadership data does load into Username and Scrore, but is then loaded into lstHighScoreOutput. Once that method finishes, Username and Scrore are removed from memory. So the only place where your data is actually present once the UI is loaded, is in the UI component of lstHighScoreOutput.
    • Once the form Leaderboard closes, it disposes (clears out of memory) all of it's referenced components, including lstHighScoreOutput. So at that point, the data is removed from memory, which is why when you open the UI again, you don't see previous values.

    So what you want is the data to exist and be managed outside of the Leaderboard UI, because this is state for your game. Ok so you don't need it to persist between runs, that's fine. But you want it to be available no matter how many times you run the game portion and show the leaderboard.

    A simple place to put this is in your main/calling window (probably your main menu in this case). So you store the data there, update it

    • after the game is complete. The data is then passed to your Leaderboard. So the Leaderboard becomes a "dumb" component which just renders what it's given.

    OR

    • Let the Leaderboard compute the high scores, which works but requires the UI to be opened to adjust the scores. If a user don't run the leaderboard, the scores aren't updated. So try the first approach.

    A more 'correct' way of storing leaderboard data is in it's own class, one that holds both score and user arrays. Then you're not passing 2 arrays around the place, you just pass the leaderboard instance.

    public class LeaderboardData
    {
      public string[] UserNames { get; set; }
      public int[] UserScores { get; set; }
    }
    

    You can even take this further with the Singleton Pattern. This is a special design pattern which lets your class only ever have once instance. With a singleton LeaderboardData, you don't even need to pass references to the class. It will hold onto and look after it's own data for the lifetime of your running game. This is the approach I would take.