COLTON OGDEN: All right, we should be
live here on CS50's Twitch channel.
Apologies for the delay.
I'm afraid our PC actually
had a blue screen of death.
So we brought in a reserve machine.
So thanks to Dan Coffey
for helping out with that.
But, yes, welcome to everybody who's
been patiently waiting in the chat
here.
First of all, for folks who don't know
who I am, my name is Colton Ogden.
I work full-time with CS50.
And I thought as part of
our new Twitch channel,
we'd take some time today, about a
few hours, to from scratch code a game
called Snake, which is sort
of an old school 2D game.
If you ever grew up having a Nokia phone
or the like, you've probably played it.
And it was played on sort of
older computers back in the day.
And we'll take a look
at what that looks like.
But, anyways, thanks
so much for tuning in.
Let's dive into what we're doing and
start to take a look at everything.
So I'm going to pull
up my computer here.
And now we have a little
twitch.tv/cs50tv graphic there.
So if you're not following
the CS50TV account on Twitch
so that you can join
the live chat with us
and we'll have some back
and forth conversation,
definitely go ahead
and follow that link.
Hit Follow.
If you're watching this
on YouTube after the fact,
we will be pushing this
video to YouTube later.
But there is a 10 minute
waiting period, I believe,
for if you want to actually chat.
And I'll see your chat messages
here as we go ahead and program.
All right, so what you see here
is a text editor called VS Code.
This is what I'll be
using today to program,
but you can use sort of any
text editor that you want.
You could even theoretically use,
like, Word Pad on Windows or whatever.
I would definitely recommend getting
something like VS Code or Atom
any of the free sort of text
editors that are out now.
I personal like VS Code.
And it has some cool plugins, one of
which I will use for the framework
that we'll be taking a look
at today to make Snake.
Let's take a look at what
Snake actually is, I think,
before we go too in-depth into
what we're doing programming wise.
So Snake kind of looks like this.
It's basically a grid, right, where
you have this green long thing that
goes in various directions.
And it continuously moves throughout
the sort of grid space in the game.
And then every time you pick
up these red dots or whatever
object that you want
to frame in the game,
you're a snake creature which starts
off with just one piece will actually
get another piece at the end of
it at the tail, and you'll grow.
And so the catch is the
longer your snake grows
sort of the more you run the risk of
biting your own tail or your own body,
so to speak, because
you're continuously moving.
And when you bite yourself,
the game actually ends.
And so the goal is to essentially
grow the snake as long as possible
by eating apples or however you want
to visualize the red dot in the game.
Hello to Bhavik Knight
there in the stream.
Thank you for visiting the chat
today, visiting the stream.
But, yeah, the game that
we're going to be making
will look very similar to this.
We're not going to
get too fancy with it.
I actually haven't done
any prep or any research
into how we're going to make the game.
I figured we'll just kind of dive
into figuring it out on the fly.
So that's Dan Coffey in
the stream, everybody.
Say thanks to Dan for fixing our broken
machine today and getting everything
up and running.
All right, so first things first.
In order to start programming
something visual or to program a game,
we'll need to find some tool, some
game engine, to do this for us.
And I'm a big fan of a 2D game engine,
a 2D framework, called LOVE, which
uses Lua as its scripting language.
What's up, Steve Benner?
Good to see you again.
Thanks for coming by.
So LOVE is a 2D framework.
It's an OpenGL framework strictly
catered towards 2D games.
And it has a very nice
API for game programming,
which makes it pretty easy,
nice and simple to get into.
But it does allow you a lot
of low level flexibility
that some bigger engines
kind of take away from you
or at least kind of hide
behind layers of abstraction.
I'm thinking of things like UNITY, where
there's a big learning curve associated
just with kind of getting the engine
right, getting the UI correct,
and not necessarily diving too deeply
into code for at least a while.
Hello, Elias from Morocco.
Good to see you.
D. McDermott, good to see you.
OK.
So we're going to go ahead and take
a look at LOVE2D and sort of Lua
and its syntax and the
different functions therein.
In order to get started,
you'll need to if you
want to follow along
whichever version of LOVE
corresponds to your operating system.
Today, we'll be using the newest version
which is version 11, or 11.1 rather.
I'm on a Mac.
I'm running Mojave.
If you are on a Mac running
Mojave, you might have some bugs
with actually running version 11.
So make sure in your
Accessibility panel, which
I'll take a look at here just
for those running on a Mac,
if you go to your Security and Privacy,
rather, and then your Privacy section
here, there is a section
of apps that basically--
part of Mohave, it
seems, allows a lot more
apps to have accessibility features.
I'm not exactly too sure of the
low level aspect of what they do.
But they basically allow the apps to
have more control over your computer
than normal.
So in this Accessibility panel
here, under the Privacy panel,
just make sure that LOVE and
Terminal and also whatever text
editor you're using are checked.
And this is Mac OS X Mojave specific,
not specific to Windows per se
or Linux.
And that's also on version 11.
I've gone ahead and
downloaded it already.
So if I go into my
console here, assuming
that I've put my executable in the right
spot, I should be able to just of LOVE
normally.
And you'll see this sort of thing
with a balloon and a tail that says,
"no game" and that's perfectly normal.
If you're on a Windows machine or if
you're on a Mac or if you're on Linux
and you run the binary, you
should get the exact same thing.
And I have a shortcut to
that on my desktop here.
Let me just hide everything.
That doesn't want to hide.
But I have a shortcut to love here.
If I double click on that, I get
the same exact window there as well.
So the reason I'm able to access
the binary through my command line
is because I've actually aliased it.
So if anybody is
curious, there's actually
instructions for this on the Wiki page.
But if I go to nano home
directory bash profile,
you can see that I've aliased
to the command to love to be
Application.love.app
/Contents/MacOS/love.
This is another Mac slash
Linux specific thing.
But for today's examples,
it suffices just
to double click the executable
that you download from LOVE2D
and/or drag your project
folder onto that executable.
And, again, the instructions
available for how to do what I did
are on the Wiki.
I've got to figure out
where the exact page is.
I think it's on the Wiki here and
then the Getting Started page, yeah.
So on the
love2d.org/wiki/gettingstarted,
you can get some more instructions
on to actually pull up LOVE2D,
you get it to work by itself, get
it to work with files specific
to your operating system.
Again, I'm using Mac OS X today.
Mac OS X is similar to Linux, but
Windows will be slightly different.
But that's LOVE2D.
So once you have that all
set up and ready to go,
if you've double clicked the
executable and you've got that window,
then we should be ready.
So I'm going to open
up my text editor here.
And I'm just going to fire up a
very simple LOVE2D application just
so we can see sort of the
backbone of how it works.
By the way, sorry, I haven't been
paying attention to the chat.
OK.
So Bhavik says, "I will try Atom.
I know a bit of Vim.
David uses it.
I'd like to useful plugins for Vim."
I don't use Vim too often, so I can't
actually suggest useful plugins.
I know that Jordan Hayashi, one
of our alums, uses Vim a lot
and has a lot of plugin help.
Maybe on another stream,
if David gets on,
we can maybe ask him what
plugins he likes to use.
I am not a Vim expert by any
stretch of the imagination.
But, yeah, Atom is great.
Visual Studio Code, VS
Code, is great as well.
So I'll be using those.
But perhaps in the
future, we'll take a look.
And then Sugo Gruber from
Germany, Bhavik Knight says, yeah.
OK, awesome.
So we're going to go
ahead and let's say that I
want to create a new LOVE2D application
on some location on my hard drive.
So I'm going to assume that I have
a folder called Dev somewhere.
Within that, I'm going to say
just for the sake of today
I have a stream subfolder.
And I'm going to create a
new folder called Snake.
And in that new folder
called Snake, I'm first
of all going to open that
folder with my text editor.
So on Mac, you can just click and
drag it onto your text editor.
And that will open that text
editor in a Project view.
I'm going to click and close out the
other window that I had available.
And I'm going to right
click, do a New File.
And I'm going to call
this file main.lua.
Now, in LOVE2D, main.lua is sort of
like the entry point of your game.
And that's going to be necessary
in order to tell LOVE2D
where to start running your game.
And in C, and even in Python
in certain situations,
you have the same exact idea.
In C, you have this
function called main,
which the C compiler knows to look for
and then bootstrap your application.
In Python, you can do this check
that says, if name equals main.
And, basically, that
will launch that Script
file as the bootstrap
of your application
if you have multiple script files.
In LOVE2D, the same aspect applies.
But it looks for a file called main.lua.
And this should be in the root
of your project directory.
We've got some other people,
Blow Into The Cartridge,
has a Heart Symbol,
love the name, Belacura,
Bhavik Knight says, "sure, hello all."
Thanks very much for
coming to today's stream.
Hope you're ready to code
some Lua, some Snake.
OK.
So I have my main.lua file,
but it's completely empty.
So it's obviously not going
to be useful for anything.
I'm going to just write a
couple of functions here.
So function love.load is one of them.
And this is using Lua, the language Lua.
So we've got Lua syntax.
So we have function,
the keyword that says
this is going to be a
function called love.load.
Notice the function ends with this end
keyword, which is a Lua specific thing.
I want to make a function
called love.update.
Notice that load does
not take a parameter,
but update does take
a parameter called dt
which means that love.update will have
access to this variable within it.
And we'll take a look at what
that means a little bit later.
I'm going to define a
function called love.draw.
And this is the function that's going
to handle everything related to actually
rendering things onto the screen.
And then that should be it for now.
Oh, actually, one more
function, I'm going
to do this just out of convention.
It doesn't really matter where
you define these functions
in terms of their order.
But I'm going to define a
function called love.keypressed.
And this one takes a key parameter.
So this function, as
you might surmise, is
used to check whether a given
key is input on any given frame.
And with that key, we can
then do some sort of logic.
In this case, very simply, I'm going
to do a condition here that says,
if the key is equivalent to
this string called escape, which
means if the user
pressed Escape key, then
I want to call this function
called love.event.quit.
And very simply, this will allow us
to test whether our application is
running correctly.
So I'm going to, first of
all, make sure where I am.
I'm in my home directory.
I'm going to cd into that
folder that we created.
So cd dev/streams/snake
and then ls there
for list, which means I can see
sort of what files are there.
I can see this main.lua
file that I created.
And so if I just type love
space dot, what I should see
is a black window, because I
haven't defined any rendering.
But if I press Escape, which
I did, the window closes.
So everything seems to
working appropriately.
Black screen is completely normal.
Notice that when we open that up,
it said sort of this untitled word
at the top of the window
where the window bar is.
So if I do love.window.setTitle to,
let's say, snake, or snake50 rather,
and if I rerun this as I did before,
notice that, oh, it did not work.
Did I forget to save it?
Make sure I'm in the right place.
love.window.setTitle
snake50 should work.
Huh.
Strange.
OK.
Not sure why that's not
working, hiccup, minor hiccup.
So this is in the Snake directory.
Let me make sure.
Oh, LOVE 11 may have changed
the function actually.
Sorry, LOVE2D v11 or
love window setTitle.
There are a couple of things that
LOVE 11 has changed versus LOVE 10.
So LOVE 10 was what I taught the
Extension School course, the games
course.
By the way, I'll plug that here,
cs50.edxorg/games if you're interested.
This course teaches LOVE2D
and Lua with version 10.2.
And in the middle of the course, they
actually updated LOVE to version 11.
And they changed a lot
of the functions therein.
So part of that is going
to be me figuring out
what some of these new functions are.
But it looks like they didn't
change the setTitle function,
so I'm a little bit curious
as to what's going on here.
If I run this--
oh, you know what?
Hold on.
Let's do a drawing example
just we can figure out
why exactly it's not working.
"What is the name of the software
you are using," says Elias.
So Elias, this text editor
it's called VS Code.
This is just a text editor.
And it's made by Microsoft.
And it's kind of like a simplified
version of Visual Studio,
hence the VS part of it.
And then the terminal
here is just a shell
that you would use on a Mac or PC.
So on a PC, it's called
CMD for Command prompt.
And on a Mac, it's called Terminal.
And I'm actually not entirely sure
what it's called on a Linux machine.
I think it's called Terminal as well.
Dan, do you happen to know?
It's called Terminal?
Yeah, it's called Terminal
on a Linux machine as well.
And then, obviously--
Chrome to browse the web.
But, yeah, the main two things are just
the VS Code and the Terminal there.
"Black text on black
screens," says Dan Coffey.
Oh, did I write that in
the love.keypress function?
Oh.
Pff.
OK.
Sorry, yes.
I thought I was in the
love.load function.
Thanks, Irene, for pointing that out.
The love.window.setTitle wasn't working,
because I thought I was writing it
in the love.load function.
But the logic that I was just writing
was that if I press the Escape key,
set the title to snake,
and then quit the game.
So we weren't actually seeing it.
So that was a silly issue on my part.
If I run the code now,
now it's says snake50.
So thanks, Irene, for pointing that out.
And thanks for joining this stream.
I appreciate it.
I might need you to keep me in check
while I make all these mistakes today.
Great.
So that was how to get a window
up and running more or less
and how to test for keyboard input.
Does anybody have any
questions before we
get into maybe doing some
stuff related to graphics?
Is anybody having issue getting LOVE2D
or Lua running on their machine?
By the way, you can click
and drag a folder onto LOVE
if you're not comfy
with the command line
and you would prefer to just use
more of a drag and drop interface.
If you have a shortcut
on Mac or PC and you
have a folder within
which is your main.lua,
you can click and drag the
folder onto the executable.
And that will function the same way.
In this case, interesting--
OK, hold on.
There we go.
So if you click and drag the--
I'm not sure why it didn't
work the first time--
but, normally, you click and drag
the folder itself, not the main.lua.
Don't drag the main.lua,
because the LOVE executable
is going to look for a folder.
Click and drag the folder onto
your shortcut on your desktop
if you have it there.
And then that will trigger
LOVE2D running the application.
Blow Into The Cartridge says, "all good.
Thanks for this awesome video.
Stumbled upon it and
already super hooked.
Keep it up."
Thanks so much, Blow Into
The Cartridge, appreciate it.
Thanks for coming by.
I'm going to go ahead and start messing
around a little bit with graphics.
So I know that Snake is
generally a grid-based game.
It looks like it's mostly
just made out of squares.
So we can sort of visualize our game
space as being this 2D grid, right?
And in LOVE2D, we can visualize a
coordinate system where on the top left
corner is our origin or 0, 0 point.
And anything in the positive
direction is going downwards--
sorry, on the y-axis is going downwards.
And on the x-axis, anything
that's going sort of to the right
would be positive as well on the x-axis.
Inversely, if you were to go to the
left side past the edge of the screen,
that would be negative on the x-axis.
And if you were to go above
the screen on the y-axis,
that would be negative on the y-axis.
So you can always visualize anything
that you put in your game world,
whether it's a sprite or a shape,
as having an x, y coordinate.
So I want to start
thinking about maybe how
I can fill this space with my
game world with a bunch of squares
that sort of represent, you know,
the ground and the snake as well.
So there's a function in LOVE2D
called love.graphics.rectangle,
which takes a mode first of all, so
whether I want a filled rectangle,
so something like filled with color
or just an outline of a rectangle.
In this case, I do want
a filled rectangle.
But we're probably going to want
something with maybe an edge
as well, so that I can see the
individual pieces of the snake.
And now I just have one
contiguous snake if it's
just all a bunch of squares
sort of chain together, right?
And I'm probably
thinking I want some way
to visualize where the
head of the snake is, too.
Because especially as the
snake gets really big,
I'm going to kind of want to
keep track of where it is.
Otherwise, it'll get lost
amongst all the green squares
that I'm seeing throughout the game.
So I'm going to just pass
in a string called fill,
which just tells this function
we're in fill mode not in line mode.
The other one would be line.
And then I'm just going to say,
I want to draw a square just
in the upper left.
So I'm going to say 0, 0.
And let's just say my snake
is going to be 16 pixels--
this square, rather, is going to
be 16 pixels wide on this x-axis
and 16 pixels tall as by the y-axis.
I'm going to save it.
I'm going to rerun it.
And then notice that up
at the very top left--
it's kind of small--
we do have a rectangle,
but it's white which is interesting.
So I didn't have an option
to specify a color here.
It just sort of defaulted to, oh,
whatever I draw just make it white.
It turns out that
LOVE2D, just like OpenGL,
is based on a state machine,
state machine just meaning
that the LOVE2D kind of has any
one given state at one time.
It has a set of variables
internally that it
manages that allows us to just change
the way that it renders things.
In this case, I want to do
love.graphics.setColor to some value.
And that will change the state
of LOVE2D such that anything
I draw after that will abide
by that state transformation.
So any color that I pass into this
function will apply to this rectangle
that I just tried to
draw right afterwards.
And this was different
in LOVE version 10.
But in version 11, all of these
variables are now between zero and one
that I pass into this function.
And it's going to take four
variables, a red, green, blue,
and an alpha component.
And these four variables are what
determine the color of something.
So in this case, I want a green square,
because the snake generally is green.
So I'm going to say 0 red, 1
green, 0 blue, and 1 alpha.
And what that will translate to is
no red at all, full green, no blue,
and full alpha.
And for those unfamiliar
what alpha is, is basically
how transparent or opaque that color is.
So at 1, it's fully opaque,
meaning I can see it completely.
But if I were to say 0.1 or 0, it
would be invisible or slightly visible.
Bhavik Knight says, "my Scratch project
for CS50 P set 0 was based on Snake,
but tail wasn't dynamically increasing."
Well, hopefully, we can
demonstrate some techniques
for potentially making that work today.
And there's multiple
different ways we could do it.
I have a couple ideas in mind,
but we'll see what may be best
and what we can figure out live.
OK.
So I've specified the color of my state.
Whoop, sorry.
I'm going to save it first.
Always make sure you save
your project and rerun it.
And then when I rerun it, notice
that, indeed, we have at the top
left no longer a white
rectangle, but a green one.
So it seems to be working quite well.
Now, this isn't all that
interesting, because, you know,
we can't move the snake.
It's not doing anything.
It's just a static
rectangle at the top left.
But we do have something that
we're drawing to the screen.
That's a good start.
But let's say now I want to
move my snake to the right.
We'll do it continuously, I suppose.
And so we've been Messing
around with a love.draw
function, which is where you can define
everything that you draw to your scene.
We messed around with love.keypressed.
Now, we want to sort of
take a look at love.update,
because it feels like now
I want to actually update
what's going on in my game.
I want to change something in the scene.
So in my love.update, I want to think
about how I want this green rectangle
to move.
And I'm thinking I kind of
want it to move to the right
maybe just infinitely.
I'll just infinitely
move it to the right.
So since we talked earlier about how
everything exists on an x, y plane,
sort of like this
coordinate system, I know
that if I want to move it to the
right, the right is positive.
And it's a positive
transformation on the x-axis.
So whatever I draw on this x-axis, which
is here, this third or fourth variable
in the rectangle function which is this
x-coordinate, if I just increase that
over time, that will have the result
of my rectangle moving to the right,
right?
And if I were to decrease it, it
would move the rectangle to the left
and it would come out of view.
So let's say I want to make this
a variable, maybe call it snakeX.
And let's go ahead, first of
all, declare snakeX up here.
local snakeX equals 16.
So it's going to initialize itself
to the value 16 which we had before.
If I rerun it, notice that
it stays the exact same.
But I want to change this over time.
So what I'm going to
do is this variable dt
is what's probably one of the most
important variables in game programming
generally.
But specifically here in LOVE2D
within the update function,
this will allow us to update anything
by multiplying dt by some value.
Because every frame, dt is
essentially the amount of time that's
passed from one frame to the next.
So between frame one and
two, about, what is it,
0.16 seconds elapses,
which is 1/60 of a second.
And if we define a constant and
multiply it, it'll move that constant
or manipulate it based on how much
time has passed between each frame.
SP Vendor says, "isn't that the
size of the rectangle you're
changing rather than the position?"
Oh, yes.
So sorry, yes.
That's correct, my bad.
The x, y is actually these first two
numbers, not the last two numbers.
And in this case, this
would need to be 0.
So good call, Steve.
Thanks for pointing that out.
snakeX should be that first parameter.
This is why we do it live, so
we can see all these wonderful,
wonderful blunders.
OK.
So, anyway, dt is
basically going to allow
us to multiply any
transformation, any value,
by the amount of time that's
passed between frame one and two
or between two and three.
Just every frame, this
will be a new value.
Let's say I want my
speed of my snake to be,
oh, I don't know, 100 pixels a
second, which it would effectively be.
This will effectively be how
much that moves per second.
And if we multiply--
sorry, if we were to assign snakeX to
itself plus the snake speed times delta
time, what that'll effectively
do, just sort of like plus
equals in other languages
like Java or C--
unfortunately, Lua doesn't have a plus
equals operator which is unfortunate.
But we can say take my
value of snakeX and then add
that speed value times delta time.
So this will take place every
frame, so 1/60 of a second.
So this will be 0.016
approximately multiplying by 100.
So over the course of 60 frames or 1
second, we'll have moved 100 pixels.
It'll basically just
update itself consistently.
So now if I were to do
that, notice that we
are moving, indeed, about
100 pixels every second
if you were to calculate it out.
And the beautiful thing about this
is it'll actually stay consistent
between different hardware.
So if you're running on a
machine that's kind of slow
and it can only process one
frame for every three frames
that another machine produces,
the dt will be triple the amount
in between the individual frames.
And so this will still move
it 100 pixels per second.
Got a few messages in the chat--
"isn't this written--
yes, that's why I guess
he's using the function
update for moving positions."
That's correct, Zad.
We are using update to move positions.
We do it so that we have
access to this dt parameter.
Because if I were just to
do it without dt, right,
and we're not scaling it by
the amount of time per frame,
this will run absurdly fast.
First of all, you didn't even
see it move-- well, hardly.
Because it's just moving at an insane
rate, basically as fast as my CPU
is capable of running this operation.
Or I guess, rather, I think, it's
hard cap to 60 frames in LOVE2D.
Often, with game engines, if you don't
cap the FPS, this won't run at a cap.
It'll run hundreds of times.
But in LOVE2D, I do
believe that this actually
only runs 1/60 of a second either way.
So if we're running at 60 frames, we
are moving per second, that would be,
6,000 pixels.
So we moved 6,000 pixels in the span
of a second versus 100 pixels, which
is not what we want, generally.
That's too fast for
the human eye to see.
So we use dt to scale everything based
on the time that's elapsed per frame.
Time difference, Bhavik Knight,
it's short for delta time,
so, yeah, effectively
the time difference
between one frame and another, the delta
between frame x and frame x plus 1--
I should say, rather,
frame x and x minus 1.
Awesome.
OK.
Sounds like everybody's
on the same page here.
So I have a moving snake, but
it's not really responsive.
I can't do anything
if I were to run this.
And I'm pressing the keys.
I kind of want to have control
of where the snake goes, right?
I want it to move down, left, up,
and right if I hit those keys.
And I'll just kind of infinitely
move in one direction.
Moreover, I want it
to move in such a way
that, if it were to go past
the right edge of the screen,
it should actually come back--
sorry, reverse, mirror's flip.
It should actually come
back on the left side.
So if the snake goes all the way to the
right, it should come back to the left.
And if it goes all the way up, it should
come back from the bottom going up,
so that we kind of have
this looping world space.
Because, otherwise, we won't see where
the snake is if it doesn't come back,
right?
It won't work that way.
So we have a few problems
that we need to bite off here.
The first thing that I
think I'd like to tackle
is changing the direction
which the snake is moving in.
OK.
So we're going to need
a couple of things here.
So if we can move in not only the x
direction, but also the y direction,
I should probably make another
variable called snakeY and also set
that to zero, right?
Because we're going to potentially not
just move the x, but also move the y.
Andre, "is LOVE's delta time
constant, or will it theoretically
be a larger value if
the frame rate drops?
Is there an equivalent to UNITY's
fixed delta time in LOVE2D?"
The first answer-- yes, it will be
larger value if the frame rate drops.
And in terms of fixed delta
time, I do believe it does.
Let's look up the LOVE2D
Wiki, LOVE2D fixed timestep.
This is somebody using it, I think,
in a project, but not actually-- oh,
this is like somebody manually
creating their own fixed timestep.
It may be something that
you have to fix yourself.
Typically, you use this sort of
thing in physics calculations
if you need to apply some physics
transformation step by step rather
than having it apply to some, have
it be like, a multiplicative value.
In this case, I don't know if LOVE2D--
I actually haven't used it myself.
Or at least I think I did once,
but it's been a long time.
I don't remember offhand.
I think you do actually have to
write your own fixed timestep.
And I think you can actually mess
with a LOVE run function-- sorry,
not LOVE runs out, LOVE2D run function--
which, underneath the hood,
this is LOVE's function.
Yeah, you can see it here.
I think this is where you would
ideally write your own fixed timestep
code where you sort of
perform physics calculations
and whatnot and do sort of like minusing
of the dt off of that larger timestep
and then apply the
update changes but this
is where you can sort of
see what the overall sort
of main function of LOVE2D, or rather
the game loop of LOVE2D, looks like.
For those unfamiliar, every game
has to have a loop where it runs,
like I said, every sort of frame over,
you know, usually 60 frames per second,
every single frame doing some updating,
doing some rendering, all that stuff.
This love.run is LOVE's example
or LOVE's implementation
of this game loop.
But, yeah, to answer the
second part of your question,
Andre, I believe in LOVE2D
you have to actually implement
fixed time step manually, not as it
normally exists with update and dt.
Good question.
OK.
So to move our snake, we have the
ability to currently just move it
in one direction.
Let's test it first with being
able to move it left and right.
So if I were to, for
example, press left,
then instead of adding the
snake speed to my snake,
I should probably subtract it.
Because that'll take me
from going to the right
and increasing my x value to going
to the left, decreasing the x value.
And I realized, again, that my
character on the screen is flipped.
So if I do key =, for example, left
here, then I should be able to say--
let's say I need to keep a
variable of this, whoops.
snakeMoving = left, right?
And then-- else if key =
right, snakeMoving = right.
And then up here I'm going to
need to keep track of that,
so snakeMoving = right.
So now I have a variable that it'll
at least let me toggle back and forth.
It's given my snake kind of
this state where I can say, OK,
am I moving right or left?
And if I am moving right, add the value.
If I'm moving left, subtract the value.
To do that actual math, that's
going to take place here
in the love.update function.
And then what I can do is basically
say, if snakeMoving is equal to left,
then taking this statement that I
wrote before, I should probably,
instead of adding the snake speed times
delta time, subtract it like that.
And then else, it's going to
only be for now left or right.
I can say snakeX = snakeX +
SNAKE_SPEED times delta time.
And if everything is going according to
plan here, I'm moving right right now.
If I press left, oh, indeed, now
my cube is moving to the left
instead of to the right.
And I can switch back
and forth as needed.
And it's got this sort
of continuous motion.
So we've taken a step
closer in the route
that we need to actually get a sort
of snake moving around the screen.
Does anybody have any questions so far
as to sort of what we've been doing?
Looks like you haven't had any
questions in the chat just yet.
Feel free, ask as many
questions as you want.
I'll be monitoring the chat.
I'm happy to answer, happy to talk
about whatever folks are interested in.
But if there are no questions,
we'll just go, I think,
dive into sort of how we might do
this by moving the snake up and down
as well as left and right.
"Would it work to do something like
snake moving = key without the if else
if?"
Yes, in theory.
But this key can be anything, right?
So if I were to hit A, or Escape,
or Spacebar, or Shift, or Return,
it's going to get that value.
So it would only work in this particular
case if I input one of those two keys.
Now, we do have an else if
key-- oh, actually, it's
not going to evaluate either these
statements as true in that situation.
So it's just going to stay on
whatever its last value was
even if we tap a weird key.
But, generally, that's
not an ideal practice
if you expect that you can
get a lot of other values
and if you're sort of parsing those
values in fairly complicated ways.
In this case, it would kind of work.
But I would recommend
against it probably.
It's all a little bit more obvious
to know what's going on here, too.
Good question, though.
It's a good thing to sort of
try and stay conscious of.
You know, a lot of like
hash map related logic
can derive from that
sort of way of thinking
which can optimize your workflow.
OK.
So let's go ahead and do the following.
I'm going to add a couple
more conditions here.
So I added two more conditions.
And these two conditions are
just to take into consideration
whether you're moving up or down.
So in this case, snakeMoving
= up, snakeMoving = down.
And also, another thing about
this particular instance
is we're using strings
to test for values.
Sorry, we're using strings to
assign sort of state to something.
Generally, you wouldn't always do
this, because are a little bit more
weighty to use as state variables.
In this case, for the
key checking, they've
defined a bunch of different states.
But if you have a sort
of snake class or snake
object that has maybe 15,
20, 30 different states,
I would probably use an int or
some value and not a string just
to save memory, just be a
little more efficient in terms
of actually checking for comparison.
Because it is a little bit
more intensive to check
against a string than it is
to check against some integer
that we've defined.
For example, if I've declared
a table up here-- by the way,
we'll get into tables in a little bit.
If I have a States table, for example,
up, down, left, right, now I've
actually created--
sorry, and an up in this case.
These wouldn't exist.
But let's say one, two, three, four.
And let's say maybe those are defined
elsewhere as up, down, left, or right.
Now, I can just, for example,
up = 1, down = 2, et cetera.
Now, we can actually do a comparison
against an integer rather than a string
and just save us a little
bit of processing power.
But we won't worry too much about
those kinds of optimizations
today, because those are not
something that we need to worry
about for a project this small.
That would be for something
a little bit larger scale.
But, anyways, the next
part of our logic--
so we have, you know, the
simple basically take a key,
assign whatever direction you're
moving to its state, right?
We have some sort of
state with our snake
whether its moving up,
down, left, or right.
But I want to not only
test for left or right.
I also want to test for up and down.
So we can kind of do
the same logic that we
did before, else if snakeMoving
is equal to right, we can do that.
Else if-- whoops, it
wants to auto-indent
on me-- snakeMoving is equal to up,
then do this, whatever that might be.
Else if snakeMoving is equal
to down, then do the following.
Now, these are going to differ,
because we are no longer going
to add or subtract from our x value.
We want to add or
subtract from our y value.
Because our y value is
what's representing our
up and down in the game.
So I'm going to go ahead,
I'm going to copy this.
I want to just paste this in
here, change snakeX to snakeY.
Notice that my editor lets me select
multiple things and sort of edit them,
which is kind of nice.
And then this will function
in sort of the same way,
except this needs to be plus.
So if we're going down, remember
y increases as it goes down
and decreases as it goes up.
And, here, don't forget
we're always going
to be at zero unless you replace our
y-coordinate with the hard-coded zero,
to the y variable that
we're storing now.
OK.
So let's go ahead and run this.
Whoops.
Then expected near snakeX on line 30.
Ah, right.
I forgot a then keyword
right here, very important.
So, now, I'm moving left or right.
If I hit Down, notice
that now we can move down.
I can move up.
And I can keep moving sort of
arbitrarily around the scene,
so pretty cool.
So we've taken a few steps
in the right direction here.
Anybody have any questions on sort
of what's going on here at all?
And if not, we'll go
ahead and maybe talk
about getting everything
working in a grid perhaps.
OK.
Cool.
And this will be where we have
to spend a little bit more time
thinking about sort of the data
structures and algorithms that
underlie the game.
But right now, we just have continuous
movement along either the x or y-axis.
But I want to make my game, first of
all, a little bit easier to deal with.
Because if I can have
it in a grid, I don't
need to actually worry about continuous
collision detection which would
be maybe a topic for another stream.
But I can just have a very
discrete layout of squares
and basically check is there
a snake piece in this square,
if there is, and then do
whatever logic I need to do.
You know, check to see whether my
head is biting the tail of the snake
or whether there's an apple
in that tile, for example.
We basically need to think about
that way of looking at the problem.
But it's just a little bit
easier to also render it and just
check for those sorts of things, right?
Collision detection, if it's
continuous, we have a little bit more
to worry about.
We have to actually put our snake
into a series of bounding boxes.
Every single piece of this thing
needs to have a rectangle that
represents its collision.
And we need to check to see whether
any of those individual rectangles
are overlapping some other rectangles.
But, again, we won't
worry about that today.
Because Snake is a
grid-based game, we actually
get a little bit of that for free.
And the same logic sort of applies
to tile-based games, too, thankfully.
""[INAUDIBLE] is used all the time in
this context, not a time derivative,
which would be an
infinitesimally small value,
whereas delta time is just a small, but
real, value, something like 0.01667."
Correct.
Thanks, Andre, for clarifying that.
OK.
So, now, we can sort
of talk about tables,
because tables are very important.
In C, you get these
things called arrays.
And in Python, you get lists.
And in Java, you get arrays as well.
And JavaScript gives you arrays.
Generally, you see them
as arrays or lists.
In Lua, there isn't the
notion of an array or a list,
but rather a table which
is actually a combination
sort of of lists and things
called hash tables or hash
maps where you can assign some
sort of key with some value
and pair them together, and then
look them up with a very quick lookup
time versus having to traverse a list
and look at each individual value.
Today, we're going to use the
table data structure as a list.
So I'm going to say I
want a local tile grid.
And, again, local, I don't
know if I talked about it.
Local is basically just
saying I want this variable
to be exclusively within the
scope of where it's defined.
For example, if I were to declare
a value called local val = 1
in my love.load function--
sorry about that.
If I have a variable val that's
defined as local within this love.load
function, I cannot access
val outside of this function.
So if I were to try to
do val = 3 right here--
well, actually, in this case,
because there's no typing,
it's just going to override this value.
But they're not the same.
But if I were to do
print val, for example,
where it needs to access a
value, access of variable that's
been defined already, it's
going to throw me an error
and say, oh, val is
not declared anywhere.
It doesn't recognize that symbol.
In the case of local defined
here, because we're defining it
at the very top of the file
outside of any functions,
it just means if I have
other Lua files in my project
they don't have access to that variable.
But if I were to say, tile grid
without local, then other files
that import this module
or make reference to it
actually can get access to
that value which we don't want.
So I'm going to do local
tileGrid = and then
these two curly brackets here
which basically tells me I
want an empty table here.
And then we're going to fill it
with whatever information we need.
""[INAUDIBLE] relatively
bigger time difference."
Correct.
OK.
So I have a empty table.
And I want this table
to be representative
of my game space, my game window.
And I want to figure out how
many tiles we can actually
fit within my game window.
And so I'm going to, I believe
it's, love.window.setDimension--
SetMode, sorry.
And then this is where you can actually
specify how large your window is
when you open it.
And I think the default,
is it like 800 by 600?
I'm not entirely sure
what the default size is.
But let's just say I want
it to be 1,280 by 720.
And then what were the other parameters?
I think it also has some flags.
We could say full screen is false.
Resizeable is true, those sorts of
things, which allow us just to resize.
I'm actually going to set realizable to
its default which I believe is false.
And then I'm going to run this,
make sure I used it correctly.
Now, notice that it
fills my entire screen.
Since my resolution on my monitor is
currently 1,280 by 720, the window
itself now fills the entire
monitor which is useful.
Now, that I know what my
resolution is, I can say, OK, maybe
I want to have all of
my squares be 16 pixels.
Or, actually, let's do 32 pixels.
Let's make our square size 32.
So I'm going to say TILE_SIZE gets 32.
And then just to have good style, I'm
going to say window width is 1,280
and window height is 720.
I'm going to come down here,
replace this with window width,
replace this with window height.
And now that I have these
values, actually I'm
going to put this down here
since this is gameplay.
Whoops.
That was not what I meant to do.
I didn't copy it.
Copy.
Paste.
I'm separating sort
of the more rendering
stuff from the gameplay stuff.
So the snake speed is 100.
The window width and the
tile size are up here.
I'm going to go ahead and say I
need to figure out, basically,
how many tiles can I
render on the x-axis
and how many tiles can
I render on the y-axis.
Because I'll need to know this
when I start assigning positions
of all the individual tiles, right?
So I'm going to say tiles--
rather, MAX_TILES_x =
WINDOW_WIDTH divided by TILE_SIZE.
And MAX_TILES_y is WINDOW_HEIGHT
divided by TILE_SIZE.
And assuming that those divide evenly,
this should work out just fine.
I'm actually not sure offhand.
I think those divide evenly.
Let's figure that out.
So 1,280 divided by 32 is 40.
And 720 divided by 32
is-- oh, that's not, 22.5.
OK.
So I need to actually, because it's
going to truncate it down to 22,
because it's an integer--
recall, 1,280 divided by 32.
No.
I apologize.
No, Lua actually does do floating
point arithmetic I believe.
So toint I believe is
the function plus 1.
So now this will take whatever the
value of the expression that I'm
evaluating here is, assign it to
a integer, and then add 1 to it.
So it's going to bring 22.5 to 22 and
then add 1 Actually, as a sanity check,
let's make sure that's correct.
I'm actually not 100% sure offhand.
Lua, I'm going to say 22--
or I'm going to say,
what's 1,280 divided by--
sorry, 720 divided by 32?
Print 720 divide by 32.
OK.
It does do floating point as I thought.
sorry
so integers and floats, they aren't
really separate data types in Lua.
Lua has this data type called a number
type, unlike languages like Python
which do have floats and ints.
And C, which actually enforces
that you use floats and ints
and adhere to it more or less,
well, with some type coercion,
the number type will just automatically
do floating point arithmetic
when you divide.
So unlike some languages where it
just gets turned into an integer,
in this case, it'll actually
get turned into a float.
And actually I'm realizing now it's
going to be like half of a tile
off of the bottom, which is
going to look a little bit weird
if we get to the bottom.
So what I'm actually going to
do is I'm not going to add one.
I'm just going to take the
int of this, so that it'll
kind of come up from the bottom.
Because we're at a resolution where
it doesn't evenly map 32 pixels
divided on both axes, we're
going to deal with it.
There will be a little bit
of black space at the bottom,
but that's OK, better than it
sort of cutting off the snake when
it gets to the bottom of the screen.
OK.
So now we know how many
tiles we need to render
on the x-axis, how many tiles
to render on the y-axis.
I'm going to figure
out a way to draw this.
I'm going to look at the
chat really fast just
to make sure it didn't miss anything.
OK.
Hi, Nimble.
Good to see you.
Thank you.
OK.
Bhavik Knights says, "take the
greatest common denominator?"
Yes.
OK.
Yeah, we could do that if we wanted to
enforce our resolution sort of work.
But because, generally,
16 by 9 resolutions
are kind of the way that we want--
yeah, yeah tile size 40
would absolutely do it.
You don't really see
tile sizes that are--
yes.
And, Jeffrey, you could use
the round function as well.
That's correct.
But, sorry, the 40 pixel size
on the tile it's something
you don't really see too often in games.
You could absolutely do it and be fine.
But a lot of the time, graphics,
there's like sprites that you get online
or that you have your
artist create adhere to--
oh, yeah.
Irene, good point.
Yeah, I would round to 23.
But, yeah, if you are
looking to round something,
my approach is a little bit more--
oh, wait.
Actually, yeah, I
would round to 23 which
was the same point that I made before.
Anyway, what I'm trying to say is 32
pixels, because it's- what is it--
a multiple of two, it's something that
you'll just generally see more often
than something that's 40 pixels.
And it's not necessarily because of
any technical reason, although this
was more so the case back in the day.
But, nowadays, you'll
see a lot of retro art
is just kind of modeled after
that old school approach.
And it's more of a convention
than it is anything else.
OpenGL used to enforce that
textures were a power of 2.
And old hardware just kind of enforced
that blocks were drawn in tiles of 32,
just because that's the way
the hardware was modeled.
Because it was just circuit, you
know, binary circuitry effectively.
But, yeah, suffice to say,
40 pixels would work here.
But it's not a trend
that you would often see,
I don't think, in games unless you
wanted to do it as a creative decision.
And it wouldn't strictly map well to
changing resolutions to weird other--
like, because resolutions can
vary all over the place, too.
You kind of just have to take one
sort of trend for your art or one size
and make these sort of
decisions where you maybe
don't display half of the bottom row of
the grid to make 720P work, basically.
So good points all around, though.
"True, we will need a
custom function that
will get a second parameter to
specify if we are going up or down?
True, yeah.
And you can you can also use
math.ceiling or the ceiling function
and the floor function which they will
give you the highest integer around it
and the lowest integer around
it, respectively, regardless
of where the number actually is.
"Don't forget to change the tile size
in the last function," says Steve.
OK.
Let's take a look here.
Oh, right.
The actual rectangle being rendered?
Correct.
Yes, good point.
Thank you for bringing that up.
And that is why we
defined that up there,
so TILE_SIZE and TILE_SIZE, right.
So what we were getting at is now that
we've defined how many grid spaces we
want sort of on the x and the y-axis,
we should just sort of like sort of draw
out everything.
We can make sure that we've
aligned everything appropriately.
So what I'm going to do is I'm going
to use a for loop, a nested for loop.
I'm going to say for every
sort of column in the game.
And then within each column, I'm
going to say for every row, right--
I guess it's be backwards.
For every column and then
every row in that column,
I want to render a tile in that x,
y position, right, times tile size.
And we effectively looking in
those increments of 32 pixels.
So I'm going to set my color for
debugging purposes to blue, actually.
I'm going to say RGB to 1, right?
So if we render this-- whoops, toint--
oh, sorry.
Oh, sorry.
Is it to number?
Oh, sorry, no.
You have to use floor, math.floor in
this case or math.round, not toint.
Is that correct?
Hold on.
Oh, yeah.
Because, duh, there's no int in Lua.
So you can't actually do that.
So in this case, I'm going
to set it to math.floor.
And that should work.
OK.
So now we have a 32 pixel by 32
pixel blue tile moving around,
but we're not actually trying to
figure out where the blue tile is.
I want to make it cyan.
I feel like that's
easier to see on screen.
So I'm going to do something like this.
For y = 1 for the--
what do we define it as--
MAX_TILES_y do for x = 1 MAX_TILES_x do.
And what this is doing
is it's a for loop.
This is Lua's version of a for loop.
But we're saying we have
our initializer here.
For y gets 1 until it reaches
the value of MAX_TILES_y
do whatever's in here
and then the same thing
for every x-coordinate, x position.
For x = 1 until MAX_TILES_x, do this.
And so if our number of tiles
that we can fit on, let's say,
the x-axis is like maybe
40, it'll go from 1 to 40.
And then same on the Y--
1 to 30 or 1 to 40, what have you.
Don't forget to do--
OK-- making sure I'm
up to date on the chat.
And then what I want to do is,
for every one of these positions,
I wanted to call
love.graphics.rectangle.
And I just want a line
function right now.
I want to see my grid visually.
So I'm just going to
use lined rectangles.
I'm not going to use filled rectangles.
And then I'm going to hide
that left panel there,
so we have a little more
room here to work with.
I'm going to say, because
we need to do a x, y first,
I'm going to say x minus 1.
And this is an important thing to
be conscious of, because normally
loops in Lua they start at 1.
And, also, array indices,
tables, start indexing
at 1, which is a kind of
a weird Lua-based thing.
So we want to actually
subtract 1 from x and y,
so that it maps to
our coordinate system.
Because our coordinate system actually
starts at 0 just like most things in CS
usually do.
Lua is kind of a black sheep in that
it conventionally and syntactically
a lot of its things start with 1.
So I'm going to say x minus 1 times
TILE_SIZE, y minus 1 times TILE_SIZE.
I'm going to then say the size of that
rectangle on the x-axis and the y-axis,
which should be just that.
And if I've written
everything correctly,
I should now see a cyan grid.
And I do.
Perfect.
So this is an indication
of what my grid looks like.
It kind of gets cut off at the
bottom, because my monitor is not
quite tall enough for me
to see the whole window.
But, clearly, we're moving not only
anymore in just the, like, black space.
But we've outlined this grid that
we're going to start adhering to.
Now, if you notice, the
square doesn't adhere
to this grid in any
kind of discrete way.
It's sort of continuously moving
throughout the world space.
And you can see it can kind of get like
caught up in between coordinate points
and a little bit weird.
So what we need to do is
lock our square to this grid.
We need to hard lock it.
And I'm actually going to minus 1
off of the tile size on the y-axis,
just so we can see it
a little bit better.
And it looks perfect.
There, now, it maps perfectly to it.
We're not going down
a little bit too far.
But, yeah, again, we're going to need
to align this square with our grid
so that it's completely locked into it.
Let's keep the grid
active for a little bit.
And so what I'm going to do,
actually, I'm going to take this code.
I'm going to take it out of here.
And I'm going to define a
new function called drawGrid.
And I'm just going to paste
that into there just like that.
And then I'm going to define
another function called drawSnake.
And I'm going to call it here.
I'm going to say drawGrid,
drawSnake, right?
Because I want the grid
to draw first, and then
my snake to draw on top of the grid.
I'm going to paste that
bit of snake drawing
code into the drawSnake function.
And then just so I know the difference,
the clear difference between them,
I'm going to set the color to
green on the snake just like that.
So if everything's correct, now
I have a green snake rendering.
And it might be hard to see on stream,
but I have a green snake rendering
on top of a blue gridded background.
So if anybody has any questions,
definitely let me know in the chat.
The next thing that
we'll tackle I think is
sort of aligning our snake with
this grid, and then apples,
and then eating apples,
and growing the snake.
Well, we'll start with
just eating apples.
And then we'll add to
the end of the snake.
So any questions at all?
I'll monitor chat for a little bit
just in the corner of my eye while we--
oh, I also kept this local val = 1.
Let's get that out of there.
Don't need that anymore.
And, yeah, perfect.
So we have this grid table.
In this, we're not actually using
the grid table for anything.
But we will start using the grid table.
I'm going to assume that each
grid tile, each tile in my grid,
should have some value whether that's a
0, meaning that there's nothing there,
no snake, no apple, a 1, meaning that--
or maybe a 1 for the snake just
so I can differentiate the color.
And then a 2 for the snake and then
maybe a piece of the snake body.
And then 3 for the apple that
I want to eat with the snake.
And let's just say
that that's the extent
of our game, the mechanics of our game.
You have apples.
You have the snake head, and then
the snake body which can move around.
Basically, what I want to do
is I want to take this table.
I want to set all of its
values to the 0, 1, 2, or 3.
And then I want to iterate over the
table, basically, and do the same thing
that I did down here in drawGrid.
So what I need to do is
initialize the table.
So what I'm going to do is I'm going to
make a function called initializeGrid.
And I'm not going to make
it take any parameters,
because our grid is global in this case.
So we'll just assume
that it stays global.
I'm going to go ahead and kind
of just do the same thing.
I'm actually going to just
take this here, right?
I'm going to leave it
in there, because we're
going to sort of need that same logic.
But I'm going to copy this.
And then I want to do
something in here where I
can initialize the table to something.
Now, for each column I'm
basically going to want a table.
In order for this to
work, for my table to work
as a data structure
for this example, I'm
going to want what's called a 2D array.
I'm going to want a 2D grid
of integers, basically.
So it's 2D array in C. I
get a 2D list in Python,
although Python's a very flexible
language where you can have lists
of tables of all sorts of weird stuff.
Lua's kind of the same
way, but we're going
to have just a bunch of
tables within this table.
And then those tables are going to
kind of represent our rows rather.
So I'm going to say
table.insert into my--
what did I call it--
a tileGrid.
I'm going to insert into
tileGrid an empty table.
So the result of this
is that for every row,
I'm basically counting
down on the different rows.
For every single row for every y value,
I'm going to add to an empty table
into my grid, my initially empty
grid variable or table variable.
And then within the x loop
of this initialize function,
I know that I can index into this table
my tileGrid at the index that y is.
Because whenever I insert a new table
into that grid, tileGrid, I will have,
basically, tileGrid 1 if y
is 1 equal to a new table.
If y is equal to 2, this will exist.
This will exist.
This will exist every
time I increment y.
So for that reason, I can
go into the tileGrid1--
or, rather, y, which will be 1,
2, 3, 4, or 5, until MAX_TILES_y.
And then I can insert some
value there, because that's
where we're actually going to store
the integers, the 0, 1, 2, or 3.
So I'm going to set them
all by default to 0, right?
And I should be a little
bit better about this.
What I'm actually going to do is
I'm going to say TILE_EMPTY = 0,
TILE_SNAKE_HEAD = 1, TILE_SNAKE_BODY
= 2, and TILE_APPLE = 3.
So, now, I'm not using what are called
magic numbers, which we teach in CS50.
They are sort of constant variables.
And I can replace this with TILE_EMPTY--
so a nested loop basically
going through our entire grid
for the size of the tiles
on the y times x effectively
and then initializing all of those
to 0, all of the tile indices to 0.
"I've never had the
chance to get into Lua,
but I saw you easily control
the snake with your keyboard.
Is it a code you did, or something the
LOVE library allows you by default?"
How I was able to control the snake
was the keypressed function here.
Basically, I'm looking to see--
anytime you press any key in LOVE, it's
going to call this function keypressed.
And it's going to have some value
that this key variable gets.
And it's going to be a string.
And in this case, I can check,
for example, is it escape.
Did the user press Escape
if they pressed a key?
And if it is, I can quit.
If it's left, right, up, or
down, I can change some value
that my code lets me do.
So snakeMoving is a
variable that I declared,
which allows me to move
left, right, up, or down.
And then in my update function, which
is a function that occurs every 1/60
of a second approximately depending on
how your computer can handle LOVE2D,
which generally it's
fine, every frame that
elapses love.update gets
called with a parameter
called dt, which is the
amount of time that passed
since this frame and the last frame.
And the snakeMoving variable will
have left, right, up, or down
set to it, right?
So then if it's moving left, I
can decrease this snakeX variable
that I declared earlier.
The snakeX variable is just my
position in the 2D coordinate system,
so its value on the left and
right, the horizontal axis.
And same thing with up
or down-- letting me
manipulate my snakeY variable, so
whether the snake is going up or down.
And then in this case,
what we're doing is
we've defined some speed
that I want my snake to move,
but this will be sort of relevant still.
But it won't work exactly
the same going forward,
because we're going
to start transitioning
into a grid and discrete movement.
But the SNAKE_SPEED variable
here we're multiplying
by delta time, which means that no
matter how many frames have elapsed
between now and the last frame, or
sort of how many frames are skipped
if you're running on a machine that
is a little bit slower potentially,
this value will be consistent.
It will consistently
move you to the left,
right, up, or down by
some value over time.
And so we, therefore,
use snakeX and snakeY
in our love.graphics.rectangle function
call down here when we call drawSnake.
And then notice that drawSnake itself
is called up here with drawGrid
in our love.draw function.
So there's a lot of pieces.
We've put this all together.
This isn't something necessarily
that LOVE2D has allowed us to do.
But it's made it fairly easy in as
much as we can check for user input
and how much time has
elapsed since the last frame.
And we have these nice convenient
functions for drawing simple graphics.
These are the things
that LOVE2D gives you.
And it's up to you to
take them and program
them to fit the needs of your
particular game, so good question.
"How to do a main menu, like
a start, play, or option?"
That's a little bit more complicated.
You would generally use what's
called a game state machine
and have a sort of certain object that
keeps track of what state you're in.
So am I in the main menu?
Am I in the play state?
Am I in the game over state?
And if you are in any
particular state, you
render a specific set of things,
whether it's the interface options,
like pause game, start
game, continue game,
whether it's a character running around
a field, anything that you name it.
But, basically, you
have different things
you render upon a different
state being active at once.
And you toggle some variable that
keeps track of what state you're in,
so good question.
We probably won't do
this ourselves today.
But in a future stream, I'd be happy to
talk about implementing a state machine
and going into maybe a menu for
a game or something like that.
Shayna says, "did you align
it vertically to the grid,
or it's not necessary
when the snake moves?"
We did not align it vertically yet.
So if we run it, we can actually
see that we can sort of move it
wherever we want to.
And see, now it's kind of stuck between
different axes, different grid lines.
So it's up to us as our next step to
actually do this alignment, which I'll
demonstrate how we do very shortly.
Irene says, "to draw
row by row, otherwise
you draw a column by column I guess."
Bhavik Knight says, "I don't
understand why column is the outer loop
and the row is the inner loop."
You can technically do it either
way, but the trend is generally
to do the y on the outside.
Because if you were to visualize a
table as being like this, for example--
and this is essentially what
our table's going to look like.
If we have a smaller
version of our table,
you can kind of see that, if
we're iterating over the y,
we're going table by table by table.
Basically, if we were to
look at our table row by row,
we're actually looking at this being
an element, this being an element,
and this being an element.
So it's easy to iterate first over
y, adding a table, adding a table,
adding a table on the
horizontal and then,
once we've added those
tables, to iterate over the x
and populate them with these values
just like this if that makes sense.
It's generally easier just to
think about the problem that way,
but you can, I believe, just
technically do it either way and just
have your rendering code
address it properly.
But this is what you'll see vastly more
often is the y first and then the x.
Good question.
OK.
So we have a grid available.
Let me just make sure I haven't
added any unnecessary code.
So, now, we've done a little
bit of modularizing our code.
We've taken some of
these statements out.
We've put them into functions.
We have an initializeGrid
function, drawSnake, drawGrid,
all these sorts of things.
In the love.load, this is where I
actually want to initialize the grid.
So I actually have to call it somewhere
before it'll actually execute,
because all we've done is
just initialize a function--
or rather, sorry, declare a function.
But we haven't called it.
So I'm going to call the
initialized grid function here,
which will actually run that
code and populate our tile
grid with all that information.
So, now, we have a grid
of 0s, effectively,
being the size in the x and
the y of our game window.
So what I want to do
is, in our drawing code,
this is where now I probably want to--
or rather, for the drawGrid function,
this is probably where I want to check
to see what exists in that grid, right?
What's going to be the value at any
particular index in our 2D array?
Because if it's a 0, remember,
I want to draw an empty tile.
And if it's a snake head, I
want to draw the snake head.
If it's a snake body,
if it's an apple, I
want to draw different things, basically
change the color of my OpenGL state,
of my LOVE2D state.
So I can do if the tileGrid
y, x is equal to, let's say--
how did I have them--
TILE_EMPTY, do-- sorry, not do, then.
I always get those confused.
If the tileGrid at index y, x--
which, remember, arrays slash tables,
they start at 1 in Lua by default.
If it's equal to TILE_EMPTY
at whatever y, x,
I want to call
love.graphics.rectangle line.
And then I'm just going to
draw a white grid, actually.
So I'm going to set this
up here to be 1, 1, 1, 1,
the love.graphics.set color.
That will make the entire grid white.
So all 1s is white.
And all 0s is black.
And, specifically, these three, this
value here is just the transparency.
This has to be 1 for
it to be pure white.
If it's any lower, it will just
be kind of a transparent white.
But black will render the same
either way whether this is 1 or 0.
I'm going to draw the x minus 1 times
TILE_SIZE y minus 1 times TILE_SIZE.
Remember-- because the coordinate
system is 0-based, not 1-based,
even though we're 1-based
currently in LOVE2D's tables.
And then I'm going to do
TILE_SIZE and TILE_SIZE, right?
So if this is correct,
then the grid is only
going to draw if that
index at that grid is a 0,
is TILE_EMPTY variable, which we
have declared up here to be 0.
So if I run this, notice
that it indeed works.
We now have a white grid
instead of a cyan grid.
And if I were to go here, else
if love.graphics-- or sorry,
if tileGrid y, x is equal
to TILE_APPLE, for example,
then I want to do this same exact thing.
I'm just going to copy and paste it.
Actually, no, the same
thing is going to happen.
Sorry.
I have the logic mixed up.
This is the important part.
We're going to draw this either way.
Although that's not technically
true, because sometimes we
want to draw a filled and
sometimes we want to draw a solid.
OK.
Never mind, scratch that.
I'm going to do this.
I'm actually going to take
this setColor function.
That's going to go
into the if statement.
So change the color
to white for the grid.
And notice that I'm using
comments with a dash dash.
So that's how you do comments
in Lua is using the dash dash.
To do block comments would be dash
dash square brackets square bracket.
And then now anything within that
will be considered a block comment.
But I'm just going to use a
single line comment for now.
So we're going to set it to
white and then draw a line
rectangle if it's equal to TILE_EMPTY.
But if it's equal to TILE_APPLE--
which you don't actually
have any in the scene yet, but we
will add one just to demonstrate.
love.graphics.rectangle fill, we want
it to be a solid red rectangle, not
a line rectangle in this case.
I'm going to the same exact thing--
times TILE_SIZE y minus 1
times TILE_SIZE and then
TILE_SIZE and TILE_SIZE, right?
And then I want to also make
sure that I set the color to red,
which is the 1 in the R, 0, 0, 1.
So red is 1.
Green is 0.
Blue is 0.
Alpha is 1.
Change the color to red for an apple.
So just a little bit of commenting,
documentation goes a long way.
Make sure, as you're
writing your application,
that you try to comment things that are
maybe not super obvious in this case.
These are pretty fairly
obvious things to comment,
but it's still helpful at a
glance to know what's going on.
Elias says, "do you
stream every Friday?"
Not yet.
This is only our second
stream, but the goal
is definitely to establish a
consistent streaming schedule.
And I'll probably be doing
a lot of game related stuff.
But we have other people
that were talking about
to maybe do some web
development or Python stuff,
maybe even get some GitHub stuff.
So definitely, if you
have any ideas and you
want us to stream any other
type of content, let us know.
Write us here or
comment on our Facebook.
And just let us know sort
of what would be useful.
Anyways, the logic is now in
place for drawing an apple.
So if we run this, notice that it's
not going to draw any apples at all.
Because we don't have any
grid indices in our table
that are set to 1-- or, sorry,
it's 3 I believe is what I made it.
Apple to 3, right?
So in order to do this, we
can initialize our grid.
Let's go down to the
initialized grid function.
As part of our grid
initialization, let's
just say I want to choose a
random x, y and make that an apple
so this is actually pretty easy to do.
I'm just going to say local appleX,
appleY = math.random MAX_TILES_X
and then math.random MAX_TILES_Y.
And notice that I'm doing
dual assignment here.
I have appleX, appleY with a comma.
I'm assigning it to
math.random MAX_TILES_X
and math.random MAX_TILES_Y.
This basically lets me create two
variables and initialize two variables.
And the two will match up, assuming that
there's the same number of arguments
on each side or variables on each side.
And there are certain situations
where this won't exactly
work if you have variables that equate
to multiple return types, return
variables.
But we won't get into that.
But basically what this is doing is
it's saying get an x and a y-coordinate
for the apple that we want to place.
And then give us a random value between
1 and some value, so MAX_TILES_X.
So give us some value
between 1 and MAX_TILES_X,
which will be anywhere on the
horizontal plane within our game.
And then same thing for the y-axis--
math.random MAX_TILES_Y.
And if I go into tileGrid
y, x, I can assign it to--
sorry, appleY, appleX, I can
assign it to TILE_APPLE, right?
So I've initialized these two, this
X and the Y. And I've added them.
Remember Y comes before X.
And then I run it.
And then, boom, I get
a random apple there
or some representation of what an
apple is in our scene somewhere.
And obviously, if I try to go over
to it with my current implementation,
nothing's going to happen.
I should just overlap
it, but that is how
you would randomly assign
something in your game, very
simple random generation.
OK.
So we've done that.
Now, something to think about--
every time I run this, pay
attention to where the apple is.
Notice that it's going to be in
the exact same spot every time.
That's not very random, because the
random number generator in LOVE2D
gets initialized to some value that's
consistent every time we run the game.
So appleX and appleY are always
going to get the same value no matter
how many times you run this application,
even though it says math.random.
In order to fix this, we need to do
what's called seeding our random number
generator.
We give it a seed value.
And that seed value is going
to influence the algorithm that
takes place underneath the hood
when it comes to actually generating
our random value, our
random x and y value.
The random number generator
does a bunch of stuff
where it uses some math that gives us
the illusion of a random number It's
not really random in
the technical sense.
But it works well enough for us
to perceive it as being random.
Elias, "thanks for teaching us.
Keep up the amazing--" thank you so
much for your comment, Elias, really
appreciate it.
Thank you for tuning in today.
I'm going to go ahead and do what's
called seeding this random number
generator.
I'm going to do it in my load function,
because that's where it should ideally
take place before I initialize
the grid, obviously,
so that it doesn't use this default
seed and then seed the random number
generator, right?
So I can do math.randomseed.
That will allow us to seed
the random number generator
and give it some value.
And then we need to actually
give it a value that varies
every time we run this game, right?
Because if we seed the random
number generator with some 0,
1, 2, 3, value, that's effectively
the same thing as just letting it
give us a random seed that
its pre-chosen, right?
And so one of the things that we
can do to accomplish this is just
give this function, this
value, called os.time,
which is a function that returns
the number of milliseconds that
have elapsed since, I
believe, it's the creation
of the first Unix system, which
should be some along value that
looks that basically
looks like that which
will be different every single
time we run this application.
So the next time it might be
like that or, you know, whatever.
But it will always be different every
time when we run our application.
So now that I've called
math.randomseed os.time,
notice that now the apple's
in a different spot.
And if I run it again, the
apple's in another spot.
And if I run it again, the
apple's in another spot.
And so now we've effectively
accomplished actually
randomly assigning the
apple to some location.
It's not consistent across
every run of our game
which would not be interesting.
Although you would want this.
You would want this behavior if you were
trying to debug something that you see.
Let's say your debugging Minecraft.
And you have a particular world, and
you notice this weird graphical glitch.
You might not necessarily
run into it if you just
allow it to initialize to
some random value every time.
You want to consistently determine how
to generate that world the same way
every time.
And so you would keep
that same seed and then
rerun it over and over again to debug.
The JP Guy says, "hey, everyone,
has the stream been live for long?"
We've been live for
about an hour and a half.
So we've been coding Snake from scratch.
I'll put all of this in a GitHub repo,
so everybody can take a look at it.
It'll be github.com.
Let me see if I can log in here.
Whoops.
It's a little that small, or a
little bit large I should say.
I want to sign in.
It's probably going to make me
use two-factor authentication,
but that is OK.
Yup, authentication.
So give me just a second.
I'll put this on GitHub, so
everybody can download it.
Let me just go off of the
screen here for just a moment
while I get this all set up.
One, two, seven, eight
, six, da, da, da.
Verify.
OK.
I'm going to create a new repo.
What will we call this?
Twitch stream-- no,
we'll call it Snake 50,
screw it, Snake demo talk
during Twitch livestream.
Make this public, create repo.
So I don't know if I have Git
actually installed on this account.
But if you go to this repo,
github.com/coltonoscopy--
here, let me bring up my thing here.
github.com/coltonoscopy/snake50,
you will see that live.
And I'll post all of the code
to the repo in real time.
I might have it available.
So I do have Git on here, but
I don't think it's functioning.
So I'm going to commit
and push everything
after we're done, because I think
I'll get an error if I try to do that.
I'll try.
So let's see.
I'm in-- OK, blah, blah blah.
Git init, git add, git commit add,
first commit, git remote add origin.
I apologize if this is a little dead
at the moment. git push origin master,
will it work?
Crap.
So this is where I run into issues.
Yeah.
Because I have two-factor
authentication enabled to my account,
there's a few weird things I
have to do to get this working.
And I'd rather not take too much time.
But I'll do this on my other account
that I have that I'll set up on,
and we'll be good to go.
But for now, I'll just run through
the code, I guess, a little bit.
I think you can also skim
through the VOD if you want
and sort look at what's happened.
But-- bunch of constants
for our tile types,
our resolution, how many
tiles on the x and y-axis
we're going to render
in a grid, variables
to keep track of whether our snake is
moving in a particular direction, the x
and the y, the grid itself, a function
to initialize everything in LOVE2D.
So if you're not
familiar, LOVE2D is what's
going to be the framework which we're
using to do all this game programming.
And then keypressed,
which will press a key,
move the snake left, right, up, or down.
Update that snake, edit its x and y
based on this thing called delta time,
which is a floating point
value which will give you
the amount of time that's elapsed
since this frame and the last frame.
Functions to draw a
grid, which is just a
nested for loop to iterate
over our grid and its numbers
and then render different
squares depending
on what we've set in that grid.
The snake, which will be a rectangle
which has our snakeX and snakeY.
And then the function that
initializes our grid set it to empty.
Sets it to a bunch of 0s.
"I'm familiar with using C#, but I
recently took an interest in this CS50
into course."
Oh, awesome.
Glad to have you, JP Guy.
If you want, I teach this
framework on an edX course.
So it is cs50.edx.org/games.
And we go through all this stuff and
make a bunch of different games based
on sort of like famous retro games,
like Super Mario, Pokémon, Zelda,
all those kinds of games.
And we use LOVE2D and Lua for most
of it and some UNITY towards the end,
which you're probably familiar
with if you've programmed in C#.
Jeffrey Hughes, yes.
JP Guy, thanks for sharing the link
to the GitHub repo in the chat there,
appreciate it.
OK.
So let's go back to the game.
So the next thing we need to do-- so
we got our random apple working fine.
So now we have our grid
that's being rendered.
We have a snake which
is moving continuously,
which we're going to change.
It's going to be moving
discretely before long.
What's the time?
4:38?
Good.
OK.
We still have an hour and a half about.
And then we have our
apple, which it's basically
iterating over this
massive grid here and then
just checking each individual spot.
What's this equal to?
Is this a 0?
Then render a white empty
rectangle, 0, 0, 0, 0, 0.
And then eventually,
it gets down to here.
And this is actually
equal to a 3 I believe.
Yeah, 3.
And instead of drawing an
empty white rectangle there,
it draws a filled red rectangle.
So that's effectively what we're doing.
You can kind of visualize this
grid as being a grid of numbers
that we've just converted to
different types of squares.
And that's the overall
basis behind Snake.
Jeffrey Hughes, ""[INAUDIBLE]
I love the way you teach.
You sound just like Bob Ross."
Awesome, thank you.
Well, hopefully, I'll maybe be good
falling asleep voice for people then.
OK.
Let's go ahead and
figure out now how I want
to take this snake that
we have moving around
and then, instead of making it
continuous, I want to make it discrete.
I want it to move sort of
grid unit by grid unit,
not just like continuously, so
that it won't get trapped in
between different grid lines, right?
It'll always be aligned perfectly.
It'll be what's called axis
aligned, I guess, in this case.
Well, this isn't strictly axis
aligned, but discreetly aligned
based on some tile increment,
which in this case is 32 pixels.
So that means that we're kind of
going to have to get rid of our snake
in the sense that it's no longer going
to have just an independent x and a y.
It's going to have a
grid x and a grid y.
I guess we're not
really getting rid of it
as much as we're going to have to do
some pretty significant changes to it.
First and foremost, it
will no longer do what
it's been doing in the update
function, whereby the x and the y
get updated with the
snake speed variable.
That's no longer going
to be the case, right?
The logic is going to
kind of be the same.
But instead of adding
delta time to the variable
and then adding or subtracting
that from the x and the y,
we're going to effectively need to
have a timer, which that timer can then
see has a certain amount of time
elapsed in between each individual tile
movement.
Jeffrey Hughes, "not in that
way, but mostly you help people
love what you do."
Thanks, Jeffrey, really appreciate it.
Thank you, man.
So instead of having this
continuous value, which
is going to be moving very
cleanly throughout the space,
we are going to move it in chunks.
So we're going to need to
align it with our grid.
And to do that, we're going to have
some sort of timer, some sort of amount
of time in which we determine exists
between each separate discrete movement
on the axis.
So let's spitball and say,
maybe I want the snake
to move one increment every half a
second for now, 500 milliseconds.
So we're going to need
a couple of things.
So I'm going to define a constant.
And by the way, these
are being capitalized,
because these are variables
that I'm basically saying
they should never be altered at all.
In an actual game that you
ship, these will probably
get altered, honestly, 1,280 by 720
being your window width and window
height.
Because people change their resolutions.
But most of the other ones
are not going to be changed.
This MAX_TILES_X and
MAX_TILES_Y would also probably
be changed if these got changed.
But we're going to assume that for
the sake of this game, this demo,
these are all going to be constant.
So anytime you see something
capitalized and underscored like this,
it's a constant variable that will never
get changed or should never get change.
There are some languages
and environments
in which you can declare that something
is a constant and can't be altered.
But in this case, Lua is a
dynamic programming language.
It's interpreted.
And at runtime, it does not
enforce whether some variable
is to never be altered or not.
it.
Will just accept whatever
variables you put in your namespace
and let you do whatever
you want with them.
And so I'm going to
set a variable that I
want to assume I'm not going to edit
for now, although this can be adjusted
later for gameplay purposes.
I'm just going to use
SNAKE_SPEED actually.
And I'm going to set that to 0.5.
Now, 0.5, I'm setting it
to 0.5 instead of 100,
because, previously, we used
100 to be the number of pixels--
excuse me-- per second
that we want it to move.
But, now, I'm going to basically say
the snake speed or, I guess, maybe--
yeah, snake speed is going to be 0.5.
So time in seconds that the
snake moves one tile, right?
So snake speed-- 0.5.
And so, now, I can
have sort of a counter,
like a timer variable that basically
takes delta time, which we're
using in update and previously
we were multiplying by our speed
variable, our speed constant.
I can take delta time and just
add it to counter every frame,
to this counter timer
variable, every frame.
And then once it's added up to 0.5--
because, remember, delta
time is going to be
given to you in seconds
as a floating point value,
so usually point 0.1667
whatever every frame.
Once that's added up to 0.5, that
means that half a second has elapsed.
And therefore, we can move the snake
one tile in whatever direction, right?
So to go along with that,
we're going to need some value.
I'm actually going to declare
it underneath our other snake
local variables, or our
variables that actually change.
And I'm going to say snakeTimer,
I going to set that to 0.
OK.
So, uh-oh, we have a visitor.
Everybody say hi to Dan
Coffey in the chat, everybody.
This is actually Dan Coffey.
He's a man of few words.
So we have a timer that we've declared.
So we need to actually sum dt to that.
So all we have to do
in this case is say--
whoops, sorry, got a little lost there--
snakeTimer = snakeTimer plus delta
time.
Remember, Lua does not
have the plus equals.
Otherwise, you'd probably use that.
So snakeTimer = snakeTimer
plus delta time.
And so now all we need to do-- it
looks like you got a little bit of love
there in the chat, Dan.
JP Guy's showing you some love.
We're going to say if snakeTimer
is, remember, greater than--
and then up here we
declared snake speed, right?
We'll say greater than
or equal to SNAKE_SPEED,
then now this is where we're
going to move our snake.
Except we're not going to move
our snake along pixel wise.
We want to move it in increments of 32,
so that it stays adhered to our grid.
Looking good, Dan.
Got a lot of fans for Dan.
snakeTimer is greater than
or equal to SNAKE_SPEED.
We're going to repurpose
the snakeX and snakeY
that we had before, except now these
are going to be indices into the grid,
right?
So I'm going to say 1, 1.
Because, remember, everything
is 1 indexed in Lua.
And these are going to map to
our grid positions later on.
We're going to actually keep a record of
where our snake is in the grid as well.
If it's greater than or equal
to SNAKE_SPEED, then snakeX--
oh, and then here we also have to check
to see what direction we're moving in.
So if snakeMoving is equal to up, then
else if snakeMoving is equal to down,
then else if snakeMoving if
equal to left, then else--
we're not going to check
for the last condition,
because it will always be one of
these four, up, down, left, or right.
So it'll be right otherwise.
snakeY equals snakeY minus 1.
snakeY equals snakeY plus 1.
snakeX equals snakeX minus 1.
And then lastly, snakeX
equals snakeX plus 1.
OK.
So now we have our snake
moving with some timer.
And it's going to move every time this
snake timer increases past the snake
speed constant that we declared before.
"There can only be one Bob Ross,
but you are pretty awesome.
Thanks so much."
Thanks, [INAUDIBLE].
I appreciate it.
I'll take being second to Bob Ross.
That's OK.
OK.
Well, there's a couple of things here.
So I'm going to run it.
It should run.
It should not run, main 109 saying--
oh, I think I missed a bit of syntax.
Let's see.
Else-- oh, right.
I forgot the end here.
So every if needs to have an end.
If you see some EOF issue, then
you do need an end statement there.
Irene, thank you.
Irene, appreciate it.
OK.
So now it's fine.
So now notice, though,
we're not moving discretely,
because we're still keeping track of
snakeX, snakeY, still rendering them.
But we did get a bit of a delay, a 0.5
second delay before we started moving.
So that part is working.
But we don't want to move
in increments of one.
We want to move in increments of 32.
And so this is where we need
to go in our update function--
sorry, rather, our drawSnake function.
And instead of drawing snake
here, the snakeX, we're
going to treat x and y as grid indices.
So I'm going to say just
like we did before--
because, remember, coordinate
systems are 0 indexed,
but tables are generally 1 indexed.
So snake minus 1 time TILE_SIZE. so
now we're multiplying it by TILE_SIZE.
So this 1, 2, 3 pixels that
we're moving before continuously
are now going to map to perfectly
slotted places in the grid.
We'll do the same thing here.
snakeY minus 1 times TILE_SIZE.
I'm going to bring this down
here just for readability.
We're going to run this.
And now notice, though,
we're moving so fast
that we can't even see the green
square after a certain amount of time.
It just moves astronomically fast.
But we're supposed to be moving,
you know, every 0.5 seconds.
So what's the issue here?
Does anybody know what the
main problem is with my logic?
It should be working, right?
Remember the end at the end
of the if, else if, yeah.
"What extensions do you use
to code in Lua in VS Code?"
I use the Pixelbyte LOVE2D
extension which allows
me to hit Command L on my MacBook.
And it will just run.
Rather than having to go back
to my terminal, which you
see me been kind of doing it hidden.
But if I were to instead do this,
it does the same thing, right?
But I don't have to
actually go to my terminal.
If I had Command L with the extension
I have, it gives me that for free.
So that's the main extension
that I use for Lua.
And I think there's other ones.
But, for example, if
I were doing C# stuff,
OmniSharp is a really good extension
which allows you to do sort of a live
compilation and debugging,
which is pretty cool.
But, yeah, there's clearly an issue here
with my movement of the snake, right?
And that is simply the fact that when
my timer gets to be 0.5 and higher,
it never resets to 0.
So in this case, I just want to make
sure that whenever you get to 0.5,
I go back down to 0.
I reinitialize my timer.
So I'm going to set my
snakeTimer to 0 in here.
So if we get to the point where
I've gone up to 0.5 or 0.51
or whatever it equates to
on that particular frame,
I'm going to be back
to 0 on the next frame.
So if I rerun this, notice
that now we are indeed
moving along the x direction.
And if I hi Down, which I just did,
notice that now I'm moving down.
And I hit Right, I go
right, Left, I go left.
And then Up, I go up.
So now it's kind of getting there.
We're on the right track.
It's a bit slow, though,
to be quite honest.
So I'm going to bump it down.
Let's say 0.1, see if
that's good enough.
OK.
Not bad, right?
So now this is more or less
a workable solution, right?
And so if I walk over the
apple, nothing happens.
We don't have any logic just yet
to keep track of whether we've
consumed an apple or not.
That's going to be kind of
one of the next steps, right?
The next thing that I probably
want to do is grab the apple.
And if I eat the apple, I want to
respawn a new apple somewhere, right?
And then the next part
after that is going
to be I need a way to map
my snake onto the grid,
so that I can check in the
grid at any given index
whether that's owned
by a snake tile or not.
So that I can, therefore, do
collision between my snake head
and any part of my snake
body if that makes sense.
So anybody, let me know if you
have any questions thus far
about how this works.
Otherwise, we'll do a couple
of things to get started
on actually getting the apples to work.
I thought I had a water
in here somewhere.
Did I?
Did I have a water in here, by chance?
That's OK All right.
Actually, I'm getting
a little bit parched.
I think I'm going to grab--
SPEAKER 1: [INAUDIBLE]
COLTON OGDEN: It's over--
where is it?
SPEAKER 1: I'll get it for you.
[INAUDIBLE]
COLTON OGDEN: You sure?
Oh.
OK, thanks.
Appreciate it.
OK.
Oh, thanks.
Sweet.
All right, it's thirsty work.
Oh, sorry.
Irene says, "does the square continue
moving when you're not pressing?
I can't tell from here."
Correct, yeah.
The square will always
move, because in our logic
we've just basically said when the
key is pressed just change the state.
But it's not actually checking an
update whether the key is being pressed.
It's only checking that state variable.
So if it's up, down, left,
or right and the timer
has gone past a certain amount,
it'll move it on its own basically.
OK.
So I'm kind of getting a little bit
tired of looking at the grid I think.
So what I'm going to do is I'm
actually going to nix drawing the grid.
Actually, well, no, that's not true.
Because what I need to do
is I need to draw the grid.
But also, if the grid contains the snake
and/or the apple, which eventually it
will, I still will need to draw it.
So what I'm going to do is I'm just
going to get rid of this bit of code
here.
I'm going to comment it out.
I can do command/ on my computer.
And so now it's a comment, and
so it won't actually execute.
So, now, I don't have a grid, but I do
have a black background and the snake
sort of going around.
And I can go on the apples
or whatnot, but it's not
interacting with the apple.
So let's go ahead and figure out
how we can get the apple rendering--
or, sorry, get the collision
between the snake and apple working,
and therefore get new snake components
and then maybe even have a score.
So the score will be
easier to start off with.
Adding a new element
to the end of the snake
is going to be a little bit trickier.
Oh, and there's actually one more thing
that we should take into consideration.
And that is the fact that
as it stands, if I go up,
I don't actually loop
back from the bottom.
I have to hit down again, and then
eventually my snake will come.
So what I'm going to do
is a simple set of if
statements where I have my snake
being edited, which should be here.
So in my update function, should I
put this all into its own function?
Most of it's going to be update
code for the snake anyway.
As your functions get
longer, it's generally
better practice to sort of break
them out into different pieces.
But I'll keep most of it in here
unless it gets really unwieldy.
So what I want to do is
notice that we're just
kind of moving the snake minus 1
and snakeY plus 1, snakeX minus 1,
snakeX plus 1.
We're kind of just moving them
without really checking for bounds,
for checking boundaries on whether we're
at the edge of the screen on the up,
down, left, or right axes.
So what we should do is
do some simple checking.
If I want to move up, for
example, and I'm at index one,
well, I should actually move
my snake to the bottom index.
And if I'm, for example,
moving to the right
and I happen to hit the
last index in the grid,
I should probably move my snake
back to the index one on the x-axis.
So it looks like I went
from right to left.
So I looped around, came full circle.
So let's do that logic here.
So I'm debating whether we should
make the snake part of the grid first
or whether we should just do it.
I'm thinking.
OK.
So we can get rid of
draw snake altogether.
So let's do that.
So let's make the snake
part of the grid first,
because this will actually
be fairly easy to do.
If I go to else if in my render
function, else if tileGrid y,
x is equal to TILE_SNAKE_HEAD.
Then I'm going to just copy
and paste the apple code.
I'm going to set that to 0, R, G.
So I'm going to set it to that.
But I'm going to make it kind
of cyan colored a little bit
and just have that be my code here.
So I'm actually not going to draw the
snake anymore as a separate function.
We're just going to make
the grid draw the snake.
The snake is actually going
to be part of our grid
just like the apples are just to
keep everything kind of simple.
And then that way we can check to see.
We can look at any particular
index in the grid at y, x
and know, oh, that's actually
a snake body part, for example.
And then, yes.
Jeffrey Hughes, "you
can create a function
to remove the redundancy of that code."
Yes.
Yeah, you probably could.
In this particular instance,
these are kind of the same.
It differs a little bit if
you have this code here,
which is drawing it as a line.
But you could define a
function called draw tile,
for example, that takes in the
draw mode of this, the x and the y,
and then the color and then
have it do that as well.
Just for the sake of time, we won't
modularize things to a super extent.
Maybe if we have some extra time
we'll do that as a creative exercise.
But, yeah, good point.
You very much could and should do
that in a proper, like, full project.
Just for illustration, though, we're
just going to use a little simple copy
and paste just in sake of time.
Because we have about an hour left, and
it'd be nice to get as close to the end
as we can.
OK.
So we have TILE_SNAKE_HEAD being here.
And so now what we
want to do is set the--
we need variables, essentially, to keep
track of where the tile snake head is.
And we can use snakeX
and snakeY to do that.
So do we want to do it that way?
Because the snake is going
to actually have to move.
So each individual-- oh, wait.
No, that's not true.
That is not necessarily
true, because, yeah, we
will need to keep track of
each individual snake piece.
So let's go ahead.
Like I said, I haven't
spent a terrible amount
of time working through this, because
it'd be fun to I think kind of figure
it out on the fly.
We're going to make
a new variable called
snakeTiles, which is going to kind of
go hand-in-hand with our tile grid,
I guess.
And then we're going to
initialize the first piece here
to be snakeX and snakeY.
And actually this should go below
this piece here, snake data structure.
And then every time we
want to add a new piece,
we're effectively going to take
whatever the last piece is,
and then we're just going to add--
well, what we're effectively going
to do is move this down to here.
And then we're going to
take the next position,
depending on whether we're
moving left, right, up, or down,
and we're going to fill it in here.
But since we only have
one index at the moment,
we only are just going to put in the
snakeX and the snakeY right there.
So that works just fine.
We might be able to even just keep
track of this snake data structure
independent of the grid,
but it kind of makes
it easier for checking
the grid for different y,
x if we also just kind
of add them to the grid.
So we'll try it that way.
OK.
So we have our snake data structure.
So in this case, we only really need
to keep track of the head of the snake
in terms of moving that.
Because we're going to move that.
And we're going to take the tail, and
we're going to basically take the tail
and put that where the
neck of the snake is.
Basically, where the head just
moved, we're going to take the tail
and move it there.
And that'll have the
effect of sort of moving
the snake all around the
map pretty seamlessly
without having to manually move every
single one of these snake pieces.
Because you can sort of visualize
if I can pull up Chrome and have
the image here, snake game images.
If the head is here--
which is kind of actually
hard to see in this particular instance.
If the head is here and
it moves one up this way,
really the entire snake is
going to stay the same overall.
Excuse me.
The vast majority of it's
going to stay the same.
The only thing that's going to change
is this tail right here, right?
So we can effectively take this
tail, remove it from the bottom,
and then plug it here.
And then that will have the effect
of our snake moving one forward
when all we've really done is just move
the head forward and taken the tail
and sort of made it
the neck of our snake.
So as a performance
optimization, now we don't have
to manually move every single tile.
If our snake is moving, we don't
have to move every single one of them
every frame.
We can just move one piece at a time.
So this is a bit of an
optimization and just
like an easier way to
program the snake as well.
So that's the algorithm that
we'll take to actually solve this.
So this will be my head,
right, the head of the snake.
And every time it moves,
whatever is in our tail--
this is, first of all,
the head is basically
going to shift to be whatever the next
title is in the direction we're moving.
And then this is going to come down
here and fill in that spot basically,
should work.
Well, we'll see how it looks
when we actually implement it.
OK.
Just make sure everything
is working still.
It is not.
Oh, right.
OK, right.
Because we're no longer drawing things.
OK.
So we're no longer drawing
the snake, but let's
go ahead and change that by
making it part of the grid.
We should make part of
the grid here first.
So I'm going to say actually
after the grid is initialized,
grid at snakeTiles 1, 0
and then snakeTiles 1, 1.
Whoop, sorry.
I'm 0 indexing when it's not 0 index.
There we go.
So the grid at snakeTiles 1, 1--
so the first inner table of
our snake data structure.
And then the first subelement
of that table, so snakeX--
or, actually, this should be reverse.
Because, remember, it's y, x, not x, y.
Oh, actually.
Interesting.
So snake [INAUDIBLE] 1, 2.
No, sorry.
I have that backwards.
Sorry, having a mild brain fart here.
Let's figure this out.
So as an aside, "what retro game
do you enjoy remaking the most?
What about the least?"
Game that I enjoy remaking the most?
I really like RPGs, so I like a
Final Fantasy kind of game I think
would be a lot of fun.
I did something similar to that.
Well, we started with Pokémon in the
edx.org course, the games course.
The least?
I'm not sure.
I like a lot of different games,
a lot different game types.
I'm not a huge fan of physics-based
games, I guess, like Angry Birds.
We had to make Angry Birds
for the course as well.
And I didn't find it
super interesting I guess.
A lot of that is taken care of for
you with the physics framework.
And I think it's kind of cool to make
the different composite physics objects
and whatnot.
But the actual-- yeah, I don't know.
It wasn't as interesting as something
a little bit more from scratch I think.
But what about you, JP?
What would you like to make,
I guess, in terms of a game?
In the meantime, let me figure
out exactly what's going on here.
So grid at-- so streamer game fart.
Apologize.
I'll figure it out.
OK.
Oh, right.
OK.
No, I had it. snakeTiles
1, 2 = TILE_SNAKE_HEAD.
That's what we needed to do.
So basically, take the x and the y
from these snakeTiles first inner table
and then map that to
our grid by putting--
basically, this is the first
table within our table and then
the second subelement, so the y
here, and then the same thing here,
but the first subelement, so
then this snakeX variable.
And then I'm going to assign
that to TILE_SNAKE_HEAD,
which we've shown down here, right?
And so if I run this, it should
start off with a-- whoops.
Grid 38-- oh, yeah.
Not grid, it should be,
what is it? tileGrid.
So let's call this tileGrid.
Run it.
OK.
So it did work.
So it started off with a
green tile at the very top.
But it's not moving
anymore, because we're
no longer editing that grid be the
tile snake head or tile snake body,
however we want to do it, right?
So we're going to need to alter that.
So let's go ahead.
And in our update function, that's
where we're going to need to do it.
snakeY = snakeY minus 1.
So we do still to keep
track of our snakeY.
And so what we're going to
do is that's still fine.
We need to do tileGrid snakeY snakeX is
equal to TILE_SNAKE_HEAD here, right?
So this is still moving
and still updating
that x and y value that we
had before, which should still
be being edited underneath the hood.
But now notice that we do
get our snake rendering.
However, its rendering in such a way
that it doesn't actually discretely
change position.
It does, but it keeps the old previous
values being written in our grid
for the title snake head,
which is not what we want.
We want to basically
set those values to 0--
not to 0, but to whatever the last
element is in our grid, right?
Yeah.
Because the snake head's
going to move forward.
It's going to create a gap.
And if it's just by itself,
it's not going to create a gap.
It's just going to be moving by itself.
But assuming that we have a full
snake longer than one segment,
it's going to create a
gap where the neck is.
And so to fill that gap,
recall we can take the tail
and we can put it where that gap is.
And that'll have the
effect of moving the snake.
So let's go ahead and tileGrid if.
Let's do an if statement here, because
if we try to index into the table when
it's only a size one, if
we're trying to get the tail,
it's not really going to work.
I guess it would work.
It would just use the head, which
we've already moved forward.
So that would have the result
of moving it backwards,
which wouldn't work at all, right?
Because we want to use a tail or
nothing at all if it's one segment long.
And we can use this thing
called this pound symbol, which
is a length operator for tables.
And we can say if length of
tileGrid is greater than 1, then--
whoops.
Actually, what we should do--
we're going to need to cache our
old values so that we can take the--
so we have to remember
their old value of our neck,
so that we can put our new
variable into that neck, right?
So let's go ahead and say--
hold on, let me read
the stream real quick.
I haven't been keeping up.
Dungeon Siege II.
Yup.
Dungeon Siege II.
I've played Dungeon Siege I. I
haven't played Dungeon Siege II.
But I liked it a lot.
It was a lot of fun.
Good question.
I think I'm on the opposite side.
If I could program 2D games, I'd
probably make something physicsy,
like some 2D Kerbal Space program
or Asteroids or something.
Yeah, that'd be a cool idea.
I think physics games are really cool.
And I don't dislike programming them.
I think it was just in comparison to,
say, Zelda or Mario or Final Fantasy.
Those are the kind of games
that I enjoyed growing up.
So I might have a soft spot for them.
But, yeah I know a 2D physics game--
Kerbal Space Program in 2D
would be pretty cool, actually.
That'd be really cool to see.
Shayna, "but each time
the snake eats the apple,
will the snake become
longer by adding new tiles,
or the head tile will
go back to the tail?"
So we will add a new tile.
Basically, we're going to need to--
oh, you know, actually what we can do,
we can pop off the tail, right?
Because we can just get rid of the tail.
And we'll just add a new grid element
where the head was, so super easy.
Yeah.
That's a much easier way to do it.
"Why not just cut the
tail piece and paste it
in front of the snake's current
head in the appropriate direction
as it propagates?"
JP Guy, good idea.
That would work, too, actually.
That's actually probably better.
We just need to then flip
the snakeX and snakeY
variables that are currently pointing
at the head to point to the new head,
right?
But that should be easy enough, right?
I actually think that already happens.
"Sorry, I just found this channel.
Why are you programming this game
in Lua instead of, let's say, C#?
Just curious, Salmedo."
Thanks for joining the stream, Salmedo.
Short answer, because it's easy
to get into compared to C#.
Because C#, you're presumably using
it in the context of either X and A,
a 2D framework, or using in the context
of UNITY most likely of those two
options.
And UNITY is something that I would
like to start teaching on stream,
but there's a bit more
overhead with that.
And I actually myself should
spend a little bit more time
sort of getting all the nuts and
bolts set right with that in my mind.
And X and A is honestly a framework
that I'm just not super familiar with,
although I think is somewhat comparable
to what we're doing here with LOVE2D.
But good question.
I would anticipate seeing
UNITY streams in the future,
because it's a game engine
that I would really like
to teach and get really familiar with.
But this is a framework that I taught
in the game course that took place this
last year, cs50.edx.org/games.
All right, so, yeah.
So whose suggestion was it?
"Why not just cut the
tail piece and paste it
in front of the snake's current
head in the appropriate direction
as it propagates?"
That's an excellent question.
And I think we are going
to do that probably.
"OK.
Thank you very much, Salmedo?"
No problem, Salmedo.
Thanks for the question.
Thanks for tuning in.
OK.
So pop tail, put it 2x
tail behind the head.
Pop the tail.
Put it two times tail behind the head.
Put it two times behind the--
I'm not sure I follow, Bhavik
Knight what you're saying there.
Would you mind elaborating a little bit?
OK.
Hopefully, we have
enough time to do this.
Hopefully, I'm not going to blunder
my way too far past 6:00 PM here.
But let's say, OK.
Let me just get reacquainted
here with the code.
So if we rerun it, we have
just the snake moving.
OK.
So the thing we needed to do was
clear where the head used to be.
And yes, JP Guy.
"You mean when the snake eats an apple?"
Yeah, presumably.
I think that's what he's talking about.
OK.
So basically, let's
erase, first of all, where
the head was before which we can do.
So we will need to store all
of these snake pieces together.
Because we're going to keep track
of all of them as we pop the tail.
That's going to be important.
So we can't just store the information
in the grid blindly and use that.
We actually have to keep
a record of everything.
So we're going to keep the
same model that we're doing.
And then if the tile grid, number of
the length of the tile grid is 1-- so
this is where we're talking
about if we're just ahead or not.
If we're just ahead, then we
just erase what was behind us.
And if we were not, if we were more
than 1, then we need to pop the tail.
And if we take JP's suggestion, we
can put that in front of the neck.
And then that will be
our new head, basically.
But we need to keep track of where the
head was before we actually move it.
So I'm going to add a couple
new local variables here.
I'm going to call it
priorHeadX and priorHeadY.
And I'm going to set that
to snakeX and snakeY.
So that is going to let me keep a
record of where I had the head before,
so that I can do something like this.
If the number of the tile
grid is greater than 1,
right, I'm going to-- well,
I'm going to leave that.
That's going to be a to-do.
Else tileGrid at
priorSnakeY and priorSnakeX
is going to be equal
to TILE_EMPTY, right?
And so this should work off the bat in
terms of giving-- oh, no, it doesn't.
OK.
OK.
priorSnakeY, priorSnakeX
is equal to TILE_EMPTY.
If the number of the tile
grid is greater than 1,
which will be in the case that we
have a head, and then we have--
oh, this is the tile grid.
Sorry.
We need to look at the
snake grid, the snake tiles.
Sorry, that's my bad, snake tiles.
So the length of the snake
tile is not the tile grid.
The tile grid will always be the x
times y number of tiles on the screen.
Oh, index a nil value, main 78.
tileGrid priorSnakeY priorSnakeX
is equal to TILE_EMPTY.
If snake is moving, so
snake will be moving right.
OK.
Now, this should work.
That's interesting.
OK.
Let's figure this out.
This is some live debugging.
This is why we're here.
So priorSnakeY at priorSnakeX
is equal to TILE_EMPTY.
And it started to render,
but it stopped rendering
as soon as we tried to move
to the right it looked like.
So if that's the case,
then snakeX priorHead-- oh,
because I called it priorHead, my bad--
priorHead, not priorSnake.
OK.
That should work.
There we go.
So, now, notice that we have
our snake moving around.
And it's part of our grid.
Now, what happens if I try
to move it beyond the bounds?
It doesn't work.
And the reason that it
doesn't work is because, if it
gets to be 0 or some
negative value on either axis
or a value that exceeds
the bounds of the grid,
remember we haven't programmed
to bounds check for these things.
It's going to try to access index 0, or
index negative 1, or index, like, 60.
And that's not within the range of the
game, exactly, out of bounds error.
So what we need to do is
start checking for bounds.
If we're trying to move up, down,
left, or right and we're at an edge,
then we should accommodate that.
So we're going to go to
snakeY is snake minus 1.
So this is all where we actually
update the snake in its position.
So if it's up, snakeY
is snake y minus 1.
Now, let's do an error check with that.
We're moving up, right?
We need to check to
see if we get to be 0.
If we're at 0 or less
than 0, then we need
to loop back down to
the bottom of the map.
So if snakeY is less than
or equal to 0, then snakeY
is going to be equal to
MAX_TILES_Y else snakeY
is going to be equal to snakeY minus 1.
And so this will at least check for
an out of bounds error going up.
Now, if we want to check for
down, left, and right, we
have to do essentially the same thing
just with a little bit different logic.
So if snakeY is greater
than the MAX_TILES_Y, then--
whoops-- else.
So this is if we're going down, right?
So if we go down to the bounds, if we
go down and our index is at MAX_TILES_Y
and we try to go one past that, we're
just going to set our snakeY to 1.
Because, remember, everything is
1 index, not 0 indexed in Lua.
And then this is the same logic
as before when we're moving up,
but it's on the x-axis.
So if snakeX is--
oh, sorry.
Actually for less than
or equal to 0, we need
to check to see if it's less than equal
to 1, not less than or equal to 0.
Because we don't want to
set it to 0 to begin with.
We want to make sure when it's at
1 that we loop back around, not 0.
Sorry.
And then if it's greater than or equal
to MAX_TILES_Y, then set it to 1.
If snakeX is less than
or equal to 1, then I'm
going to set snakeX to
be MAX_TILES_X else.
Whoops.
And then I'm going to just copy
that, bring that over there.
And then we're going to
do the same thing here.
If snake is greater than or
equal to MAX_TILES_X else.
And then snakeX equals 1, rather.
Now, if I've done this correctly,
my logic isn't incorrect,
I should be able to go
out of bounds and loop
back to the bottom,
which it looks like I do.
I go down as well.
Awesome.
So, now, we have that
basic functionality.
So whenever we reach the left,
right, bottom, or top edge,
we're appropriately moving to
the other side of the screen--
so a fairly simple problem to solve.
That's a big thing that we needed to
correct, but now it's been corrected.
"Snake tiles in the tile grid."
Yeah.
Thanks, Andre.
Yeah.
Those kinds of things
are so easy to miss
when you're actually programming it.
And then I know how
frustrating it is to see
someone else miss something so obvious.
So apologies for that.
JP Guy, sweet.
Thanks for the support.
OK.
So we have now our bounds checking and,
also, setting our head on the snake
or whatever it previously was
to TILE_EMPTY, which is great.
Now, we need to go through and write
the logic for actually eating the apple.
So once we eat the apple, let's also
have some score variable, right?
And let's monitor that.
So we can check how
many apples we've eaten.
I'm going to create a new variable
here called score set to 0.
And then down here in the draw function,
I'm going to call love.graphics.draw--
or, sorry, just print.
I think that should be
fine, score: tostringscore.
I'm going to draw this at 0, 0.
Yeah.
I'll draw it at 0, 0 for now.
That's easy enough.
Rather, I'll draw it at 10, 10.
And that should be it actually.
So let's draw that.
Score is 0.
So you see it in red there,
because it's actually technically
after I've called love.graphics.setColor
to red for the apple.
But that's at the end
of drawGrid, right?
That's the last thing
it drew in this case.
So I need to actually reset the color,
so love.graphics.setColor 1, 1, 1, 1.
So for white there, we got score 0.
So we know that we haven't
eaten any apples yet.
It's a little bit small.
And actually, offhand, I know
you used to create a new font.
But if we don't have a Font
file, love.graphics.new font.
Let me just make sure that we can create
a new font without a font file type.
Can we do that?
OK.
Cool.
Yeah.
We can create a new font with a default
size using-- it's called I guess--
Vera Sans is the default font in LOVE2D.
So let's go ahead and do that.
So the font was way too small.
In order to draw something at a
larger font size in LOVE2D what
we need to do is up here,
I'm just going to-- actually,
I'm going to make it
a global font up here.
So I'm going to say--
let's do it up here--
local largeFont =
love.graphics.newFont size 32.
I believe the default size is 16.
And then in here, we can say
love.graphics.setFont largeFont,
because you have to
actually set the font.
We've created a font object
up here, this large font.
But we have to actually set it in
the actual UNITY, the state machine
aspect of UNITY.
Sorry, I keep saying UNITY.
I just had a UNITY workshop
yesterday, so part of it
is still kind of in my mind.
But in LOVE2D, we need to specify the
creation of the font object and then
the actual using it, setting the
font for the LOVE2D state machine.
So this is what we're doing here.
And if I'm correct in my programming,
we should now see score is 0.
It looks actually pretty
nice and clean I will say.
So cool.
So now we have a score meter
always visible on our screen,
perfectly functional inasmuch as it
renders text, not functional in as much
as we're actually
keeping track of score.
"If there's no Comic
Sans, I'm unsubbing."
JP, I'll get you some Comic
Sans in there next time.
If we've got to make this stream legit,
we're going to get some Comic Sans.
We'll get a nice a nice Comic Sans "This
is CS50" as well just for fun maybe.
But, anyway, I'm going
to think about now
how we want to start detecting whether
we've reached an apple with the snake.
And if we have, we should
increment our score, right?
I'm going to be kind of bummed if
we don't get this full thing working
by today, by 6:00.
But I'll put it on GitHub if not.
And then we'll do a follow-up
stream maybe on Monday
just to finish it off and then
maybe talk about some other stuff,
and then maybe next Friday
start making another game that's
maybe a little more complicated.
But I'll do my best to
see how far we can get.
Games take a long time.
Anyways, OK, so we have a snake head.
We have variables snakeX
and snakeY that point
to our snake head that are keeping
track of where it is at all times.
So what I'm going to do
is, in our update function,
this is sort of where we need to check
to see whether we've reached an apple,
right?
So before we actually set
that place in our tile grid,
that's where we want to do the check.
We want to say, OK, so if tileGrid
snakeY, snakeX is equal to TILE_APPLE,
then--
and this will be the same either way.
We'll still need to
update the head location.
But check for apple and
add to score, right?
And this will eventually be where we
also add a node to the head, right?
"Love me some sans serif font."
Yeah.
Me, too, Shayna.
Excuse me.
OK.
So if in the tileGrid at snakeY, snakeX,
which is where the head currently is,
we're going to--
sorry, we're checking it.
If it's equal to TILE_APPLE,
score equals score plus 1.
And then we're going to
overwrite it down here.
So we don't actually
need to change the tile,
but we do need to initialize
another apple, right?
So what we need to do here is say--
and this is kind of where we
need to be a little bit careful.
We need to basically say if--
or we should rather say,
local newAppleX, newAppleY
equals math.random MAX_TILES_X and
math.random MAX_TILES_Y, right?
We're going to get new random
variables to be our x, y for our apple,
because we want to place
in a new random spot.
And then we're going to say tileGrid
newAppleY, newAppleX = TILE_APPLE.
And what this will do is it will blindly
look throughout our entire level.
Or it won't look through the level.
It will just blindly
choose two random variables
between the limits of our level
and set the apple to that value.
But there is going to
be an issue with that,
especially as our snake gets larger.
And that's the fact that it could just
overwrite one of the snake body parts,
right?
So we want to be careful with that.
We want to make sure that
we're actually checking
for whether a snake body part
exists in the grid at that location.
So we can do that.
We can absolutely do that.
For now, we're just going to blindly
do this and make sure that it
works, the score and everything else.
So I have a syntax error.
Oh, right.
I'm doing a novice mistake.
If tileGrid snakeY, snakeX
double equals TILE_APPLE.
OK.
So now we're moving around.
So if I eat the apple, if I can, boom.
Cool, we increased our score to 1.
And the apple moved to a new location.
Awesome.
Boom, did it again, did
it again, did it again.
So it works.
It works perfectly well.
And it's not an issue
that we're not checking
for whether it's in
the snake's location,
because, well, our snake's head is
only one segment long at the moment.
So it's very unlikely that it'll
actually appear in that location,
but it's possible.
So we're going to add some bounds,
or rather, some error checking
here soon in order to make that work.
But the next piece of
the puzzle is going
to be how do we start adding
segments to our snake,
because that's very important, right?
If we don't check for
segments, we'd never
run the risk of ever losing the game.
So that's something that we
need to take into consideration.
And another thing to
take into consideration
is the fact that we shouldn't be able
to move in the reverse direction which
we're already moving.
Because if we do that, then we'll
collide with ourself instantly, right?
Because we'll have had a
segment in the past direction
that we were moving from
the last frame, right?
So-- two things that we should
take into consideration.
Snake is a surprisingly complex
game when you get down to it.
I mean fairly simple
and straightforward,
but there's a lot of little things
you've got to pay attention to
in order to get it working just right.
All right, so let's think
about how we can start getting
new segments added onto the snake.
So the new segment, the actual
part where we add a new segment
is going to take place here between
lines 96 and 101 where I actually
have the snake data structure
look at its tail, right?
And then put the new piece
effectively onto the front of the--
"snake is going to be huge."
Yup, that's correct.
Well, if I'm good at Snake, it will be.
If I'm terrible at Snake, maybe not.
So in here, once we
have consumed the apple
and we've placed it
in a new location, we
want to actually add a new
component to the snake.
So what we can do is I'm just
going to kind of code through this.
And we can figure it out.
But I know that I'm probably going
to want to insert where our--
I mean, for the sake of ease, I guess
I can probably do an error check here
first, actually.
If the length of the snakeTiles
is greater than 1, then do this.
Otherwise, it's just going to
add to the snake tail, right?
So take tail and add to head
if greater than one segment.
Otherwise, we want to just add a
new head segment effectively, right?
Because if we only
have one segment, then
we have no tail besides the current
segment we have to add to the front,
right?
Otherwise, it's kind of like an
infinitely recursive operation
at that point.
So if the number of snake
tiles is 1, OK, else.
Do that, OK.
So if it's greater than 1, I'm not
going to worry about that case for now.
I'm going to worry
about adding a new head
segment if we are just at one segment.
So our snakeTiles, snakeX and
snakeY, is going to be here.
And we aren't actually editing the
snakeTiles table if I'm not mistaken.
All we're doing is we're
editing the tile grid.
And so what we need to do is actually
edit the tiles within the snake itself,
too, right?
Because that's how we
keep track of the tail
that's how we pop the
tail off the snake.
We take the tail.
There's going to be a
new tail after that.
And then we need to
put it to the front--
rather, is that true?
That's it we're moving.
Sorry, that's if we're moving.
Then we're going to do that.
We take the tail, put it to the front.
If we're eating an apple, then
we actually keep the tail,
we keep the head, and we just add
a new snake element to the front.
I believe that's correct.
Yeah, that would be correct.
I'm trying to remember
in my head actually how
it works when you eat a new segment.
Does it add it to the tail, or
does it add it to the front?
I think it adds it to the front.
Let's see.
So open a new incognito window.
Go to snake game play.
And then, oh, it's looking for
like Snake classic game play.
OK.
Snake game.
Let's see what it does.
So it moves.
So the tail's being
moved, added to the front.
Oh, interesting.
OK.
So it moved.
And on the next frame, it just added
a new head without popping the tail.
OK.
So it doesn't pop the
tail on that frame.
So that's how we're going to do it.
We're just going to keep the tail and
not pop it when we add the new element.
And that'll work.
Cool, easy enough.
OK.
So priorHead, priorHead is TILE_EMPTY.
Whoops.
No, this isn't where we need to be.
OK.
So if we're at just one segment--
and apologies if I'm
going a little bit slowly.
I just want to make sure I
think through this correctly.
We're going to move forward.
And then we're going to
keep the head where it is.
So snakeX and snakeY is
still going to exist.
And table.insert at index 1, the new
snakeX and snakeY, correct, and the--
oh, actually what we need
to do is snakeTiles--
oh, sorry-- at index 1, this
is going to be table.insert
into snakeTiles at index 1.
I believe this is correct syntax.
table.insert takes a optional
parameter to tell you where to insert.
I have to remember exactly where.
table.insert A 115--
OK.
So it takes the table, then
the index, then the value.
Got it.
I had that backwards.
Table, index-- so we're going to
insert into snakeTiles at index 1,
so at the beginning of the 2D array, or,
sorry, at the beginning of the-- yeah,
at the beginning of the table, not
a 2D array, just the Snake table.
A new element, which is going
to have our new head, right?
And then the snakeTiles at index
2 is going to be equal to snake--
what is it?
snakeTiles 2 is equal to snake--
sorry.
I keep having a couple of brain farts.
I might need to drink
some coffee or something.
Let me read the stream for a second.
"Add to the front."
OK.
"It would be cooler if
the snake's body had
to travel all the way until
the original apple tile was
right behind this snake's tail--
cool if the snake's body had to travel--
oh, you mean in order for
it to fully eat the apple?
Like it goes through the entire snake?
Because that'd be really difficult.
That'd be like expert mode Snake.
Expert mode Snake, got to basically
draw the snake with the apple kind of.
Oh, prior.
We got to use prior X, right?
So it'd be priorHeadX, priorHeadY.
And then it would be we have
to erase as well the old--
yeah, see, down here,
we're setting it to empty.
Oh, but then this case, we're actually
going to be at greater than 1.
OK.
Oh, but that we do still need to--
yeah.
This is part of the equation.
Part of the actual algorithm
for moving the snake
is that when it's greater than
1, this is where we actually
have to do the thing
where we pop the tail,
and then we add that to the
front of the snake as well.
So it's a little bit more complicated
than what we did down here,
whereby we took the priorHeadY,
priorHeadX index of the tile grid,
and then we set that TILE_EMPTY.
If we're greater than 1, then we need
to basically do the pop operation
every time.
"Will it be easier if you create
two apple tiles, one when it's eaten
and second one each time it disappears?
Example, when the snake eats apple one,
it disappears and apple two appears.
And it goes on vice versa."
Yeah.
Oh, wait.
"Create two apples, tiles, [INAUDIBLE]."
If I'm understanding correctly, I think
what we're doing is what you're asking,
whereby we just have
the single tile apple
and its indices that are being-- well,
rather, it's being stored in the grid.
And then when that happens, we just
overwrite the tile with the snake head.
And then we just generate a
new random tile for the apple.
I think it functions kind of
similarly to what you're asking
if I'm understanding you correctly.
Shayna, thanks for the question.
OK.
Sorry, getting a little distracted.
Let's see, tileGrid
priorHeadY, priorHeadX.
So this part was working fine.
And it's this part when we need
to start popping and adding.
OK.
So first of all, let me see
if this is even correct.
So part of this is the snake
tiles itself is just a data
structure that we're
using to keep track of,
effectively, where the tail
location is at all times.
But it needs to maintain a history
of where all the tiles are so that we
can change the tail accordingly.
So table.insert snakeTiles
1 snakeX, snakeY,
so that part is adding the new
head to the front, so the snakeX,
snakeY variable that we created before.
And then snakeTiles 2 is going
to be our priorHeadX, priorHeadY.
So now that's going to be
the new tail, basically,
where our previous head used to be.
And then once we have a new
tail, what we're going to do
is we need to set that in our grid.
We need to say tileGrid
at snakeTiles 2 index 2.
And snakeTiles 2 index 1 is
going to get equal to TILE_SNAKE.
Because it's not going to be
TILE_SNAKE_HEAD at this point.
It's going to be TILE_SNAKE.
And to make it a little
clearer, I'm going
to also have to change the
drawing function for this.
Where do I do that?
In love.draw, which is--
sorry, rather, the drawGrid function.
So else if tileGrid y, x
is equal to TILE_SNAKE--
oh, it's body, sorry.
Rather, we've defined it to be body.
Whoops.
Then I'm going to do
basically all this same stuff,
but I'm going to change
the color a little bit.
I'm going to change the snake head
to be a little bit more cyan color.
And then the body is
just going to be green.
And then I need to go up here where I
actually wrote that, so TILE_SNAKE_HEAD
and then TILE_SNAKE_BODY right there.
And then that will have the effect
of basically making the grid
reflect where the snake body is.
So let's go ahead and see if
this actually works first of all.
OK.
So it is working.
So, now, it's registering that we
have a body, but it's not moving.
So what we're doing is we're
effectively doing what we did before.
Whoa, that's going to
be impossible to get.
Oh, yup.
I think I screwed myself.
Oh, yup.
See, I did.
That's fine.
So we kind of have a step in the right
direction where we now recognize,
when we eat an apple, we add
a new element to our body.
But the snake head is overwriting
all those other variables--
or sorry, those other tiles in our grid.
And it's not sort of keeping
track of where the tail is,
which was part of this
last piece down here.
Was it down here, the
updating the head location?
Because if our snake
is only of size one,
then our head location is just
going to be wherever snakeY,
snakeX is, which we do here.
We update it in the tile grid.
But to erase it, what
we did before is we just
set the tileGrid at priorHeadY,
priorHeadX to be TILE_EMPTY.
The logic is different if we
have greater than one element.
So what we need to do is basically
erase wherever the tail was, right?
And by setting tile grid, for now
we'll just assume that we're of size 2,
right?
Ba, ba, ba, ba.
In this case, we'll assume we're
working just with a 2 unit snake,
and then we'll move up to multiple.
But we're going to need to set
tileGrid at snakeTiles 2, 2
and snakeTiles 2, 1,
equal to TILE_EMPTY.
And then we need set snakeTiles to--
we need this to update basically.
So we need to set TILE_SNAKE tiles 2
be equal to priorHeadX and priorHeadY.
And so that should work.
Let's run it.
Let me make sure I'm not crazy.
Yup, perfect.
So now we have 2 unit snake, but it's
only going to work for 2 units, right?
Whoops, if I can pick it up.
Whoop.
OK.
So it's still 2 units, right?
We've made progress, though.
We've basically said,
OK, in our tile grid
we want to erase whatever
the tail tile was last time.
In this case, we're hard-coding
2 in, because we're only
experimenting with the snake of size 2.
And then we want to set that last unit
to be equal to whatever the priorHeadX
and priorHeadY was equal to.
But this isn't going to work for a
snake of size, you know, 3 onwards.
Because this is hard-coded just for 2.
We need slightly different logic
in order to handle that use case.
OK.
"I mean, essentially having
an object pool of two apples,
it'd probably be harder.
You'd have to keep track
of which one is active.
The tiles here are being
rendered on the fly.
It's not like, say, UNITY, where
you can deactivate the mesh renderer
and make objects not be rendered."
Yes.
Often, object pooling
is very good use of--
excuse me-- resources,
but typically only
when you have a ton of different objects
that you need to keep instantiated.
And in this case, per Andre's point,
yeah, they're being drawn in real time
with the size of the grid and not being
actually stored necessarily in memory.
Although the grid itself
is being started in memory,
but it's not consuming more
memory by having the apples there.
There's going to be an integer, a 0, 1,
2, or 3 regardless of what we're doing.
"I'm assuming there
will also be a bug where
an apple spawns onto a tile that's
already occupied by a snake tile."
Exactly.
Yeah, I mentioned that previously.
And that's something that we'll have
to sort of take into consideration.
When we spawn the apple, we want
to basically look and say, OK,
is tileGrid y, x = apple?
Sorry, is it equal to
snake head or snake body?
And if it is, we need to generate
another random x, y value basically.
Just keep doing that in a loop until
we find a value that actually works.
OK.
So we have a two part snake here.
Let me take a sip of
water, little bit parched.
How much time we have left?
Oh, we have 12 minutes.
Oh, well, I'll go a little bit over
just because we were a little bit
late to start.
But now we can sort of think
about, OK, I have a two part snake.
But let's take the
problem to the next level.
Let's take it to where we
have three parts to my snake.
And let's actually do the logic
where we take the tail of the snake
and then put it in front, essentially
keeping the head where it was.
And then let's write to the grid
those two new locations, right?
We're going to need to
erase our tail location.
That needs to get erased from the grid.
And then we need to write our
new sort of head location, which
will be an empty tile presumably.
OK.
So let's go ahead and do that.
So the first thing I said, we
need to pop the tail of the snake.
Also, am I actually adding
the last piece appropriately?
Let's see.
Take tail and add to head
if greater than one segment.
Oh, you know what?
We're not actually
going to take the tail.
We're going to just add a head and
keep the tail where it is, right?
Because this is in the
context of eating an apple.
Because we don't want
our snake to get smaller.
We want our snake to
actually increase in size.
And so it makes sense for
us to take a brand new tile
and add it to our snake, not the
other way around that we did before.
So if the length of snakeTiles
is greater than 1, let's go ahead
and, first, we need to add to the table.
So table.insert into snakeTiles
at 1 snakeX and snakeY, right?
Because we're going to basically
keep what we had before.
And then in the case of having a new
head segment, we didn't have a tail.
But that honestly doesn't really matter.
Like I said, yeah, I think
we can just add a new--
I think I might have gotten this
confused with the actual code
with moving.
But I think we should just
be able to tileGrid at--
sorry-- snakeX-- rather, snakeY, snakeX.
We can set that to tile TILE_SNAKE_HEAD.
And then at tileGrid where we
had priorSnakeY and prior--
whoops, sorry, priorHead.
I did the same exact typo before--
priorHeadX.
We can set this to TILE_SNAKE_BODY.
And this should accomplish the same
thing if I'm not mistaken, right?
And then the only thing is the
head location needs to change.
So this is a hard-coded use case
where we have a snake of size 2.
"That's not a bug.
It's a worm."
Yes, correct.
"I'm assuming [INAUDIBLE]."
Yeah, that's true.
Very clever, Shayna, very clever.
OK.
So when we're moving, remember,
we got to take the tail.
So table.insert-- remember, we're going
to insert a new head location, right?
Or actually it's going to be the tail.
So what we're going to
do is we're going to say
we're going to say tileGrid priorHead--
oh, whoops.
OK.
So we're already doing it up here.
We're already adding the head location.
And then we're going to-- oh, no.
This is updating the head location
in the actual grid itself.
And we need to do this in the
snake data structure as well.
So snakeTiles, because
the head's moved, right?
So the head doesn't really
need to move I guess.
Oh, actually, yes, it does.
Does it actually?
Because we're always
going to look at the tail.
And. if the snake is greater than
1, the algorithm kind of changes.
So I guess we don't really
need to worry too much
about checking if we're only a size 1.
Or rather, I don't think we have to
update the snake's head automatically.
OK.
So if we're greater than
1, let's take the tail.
So local tail gets snakeTiles
at length snakeTiles.
So this will be our last into the--
do we even need a reference to it?
Because we're just popping it, right?
Well, I guess we do, because we
want to set tileGrid tail 1--
or 2 rather.
Did I do that correctly?
Yeah.
Tail 1 to TILE_EMPTY, right?
Because, remember, we're taking the
tail and we're making that empty.
And then we're going to take
the head, and we're just
going to add another
element to the head, right?
So in this case, table.insert into
snakeTiles at index 1 a new head
called--
because we don't actually necessarily
need to move the head to the--
do I need to move the head to--
or, sorry, we don't need to
move the tail to the head.
We just need to add a
new element to the head.
Because the tail element is
going to have different x,
y coordinates than the head element
is going to have x, y coordinates.
Because the tail is way over here.
And the head is over here.
By moving the tail up
to here, all we've done
is basically just added another
table with some random numbers that
aren't terribly useful
if they're completely
disparate from what that actual
nodule supposed to represent.
So I'm just going to add another unit
to the front of the head, snakeTiles
at index 1.
And I'm going to say
this gets snakeY, snakeX.
This is where our head is right now.
So we popped the tail and then
added another element to the front.
It'll have the result of growing
the snake fairly seamlessly.
Let's see if I have all
the pieces in play here.
I'm guessing I probably
screwed something up.
Yeah, looks like I did.
OK.
So I'm not actually growing
the snake in this case.
So why are we not growing the snake?
Oh.
I don't have an else statement
here to grow the snake.
Oh, actually because, remember,
we basically separated this out.
We don't need an if statement.
So I can just copy that out,
whoops, paste that into here.
Make sure these are
indented the same way.
Let's try that.
Does that work?
Whoops.
Oh.
No, crap.
It's still not completely working,
because we're not erasing.
Notice that whenever we get an
apple, it's adding a body element,
but it's not moving around
which is kind of an issue.
Because that's another thing, too.
We do need to move the snake.
Well, actually, we are
doing that already.
But we're not reflecting the
correct tile when we do that.
We're drawing the head
instead of the body.
So let's figure out
why that's happening.
snakeY, snakeX is equal
to TILE_SNAKE_HEAD.
And then the previous one-- oh, right.
Because if there is a previous one,
then we need to draw the body at--
like, our last location needs to
be the color of the body, right?
So the tail is going to be empty.
And then the location of the prior
head needs to be the body color.
So tileGrid at priorHeadY, priorHeadX
is going to be equal to TILE_SNAKE_BODY,
right?
And if I'm correct, this
should at least draw
the snake body, the green
color that we're looking for,
which it does perfectly.
I'm noticing, though, that
the head is still kind of hard
to see relative to the body, because
the color's a little bit too similar.
But it works well enough.
It works well enough.
I can make probably make it a
little bit easier if I set the body
to be something like this, maybe 0.5.
Oh, sorry.
Yeah, that might work actually.
Let's try that.
Yeah, there we go.
That's good.
That's a good color.
Now, unfortunately, the
snake isn't actually moving.
And we can still collide with
ourself which isn't ideal.
But it's a step in the right direction.
So let's go ahead now and figure
out why the body is still rendering.
Clearly, the tail is
not being taken away
and not being erased from the grid.
So we kind of need to
fix that up, right?
So if I go to updating the
head location, so tail 2,
tail 1 is equal to TILE_EMPTY.
Now, presumably, this is the
case, because tail 2, tail 1
is not getting updated I'm guessing.
So let's figure that out.
Let me look at the chat and
make sure I'm not mentioning it.
"Strange, it worked earlier when you
limited the total length to two tiles."
Yeah, it did.
I had a slightly different
hard-coded logic in there.
That made it a little
bit simpler, because we
weren't messing with like
taking a tail and appending
a new element to the table.
I think I just kind of added
a new element to the thing.
And then I was keeping track of them.
But we'll figure it out.
We're here.
We have a few more minutes.
It's 6:00 now, but I'll go for
a few more minutes so maybe we
can get a little bit closer.
And then maybe on a
Monday follow-up stream
we can finish this up
sort of live and then
have a completed game
to at least look at.
And then by putting
this on GitHub, maybe
if everyone wants to experiment on
their own and try to figure it out,
it'll be a nice cool exercise.
We'll come back together on
Monday and figure it out.
But before I adjourn, I would like to
try and make a little bit of leeway.
I won't keep folks for too much longer.
We'll maybe give another
10 minutes or so.
Let's go ahead and--
by the way, the risk of live coding
is you never know what to expect.
And like I said, I did no
research into all of this,
but that's part of the fun of
programming and problem solving is,
you know, sort of trying to
figure things out on the fly,
figure out a problem that
you've never tried to before.
It's kind of interesting, right?
I get to embarrass myself a little bit
live in front of everybody, you know?
OK.
So the head location is working fine.
We can see that by seeing the
game and seeing that the cyan
sort of colored square is updating.
But the tail isn't erasing what's
behind it, which is the important thing.
And so tail, get snake
tails, snakeTiles,
priorHeadY, priorHeadX is
equal to the TILE_SNAKE_BODY.
So we're moving that.
So we could see that working.
It just needs to erase what's
at the end of the tail, which
is what this line was
intended to do, which
was take the last tile sort of
in our grid and then edit the--
what is it?
Sorry, brain fart.
In the tile grid, change the
number from 3 to 0 effectively.
"Maybe figure out where the head should
be after delta time and add tail.
There we're making it head color.
Make head a part of the body.
Live coding's like a box of chocolates."
That's true, Andre.
There's very true.
It is.
I think it's fun.
I could see why someone might get
frustrated watching, hopefully not.
"Life's just a huge p set that
with a lot of subproblems--"
very true, very true,
a ton of subproblems.
Every problem you solve sort of
just adds new little problems.
And you know, there's people
online that make this game.
And they live code it in like
10 or 15 minutes or whatever.
So they are a lot more
impressive, I guess, to watch.
But I guess the point of this
more is to kind of illustrate
like live problem solving,
and to take questions,
and to sort of have a
conversation about it
and facilitate back and
forth communication.
"Thank you, Colton.
I will join you again on Monday.
Have a great weekend."
Thanks, Steve, appreciate
you coming by again.
Yeah, we'll, do it again.
We'll do a follow up on Monday,
and we'll figure it out.
"[INAUDIBLE] it's really good, though."
Yeah, thank you, appreciate it.
Yeah.
It's a work in progress for sure.
But I would say it's
3/4 of the way there.
And on Monday, we can get it to 100%.
"Am I the only one scared by the
amount of figuring out this requires?"
Nowanda 3333.
Yeah.
I mean, that's kind of, I guess, the
point of doing this sort of thing
is to kind of have like a demonstration
of what coding and solving
a new problem feels like.
I mean, some people might
be better at this than I am.
And I'm certainly not
the greatest programmer,
but I've definitely
programmed a bunch of games.
So I kind of have a general idea
more or less of how to do stuff.
But a lot of it, especially
if you've done no research,
is just kind of figuring
out like, OK, what works?
What can I guess would work?
How can I structure my
data to make it work?
There's a lot of solutions
to every problem,
like when we're talking about taking
the tail and appending it to the head,
adding a brand new head element.
Replacing the neck was
my initial inclination
rather than adding to the head.
The head is probably a
much cleaner way to do it.
There's so many different ways to do it.
And even simple games like this--
games are particularly difficult,
because they have a
lot of stuff going on.
You have things occurring over time.
And you're messing with graphics.
And you're solving abstract problems.
It can be difficult. But
I find it a lot of fun
even if we didn't get 100%
of the way there today.
I think we can close the loop and do
a lot more fun stuff in the future
as well.
"I couldn't make it work
for my Scratch project
either, making the
snake dynamically grow."
Well, we'll definitely get it to work.
We'll get it to work on Monday.
And I don't know.
I guess maybe I can in advance
take a look at some algorithms
for this kind of stuff.
But the figuring it out live kind of
feels a little bit more authentic.
And I guess it allows us to also have
this sort of like two-way conversation
which I kind of like, like
taking suggestions from everybody
and being able to say, oh, maybe
this solution is actually better.
Maybe this suggestion could
be changed in this way.
You could do it this way.
And then we actually end up making
a project together versus me
just kind of saying, oh, here is
how I figured out how to do it.
This is how you do it.
Let's do it and kind of like
me talking to a wall almost.
Not really a wall, but, you know,
you get the idea, sort of idea.
So I don't know, I personally like it.
But let me know what your thoughts are.
"I'm a bit confused as
to what the difference is
between the blocks between 112 and 119."
112 and 119, OK.
So this is updating the head location.
And 102 and 107, excuse me, 102 and 107.
In this case, when we have an apple
and we want to add it to our score,
we want to not take from the tail.
And we're just going to add a
new head element to our snake.
Because our snake is
growing at that point.
If we're just moving, the goal of
moving is to take the tail element
and think about putting it
at the front of the snake.
So we're kind of like constantly doing
this shift operation with the snake.
But in reality, we don't really need
to take the tail and put at the front,
because the front should kind of have
the information about where that head
element is.
And if we take the tail, the tail
element has the x, y of the tail.
So it doesn't really work.
So we kind of just have to pop
the tail and then push a new head
element with the new x, y
information to the front
of the snake, so good question.
"Thank you for your time.
See you Monday.
Time in Morocco is 11:00 PM.
I need to go to sleep."
Thanks, Elias, appreciate you tuning in.
I'll see about maybe starting
a stream a little bit earlier,
maybe like at 12:00 my
time, noon here, which
should be ending a little bit more
around 7:00 or 8:00 your time.
We'll see.
So thanks for tuning in even though
it's really late, appreciate it.
JP, "the code's also been writing
to look and read intuitively.
The movement's pretty straightforward.
Drawing the snake is
also straightforward."
Yeah, Lua's a great language.
I really love Lua a lot for that reason.
It's pretty easy.
It's kind of almost C-like.
But it also has a lot of readability.
And it's just kind of clean and nice.
A lot of the things about it I find--
or not a lot of the things about it.
But a couple of the things about it
I find a little bit questionable,
like the do and then the end
keywords and stuff like that.
I'd probably change that about
it if I were redesigning Lua
from the ground up, but
it's a good language.
I like it.
It's fun.
Shayna says, love the
live coding session.
You did a great presentation,
very interesting and authentic."
Thanks, Shayna, appreciate it.
Would have preferred
it be a little cleaner,
but, you know, we'll get there.
This is only stream number two.
We got a lot to go.
So on Monday, hopefully,
we can fix everything up.
Nowanda, "it's very fun.
I forgot about my tea.
And it got cold while
watching you twice."
Thanks, Nowanda, appreciate it.
I hope you get to drink your tea.
I hope it's not too late.
"You're streaming on Monday is the
same time, 3:00 Eastern Standard time?"
I think it might be, Elias.
I'm going to see about the logistics
of getting it up and running
at 12:00 Eastern Standard
time instead, so it's
a little bit easier for folks that
are overseas to maybe tune in.
You know, we'll see.
We'll see what I can do.
"My gut feeling tells me that
keeping track of the snake's body
coordinates in an array
or list would actually
make the code less readable, but
more abstract and most likely
more editable."
Yeah.
Yeah.
That's effectively what we are doing.
We just need to store
them, because we need
to keep track of where they
all are at any given time.
But we also need to edit them.
And that might be part of the
thing that we're not doing
is actually going back
through all the snakes.
Well, actually, is that true?
We don't really need to edit them.
Or do we?
That might be part of the problem.
I think we might actually
have to edit them,
because the tail information
is constantly changing.
I don't know.
I have to think about
that a little bit more.
I have to think about
that a little bit more.
But we do need to keep track
of all of the different nodes,
so we have the tail from
which to, I guess, pop off.
I have to think about a little bit
more to be quite honest with you.
My brain's a little bit fried after
the three hours of programming.
So I'll spend like an hour
or two looking at this
and figuring out a plan of action.
And then on Monday, we can come take
a look at it and finish tackling it.
"Great job on the stream."
Thanks, appreciate it JP.
Thanks for coming in.
I guess here is where
we'll cut the stream.
Again, Monday I'll set up an event.
I'll set up a stream to finish this up.
I'll look at it over the weekend.
We'll get it all sorted and situated.
Get it on GitHub as well, so
that all of you can download it
and mess around with it.
If you have suggestions on a
next game to implement next week,
maybe starting on like a
Thursday or Friday, let me know.
Someone suggested Pac-Man.
So Pac-Man might be fun.
That would be a little
bit more complicated.
So that one for sure would probably
take I want to say like four or five
streams to fully implement.
I could be overshooting it, but it's
a bit more complicated than this.
And this took more than one stream.
So we'll see.
But, yeah, let me know on
Facebook and on Twitch, wherever.
Let me know your suggestions
for more games to make.
I'd be happy to create some new ones.
Thanks, Irene.
"It's been great fun today.
Thank you.
Will probably not be able to catch
it on Monday, but we'll watch later."
Yeah, we'll post to VOD, so
you'll be able to see what's up.
Thanks, Irene for tuning
in, appreciate it.
OK.
So I think with that, we're
going to call this stream.
So, again, this is part one of Snake.
We got the snake sort of rendering
around, moving around, consuming
apples.
We have a score.
We have a grid.
But we do not currently have
the snake moving correctly.
And we do not have collision
with the snake and itself.
But those will be the things that
we tackle in the next stream.
So thanks again so much,
everybody, for coming.
I will see all of you
at some point soon.
Good bye.
