WORDLE in AGS?

Started by tilapisha, Sun 03/04/2022 18:18:41

Previous topic - Next topic

tilapisha

I'm interested in a complete implementation of Wordle as well, in the future it would be cool to send out an API call to get the word used in the actual wordle. The scope of this project is much smaller, as the whole game is due on April 21!


I got the greens to work, just an issue with capitalization. I'm trying to write the more complicated yellow case (if the letter is already green, don't make yellow) but most wordle clones online don't do this even. I appreciate all the help, and I will post the final game once it's done too. I had a question about implementing easymirrors module as well, not sure if that's something that you know about.

eri0o

For full wordle, if desired to have a word of the day feature, you can have a list of words, and then get a random word using a seeded random, and use the day information as seed, so that everyone that loads the wordle game gets the same word.

tilapisha

I am trying to work at the logic of turning yellow,

for i in xyzzy;
    if guess == xyzzy
        format green
    else if guess in xyzzy && x.count <1 && y.count <2 && z.count<2
       format yellow
    else
       format gray

I could hard code this, and I believe it would work, but I am having trouble with it not hardcoding it.
   

tilapisha

#23
Code: ags
      WordleFieldState[] checkGuess(String guess, String correctSolution)
      {
        if(guess.Length != WORD_LENGTH || correctSolution.Length != WORD_LENGTH)
        {
          AbortGame("Invalid Wordle data");
        }
        
        WordleFieldState format[];
        format = new WordleFieldState[WORD_LENGTH];
        int xCount = 0;
        int yCount = 0;
        int zCount = 0;
        
        for(int i=0; i<WORD_LENGTH; i++){
          if (guess.Chars[i] == 'X' || guess.Chars[i] == 'x'){
            xCount++;}
          else if (guess.Chars[i] == 'Y' || guess.Chars[i] == 'y'){
            yCount++;} 
          else{
            zCount++;}
            
         if(guess.Chars[i] == correctSolution.Chars[i]){
            format[i] = eWordleFieldGreen;
          }
        else if(correctSolution.IndexOf(String.Format("%c",guess.Chars[i])) != -1 && (xCount < 1 || yCount <|| 2 && zCount < 2)){
          format[i] = eWordleFieldYellow;}
        else{
          format[i] = eWordleFieldGray;
        }
        return format;
      }
     }
      }

Snarky

#24
The right way to code this is to follow Khris's outline:

Quote from: Khris on Sun 10/04/2022 18:54:00
Start with green and remove all matching letters from the guess and the solution (you can use an array to store the letters and replace them with a space character for instance).
Next, iterate over the remaining characters of the guess; if the character exists in the remaining characters of the solution, it's yellow. Again, remove the yellow match from both guess and solution. The remaining letters of the guess are grey.

Code: ags
WordleFieldState[] checkGuess(String guessWord, String solutionWord)
{
  if(guessWord.Length != WORD_LENGTH || solutionWord.Length != WORD_LENGTH)
    AbortGame("Invalid Wordle data");

  WordleFieldState format[];
  format = new WordleFieldState[WORD_LENGTH];

  // Make sure we ignore case:
  guessWord = guessWord.LowerCase();
  solutionWord = solutionWord.LowerCase();

  // Color the green fields (and the other fields gray):
  for(int i=0; i<WORD_LENGTH; i++)
  {
    if(guessWord.Chars[i] == solutionWord.Chars[i])
    {
      format[i] = eWordleFieldGreen;
      solutionWord = solutionWord.ReplaceCharAt(i, ' ');  // Remove the letter from the solution so we don't match on it again
    }
    else
      format[i] = eWordleFieldGray;
  }

  // Color the yellow fields:
  for(int i=0; i<WORD_LENGTH; i++)
  {
    if(format[i] != eWordleFieldGreen) // Don't process letters that are already matches
    {
      hitIndex = solutionWord.IndexOf(String.Format("%c",guessWord.Chars[i]));
      if(hitIndex != -1) // This means the solution contains the letter in the guess
      {
        format[i] = eWordleFieldYellow;
        solutionWord = solutionWord.ReplaceCharAt(hitIndex, ' ');
      }
    }
  }

  return format;
}


