Resources

Winston Chang || Part III: Adding a Keyboard to a Wordle Shiny App || RStudio

00:00 Introduction 00:25 Setting up a keyboard 00:54 Using an HTML p tag to print out letter indicators 01:56 Back to our keyboard! 03:44 Setting up a search and replace 06:32 Removing letters using regular expressions 08:43 Making guesses a reactiveVal() 11:00 Avoiding an infinite loop with reactiveVal() In Part III of this four-part series, Winston walks through how to build a keyboard in a Shiny Wordle app. Code + word list: https://github.com/wch/shiny-wordle Check out the full Shiny app here: https://winston.shinyapps.io/wordle/ You can learn more about Shiny here: https://shiny.rstudio.com/ Got questions? The RStudio Community site is a great place to get assistance: https://community.rstudio.com/ Content: Developer (@winston_chang) Animation, design, and editing: Jesse Mostipak (@kierisi) Wordle: https://www.powerlanguage.co.uk/wordle/

image: thumbnail.jpg

Transcript#

This transcript was generated automatically and may contain errors.

Okay, so that's the basic Wordle. One other thing that I really like about the real game is that it shows a keyboard. It shows a keyboard of what letters you've used and which ones match, so we can add something like that really quick. So I'm just going to keep it simple and make another text output like this and have that display the keys that are remaining.

So let's do another verbatim text output, call it keyboard, and also I just realized one other thing, which is that we need to give, we should give some indication what these brackets and parentheses mean. So we can do that by writing some, essentially just this is writing some HTML, this is like a p HTML tag. So let's just say bracket means that the letter is correct, and the phrase is correct. I'll do that. And another one, so parentheses x means that the letter is in the word, but in the wrong place.

Okay, so that should, yeah, so saves. Okay, so now we've added that information there and now we can add a keyboard showing the remaining letters. So let's say output keyboard. Let's do render text again, and we need all the letters. So I guess I'll just do it the old-fashioned way.

Setting up the keyboard display

So letters equals, and I think, what's the best way to do this? Let's just do paste. All right, so I'm making enough space around these letters to put like a bracket around it. Oh, do I need to do that? I don't need to do that right now, but I'm gonna do it anyway. And then this is gonna be shifted over a little bit. All right, and one more.

Okay, so those are our letters. That's our keyboard, and we're going to use a new line between each of those. Okay, actually let me call this keys, and so what I'm gonna do here is like, is really basic. I'm just gonna do a search and replace. So what I need to do is take the letters from all of the guesses, and I need to check if each one is in the keyboard. I'm doing this in a really simple way. I'm not gonna indicate on the keyboard like which letters are correct, or that you used and were correct, and which ones were not correct and not in the word. That's how the real Wordle game works. In this case, I'm just gonna indicate which letters you've used up and which ones are remaining.

Setting up a search and replace

So, and that's really easy. So I can just take all of the guesses. All right, I can say, um, let's see, used letters is, first let's, yeah, let's paste together all of the words in all guesses. All guesses, collapse equals that, and then we can split that into a character vector. Again, doing this, you know, string split, and then bracket one again, like the way we did it before, and then we can say used letters is unique, the unique elements in that.

All right, so just, you know, we can, let's just try this at the console. Let's just assign something to all guesses. Let's say this is, you know, first, second. Let's just say those were our guesses, and then we can step through here. So the used letters is those two words stuck together, then we split it into the individual letters. Okay, and then we take the unique ones out of there. So we should not see, in this case, there's two S's, and we should see those, one of those disappear.

Okay, so these are all the letters that we've used so far, and we just need to remove those from this keyboard. We'll just use that, we'll do that using a regular expression. So that should be, that's really easy. So let's say for letter in, used letter in used letters. Actually, let me just say letter, that's a little bit simpler. Okay, keys is, and we'll do, we use sub to replace letter, oops, not in quotes, the variable letter with an empty string, and that's in keys. And at the end, let's return keys.

Okay. Alright, let's see if it works. There's keyboard, and that's great. So let's say aisle, it did not do anything. So that, oh, you know why? Okay, here's the problem. So this didn't work, and that's because all guesses, there's not, this render text happened once, but there's nothing, because this is a reactive object, and there's nothing that's triggering the reactivity to re-execute, because all guesses is just a regular variable, it's not a reactive one.

Making guesses a reactiveVal()

So if we want changes to this all guesses variable to trigger this render text to change again, or to re-execute, and then update which keys are available. So there's sort of the correct way of doing it, and then there's the sort of, there's a hacky shortcut we could use, and one, the hacky shortcut we could use is to use, to have it listen to this go button, but sort of the correct thing to do is to look for, is to look for changes to the all guesses vector, and only, and re-execute when that changes. So let's make all guesses a reactive val, reactive val, which has the starting value of an empty character string.

Okay, and we need to update it here. I'm gonna split this into two lines just to make it a little clearer what's going on. So all guesses new is that, but because this is a reactive val, we need to actually, we need to sort of, we need to invoke it like a function to get the existing value out of it. We can't just, we can't just access it without the parentheses. So we're going to take the existing value for all guesses, append the new guess onto it, and we're going to save it in this local variable called all guesses new, and then we need to assign it back into all guesses. So, and that, the way we do that is all guesses, parentheses, all guesses new. I'm going to assign it back, and then when we, we use it down here, we'll just invoke it like a function. And because we're, we're using this reactive val from this render text, every time this thing changes, the value here changes, it will cause the render text to re-execute.

Okay, so let's try this again. I'll go, that totally did not work. Error in compare words. Oh, yeah, there we go. We need to call like a function there as well. All right, reload. I'll, great, and then saves. Great, and it's, as you can see, it's taking the keys out of here. And it gives, boom, and that's working correctly.

Avoiding an infinite loop with reactiveVal()

Now, there's one thing I want to mention about using the reactive val, which is that one thing that's happening here when we call all guesses, is that normally when we do this, because this is reactive, reactive val, this render text will take a dependency on all guesses. So whenever all guesses changes, it would re-execute. So if you, if you do this in most, in many cases, what can happen is you end up in this sort of reactivity loop where you keep appending something like, you keep appending something to all guesses, and then that causes render text to re-execute, and then it, you know, it grows again, and it keeps, it keeps going over and over again. We happen to not have that problem here because we're using the bind event.

We happen to not have that problem here because we're using the bind event.

So the only thing that will cause this render text to re-execute is when the input go button is pressed. And so when you do this bind event on the render text or on a reactive or observer, it makes it so that it doesn't listen for changes to the reactive, you know, reactive invalidations of the things that are inside of this block of code. So we're lucky in this case that, you know, that we don't have to worry about that sort of infinite loop happening.

If we did, you know, if we weren't using the bind event, and we're just using a render text by itself the normal way, then we could use a function called isolate. So we could say isolate, and then that would cause this render text to not take a reactive dependency on that reactive val. And we would have to do it here and here. So, but we don't have to because we're using the bind event.

So here we go. This is, yeah, this is our basic Wordle game. I mean, this is the basics of the game, and you can waste a lot of time playing it. I already have.