Kevin and Andrew migrate their snake to run entirely in Directus Automate's low-code builder Flows. Is this the right way to run a Battlesnake? Tune in and find out.
Kevin: Hello, and welcome back to we've got a catchphrase, Andrew. Are you ready? Ready, set, That's easy. We got 4 more We got 4 episodes. Exactly.
We're we're we're we're cutting our losses for this one. In this show, we are continuing to build a Battlesnake, and progressively enhancing it, using Directus and Devcycle. My name is Kevin. I work at Directus, which we'll be using a little bit today. And My
Andrew: name's Andrew, and I work at Devcycle, which we'll be using in a future episode. And this is Battlesnake.
Kevin: Yeah. Give us the give us the 90 seconds for people just dropping in now. What is Battlesnake?
Andrew: Alright. Battlesnake is the way you wish that you had learned how to, program in a new framework or language from the very beginning. And now as a senior developer, Imagine you were playing the old classic snake game where you go around a board trying to eat food and survive. Now transform that into a programming game where you have a web server that's communicating with the Battlesnake game engine to communicate through code and tell your snake whether to go up, down, left, or right, and to try and survive as long as possible.
Kevin: Yeah. And we do that by implementing a web server, which implements the Battlesnake API. We've already had one episode of Ready, Set, Battlesnake. So we're not going to recap everything we did. If you are still a little fuzzy on how this works, it will probably become clear.
But if you're completely fresh and you're like, I'm lost, go watch the first episode and watch a struggle through building the Node. Js starter snake. But today, we're gonna further build on top of that snake. Last episode, we built a battle snake based on the Node. Js starter, and we implemented here we are.
And we implemented some logic. That logic, what what did the logic do? The logic stop listening dying by going hitting into walls. That's good. Then we got the snake to not hit itself or any other snake parts, because that also is instant kill.
So that's all our snake does. Right? Yeah. Yeah. That's all our snake does.
And we're probably not gonna make our snake much more clever today, but we are now going to introduce a new tool into the stack and get it out of Replit and into another environment. Anything else you think that's important, Andrew?
Andrew: So the great thing is you can follow along with kind of all of the base code for all of this over on the GitHub repositories that exist around. Snake. So if you're watching this and you're like, I want to do it in my own language or framework, you can go and check out all the
Kevin: awesome Angular projects
Andrew: that are over there.
Kevin: So actually, let's take pause for a moment because this is important. What is a Battlesnake? So you have to implement a web server that has a few specific route handlers. The a few specific endpoints. Firstly, this just route route.
Right? And all this one does is it returns information about the snake, metadata about the snake. And we'll play around with that a little bit more today. The mode then we have the start endpoint, which, Andrew, I think that's the before the first move in any game. Right?
Andrew: So we got our start end point, which is great. We've got our root end point. What is the kind of most important endpoint of all the endpoints though, Kevin?
Kevin: It is this move endpoint. As we discovered last time, the move endpoint, every single turn of every single game will receive a request with the state of that game and the board, which includes every other snake, all the hazards, all the food. You have 500 milliseconds to respond with a string up, down, left, or right. And that is this is the most important endpoint. But you there is also an end endpoint, which is what you would potentially use to, like, wind down any resources or, you know, do any analytics storage, you know, and you just do it at the end.
So we have these 4 endpoints that we need to implement. Move is is kind of the most critical. I think only move in the the root are required for any given snake. So that's worth noting because that's gonna be important. So what we're gonna do today, we are going to be building this snake inside of Directus.
If you've not heard of Directus before, I hope you have because you're watching this on Directus TV. But Directus is is a back end toolkit to build applications. It has 2 parts. One part which is all around API generation from your database and asset storage, and the other side, which is a web application to kind of work with that data. There's a whole bunch of other stuff in there.
So we're gonna be implementing a Battlesnake inside of a director's project. So we'll we'll we'll kinda see how this shakes out over the next over the next little while. Now originally, until about 5 minutes before we started recording, I thought that we would perhaps use, Directus' automation UI tool in order to build our snake because it is it feels quite viable on the surface. It does. And then I realized it ain't it ain't viable.
And the reason it isn't is because one of the triggers that can begin a flow is a webhook, an inbound webhook, but it is unique, and randomly generated at the time the flow is created, which means we can't implement a root endpoint and then slash move, slash start, slash end. And that's gonna be a challenge. So rather than just trying to to power on through that, rather than sorry, rather than just trying to power on through that, we instead are gonna build an extension. Flows is effectively a UI layer or a UI version of, hooks of hooks. So, you know, we can we can do it in code.
We also have endpoints, which is probably more suited for this where we can literally create complete endpoints, and this is a no doubt. So we'd probably lift and drop a lot of our logic. This might end up being a bit of a lighter episode, but there would be nothing wrong with that. But I've done the thing. I've jinxed it.
Andrew: You have. I did it last time, though. So in fairness, we're we're each one in, and and so I feel like Indeed. This is way to do it. Indeed.
And so we're also we're gonna be implementing some some, like, very like, this whole rate limiting thing has caused us to have to think about a lot of other technologies as well too. Right? So it's not just this concept of of sort of how we're gonna implement Directus. It's also
Kevin: You're right.
Andrew: How it's gonna be able to interact with with the Battlesync platform. Right?
Kevin: Yes. So here here's the plan. Here's what I think the game plan is. We're gonna spin up, direct us locally using Docker. And that is an environment in which we can build extensions.
And we could then just deploy it somewhere and be happy days, you know, deploy it on some some web service, or we could just, like, open up a tunnel to our local machine. And ngrok is kind of the tool that we've as as developer educator types have have historically jumped to, so I think it will be the tool we use today. I also found out today I'm apparently still paying for it on a grandfathered plan that gives us the pro features. So, you know, I think it's one I might wanna
Andrew: Let's do it.
Kevin: Get off the card, but I'll use it today.
Andrew: I saw a I saw a really great, idea from someone recently just to, like, automatically, not only list all of the subscriptions that you have, but actually unsubscribe you from something if you haven't accessed it in a while. And I was like, that's a brilliant idea because I have so many things to subscribe for that I'd like to remove my credit card from.
Kevin: Yes. But I'm happy in this moment in this moment that it's working. Now one thing that's really nice here is the extension entry point. If we look at this, example, the way it's built is you give an endpoint an ID, which becomes the kind of root path. So this would be like our director's project slash greet, and then you can create the root, the slash move, the slash start, the slash end, and so on.
So this part should be pretty straightforward. Let's let's see, and if the docs end up being misleading, that's literally my job. So it's it will be incredibly self inflicted. So we'll we'll see. So we're gonna start by following the Docker guide for getting started with directors.
No. We're not. We're gonna look at the self hosted quick stuff, which which
Andrew: is super smooth. It is a very smooth Docker process, I will say. Like, setting it for the first time, it was it was chef's kiss.
Kevin: I like it, and it's pretty, we we kinda sometimes refer to it. I mean, we will use different words, but I call it like full fat. This is the full thing. There's directors cloud, which is like, you know, a a managed platform. But this if you self host it, it's full fat, which is really nice.
So I already have Docker running in the background and I already have a code editor here, completely completely blank code editor and a new directory, which I've opened up. So I think we're just gonna we're just gonna crack on straightaway in here. So first thing we need to do is create a Docker Compose file. It's a Docker dash compose dot YML file that describes all of the settings that we have with directors. We may need to fiddle with this later, But for now, docker compose dot yml.
And let's make my font size significantly bigger. There we go. We can copy and paste the value in here. Keys and secrets are meant to be random values. They're used for, identifying, your account in, like, a horizontally scaled setup and the secret is used for JWT validation, and we don't care about that right now.
So we're just gonna leave those. Hunter 2 is when I set up projects my default. So we're gonna Good
Andrew: to know. Good to know.
Kevin: Good to know. Good to know.
Andrew: I'm searching around for past Kevin projects and just trying to break in.
Kevin: Absolutely not. And no. No. That will that will not be good. We're gonna use, yeah.
Sorry. Go on.
Andrew: No. I was gonna say an interesting note here is I discovered with the self hosted version, you can't use a fake email address. You can't use dot test. It won't actually accept it as an email address in the platform.
Kevin: Yeah. Interesting. Interesting.
Andrew: I was when I was building out that railway sort of like starter kit for it, I was trying to just set it up with a test and it would not it wouldn't go. You had to put it in an actual address.
Kevin: Interesting. As in, like, it follows the structure of an email address, but it isn't real because it should do. That should be fine.
Andrew: I think it was because it was a dot test, t, like, t Yeah.
Kevin: But that that's they're legit characters in an email address. So anyway anyway. Things that's important yeah. One thing that's important to know about directors is it doesn't provide a database. It connects to an existing database.
We're gonna use this lightweight file based database called SQLite. We're gonna point it at the file. And on first run, it will create the file for us. That isn't Directus, that's a SQL like database that Directus connects to. So just a little bit of important distinction there.
10.8.3 is the latest version at the time of recording. By the time of publishing, that will not be the case. I think we might be on 10.9 by now. That's pretty exciting. Yeah.
All good Database persistence, file persistence, extension persistence. WebSockets are enabled even though I'm not sure we're gonna use them. And instead of, continuing to talk through every line in excruciating detail, I think we can just write Docker Compose up.
Andrew: It should kick off.
Kevin: Great. It's attaching. Boom. Boom. Boom.
Doing a bunch of stuff first time and running now on local host 8055. So if I hit local host 8055, brand new directors project with randomly auto filled credentials, which are not I think I have to move them out of, yeah, manage passwords, but that's not for today. I bet you that was a hunt to 2 password too. But this is a blank director's project ready to go. So that was that was great.
What else is important here? If I open up oh, I haven't opened up the folder. This is annoying because obviously the terminals attached to it. So let's just, let's just stop that running. Let's open this snake folder here.
I think I might have been able to add folder to workplace. We're here now. Let's just run Docker compose up again. Great. And just refresh that and everything should still be fine and dandy as it is.
Now the first time we ran that, it created some directories here on the side, which I think are gonna be quite small right now on people's screens. But as a database directory with our SQL like database and extensions directory and then uploads directory. Grand. So we have this direct as project and it's great that we, you know, can go in and create, you know, collections in our database and upload files and create dashboards and all of that stuff. But really, all we care about is implementing these endpoints from our Replit and bringing them over.
So let's go back to the docs. Let's talk about building extensions and get that get that game get that game underway. Directus has a whole bunch of extensions. Some of them affect the UI that that web app that we just had open. Some of them are API side like custom endpoints and hooks.
Yeah. As it says here, hooks are similar to flows, but they do not have the UI, which is in the director's data studio. I think to create an extension, we're gonna go I'm sounding a little uncertain. I don't know why.
Andrew: Like No. I love it. It makes me feel much better about about episode 1, now that you're feeling unconfident about the thing that you deal with every day. I feel like imposter syndrome is real, but at least you haven't had to Google how to make a JavaScript loop yet. So
Kevin: Oh, good.
Andrew: The episode is young. Run
Kevin: so I'm gonna run this MPX create directors extension. I'll pick an endpoint, pick a name, directus dash extension dash, snake. Snake. Snake. I it doesn't it doesn't really matter.
We'll use JavaScript. We'll auto install dependencies, and we'll give it a moment to scaffold that extension for.
Andrew: This is a nice CLI. Like, I gotta say, this is a very enjoyable experience.
Kevin: Yeah. There's still work that needs to happen, and it is happening. But, yeah, this boiler plating isn't too bad, actually. I, yeah, I I I quite like it. So inside of our extensions directory, we have this new snake.
We have source. We have this index dot JS. Grand. Now what we want and so this, I think I think this might be available, I think, at directus dash extension dashnet like slash. It's a little bit clunky, but we're not going to use this format.
It doesn't really matter. We're going to use this. We looked at it earlier, this alternate format, where we can do all of the subroutes straight in here. So I think I think I can just copy and paste this.
Andrew: Kevin, is this just is this just vanilla, or is this React? Like, I this looks very clean, but it doesn't Yeah.
Kevin: So this so the front end of Directus is a view app, but this what you're seeing right now and ding, ding, ding, I think you've just spotted it. Endpoints use oh, directors also uses the express router. So it is very much what we did in the first night, which is why being able to lift and drop it is gonna be gonna be quite nice.
Andrew: It's so clean. I dig it. I love this. This is beautiful.
Kevin: Yeah. Thank you. So we have snake slash snake intro snake goodbye. So let's get this running. So we're gonna c d into our Directus snake directory and we're gonna run npm run build.
That's built the extension into a disk directory. And now we need to restart Directus now. I think I think it might be broken, but I think we can get it to I think we might be able to get it to, like, auto, like, load the the changes. What am I looking for? Self hosted config.
Okay. So we are looking for extensions, extensions auto reload. Here we go. So Boolean will automatically reload extensions when they have changed. So we're back to our Docker Compose.
We'll throw that in and make the value true. Maybe that will work. Maybe that will work. So run Docker compose up again. It's now gonna use this Docker compose file again with this new variable.
Let's take a little look through the logs because hopefully yes. It says loaded extension, direct us extension snake. And then the hope is if we run npm run build again, it hopefully would have auto loaded, but that isn't the case. That's fine. That means we're just gonna have a slightly clunky development experience.
So I'm gonna have to control C up enter, you know, off the baton.
Andrew: That's fine. I gotta say though, like I remember like outside of this, I feel like we've gotten very spoiled in the development world now. Like, that used to be just the way that you did it. I used to use some.
Kevin: Dragging files with Filezilla or Cybernet. Yes. A 100%. Yeah. Yeah.
Andrew: I that's so I thought that's how you did it. I genuinely thought that that was the only way to do things.
Kevin: No. Yeah. But not here. Like, this this, I'm not quite sure what the deal is. And by the time this is published, maybe this won't be the case anymore.
Just for the last couple of versions, if you're running under Docker, I've noticed this environment variable isn't working. Our team's aware that's fine. I just wanna try it on the off chance there. Yeah. Whoops.
So if we hit 8055 snake, we get the hello world. If we try snake slash intro, we get the nice to meet you. So these are endpoints that are returning stuff, which means we should now be able to lift and drop our snake pretty expediently. Yep. So what we what are we gonna do here?
We have handlers dot info for the root. Yep. So you're gonna grab that. Return that. So I am just gonna make this slightly more verbose so I can space it out nicely.
Yeah. So, can you bump up the
Andrew: size a little bit too?
Kevin: A little bit more?
Andrew: Of course. Is that any any better? That's that's much better. Yeah.
Kevin: Lovely. Rez got Jason. Yep. Do you need to return it?
Andrew: I I believe so. Yeah.
Kevin: Cool. Nice.
Andrew: Yeah. I think everything you need to return.
Kevin: Whoops. Got a little too a little too direct there. Let's, let's while we're here, maybe let's do some customization of the snake. That feels good. Yeah.
That's good. So head over What color do we wanna make it?
Andrew: Oh, what's the direct is it a direct purple? Is the is the color purple that you're using?
Kevin: It is a purple. And, honestly, my lot are gonna kill me for, like, not remembering it, but I think it's, like, 6 6. I wanna say it's like 6644 f f.
Andrew: I like it. Let's see. We'll find out momentarily whether this is Yes. Yes. That's right.
I got it. 6644ff.
Kevin: I logged into my Battlesnake account. We're gonna look at the customizations. And these are the customizations we can have for our snake. Andrew, you you may do the honors from the ones I have.
Andrew: Oh my goodness. So I'm a big, big fan of mustache snake, but I'm pretty sure that there if we scroll down, there may be a rabbit in here now. I thought I thought we
Kevin: had play around seeing if I could get the director's Bunny to look good in one of these, and I had just I couldn't with, like, the it has to be full width on the left. The Bunny, let's take a look.
Andrew: Do we have a Bunny? If not, I know he exists in the backlog. So we may we may be able to get Bunny out at some point here, or he may be locked down. Either way, if not, if we can't get our bunny Oh,
Kevin: I have 200 I have 200 coins as well, so I can unlock any one.
Andrew: Oh, so what do we see? Do we see any that we really like? So we got chicken. I quite got
Kevin: I quite like this dragon head here. I have to be honest. Yeah.
Andrew: Take the dragon. But is
Kevin: it was really funny. No. They're all 200. I don't see a bunny.
Andrew: No bunny. That's disappointing. So let's go for dragon. I feel like that dragon
Kevin: head Well, hang on. Let's let's look at the tails too. Let's let's, you know, maybe not spend all of our coins at once. True. No.
I think I think the I think I like the heads. Well, yeah, let let's do the dragon. I've done it. Right. So the head we want is dragon, and the tail is So,
Andrew: I mean, let's go I think cosmic horror's tail is pretty fun.
Kevin: I like that. I like that. Okay. Cosmic Chorus. So that's our that's our snake.
And once again, actually, should we get all the logic in? I think we should do this bit by bit. Like, we could now go off and make sure that this works, which is gonna be a whole task in itself. Does that feel okay?
Andrew: Yeah. Yeah. I think let's do that because then once we know it's working, then we can kinda go through. Is that what you're saying? You're thinking, like, let's go in and actually add it to the platform.
I am with NVIDIA because we gotta get we gotta get ngrok set up now.
Kevin: Yeah. So do you wanna explain what ngrok is while I
Andrew: get sucked in? Yeah. Yeah. Absolutely. So if you've never used ngrok before, it is a way to publicly expose your local, development environment to the outside world, whoever you would like to be able to access it.
So if you often use, say, Gitpod or, Codespaces, there's often the way that you can make that public u that URL from private to public. That's fundamentally what this ngrok, endpoint that it's giving you is doing. But you can do it on a on a, case by case basis. And you can kinda keep it in one place, and you have a bit more control over the power of that and who can see it and all the rest of that stuff. It's our and it's really great, especially if you're, like, say, somebody that's building a site and you wanna see what it looks like on mobile.
I think they might still do it. But I know there's some services that I've used that it would automatically generate a QR code from the n grok address so you could just, like, scan with your camera and then view on mobile. Doesn't impact us here, but, really, really good. And ngrok has taken a lot of steps to make things more secure as well. So it used to be that sort of anybody could spin up an ngrok address and there was no issues at all.
And they've added a lot more security and ensuring that the things that need to be in place are. Yeah. And I will say around ngrok, if you are, on a local development environment on a Windows device, so a big Windows developer, there's a service called Laragon, which is like, an alternative to like a LAMP stack. And it has ngrok built in. So in there, you can just click share and then it automatically will, like, push that out to ngrok that you could share with people.
But, yeah, powerful, powerful service.
Kevin: So there's a little bit of a moment here. I'm not quite I don't quite have the memory of how this works. I think it works like this. I think based on what I've read, So you do ngrok HTTP, the port, which is 8055 and then dash dash domain and then your your domain. No.
Reserve this name on your dashboard.
Andrew: You don't need to do that. It'll automatically just generate a random one for you. So just No.
Kevin: Yeah. But I don't want that because I'm gonna have to keep updating it every time it restarts.
Andrew: I think I
Kevin: have I have a static. I have a static.
Andrew: Oh, I see.
Kevin: Like, because I because I'm on this plat this old plan. Right? Yeah. So I wanna work out
Andrew: Yeah. I haven't touched ngrok in in a little while. It's funny, like, it stopped working in that environment. There was, like, they hadn't updated the way that was integrated, and they added an authentication key. And so, it switched things up.
So your guess is as good as mine.
Kevin: We're gonna get oh, alright. Here we I mean, yeah. I suppose I could just do the help, right? So start engrop. That's cool.
Basic auth. Cool. OAuth 2 cool. Here we are. Use it the same time by using dash dash domain.
But, and I thought maybe I, maybe I.
Andrew: What's your domain?
Kevin: I thought it was that. That's why I that's why I paused. So if I look at domains, there it is. Unless I have to do the whole domain.
Andrew: Try the whole domain and see what happens.
Kevin: Because it because it did just say the ngrok.io is only for, like, legacy accounts. Yep. And that was it. Yeah.
Andrew: There you go.
Kevin: Great. So we've now got we've now got engrok rocking and rolling over here. Great. So now what we're gonna do is go over to battles go over to Battlesnake. We'll create a new snake like we did last time.
So this is ready, set, battlesnake. Ready, set, battlesnake. Cake.
Andrew: Yes. That's it. Nope. Nope.
Kevin: K for Kevin.
Andrew: That's it. I love it. Beautiful.
Kevin: So https phzn.ngrok. Io/snake. Engine region, Netherlands or Netherlands? Yeah. Well, Europe.
So yeah. Sure.
Andrew: Yeah.
Kevin: Programming language, JavaScript, and platform Director. Should be
Andrew: there. Directus. Now my question here is, does the logo look good?
Kevin: Oh, let's see what happened to it.
Andrew: So I used the I got them to add Yeah. It doesn't look too bad.
Kevin: Yeah. It's an old it's an old one. So we'll fix it by the next episode, but it's good. It's just colors just colors and details. But yeah, that's that's our little bunny rabbit boy.
Unknown API version. So there was an error. Yep. Even though we copied
Andrew: You should paste it. So what I would say is, I think I think I think I think you might need to just reset. Because this used to happen on
Kevin: Reset reset what bit?
Andrew: Like oh, sorry. Reset everything. So just, like, ping it again. And if it doesn't work, then actually start running, like, restart your direct to server. Okay.
This used to happen with replit all the time is it would get this, and then it would just need a couple minutes to start syncing up in the right way.
Kevin: I don't know what that mean. I don't know what that means.
Andrew: No. It's no. That's right.
Kevin: So if I hit that endpoint Yep. Oh, it still says hello world. Oh. Why? Oh, why?
Because we needed to rebuild the extension, then we needed to restart director. So there you go. That was our little people.
Andrew: What are you doing?
Kevin: No bill. What what what are you doing? Hit ping. Perfect. There it is.
Oh, battle snake. It looks sick. Yeah. I love that. Yeah.
So now we have a snake. Right? But we haven't implemented any other endpoints. So this is the time to do it. Now this is the point where maybe, like, we split out the move logic into a separate file to stop it.
No. Do you wanna know what?
Andrew: I actually wish that the move logic was in with everything, even in the JavaScript starter snake. I understand why it's not, but I actually think it's way better to have everything in one place just so all of your routes are are there and you're you're able to change that data. So I think I think let's do exactly what we That
Kevin: works for me. So there we there we go. And I am just gonna shrink this slightly and shrink this slightly so we can get the replet up and kind of do a little do a little moving from one to the other. Now can I, can I shrink this sidebar down? Yes.
Perfect. And maybe even shrink that. Okay. Rock and roll. Get rid of that.
Oh, and get rid of that. There we go. So first thing we're doing is is move safe? So move, get some data in. Let's console log the request just so we can make sure, like, is it in request dot body?
I I mean, I'm assuming it's in request dot body. Yep. Let's let's just double check that, and just respond with hello.
Andrew: I like that after our last adventure where we had to disappear and through the magic of editing come back, we've decided to now add console logs earlier as we go so we can save ourselves some trust down the road.
Kevin: That's my style. That's my style. Discum. Okay. Experiential.
This is the point where and these these logs are gonna get noisy in a moment. Yep. I may even take them down inside until we need them, and then I can make them smaller still and then I can bump them back up. So what are we gonna do here in Battlesnake? We have a Battlesnake.
Let's create a game, create game. Add ready set Battlesnake standard board 11 by 11.
Andrew: And that's good start game.
Kevin: Good. 404, 404, 404, 404.
Andrew: Well, great. So but here's the thing. If you didn't watch episode 1 so here's the interesting thing. Is technically, technically in Battlesnake, you can actually win if you 404 or have a 500 error every single time because you have to set a default move that your snake makes. And I think that there's something, I mean depending on the implementation, I think there's something cool that you could do about actually having a way to change that default.
Anyways, but then
Kevin: game's still in progress?
Andrew: Yeah. It's been weird and slow. It's, like, not actually still in progress, but the platform's taking a bit of time to catch up with, I think, what's actually going on. So Why is it 404ing?
Kevin: Root to talk. Kevin. Host. So we'll rebuild that, and we'll restart directors. I was using the wrong method right here.
It's a post request to slash move. Okey dokey. So create game, add ready set battlesnake, hit start game. That should be better. There we go.
So move gets hit. Oh, so this is it. So the data comes in. Yes. Req dot, req dot body.
Fantastic. So now let's move all of this over. I might do a little bit of cleaning up as we go.
Andrew: It'd be interesting. Yeah. Never mind. Let's let's get to it.
Kevin: Let's get to it.
Andrew: Go ahead. I was gonna say it's it'll be interesting if we if we wanna add the start and end point and log what's coming through there. I think there's something interesting in that as well, just to kinda see what that looks like before the logic is implemented. Because it does get really busy once the logic is implemented. At least right now, we're just getting those 404 errors or, like, the basic logs.
Kevin: It's a start. And is it a post request? I think it is. Yep. Yep.
Post. We probably should respond. Right?
Andrew: Yeah. And and it'll it'll get angry with us. But
Kevin: And and. Yep. And just because they are gonna get in the way, I'm just gonna pop them down there. Okay. Let me just, cough.
Let me mute.
Andrew: Yeah. And so what's interesting here is so I think we we said this earlier, like, the start and end points. You don't really need them for anything, but they're useful if you wanna implement some more serious logic, which we may do sort of with our direct as Battlesink down the road. So you'll see here once we rebuild and then save again, you'll actually be able to kinda see the stuff that comes in
Kevin: Yeah.
Andrew: Through there.
Kevin: So we need the width The height. And the and the height and the Head. Snakes. I think that's all we need. From rec.body.board.
Right? Yeah. It's all in this thing called board. Yeah. Great.
And the other thing we need is my head, and I am just gonna copy and paste that little convenience there because that's that's wicked.
Andrew: If you by the way, if you really don't wanna have those 2 constant, like, lines that you have there, if you're just, like, really focused on compressing, you can technically pull it from that sneaks variable that you're defining in there. It's just
Kevin: a little bit more more to do it. Absolutely. Yeah. Just can't I mean, because
Andrew: be bothered.
Kevin: Can't be bothered. Right. So so so we've got the we've got the width and the height. So first thing we were doing is don't don't move out of avoid walls, and we're just gonna copy and paste it. Now we're not.
First thing we're gonna do is set do the is moves we're gonna do the is moves safe thing. That's the other bit of, boiler plating, I guess.
Andrew: Yeah. And to be honest with you, you don't I mean, so this is like the the default logic. Technically, you can implement logic that doesn't use this, but this is just like the most straightforward way to do it. So
Kevin: Sure. So this is avoid walls. So my head exists. It's movesafe.leftforce. Great.
Board height. Interesting. I think we just called that
Andrew: We call it height Height. So we just need to change all of those heads to my head and all of those heights to
Kevin: No. Head to head is correct. My head. Oh, you just
Andrew: got to use my head? Okay.
Kevin: Yeah. But board height. So what we're gonna do here is say height. We'll put bring that in as board height so I don't have to rewrite everything. We'll bring this in as board width.
There we go. Job done. I'm also just while we're here, I'm just gonna just gonna we'll make things just a little more little more succinct, perhaps. This is valid. Right?
Just so it's 4 lines as opposed to so many so many lines.
Andrew: This is valid JavaScript. Really? Yeah. I hate indenting so much.
Kevin: I would But indentation doesn't matter. So we avoid we avoid the walls. Great. And then it's avoid bodies. And we've already got the snakes.
We've already got snakes stored here. So that is the equivalent of that line there. And so we're gonna take this for loop. We will dump that in here. And once again, I'm just gonna take this moment to just, to just clean up a little bit.
This time because the conditionals are longer, I probably am inclined to leave them to leave them like this, but just just remove a bunch of, you know, white spacing. And there's some funky indentation going on here. There, boom. There we go. All right.
Then if there's any save moves left, the console log and we return move down. Oh, if if there are none left, we go down. I got it. I got it. I got it.
So, all the safe moves if no go down. Great. There we go. And it won't be return. It'll be red stop return red stop JSON.
And then it won't go further down. Alright. And it will have returned there, so it won't continue. So I don't need to announce or anything. We pick the next move.
I am noticing hang on. Hang on. Game state dot turn is not correct. Let's make sure my head piece. Yep.
Snakes. Everything there is fine. It's just it's just here. Game state doesn't exist. That's req.body.tan, which means oh, no.
Req. Yeah. That's fine. Req.body.tan. And then res.json, then we'll look at safe moves.length, pick the next move.
We'll log it. And we will return it. I'm sorry. It isn't a string. It's a it's an object, isn't it, with a move value?
Andrew: It is
Kevin: Next move. So I think I think that is that's it. That's that's
Andrew: a whole efficient, Kevin. That was wonderful. Okay.
Kevin: Thank you very much. Thank you very much. Yeah. I think that is our our snake as implemented last time. Let's rebuild the extension.
Let's restart the. Once again, that shouldn't need to be the case, but it is just at this point we're recording, so so be it. And oh, that game in progressing has persisted to be a challenge.
Andrew: It has. So ignore. If I refresh,
Kevin: does it know it's done? No. Okay. So New game. We'll have to keep.
New game, and we'll send in, I don't know, loopy bot. Or does loopy bot just do the
Andrew: Just does the loop. Yeah. Yeah. Hungry is good.
Kevin: Hungry is probably a good little test.
Andrew: Hungry is good. He was a good test for us last time. So I feel like we can see how he responds.
Kevin: Okay? Obviously, it's a little more verbose down here. You can reduce the amount of logs with an environment variable. We died that, you know, that's the end state. So, oh, wait.
If it won't, can I hit play? Yeah.
Andrew: You can hit play.
Kevin: And sorry. Show the coordinates. Fast, please. Save game.
Andrew: No. Just go back. Just go
Kevin: back. No. Oh, do I just I'll back back back back back. Alright. So r one is the orange is the, oh, we won.
Andrew: So what did
Kevin: it say
Andrew: in the logs that you thought we lost?
Kevin: No. No. We we didn't lose. It ended. I sorry.
I said we died, but I meant it ended.
Andrew: What does the what does the actual response say though? I'm not I'm curious. I don't have any idea.
Kevin: It is game ID rule set map stand up source. The oh, it's the it's an n the last 10.
Andrew: Yeah. Yeah. But does it say 1?
Kevin: Let's find out.
Andrew: I feel like it somewhere should say 1.
Kevin: I think no. I think snakes is just one long. Oh, yeah. But it could be you or anyone else, I suppose. Fair
Andrew: enough. It may not say it in there. I'm just curious.
Kevin: Maybe it's the fact you still exist. I mean, let's find out when we die. Do you know? Oh, yeah. If we look at you, it has the ID oh, let me, woah.
Andrew: There we go. I was gonna say I feel I felt like I wasn't seeing it, but I
Kevin: feel like Sorry. That was, that was a keyboard shortcut thing going on. Alright. It has a health. I wonder if the health knows that.
I mean, we'll just do it a couple of times, but let's let's watch. Let's watch our our little sneaky there.
Andrew: You do it well. Hey. Like, that logic is good. Okay.
Kevin: Head to head combat, which we haven't built logic for. Maybe we'll do that this time.
Andrew: It's a good idea. I
Kevin: think we because I because I kind of feel like that was what we intended to do this whole episode. Yeah. Congratulations. We're done because we can't build it in flows, and that's the that's the thing here. I have one other I maybe you can execute a flow from Yeah.
From this, and then this just becomes the entry point into the flow.
Andrew: That's what I was just gonna say. I was like, is there a way that we could set up the one of the endpoints in here and then have the trigger in flows? You're able to do everything from there?
Kevin: Maybe.
Andrew: That might be fun. I feel like that's an interesting way because I I do think that flows is like this interesting.
Kevin: I've never done it, but, I'm sure it will be fine. So let let's take another look at some new extensions documentation that was recently written. The resources on the not this one. I'm looking for resources, components, packages, extension services. Here we are.
So underneath directives, every time you make a request, it's actually going off to one of these services, which then, you know, which then executes your your request. And inside of extensions, you can also access these services. Various services are available within directors, including item service, collection service, file service, and they're available within the extensions context. So there is a router here in the handler, but I think we can also pass in this context. Right?
Let me just think about the best way to do this. I think it might be just playing around with this router dot get here. So, so we have this context. I'm gonna bump that back up actually. And if we console dot log, the context and we see just here, we, destructure get schema and services and then we destructure the individual service.
So let's let's just do that outside of the individual. Outside of the individual. No. I think for just a moment, we'll do it just inside of this one just because this is the one we're gonna hit. So we don't we don't just have to mess with that.
And then instead of doing this, let's just console log the the services and just see what happens. I'm actually gonna do a console dot here, services dex null. And what this will do is effectively a console log that never does this kind of cut off thing. It'll always show you all of the nested layers just because I'm not sure what it looks like and I don't wanna I'd rather just be a little more verbose once. So dotcom.
So what we're gonna do here is go back to our root endpoint and hit enter. Oh, sorry. Not root. R snake.
Andrew: There we go.
Kevin: And what happened here? So we have some services. Let's, yeah, let's just expand this for a moment. Webhook service. I, there is a flow.
I, I believe a flow service.
Andrew: Oh, nice.
Kevin: Gonna that's gonna wanna be the one that we I mean, is this the one we wanna I don't know. I mean, all we wanna do is hit all we wanna do is hit an endpoint. But Yeah. Let
Andrew: let's go for have
Kevin: a let's have let's have a play. Sorry. Go on. No.
Andrew: I was just gonna say, do we wanna go in? I don't know. Never mind. Let's let's test this out and then and then we'll go forward from there.
Kevin: Sounds good. So let so what you do here is you create a new service with the collection there. So you know what we'll do here. There are some commonly used services. We can just refer to the full list of services directly in the code base here, which is actually what I should have just done.
So we have flows here. So we can create flows, create many, update, delete. This actually isn't what we want because this is all about running flows. Sorry. And the the crud of flows.
And we don't wanna do that. We just wanna we just wanna run a flow. So maybe the first thing we sorry. Go ahead.
Andrew: No. No. No. Is it just the webhooks that we want, the webhooks service then? No.
Because that's to create a webhook. That's
Kevin: not Yeah. Exactly. That's outbound. So so what we'll do here is we'll go to the admin. Oh, wow.
We will create a little flow and we'll see if we can trigger that from our code. That's basically the test here that it works. So we create a flow. We'll call this info. Snake info.
All of that feels good. Maybe traffic activity in logs. I think that's fine. Then we have the webhook. Webhook.
You're right. It will be a get request, response body, data of last operation.
Andrew: Feed all the data just to have it?
Kevin: That that that's happened. So oh, no. So this is the response body. This is what it will respond with. We only want to we wanna create the response and send it.
But, actually, what you're describing is it was here. It was this option here, track activity in the box.
Andrew: Alright. There we go.
Kevin: So back to the trigger setup, we'll turn cash off. We don't want it to be asynchronous, get request, save, And we will re- we- it will be the data from the last operation. So we'll just run a script and we will just return this, I think. I mean, that makes sense. Yeah.
That works. I've not done this before. So we are we are really finding out together how this works.
Andrew: Works is very smooth. I like just being able to have it.
Kevin: I really like it too. So we've got a save. So now we have this idea of a flow, and I think that's the thing that's interesting to me at least.
Andrew: So you'd rather use the ID than actually just use the flow endpoint, like the webhook endpoint?
Kevin: I could. I don't think that's, like, the proper way of doing it. But could we do that? Yeah.
Andrew: I mean, I feel like I feel like Okay. I'm interested in seeing both ways, right? I'm interested in seeing ID flows, which I think is the proper way, and then just calling the webhook from within the code. Right?
Kevin: Yeah. I'm gonna be I I don't even know how to make a web request in these off the top of my head.
Andrew: Just I think it's just Axios, isn't it?
Kevin: I think it's just Axios. But but how is Axios, like, exposed? And do you wanna know what? I'm pretty confident there are just some guides that were written around this that I think will just help us. Endpoints create a public API proxy.
Here we go. This will this will help. Yeah. It's a it's a fetch.
Andrew: Oh, alright.
Kevin: Cool. So we will so we hit slash. So what we're gonna do here, we're we're forget all these services for a minute. They don't I don't think they're gonna matter.
Andrew: No. They won't matter because literally we're just basically now that we've created that flow, we're just doing a call to that webhook and then
Kevin: Yeah. We're gonna do a response, await, fetch, and then slash flows, async. Okay. And then, yes, this is response okay, response Jason await Jason, all of this jazz. I normally, honestly, just do this pretty low rent, then r r dot JSON and our response is the data.
So I think we can just return reds dot JSON response. Yeah? Yep. I might just be explicit here. Flow response.
Flow response. Yeah.
Andrew: Yeah.
Kevin: And maybe yeah. That yeah. Screw it. Let's try it.
Andrew: Let's do it. Gotta get this I feel like this is why I enjoy, like, non typed languages because you just mess around and see what happens.
Kevin: Yeah. I mean, you can build extensions just as an FYI with TypeScript. I just didn't oh, yeah. I thought this might be a little easier. So if I hit refresh here, we should see the same the same thing the same thing.
Oh, wait. Hang on. I don't know why that's happening. Oh, was there an error? You have triggered an unhandled rejection.
Failed to pass URL. So it didn't like the
Andrew: Yes. So just add
Kevin: the link to the I mean, sure. But that that doesn't make this portable. Right? That isn't let me just have a quick think here.
Andrew: Oh, I see what you mean. You wanna be able to pat yes. Okay. I see what you mean.
Kevin: Because then if we deploy it somewhere else, this will Yeah. This will still work. That's that's that's gonna have another little look. Let's keep having a little look here. So still sort here.
No. Because now I'm right because this is the flows. All I wanna do is know, yeah, how do I run a flow? Let's just do you wanna know I think I've just had I think I've just had an idea on how this works,
Andrew: which is
Kevin: actually not looking at the services. It's looking at the the way the SDK does it. So if we look at the, extensions, packages, sure. The SDK just like type, type doc. This is just the the full dump of everything the SDK can do.
There is an endpoint in here called something there's something in here for running flows. Andrew, I'm going It is I love a c.
Andrew: So even if you write the documentation, everybody, you still need to read the documentation.
Kevin: You do still need to run it. Here it is. Right. Create a flow, retrieve a flow, list flow. So this is all the stuff we just saw, but Yes.
Here we are flow with get webhook trigger, trigger flow. What is going on here? Let's take a little look. Failed to pass URL from /flow/trigger/uid.
Andrew: Yeah. That I mean, that makes sense to me because it's not a full URL. Right?
Kevin: Do you wanna know what I did sneakily on on the side here. I have a chat with one of my colleagues because, of course, I did because I have that power. It does just need to be the full URL. So let's stop. Let let's stop battling against I was right.
You're right.
Andrew: You're right. But you're right. It's not portable. You're very true. It's like you'd now need to, like, worry about changing this if you change where you're you're hosting it.
Kevin: So I do think Which I think won't matter for the scope of this. So now built that, restarted that. So now if we hit this, it did return and it did it by getting by by getting the the, yeah. So, oh, that's really exciting. Okay.
Let's abstract this logic out then. Let's get this whole move thing. Let's just comment it out. We know it works. Okay.
And what we're gonna do here instead is we have a post. And so, let's set up a new flow. Not here. Let's set up a new flow. Oh, that's so exciting.
Okay. Cool. Snake.
Andrew: So we're gonna we're gonna create a move we're gonna create literally recreate this entire logic as separate flows. Why not do it as one flow, though?
Kevin: Oh, because it No. No. No. Just because it won't be each endpoint. So everything move is gonna happen in one flow, and that that's yeah.
Yeah. It's cool, isn't it? I think track activity and logs, I'm going to level with you. I'm not I'm going to say don't track anything because it's just gonna it's gonna absolutely explode the logs because it can be every like, the full body every time. And we have the the the logs over here.
So we won't do that. That will be a webhook. Data of lost operation, that's fine. Not asynchronous. Don't cache.
Post. Okay. So exciting. Okay. And we will, run a script and we will return move left.
Right. We'll just we'll just do that to start and just test test it works. Return move. Hit save. And we have an ID for this one.
Great. And, what we're gonna do here? We're gonna do we're gonna copy this. We are gonna change the ID and we're just gonna go post, sorry, methods. I wanna say it's post and I think it's body equals JSON dot stringifyreq.body.
I think that's I think that's the fetch. I think that's the fetch way of doing it. So What
Andrew: is the fetch way of doing it?
Kevin: Is it good? Okay. Fetch. My memory has not failed me today. I'm literally I'm so pumped that we're able to do it in flows.
That was a really good I I think that was yours. You can have that one.
Andrew: No. We're gonna I'm gonna give that that's that was a 5050. We left brain, right brain thing here. I feel like it was one of those days.
Kevin: Okay. Okay. Alright. So we're ready set. Ready set Battlesnake, Hungry Bot.
Sure. And start game. What's happening?
Andrew: It's happening. It's happening. It's happening. It's happening.
Kevin: But it will just go left, left, left, left, and die. Yep. Right? That's fine. Yeah.
That's fine. It's still
Andrew: it's still going into
Kevin: the world. Awesome. Which means we are done with the code. We're not done. We'll need it for reference.
Yeah. But we are in flows now. It's happening.
Andrew: What's interesting here too, folks, is, like, you like, literally with those two endpoints, like, you don't actually need to implement start and end. Like, you can stop with just those 2 if you're like, I don't wanna have to manage 4 flows. Like, you don't actually need start and end at this point. And so I think, like, don't use them.
Kevin: So first thing we did was avoid walls. I'm gonna run a script here. We'll return it, and then I'm just gonna do the it's annoying. You have to kind of create it from an existing path. Yeah.
And then we're gonna take that and dump it in there. Okay. Avoid walls. Back to the back to the primitives. Now, you know what we didn't do?
We didn't because we don't have the logs. We actually don't quite know what it looks like in here, which is annoying. Just a thought I've had.
Andrew: Yeah. It's true.
Kevin: But I think I know.
Andrew: Well, I mean, let's see. Let's see what happens here. And I think the cool thing here is, like, now we return to, like, traditional programming as well. Right? There's a lot of different ways you can implement this within the flow.
Like, there's so many different operations. Technically, you could feedback a lot of the information and then make, like, decisions there or just do it.
Kevin: Let's let's return the data. Like, that's the whole data chain. And then so this first time, this is gonna fail absolutely miserably. But, oh, no. It won't log anywhere, will it?
Do we
Andrew: wanna turn on those logs then? I know we didn't want
Kevin: to, but Yeah. Yeah. Can I turn them on then turn them off? Yes. Fine.
Yeah. That's fine. Yeah. Yeah. Yeah.
Sorry. I thought it was a one and done kinda deal. So let's create a new game. Create rematch. Okay.
Cool. Whatever. Sure. Refresh. And there should be many Yeah.
8. That was 8 moves. So it's it's gonna get unwieldy, but this is what matters.
Andrew: That's great. That's awesome. Yeah. So we get
Kevin: Except the distinct so the trigger where's where's all the, not being oh, so Are we not sending it? Are we not sending it precisely? And this is why I wanted to check. Yeah. Yeah.
Oh, wait, what
Andrew: did that say failed up there? Go back up to the options.
Kevin: No, no, no, no, no. Force. Okay. Cache enabled force, cache enabled force. So cool.
We have this fetch. We send that is how you that is how you do it. Right?
Andrew: I mean, I could have been wrong. I was assuming, but ah, here we go. I'm glad that it has happened.
Kevin: Yeah. Body.stringify. Body data type must match content type header, but I I don't think sure. Do you wanna layer what? Sure.
Let's just whack that header in. Just in case it's that.
Andrew: Yeah. See what happens.
Kevin: I'm not losing any sleep over over adding that. Is that an error? Oh, no. We've left a log we've left a login somewhere. All the serve hang on.
They're all the services.
Andrew: Yeah. Didn't we didn't we remove that?
Kevin: No. It's the it's the context. It's this console log context.
Andrew: Oh, there we go.
Kevin: I was like, that's a big that's a big object. So right. Restarting. Okay. Okay.
Okay. We're gonna create rematch.
Andrew: Let's get some logs.
Kevin: Nice. Oh, head to head. Some quick a nice quick death. Okay. Are we getting Payload.
There we go. There we go. There we go. So it's in trigger dot. Body.
Body, I think.
Andrew: Yep. Yeah. Trigger dot body. Yeah.
Kevin: That looks correct. So, I mean, there's one way to find out, right, which is, so, trigger data dot trigger dot Bobby. Nice one. Bobby. So let's go and take this first up.
So first thing we'll do, of course, sorry, is we will is move safe. We have to think about how we, because we, we need to kind of alter this value as we go. Maybe we return that. Maybe we return that with each step. Right?
So we'll start with a is move safe and we'll just return it every time. So Okay. We'll start this time, we'll create it. I also think that could be a const. Anyway, avoid walls.
So we need these values. Do you
Andrew: wanna bump up the text size there a little bit more? Thank you.
Kevin: You know what? I'm actually inclined. Let's rename this one. Let's call this one set up variables. Yeah.
Like that. Let's call them let's call it globals. We'll just make the key global so it's a little shorter. All we're gonna do is return is return. We're gonna return what was it called?
Is move safe. We will also do forward width. It's going to be data.trigger.body.width. Old height. What was the other thing?
Oh, my head. So that's all of that. And then, we yeah. Just return that. Right?
Yep. Sponsored global. Yeah. Yeah. We will.
Just just I I wanna be I wanna be testing early in Austin even though, you know, we're not doing much there. I just wanna I just wanna be validating that we haven't broken something there. So we will create a rematch. So all we should be getting now is an extra property in the data chain. Wow.
That was a really short, really short game. I think it was, like, 2 moves. Yeah. Oh, rec is not defined. I think I left in a a remnant of what it was before.
Rec.body. Yeah. Data. Trigger dot body. That's why we test.
Create a rematch. What else have you used Flows for in the past?
Andrew: So I did the, the DevCycle Directus integration that I built out. So this was, it's worth checking out, just to see, like, the different use cases here. So it basically allowed for you to do an API call to a patch API on the dev cycle, patch API endpoint on the dev cycle platform to, like, pass basic data into into the service and to, like, put, like, an edge database for feature flag management. We might use that actually, when we start to do the dev cycle integration just because it's a really fast way, and I know that it already works with Directus. So, nice.
But yeah.
Kevin: That's called EdgeDB. EdgeDB. Yeah.
Andrew: So it's just, it's just, like, a faster way to pass data. So how are we doing here?
Kevin: No. No. But if I refresh, I think I fixed it.
Andrew: Yeah. Right.
Kevin: That's everything in the in the global. Now I'm not sure. So I think is move safe is one that we might need to pass, like every move we're going to need to we're going to need to reset that, but that's fine. So cool. Next thing we'll do then is avoid walls.
Avoid walls. We're gonna run a script. We have data, and we have our avoid walls logic here.
Andrew: Yeah. I feel like this is also just a really nice visual way to represent this whole
Kevin: This those are the steps.
Andrew: I really wanna go and find my Node JS snake or my, my Node RED snake that I created because I did the same it was the same process that I went through.
Kevin: Was it sorry. Just very quickly. Well, did I call it my head still? I think I did. Yeah?
Yep. You did. Yep. So the nice thing about this is we have is say is move safe left and all of this. So I think we wanna create a new variable called is move safe, and the value of that, I think this works, is gonna be data dot globals.
Was it multiple? No. Global.
Andrew: Yeah. Global.
Kevin: Yeah. I think so. Yeah. Dot is move safe. Right?
Andrew: Yeah. This this
Kevin: is And then yeah. This is
Andrew: something that's really interesting to know having used flows before. I was not taking advantage of the globals like that or not the globals because that's the thing you're creating here, but, like, the But the data chain. Touch on data chain. I thought you lost the data, basically No. As you went through.
So that's interesting.
Kevin: And I've had another thought about this. I think the way we're gonna do this is using last. So last, dollar sign last is a dynamic variable that will always be the output of the last step. But that means this line will become basically a boilerplate first line on every step in, I think. I think this creates a copy.
We'll find out. We'll find out together. So next thing we're gonna do is we're going to destructure, my head board width, and board height from data dot globals. And then the rest of the code should be able to be untouched. Right?
Exactly. Yeah.
Andrew: It looks good. The
Kevin: only and the only thing we wanna the only thing we wanna return is is move safe. Only thing we care about. Yeah. If this works, I'm gonna be so pleased with myself. I have a hunch it won't just because, of course, why would it?
Let me just move return move out the way. In fact, we can remove that. So no. Of course not. Sorry.
Ah, I've deleted it now. It's fine. We're here. So return move because what we're gonna do in this step is we're gonna just do that last bit of logic. If all there safe moves, if no go down.
So we're gonna get rid of that. All we care about is next move and return. Yep. Because that is a a step between where we are and where and where we're going. So I have in my here we are.
This is what we just had in our last step. So we have beta dot globals. Now is move safe. There we go. So next move, safe moves.
Is it safe safe? Why is it called safe moves? Oh, here. I need this line too. I'm gonna call that one.
Safe moves. Object dot keys is move safe. Filter down is move safe key. Fine. Fine.
And that's just getting the the the values. Then next move, is safe moves, math floor safe moves. So that that should be it, And then we should just be able to return move next move like so. So that's the last step in. That's the last step in the chain.
Let me bump that up one more time. Sorry. So that's all the logic again. We have that boilerplate starts. We don't quite know if that works yet, but in theory
Andrew: It should.
Kevin: Theory, it should. It should. Dangerous last words. Those. Sorry.
So we'll just set ready set battlesnake in there because we're not equipped to have multisnake yet.
Andrew: Nope. Did not work. What are our logs saying?
Kevin: Thank you. Thank you very much. Yes. It did not work.
Andrew: You're welcome. I feel like I need to add these
Kevin: useful Last is not defined. Absolutely. I know exactly where the typo was. It's data dot last, and it will be wrong there as well. Data dot last.
Right? Yep. Yeah. Cool. And what's nice now is you're not having to restart Directus every time because now it's handled by the flow.
That's wonderful. It is cool. There was a little bit of of, let's call it friction first time. Okay. That's not ideal.
42, 44, payload. Cannot convert undefined or null type. Okay. What are we? Payload after after but here's the thing.
After avoid walls, look at that. It did say up force, which means everything's working until the return move. So that's fine. So that means it works, this whole passing passing is move safe each step. Yeah.
But where it's breaking is this last one.
Andrew: Okay. So let's look at this again. So async function
Kevin: the error was cannot let's actually read the error. The error says, cannot confer undefined or null to object, which means the issue is here in object dot keys. So we have is move move safe. I think it can just be data dot last if that's all we're passing step to step because we don't return an object with is move safe. We just return is move safe, which means here again, although
Andrew: I mean, let's see. Let's see what happens.
Kevin: Yeah. I think I think that'll fix it. So if we hit create rematch Yes. It avoided pause. Nice.
Lovely stuff. And let's just, let's just validate that was indeed what we needed it to be. Left.
Andrew: Hey. There we go.
Kevin: Heck, yeah. Which means we're onto we're we're now onto a winner, basically. It's this is the starting logic for our for the no. It isn't. Oh, it's it's somewhere in between that we haven't quite got the standard because this is the first one after the global and this is the last one.
So that's fine. We need to create one more here. Yep. And, what's next? Avoid bodies.
Andrew: Avoid others. Yep.
Kevin: Run script. Paste that in. And we don't need is move safe anymore. We have the data dot globals. And where are the where are where is the logic?
It's here. And just because I think I think it's gonna be easier, I'm just gonna great. Great. And I don't think we need board width or board height anymore. We just need my head.
We have snakes. So I believe we had bored. I think we got bored out. Yes. I'm not sure we did.
Let's look at the globals again. The actual board itself needs to come with 2. So, let's call it snakes. Let's just add snakes here. So data dot trigger dot body dot board dot snakes.
Then let's create this last one again, as avoid bodies and that should have saved, but it's fine. Okay. So we have my head and we have snakes. I think I gen oh, yeah. I genuinely think nothing needs touching here.
Andrew: No, I think it's good. And I think we've the, the code works and we're even past.
Kevin: Yeah. I don't see any point of weakness in this at the moment. So we hit say, oh no, we don't, we just,
Andrew: Famous last words.
Kevin: We do that. Then we do that. So I'm gonna just Very good. We'll hit save. Oh,
Andrew: what's going on? Value fulfilled resolving collection, direct us operations.
Kevin: Value fulfilled resolve. In fact, it has to be I, don't know what that is. Record not unique.
Andrew: For field, resolve and click direct operations. Maybe if we turn off that logging now. Just turn off the logging because I think that's Yeah.
Kevin: But that isn't logging. That's a database save error, but I don't know why. Let let's just forget this was here for a moment like it was before, and it was fine. Okay. That does save.
So if I take this instead and send it to avoid bodies and hit save, it's fine. And then if I go here to return move, fine. Do you wanna know why I'm just gonna walk away slowly?
Andrew: That's the right that's the right answer. If the thing starts working
Kevin: So we don't wanna create a rematch now because we wanna create a game with another snake. Yeah. Yeah. Star hungry
Andrew: in there. Alright. Let's see how this works. Okay. We got it.
He Hang on.
Kevin: We've not had to Yes. Hey. There we go. Yes. Nice.
Get in. Okay. Done. Nice. So the final thing here was this, if there's no safe moves, go down.
Andrew: I don't think we need that. I genuinely it's like a Oh, yeah. Backup. It'll always Yeah.
Kevin: Well, because if there are no safe moves, you're toast anyway.
Andrew: Exactly. So it's really just, like, if you want to have, it I don't know. It's good programming practice for you to build it in, but, like, I think here, like, we can worry about that down the
Kevin: road. Do you wanna know what? Given that this was basically the scope for this episode, I'm gonna add it. Why don't we add 1 one new block in?
Andrew: Right.
Kevin: Because I wanna do avoid avoid head to head.
Andrew: K. Yep. That seems easy. Yeah. Because we're just basically doing look ahead here.
Right? We're looking at, like, one extra block ahead. So so yeah. I mean, so this is the if you didn't catch it on the last stream or on the last, the last episode, we so head to heads, you can do and you can win, but it's all about size and rules, but, like, these head to heads here. But with the logic we currently have, we're only ever looking at what's directly next to us.
So ensuring that, like, if we have another snake beside us or directly in front of us, we'll never go that direction. But if there's a snake that's 2 away in the same direction, so basically, we're repeating the logic that's in here and just or we could technically revise this logic. I think it's similar because it's just like Yeah. Yeah.
Kevin: It will be similar, but I don't I do not think it's the same. So it's not the same. If we ditch this for a moment, we'll leave it as we could see it. So we care about the snakes, every snake, and we're gonna get rid of us. Now there is a a fancy way of doing this, which is, js return array without first item.
To remove the first element in the in the array, you can use shift. Shift removes the first element of the array and returns it. So I think we can do this just to get rid of us first.
Andrew: Yeah. And then we have to find our
Kevin: because then we would have no valid no valid way to go. So So now we care about every other snake. So I'm gonna say head, it's gonna be snake dot body. Does snake have a head too? Does every snake have a head?
Andrew: Everything has a head.
Kevin: Snake dot head. So okay. Cool. And what we wanna do is look one space left, right, up, or down and remove if that's gonna be the direction we go in. Right?
But we I'm just trying to have a think here because I think these are actually much more complex if statements.
Andrew: They are. It's basically if I remember having done this before, it's basically all of those if statements that we had there repeated 4 times, if that makes sense.
Kevin: Which we absolutely will not do. We will we will do, like, a nested loop. But I'm just trying to think through that. I'm just trying to think through, like, what is the logic here? Well, the first thing we wanna do is check every feasible space we can move into.
That's the way we're gonna do it. So feasible directions. I remember encoding Valdez to do stuff like that, and then we had to remember what it meant. Possible next steps. And we actually don't wanna do this inside of this loop of every snake.
We wanna do this just for us. So possible next steps are, is I don't know that because that's gonna be possible next steps is an array is an array. Cool. And we're gonna push into this array everything up, down, left, or right of us. Yeah.
Then we're gonna remove our body piece, our neck. We're gonna remove our neck. Yeah. Okay. Cause we don't want our neck in there.
So that, that will release 3 directions. And then for every snake, we're gonna repeat that logic. We're basically gonna say if any of these match, you remove it. So when we store them in here, we also have to store whether it's up, down, left, or right.
Andrew: K. Alright. Let's see. You have a plan, and I'm very curious to see how this
Kevin: will be implemented. First thing we wanna do basically is say up, down, left, or right. What is the coordinate? So what are the coordinates of that? So we're gonna say, in fact, I I think this might be easier.
So we're gonna go possible next steps dot push. We'll we'll start with left. No. Left is going to be x is, come on, Kevin, you can do this. X is going to be our current X position.
So this is going to be I don't just want my head. I want I want you. So I'm gonna save this. We'll come back here. I don't think we have you.
No. No. We have my hand, which is great. Let's just throw you in there. Then we'll come to avoid head to head and what just while I'm here.
And return move. Right. Return head to head. So now we have access to you in here. So we want Datastock Global's my head, you, and my head.
Cool. So that's gonna be you dot, why why did I do you? We just care about my head. What are you doing, Kevin?
Andrew: Yo. Because you wanted to remove the neck. You were this
Kevin: Yeah. But not oh, yes. I suppose that will be that will become important, but the neck comes later. Yes. So we just wanna say my head dot x minus 1 and y equals my head dot y y.
That's left. Bright. So that's the the same. Up. And so that's gonna be x remains the same, but y this time, r is minus 1 and down, plus 1.
No. It starts the other way around. Down because bottom left is 0. Up. So that's pushing in push in all I mean, it's it's pretty obvious actually by the, by by the name of what's going on.
Then we wanna remove our neck from that. Then there'll only be 3 items in the array. So we'll go possible next steps. How do I do this? Equals poss that becomes a let.
Possible next step, stop filter. Possible next steps. So steps, where the step is not equal to you. You dot what is the it's a bit disruptive, but I'm gonna just what is the what's it called? U.body.body.
Andrew: I love that my my many, many if statements, my heuristic was not okay, but there's this will be so much cleaner though when it's done. I will agree with you there.
Kevin: Thank you. So this is remove net. I think that's what that does. So every item where that because all the body parts are is this. It's an x and y.
Right?
Andrew: Yep. Yep.
Kevin: I I'm a little bit I think I think object, object comparison is a little bit hunky. So what I'm gonna do just to be on the safe side here is say, where s dot where s dot x is not equal to x and where it's not equal to y. So if both those things, no or no and hang on. This is this is important. I'm gonna do it the other way around.
So where they are equal to x and they are equal to y, that means, hey, this item matches or wrap the whole thing in another bracket and we will negate it. Okay. That should be removing. Does that make sense?
Andrew: I'm so glad we have a video because that's super unreadable, but I get the logic makes sense. No. No. No. Not not unreadable.
Unreadable. I mean, like, I feel like if somebody looked at this, they would be like, what is happening in this line?
Kevin: So, I mean, I mean, we're removing the yeah. Yeah. Right. Okay. Sure.
Yeah. So Who needs to
Andrew: read this code except for us?
Kevin: Exactly. No. This makes sense. Okay.
Andrew: Yeah. I
Kevin: think that works. So we removed the neck. So that's removing that's saying all of our possible neck positions. Now we wanna basically figure out all of the every other snake's possible next steps. And if there's any matching item oh, there's a there's a silly, silly little thing going on here, which is we're gonna push in.
I'm gonna just call the variable c to mean coordinates and that and this is where it all starts falling apart in terms of, like, clarity. And I am gonna I am I I've decided I am gonna be more explicit here.
Andrew: No. I think it was
Kevin: I mean, I
Andrew: think it's I think it's very clean code before, but, yeah, I think that this makes a little bit more sense.
Kevin: Yeah. So we have the cohorts. And the reason this matters, It's because we don't just want to store the coordinates. We also wanna store the actual reference to which direction it is. So as well as the quotes, we are gonna say direction left, and we can get rid of that.
We can get rid of that. So direction left, direction right, direction down and direction up. Right? So now we just have to change this bit here because it's not s dot x anymore. It's s dot quartz x and that's dot quartz y.
But it will when it removes it, will physically the directions will no longer and then at the end, we're gonna just return the those directions. So I think that that's an important thing. So we can actually ultimately at the end change is move safe and know what values we are we are removing. Yeah. Because before, there's no how do we know which one's which other than the order we initially push them in?
Andrew: Mhmm. Not a
Kevin: nice. The next thing we want to do is go around every snake, and we wanna basically do this same game here where we go left, right, up, down, push it into an array, cross reference them. If any of them are the same, we know the direction in which they match. Oh, that That's funny. Off reactions gone.
Are you familiar with these?
Andrew: I am, but you're in Arc. Right? Is that where
Kevin: it actually is?
Andrew: Or is it within Riverside?
Kevin: No. It's it's macOS.
Andrew: Oh, it's in Mac. Really? No way.
Kevin: Yeah. But I don't Well,
Andrew: I'm not I'm on Windows. Windows is like, no. We don't we don't think these are things that we need.
Kevin: That is so funny. So now we now we wanna do the same thing for this snake. And this is this is the important thing is that for every snake, we are literally gonna do this exact same logic, except we're not gonna remove the neck. We're just gonna say, oh,
Andrew: are we gonna remove the neck? No. No. We'll just remove the head.
Kevin: No. No. No. No. Because if we you know, because if we're colliding into the neck, we're colliding like that would have been handed by the dome.
Yep. Yeah.
Andrew: Exactly. Yeah. So it's just the head. Exactly. Exactly.
Kevin: So we just care about this logic. So possible next steps. And I am just gonna just shrink this down slightly because it'll be contextually clear what's going on here. So it's no longer my head. It's no longer my head.
It's head. Head. Okay. And so that's so that's that. We're doing that for every single snake.
And now this is the important thing we are going to compare. Yeah. We are going to compare. Let's just have a think about this. We actually don't care about the directions this time.
The directions are irrelevant. I think, I think we'll leave them in for the sake of having the same data structure. So we know exactly what we're doing, but this bit's irrelevant. We just yeah. It's it's irrelevant.
So this n is going to go before it's just one big array of every snake. We don't need to have it on a per per snake basis. So this is us populating and with every viable direction that could ever be in. Actually, I've had a change of heart. We are we are gonna Sure.
That down.
Andrew: And then we're just gonna be comparing our end versus our possible next steps. Hence, look, we're
Kevin: just gonna be You absolutely you absolutely got it.
Andrew: I like it. Okay. This is cleaner than I thought. I was definitely you were, I get where your head is at with removing some of this stuff now. I love it.
It took it took you getting to the end of this for me to be like, yeah. I I this makes sense. Good. Good. Good.
Thank you very much. Thank you, Ro. Smart code. Very much.
Kevin: So now what we're gonna do is compare every single value in n. So we're so what we're gonna do now is and yeah. Yeah. Perfect. So we'll we'll loop now.
So it's a for let n of, can't do n.
Andrew: Yeah. If you just change n to to next moves or something like that or or yeah. Yeah. There you go.
Kevin: Sure. Oh, I see. So for so next, I'd rather that's fine. For Yep. So I can use the shorter version inside the list.
I'm about to use it, I think, a reasonable amount. So for every single value in there, is it one of those? It's all we care about. Let's have a think let's have a quick think about how we're gonna achieve that. Okay.
So for every single one, we're gonna say, does next possible Does value in one array appear in another?
Andrew: Hey. There we go. Nice little vanilla JavaScript there.
Kevin: Absolutely. Include some and include. Some text element against the test function that return. Yeah. It's great.
Cool. I don't think this is, oh, but it's that's no. It's that same thing again with objects. So I think the object nature of it, like, object comparisons are are specific. Yeah.
It's a find and a size I think this is actually flipped.
Andrew: What are the responses? Did they say it actually works?
Kevin: I think so. That's a bit of a it's a bit of a big big example. Got it. Got it. Lovely.
That's the answer. So I'm gonna just I'm gonna just pop it here and comment it out so we just have a little reference at the point where we're writing it. So this is an array. Let's consider this as our possible next steps. Yeah.
So we wanna say, for, matches r.sum. It's not called r, is it? It's called next possible steps. Possible
Andrew: next steps. Possible next steps.
Kevin: Possible. I'll fix that in a moment. So sorry. The sum method of an array test whether at least one element in the array passes the test implemented. Okay.
So next possible did I make a typo?
Andrew: Yep. Possible next steps, not next possible steps.
Kevin: Just Thank you.
Andrew: It's what I'm here for
Kevin: Possible next steps. And it immediately changes color to be like, yeah, you can spell now. Thanks. Possible next steps dot sum item. We'll do that again, and it's item dotcoords dotcoords.
Dotxmatchn dotx. Dotxanditem.coords.y. For n of next. Is this is this how is this really I I think I I think there's some redundancy in here, actually. Yeah.
What? Yeah. This is all we're this is this is trying to, like but we're already in a loop. We're already in a loop. So all we wanna know was, I've got it.
Matching collision. Collision. Is that how you spell it? Collision Yep. Equals possible I got it.
I got it. I got it. I got it. Next steps dot find and because we just wanna find does does one match. Right?
And what we wanna know is step in there, is there a value where the cohorts dot x are the same as n.xandstep.cowards.y equals m.y. If this returns a value
Andrew: It works.
Kevin: Then that yeah. Then that collision it's gonna be a little bit of a long winded one to test because also we're gonna have to go we're gonna have to go back through the logs. This one's gonna actually be really painful to test because in theory, if it works, you know, but, so what this will return is a possible next step. And so what we will then do is if this this will have the value of possible next step in it. Mhmm.
If it matches, so what we will then do is say if collision, because it will be 4 to undefined, like, some faulty value if if there's nothing returned in there, then possible just get myself a few characters because I think I'm gonna go quite far to the right here. Possible next steps equals possible next steps dot filter, and we wanna remove this item. And it's this. It's this logic right here that we already wrote, but slightly different. So we wanna say we're s.coawards.x, where collision by collision.cohorts.x.
Oops. This one here, where it is the same as collision collision. Am I spelling it right? I I swear I swear I swear I
Andrew: That's right. No. You're good. You're good.
Kevin: I'm literally just like, you know, when words start to look wrong.
Andrew: Yep. No. But it's catching it. It's catching it, turning it purple if you're right.
Kevin: And this is the same as collision dotcoorg.y. Okay. I think I think this is
Andrew: it.
Kevin: Then oh, no. There's one more step. So now possible net, possible next steps is a shortened array which has just items which are viable. Yep. Just items which are viable.
Let's just have a think because we we don't wanna
Andrew: What is that actually gonna look like, though? Do we know right now?
Kevin: It it will be an array of objects that look like this.
Andrew: Is it just gonna be a same case?
Kevin: Yeah. Yeah. But just a shortened a shortened list. Yeah. Now what we actually wanna do now I think about it is we kind of want the others, which I've just realized we want we want the other items.
We want the ones that are being removed. So Oh, to So removed from his move. Yes. Yes. Yes.
Yes. So let make false. This is we're starting to get a little hunky now in terms of, like, code quality, but, like, it's fine. So if collision we don't wanna screw around with possible next steps. We don't care about that.
All we're gonna do is add collision dot direction to make force, and we're not even gonna do that. We are gonna just straight up say is move safe dot left, and the value of left will be in collision dot the equals false. And that's the direct we are making the the value that we came in with. Yeah. We're setting it to false because we're gonna collide.
And I'm not sure if this is valid, syntax.
Andrew: I will find out.
Kevin: We will find out. I might I might just save ourselves some effort here. Commission dot the okay. Alright. Sorry for bringing oh, don't don't play this game.
I, I'm sorry. I felt like that wasn't an overly, like, collaborative exercise, and I'm sorry.
Andrew: That was it was wonderfully collab I don't know what you're talking about. See, my favorite collaborative experiences are the ones where I can nod along and say yes, And I can I can help pick out when you've misspelled something? That's, I feel like I've I've contributed in my own YouTube. I don't
Kevin: know don't know what that little error is at times. Anyway
Andrew: It's weird.
Kevin: Let's and this one is gonna be a little bit of a hard one to to test. So we're gonna just have to watch watch this carefully. The snake's going up the whole time.
Andrew: Okay. So there's a bug somewhere.
Kevin: There's a bug somewhere, but we will be able to See debug it. Oh, it all died here. Un un there's a problem in the setup of the globals. There's a comma.
Andrew: Change in here.
Kevin: Comma.
Andrew: Comma. Those commas.
Kevin: It's always the commas.
Andrew: Grammar's out to you.
Kevin: I'm loving I'm loving now that we don't have to touch the code editor at all.
Andrew: Yeah. Agreed.
Kevin: Okay. See. So where K. Avoid head to head. Fine.
Unexpected token line 8. Great. Okay. 8. There is an extra bracket here and an extra bracket.
No. No. No. The one at the end is the one at the end is correct. There's an extra bracket here too.
Great. Let's see if that does it. Yeah. I I literally cannot believe we're okay. I was gonna say we're getting it to work.
Andrew: We're getting things are happening. We're debugging, and that's the important thing.
Kevin: Alright. Snakes is not defined. Okay. Not a problem.
Andrew: What's the I mean,
Kevin: this is good. Oh, snakes. Fine. It's all really small, like, little errors, quite well defined, quite quick to fix, allegedly. Alright.
Alright. Alright. I need everyone to know at the time of recording, it is 11:30 at night. Snakes dot shift is not a function or its return valuable value is not iterable. Right.
That's that's fair. And that is here, snake dot shift. So what we're gonna do is just it really doesn't matter. Like, snake star shift, and I I'm assuming it that's how it like, it needs to happen on its own line first.
Andrew: Kevin, if this is what you programming at 11:30 at night does, I don't wanna see you programming when you're fully rested and awake.
Kevin: Well, here's here's the joke. I'm I'm never fully rested. I mean, children. Children. Yeah.
Fair enough. Well, it's going it's going up, but I also didn't set that up correctly. But let's let's keep having a look at the logs. S is not iterable. Okay.
I think I've misunderstood how shift works. 1st element. Oh, it returns the first element. I don't want that. I I want to return them.
Right. Right. Right. It returns just the element. It pops out of the top, but I want everything else.
Andrew: Oh, everything but. Yeah. So what's the option of the shift?
Kevin: It is to do the head, avoid head to head. So it is to do the work. So it's to do the shift snake stop shift, and then I think it might actually be to put snakes back in there. Yep. But I'm not sure if snakes is re I'm not sure if that will work.
I think it will. Okay. I think it's not doing that anymore. This is obviously not the test, the,
Andrew: we need to But we're not we're we've got no actual errors in the code that we've well, that we I use we very liberally. The code that has been produced in that final card is working and it's definitely a little bit slower now. So I think that we're definitely getting a, there's a lot more compute going into this, which totally makes sense.
Kevin: Yes. Okay. We're we're still going. Woah. That was a okay.
Okay. It did end up giving up. Why? Oh, because there was no viable choice. Yep.
So that's Which is yeah.
Andrew: Wow. Hey. We wanna move fast oh, sorry.
Kevin: What do you mean?
Andrew: I was gonna say if we wanna move faster for the head to heads, put multiple snakes in more than just one.
Kevin: Okay. I I think we I think we got a snake.
Andrew: I think we did too. I think it's working.
Kevin: I'll stick 2 hungry bots in that There we go. Cough.
Andrew: That should do it.
Kevin: Should have hit start game first, so it did its job. Okay. Head to heads are obviously gonna be hard to to start.
Andrew: Yeah. But we can
Kevin: kind of see we can kind of see here when it ends it ended. Okay. So now we can watch the whole thing.
Andrew: Yep. So he's not what's he turn why is he turning? Was there a safe move there?
Kevin: There was a safe move. How do I go back? Pause back. No. There wasn't.
Andrew: Nope. There wasn't.
Kevin: It got itself into a corner. Fine. I'm confident I mean, I'm not confident enough. I do that were there any head to head opportunities? And I didn't see one, did you?
Andrew: Nope. No. But I think I think we're not getting any errors at that final stage, and I think that's a good sign that the logic in there makes sense.
Kevin: You yeah. Yeah. But it might not be doing what we want. So what other few do you think 11 by 11 is still a good size, or maybe I should make it small?
Andrew: Make it small, and then we can see faster. And, yeah, give that a try. Yeah. Yeah. There we go.
Yeah. Run that and see what happens.
Kevin: Oh, it's it it went on for a reasonable length of time.
Andrew: K. So there looks like
Kevin: No. Our snake didn't didn't need to didn't have a head to head moment. Well, I mean, it avoided a potential head to head moment there, but But it's I'm happy I'm happy in no. Yeah. Because we'll never know, will we?
Yeah. We'll never know.
Andrew: We'll know if we play enough games over a long enough period of time, but I think the code is there.
Kevin: And we would know it doesn't work if there is a head to head, but they don't happen often enough. Hey. We leveled up our snake.
Andrew: That's solid. So not only move it to a different platform where it's running, same logic, and then adding that extra step and super visual. Super visual. I'm very I'm very,
Kevin: very satisfied. I'm very satisfied by it that working. Awesome. I think that brings us to time. I mean, we we have to be at time at this time.
You gotta go to sleep. I've got yeah. My kids will be up in 5 minutes. So, awesome. Thank you for for for for doing this again.
Andrew: Yeah. Thanks for joining us, everybody. I feel like I'm glad that you were driving this time because had I been driving, this would have not gone as smoothly in any way, shape, or form. So I feel like this was gonna be
Kevin: be silly.
Andrew: But I I am excited to eventually drive when we, again, in episode 3 or 4 when we start to implement some other technology. So do we know what we're doing on our next episode yet, or is it all
Kevin: In our next episode, we're gonna stay in Directus, and we're not gonna be leveling up our snake. But given that Directus is backed by a database, we're gonna start actually storing data about games and then seeing what we might be able to learn with Directus Insights.
Andrew: I love it. I love it. That's gonna be an awesome next episode. So, we've got a snake. You've seen how it works.
That's all of the steps for the starter snake that are worked out in there. It's actually just a functional JavaScript snake. You could pull all of that logic out. Well, most of that logic out and just put it into a JavaScript Express server and just go to town. But, but we've got it working in here.
And so now let's do a little bit more. Let's see how powerful you can make Directus with Battlesync data. I'm very excited.
Kevin: Yeah. This is awesome. So until next time, this has been ready, set, battles, stake.
Andrew: We're getting better. We're getting better.
Kevin: Some some would say. Some would say. Till next time, folks. Bye, buddy.