Playing Pokemon Together with Node.js - Samuel Agnew
Hey!.
OK.
Another fun fact about our next speaker: If you haven't seen his profile picture for the
conference, definitely check it out.
It's actually him playing onstage at metal fest in Peru, when he did a South American
tour with the thrash metal band.
Lots of talent.
So next we have playing Pokémon with Samuel Agnew.
Guess I should have checked this before.
I see someone coming forward.
[applause]
Is there any way I can get like something where I don't need to use my hands?
>> Unless I could, could, like, one-hand live code.
>> AUDIENCE: Technically you just did.
>> Yeah, I guess I did.
There's my talk.
Dang, though, so this thing is not a thing.
OK, I don't need to be coding until a couple minutes into my talk so I might as well get
started.
How's it going JSConf, a company called Twilio pays me to build goofy hacks and about them
and I flew in from New York City the other day, and speaking of JavaScript developers
from New York, I want to give a quick shout to by buddy speaking in the other track.
You should watch it on YouTube later because he also organization a meetup called Queens
JS which is one of my favorite ones and sometimes I convince my employer to give them money.
This talk is going to be more of a fun one than a serious.
It's the last session and I know towards the ends of conferences attention spans start
wane, so I figured what could be more fun than playing Pokémon with a bunch of JavaScript
developers.
I'm running Pokémon red version which is a game I played a ton as a kid.
And I have the Windows version of this emulator running, even though I'm on a Mac and the
reason for that is there are a lot of old game emulators where the Windows versions
of them have developer tools, because people still make real games for these old systems,
and for some reason or other, most of those developers use Windows, so one of the features
that this emulator has that I'm going to be using for my talk is scripting functionality
so we can run lui scripts and we can control the memory of the game and button input and
stuff like that.
I'm being mic'd up.
Cool. [laughter]
So what we're going to do is we're going to write a Lewis script that interacts with a
node.js express server sitting behind a Twilio phone number and I'm going to have you all
text a Twilio phone number to tell me what buttons to press in the game and the Node
app and the script are going to communicate with each other.
Testing, can you hear me?
AUDIENCE: Yes!
Sweet, awesome, so this might sound kind of familiar to to some of you.
If you about five years ago, there was a twitch.tv livestream but instead of a human being playing
it, it was everyone watching the stream typing in the chat which buttons would be pressed
and when you have 80,000 people playing the same game controlling it at the same time,
things get pretty interesting.
So when I found out that these emulators had scripting, I guess because I work for Twilio,
I was like, whoa, wouldn't it be cool if you could do that with text messages, too?
And I it turns the Lua scripting thing is really popular in the speed scripting community.
Where people try to write scripts to complete games in the most efficient way possible.
This documentation seems pretty bare boneses so I've actually been following this other
emulator's documentation.
Turns out all these old game console emulate y they have the exact same Lua API I guess
because they're all open source and once one implemented it the other ones were like, cool,
gonna copy and paste that.
You can do stuff like read and write bytes to the game's memory, like the RAM and ROM
and that kind of stuff.
I'm going to have this Twilio phone number set up so that I can programmatically control
T so whenever you send a text message to this phone number and it's a 760 phone number which
I think is an area code around here.
So whenever you send a text to that number Twilio is going to send a post address to
this address right here, which is basically a tunnel to a port on my local machine that
my Node express app is going to be listening on.
That app does not exist yet so we'll get to that later.
If you text this number right now, literally nothing will happen because it will be dropped
on the floor because the code doesn't exist.
I'll show it on the screen later so you don't have to copy it down right now.
Anyway, let's get into the basics of how to write code for this Game Boy emulator.
I'm going to do a Hello World thing, you might have seen on this, I have my Twitter handle
up on the Game Boy emulator.
My Twitter is sagnewshreds,
I'm going to say hello, JSConf with a smiley face, and I'm going to save that and I'm going
to stop this other script from happening.
And I'm going to go find this Lua script on my computer in my talks folder in the JSConf
folder and I'm going to run that and it says, hello, JSConf cool, we've run some Lua.
But it disappeared.
And the reason is it's only running once.
We can repeatedly print this on every frame if we want it to stay.
And for that we need to write an infinite loop.
Because infinite loops rule, right?
We write those all the time.
Because this emulator, the whole environment in there is single threaded this will actually
stop the game from even playing.
So this emu object gives you a function where you can control the frames so you can advance
to the next frame.
This way our infinite loop is pretty much taking over the run time of the game so that
each iteration of this loop is going to be one frame of the game.
So all this code will execute on each frame so I'm going to advance to the next frame
at the end of each loop and going to restart that and now the hello JSConf is going to
stay there.
Now I'm going to write some useful stuff.
So what we want to do is this Lua script is going to communicate with our Node.js stuff.
I know this is JSConf and I'm writing Lua.
I'm not much of a Lua developer.
I don't know how many of you in this room consider yourselves Lua developers, but I'm
not particularly.
So I'm going to write some code to communicate with that Node.js and we're going to do something
technologically advanced here.
Really complicated stuff.
We're going to communicate by reading and writing to text files.
Yeah, so on each frame I'm going to read from a file called button.txt, to see if a button
was pressed.
So I'm going to write some utility function, so I'm going to write a function that can
read a file and then I'm going to open that file.
If I can type correctly.
Cool.
So I'm going to open that file name with the read flag and then I'm going to check to see
if that file actually exists, so if it's nil, then I'm going to set the input to be that
file, and then I'm going to create a variable called content and read the contents of that
file into it.
And then I'm going to close the file, because you always gotta close something you open.
You gotta finish what you started, right?
And then I'm going to return the content of the file which should just be a string of
whatever was in the file.
And next I'm going to write a quick function to press a button in the game.
So this is going to take a button, just like the text of a button, like A, B, start, select,
up, down, something like that.
And the way the joy pad API works in this Lua scripting is that it takes a table of
buttons, like a key value kind of thing, like a JavaScript object or a Python dictionary
and it takes the button name and true or false for whether that button is being pressed or
not.
So I'm just going to create an input table here.
And empty one, and I'm going to set whatever button we're trying to press equal to true.
And then I'm going to call the joy pad.set function, and this is actually kind of interesting
right here.
One of the idiosyncrasies of this is there's only one or two controllers, but I guess because
they copy and pasted for the consoles, you still have to give it the button input.
So unless you're giving the link input, there's only one player, so player 1 is going to have
this input table and I'm going to go down to this infinite loop and we're going to say
for each frame of the game I'm going to check to see if there is anything in button.txt,
so I'm going to create a variable that is going to read the contents of button.txt and
if the button is nil.
Press the button, pretty simple, right?
So I'm going to put a message out to say I'm pressing the button.
As I said, I'm not much of a Lua developer, so Lua string concatenation is just two dots.
So if that looked a little funky to you, that's what that is, that is just string concatenation.
So I'm going to avoid duplicates like if you send A to the text message thing and then
it writes the button to that file, I just want to press the button once, I don't want
to keep pressing on each frame repeatedly.
Like if you want to press the button twice, you'll.
It's going to return nil and button is going to be nil, so this won't be executed, but
speaking of pressing buttons for multiple frames, that's another thing I want to address.
So you are all human beings and when you're playing a video game, you're not playing as
fast as machine would, so when you press a button on a Game Boy you're at least holding
the button down for a solid portion of a second.
So we're going to want to press it for a certain number of frames.
Let's say 5.
Which is still not a long time, but long enough for it to register and this is the syntax
for a foreloop in Lua.
But I want to make sure I advance the frame again after each iteration of the loop.
So we're done with the talk using a language no one writes, or at I guess some of you do.
I guess technically do.
Because I'm writing it right now.
But first I'm going to reload this Pokémon.lua script.
Now I'm going to write the letter A, capital letter A in this text file and if I didn't
mess anything up, when I go back to the emulator, it should be pretsing the A button and as
you see, it's on this menu screen, so if it works this should go to the screen where I
select a new game.
Pressing A. -- I mean I guess it said it was pressing A. Oh, there we go.
Maybe five frames wasn't enough, actually.
But we'll see.
So the Lua code works, that's great.
I'm glad to hear that.
Now I'm going to open up a saved state that I have, because later on once we're actually
playing this, I don't want to have us to sit through Professor Oak telling us all what
to do.
All right, time to go now, I'm going to write some JavaScript finally.
So I'm going to open a file called index.js and I'm going to build a quick express app
so for a lot of people who are already familiar with backend Node development this will seem
pretty straightforward for you for those of you who aren't familiar with backend node.js
development, I guess this will be a good little Kickstarter.
So first, going to require the FS module, because you know, writing to files, gotta
use the file system for that.
Also going to require express, because it is the web framework that I'm using, and I'm
going to grab body parser, because I'm going to need to deal with the body of a post request,
because when you send the text message, Twilio sends the post request representing the message
and I need to get stuff in the body of that post request to access what the text of that
message is.
So body.parser and now I need to use the Twilio API.
The Twilio node module and I'm going to grab the messaging response object from that so
this will generate the twiml for me.
Twiml being the Twilio SMS.
My web app is going to have to respond with these XML tags, particularly the messaging
response one, saying hey, respond to this message with a text message and no one wants
to write XML by hand, I don't so I'm going to do this.
Cool, I'm going to create my app object and I'm going to make an array of valid Game Boy
buttons that you can text.
They're also the start and select buttons on the Game Boy, but I'm not going to use
that one, because that opens up the opportunity for you all to troll me by spamming the start
menu and that's not fun, so I'm not going to let you do that, at all.
Next I want to make sure I'm using the body parsing middleware, can't forget that so when
I get the URL encoded post body I'll actually be able to use it.
Gonna write that real quick.
Cool.
Now we can get to the fun stuff.
Writing the post handler, so this will be a /sms route as you saw me write in my Twilio
function.
First time I'm going to do is create a new twiml messaging response object and then I'm
going to grab the button that you want me to press so I'm just going to assume whatever
text message you sent me is only going to be a Game Boy button and I'm going to grab
that from the request body.
This is going to look slightly confusing, because the inside of the body of the post
request, the value that I want is body with an upper case B because that's what Twilio
sends you as the actual text of the message.
It's called the message Body.
So I'm grabbing the body and I'm going to convert it to lower case so I can make this
case insensitive and also because I don't have to put the burden of you dealing with
your autocorrect and stuff.
We don't want to deal with that.
So going to convert that to lower case and then I'm going to see if what you texted me
is actually in our list of valid buttons that we want you to be able to text, so if you
text me start, not gonna get that.
So if you sent me a valid button, I'm going to add a message to this twiml objected and
this will be the text message that I sent to you in response and I'm going to say thanks
for playing Pokémon with me and if you have any questions I'm going to give you my Twitter
handle again, or my email address in case you want to send me some more longer-form
complaints about how I'm using semicolons and you hate that or something and I'm going
to send you a smiley face because I'm really happy you're all here.
So next I'm going to write this button to the text file to communicate with my Lua script,
but an interesting thing here is when you're trying to press the A or B button, it expects
it to be upper case, but if you're trying to press like up, down, left, right, start
or select, it expects it to be lower case, so slightly annoying, but we can deal with
that.
I'm going to say if you're trying to press A or B, going to throw that back to upper
case -- oh, no, I'm going to say if you're pressing A or B in this little "if" block
here then I'm going to set the button to upper case so then I'm going to actually press the
button by doing this super-complex writing to a text file.
So it's going to go to button.txt, I'm going to write that button to it and I'm going to
go to utf8 encoding and now if you text me something that's not a valid Game Boy button,
I'm going to respond with a message asking you to send me a valid Game Boy button.
Because you know, you might make a typo or something, or maybe you want to comment on
my hair or something, I'm just going to say hey, please text me a valid Game Boy button.
No smiley face on that one.
[laughter] And now I'm going to deal with actually responding
to this HTTP request, so I'm going to make sure that it's a 200 response, because if
I'm actually getting the text message and stuff, just going to give it 200, let's go
with that.
I'm going to set the content type to be text/xml, and I'm just going to send the twiml object
a stringified version of it, so this is just going to be the xml that it generates, which
is just going to be the text message I want to send back.
Should be about done now, make sure this is listening, I'm going to go to port 3000, console.log,
express app listening on port 3000, going to put a bunch of exclamation points.
That should be all the JavaScript I have to write before I open this up for you to all
play Pokémon with me, though, I want to make sure I don't have errors.
I'm going to run this and I'm going to text this, if you did take that number down, I
guess you can play it.
yeah, someone pressed up, someone lent left.
OK, so people are actually -- it looks like the code is working at this point, because
I'm not getting any errors.
And you know what that means?
That means we get to play Pokémon together, so who wants to play Pokémon until we run
out of time.
So I'm going to need you to take your phones out, and send a message to this number, 760492-6659,
and let's see if we can go select a starter Pokémon and battle our rival.
If we can get past that, I'll be pretty stoked.
So want to go up and down, want to get out of mom's house, because we're a big kid now.
All right, it's sagnews house, that's my house.
We want to go up and to the right.
All right, we want to go right.
Let's do that.
Isn't it great that I didn't let anyone press start because we would just be standing still
right now.
Gotta get in the tall grass so Professor Oak will see us, be like.
All right, so you want to go right and up.
Let's do it.
Right?
I believe in you.
I believe that this community can stick together and we can defeat.
Yes.
OK.
All right, Oak is looking out for us, now let's just span A and B so we can get past
this guy talking to us, this science guy, Professor Oak the science guy.
Cool we're on our way to his lab.
Gonna get our first Pokémon, I'm excited for our adventure, we don't even have to write
up, the game scripts that for you.
As you can see, I named my rival fool.
Because it's customary to name your rival will something not nice and I think he's kind
of a fool.
When I was young I was a serious Pokémon trainer, cool.
I'm going to span 3 so we don't accidentally spark a conversation with him.
All right, so now we want to go down and right once we're done this.
All right, let's go right.
Let's do it.
We got this.
So we're going to go right.
Oh, I believe in us.
I believe in us.
I actually just copied right and I'm just copying and pasting it now and sending it
in a text message.
Now we want to go up and press A. Which Pokémon should we select?
Well, at least we're going to be able to fight against charm it's
probably better.
Oh, A, AI, OK.
J, P, -- or JZ OK, cool, really creative nickname, extremely creative nickname and this fool
is going to take charm ander, sow now we have to battle him.
And what's our strategy going to be for this battle?
Are we just going to repeatedly tackle?
You want to use growl at all?
>> Bulbasaur, yeah, I think Charmander has tail whip.
Oh, no, we hit growl.
We're growling at each other.
Oh, yeah, we're going to tackle, tackle him now.
Not a very strong tackle, probably.
Oh, no.
We're -- if we lose to this Charmander, what does that say about the JavaScript community?
Oh, he's scratching us.
I think we can do this.
I should have grabbed this possession and then saved after that.
But that would have been cheating.
We don't need to do that.
We can do this ourselves.
As long as we spam A and get lucky.
I think we're going to win.
Uh-oh.
We've got to press up, like, once.
Oh, no.
At least the Charmander is going to be really weak.
We're just growling at each other.
This is like an intimidation match right here.
I still have faith in us.
I think we can -- all right, we hit tackle.
We hit tackle.
Oh, no, critical hit uh-oh.
That ain't good.
Uh-oh, I think we can do it, though, we're at least making him weaker.
All right, we're tackling again.
Charmander is growling at us, getting weaker as time goes by, but so is Charmander.
Oh, no!
We're doing one damage to each other.
Growl can't make him any weaker.
No.
Oh, no.
All right, nothing happened.
All right.
We got growl.
If nobody ever presses up or down again, then I think we got this.
I think we can win.
Oh, no!
You know, I still believe in us.
Oh, no!
You know, if we get a critical hit, we can make this happen.
We can win.
Oh!
It's still possible.
It's still possible!
Oh, no!
OK.
All right.
You know, I'm never -- I'm not going to give up on us.
I'm not going to give up.
That is unfortunate that we missed.
And he's still growling.
OK, come on, critical hit.
No.
All right.
No.
Well, well, we lost -- I guess we're the fools now.
Oh, man.
Well, whoever shouted Charmander should be happy now, because at least you know Charmander
is the best choice.
And that's my favorite line, gramp, smell you later.
But I guess we lost that first battle, but I have faith we can grow as Pokémon trainers,
and learn, because you know, going to conferences is all about learning and improving and stuff,
right, so we're going to become better Pokémon trainers yet, just you wait and see.
With that said I think that's all I have.
My name is Sam Agnew, I'm a developer evangelist at Twilio.
I'll be at the pool and the after party later.
Have a good rest of JSConf.
[applause]
And thank you for playing Pokémon with me.