0

I am building a optimizer for a game in WPF C#. You start of by adding all the monsters you have and the runes (runes are almost like armor/weapons). You can then choose one of the monsters from a list that you want to optimize. Each monster can equip 6 runes (each rune has a specific slot it can go to, 1-6). The user can then choose what type of runeset it wants (up to 3 diffrent). You need to have 4 or 2 pices (depending on runeset) of the same rune for it be be a runeset. The user can also specify what main type of rune it wants on slot 2,4 and 6 e.x atk% or hp%. After this the user hits a button to optimize.

Here comes my problem, since we want to add all the results in a datagrid so that the user can sort after most possible hp, speed and atk etc. This list will be huge depening on what user has specified in the comboboxes (filtering runeset, type of rune). If no filter is given we are talking about 30^6 results, maybe even more. Ideally the user should be able to do such a big search since it gives the best result, but if that is not possible i can set a limit so the user have to choose some of the filters to cut down the results. My first approach on this was just to make a list add each possible runecombination, and that resultet in out of memory after a few milion searches. Then i tried to save the data to an xml file, this worked but was very slow (did never finish a full search becouse it would take weeks). Then i check up SQLite, and this is where i am now, its decent fast and it might just work. But I am fresh with C# so my code might be very bad and poorly optimized so could need some guidence here to help me out on the code. I dont want to post my full code since its long so I will paste a few important ones and maybe someone can give me some tips on how to optimize this better:

This is the code for the Rune class:

public class Rune
{
    public int Rune_ID { get; set; }
    public int Rune_Monster { get; set; }
    public string Rune_Monster_Name { get; set; }
    public string Rune_Set { get; set; }
    public int Rune_Slot { get; set; }
    public int Rune_Grade { get; set; }
    public int Rune_Level { get; set; }
    public string Rune_Main_Type { get; set; }
    public int Rune_Main_Amount { get; set; }
    public string Rune_Innate_Type { get; set; }
    public int Rune_Innate_Amount { get; set; }
    public string Rune_Sub1_Type { get; set; }
    public int Rune_Sub1_Amount { get; set; }
    public string Rune_Sub2_Type { get; set; }
    public int Rune_Sub2_Amount { get; set; }
    public string Rune_Sub3_Type { get; set; }
    public int Rune_Sub3_Amount { get; set; }
    public string Rune_Sub4_Type { get; set; }
    public int Rune_Sub4_Amount { get; set; }
    public int Rune_Locked { get; set; }
    public int Rune_Sub_HP_F { get; set; }
    public int Rune_Sub_HP_P { get; set; }
    public int Rune_Sub_Atk_F { get; set; }
    public int Rune_Sub_Atk_P { get; set; }
    public int Rune_Sub_Def_F { get; set; }
    public int Rune_Sub_Def_P { get; set; }
    public int Rune_Sub_Spd { get; set; }
    public int Rune_Sub_CritRate { get; set; }
    public int Rune_Sub_CritDmg { get; set; }
    public int Rune_Sub_Res { get; set; }
    public int Rune_Sub_Acc { get; set; }
}

Optimize button click:

    private void butOptimize_Click(object sender, RoutedEventArgs e)
    {
        //OptimizeList.Clear();

        if (cmbOptMonsters.SelectedItem != null)
        {
            selectedRuneSets.Clear();

            //Makes list of selected runesets
            if (cmbOptSet1.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet1.SelectedValue.ToString());
            if (cmbOptSet2.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet2.SelectedValue.ToString());
            if (cmbOptSet3.SelectedIndex != -1)
                selectedRuneSets.Add(cmbOptSet3.SelectedValue.ToString());

            //Makes list of selected runeslots
            if (cmbOptSlot2.SelectedIndex != -1)
                selectedRuneSlot2 = cmbOptSlot2.SelectedValue.ToString();
            if (cmbOptSlot4.SelectedIndex != -1)
                selectedRuneSlot4 = cmbOptSlot4.SelectedValue.ToString();
            if (cmbOptSlot6.SelectedIndex != -1)
                selectedRuneSlot6 = cmbOptSlot6.SelectedValue.ToString();

            var selMob = from y in Monsters where y.Monster_Name == cmbOptMonsters.SelectedValue.ToString() select y;
            ref2Monster = selMob.First();

            worker.RunWorkerAsync();
        }
    }