(I haven't tested this code, so it may have minor errors. Instead of removing matching letters also from the guess, as Khris suggests, in the second pass I just check whether a particular letter is already a match, and skip it in that case.)

Notice also the formatting of the code, and how it is indented. This is an important part of programming, because otherwise your code becomes an unreadable mess, making it easy to make mistakes that are hard to find. The rules for formatting and indenting code are pretty simple:

Spoiler
1. Code that isn't contained by some larger structure is not indented. Here, that applies to the function declaration and the curly braces that wrap up the content of the function:
Code: ags
WordleFieldState[] checkGuess(String guessWord, String solutionWord)
{
  // ...
}


2. Any time you have some block of code that is contained inside a structure, it is indented one tab level. By AGS default, a tab level is two spaces. This applies to anything inside a function, a for loop, an if or else block, or the contents of a struct or enum. The curly braces that wrap around blocks of code stay at the level of indention of the outside:

Code: ags
WordleFieldState[] checkGuess(String guessWord, String solutionWord)
{
  // ...

  for(int i=0; i<WORD_LENGTH; i++)
  {
    if(guessWord.Chars[i] == solutionWord.Chars[i])
    {
      format[i] = eWordleFieldGreen;
      solutionWord = solutionWord.ReplaceCharAt(i, ' ');
    }
    else
      format[i] = eWordleFieldGray;
  }
  // ...
}


Some people put the opening curly brace { at the end of the line above, but I think it is more consistent and convenient to have it on a line of its own. That way you can visually line up the closing curly brace with the opening one, and easily find which block it belongs to. (See how the braces for the if-block and for the for-loop in the sample above visually enclose their contents, because they line up vertically and every line in between them starts further to the right.) It also ensures that you don't forget it, which can lead to hard-to-find bugs. You should never have the closing curly-brace on the same line as the code.

3. If a "block" of code consists only of a single line, you can omit the curly braces. See the "else"-line in the sample above.

The rules for how it all works is pretty much like a list with sub-bullets.

The AGS script editor does a lot of this formatting for you automatically. If you write a {, it will automatically indent the next line one tab level, for example. You can also increase or decrease the indentation of several lines at a time by selecting them and hitting Tab (to increase the tab-level) or Shift+Tab (to decrease the tab-level).
[close]

Khris

Here's a function that returns an array of color indices:
Code: ags
int[] CheckGuess(String solution, String guess) {
  char s[] = new int[5];
  char g[] = new int[5];
  int r[] = new int[5]; // 0: gray
  for (int i = 0; i < 5; i++) {
    s[i] = solution.Chars[i];
    g[i] = guess.Chars[i];
    if (s[i] == g[i]) { 
      r[i] = 2; // 2: green
      s[i] = ' ';
      g[i] = ' ';
    }
  }
  for (int i = 0; i < 5; i++) {
    if (g[i] != ' ') {
      for (int j = 0; j < 5; j++) {
        if (s[j] == g[i]) {
          r[i] = 1; // 1: yellow
          s[j] = ' ';
          break;
        }
      }
    }
  }
  return r;
}


CheckGuess("XYZZY", "XBYDE") for instance will return [2, 0, 1, 0, 0]

Snarky

Not sure why you saw a need to post pretty much exactly the same solution as the post above, Khris.  ???

eri0o

@Snarky, more options doesn't hurt. :P

Snarky

Sure, but this isn't really a different option. It's the same solution, with only very minor differences:

-It skips any kind of validation of the input, and does not ensure that the matching is case-insensitive
-It copies the input strings into a couple of char arrays and modifies/loops over these instead of using String.ReplaceCharAt() and String.IndexOf()
-The skip-condition on the second loop checks whether the "guess" character at that index has been blanked instead of whether the result array has been set (but these tests are equivalent)
-Code formatting, variable naming and comments, and using hardcoded ints instead of (already declared) constants and enums

So it puzzles me why Khris would feel the need to redo the work of the post right above his and post it. It suggests he thought there was something about it that was not good.

Khris

Not at all, I just wanted to post a function that is completely decoupled from custom structs so people can use it independently.
I didn't try to one-up you in any way, although I get why it would look that way.

Yes, it's the same solution because it uses the same algorithm I outlined earlier.

Snarky

All right, cool.

I might contribute a little bit still about formatting the display (particularly the keyboard, which is still TBD) and checking a wordlist.

tilapisha

#31
Code: ags
          if(guess.UpperCase() == solution.UpperCase())
        {
          WordleSolved = true;
          Wait(80);
          gWordle.Visible = false;
          cClown.Say("Ahhhh! XYZZY. I should have known. Well, see you Goby. Good luck out there! I've gifted you my goggles and flippers, you'll automatically put them on when needed. Keep them, no use for a coin like me. Go left, brother.");
        }
          currentGuess++;
          currentInputLetter=0;
          
          if(currentGuess >= MAX_GUESSES)
          {
            gWordle.Visible = false;
            cClown.Say("Do you know how to play Wordle? Green is correct letter, correct spot. Yellow is correct letter, wrong spot. Try again!" );
            gWordle.Visible = true;
          }
        }
      }
    }
  }
}



