Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Go Ahead And Cheat! You Know You Want To!

5.00/5 (7 votes)
13 Sep 2022CPOL4 min read 17.1K   79  
Create drastic life improvements by completing your Wordle puzzle in minutes instead of days!
This is a quick .NET project to solve the Wordle puzzle in minutes, not days! It was created to learn a little more C#, and play around with .NET controls. This Form1 is the epitome of laziness, because cheaters are lazy.

Introduction

We've all been there. Slaving over the daily Wordle puzzle, brain a-mush, thinking, "Why won't they let me out of here, so I can go make more friends and better my life? But no, the karma masters are a harsh lot, insisting you exert youself until your little grey cells can take no more.

Well, Form1 is here to help! With this Wordle solver, you can break the chains of your oppression, and get to bed earlier tonight!

To Use

The attached project has a debug and release executable in it. Just double-click whichever one you prefer. It will appear with an OpenDialogBox, prompting you for the location of the words.txt dictionary file. Download it from https://github.com/dwyl/english-words, point the dialog box to it, and wait a couple seconds for the words to load. For some reason, the form sometimes appears behind other windows, but cheaters are lazy, and I can't be bothered to fix that, so you may have to go digging.

Once that appears, as you are playing Wordle, enter your letters in the Form1 solver, and it will eliminate any words from the dictionary that don't meet your criteria.

Again, cheaters are lazy, and being one, I initially didn't label the five boxes which specify matched letters. But cheaters are also smart, and since you are reading this, I figured you could figure it out! But then I realized I hadn't included a way to whittle out words where you knew a letter couldn't be at a certain position. So I added that, added some labels (because lazy people can't read minds), and updated this article. But I did not add a mechanism to duplicate a letter between 'Possible letters' and 'Non-positioned letters'. This means that if 'e' is a possibility, but not at position 4, you will have to place 'e' in both boxes manually. I'm too lazy to make it automatic.

As an FYI, you can add multiple letters to any of the 'Fixed letters' and 'Non-positioned letters' boxes. Behind the scenes, the code will only look at the first letter in the 'Fixed letters' boxes, but it will iterate through every letter in the 'Non-positioned letters' boxes. Being smart, you can probably figure out why I did that. If you can't figure it out, my hint for you is a recurring theme in this article.

I have added two columns for convenience. One is the sum of the letter frequencies found in each word. Those are from the Dictionary Letter Frequency column at https://en.wikipedia.org/wiki/Letter_frequency. You can use it to try to knock off highly-used letter words first if you so desire. The third column just gives a quick way to see the number of unique letters in each word.

Sorting on these columns is possible, but does take a couple seconds in some cases. Especially if you reverse a previously sorted column. Evidently, datagridview uses bubble sort behind the scenes.

Behind the Scenes

This is just a very simple entry eliminator program, but for simplicity, it is built as a list builder. Any time a text box changes, corresponding text strings are changed and the program iterates over all the five letter words in the dictionary to see if they match all of the criteria that has been entered in the boxes. So Lists are continually created and destroyed. There are probably more optimal solutions possible. Don't care - too lazy to optimize further!

If you want, you can hard-code the words.txt file location. That would save more time for the lazier among us. An example is commented out in the code.

Here is a dump of the main part of the code. It is pretty simple. Enjoy!

C#
namespace wordleSolver {
   public partial class Form1 : Form {
      public List<string> allWords = new List<string>();
      public List<string> allFiveLetterWords = new List<string>();

      string nonCharacters = "";
      string hasCharacters = "";
      string p1Char = "";
      string p2Char = "";
      string p3Char = "";
      string p4Char = "";
      string p5Char = "";

      string p1NonChar = "";
      string p2NonChar = "";
      string p3NonChar = "";
      string p4NonChar = "";
      string p5NonChar = "";

      public Form1() {
         InitializeComponent();
         }

