Bryant races against the clock to build a real-time leaderboard for the Directus Arcade game – Duckin' Cold Emails. Watch as he builds features to submit and display high scores for the game using Directus Realtime and websockets.
Speaker 0: Hi. Welcome back to another episode of 100 Apps, 100 Hours. I'm your host, Brian Gillespie, and today we are building something that I think is pretty cool. But here on 100 Apps 100 Hours, we build your suggestions, your favorite apps, or rebuild a clone of some other app in 1 hour or less, or publicly fail, get humiliated, trying. It was a lot of stress, to be honest with you.
So the rules for this, number 1, we have 60 minutes to plan and build an application, no more, no less. And number 2, just use whatever you have at your disposal. So, could be AI, could be past projects, whatever we've got. We're just trying to speed run through applications. And today we've got a cool one.
We're going to be building a real time leaderboard. So recently, as far as, like April Fools' Day, we put out this amazing game to make you a duckin' pro at cold email. So it's called duckin' cold emails. It's just a runner game where you face off against a sales rep who is sending you cold emails and pop ups on the screen here. So you duck or jump, you get a couple of lives, and at the end of it you can choose to play again, and you get a nice little score.
So what we're going to do here is create a leaderboard for this, and riggity jig, away we go. Alright. So, 60 minutes to plan and build. Hopefully we don't need all of that, but maybe we will. That's how this works.
Alright. So I usually like to start by mocking out the functionality that we need out of this. So what do I really want to happen here? When I play, I wanna be able to submit my score and if somebody else is playing around the world, I want that score to and they get a high score, I want that to pop up. I wanna see that.
So we've got a real time tracking of high scores. This is really large. Let's just trim that down. Real time tracking of high scores, submit a username and message with that, maybe? Maybe we'll make this a little more competitive and that we can talk trash to each other while we do it.
What else is there really? Display those scores. Display high scores. I'm gonna put maybe sound effects. I'm gonna put that in question mark.
Maybe that'll be our stretch goal. So that's that's basically the functionality of this. Right? I don't know explicitly how long that's gonna take, but what do we have as far as a data model? That's one of the other things that I like to map out.
We've got some actual players, but, you know, I'm not going to have a user that needs to log in for this. I'm just going to let we're going to do it NBA Jam style, where you can just key in your initials or something like that. So I'm just thinking here, there's probably only maybe just a single table for this, which is rare for us on a 100 apps to a 100 hours. We're going to have a value for the score, a username, there's gonna be an ID and a time stamp of of when that high score was, and then maybe a message. That seems like it's it to me.
I don't wanna make this over complicated. Alright. So I'm gonna go into my direct assistance. This is totally blank. We're gonna create a new table.
We're gonna call it scores. Can I zoom in on this? Yeah. There we go. We'll make it really fancy.
We'll do the generated UUID. We've got date created. So this is a system field or optional system field that will basically whenever this item gets created it will pre populate some stuff for me. So basically what it does on create of this record in this collection, it will save the current date and time. So we'll unhide this because I do want to see that, And then we'll add a value.
So as far as the number of emails ducked, I can't duck half an email. So that's gonna be an integer. Looks great. What else do we have? Maybe we'll make that half width.
And a username. So go sweet handle. Cool. And then we're gonna have a message. So we'll just use the text area for that.
The type is text, of course, so I could add, like, markdown or something for that if I wanted to, but we'll just keep this short and sweet. Alright. So as far as our data model, is that that's probably it. Right? Looks good.
Getting on my toes here for this one. Alright. So we got this, specific application. It's basically just a Nuxt single page application. If I look, I've got a single index page, there's an arcade cabinet component that is doing most well, it's not really doing hardly any of the work it's just presentation.
It gives us this nice arcade cabinet and some text down below. And then we also have oh, these were my buttons I was trying the last time I was in here. And then we have a game component. So the library that we're using for the actual game here is pretty interesting itself. You might go ahead and check it out if you are trying to build a game.
Did we start the time? Oh, yeah. We did start the timer. Okay. It's called Kaboom JS.
A pretty interesting library, actually. Makes it really easy to build quick little mini games like this, build stuff that's fun for your users. Alright. So that's the meat and potatoes of it. You know, there's some sprites, there's some functions, there's a couple of different scenes inside the game, but that's what we've got, right?
So we've got our scores, let's go about setting up our real time connection. So Directus has real time baked into the platform. If I pull up my other project that I'm actually connected to, you can see I have enabled my WebSockets here. And away we go. Alright, so as far as the Knox application, I've just got a simple Directus plug in that provides me with a real time client.
That's really all we've got. I've set the auth mode to public just so I've got, you know, anybody can connect to this. Again, we're not going to make authentication required here. What I'm also going to do is the ability to read scores and push scores. Okay.
So what else do we have? I think that's pretty much it. Right? Let's dive in. Let's go to our arcade cabinet.
Pull this up. Right. This is our wrapper component. I'm not sure where I'm gonna stick the leaderboard. Maybe up here at the top, maybe somewhere down here at the bottom.
We could just cannibalize this bottom section down here. I'll zoom out in a little bit. But we're gonna get started with the SDK and WebSockets. So we've got a great guide on the documentation for this. It walks you through this in great detail.
But we're just gonna pull this up. I'm gonna pull that Directus client. Direct cuts. Directus. I can't spell.
Alright. We're gonna use the Nuxt app. Use Nuxt app composable to get that client. And then what we're gonna do is await directus dot connect. Alright.
So that will connect and if we keep scrolling down, we can see that we've got a, open function here. You know, if we just wanna log that this was opened. We do this and we go back. And if I look in the console, should be getting a message that this was opened. Switching protocols.
Not seeing any messages in here for whatever reason. Yeah. I don't see any particular messages, but let's keep moving on. We probably do need this directus dot message handler. Directus dot on web socket dot message console log events.
What if we just log the entire event? Actually, that'll be a message. Same thing. Let's see what we get here. Do we have that connection established?
Where is our connection? Why are we not seeing any messages through here? Right? Okay. Let's just add a subscription as well and see what we got.
Oh, could it be our permission settings? Right? Let's log in and see. Directory's password. Alright.
So if we look at no. We've got access to that. Alright. We've solved that problem already. Alright.
So let's just create a subscription and see if we get that. Alright. So we'll go in and do directus dot subscribe, and we're gonna subscribe to the scores, And we're gonna get, we're gonna pass some options to this. So we want the event to be create. The query do we actually need a query?
I don't think we let's not limit ourselves at this moment. And then maybe I wanna add, like, a UID to this, and we're gonna call this the scores sub. Subscription direct us dot subscribe. And let's see what we got here. Okay.
So now I can see that particular message coming through. There's our subscription for this. So we're actually getting some data. Let's take a look and see if we open this up in a separate window. So we'll just open scores up.
We'll create a new scores item, see if we get subscription for that. Yep. Okay. I see some data coming through. Great.
So we are getting some data for our scores, which is good. Not sure why it's not showing the open function. Not really concerned with that, anyway. Alright. So what we wanna do, just one moment, please.
We'll stop the clock. Okay. Had a hot call come in. We're restarting the clock. No extra work has been done, and I've totally lost track of where we were at in the process anyway.
Alright. So, we've got our subscription. We can see that come through. You know, if we create scores over here, 77. If we create these scores, we should not we'll still see those come through, which is great.
You know, we need to start working on our board. Alright. So let's flush this out a little bit more. Yeah. Maybe we want to grab the let's just work on our leaderboard first.
That's what we'll do. We'll get some UI rolling for this. I'm just gonna replace this down here at the bottom. So we'll just drop this text out. Great.
Just totally disappeared. And then we'll do let's add a add a heading for this. P high scores. Class font mono, font bold. Make this look really, really nice.
Yeah. Text 2 XL, Text center. Add some styling to it. See what we get. High score should be white.
We want all this to be white. Text white. Alright. There's our high scores. We'll go in and add some padding.
Oh, I don't wanna pad that. Let's add padding to this. Maybe we do p 6, p 8, something like that. Alright. And then let's do a list of our high scores.
So we'll do a list v 4 scores, 5 scores, and 7 scores ago. This will be grid. Grid calls 4, and then we'll just do, this is gonna be our score. Let's say 5. These could be a span, I guess.
I don't know what the rules are about putting a p tag inside there. Span, we got John Ross, then we've got the message, and then we've got a time stamp. 3 minutes ago. Alright. Let's see what that looks like.
Of course, it's not displaying. Let's put that text as lime green. Make it lime green. We'll make them bold as well. Do font mono.
Okay. Alright. And then we probably got well, actually, let's make this our header row. Right? So we got the score.
Got the user, And then we have the time. Alright. As far as our scores, we will make those extra large. Score, message, time. Great.
Good. Okay. So as far as our scores, right, we're gonna need an array of scores. So let's go up, and we'll just add that. So we got scores here.
Get your scores here. There's an array of those particular scores. And how do we, on a knit, we want to get those scores. Alright? So I could potentially fetch those via the REST client, but, let's use our put our heads together for this, and we'll do, we'll just send a message.
Right? So let's create a quick function. We'll we'll reuse the same connection. So we're creating a subscription there, but let's create get initial scores, get initial scores. It's great.
And then we're gonna do this where we say, direct us dot send message. So we're gonna send a message via WebSocket connection. We're gonna use the items type. We're going to use our read action, and then we have our collection of scores. We want to limit that to 10 scores.
That makes sense. And then maybe we add oh, I'm sorry. This needs to be wrapped in a query though. Limit 10. And then maybe we want to sort those by value.
So we're gonna get initial scores, and what? Okay. We'll just call get initial scores. Let's see what we've got when we open this up. So there's our data.
Right? Looks good. But and those are sorted in the correct order. Alright. So let's add a handler for this.
So maybe we add a UID for this as well so we can keep track of these. These are gonna be the initial scores. Alright. So if the event dot UID equals initial scores, we're going to populate the scores dot value with event dot data. Is it just dot data?
I think it should just be this. Right? Alright. So we open up our view dev tools. We go to arcade cabinet.
We'll just see. We've got some scores. Cool. Alright. So now that we are getting those scores, let's just iterate through those scores.
So we'll do the same thing with the list. And this one we're gonna add a v 4. So that'll be a score and scores. Key will be the score dot ID, not scores, just score dot ID. And then we'll start populating.
Score dot value. Why does it keep auto completing on me? Score dot username score dot message. And then the score dot timestamp. Alright.
So we got our scores. Those are a little huge, so maybe we shrink those down. And maybe this is actually we want the score to be larger. Tex x l. Cool.
And maybe we actually make these small. Okay. Alright. So I do have a function already, like a helper function in this. It's called get relative time.
It is the function. It just returns like a a relative time stamp. There we go. 8 minutes ago. Looks great.
Got our messages. We got our username. We got our scores. That's great. So now if we anytime we load this page, we're we're getting those initial scores.
And, you know, now we've got to like, how are we gonna submit our other scores? How are we doing on time here? We got about 40 minutes left. So we we probably wanna build a form at the end of the game to track those scores and and allow people to submit them. But one of the other things that I wanna do here is maybe we use a a computed prop to to show these as well.
We only want the latest ten scores maybe. Or, you know, we we could show the whole list, but then we're gonna have to resort those scores as well. So this is it might be a good thing that the computer prop is for. So we'll do, what, compute, const equals sorted scores equals computed. Oh, there we go.
GitHub Copilot to the rescue, so we're at scores dot value. And then maybe that's what we use to show. Sorted scores. Alright. Do we still get the same thing?
That's great. Okay. Awesome. And what else are we going to do? Maybe we add just a little bit of gap.
Gap y 2. Oh, no. It does. It's not gonna be a gap. Let's do this where we do, like, a space y 2 below them.
Do we need a divider there? Maybe we do have a divider. Class equals text at lime 500. I don't think that's having the effect that I want. Okay.
Regardless, there we go. We've got this function. Now we want to, whenever we play the game, right, we need to submit a score. So at the end here, I've got 0 emails. Maybe we add another button for our actual scores.
So we're gonna go into the game itself. Right. And what are we gonna do here? We're gonna add a couple things. Right.
We probably want to, the Kaboom library is based on Canvas, so we're not going to have like HTML elements there to work with. So I'm thinking we just maybe show a modal. I've got this Nuxt UI library included in this starter kit for this, And I do know that they have a little modal component that we could pop up. So let's roll with that and see what we got. Alright.
So inside the game, you can see here's all our variables for that. But let's just a knit a couple of of different variables up here at the top. We've got a, maybe like a show form. That'll be reactive. And then we have, like, a, what are we gonna call this?
I don't wanna call it score because Kaboom uses that internally. Maybe we just call this the player score, and we could use just like a reactive object for this instead of a ref. And so we have a value for the score, we'll set that to 0 by default. We've got a message for that and we've got a username. So this is gonna be the data that we submit, when the game is completed.
Alright. So scroll down. We'll probably need a submit function for that as well. So we'll do a function. And we don't have to use async here because we're gonna reuse that same WebSocket connection.
Right? So we got submit score. Let's go up here to the top. We're gonna get our directus client. Directus equals use Nuxt app from that plug in.
And inside here, let's do do we do like a try catch? Just in case there are errors. Cool. We are going to submit a message. Direct us, send message.
And here, the type is gonna be what? It will be items again. So we wanna send to the items. We're gonna do an action. I don't need to put that there.
Do action of create. The collection is gonna be scores, and then our data will be the player score. Okay? And then after that, we would not show the form, and then, yeah, maybe here we'll just console dot error any errors that we receive. Alright.
So that's the scaffold here for this. We're gonna need a template for this. So we'll have to add that modal to it. So we'll scroll down. Scroll down.
And then we we're gonna need to actually show that somewhere as well. So Canvas. Let's go back into the arcade cabinet. Class p t. Actually, gonna do do I need this?
Yes. Let's place that here. P t minus what was it? P t 24 fix. Alright.
So I'm gonna move that here. And can I just stick this model in there? U model v model equals show form. And do we want we don't want the overlay for this either. So let's do false on the overlay.
And we'll say form here. Alright. Does that mess with anything on our game? I don't think so. If we go into our game, we should see this variable somewhere in show form.
We can check the box, change that variable. Nope. Not updating it. But regardless, should be good. Okay.
Why why can't you edit that? True. Just always set to false. Weird. Alright.
Regardless, what we're we're gonna do now is, at the end of this game, we need to add a button to submit our scores. So we're gonna find the end scene. Is it end? No. What is this gonna be?
Countdown. Let's just start countdown. Okay. So we're looking for the lose scene. And here inside the lose scene, we've got a restart button.
We've got a text inside that button. So I'm just gonna copy these items here. Come down. This is gonna be the submit high score, score button logic just so we can comment. Let's call this the submit score button Submit.
Score button. Is it gonna be the same color button? Maybe we make it purple. I don't know what the RGB is for purple. Purple RGB.
Let's see. Like a blue violet. Okay. We'll roll with that. Not sure that's exactly what I want, but okay.
And then on the submit score button dot on click, we want to show form. Nobody ever calls me. Show form equals true for the value and then inside the actual text for this, we're gonna say submit score. And that will be where we've got that restart button. We're gonna do submit score button dotpos.
Okay. Alright. So if we did everything correct, hopefully, this will work. And we'll be able to see the submit score button. Okay.
It's now taking the place of our other button, but we need to fix that. So let's just move it out of the way. Submit score button. Let's move it to the other side so we could see this is width divided by 4. Let's do the width.
We'll just do the width of the canvas minus width divided by 4. Is that gonna get us what we want? We'll just quickly lose and and test to make sure. Lots of distractions on this episode as well. Alright.
So submit. We can see a form here. That's looking great. Let's wrap this inside a card. Ucard.
K. And then we're gonna build a form based on this. So, Nuxt has this UI library has some of these form elements. We're going to use a form group. We'll give it a label.
What are we going to have for the label? We'll have the username. And inside that we'll have an input. So u input v model player score dot username. Let's see what that looks like.
Okay. That looks nice. So also inside this, maybe we have a p, submit your score. Old text x l. Submit your score.
We got a username. And then we want to actually show the score as well. Right? So fontmonotext. Violet.
500. You deduct what? Player score dot value emails. Alright. So we're only showing that we ducked we ducked, just 0 emails.
Right? So we're probably gonna have to fix that. Maybe we don't even need that actually. We'll just show you doc 0 emails. Add some padding between these 2.
Just using space y 4, just a little helper class. Alright. Is that giving me what I want? No. It's not.
Something about the card space y 4. Okay. And then we're gonna have another form group for our message. Alright. We got our message.
You input that's actually gonna be you text area, v model. K. And what else do we need? We need some buttons. Submit, u button, cancel.
So we get 2 buttons. Let's make this size large. And this will be let's wrap this in a div. Just flex them, give a little bit of gap between the 2. And this will be variant, what, like a outline, maybe?
That's good. P class. K. And then we're gonna I don't know what happened to our spacing. Let's add that back.
Okay. So there's our form. We've got submit score. You deduct 0 emails. The other thing that we're gonna do here, if we submit this score let's just test it out and see.
Right? Bryant Ross. Submit. Are we actually getting the scores? We are not getting the score.
Is that score showing up though? We refresh indirectus. Okay. So we just see that score show up, but we're not populating that score to the scores array, which we need to sort. And then, also, if we submit the scores again, you can see we've got the same message.
So we probably want to reset the message every time. So let's just do this, player score dot, message equals null. Okay. So whenever we score, submit that score. Good.
What's the other thing that we need to do? We need to iterate this player score reactive value anytime we increase the score. So here's a score plus plus. What we're gonna do here is player score dot value equals the score. So after we iterate, we're going to plug that score, and then we need to actually populate our scores here as well.
Right? So if we go back to our arcade cabinet, we need another event handler here. So if the event dot UID equals scores dot sub, then we want to push that event data into the scores array, and that should trigger what we want. But, this is actually gonna be an array, so we can't really push that into the array. We need to destructure that.
So, thinking this should get us where we want to be. Event data is not iterable. Let's scope this down. Event dot event equals create. And this is confusing.
Right? Let's swap this out. Message. Change this over to message so that that is a little less confusing. Okay.
So now we're seeing those scores populate correctly. Let's just give this a shot and see what we got. Alright. We're facing off against John Ross. Duct a couple of emails.
We'll close this out. We'll submit our score. You ducked 4 emails. This is gonna be John Ross. Hi, guys.
And we submit, and now we can see that score being populated automatically. So that's pretty freaking awesome. Right? What else do we want to do with this? Maybe we animate it a little bit?
One of the libraries that I like to use that is actually included in this one is, v auto animate. Or, well, it's not, v auto. That's the directive for this. But it is, auto animate by the the fine folks at FormKit. So one line of code, you know, you've all seen this before.
You add logic to it. But this one, it auto animates for you, giving you kind of a nice UI. So if we try this again, test message. Yeah. Probably have to refresh the page after I save it.
But there we go. We've got our scores populating. These should be sorted in order as well. What do we have time left on the clock? We've got 22 minutes left.
You know, we could go through and build a dashboard for this, but let's just kinda recap where we're at. We've got the real time tracking of the scores. We've got to submit a username. We are displaying those high scores. Let's add some sound effects.
Let's just test it out one more time, make sure this is actually working first. Alright. Duck. Alright. Got 6, 7.
Let's just die right now at this moment. And this will be Bry Ross is my username. You stink John. And I take my place on the leaderboard. Cool.
Alright. So calling out when, you know, sound effects, maybe we could go in those. I'm not sure you're gonna be able to hear them though on this recording. So as far as a real time leaderboard, I I think we're calling that good. That is finished.
Pretty savvy. Where could we take this from here? Right? We could go a a lot of different directions with this. You know, expanding this for, like, user authentication, maybe pull in, like, a GitHub username, something like that.
But I'm pretty comfortable with this. I think I'm going to button this up and we'll probably actually ship this to the live version of this at directus. Is/arcade. I think this will look nice with a leaderboard on here. So that's it for this episode of 100 Apps, 100 Hours.
I think this may be the shortest episode yet. That's it though. That's good. In and out. I hope to see you on the next one.
Bye.