Background worker do work code:

     // run all background tasks here
    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {

        BackgroundWorker worker = (BackgroundWorker)sender;
        List<string> listCurrentSets = new List<string>();

        IEnumerable<Rune> Slot_1 = from y in Runes where y.Rune_Slot == 1 select y;
        IEnumerable<Rune> Slot_2 = from y in Runes where y.Rune_Slot == 2 select y;
        IEnumerable<Rune> Slot_3 = from y in Runes where y.Rune_Slot == 3 select y;
        IEnumerable<Rune> Slot_4 = from y in Runes where y.Rune_Slot == 4 select y;
        IEnumerable<Rune> Slot_5 = from y in Runes where y.Rune_Slot == 5 select y;
        IEnumerable<Rune> Slot_6 = from y in Runes where y.Rune_Slot == 6 select y;
        IEnumerable<Rune> tempStoreRunes = from y in Runes where y.Rune_Monster_Name == ref2Monster.Monster_Name select y;

        int rcount1 = Runes.Count(n => n.Rune_Slot == 1);
        int rcount2 = Runes.Count(n => n.Rune_Slot == 2);
        int rcount3 = Runes.Count(n => n.Rune_Slot == 3);
        int rcount4 = Runes.Count(n => n.Rune_Slot == 4);
        int rcount5 = Runes.Count(n => n.Rune_Slot == 5);
        int rcount6 = Runes.Count(n => n.Rune_Slot == 6);
        int modulo = 1000;
        double count;
        double countLoops = 0;
        double countMatch = 0;
        double timeETA = 0.0;
        bool setIsMatch;

        Optimized optTempSearch;
        var stopwatch = new System.Diagnostics.Stopwatch();
        var time = TimeSpan.FromMilliseconds(timeETA);

        if (selectedRuneSlot2 != "")
        {
            Slot_2 = from y in Runes where y.Rune_Slot == 2 && y.Rune_Main_Type == selectedRuneSlot2 select y;
            rcount2 = Runes.Count(n => n.Rune_Slot == 2 && n.Rune_Main_Type == selectedRuneSlot2);
        }
        if (selectedRuneSlot4 != "")
        {
            Slot_4 = from y in Runes where y.Rune_Slot == 4 && y.Rune_Main_Type == selectedRuneSlot4 select y;
            rcount4 = Runes.Count(n => n.Rune_Slot == 4 && n.Rune_Main_Type == selectedRuneSlot4);
        }
        if (selectedRuneSlot6 != "")
        {
            Slot_6 = from y in Runes where y.Rune_Slot == 6 && y.Rune_Main_Type == selectedRuneSlot6 select y;
            rcount6 = Runes.Count(n => n.Rune_Slot == 6 && n.Rune_Main_Type == selectedRuneSlot6);
        }

        count = rcount1 * rcount2 * rcount3 * rcount4 * rcount5 * rcount6;

        OptSearchCount = count;

        //Starts timer
        stopwatch.Start();

        // Creates new sqlite database if it is not found
        using (var conn = new SQLiteConnection(
            @"Data Source=" + AppDomain.CurrentDomain.BaseDirectory + "\\Resources\\Optimizer.sqlite"))
        {
            //Connects to database
            conn.Open();

            using (var cmd = new SQLiteCommand(conn))
            {
                using (var transaction = conn.BeginTransaction())
                {
                    foreach (var r1 in Slot_1)
                    {
                        foreach (var r2 in Slot_2)
                        {
                            foreach (var r3 in Slot_3)
                            {
                                foreach (var r4 in Slot_4)
                                {
                                    foreach (var r5 in Slot_5)
                                    {
                                        foreach (var r6 in Slot_6)
                                        {
                                            countLoops++;

                                            //Makes a list of the current sets that is made by runecombination
                                            listCurrentSets = CheckIfRuneSet(r1, r2, r3, r4, r5, r6);

                                            //Check if selectedRuneSets exists in the list listCurrentSets.
                                            //We only want to make a search on the selected runes
                                            setIsMatch = new HashSet<string>(listCurrentSets).IsSupersetOf(selectedRuneSets);

                                            if (setIsMatch == true)
                                            {
                                                countMatch++;

                                                //Gets the current stat of the monster using the current runes from loop                 
                                                optTempSearch = SumStat(ref2Monster, r1, r2, r3, r4, r5, r6, countLoops);

                                                //Makes a command that will be sent to data base later in commit
                                                cmd.CommandText = "INSERT INTO OptSearch (id, runeids, atk, def, hp, crate, cdmg, acc, spd, res) VALUES (" + countLoops + ", '" + optTempSearch.Opt_RuneIDs + "', " + optTempSearch.Opt_ATK + ", " + optTempSearch.Opt_DEF + ", " + optTempSearch.Opt_HP + ", " + optTempSearch.Opt_CRate + ", " + optTempSearch.Opt_CDmg + ", " + optTempSearch.Opt_Acc + ", " + optTempSearch.Opt_Spd + ", " + optTempSearch.Opt_Res + ");";

                                                //Executes database command
                                                cmd.ExecuteNonQuery();
                                            }

                                            //Uses modulus to not opdate the progress to fast, not nessecary. 
                                            //Then reports the progress to the worker
                                            if (countLoops % modulo == 0)
                                            {
                                                worker.ReportProgress((int)countLoops);

                                                //Calculates estimated time
                                                timeETA = stopwatch.Elapsed.TotalMilliseconds / countLoops;
                                                time = TimeSpan.FromMilliseconds(timeETA * count);

                                                //You cannot change the ui since its owned by the "main", so to by pass we do this
                                                this.Dispatcher.BeginInvoke((Action)delegate ()
                                                {
                                                    lblOptMatch.Content = "Matches: " + (int)countMatch;
                                                    lblOptETA.Content = time.ToString(@"dd\:hh\:mm\:ss");
                                                });
                                            }

                                        }
                                    }
                                }
                            }
                        }
                    }

                    //Saves to database
                    transaction.Commit();
                }
            }

            //Closes database connection
            conn.Close();
        }

        UpdateMonsterStats(ref2Monster, tempStoreRunes);
        //xmlDocument.Save(AppDomain.CurrentDomain.BaseDirectory + "\\Resources\\Optimizer.xml");
    }