      private void Form1_Load(object sender, EventArgs e) {
         var path = string.Empty;
         using (OpenFileDialog openFileDialog = new OpenFileDialog()) {
            openFileDialog.InitialDirectory = "";
            openFileDialog.Filter = "txt files (*.txt)|*.txt";
            openFileDialog.FilterIndex = 1;
            openFileDialog.RestoreDirectory = true;
            openFileDialog.Title = "words.txt file location?";
            if (openFileDialog.ShowDialog() == DialogResult.OK) {
               path = openFileDialog.FileName;
               }
            }
      
         //string path = "D:\\Programs\\MyProgs\\wordleSolver\\words.txt";
         System.IO.StreamReader sr = new System.IO.StreamReader(path);

         while (!sr.EndOfStream) {
            string str = sr.ReadLine();
            allWords.Add(str.ToLower());
            }
         sr.Close();

         foreach (string str in allWords) {
            if (str.Length == 5) allFiveLetterWords.Add(str);
            }

         dataGridView.ColumnCount = 3;
         foreach (string str in allFiveLetterWords) {
            int val = getVal(str);
            int numChars = getChars(str);
            dataGridView.Rows.Add(str, val, numChars);
            }
         dataGridView.Sort(this.dataGridView.Columns[1], ListSortDirection.Descending);
         }

      private int getChars(string str) {
         int count = 1;
         bool isDifferent = true;
         for (int curPos = 1; curPos < 5; ++curPos) {
            for (int x=0; x<curPos; ++x) {
               if (str[x] == str[curPos]) isDifferent = false;
               }
            if (isDifferent) ++count;
            }
         return count;
         }

      int getVal(string str) {
         //This will be a five-letter word, so no error checking: 
         int total = 0;
         for (int i=0; i<5; ++i) {
            switch (str[i]) {
               case 'a': case 'A': total += 780; break;
               case 'b': case 'B': total += 200; break;
               case 'c': case 'C': total += 400; break;
               case 'd': case 'D': total += 380; break;
               case 'e': case 'E': total += 1100; break;
               case 'f': case 'F': total += 140; break;
               case 'g': case 'G': total += 300; break;
               case 'h': case 'H': total += 230; break;
               case 'i': case 'I': total += 860; break;
               case 'j': case 'J': total += 21; break;
               case 'k': case 'K': total += 97; break;
               case 'l': case 'L': total += 530; break;
               case 'm': case 'M': total += 270; break;
               case 'n': case 'N': total += 720; break;
               case 'o': case 'O': total += 610; break;
               case 'p': case 'P': total += 280; break;
               case 'q': case 'Q': total += 19; break;
               case 'r': case 'R': total += 730; break;
               case 's': case 'S': total += 870; break;
               case 't': case 'T': total += 670; break;
               case 'u': case 'U': total += 330; break;
               case 'v': case 'V': total += 100; break;
               case 'w': case 'W': total += 91; break;
               case 'x': case 'X': total += 27; break;
               case 'y': case 'Y': total += 160; break;
               case 'z': case 'Z': total += 44; break;
               }
            }
         return total;
         }

         private void possibilitiesTextBox_TextChanged(object sender, EventArgs e) {
            hasCharacters = possibilitiesTextBox.Text;
            whittleDownPossibilities();
            }