In between line 14/15, I would like to reset the board. This doesn't seem to work.

Code: ags
 if(guess.UpperCase() == solution.UpperCase())
        {
          WordleSolved = true;
          Wait(80);
          gWordle.Visible = false;
          cClown.Say("Ahhhh! XYZZY. I should have known. Well, see you Goby. Good luck out there! I've gifted you my goggles and flippers, you'll automatically put them on when needed. Keep them, no use for a coin like me. Go left, brother.");
        }
        
          currentGuess++;
          currentInputLetter=0;
          
          if(currentGuess >= MAX_GUESSES)
          {
            gWordle.Visible = false;
            cClown.Say("Do you know how to play Wordle? Green is correct letter, correct spot. Yellow is correct letter, wrong spot. Try again!" );
            for (int i = 0; i<currentGuess; i++){
              for (int k = 0; k<currentInputLetter; k++){
               currentInputLetter--;
               WordleGrid[currentGuess].LetterField[currentInputLetter].Text = "";
            }
              currentGuess--;
            }
            gWordle.Visible = true;
          }
          }


tilapisha

Quote from: Snarky on Wed 13/04/2022 12:52:30
So, I did a little more on this. First, I added an enum to keep track of the field states, for formatting purposes:

Code: ags
enum WordleFieldState
{
  eWordleFieldBlank = 0,      // Nothing entered
  eWordleFieldEntry,          // Filled field in current row during editing
  eWordleFieldInvalid,        // Feedback that the word is invalid
  eWordleFieldGray,           // A gray field in a submitted row (letter not in word)
  eWordleFieldYellow,         // A yellow field in a submitted row (letter in word in other position)
  eWordleFieldGreen           // A green field in a submitted row (letter in word in same position)
};


Then I added a couple of functions to check your guess once it is submitted:

Code: ags
bool checkValid(String guess)
{
  // TODO: Just a placeholder. Need to add check against dictionary of valid guesses
  return true;
}

WordleFieldState[] checkGuess(String guess, String correctSolution)
{
  if(guess.Length != WORD_LENGTH || correctSolution.Length != WORD_LENGTH)
    AbortGame("Invalid Wordle data");
  WordleFieldState format[];
  format = new WordleFieldState[WORD_LENGTH];
  for(int i=0; i<WORD_LENGTH; i++)
  {
    if(guess.Chars[i] == correctSolution.Chars[i])
      format[i] = eWordleFieldGreen;
    // TODO: Replace this by something more complicated
    else if(correctSolution.IndexOf(String.Format("%c",guess.Chars[i])) >= 0)
      format[i] = eWordleFieldYellow;
    else
      format[i] = eWordleFieldGray;
  }
  return format;
}


Note that these functions are just placeholders! The second doesn't implement the full rules of when fields are marked yellow (because that's more complicated), and the first one doesn't really do anything at all, but it's where we would put the check for whether the word submitted is a valid guess or just "cnofi"-style nonsense.

Now we can put this into the on_key_press() logic:

Code: ags
    else if(keycode == eKeyReturn)
    {
      if(currentInputLetter == WORD_LENGTH) 
      {
        String guess = WordleGrid[currentGuess].ToString();
        if(guess.UpperCase == solution.UpperCase)
        {
          // TODO: Win!
        }
        if(checkValid(guess))
        {
          // Update state
          WordleFieldState formatRow[] = checkGuess(guess, solution);
          for(int i=0; i<WORD_LENGTH; i++)
          {
            switch(formatRow[i])
            {
              case eWordleFieldGreen:
                WordleGrid[currentGuess].LetterField[i].TextColor = Game.GetColorFromRGB(0, 128, 0);
                break;
              case eWordleFieldYellow:
                WordleGrid[currentGuess].LetterField[i].TextColor = Game.GetColorFromRGB(128, 128, 0);
                break;
              case eWordleFieldGray:
              default:
                WordleGrid[currentGuess].LetterField[i].TextColor = Game.GetColorFromRGB(64, 64, 64);
                break;
            }
          }
          currentGuess++;
          currentInputLetter=0;
          if(currentGuess >= MAX_GUESSES)
          {
            // TODO: Lose!
          }
        }
        else
        {
          // TODO: Feedback: invalid word
        }
      }
    }


Again, using text color for the feedback is just a placeholder. Later on we would change this to actually change the background color of the field.

The game is here: https://tilapisha.itch.io/gobyworld and the password is gummy. I'm running some user testing now.

I would like some feedback when the user solves the Wordle, for some reason it doesn't turn green but goes straight to the solution solved. How do I fix this?

eri0o

How do I reach the wordle in the game? I only managed to put the suit.

tilapisha

Hi, I just fixed an error I had with the suit (you shouldn't be able to put it on until the end).

The clown coin should instruct you what to do, once you've collected all 3 items (suit, snowglobes and dog food), he should present you with the Wordle.

eri0o

Am I supposed to have more cursors than the hand? Like, do I need any other type of interactions beyond left click using the hand cursor? I could not find the clown coin.

heltenjon

#36
You walk by right-clicking. Or by using the arrow/cursor keys.

I played through it to the ending, but didn't manage to solve the wordle. Will try again to see what happens then.  ;)

Edit: Gee, I don't know how I managed to miss that. But okay, I see your point. Nothing really happens after the wordle, and you want it to show properly that the solution is correct. And probably add the option to replay and fix it so you don't get the reward if you lose. I'll leave helping with the coding to the experts. (BTW, the second time I took the dog with me to the ending. I don't think you intended that.)

eri0o

@tilapisha, if you are using anything other than left mouse click, please mark in itch.io that the html game doesn't support mobile devices. I was trying to play on my phone, hence the confusion.

tilapisha

Quote from: eri0o on Mon 25/04/2022 22:13:04
@tilapisha, if you are using anything other than left mouse click, please mark in itch.io that the html game doesn't support mobile devices. I was trying to play on my phone, hence the confusion.

Thanks, just changed it. Let me know if you figure out the Wordle solve.

eri0o

#39
I did, but unfortunately in my first try because...

Edit: in the code here: https://www.adventuregamestudio.co.uk/forums/index.php?topic=59873.msg636645516#msg636645516

Line 5 of the top code there, you make the GUI Visible be set to false before the clown says something

Code: ags
          gWordle.Visible = false;
          cClown.Say("Ahhhh! XYZZY. I should have known. Well, see you Goby. Good luck out there! I've gifted you my goggles and flippers, you'll automatically put them on when needed. Keep them, no use for a coin like me. Go left, brother.");


Instead you should reverse that:

Code: ags
          cClown.Say("Ahhhh! XYZZY. I should have known. Well, see you Goby. Good luck out there! I've gifted you my goggles and flippers, you'll automatically put them on when needed. Keep them, no use for a coin like me. Go left, brother.");
          gWordle.Visible = false;


In this way the cClown will block and the player will be able to see they got the word correctly guessed.

I actually would break those lines so it's easier to read...

Code: ags
          cClown.Say("Ahhhh! XYZZY.");
          cClown.Say("I should have known.");
          cClown.Say("Well, see you Goby. Good luck out there!");
          cClown.Say("I've gifted you my goggles and flippers, you'll automatically put them on when needed. Keep them, no use for a coin like me.");
          cClown.Say("Go left, brother.");
          gWordle.Visible = false;


Also, it should not be possible to enter back to wordle GUI screen anymore after you won it once, or the GUI needs some way to exit it.

Spoiler
Also, not sure what to do with the ladder behind the mirror... Going up doesn't do anything...
[close]

SMF spam blocked by CleanTalk