If anyone need the full code just ask and i can upload the project. Here is a picture of search gui: https://i.stack.imgur.com/cXZCf.jpg

Is there a better solution to dealing with this big searches?

  • No one can look through 30^6 result rows (memory implications aside). You need to rethink the UI and filter the results in some way. If you end up with more than a few hundred results, consider using virtual mode for your listview so you don't actually load all results in one go into the listview but query them on the fly as the user scrolls through the list. – xxbbcc Dec 02 '15 at 16:14
  • " Ideally the user should be able to do such a big search since it gives the best result, but if that is not possible i can set a limit so the user have to choose some of the filters to cut down the results." There is alrdy a filtering and this is also why I am asking, as I said in earlier its just optimal for it to be that big. I just want to know how big search i can make before it gets to big to handle. I can force the user to pick some of the filters to cut down searches. The bigger the better in this case. – Michael Blomli Dec 02 '15 at 16:22
  • 1
    You're wrong. No user can look through more than a few hundred results. There's a reason most sites limit pages to about 100-200 records per page. If a single, massive list contains thousands of rows, most of them will be overlooked. It's counterproductive, not helpful. – xxbbcc Dec 02 '15 at 16:26
  • Well I am not so sure on this, I got this whole idee from someone that made this alrdy. I wanted to make my own becouse he had made his in an webb application (not sure what language). On his optimizer the user could make upto 200 000 searches and was only limited due to the webb browser. I thought of this as a fun project for me to learn c# and also make an improved optimizer at the same time, that maybe used a database to make it be able to handle bigger searches. But maybe that isnt the case then.I know i had ir working on around 1.000.000 lines in the datagrid before it went out of memory. – Michael Blomli Dec 02 '15 at 16:31
  • This does sound like it's a fun project to learn from. When you used the other website (that gives you back 200,000 results in a single list) can you actually look through all those results? I'd bet no - this means that the vast mejority of those 200,000 records are a waste of memory and network bandwidth - they're sent to your browser for you to ignore. It's much better to provide more relevant results that a user can actually review. – xxbbcc Dec 02 '15 at 16:34
  • As an anectode, I used to work on an application where users (dealing with a lot of money per record) demanded to be shown at least 2000 records at a time, to see sums. We did this but then they complained that it took too long to get search results (query was complex). When we reduced the result rows to 500, they never even noticed that - they were only happy that the search was now faster. We didn't get a single complaint that they had too few results. – xxbbcc Dec 02 '15 at 16:37
  • Okay I misunderstood you. 100-200 searches per page that is shown is fine, aslong as the total search can be big. After the search is done i will add in additional fltering for the user to filter the search down to as little as he wants by setting ex: min hp and atk etc. – Michael Blomli Dec 02 '15 at 16:38
  • You don't need to do paging on a WPF UI (you can, if you want to). WPF list views have [virtual mode](http://stackoverflow.com/questions/14456075/how-to-enable-ui-virtualization-in-standard-wpf-listview). – xxbbcc Dec 02 '15 at 16:40
  • Is there by any chans possible to chat with you? So this thread wont become a chat. (BTW i dont have enough to enter the chat here since i dont have enough points.) – Michael Blomli Dec 02 '15 at 16:43
  • This is not the problem, the user dosnt have to see more then 50 or so results in the grid. The problem is that the List that i add in the searches in will be so big that my pc runs out of memory. LEts say the search is done and we have like 1 mil diffrent combinasjons of the search, the idee is that the user can set a filter to just show all that have diffrent stats that is bigger then the choosen value. So in the end there might just be a few that satesfy the criteria. This is why i want it so that the search can be big so the result will taken down from milions to just a few by filtering. – Michael Blomli Dec 02 '15 at 17:02

0 Answers0