WORDLE in AGS?

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

Previous topic - Next topic

tilapisha

I was hoping to include having to solve the worldle (does not have to be the official one/ could be hard coded as one word) as the key to enter the next room.

I welcome any advice on how to do this, as I've just completed the AGS tutorial and am very much a beginner.

Much appreciated!  :grin:

Snarky

#1
Hmm… on second thought I think this proposal overlooks some key parts of Wordle, particularly that guesses have to be valid dictionary words. Let me get back to you…

Spoiler
Should be pretty easy. The hardest part is to deal with the whether to color a letter yellow, since the rules for that are a bit complicated (don't color yellow if the letter is already "taken" and occurs elsewhere in your guess as green or yellow, unless there are several of the same letter in the solution). Let's ignore that part for now.

(Also, I'm not doing "hard mode" where you have to use the letters you have already found. That's a bit more work.)

Let's set up some basic variables:

Code: ags
String correctWord="SNARK";
int currentGuess;


Now a function to check whether a letter in a guess is gray, yellow or green:

Code: ags
// 0=gray, 1=yellow, 2=green
int checkLetter(String guess, String solution, int i)
{
  if(guess.Chars[i]==solution.Chars[i]) return 2;
  if(solution.IndexOf(String.Format("%c",guess.Chars[i]))>=0) return 1;  // This is a simplification, doesn't check whether letter is "already taken"
  return 0;
}


Now let's say the player types their answer into a TextBox called inputText. When they press Enter, we run inputText_OnActivate():

Code: ags
function inputText_OnActivate(GUIControl* control)
{
  String guess = inputText.Text.UpperCase;
  guessedWords[currentGuess] = guess;
  if(guess.Length < 5) // Not enough letters, keep going
  {
    return;
  }
  if(guess.Length > 5) // Too many letters, ignore
  {
    inputText.Text = "";
    return;
  }
  if(guess == correctWord)
  {
    // TODO: Correct! Go to puzzle solved
  }
  else
  {
    // Show the feedback
    for(int=0; i<5; i++)
    {
      int col = checkLetter(guess, correctWord, i);
      // TODO: Display the letter in the appropriate color in the Wordle grid
    }
    currentGuess++;
    if(currentGuess>5)
    {
      // TODO: Out of turns! Go to puzzle failed
    }
  }
}


What's left to do now is just to display the Wordle grid to the player. One way to do that is on a GUI with a set of 5x6 buttons, where we set the Button.Graphic property to a sprite with the right background color and Button.Text to the letter.
[close]

Snarky

One thing to think about with this as an adventure game puzzle: what happens if the player fails the puzzle? If they cannot try again, then it must either be a death/game over (the player is caught by the security guards!), or you need some other way to get past this door. If they can simply try again, and the word is hardcoded, then it's essentially the same as having infinite guesses, and the puzzle becomes very easy. Or you need to make it so that the word changes each time.

tilapisha

I'm thinking using GUI's I could complete this.

Snarky

#4
Using GUIs for the output and input is certainly the easiest way, yes. The more interesting part is how to implement the rules of the game itself.

But to get you started: Create a GUI Window called WordleGUI with a 5x6 grid of equally sized square Buttons. This will be the Wordle grid. (We use buttons simply because they have the display features we need, not as something for the player to click on.) The code becomes much easier if you add the buttons in order (first button is the first letter of the first word, then the second letter and so on, and then the next row).

If you want to also have the feedback keyboard, you would make that out of Buttons as well, but I would suggest waiting to do that until you have the rest of it working.

Now let's link this GUI up to a basic representation of Wordle:

Code: ags
#define WORD_LENGTH 5
#define MAX_GUESSES 6

// This represents a row in the Wordle grid (IOW, a guess)
struct WordleGuess
{
   public Button* LetterField[WORD_LENGTH];
}

// This is our wordle grid: a set of Wordle rows
WordleGuess WordleGrid[MAX_GUESSES];

int currentGuess;
int currentInputLetter;

// This function just links up the buttons of the GUI with our Wordle grid
void SetupWordle()
{
  int control;
  for(int i=0; i<MAX_GUESSES; i++)
  {
    for(int j=0; j<WORD_LENGTH; j++)
    {
      WordleGrid[i].LetterField[j] = WordleGui.Controls[control].AsButton;
      control++;
    }
  }
}


You should run SetupWordle() on game start.
Now we can link the buttons up to keyboard input:

Code: ags
function on_key_press(eKeyCode keycode)
{
  if(WordleGui.Visible)
  {
    if(keycode >= eKeyA && keycode <= eKeyZ) // Typed a letter
    {
      if(currentInputLetter<WORD_LENGTH)
      {
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = String.Format("%c", keycode);
        currentInputLetter++;
      }
    }
    else if(keycode == eKeyBackspace) // Remove the last letter
    {
      if(currentInputLetter>0)
      {
        currentInputLetter--;
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = "";        
      }
    }
    else if(keycode == eKeyEnter)
    {
      if(currentInputLetter = WORD_LENGTH) // TODO: Check that word is a valid guess. Submit only if it is
      {
        // TODO: Check if guess is correct, update state of game
        currentGuess++;
        currentInputLetter=0;
    }
  }
}


This should let you type in guesses. It won't actually check what you've typed or anything, but once you have this set up you can start adding in the Wordle rules.

I would suggest doing the colors by having different sprites with the gray, yellow and green squares, and changing the Button.Graphic to the appropriate sprite.

tilapisha

This is so helpful! The wordle I'm going to use is XYZZY  :-D

Khris

Regarding the coloring of the guess:

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.

tilapisha

#7
I'm setting up a prototype of this in a new game, trying to get this to work before implementing it in the actual game files. I'm having trouble implementing it. I'm going to hardcode the correct guess as "XYZZY".

Code: ags
// main global script file
int wordLength = 5;
int maxGuesses = 4;
 
// This represents a row in the Wordle grid (one guess)
struct WordleGuess
{
   public Button* Letterfield1[wordLength];
}
 
// This is our wordle grid: a set of Wordle rows
WordleGuess WordleGrid[maxGuesses];
 
int currentGuess;
int currentInputLetter;
 

// called when the game starts, before the first room is loaded
function game_start()
{

// This function just links up the buttons of the GUI with our Wordle grid
void SetupWordle()
{
  int control;
  for(int i=0; i<maxGuesses; i++)
  {
    for(int j=0; j<wordLength; j++)
    {
      WordleGrid[i].Letterfield1[j] = gWordle.Controls[control].AsButton;
      control++;
    }
  }
}
}

// called on every game cycle, except when the game is blocked
function repeatedly_execute()
{
}

// called on every game cycle, even when the game is blocked
function repeatedly_execute_always()
{
}

// called when a key is pressed
function on_key_press(eKeyCode keycode)
{
  if(WordleGui.Visible)
  {
    if(keycode >= eKeyA && keycode <= eKeyZ) // Typed a letter
    {
      if(currentInputLetter<wordLength)
      {
        WordleGrid[currentGuess].Letterfield1[currentInputLetter].Text = String.Format("%c", keycode);
        currentInputLetter++;
      }
    }
    else if(keycode == eKeyBackspace) // Remove the last letter
    {
      if(currentInputLetter>0)
      {
        currentInputLetter--;
        WordleGrid[currentGuess].Letterfield1[currentInputLetter].Text = "";        
      }
    }
    else if(keycode == eKeyEnter)
    {
      if(currentInputLetter = wordLength) // TODO: Check that word is a valid guess. Submit only if it is
      {
        // TODO: Check if guess is correct, update state of game
        currentGuess++;
        currentInputLetter=0;
    }
  }
}


On line 8, I'm getting an error of "expected variable type". I also made each button a text box button, and named them "letterfield1, lettefield2"...etc.



This is the GUI I've drawn, and I'm planning to put 20 buttons. So far, I'm working on the first row so only have 5 buttons.

Snarky

OK, so a few things about your code.

First, skip the public inside of WordleGuess. That's my mistake, I forgot you don't need it in AGS.

Second, you can't have wordLength and maxGuesses as int variables. Since they are used to set array lengths, they must be constants, so do it the way I showed in the example.

Third, you changed the name of the array from Letterfield to Letterfield1. AGS won't care either way, but this name is wrong. The array isn't just the first letter field, it is all of them.

Fourth, you have put the function SetupWordle() inside game_start(). That's not how functions work. You have to put them separately, and then you call SetupWordle() from game_start(). (Here's where I would recommend breaking up the Wordle code into a separate module, but we can do that later.)

Fifth, you say:

Quote from: tilapisha on Mon 11/04/2022 04:44:49
I also made each button a text box button, and named them "letterfield1, lettefield2"...etc.

So, I said to make them Buttons, not TextBoxes. A TextBox will not work for what we need. So delete them and start over, using Buttons. It won't look the way you want in the editor (or in-game initially), but we will be able to fix that later.

Also, while you can name them what you want, avoid using the names of any variables in the code. We will not be referring to these Buttons by name, but use the GUI.Controls[] array to loop through them. And, since this is a common misunderstanding about how names work, I'll point out that there's absolutely no way to use a pattern in the variable names like letterfield1, letterfield2, … to loop through them. IOW, you cannot do anything like this:

Code: ags
// WRONG!
for(int i=1; i<15; i++)
{
  Button* a = "letterfield" + i;
  a.Text = ""; 
}


The names are just labels, and they cannot be interpreted or manipulated in the code in any way. That's why we use the GUI.Controls[] array instead.

Which brings me to the final point. It's a nice drawing of the GUI, but I want to remind you that you should start only with the Wordle grid (that means not adding things like the label at the top yet). The code depends on the order in which you added things to the GUI, and only works if the Wordle grid buttons are the absolutely first things you added.

tilapisha

Gotcha, I appreciate all this info. I will instate it. As for the GUI, it's all a background image. So, the only thing on the GUI will be the buttons for the grid.

tilapisha

I've having some errors on line 71, and when I comment out the keycode Return else if statement (eKeyReturn instead of eKeyEnter, which throws an error), also line 25 .

71: Parse error with currentInputLetter, but I tried adding them to the Global variables. Still error.
25:  Will run the game, but with call stack error on 25 and 35.  "Error running function'game_start': and Null pointer referenced

I know basic programming in Python, HTML/CSS JavaScript and Java, but please forgive me as I do not know C++ well. Let me know if I should check out other resources before posting more on here, and I would also be very open to a zoom call or something of the sort if it's easier! <3 Lisha

Code: ags
// main global script file
#define WORD_LENGTH 5
#define MAX_GUESSES 6
 
// This represents a row in the Wordle grid (one guess)
struct WordleGuess
{
   Button* LetterField[WORD_LENGTH];
};
 
// This is our wordle grid: a set of Wordle rows
WordleGuess WordleGrid[MAX_GUESSES];
 
int currentGuess;
int currentInputLetter;

// This function just links up the buttons of the GUI with our Wordle grid
void SetupWordle()
{
  int control;
  for(int i=0; i<MAX_GUESSES; i++)
  {
    for(int j=0; j<WORD_LENGTH; j++)
    {
      WordleGrid[i].LetterField[j] = gWordle.Controls[control].AsButton;
      control++;
    }
  }
}


// called when the game starts, before the first room is loaded
function game_start()
{
SetupWordle();
}

// called on every game cycle, except when the game is blocked
function repeatedly_execute()
{
}

// called on every game cycle, even when the game is blocked
function repeatedly_execute_always()
{
}

// called when a key is pressed
function on_key_press(eKeyCode keycode)
{
   if(gWordle.Visible)
  {
    if(keycode >= eKeyA && keycode <= eKeyZ) // Typed a letter
    {
      if(currentInputLetter<WORD_LENGTH)
      {
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = String.Format("%c", keycode);
        currentInputLetter++;
      }
    }
    else if(keycode == eKeyBackspace) // Remove the last letter
    {
      if(currentInputLetter>0)
      {
        currentInputLetter--;
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = "";        
      }
    }
    else if(keycode == eKeyReturn)
    {
      if(currentInputLetter = WORD_LENGTH) // TODO: Check that word is a valid guess. Submit only if it is
      {
        //TODO: Check if guess is correct, update state of game
        currentGuess++;
        currentInputLetter=0;
      }
    }
  }
}

Khris

Code: ags
  if (currentInputLetter == WORD_LENGTH)


You need == for comparions.

tilapisha

Still throwing an error on lines 25 and 35 now. The null pointer, going to examine it carefully.

Khris

Did you create 30 buttons in your gWordle GUI?

tilapisha

It's working!

Code: ags
// main global script file
#define WORD_LENGTH 5
#define MAX_GUESSES 4
 
// This represents a row in the Wordle grid (one guess)
struct WordleGuess
{
   Button* LetterField[WORD_LENGTH];
};
 
// This is our wordle grid: a set of Wordle rows
WordleGuess WordleGrid[MAX_GUESSES];
 
int currentGuess;
int currentInputLetter;
 
// This function just links up the buttons of the GUI with our Wordle grid
void SetupWordle()
{
  int control;
  for(int i=0; i<MAX_GUESSES; i++)
  {
    for(int j=0; j<WORD_LENGTH; j++)
    {
      WordleGrid[i].LetterField[j] = gWordle.Controls[control].AsButton;
      control++;
    }
  }
}
// called when the game starts, before the first room is loaded
function game_start()
{
  // initialize gPanel controls
  initialize_control_panel();

  // set KeyboardMovement movement mode
  KeyboardMovement.Mode = eKeyboardMovementModeTapping;

  // set KeyboardMovement keys
  //KeyboardMovement.KeyUp = eKeyW;
  //KeyboardMovement.KeyDown = eKeyS;
  //KeyboardMovement.KeyLeft = eKeyA;
  //KeyboardMovement.KeyRight = eKeyD;
  
  SetupWordle();
  
}

// called on every game cycle, except when the game is blocked
function repeatedly_execute()
{
  cMallory.FollowCharacter(cGoby);
}

// called on every game cycle, even when the game is blocked
function repeatedly_execute_always()
{
}

// Called when a dialog script line "run-script" is processed
function dialog_request(int param)
{
}

function show_inventory_window()
{
  mouse.Mode = eModeInteract;
  open_gui(gInventory);
}

// called when a key is pressed
function on_key_press(eKeyCode keycode){
  
  if (IsGamePaused() || !IsInterfaceEnabled())
  {
    // game paused, so don't react to any keypresses
    keycode = 0;
  }
  else if (keycode == eKeyCtrlQ)
  {
    // Ctrl-Q will quit the game
    open_gui(gExitGame);
  }
  else if (keycode == eKeyF9)
  {
    // F9 will prompt to restart the game
    open_gui(gRestart);
  }
  else if (keycode == eKeyF12)
  {
    // F12 will save a screenshot to the save game folder
    SaveScreenShot("screenshot.pcx");
  }
  else if (keycode == eKeyCtrlS)
  {
    // Ctrl-S will give the player all defined inventory items
    Debug(0, 0);
  }
  else if (keycode == eKeyCtrlV)
  {
    // Ctrl-V will show game engine version and build date
    Debug(1, 0);
  }
  else if (keycode == eKeyCtrlA)
  {
    // Ctrl-A will show walkable areas
    Debug(2, 0);
  }
  else if (keycode == eKeyCtrlX)
  {
    // Ctrl-X will let the player teleport to any room
    Debug(3, 0);
  }
  else if (keycode == eKeyReturn)
  {
    if (gRestart.Visible)
    {
      // Enter confirms a restart
      RestartGame();
    }
  }
  else if (keycode == eKeyTab)
  {
    // Tab opens the inventory
    show_inventory_window();
  }
  
  if(gWordle.Visible)
  {
    if(keycode >= eKeyA && keycode <= eKeyZ) // Typed a letter
    {
      if(currentInputLetter<WORD_LENGTH)
      {
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = String.Format("%c", keycode);
        currentInputLetter++;
      }
    }
    else if(keycode == eKeyBackspace) // Remove the last letter
    {
      if(currentInputLetter>0)
      {
        currentInputLetter--;
        WordleGrid[currentGuess].LetterField[currentInputLetter].Text = "";        
      }
    }
    else if(keycode == eKeyReturn)
    {
      if(currentInputLetter == WORD_LENGTH) 
      {
        currentGuess++;
        currentInputLetter=0;
      }
      
      //if(WordleGrid.LetterField[0].Text == "x")
      //{
        //WordleGrid.LetterField[0].TextColor = 2016;
      //}
      //else if(WordleGrid.LetterField[0].Text=="y");
      //{
        //WordleGrid.LetterField[0].TextColor = 65510;
      //}
   
    }
  }
}


I'm now trying to get the buttons to change color, starting with textColor for testing.
Follow along here: https://github.com/lishap/gobyworld

tilapisha

Getting an error of an expected "[". Am I referencing the button correctly? You can also see this in context on my GitHub link. Thank you.

Code: ags
 else if(keycode == eKeyReturn)
    {
      if(currentInputLetter == WORD_LENGTH) 
      {
        currentGuess++;
        currentInputLetter=0;
      
        if(WordleGrid.LetterField[0].Text == "x")
        {
        WordleGrid.LetterField[0].TextColor = 2016;
        }
        else if(WordleGrid.LetterField[0].Text=="y" || WordleGrid.LetterField[0].Text=="z");
        {
        WordleGrid.LetterField[0].TextColor = 65510;
        }
        else
        {
          WordleGrid.LetterField[0].TextColor = 26;
        }
      }
    }

Snarky

#16
WordleGrid is itself an array, so you you have to pick one entry first (the current row). In other words, WordleGrid[currentGuess].LetterField[j].Text (where j is some index variable).

However, you are going down the wrong path here by hardcoding the test letter by letter. What you should be doing is writing a function that will test it in general, according to the Wordle rules. That will end up being simpler in the end.

I wrote a version of such a test in the first response, the checkLetter() function (which checks letter by letter), but Khris is right in his later post: it needs to be a function that treats the whole word together, because whether a letter is yellow depends on other letters in your guess. His suggested approach also seems sound.

I would implement it as a function String checkGuess(String guess, String answer), where the output is a String like "00120" (with 1 for yellow, 2 for green, 0 for gray). Or it can output an array of ints if you prefer. You give it the guess and the right answer, it gives you the pattern of the colors, and then you update the display accordingly. You can start off by just having it identify the green letters, since that's easy. Then work on doing the yellow ones as Khris suggests once you have that working.

The first thing I would do, though, is to update the WordleGuess struct with a ToString() method:

Code: ags

struct WordleGuess
{
   Button* LetterField[WORD_LENGTH];
   import String ToString();  // <-- Add this line to the existing struct
};

String WordleGuess::ToString()
{
  return String.Format("%s%s%s%s%s", this.LetterField[0].Text, this.LetterField[1].Text, this.LetterField[2].Text, this.LetterField[3].Text, this.LetterField[4].Text); 
}


This way you can easily get the word out of each row of the Wordle grid: String guess = WordleGrid[currentGuess].ToString();

Snarky

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.

tilapisha

It's working! I made this append to line 6,
Code: ags
   if(guess.UpperCase() == solution.UpperCase())
. Because the solution "XYZZY" is not a 'valid' guess in the real Wordle, I will not need to be checking for validity.

Right now, I have added
Code: ags
 String solution = "xyzzy";
above the String guess declaration. Right now, the guess "XYZZY" is giving a yellow output, but I am working on that.

This is what I've written under the win case so far, and WordleSolved boolean unlocks the ability to enter the next room.
Code: ags
 if(guess.UpperCase() == solution.UpperCase())
        {
          WordleSolved = true;
          gWordle.Visible() = false;
        }

Snarky

Glad it's working! (More or less. XYZZY should certainly not show up as yellow if that's the solution.)

Quote from: tilapisha on Wed 13/04/2022 16:27:04Because the solution "XYZZY" is not a 'valid' guess in the real Wordle, I will not need to be checking for validity.

"XYZZY" may not be a valid guess in actual Wordle, but if you don't have some list of words that are allowed as guesses, people will be able to type literally any random combination of letters. This, it seems to me, rather negates the point of the game. There's no reason to not just enter AEIOU, YTRNS, and so on. It removes the skill challenge.

In any case, I'm interested in a complete implementation of Wordle in AGS.

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]

tilapisha

You have to put on the suit to go up the ladder! Should take you to a handful of other rooms!

tilapisha

Quote from: eri0o on Tue 26/04/2022 01:56:51
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.

Code: ags

       if(currentGuess >= MAX_GUESSES)
          {
            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;
            WordleSolved = true;
          }


I did this, and still didn't show the green.  :-\

tilapisha

@eri0o Did you ever figure out why the green feedback never worked? Lisha

SMF spam blocked by CleanTalk