Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tracking High Scores on Windows Phone 7

0.00/5 (No votes)
12 Mar 2011 2  
How to implement local high scores in Windows Phone 7

Another frequent question I come across in user forums is related to how to implement local high scores. The question has come up frequently enough for me to conclude that it is to the benefit of the community to have an implementation available that can be used in Silverlight or XNA that is ready to be used with very little setup.

So I've made a solution for others to use. By default, the component will keep track of up to 10 high scores and will take care of loading and saving itself. If you add a score, the component will take care of ensuring the score is in its proper place and removing scores that are no longer one of the top. For persisting score information, I've made use of the DataSaver<T> code from a previous blog post. I hope others will find the solution easy to use.

To get started with using the component, add a reference to my component to your project. You'll want to instantiate HighScoreList passing an optional file name that it will use to save score information. It's possible to keep track of more than one high score list as long as your instances have different file names. You might want to do this if they keep track of scores in different modes separately from each other (e.g.: a score list for Difficult mode, a score list for Easy mode, and so on).

HighScoreList _highScoreList = new HighScoreList("MyScores"); 

Upon instantiation, the component will take care of loading any previous high scores without you doing anything more.

To add a score, create a new instance of ScoreInfo and populate its PlayerName and Score fields. (There is also a ScoreDate field that automatically gets populated with the current date and time). Then use the AddScore(ScoreInfo) method on the HighScoreList instance to add it to the score list.

ScoreInfo scoreInfo = new ScoreInfo(){PlayerName = "Jack", Score = 1048576};
_highScoreList.AddScore(scoreInfo);

And that's it, there's nothing more for you to do. When you make that call, the score gets added to the high score list, scores that are no longer in the top 10 (or whatever you set the limit to be) will fall off the list, and the list will automatically be persisted back to IsolatedStorage so that it is available the next time your game runs. Easy, right?

As a test project, I've created a Silverlight application that allows you to enter new scores and see the behaviour of the component.

167980/image.png

The main bits of the source code are below. First, the ScoreInfo class which is nothing more than a serializable collection of three properties:

/// <summary>
/// ScoreInfo contains information on a single score
/// </summary>
[DataContract]
public class ScoreInfo : INotifyPropertyChanged 
{
    // PlayerName - generated from ObservableField snippet - Joel Ivory Johnson
    private string _playerName = String.Empty;

    /// <summary>
    /// The name of the player that made this score
    /// </summary>
    [DataMember]
    public string PlayerName
    {
        get { return _playerName; }
            set
            {
                if (_playerName != value)
                {
                    _playerName = value;
                    OnPropertyChanged("PlayerName");
                }
            }
        }
        //-----
                
        // Score - generated from ObservableField snippet - Joel Ivory Johnson
        private int _score = 0;

        /// <summary>
        /// The score that the player made
        /// </summary>
        [DataMember]
        public int Score
        {
        get { return _score; }
            set
            {
                if (_score != value)
                {
                    _score = value;
                    OnPropertyChanged("Score");
                }
            }
        }
        //-----
                
        // ScoreDate - generated from ObservableField snippet - Joel Ivory Johnson
        private DateTime _scoreDate = DateTime.Now;

        /// <summary>
        /// The date and time that the player made the score. If this field is not 
        /// assigned a value it will automatically be assigned with the date and time
        /// that the score isntance was created
        /// </summary>
        [DataMember]
        public DateTime ScoreDate
        {
            get { return _scoreDate; }
            set
            {
                if (_scoreDate != value)
                {
                    _scoreDate = value;
                    OnPropertyChanged("ScoreDate");
                }
            }
        }
        //-----
        protected void OnPropertyChanged(String propertyName)
        {
            if(PropertyChanged!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    #region INotifyPropertyChanged Members

    public  event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

And then the HighScoreList class, which is a collection class:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace J2i.Net.ScoreKeeper
{
    public class HighScoreList : ObservableCollection<ScoreInfo>, 
                                 INotifyPropertyChanged    
    {
        static DataSaver<HighScoreList> MyDataSaver = 
               new DataSaver<HighScoreList>();

        public HighScoreList()
        {
            
        }

        public HighScoreList(string fileName):this()
        {
            this.ScoreFileName = fileName;
            HighScoreList temp = MyDataSaver.LoadMyData(fileName);
            if(temp!=null)
            {
                foreach(var item in temp)
                {
                    Add(item);
                }
            }
        }
                
        // MaxScoreCount - generated from ObservableField snippet - Joel Ivory Johnson
        private int _maxScoreCount = 10;
        [DataMember]
        public int MaxScoreCount
        {
            get { return _maxScoreCount; }
            set
            {
                if (_maxScoreCount != value)
                {
                    _maxScoreCount = value;
                    OnPropertyChanged("MaxScoreCount");
                }
            }
        }
        //-----
                
        // ScoreFileName - generated from ObservableField snippet - Joel Ivory Johnson
        private string _scoreFileName = "DefaultScores";
        [DataMember]
        public string ScoreFileName
        {
            get { return _scoreFileName; }        
            set
            {
                if (_scoreFileName != value)
                {
                    _scoreFileName = value;
                    OnPropertyChanged("ScoreFileName");
                }
            }
        }
        //-----
                
        // AutoSave - generated from ObservableField snippet - Joel Ivory Johnson
        private bool _autoSave = true;
        [DataMember]
        public bool AutoSave
        {        
            get { return _autoSave; }
            set
            {
                if (_autoSave != value)
                {
                    _autoSave = value;
                    OnPropertyChanged("AutoSave");
                }
            }
        }
        //-----

        static int ScoreComparer(ScoreInfo a, ScoreInfo b)
        {
            return b.Score - a.Score;
        }

        public void SortAndDrop()
        {
            List<ScoreInfo> temp = new List<ScoreInfo>(this.Count);
            foreach(var item in this)
            {
                temp.Add(item);
            }

            if (temp.Count > MaxScoreCount)
            {
                temp.RemoveRange(MaxScoreCount - 1, (temp.Count) - (MaxScoreCount));
            }

            temp.Sort(ScoreComparer);
            this.Clear();

            temp.ForEach((o)=>Add(o));
        }

        public void Save()
        {
            if(String.IsNullOrEmpty(ScoreFileName))
                throw new ArgumentException("A file name wasn't provided");
            MyDataSaver.SaveMyData(this, ScoreFileName);
        }

        public void AddScore(ScoreInfo score)
        {
            this.Add(score);
            SortAndDrop();
            if(AutoSave)
                Save();
        }
        
        protected void OnPropertyChanged(String propertyName)
        {
            if(PropertyChanged!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here