         private void whittleDownPossibilities() {
            dataGridView.Rows.Clear();
            dataGridView.Update();
            dataGridView.Refresh();

            //First, eliminate all of the non-possible letters:
            List<string> currentPossibilities = new List<string>();

            bool isPossibility = true;
            foreach (string str in allFiveLetterWords) {
               isPossibility = true;
               for (int i=0; i<nonCharacters.Length; i++) {
                  for (int j=0; j<5; ++j) {
                     if (nonCharacters[i] == str[j]) {
                        isPossibility = false;
                        goto breakoutNonPossible;
                        }
                     }
                  }
            breakoutNonPossible:
               if (isPossibility) currentPossibilities.Add(str);
               }

            //Now whittle down possibilities to those in the possible letters string:
            List<string> reducedPossibilities = new List<string>();

            foreach (string str in currentPossibilities) {
               isPossibility = true;
               for (int i=0; i<hasCharacters.Length; i++) {
                  bool hasCurrentCharacter = false;
                  for (int j=0; j<5; j++) {
                     if (hasCharacters[i] == str[j]) {
                        hasCurrentCharacter = true;
                        }
                     }
                  if (!hasCurrentCharacter) {
                     isPossibility = false;
                     goto breakoutPossibilities;
                     }
                  }
               breakoutPossibilities:
                  if (isPossibility) reducedPossibilities.Add(str);
               }

            currentPossibilities.Clear();
            foreach (string str in reducedPossibilities) {
               isPossibility = true;
               if (p1Char != "" && p1Char[0]!=str[0]) isPossibility = false;
               if (p2Char != "" && p2Char[0]!=str[1]) isPossibility = false;
               if (p3Char != "" && p3Char[0]!=str[2]) isPossibility = false;
               if (p4Char != "" && p4Char[0]!=str[3]) isPossibility = false;
               if (p5Char != "" && p5Char[0]!=str[4]) isPossibility = false;
               if (isPossibility) currentPossibilities.Add(str);
               }

            reducedPossibilities.Clear();
            foreach (string str in currentPossibilities) {
               isPossibility = true;
               for (int i=0; i<p1NonChar.Length; ++i) {
                  if (p1NonChar[i] == str[0]) {
                     isPossibility = false;
                     goto breakout3;
                     }
                  }
               for (int i=0; i<p2NonChar.Length; ++i) {
                  if (p2NonChar[i] == str[1]) {
                     isPossibility = false;
                     goto breakout3;
                     }
                  }
               for (int i=0; i<p3NonChar.Length; ++i) {
                  if (p3NonChar[i] == str[2]) {
                     isPossibility = false;
                     goto breakout3;
                     }
                  }
               for (int i=0; i<p4NonChar.Length; ++i) {
                  if (p4NonChar[i] == str[3]) {
                     isPossibility = false;
                     goto breakout3;
                     }
                  }
               for (int i=0; i<p5NonChar.Length; ++i) {
                  if (p5NonChar[i] == str[4]) {
                     isPossibility = false;
                     goto breakout3;
                     }
                  }
               
               breakout3:
                  if (isPossibility) reducedPossibilities.Add(str);
               }

            foreach (string str in reducedPossibilities) {
               int val = getVal(str);
               int numChars = getChars(str);
               dataGridView.Rows.Add(str, val, numChars);
               }
            dataGridView.Sort(this.dataGridView.Columns[1], ListSortDirection.Descending);
            }

      private void nonCharTextBox_TextChanged(object sender, EventArgs e) {
         nonCharacters = nonCharTextBox.Text;
         whittleDownPossibilities();
         }

      private void p1TextBox_TextChanged(object sender, EventArgs e) {
         p1Char = p1TextBox.Text;
         whittleDownPossibilities();
         }

      private void p2TextBox_TextChanged(object sender, EventArgs e) {
         p2Char = p2TextBox.Text;
         whittleDownPossibilities();
         }

      private void p3TextBox_TextChanged(object sender, EventArgs e) {
         p3Char = p3TextBox.Text;
         whittleDownPossibilities();
         }

      private void p4TextBox_TextChanged(object sender, EventArgs e) {
         p4Char = p4TextBox.Text;
         whittleDownPossibilities();
         }

      private void p5TextBox_TextChanged(object sender, EventArgs e) {
         p5Char = p5TextBox.Text;
         whittleDownPossibilities();
         }

      private void np1TextBox_TextChanged(object sender, EventArgs e) {
         p1NonChar = np1TextBox.Text;
         whittleDownPossibilities();
         }

      private void np2TextBox_TextChanged(object sender, EventArgs e) {
         p2NonChar = np2TextBox.Text;
         whittleDownPossibilities();
         }

      private void np3TextBox_TextChanged(object sender, EventArgs e) {
         p3NonChar = np3TextBox.Text;
         whittleDownPossibilities();
         }

      private void np4TextBox_TextChanged(object sender, EventArgs e) {
         p4NonChar = np4TextBox.Text;
         whittleDownPossibilities();
         }

      private void np5TextBox_TextChanged(object sender, EventArgs e) {
         p5NonChar = np5TextBox.Text;
         whittleDownPossibilities();
         }
   }
}

What I Learned

C# uses Lists instead of vectors. Operator = for a List changes the address of the List instead of making the values of one List match another (1). C# does not have a direct equivalent of C++'s multimap unless you import something. The directions on that aren't clear, so newbies like me can get a little turned off by the effort. I gave up, and used a datagridview to solve the problem. It was more flexible in the end, but I suspect the sorting speed would be vastly improved using C++'s multimap.

What is Left Not To Do

Cheaters are lazy. This is Form1. Good enough. Need an icon? Default is your friend!

History

  • 13th September, 2022: Initial version
  • 14th September, 2022: Added 'Non-positioned letters' boxes.

And being lazy. Well, maybe not so much. Got other things to do! Enjoy the respite from the karma masters! Go out, see the world! Smile at a baby's laughter!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)