SNAKE FROM SCRATCH (PART 2) – CS50 on Twitch, EP. 3

SNAKE FROM SCRATCH (PART 2) – CS50 on Twitch, EP. 3


COLTON OGDEN: All right,
I think we’re alive now. Sorry for the delay. This is CS50 on Twitch. My name is Colton Ogden
and today we’re going to continue the stream that we
did last week on Friday where we started implementing the
game Snake from scratch. Recall, we ended up putting something
together a little bit like this. So we had a little– oh, sorry. I forgot to switch to
actual machine over there. There we go. We had something that look
a little bit like this. So almost like a little Etch A
Sketch going but not exactly Snake in the sense that most people
I think recognize Snake. But a lot of the pieces are still there. You know, we went from having a
cube that moved across the screen to having a cube that sort of
moved discreetly across the screen in like a grid sort of way. Talking about how we can actually
divide our game space up into a grid, which allows us to
then start transitioning into games like Rogue Lights and
other games that are more tile based, which we could definitely
get into in the future. And then we started talking
about a Snake data structure and we talked about some basic
drawing routines and Love 2D and all sorts of other stuff. Won’t go too much into the
details because the VOD will be up and this and the other video
will also be going on YouTube. But today, a couple of the
problems that we want to solve are one, make sure that when
we do eat an apple in our game, rather than our snake kind of
just drawing an infinite line, we want to actually get rid of the tail. We want to pop the tail while
continuing to draw, you know, the head is just moving
in another direction. And another big thing that
we want to bite off today is making sure that when we collide
with other parts of the snake that we trigger a game over, as I
would have just done right there. And those are two sort of big
pieces that we’re going to bite off and they’re actually
not terribly difficult. I spent a little bit time over
the weekend actually thinking through the problem and I was
a little bit tired last time but we should be good today. And then if we do have time,
which I think we will– we’re going to be on for
a three hour stream today. We’ll take a look at
having an intro screen. Like a– you know, the
word snake, press enter to start because right
now you kind of just get right into the action,
which is a little bit tricky to jump just right into. And then also a game over screen so that
when we do intersect with the snake, the game should stop. Maybe transition us to another
screen with some text on it that says, “you’ve got a game
over” and then display your score. [? Sue Leith ?] says hello. Hello, [? Sue Leith. ?] Good to see you. [? Bavick Night ?] says
hey, how’s it going. [? Bavick Night, ?]
good to see you again. And [? Sue Leith ?] asks,
will this be re uploaded? Yes. So last week’s video and
this video will be up– uploaded to YouTube. They should be uploaded today,
tonight, if not tomorrow morning. And the VOD from last
week is actually– should be accessible on this Twitch account. The get hub for the actual
code itself is on this URL. So, github.com/coltonoscopy/snake50. And you’ll be able to see the main.lua
file that’s in there just by itself. And recall main.lua is the
entry point for LOVE 2D, which is the framework
that we used last Friday. If unfamiliar, if you’re just joining
the stream for the first time, LOVE 2D is the framework that we’re using
to do all of the game programming. All the graphics, all the windowing,
all the input, all that stuff. So you can go to love2d.org. By default, it should take you
to a page that looks like this and then you can download the version
of ProCreate for your operating system. So I’m on a Mac, I’m on Mojave,
so you can just download here. If you want to download other
versions and older versions, you have an option to do so here. [? Bavick Night ?] says glad to be here. Glad to have you, with us
[? Bavick Night. ?] Thanks for joining. All right. So let’s dive right in. Briefly I will just summarize
the code that we have. So we have a constant table up here. So just a set of constants
that just basically say, oh what’s the window width and height? What’s the tile size? Tile size being of our grid size, so
we could think of it that way as well. The number of tiles on the X
and Y. And then a few constants to represent particular
slots in the grid whether a tile is empty, whether it’s a
snake head, whether it’s a snake body, whether it’s an apple. Those are the main sort of
game play mechanics at play. And then lastly, a constant
for the time in seconds that should elapse before the snake
actually moves in an individual grid tile in the game. We have a few other variables here. So a font, a score, which is important,
the actual grid for our tiles. This is the data structure
that actually holds the zeros, one’s, two’s and three’s that represent
what’s going on in our game world. The snake X and the snake Y, which
is where the snake’s head is located, whether our snake– what
direction or snake is moving in. Which, this should just probably
be called local snake direction equals right. But we’ve already called it snake
moving, so we’ll just keep it that way. And then a snake timer
because remember we do have to keep track of
how much time has actually elapsed over the course
of all the frames before we end up moving our snake. So we basically check snake
timer against snake speed. If snake timer is
greater than snake speed, then we should move
to the next location. And then here, we do need a data
structure to represent our snake. So because we need to make sure that
we pop our tail off of the snake whenever we move, we need to
keep track of all the nodes that we add to the
snake as time goes on. [? Sue Leith ?] says, where
did you learn all of this? Is there a Love 2D manual? So, yeah. Actually, Love 2D has some
excellent documentation. So there’s a very basic set of
tutorials just on the main page. So here you can see there’s some
examples of basic application where you draw text, draw
on image, play a sound. There are some full games
you can take a look at. I believe some of these have
source code and some of these are actually commercial Steam
projects, which is cool. Which goes to show you
that you can actually use this to make a published
game on Steam if that’s something that’s of interest to you. The actual documentation is here. So at the very bottom right, you can
just click on any of those modules and that will take you
to the page for that. [? CPU Intensive ?] says, tall hair. Yeah, I know. I need a haircut super
badly, like really bad. But if you go to love2d.org/wiki/love
is where you can actually see the documentation. And there’s a bunch of
different name spaces here. Love, love.audio, love.data, love.event. We’re going to be using pretty
much just love.graphics. We use love.window, as well,
and then the core functions that you can access in love. So love.load, love.update, love.draw. These are the functions
that make up our game loop. If you’re familiar, we
talked about this last week. Every frame, which is
usually 1/60 of a second. Love2D will execute
some code in a function called love.update and love.draw each
frame, update happening before draw. And at the very start of your program
it’ll call a function called love.load. Love.load sort of sets
up everything, if you have some variables that
need to be initialized or some resources that
need to be loaded. Optionally, as we did
up here, you could just put them at the very top of your
script, and they’ll all execute in advance in much the same way. But as is sort of tradition,
you’ll see a lot of these functions like love.window.setTitle,
love.graphics.setFont, love.window.setMode, a lot of these
functions that sort of set up the game, set up the state machine
that is Love2D, in a sense. Those all exist here, as does
setting the random number generator, which we did last week. And maybe initializing
some data, and such. [? JPguy ?] says hello again. Hello, JP. Good to see you. I wanted to have the Twitch
chat enabled in today’s stream, but we’re having a little bit
of difficulties with Streamlabs. So we’re going to try again. Next week we should actually have
the embedded chat in the final video, so that folks watching
online on YouTube or what not after the fact can see
what people in the chat saying. So looking forward to– hopefully tomorrow. So tomorrow we’re having another
stream with Kareem Zidane and if anybody’s familiar. He’s going to be doing
the Git and GitHub stream. Elias says, “Hello, again from Morocco.” Hello, Elias. Good to see you, again. Thanks for coming in. Just to cover a little bit
more of what we have going on in our love.keypressed function. It takes a key. Remember that we were testing for input. So if we pressed left,
right, up, or down, we should set our moving variable to
left, right, up, or down accordingly. And then we do a check
in our update function. So remember, update executes
once every 1/60 of a second, approximately every frame. And if we’re moving in any
of these given directions, then we should increment or decrement
our snakeX and snakeY variables. Which, remember, that’s where
our head is going to be, and that’s sort of where
its index is in the grid. Originally, that was our pixel value, so
we were measuring our square in pixels. But remember, that was kind
of continuous movement. It wasn’t actually adhering to a grid. So a little bit trickier to do
collision detection that way. So we divided it up
into a grid and we make sure to move our snake in increments of
32 pixels, instead of just one pixel. JP says, “How long have
you been streaming? I just got home from work.” Just for a couple of minutes. So we’re just reviewing all of
the code that we did last Friday. JP, I know you were there, so
this is all old hat to you. But in case anybody is
watching who needs a refresher, this sort of is a recap
of everything that we did. And then in drawGrid, we’re
basically just checking each tile in a nested loop in our tile
grid table, at yx for 1 to MAX_TILES y, and one to MAX_TILES x
on the y and the x-axes. If it’s empty, don’t try anything. If it’s apple, draw red. If it’s head, draw a
lighter shade of green. So kind of a cyan green, since we have–
remember, love.graphics has that color, takes in four variables– red, green, blue, and alpha. For a cyan-ish value, it
needs to be 1 and 0.5. 1 and 1 here. G and B both thing 1
would be completely cyan. But that’s all a bit too
bright, so we’re just going to make it 0.5 on the blue component. And then the body itself we’re
just making kind of a dark green. So instead of setting
G all the way to 1, we’re sitting at the 0.5,
which just kind of is halfway between black and full
green, effectively. And then at our xy, we subtract
1 from the x and the y, multiply that value by 32, because
tables are one index by default. But coordinate systems are zero
indexed, so we need to decrement our xy, and then multiply the
end value by tile size. That will have the effect
of drawing that rectangle, that square at the appropriate
pixel coordinate in our game. The drawSnake function we
ended up actually not using, so I think I’m just
going to delete that. I don’t think we actually
need to draw that. The grid itself is going to
handle drawing the snake, so I’m going to take
away the drawSnake call. I’m going to take away the bit of
code down near the drawSnake function, going to save it. Initializing the grid is fine. And then up in our
update function was sort of where we had the last
bit of problem-solving, before we ended the stream
at the three-hour mark. And that was checking for an
apple, and then adding a head node to the data structure and trying
to pop the tail off if we do– if we don’t get an apple, rather,
we should pop the tail off of the data structure and set to
empty that node in the 2D array, in the 2D table. If we don’t get an
apple on the next grid index, where our snake head is moving,
what we should do is still push– basically, the algorithm
that it’s going to be is we still want to push an element
onto the front of the snake. So basically add a head element. But then we’re going
to pop off the tail, so that it’s going to have an effect of
shifting the snake one tile in whatever direction we’re moving. And this will work even if our
snake is only one tile large, because that snake, as soon as we
do that, it will have two elements. So it will have a head and will
have a tail with which we can pop. That tail being the head– as it just was in the last frame– at just size one. And so all of this code is
a little bit convoluted, and doesn’t quite get the
job done in a fantastic way. So what I’m going to do is actually–
from like 95 down the line 119, after we’ve deleted the
other things– basically, checking for the apple all the
way to the bottom of this– checking whether the size of the
snake tiles table is greater than 1. I’m just going to delete
all of that, and we’re going to think about how we want to
solve this problem of getting our snake to move, and also keep its body
size, if it eats more apples. So if I run this after deleting
all that, it should still operate. Oh, no, it’s not. OK, so it’s not actually
moving, currently. Let me just verify which part
of that was the update location. Right, OK. Because we’re not actually
updating the head location. So a couple of things
that we want to do. So the first thing we want to do,
when we’re moving in our snake world, is we want to pop a new
head location after we’ve decided what direction are we moving
in, assuming that we’ve increased– remember, snakeTimer keeps
track of delta time every frame. And if it exceeds 0.1, which is
1/10 of a second, at that point, it will have exceeded snake speed. We can then add a new head
element onto the snake. So what I’m going to do is basically
say, push a new head element onto the snake data structure. And so what this basically
means is I’m going to do a table.insert,
because this is how we push new elements into a table in Lua. So our snake data structure–
remember, that’s this thing up here, the snakeTiles right here. Which, by default, just has one element. Our snakeX, snakeY. So what I want to do– I’m getting a little bit lost here. OK. So in our update function–
or approximately line 96, if you’re following along– into snakeTiles at index 1– so table.insert can take an
optional second parameter, which is where in the table we
want to insert that element. In this case, I want to
insert it at the very front. So I want to insert it at index 1. Because remember, tables
in Lua are one index. So 1 is the very first index. 0 normally is the first index in a
programming language, but 1 in Lua is the first index. So we’re going to use number 1. Sit up just a little bit here. OK. So at index 1 at the
very front of our snake, we’re going to want to
insert the element that is going to be its head next. So it’s quite simply
snakeX and snakeY, which is what we’ve just altered
up here in this if statement, this series of if statements. If we’re moving left, right,
up, down, increment or decrement snakeX or snakeY. So we’re going to do that. So now our data structure has the
next head element set in place, and whatever the head was on the last
frame is now one index below that, if that makes sense. So, good. That’s easy, that’s straightforward. That doesn’t actually influence
the view of our application. So folks familiar with MVC might think
of the snake tiles data structure as the M, the model of our application. Whereas the grid is the
view, the V our application. So we’ve updated the
model, and now we need to reflect this change in the
view, as well, effectively. We can think of it in that sort of term. So before we do that, though,
the tile grid itself also keeps track of where the apple is. And the tile grid is kind of like a
view and a model, in a sense, as well. So we’re not completely, cleanly
splitting up our applications infrastructure in as
clean of a way as MVC, but you can sort of conceptualize
it in a similar way. But basically, what we need to do
is we need to push this element. But then we also need to
check to see whether or not the next element is an apple. And if it is, then we need
to change our behavior. We can’t override that value
with snake head just yet, because then we won’t know whether it
was actually an apple that we consumed, or whether we moved into another slot. Or for that matter, as we’ll
see later on, whether we’ve collided with our own
body, which is going to be an important consideration
when we do collision detection. So let’s do that. So the next step is going to
be, basically, check to see are we eating an apple? So if tileGrid snakeY snakeX
is equal to TILE_APPLE– and I don’t actually
need parentheses here. Something that’s a hard habit to break,
coming from a more C-like programming languages, sometimes I still write
parentheses in my conditions. But Python and Lua do not
require that in your conditions. It’ll still work, but
it’s not necessary. So if it an apple, then
what we need to do– because remember, we
looked at this last week. If we’re eating an apple,
then we need to basically keep the element pushed under the
stack, and update the view, and also update our score,
and then change the board to have a new apple somewhere else. And then we also need to not pop
our tail element off of the snake, because the snake
should increase in size. Therefore, we need to
keep the tail, and then also add a head element,
which will have that effect. Which we looked at last
week, when we saw the video. So if we ate an apple– remember, last week when we did this,
we talked about random number generation and getting a new random
x and y values, in which we’re going to spawn a new apple. So let’s do that. So I’m going to say local appleX,
appleY gets math.random(MAX_TILES_X) and math.random(MAX_TILES_Y). Hopefully you can see that. Might be a little bit small, actually. Let’s increase that size a little bit. I’m going to take this off. If the chat can confirm that
the text size looks OK on this, I would appreciate it. So we’ve created the new
the two new variables that we’re going to
need for our new apple. So what I’m going to do is tileGrid– first thing’s first,
score gets score + 1. And then tileGrid at appleY,
appleX is equal to TILE_APPLE. So now we’re going to
increase our score– awesome, cool. Thanks JP, thanks [? Duwanda. ?] We’re going to increase our score. We’re going to then generate two
random variables, our x and y, which we’re going to place a
new apple into the grid. And then we’re going to set that grid
element at yx to the TILE_APPLE value. Now, if it’s not the case
that we ate an apple, then we need to pop the tail, which will
give us the effect of moving forward. And so this tail popping is
sort of conditional on the fact that we ate an apple to begin with. So I’m going to go
ahead and say local tail gets snakeTiles at index
number of snake tiles, which will have the effect of indexing
into our snakeTiles table at the element of whatever
the size, which will give us the last element in our table. And then this tail element
should be deleted from the grid. So this is where we trace our
tail from the grid itself. So our model, remember,
is the snakeTiles, and then our view is the tileGrid. So each time you move, you’re checking
whether or not you’ve eaten an apple, right? Correct. Yeah, we check first, because
that’s going to determine whether we pop our tail element. And we don’t want to override that
tileGrid element with our head value, because then we won’t know
whether we’ve eaten an apple, because it just erases that
information from the grid permanently. It overrides it. So we’re going to get our tail element
from our snakeTiles data structure. So this will be our tail, our
last element in our snake table. As soon as we get the tail, what
we want to do is update our view. So tileGrid grid at tail 2, tail 1. Because, remember, our
elements are xy pairs– sorry, rather– yeah, in here. So in our snakeTiles data
structure– so snakeX, snakeY, in this particular case. But it’ll always be an xy pair. So every element in here at
index 1 will be our x value, every element in index
2 will be our y value. There are cleaner ways to do this. We could have written it
something like this, so x=snakeY and then y=snakeX. And then we could have done something
like snakeTiles.x, snakeTiles.y, which would be cleaner and
a little bit more robust. But just as a first game example,
we’re going to kind of take things a little bit more literally. And we’re going to just use numerical
indices, which is the default Love2D and Lua behavior, rather. So basically, now you can see
that this element at index 2 is going to be our y, and
index 1 is going to be our x. We can set that to TILE_EMPTY. And then the last thing
we need to do is we need to actually pop that element
of our snake data structure. We need to delete it from the snake. And so what we can do is do
table.remove(snakeTiles). And so what that does– by default, table.remove
can take an index, but you can also just give it the table. And what the result
of that is going to be is just the last element in snakeTiles. Which is perfect for
this use case, because we want to take the last
element from snakeTiles and remove it from the data structure. So that’s a big chunk consumed here. The head element is going to get
pushed onto the data structure then we’re going to choose
whether we eat an apple. If we did eat an apple, we’re
going to increase our score, generate a new apple. If we didn’t we’re, going to pop
our tail off and erase the tail. So the next thing that we need to do– remember, we added a head
element, but we haven’t actually adjusted our view, because we needed to
check for the apple in our view, right? So then one thing we need to do here
is say tileGrid at snakeY snakeX should be equal to TILE_SNAKE_HEAD. Update the view with the
next snake head location. And then we can write some comments
here, which just says, otherwise– oh, if we’re eating an apple,
increase score and generate new apple. Otherwise, what we want to do is pop
the tail and erase from the grid. And then update the view with
the next snake head location. If everything has gone
according to plan, this should work as soon,
as I hit Command-L here. So I’m going to go ahead,
eat this, and then voila. Now we have a snake that’s moving. And last week, remember, we
had only two elements max, because our algorithm wasn’t
quite perfect last week, and we were sort of special casing it
a little bit more than we needed to. But now, it seems to work just fine. We have a snake, our score is
increasing every time we eat an apple. And also importantly, our
snake body is growing in size and our tail is popping off as
needed, when we move around, so that it looks like we have this
continuous creature in our game space. So it’s looking pretty good. There’s a couple of issues. So one, you might have just noticed,
I can actually move backwards and we get some graphical bugs, which
isn’t exactly behavior that we want. And then another important thing
is that if we collide with ourself, it doesn’t actually trigger a game over. And those two issues are sort
of compounded with one another. And we’re not actually drawing the
body as a different color, which is one of the things we wanted to do. That bit is actually fairly
easy, if I’m not mistaken. By the way, in the chat,
[? Bavick Night ?] says, “Yas.” Thanks, [? Bavick Night. ?]
Appreciate the support. And JP, “Damn, that looks good.” Thank you. I agree. I’m pretty happy. Last stream it was pretty rough. Had a little bit of a brain fart. But the algorithm is
fairly straightforward. It’s fairly simple. Once you break it down and
make it a little bit cleaner, it all sort of makes sense. Let’s bite off another part. So we have the snake
drawing as one color, but we’d like the snake head to be
a different color from the body. So this should be as simple as
tileGrid priorHeadY priorHeadX this needs to be in a condition,
because it could be the case that we have only one– oh wait, no. Is that the case, though? Because this is always
going to run after we’ve– oh, because it is outside of the
condition of eating an apple, we do need this to be special cased. So if it’s the case that our
snake is greater than 1 size– if our snake is greater
than 1 tile long, we need to set the prior
head value to a body value. And TILE_SNAKE_BODY. And so what this will do is,
assuming that our snake is at least 2 units long, when we move forward– remember, that we’re always writing
a snake head value to the next tile, but we want to write a snake body
tile to whatever the head was on the last frame, so that it
looks like our snake has a body. It’s disjointed from the rest of it. [? Arrowman123 ?] says, “Hello. It is really nice that you
started this on Twitch.” Thanks. Yeah, I’m super looking forward
to seeing where it goes. I like the fact that we can sort of have
a conversation and talk back and forth, and maybe people can
suggest techniques or ideas. Somebody suggested an idea, who was
on stream last time, for a new game that we’ll be implementing
on Friday called Concentration, which is
like a card matching game. So I’m kind of looking forward
to seeing where that goes. But OK. So assuming that I did
this appropriately, if our snake is greater
than one tile long, and I run– so currently,
it’s just one tile long. If I eat this apple,
now, our snake actually has a body segment
that’s a different color. And it will increase in size,
but our head will always be the color of the head element. So now, we can better keep track
what direction we’re going. And not that it’s terribly
difficult to see normally, but just a slight little change that
has a sort of an accessibility feature, if you will. OK. Pretty basic, after all,
despite last week kind of fizzing out a little bit at the end. But now we have a few new
features to think about. Excuse me. So first feature that we
should probably think about is triggering the actual game over. Because right now, we can
collect apples forever, but we’re never going to lose the game. So the game isn’t really– I guess it kind of is a game,
but it’s not really a full game, because we’re missing that piece of
the puzzle that actually lets us lose. Kind of important, because that’s
how you can measure your skill. I’ve got to stay hydrated a little bit. So yeah, next piece of the puzzle. How can I detect whether the snake– or rather, most and more
importantly, the snake head– is going to be eating a piece of itself? Bella [INAUDIBLE] says, “Hi, Colton. Happy to be here today.” Thanks for joining, Bella. I appreciate that you’re here. We just ended up fixing up our snake
game, so that now we can run it. And unlike last week, where we ended on
it basically being a glorified Etch A Sketch, with the snake
not deleting itself, now our snake can grow and move around
as an actual entity in our game world. So a very satisfying. We’ve come a long way. And so let’s decide how
we’re going to do this. So I’m thinking we can
probably do something up here, where we check for an apple. And maybe before we
check if it’s an apple, we can probably just do something as
simple as if tileGrid snakeY at snakeX is equal to TILE_SNAKE_BODY– because it should never be able to
move into another tileSnake head– then I want to do some
sort of game over logic. Maybe I want to set some
game over value to true. And then if the game over
value is true, then I should probably change how I’m rendering
the game, probably down in my draw function, instead of drawing the grid. I can probably say if not
gameOver, then draw the grid. Else drawGameOver maybe. End. And then these two, the score
and the other print statement, should probably go in here with this. So print score. So drawGameOver basically is
going to be the function that has this sort of
different view of the game that we want to take into consideration. So drawGameOver. I’m thinking something simple probably. Maybe something like
a really large font. Just game over. And then below that, in a smaller
font, maybe your score was 8. And then if you press
Enter on that screen, maybe we should be able
to restart the game, and then continue to be able to
press Escape to quit the game. All right. So fairly straightforward. So let’s go over to back
to our update function. And remember, we have to set
this gameOver variable to true. And then we need this gameOver
variable to be stored somewhere, so I’m just going to store it
up here with our other snake– actually, I’m going to
start it with our score. So local gameOver=false. It should be false by default. [ALARM SOUNDS] You might be able to
hear that police siren. Now, remake it in Scratch, says JP. Yeah, actually, that wouldn’t
be a bad demonstration maybe for folks taking
CS50 for the first time. I’ll have to take a look at that. I’m guessing it was you who said that
you tried to make the snake dynamically resize in Scratch, right? And you said you were having a
little bit of difficulty there? [INAUDIBLE] Yeah. A snake beginner game series
might actually be compelling. Let’s see. OK, so line 99. So if we do intersect
the TILE_SNAKE_BODY tile, if our snake head intersects,
we can set gameOver to true, and then we can sort of
make this an else if. Because we don’t want to
check apple if that happens, we basically want to change
our flow of our logic from going to check for an apple to just
skipping that altogether, and skipping this– better yet, we can probably just
return out of this, correct? If my logic is right– yeah, I think we can just return. So basically, cut this
function short altogether, not actually worry about
any of this other stuff. We don’t need to do any of this. We need to update our timer or anything. So what we can do– would we want to update the
head to reflect the collision? No, because we’re probably
going to transition right into the game over screen. Although, what we could do to show
that we’ve got a game over is– yeah, we could have it so that we have
our game over text above our grids, so that we can see where we died. Because that way we
can at least see what our whole snake looked like before
the game actually cut short. So I think I want to do it that way. So maybe I don’t want
to return off of this. I just want to set this
gameOver flag to true. I will want to update
this TILE_SNAKE_HEAD, and then I kind of want to wrap this
whole entire thing in a condition. So if not gameOver then– and I’m going to indent
all of this code. So all of this is only going to
execute if we’re not in a gameOver. So if not gameOver– so if it’s not the case
that we are in a game over, which we trigger by this
intersecting with a snake body tile, or head intersecting
with the snake body. Do all of that stuff, else,
if we are in a game over– actually, no. We’re not going to need to
do anything, in that case. Our update’s just not
going to do anything at all when we get into a game over. And what we’re going to do
to break out of a game over is we’re actually going
to add another condition in our love.keypressed function. So I can say if key==enter or
key==return and gameOver then– or rather, we’ll do it this way. If gameOver then if key==
enter or key==return then– and the or equal to
return is a Mac thing. So Macs don’t have an enter
key, theirs is called Return. Or, I think Enter is Shift-Return. But by default, people
are going to hit Return, and on Windows it’s going to be Enter. So you want to mix those two together. I kind of want to initialize everything
again, so I can do initializeGrid, and then I can set snakeTiles– or rather, what it can do is I
can say snakeX, snakeY=1, 1. And then snakeTiles equal to
a table with snakeX, snakeY. So what this basically does
is it initializes our snake. I guess I can take this out, call
a function called initializeSnake. Come down here at the very bottom
to function initializeSnake(). Paste those lines of code in there. So now we have basically just setting
the snakeX and snakeY to 1, 1. And then also creating
the snakeTiles table, which has that first element
with snakeX and snakeY. And then back up at the very top– oh. I guess snakeMoving equals right. I guess I can do that, as well. So snakeMoving=’right’. And then I can initializeSnake()
here, which I don’t need to– I guess it’s kind of superfluous at
this point, but just for consistency. That should work. It’s not necessary. I could just declare all these variables
as local snakeTiles, local snakeX snakeY, local snakeMoving,
but it at least gives us a clearer sense
of what’s going on, when we’re looking at our load function. We could say, oh initializeGrid,
initializeSnake, what does that mean? Go to initializeSnake. It means set the snakeY to 1,
1, set the moving to right, and set the snakeTiles
equal to snakeX, snakeY. And then initializeGrid. Remember, all initializeGrid
does is do a nested loop, where it basically creates a bunch of
empty inner tables for our rows, and then fills those for
each column with a 0. So just an empty tile, while
also generating a new apple somewhere completely random. So that’s all done. So if gameOver, and we
press Enter or Return, we can initializeGrid and
initializeSnake again. And then score should be
initialized back to 0. So we basically want to complete
fresh restart to the game. So that’s all pretty straightforward. The last thing that we should do, and
it looks like we did do already, was– oh, the drawGameOver function. Let’s do that. Function drawGameOver(). And actually, what we’re going to do is
we’re just going to if gameOver() then drawGameOver(). Because what we want to do– actually, rather, sorry,
this goes afterwards. So we’re going to draw
the grid and the score. We’re going to draw the grid
and the score, no matter what. But if it’s the case that we are– sorry, gameOver is not a
function, gameOver is a variable. If it is a gameOver, then we’re
going to draw the game over layout on top of that. And the drawGameOver function is
going to be something as simple as love.graphics.printf(). So there’s a printf function,
not just a print function. We’re going to say game over. We’re going to take a x and y. This is going to be
a little bit strange, this is how the printf function works. We’re going to say at 0, and then the
window height divided by 2 minus 32– actually, what should it be? Minus 64. And then we’re going to specify an
alignment width, which basically says, within this amount of text,
I want you to format it based on the format specifier that
we’re going to say as the last argument. So I’m going to say WINDOW_WIDTH. So it’s going to basically center it
within the window width, starting at 0 all the way until WINDOW_WIDTH. It’s going to center it. And then I want to specify
that it’s centered. So that last parameter, that
last argument to the function is a string that can be
left, right, or center. And so if it’s left, within
the bounds of WINDOW_WIDTH, it will basically just
draw it at that xy. If you say right, it’ll basically
pad on the left side– or rather, on the left side of your string,
it’ll pad it with whitespace until the end of the that WINDOW_WIDTH
variable, the WINDOW_WIDTH size that we specify here. So you’re basically specifying
a left and a right, kind of, with 0 and WINDOW_WIDTH. It’s more like a left, and then a
number of pixels after that size. So basically, within
the size of the window, I want to center this game over text. And then I want to also do the
same thing with Press Enter to Restart at 0 WINDOW_WIDTH. And then I want to do
WINDOW_WIDTH plus– sorry, WINDOW_HEIGHT divided
by 2 plus 64, and then WINDOW– plus 96, actually. And then WINDOW_WIDTH and then ‘center’. And so what that’s going to do is it’s
going to draw this string a little bit below. This is what the y value
here– remember, x is 0, and then y at WINDOW_HEIGHT
divided by 2 plus 96. That’s going to draw the
second string of text a little bit lower than
the gameOver string. Both of these strings are
not going to be large enough, so I want to make a large font. Actually, a really large font. I want to make a huge font. So that’s what we can do here. So a local hugeFont equals
love.graphics.newFont. And we’re going to make
this one 128 pixels. Which is why I did the minus 64 pixels
earlier for drawing it on the y, because we want to shift it
up 1/2 the size of the text, so that it’s perfectly centered
vertically on our window. So I’m going to do that. So hugeFont gets that
size, and then when I draw my gameOver,
which is down here, I want to do love.graphics.setFont
to hugeFont. And then I want love.graphics.setFont
to largeFont here. So two separate sizes. I want the game over to
be really big, and I want the press enter to restart to be big. Like the same size as our
score, but not as big. So I’m going to save. It I’m going to run it. Let’s see if this is working. So remember, this should now– I’m going to get my snake up
to maybe five or six tiles, and then I’m going to try
and intersect with myself. Oh, it worked. So game over. Press Enter to restart. It’s a little bit low, visibly, that
we can see in the game view there, but that’s because my window is a little
bit shifted down, because my monitor resolution is a little bit funky. But this should be approximately
centered in the game view. Now, press Enter. Oh, right. One other important thing. When you press Enter, make sure– where is it? In our key pressed– anybody want to predict what I messed
up here in this condition here? What did I forget to do? I know you guys got this one. This is an easy one. Simple mistake that I made. [? JPGuy ?] says, “Whoops.” [? JPGuy ?] says, “Woohoo.” Yeah, it’s exciting. A little bit of a mess up there. Let’s see if anybody’s got it. Well, the key is that I forgot
to set gameOver back to false. So now, whenever press Enter
to restart, I instantly go into a gameOver equals false. Is there a clear function? love.graphics.clear
will work for that case, but it will just draw everything
again, because in our draw function, we have this loop. We basically have it saying
draw the grid every time. The initializeGrid function is here. So what that should do is just
set the grid equal to emptiness. So let’s try that again. Let’s make sure I’m bug-free, beyond
this in another way, hopefully. So if I move over here– OK. Enter to start. And then, oh– so it actually
didn’t reinitialize our grids. Now we have our old snake there,
which isn’t what we want, ideally. But I can grab that apple
from before, and now it’s going to have two apples
that are constantly joining. So this is a bug. And I can probably keep doing
this over and over again, and I’ll have infinite snakes. And I can keep running
into other snakes. So we’re not clearing our
grid quite appropriately. And why is that? Let me see. initializeGrid. I thought I called initializeGrid. Oh, OK. That’s why. That’s why. So in our initializeGrid
function, what we’re doing is we’re not actually clearing the
grid by setting everything to 0. We’re actually adding new
empty tables to the grid. What we need to do is say
tileGrid equals empty grid first, so that it starts completely fresh. [? Bavick’s ?] got it
right, as we did last time. So let’s try that one more time. I’m going to grab one or apple, and
then try to collide with myself. OK. Boom. OK, so it’s working perfectly. That’s great. So yeah, something as simple as that. So the initializeGrid,
basically, it was– because it does this table.insert
tileGrid and a new empty table. And so that’s just going
to keep adding new tiles. It’s going to keep adding new
empty tiles to the end of the grid, and then for each of the
tables that existed already, it’s going to just add empty tiles
beyond where we can visibly see. And so that’s a bug. But what we’ve done now is we fixed,
so that is no longer an issue. [? JPGuy ?] “Good stuff.” Bella [INAUDIBLE] says, “Great.” Thank you. Yeah, it’s nice seeing it
all sort of come together. OK, so we have a game over. We have the ability to
restart our game from scratch. So now we should think about collision. Or not collision, rather, but
the ability to go backwards– or to go in the opposite
direction of where we’re moving, which causes issues. Because then we’re instantly
colliding with ourselves, and we also get some weird
rendering issues– well, we won’t anymore, because we
made it so that we collide. But we shouldn’t be able
to go backwards, basically. And so this part is
actually pretty easy. If anybody wants to suggest anything
as to how we can go about doing it. I suspect it’s probably somewhere
up here in the update function. So we have the snake
timer itself checking whether we’ve gone past our speed
variable, our speed constant, 0.1. And this is sort of where we
check to see– oh, rather, up here, in our
love.keypressed function. This is where we check the key. And if we’re going left,
we should move left. If we’re going right,
we should move right. If we’re going up, we should move up. If we’re going down,
we should move down. Right? So here, all that it
feels like we need to do, really, is just change the conditions. So if the key is equal to
left, and snakeMoving not– rather, equal to ‘right’. So if we’re moving
right, and we press Left, we shouldn’t be able to
go left, because that will be a reversal of our direction. Same here. And snakeMoving is not equal– whoops. Enable dictation? No, I’ll pass on that. Not equal to ‘left’. So this ~=is a the weird
Lua way of doing not equals. In a lot of languages you’ll see it
like that, the !=, but in Lua, it’s ~=. That’s not equals to. So if key is equal to up and
snakeMoving not equal to down, if key is equal to ‘down’ and
snakeMoving is not equal to ‘up’. So basically, now we’re
saying, if we’re pressing Left, and we’re not moving right–
so if we’re moving up or down, basically– then move left. And same thing for all
the other directions. Basically, don’t let us move
in the opposite direction that we’re already moving, effectively. So if I hit this, and I try to
move left, I actually can’t. But I can move down. I can’t move up, which is nice. But basically, now, no matter what,
I actually can’t collide with myself. So we’re in a state where the snake is
incapable of going backwards, and thus instantly colliding with itself. So that’s kind of the majority
of the Snake features. Does anybody have any questions on
what we’ve talked about thus far? Want me to step through
anything in the code? Anybody have any interesting features
that they think the game is missing? We still have a couple hours left,
so we could do some more stuff. But we can also have some
Q&A and talk about what’s going on here with the code base. We could also make a title
screen before we start. So that’s probably
pretty straightforward. Let’s see. We probably want to have like
a local gameStart equals true. It’ll be like the beginning of the game. What we can do is basically do an if
statement, kind of with the game over. If it’s equal to gameStart, then
just draw, welcome to snake, press Enter to begin, or whatever. And then when we go
from the game over back, we can just go straight to game
start, not straight to the game, just so we can sort of see
what’s going on in advance. Cool. So gameStart is true. By the way, this code
here, I’m realizing now we can we can still change
the direction of our snake, even when we’re in game over. Not that it’ll mean anything or it
will be visible, but this bit of logic here is always executing. So the better style would probably if
not gameOver then do all this stuff. And then we could probably
make an else here, but that’s only in a gameOver state. We maybe don’t want that to happen. If gameOver or gameStart, though. That could work, right? And then in our draw function, we could
do something like if gameStart draw, welcome to snake. “Wouldn’t it be better to include the
gameOver check in the initialize part?” The gameOver check in
the initialize part. I’m sorry, JP, would you explain? [INAUDIBLE] check for
gameOver at runtime. Not exactly. This is kind of a minor
optimization at this point, just because this bit
of code is just going to be constantly checking for
conditions when we’re in a game over. So it kind of makes sense
to make one if statement, and then be able to get out of that,
if it’s the case that we’re in a game over. Because ideally, you
don’t want lots of– I mean, this is a small example,
and not really worth worrying about. For this kind of game it
doesn’t matter at all. But for a large game with
maybe more complicated input, you probably don’t want it registering
while you’re in some state where it’s completely irrelevant. Because in a game over, ideally,
you’re probably doing other things and displaying other things. And if you’re taking
input and using CPU cycles for things that aren’t
germane to the scene at all, and are completely being
wasted, it’s just kind of messy. Kind of unnecessary. But again, for this use case,
it’s a very simple thing. It’s just worth mentioning because
I happened to notice it was there. Good question. Oh, right. And then if gameStart then– else. We can put all this in the else,
because if we’re in gameStart, we don’t need to– these two, this gameOver
and drawGrid rendering stuff doesn’t need to take
place in the game start. Let’s just assume we’re going to have
a black screen that just says, Snake, and then press Enter. We can store a gameOver in a variable. When the game is over, we
change into something else, and check if that variable in game over,
start new window, set initial stuff. Yeah, that’s effectively
what we’re doing. Correct. Yeah, the gameOver variable, we
declare it right up here at the top. gameOver is false. And then we even have a gameStart value. “OOP In Lua.” Yeah, we’ll talk about that, actually. So object-oriented programming
in Lua is a bit weird. By default, it uses these
things called metatables, and the syntax is a little messy. But there is a really nice library
that I use that allows you– and I teach this in
the games course– that allows you to just use very simple
syntax for declaring classes. I think I’ll introduce that
in the concentration stream on Friday, where we make
the memory pair game. And we can maybe make some classes,
like a card class, something like that. But yeah, it’s totally
possible using libraries. It’s actually quite nice. Yeah. OK, so this is where
we’re actually rendering. We’re going to render the game start
screen, so the very beginning, when we start the game. if gameStart render SNAKE at
0 VIRTUAL_HEIGHT 2 minus 64. Because we want it halfway in the
middle of the screen vertically, shifted up half of 128. We’re going to take the padding
width to be the WINDOW_WIDTH, so we’re going to center it within
WINDOW_WIDTH, starting at 0. And then we’re going to make
sure it’s in center mode. And then we’re going to do the same sort
of thing that we did here, actually. Except it’s not going to
be press Enter to restart, it’s just going to be
press Enter to start. And then as before, actually,
love.graphics.setFont– remember, you have to set the font for
whatever drawing operations you want, because Love2D is a state machine, it
doesn’t allow you to draw with a font, I guess. You have to set a font, draw, and
then set a font, draw, et cetera. So if you forget to unset a
color or a font or something, and rendering looks a
little bit messed up, it’s probably because you forgot
to change the state machine to reflect whatever changes you want. So if the game is
started, blah, blah, blah. So that’s working great. And then if gameOver or gameStart. OK, so this is where
it’s actually going to– we could do it this
way. gameStart=false. gameOver and gameStart equals false. Yeah, actually, this
should work perfectly fine. This should work perfectly fine, crash. Oh, I didn’t see what that said. Oh, I’m using VIRTUAL_HEIGHT. Sorry. I’m so used to writing VIRTUAL_HEIGHT
as a constant, because I use that in my games all the time. But what I want is WINDOW_HEIGHT. So we’ll look into VIRTUAL_HEIGHT, as we
get into some other more retro-looking games, because I like
to generally program games to fit to sort of old
retro console aesthetics. Like the Gameboy Advanced
is a really good resolution. 240 by 160, I believe is what it is. And there’s a library called push,
which I use in my games course, which allows you to say, oh, I want my game
window to be rendered at x resolution, not like an actual native resolution. So I could say, fire up a
window that’s 240 by 160 pixels, and it’ll draw it just like
that, and scale everything, and it’ll look like a nice retro
game, pixel perfect, while also being the same size as a full-windowed game. So you really do get that
zoomed-in pixelated look. And right now, all we’re
doing is just squares. So it’s pretty basic. We don’t really need to
worry about that too much. But if we get into like
concentration, where maybe we have pictures of elements,
or maybe we make an RPG or a Super Nintendo looking
game, or NES looking game, or a Gameboy Advanced looking
game, I think we should definitely dive into that a little bit. But we won’t worry about that this time. We’ll take a look at some
more features on Friday. So let’s make sure that I
have everything working. So I put up the game, it says,
SNAKE, Press Enter to Start. I press Enter to start. I’m going. I have my snake. Boom. Boom. Boom. Boom. I can’t crash into myself if I’m
going the opposite direction, which is really nice. But I can collide with
myself just like that. I get into a game over state. I can press Enter, and
now I’m back into it. Just like that. So now we have multiple
what are called game states. And we’ll also take a look,
going into the future, at what are called state machines. And what a state machine can
let us do to sort of break apart our game into a little bit more
modular and abstract components, so that we can say, I
want a title screen state with its own update, its own
render, its own input detection. I want a play state, I want a game
over state, I want a start state. All these different things that sort of
have enclosed blocks of functionality. But for right now, our game is
actually working pretty well. I guess one thing that we could
add would be like static obstacles into our game. So I think Snake, by
default, will have random– they’d be like brown
obstacles, almost like stakes in the ground, or something, where
you can’t actually collide with them. And if you do, it’s game
over, just like the body. “Don’t forget to plug your edX
and GitHub stuff in the chat and in the description.” Good point, JP. In case people don’t catch
the stream when it’s live. That’s a good point. I’ll make sure to edit that. Oh, also a good reminder. I’m going to push to my GitHub. Let’s see, where am I at right now? dev/streams/snake. It should be that. So git status, git commit. Complete snake with
title and start screens. I should’ve been committing
a little bit better. Git push. And I configured my git,
as well, because last week, when I tried to do anything related
to get, it was a little bit funky. Because I have what’s called two-factor
authentication enabled on my account. And that causes issues, if
you’re using git at the CLI. “I added different levels in Snake in
Scratch, in one of that I did stones. If snake hit the stones, game over.” Yeah, so we can do that. We can absolutely do that. Let me just refresh this. So now it’s three commits. So if you go to the– whoops, let’s go back
just to the main.lua. If you go to the repo– it’s this repo. Actually, I don’t think I’m signed in. Let me sign in here really quickly. Am I not signed in here either? “Stuff like that. Obstacles would be nice. Stuff like that. With levels, increased speed.” Oh, the increased speed of the snake. Yes, yes, yes, yes, yes. Correct. So I’m going to pop off
camera just for a second here. I should be signed in here, I think. I apologize if people were
able to hear that audio. 250 followers on the Twitch, as well. That’s awesome. And also, thanks for tuning
in, everybody who’s here now. OK, let me just make sure
that we’re at right spots. OK. All right. So that’s the URL which
will have the GitHub. If you’re curious, if
you want to grab that, get the latest commit, mess
around with it a little bit, that’s got all the stuff
that we’ve looked at today. Let’s go back to the monitor here. And right, obstacles. So currently, we have our title screen,
we have the ability to move around, we have a score. Our background is a little bit boring. So very simply, just using some
code that we’ve already got, which is just our randomizing the– “You can also program a Twitch
bot for the stream live. That would be super meta,
albeit not game related.” Yeah. That would actually be pretty cool. I’m not familiar with
Twitch bot programming, but I’ll definitely look
at, that because that’d be pretty cool, actually. Very interesting. I’m assuming it’s probably
like JavaScript or something, which I am fairly familiar with. But not as much so as
Python and Lua, probably. Yeah. So we have the foundation laid. Let’s say I want to generate stones. Let’s say I want gray blocks
generated randomly in my level, and those are stones. And if we collide with
a stone, then that should trigger a game over, just
like colliding with my body. So where do I have the code
for generating an apple? It’s up here, right? Here’s what we’re going to do. I’m going to take these
two lines of code out here, and I’m going to copy them. I’m going to call a function I haven’t
written yet called generateObstacle, and then it’s going to take a value. So TITLE_APPLE, in this case. And then I’m going to
define some new constants. Well, a new constant, for now. TILE_STONE is 4. I’m going to come down
here at the very bottom, where I have all my initialize stuff. And just above my
initialize stuff, I’m going to say function
generateObstacle(obstacle). And I’m going to paste those two
lines of code that I had before, which is just going to be
setting a couple of variables. So obstacleX and obstacleY. Same here. And then I’m going to set the
value at that actual index in the tileGrid to whatever the value is
passed to me as the parameter obstacle. So now I can use this for anything. I can use this to generate
random apples, or stones, or whatever other tiles I
might design as a designer. So elseif tileGrid y x– by the way, now I’m in
the drawGrid function, so I want to be able to
render this appropriately. TILE_STONE. And this could be just like a– oh, I realized this doesn’t
need to be– change the color to light green for snake it. Had an outdated comma there. This is to be a light gray. And so light gray is kind of like
all the same numbers on the RGB, but just not 1 and not 0. Ideally higher. So we’ll say 0.8. So I’ll say
love.graphics.setColor(0.8, 0.8, 0.8). And then one for full opacity. love.graphics.rectangle. We can just copy this line of code,
actually, and then paste it there. And the thing is this is only going to
generate an obstacle of an apple here. So this should still work. So it’s going to generate an apple. I’m going to pick up the apple. It’s going to generate a new one. That’s fine. Is it generating the
apple up here, as well? Oh, you know what it is? It’s in the initializeGrid, I think. Yeah. So back down in our
initializeGrid function, we can take out those two lines of
code that were kind of the longer ones, and just say initialize– sorry, generateObstacle(TILE_APPLE). Right here, just like that. So now it’s a little bit cleaner. It’s the same logic that we had before. Should just work right off
the gate, which it does. And then I’m going to figure out a place
where I want to initialize some stones. And I can do it here in
initializeGrid, actually. Because we’re only going to one of
initialize those stones every time our grid is initialized from scratch. So I can probably do something
as simple as for i=1, 10 do generateObstacle(TILE_STONE). And if I run this, now I have
10 stone obstacles in my game, but I can still collide
with them, and I actually overwrite them, as a result of that. So we’re kind of in the right spot. We’re generating obstacles, but we still
need to implement collision detection. It’s not quite working yet. So this was in our update
function, I do believe. Yes. Here. So we can basically do and
or statement here and say or tileGrid snakeY snakeX
is equal to TILE_STONE. Then do that. So if I do this, boom. Game over. So we overwrote the stone
there, collided with it, and that’s our game over. So now we have randomly
generated obstacles, and we have rendering and
collision detection for them. And so now we have sort of
this idea of random levels. So pretty neat. Just mess around a little bit. Got to play test. It’s an important part of the game. Make sure we don’t have any bugs
that we haven’t anticipated yet. Let’s try going from the
other side, which looks good. The only thing that I would be conscious
of is this apple is actually capable of overwriting a stone, because
the generateObstacle function doesn’t actually check to see whether
the obstacle that we’re overwriting– that index in the grid– is empty. So I should probably do that next. Just like that. Pretty slick. Come through here. It actually looks pretty nice. I’m not going to lie. Very simple, but effective. I’d be curious to know, with this
code base, how high of a score folks might be able to get. I probably won’t play
for too much longer, but I sort of feel like I’m
owed at least an opportunity to play it just for a couple minutes. Elias says, “Nice move.” Thank you. OK. So we’ll just end it right there. I tried to grab the
apple at the last second. Didn’t work. Got a score of 24. All right. So the last thing we should take
into consideration, like I said, is when we generate an
obstacle, we should probably do this in a while loop. So we probably want to do whatever
our generateObstacle function is, generate those new xy
pairs sort of infinitely, until we get an empty tile in our grid. And then we can we can set it. So let’s just do do– I hardly ever use this. do until the tileGrid obstacleY
obstacleX equal to TILE_EMPTY. So do until. [? Bavick Night ?] says, “I
did levels based on scores. If the score reaches a
number, it will up levels.” That’s a good idea. We could maybe mess around
with that a little bit. Oh yeah, that was another
thing we were going to do. We were going to add
increased speed in the game. So making the snake move a little
bit faster, the more points we get. So we should maybe figure
that out a little bit. That is as simple as just
decreasing our snake speed constant, which would therefore
not make it a constant, it would make it a just
a regular variable. We can mess around with it a little bit. So again, to cover what this syntax
is, on lines 223 here to 225, I’m using what’s called a do until loop. And in C, and most other languages,
rather, it’s called a do while loop. So I’m generating those two random
values, the obstacleX, obstacleY, and I’m getting them
as two random values. But I’m doing it until they’re for
sure an empty value in our table. I don’t want to overwrite any other
tiles that might already exist, whether it’s an apple
or a stone or whatnot. Or if it’s an apple even
overwriting the snake in the map. I don’t want that to happen. That’d be buggy behavior. So I want to go ahead and just do that. So do until tileGrid obstacleY
obstacleX is equal to TILE_EMPTY. And then set that equal
to the obstacle number that we passed into
our obstacle function. So probably won’t– oh, I think
you do need an end after the until. ‘End’ expected to close the ‘do’. Until– I never use do until in Lua. Let me refresh my mind
here a little bit. The syntax– oh, it’s
repeat until, not do until. I’m sorry. OK. Repeat until. There we go. 227, attempt to index a nil value. generateObstacle– is
this taking place before– wait, hold on a second. MAX_TILES_X– into a nil value. repeat local obstacleX, obstacleY. Oh, because they’re
local to here, I think. Yeah, that was it. So I declared obstacleX and
obstacleY as local variables within this repeat block. And so by doing that, basically,
I was erasing these values as soon as it got to this until statement. So remember, there is
a thing called scope in programming languages,
where if you declare something as local to something, anything
outside of it has no access to it. So in order to generate these values,
I have to declare them up here, so that they’re accessible
not only within this block, but also within this condition here,
at the end of the repeat block. So a little bit of a
gotcha just to be aware of. But yeah, there we go. Perfect. Those all clustered up towards the top. That’s interesting. OK. [? Bavick Night ?] says, “Gave
some lives to the snake, initially. With a game over, it decreases, so
players don’t have to play forever.” Yeah, that possibly could work. [? Tmarg ?] says, “Scoping
in Lua seems weird, too.” It is weird. It is really weird. Because a global variable is just
any variable that you say like this, obstacleX=1. Except in this case, because I
declared this is local already– but let’s say I have some
value called someFoo=1. someFoo is going to be accessible
anywhere in the whole project. It’s a it’s a little bit of a black
sheep, in terms of programming languages, in a lot of ways. This being one of them,
because a lot of languages don’t give you this
sort of global scope. Like in Python, for example,
you declare variables without some sort of specifier. You actually have to declare
global as a keyword in Python for it to have the
same kind of behavior. But in Lua, just declaring
a variable like this, in any function or any block,
makes it available everywhere throughout your entire application. And that can be a source– like [? Tmarg ?] is saying,
“I had lots of trouble with it in the Mario assignment”–
that can cause a lot of issues. So it’s best practice to definitely
keep your local variables isolated as much as you can. Even at the top of my
module up here, I have a lot of these global constant values. But all of my actual
gameplay values are just being declared as
local, even though they are functioning as the global
variables for this module. But at least this way, if I have some
other file that imports this main.lua, it’s not going to add these symbols
to the scope of that project. It would be a difficult
thing to debug, potentially. [? Bavick Night ?] says, “If you want
to take a look, it was years back, doesn’t grow dynamically.” Sure, why don’t we do that? Let’s go and take a look at
[? Bavick Night’s ?] Twitch project. If I can open it here. Should still be silent, hopefully. [? Bavick ?] [INAUDIBLE] in
on the chat one more time, so I can see it in the live
chat on my window here. Or if anybody minds copying
and pasting the link into the– lorem ipsum nonsense data. It’s scratch. Can you paste the link again in the
chat, just so I can see it live? I think I can scrub back, but just
offhand, it’s probably faster to– there we go. No, I just wanted the URL, [? Bavick. ?] All right, let’s do it. Is there is there sound,
[? Bavick? ?] Should I enable sound? Oh, there we go. Yeah. “We could start with three to
four stones, and then each level, increase the number of stones,
but limit that number.” Yeah. Absolutely. That would be an example of
easing your player into it. Just a game design decision. All right. I have sound enabled. All right. Let’s test this out here. All right. Welcome to Snake World. Press Spacebar to start, use
the arrow keys play, good luck. Oh, boy. Oh, you have continuous
snake, that’s why. Yeah, it’s a lot harder to do
it in a continuous fashion, because you have to have a
bunch of basically squares that are chained together, rather
than having a grid that we used. So we used the grid for that purpose. Yeah, very good. And I’m guessing you get to a certain
point, and then you increase the level. Hey, I mean, you have the
basic– oh, there we go. Different level. OK, I see. I see. All right. Oh, and it’s faster. OK, so you have a lot
of the mechanics there. Sort of the difficulty
increasing in some fashion. I can’t actually hear if there’s
if there’s music, currently. Oh, you know why? I screwed up. OK, I know what it was. It’s because I have that on. Sorry. Let me turn my monitor on, really quick. OK, that’s what that is. Oh, boy. It took my input out of the– I think because I close the
Twitch tab, it took my input out of the Scratch window here. But it’s very good. No, good job, [? Bavick, ?] on that. I would say, I don’t blame you too
badly for not having the growing functionality, because it’s harder to
do with a continuously moving square that’s not axis-aligned– or rather, it’s not discretely
aligned within your grid. Because collision detection is then– you have to do what’s called
axis-aligned bounding box detection, which we cover in the G50 course. But it’s more work. It’s not super easy,
so I don’t blame you. But no, good job. Good job on that. I’m curious, I don’t remember
offhand Scratch actually lets you do collision detection. I think it does. It just has collision
detection built in, so you can just kind of move it around. So you just have a list– yeah. “Enabling sound might
cause copyright issues.” Oh, [? JPGuy ?] good point. Crap. I didn’t think about that. OK, hopefully not. Worst case, YouTube will
just quiet that part out. I might be able to silence it
myself, when I cut the video and push it to YouTube. Which, it will go to
YouTube, by the way. “There’s sound–” Blah, blah, blah. Rip. Yep, Rip. “Have fun. It was. I tried, but I didn’t have an idea. It’s where I started coding.” Yeah. No, I totally understand. It’s not an easy problem to solve
by any stretch of the imagination. So no worries there. All right. Well, we could do
something similar to that. We could start with the difficulty
thing, which Bella [INAUDIBLE] provided, as well, as a suggestion. “Twitch might silence it automatically. That happens sometimes, but you
won’t get flagged or anything.” Yeah. Yeah, hopefully nothing. It should be nothing serious. Yeah. OK. So if we’re going to do this
difficulty thing, what we can do– I think we’ll just start off
with difficulty=1, maybe. Or level=1. And then what we can do is basically say
for i=1 until level times 2, maybe? And then maybe set the
speed equal to 0.1 minus– whoops. We’ll do it here. And then SNAKE_SPEED=
0.1 minus level times– let’s see. What do we want it to do? 01? Will that work? No. So math.min. So at the very– no, rather, math.max. between 0.01 and 0.1
minus level times 0.01. So what this is going to do– “Make it go exponentially.” Yeah, that’ll end up in
disaster really quick. No, we’ll do a linear function on that
for now, I think, just to keep it sane. What I’m doing now
with SNAKE_SPEED was I used this function
called math.max, which returns the greater of two values. So basically what I’m doing
is I am taking either 0.01, being the lower bound– so the fastest our speed will
be 0.01, which is really fast. And I’m doing 0.1
minus level times 0.01. So 0.11 minus level times 0.01. So this is going to take the
greater of these two values. So as level gets higher,
this value will get higher. So it’ll be 1 times 0.01,
2 times 0.01, 3 times 0.01. Which will effectively
be 0.01, 0.02, 0.03. And it will subtract
from this value, 0.11, until it gets to be the value of
0.1, in which case math.max is going to see that 0.01 is actually
greater than this value, and will always return 0.1,
no matter what level we’re on. So that’s how we can sort of cap
the lower bound on our speed. And we can do the same thing at
the very bottom, where we have– let’s see, where is it at? Here, where we have
level times 2, we don’t want this to keep increasing
infinitely, because eventually we’ll have so many stones that we won’t
be able to actually function. So let’s say the most
we’re going to have is– what’s a good number–
maybe 50, and level times 2. So math.min is the opposite
of math.max, and will return the lower of two values. So it’ll start off at
level times 2 being 2. And as level increases,
we’ll eventually get to a higher and higher point,
at which time we will exceed 50, if someone’s good enough. And once we have exceeded 50 stones– 50 will be the lower value of
this, and this function, math.min, will always return 50. And so this is how we can clamp
our value to be a certain amount. And to actually get
to the next level, we want to check that we’ve eaten
a certain number of apples. Let me see. Where is it at? This part right here. So in our update function,
when we check to see that we are eating an
apple, what we want to do is increase our score,
as we did usually. And then it’s here that we want to say
if score is greater than some amount, let’s say level times 2– or level times 4. Should it be level times 4? Level times 3. If it’s greater than level times 3. And we’re going to math.min 30 in that. So we’ll always be
looking for at least– no, that won’t work. Because score is not going to get
reset to 0, so this won’t work. Oh, I guess that actually will. Yeah. We’ll do level times 3. That’s easier for now. Our score is cumulative, so– we can make this an
exponential function. So we can say this. I think this will work. So we’ll say score is greater than
level times half of level times 3. So it’s kind of exponential. So it’s going to be 0.5 times 3. In this case, 1 times math.max
of 1 and level divided by 2. So in this case, we want to
make sure that it’s at least 1. Because level divided
by 2 on the first one is going to be 0.5, which won’t work. That will be 0.5 times 3, which’ll
be 1.5, which will be a number. No, it’ll work, but it’s not as clean. So we’re going to basically say level
times math.max of 1 level divided by 2, which will get bigger and
bigger, but at slightly less of an increased rate than a
purely exponential function. And then we’ll multiply that times 3. Just as some value. Oh, math.ceiling. Correct. Yeah. We’ll do that. We’ll do that, math.ceiling
Good suggestion, [? Bavick. ?] Math.ceiling level divided by 2. I think we even talked
about that last week. Perfect. So now that will never
be a fractional value. So if it is greater than that value,
we’re going to increment the level. We’re going to then
initialize the level. I wonder if it’d be worth having
like a press Spacebar to start thing, just like [? Bavick ?] had,
or we could just jump into it. But I think the level transition,
if they’re not ready for it, will be a little jarring. So I think it makes sense to
have like a screen that says, oh, you’re starting level x. Press Spacebar to start the level. And then if they press
it, then it’ll start. So I think that’s what we want to do. In that case– do I
also want to do this? One thing that I noticed that before
we had was that when we had game over, we were actually moving the snake into
the next obstacle, or whatever it was, and it was kind of making it hard
to see what we collided with. We couldn’t see that we
collided with the stone. So I can say if not gameOver then do
all this stuff, where we change the– sorry. If it’s not a game over, then we can
go ahead and move our head forward, make the body the last tile. And then if it is a game
over, what’s going to happen is it’ll stop before
it gets to this point, and we won’t overlap that obstacle. It’ll just make it a little bit
easier to see what’s going on. [? Metal ?] [? Eagle ?] says, “CS50TV,
what language are you using for this? I apologize if you answered it already. Just came in the stream.” No problem, thanks for joining us. This is Lua, and we’re using
a framework called Love2D. So you can go to love2d.org,
which this isn’t the correct page. Love2d.org, which will give
you the list of installers. Were using version 11.1 here. If you’re running Windows,
Mac, or a Linux machine, there’s different distributions here,
and other versions as well over here. They have a great
source of documentation. At the very bottom-right,
you can click love. You can see a few simple
examples here on that main page. And if you have a GitHub account– or even if you don’t
have a GitHub account, you can download the
repo for today’s code at GitHub.com/coltonoscopy/snake50. So thanks for popping in. OK, so if it’s not a game
over, what we want to do is– yeah, don’t overwrite the next tile. I want to be able to see that I collided
with the stone, if that’s the case. No problem, [? Metal ?] [? Eagle. ?]
Thanks again for joining us. Let me know if you have
any more questions. OK. So we did that. So that’ll fix that bug. So actually, I could
possibly run this now. Except not. Because level is– it’s up here. Local level is 1. On line 130 we’re setting the
level equal to level plus 1. Oh, I didn’t put it then statement. Got to do that. That’s important. So let’s make sure that
this is working now. Can actually collide with that stone? Boom. Perfect. So now we collide with the stone, and
it doesn’t actually overwrite the stone. We can see it when we collide with it,
which is just a little bit cleaner. We can visually see what’s going
on a little bit better that way. Oh, and notice, by the way, we only got
two obstacles, instead of the bajillion obstacles that we had before. So this is great. And the actual code to
trigger for the next level isn’t executing yet, because we
haven’t actually tested for that. Or, we’ve tested for it,
but we haven’t actually– like we’re incrementing the
level, that we’re not actually initializing the grid to anything
new or doing anything else, so we should probably do that. “And display the level.” Yes, good point. We can do that up here, actually. So if I go to love.draw, and then
drawGrid, and then print the score. Where I have print the score,
I’m actually going to copy that. Do this. I’m going to print the level. And I’m going to do
love.graphics.printf. And I’m going to set this
to 0, 10 pixels, VIRTUAL– sorry, WINDOW_WIDTH. And then I’m going to
set this to ‘right’. This is now right-justified,
it’s not center-justified. And this is going to help us
out by right padding it for us, so we don’t have to worry about it. So now, we have level equals 0
right there, which is perfect. And it’s right on the right edge,
so doesn’t quite work as well. So I’m going to set window with minus– [? We do ?] minus 5? Minus 10? Or, no. I’ll just set this to
negative 10 at WINDOW_WIDTH, and that should have the effect. Yeah, perfect. So set the start value of negative 10. So we’re shifting the amount that
we’re centering, or right-aligning, by negative 10. And we’re still keeping
WINDOW_WIDTH, so it’s basically just shifting this right-aligned
label to the left just a hair. So that way it aligns
better with the 10, 10, that we have up here
with our other score. It’s 10 pixels from the left edge. [INAUDIBLE] says, “Slick hair, bro.” Thanks, bro. I appreciate it. All right. So we have our level. And we actually see that
the level is incrementing, if I did everything appropriately. Although level is 0, for
some reason, which I’m not– why is level 0? Oh, it’s because it’s
the same value as score. Right. Let’s not do that. Let’s tostring(level). And now, level 1. OK, perfect. Try to get a few apples,
see if it increases. Perfect. So when we got to score 4,
it did increase to level 2. I was going to say,
there’s a case to be made for just increasing the
level while you’re playing, and adding more obstacles in the game. But that could potentially be
a big source of frustration, if like an obstacle generated right in
front of your snake as you’re moving. So probably not the
best design decision. You definitely don’t want to
frustrate your player base. But we’re on level 3 now, which is nice. Snake is looking good. Level 4. OK, so it’s working. I didn’t check to see whether speed was
updating, which I don’t think it is. Oh, right, because
we’re not updating it. Yeah, we’re not updating it. So we can do that. We’re going to do that. All right. So a level, increase by 1. Speed, make sure to
set that appropriately. So remember, it’s a function of our
level, whatever our current level is. We have level incrementing,
we have the speed adjusting. We need to have the screen that
shows us what level we’re on, and allow us to press Space
to actually start the game. So we’re going to need a new
variable for that, probably. And what I generally like to do
is kind of have a good game state variable, that will sort of
keep track of it as a string. “I pause for three to five
seconds on the level increase, and then they know it’s
going to get tough.” Yeah. Yeah, that could work, too. Let me see. So gameStart, local newLevel=true. We’re going to have a
newLevel variable, which means that we’re going to basically
let them press Spacebar to continue. if newLevel then– we’re
going to check for Space here. So if key==’space’ then– we’ll say newLevel equals false. And we’ll set to true by
default, which we did. [? Tmarg ?] says, “With
obstacles, you still have to make sure they
generate in a fair way.” Yeah. So part of that algorithm would be– I guess there’s a few ways you
could you could look at it. I guess what I would probably do
is, since the character is going to always start in the
top-left, I would make sure that the algorithm makes the
obstacle spawn beyond maybe five tiles in every direction, as by just
comparing whether the x or the y is greater than or equal to 5. But yeah, there are situations where
it could potentially create like a wall completely in front of
the character, which would be a little bit trickier to solve. A wall, I guess, kind of in the
direction the character is going. But thankfully, since you can
kind of wrap around with Snake, it’s not as big of a deal. We can maybe look at that. We’ll run it a few times, and see
if that’s an issue that we run into. But that’s a good to anticipate
before you release the game, just because you can have
some scenarios where you just get really screwed over by obstacles. Yeah. “Just like you said,
you could conceivably make it so they spawn away
from the player or something.” Yeah, exactly. “Did we check that apples
and stones don’t overlap?” Yes, because now in our
generateObstacle function, remember, we have this repeat block. It’ll basically ensure that anytime an
obstacle is generated– and remember, obstacle is both our
apples and our stones– if this function is called with
any obstacle as an argument, it will make sure that
it’s empty always. Because this rule repeat
obstacleX, obstacleY against two random values until that
obstacleY and obstacleX in our tileGrid is equal to tileEmpty. So by virtue of that
logic, we’ll never have an overlap in our obstacle generation. Good question. OK, so the Start screen. So if we’re at a new level, we’re
going to wait for a Space press to get to a new level. [? Bavick ?] says, “Cool.” Our drawing mode is
right here, gameStart. What we want also is newLevel, right? So else if newLevel then
love.graphics.setFont. We’ll just make them both
large fonts, in this case. love.graphics.printf. We’re going to say– actually, no. We do want huge font. There’s going to be a level,
and then two string level. 0 WINDOW_HEIGHT divided by 2
minus 64 WINDOW_WIDTH ‘center’. And then love.graphics.setFont. OK, and then we’ll just
do press Enter to start. And then we have to set it to largeFont. OK, so this should start– yep, level 1. So press Enter to start. But we can’t see the level in advance. So what I probably want
to do is basically in here is where I want this, actually. So if we’re at a new level– where we’ve drawn the grid
already, so we can then draw the level text and the
press Enter to start label. And then if get a game over,
we’ll draw game over, instead. So both of those will
occur on top of the grid, with the score and the
level currently displayed on top of the left and the right. Actually, we don’t want that we
don’t want the score and the level, I think, drawn up top. Do we? I guess it doesn’t matter that much, but
we could take that out if we wanted to. OK. So that should be able
to then draw the– if not gameOver and not
newLevel then blah, blah, blah. So remember, we need to make sure
to check for not newLevel, as well. Because we don’t want it to update. We want the world to update if
we’re in the new level screen. Let’s see if this works. So level 1. For some reason the snake’s not drawing. OK. Press Enter. Oh, right. Because it’s Spacebar, not Enter. But why is the– it’s interesting, the snake actually
didn’t get set in the initializeGrid. Oh, because we didn’t call
initializeSnake in the– or we did. Oh, no. What we did is we didn’t do this. We didn’t do this here. That’s very important. That should be part of the
initializeSnake function actually, probably. And also, it’s not Enter to start. It should be Space to start, probably. Let’s see if the level transition works. I’m not sure if it does, just yet. Nope, it doesn’t work yet. OK. So that’s OK. So I’m going to update that label. Because it says press Enter to start. Should be press Space
to start, for this part. Press Space to start. So now, that is correct. Not Enter. And then what we can do
next is determine whether we are in a new level, which is this here. So we increase the level,
decrease the SNAKE_SPEED, but we’d actually do newLevel=true. And if new level equals true, then
I believe we should just return So we’re moving around. OK. Level 2, press Space to start. The tricky part about this is that it’s
not actually displaying the next level. So that’s kind of what we want. We’re going to press Space to start. And oh, it actually kept the exact same
level, and it didn’t spawn an apple. OK, that’s a bug. Oh, is it because we didn’t generate
Obstacle(TILE_APPLE) here, probably? newLevel is true. Oh, I guess we want to
generate that here then. OK. newLevel is true. initializeGrid, initializeSnake. And then let’s just bring this line of
code to the initializeSnake function, because it belongs in there. All right. Let’s see if that works now. We’re going to initialize
the grid from scratch, as soon as we get to the next level. OK. Level 2, and now we have four obstacles. So we’re on the way. We press Space, and then we go. And I do feel the snake is actually
moving a little bit faster now. OK, so my math doesn’t work out here. OK. So our score’s at 7. We went straight to
level 3, which is fine. But I think I need to tweak my
algorithm a little bit for the score. That’s OK. It’s a little bit better this time. Let’s try this out. There we go. It seems to be working pretty well. We have obstacles. The first two levels, the math
there is a little bit screwy. So that’s OK. But this all works pretty well. We have levels, we have the speed
going up, we have obstacles generating. They look like they’re getting
higher, getting more obstacles. So this is working out pretty well. The one thing that I do recognize
about the game, which kind of sucks, is that there’s no sound at all. So I think it’d be kind of cool
to dive into sound a little bit. Before we do, does
anybody have any questions or want to talk about
anything on the stream? Anything that we have done? And I’m going to commit
this, by the way. Snake working with levels. So if anybody wants to grab
the code, the zip, or clone it, you can get it at the GitHub. So once again,
GitHub.com/coltonoscopy/snake50. There should now be
four commits on there. The most recent commit has all
of the stuff that we just added. It’s actually getting pretty long. It’s like 200, almost 300 lines of code. Granted, a lot of this
could be cleaned up a bit. We’re doing this live, we’re not really
taking a super hardcore engineering approach to this, as our first stream. Later seems will be a little
bit better about this, but this is a little bit more haphazard. Kind of do as we go, and
not think about it as much. But it’s coming along really well. So yeah, I think the next
step that I’d like to do is get a little bit of sound going. And so I think probably
the first thing I would do is add a sound effect
for eating an apple. So whenever we have an apple, just
play a little blip, or something. So one of the programs that I like
to use a lot for this is called Bfxr. And I think it’s called the same
thing, or Cfxr on a Windows machine. That’s not correct. It’s a free sound-generating program. I’ll try to pull it up here. Bfxr. Bfxr.net. So you can download
it for Windows or Mac. Apologies if you’re on a Linux machine. Sfxr might have a Linux build, maybe. Yeah, they have Linux
built for the Sfxr one. “Want to give some lives to
players, and on game over it decreases, and on no lives, it ends. And it would be like real snake games.” Yeah. We could definitely do
something like that, too. Sorry, I had something in my eye. A little bit of a life-based
approach, that they don’t die and lose all their progress right off the gate. And you could even have
something like where if they pick up enough
apples or something, they actually increase
their life counter. That’s something we
could totally do as well. But for now– let’s
see, what time is it? It’s 5:06. We have a little bit less than an hour. We have some more stuff we
can mess around with here. Let’s go ahead to Bfxr. If you’re looking at, it I’m just
going to generate a few sounds. It might be loud. OK. That didn’t sound quite good enough. Notice that there’s a bunch
of little buttons up here. So you can generate different categories
of sounds, like pickups, lasers, power ups. Got some weird sound effects here. That wasn’t bad. Kind of like a Mario coin, almost. I’m going to export a wav. Going to go to where I have it saved. dev/streams/snake and then
we’ll just call this apple.wav. So wav files are generally
what a lot of sound editors will export as their first sound. “Yes, it was there as well, like
one ups, now we’re going retro.” Yes. Yes, this is the good stuff. “I just did Mario sounds and scratch. Totally.” Yeah, Mario sounds are what’s up. I love Mario sounds. Doing sounds in Love2D is
actually really, really easy. So we can go up here,
where I have my fonts. Now, normally, I would have a separate
file that has all of my fonts, all my graphics, all my
sounds, all that stuff, in sort of like a dependencies
or a resources file. But for right now, we’re just
going to declare them up here. So local appleSound=
love.audio.newSource and I’m going to call it apple.wav. And I’m going to go over to where
I pick up the apple, because that’s the actual sound object. So now we can hit play, pause, and
all that sort of thing on that object. You can even loop it,
which wouldn’t be very good for that kind of sound effect. But you would want it for something
like a music track, which maybe we can add a music track, as well. Got something in my eye, again. Ouch. Where we find the apple right here. So increase the score
and generate a new apple, we’re also going to play a sound effect. So I’m going to go to appleSound:play. Colon operator is kind of like an
object-oriented oriented operator. It basically calls some
function with the self value plugged in as the first parameter. And that’s kind of the way that Lua
does is object-oriented programming. And a lot of these Love2D classes and
objects work with this colon operator. We haven’t implemented
any of our own classes, but we’ll see this in the
future with future streams. Possibly even on Friday,
when we do concentration. But for now, it’s sufficed
to say that in order to use these sound
objects play function, I could use this colon, not a period. Make sure you use a colon. And so once I hit Run– string expected got no value. Oh, sorry. You need a second value on
your appleSound, in this case. And it needs to be a string
that specifies whether we’re using static or streaming. Everybody, gives a shout out to
David J [? Malin ?] in the stream there, everybody. Trolling. [INAUDIBLE] “Is this live?” Yes. Yes, this is live. May or may not possibly be the real
David J [? Malin. ?] Who knows? Actually, I think it is. He’s messaging me right now. “This [INAUDIBLE] everybody’s
giving me a shoutout here.” OK, awesome. Thanks for joining us,
[? Hard Denmark. ?] Yeah, [? Bavick Night, ?] yeah,
[? professor ?] is here. Everybody throw some kappas in the
stream for David J [? Malin. ?] Throw a few kappas in there. Right. So the thing we’re missing from
the appleSound function call was the static string. So you need some string
value in order to– there we go, there we go. [? Cosmin HM ?] joined in, as well. Basically, what static
tells this audio source object is, am I going to store this
in memory or am I going to stream it from disk? If you store it in memory, it’s going
to obviously take up more memory, but it’s going to be faster access. For something like a music track or for
a lot of music tracks, that you don’t necessarily need right off
the gate, you can declare them as, I believe it’s streaming
or something similar to that. But static is a string that will allow
it to be stored in memory permanently, so your game has instant access to it. So I do that, and now we
have snake running again. It’s no longer broken. If I hit Space to start here, and I
pick up the apple, if all is going well, it works. Perfect. So that’s audio. That’s how to make sound
effects in your game. I suppose maybe we could add a victory
sound when we go to another level. So I’m an open up Bfxr again, and maybe
I’ll mess around with some of the power up sound effects. Probably not that one. That one’s pretty good. We’ll use that one. We’ll export that as newlevel.wav. And then just like we
did with this other one, I can say newlevelSound
is love.audio.newSource. newlevel.wav, we’ll make
this one static, as well. And these wav files, because they’re
sound effects, they’ll be pretty small. So declaring them as
static isn’t a big deal. But again, larger, longer
audio sources, probably want to declare them as streaming. Let’s confirm in the Love– this is a good chance to look
at the Love wiki, by the way. We can go to love.audio. love.audio.newSource right
here, and then the type. Yep. Source type streaming or static. Its stream. Oh, and this is an interesting feature. I’m actually not aware of this. There’s a new queue function. Audio must be manually queued
by the user with source queue since version 11. OK, I’ll have to take a look
at what that actually means, and if that’s any use. But for right now, the two main ones
that I’ve historically done with Love2D are static and stream. So again, smaller versus larger. Or even if you have multiple
levels in your game, and you don’t necessarily need all
the sound effects or all of the music for that sound effect
loaded up right away, you can declare them as streaming. Or you can just
dynamically unload and load the objects, if you have just
a smaller, finite set of them. OK, so we have our
newlevel sound effect. So wherever we declared that we reach
a newLevel, which was in our update function, should be right here. So this is if the score
is greater than level. And we used our math.ceil
level divided by 2 times 3, our pseudo-exponential function. I’m going to go ahead and
do what we did before. newsound:play, which will play that
newlevel sound whenever we do reach that score threshold and
we go to a new level. We’ll try it out here. Whoops. And again, I can’t move
backwards anymore, which is good. That’s behavior that we want. But I sort of instinctively
want to do that. So there we go. As soon as we cleared the level, we
played not only the apple sound effect, but also the newlevel sound effect. So we have this more robust sort of
sensory feedback system in our game. A little bit more polished. I’m going to go ahead and add
everything to the project, commit it as sound effects
for game, for snake. I’m going to push that. So now, if you clone
that or download that, you’ll see those new
sound effects in the repo. And when you run it, you should
be able to play them, as a result. OK. One other feature that I
really like to add to games, typically, is a music
track, something like that. I think that might be
another nice feature to add. If anybody has any questions
of what we’ve done thus far, definitely throw them in the chat. I’ll be looking back and forth. For now, I think I’ll pull up a music
track from one of the other course examples that I did, just
because I know it’s a free song. This is a Unity project, actually. Resources/Sounds– I don’t remember. Oh, I think I had it in Music here. Let me just make sure. Is it too loud? That’s not bad. We’ll use that. That was a free music track I pulled
off of, I believe, freesound.org. Which a lot of great
material there, by the way. I don’t know if I pubbed them last time. Freesound.org, lots
of free audio samples. You can go there. You do need an account, I
believe, to download them. But this is great for
prototyping game stuff. I use it all the time. And also opengameart.org is a great
site to download free sprites, and other resources and art. We’ll use this in the future,
when we make other games. I might even see if they
have like cards that we can use for concentration on
Friday, because that will give us a chance to actually work with sprites. And sprites are a little bit more
nuanced to deal with than shapes. And you have to split them
up and what are called quads. Especially if like this
picture, for example. If your images are actually stored
grouped together on one image, you want to split it up into squares. But more on that on the next stream. For now, I’m going to take
that music file that I copied. I’m going to go into my repo, and
we’ll call this just music.mp3. It works the exact same way
as the other sound effects do. So local musicSound=
love.audio.newSource music.mp3, static, as well. And then the thing about
music is it’s a little bit different, because we want it
running the whole time that we’re playing the game. So I can do something like– what did I call it? I called it musicSound. [? musicSound ?] setLooping to true. And musicSound:play. And now, if I start the
game, we have music. And you can still hear the
other sound effect on top of it. I think they’re in key. They sound like they’re
in tune with each other. But yeah. That’s how you get music in your game. So now, we have a pretty
layered little Snake demo there. We have all the major pieces. We have the sound effects for actually
picking up apples, which is important. We have the music. So now we have sort of everything
that most games would have, with a few exceptions. We’re missing, for example,
persistence of high score. So that’s something that we
can look at in the future, when we look at save data. So saving your high
score to some text file, so that you can remember it later. I think one of the features we were
going to look at was having lives. Yes, exactly. Like [? Bavick ?] says,
“Are we going to do lives?” Yeah, we can take a look at that. We have about 40 minutes
left, so we might as well. Bella [INAUDIBLE] says,
“Awesome, thank you.” So let’s think about that. So we can have lives. We’ll keep you here with the gameOver,
and the other state variables that we’re using to keep
track of what state we’re in. So local lives. Let’s say we get three
lives, by default. And just to test our view
out a little bit here, let’s draw that in the top-center right here. So love.graphics.setColor(1, 1, 1, 1). We’re going to keep this. I’m going to just add another string
similar to this one, the level one. And I’m going to make this lives. By the way, if we didn’t
talk about this before, this dot dot is how you add strings
together in Lua, the concatenation operator. So Lives: space dot dot
tostring(lives), because you can’t concatenate a string
and a number, in case we missed looking over that detail. But I’m going to set the first
element to 0, 10 off the y– so it’s a little bit below
the top of the screen– WINDOW_WIDTH, and then center. So we’re going to center the string. It’s going to be the
very top-middle, it’s not going to be at the left or the
right side, like we did before. So let’s go and run this. Perfect. So we have lives 3 at the
top-middle of the screen. And that’s it. So we don’t really
have anything much else to show for that, because
we haven’t actually implemented the logic for losing
lives, which you should do. Let’s take a look at
how we would do that. So normally, if we detect that we
collided with the snake body or stone, we just set gameOver to true. But what we can do instead is we
can say if lives greater than 2– or rather, we’ll set
lives=lives minus 1. if lives greater than
0, what we want to do is we want to start them
off again at the beginning. So what we can do is we can say
newLevel=true else gameOver=true. And now, I believe
this might work as is. OK, so we wrote over
that stone, so there’s a little bit of a flaw in our
rendering logic for that piece. Which we can add another if statement
at the bottom of that update function to take care of that. But it still says we’re at a level 1. If you press Space– oh, it doesn’t restart us, actually. So that’s another thing
we probably want to do. Oh, that’s going to be tricky. Well, trickier, rather. Oh, no it’s not, because you
don’t have to retain the snake. We just have to start the snake fresh. OK, that’s easy enough. So what we can do is we can set newLevel
level to true, initializeSnake(). And so what that should do– oh, but we’re not
deleting our old snake. So actually, there’s
a bit of a bug here. But we were able to overwrite
the old snake head though. OK. “If lives is 0, then game over.” Yeah, it’s effectively
what this logic is doing. So if lives are greater than 0,
we’re going to set newLevel to true, so that where we get that pop up again. And then we’re going to
initialize our snake again, so that it starts at the top-left. But we do have to make
sure that we don’t– if not gameOver and not newLevel– because this was basically
writing the snake to the grid. We don’t want to write the snake to
the grid, because we’re not erasing it, and we also don’t want to go into
the stone if we collide with it. We want to see the stone and the
snake head kind of touch each other, so we’re aware of just what exactly
happened when we lost our life. So I’m going to go ahead and run this. Space to start. Boom. So once we press Space to start
again– oh, it’s still there. OK. So it is overwriting the
grid at that location. So let’s figure out
why that is, exactly. Why it’s not deleting itself. Do a little old-fashioned debugging. [? Bavick ?] says, “Yes, saw it.” Oh, because we’re returning here. Are we? No, this is only if we
actually get to the next level. Oh, because we’re not
initializing the grid? “New level, snake
clear, draw new snake.” Yep, that is the logic. “New level.” We don’t want to make a
new level, necessarily. So the thing is we’re keeping the
old level, so that we can retry it. We’re not generating a
new one from scratch. We could do that would. That would probably be
a little bit easier. But I think it feels appropriate
to keep the level as it is, and then just have the player using the
snake to just start from the top-left again, and just reenact
the existing level. So they feel like they have gone through
some sort of discrete progression of levels that exists, rather than
just constantly refreshing and making level 1 be ephemeral, I guess. OK. It’s in our rendering code somewhere. So we are writing to– if not gameOver and not
newLevel initializeSnake, right? Which is what we’re doing. There’s a subtle bug in here somewhere. Just need to figure out where it is. It’s this line of code
that actually writes the– oh, wait. We had a prior value. Oh, yes. In that case, it’s the
prior value, not the– first of all, let’s make sure that
this is running with multiple segments. So not just the head, but multiple. OK, so it’s the entire snake. OK. So if we do get a new level,
we need to actually clear out all of the snake elements. We need to clear all of the snake
elements, and then finish the level up. OK. So what we need to do then is
some function called clearSnake, and then initialize the snake. So currently, we have all of our snake
elements, they exist in the grid, and then move and
collide with something. All of those grid indices, because
we’re not refreshing the grid, are still going to have snake body,
snake head elements still on them. So let’s create a new
function called clearSnake. And 4– oh, this is
a great chance for us to examine how to iterate
over a table in Lua. So this is perfect. So for k, elem in pairs(snake)– or rather, snakeTiles do tileGrid
elem2 elem1 equal to TILE_EMPTY. And so what that’s going to do– the only issue with that is that we are
inserting a head element into the table before we actually clear it. So what we’re going to need
to do is basically ignore the first element in the snake. First, let’s make sure
that theory is correct. So I’m going to run this. If my theory is correct, it
will erase the rock when we– oh, did I call clearSnake? I did, right? When writing a function, always
make sure you call it, as well. Yep. OK, perfect. So if I do this– by the way, that’s a torturous
location for an apple. Yep. So it got rid of the obstacle. Not what we want, right? Because remember, it pushes
the head onto the snake before it actually does
the rendering for it, because it wants to check to see if
the next element’s an apple before it does that. So what we can do is clear the snake. There’s a couple of
ways we could do this. What I’m going to do is I’m going
to ignore the first element. So if k is greater than 1. So here’s basically iteration in Lua. It’s similar to iterators
in other languages, but basically, it takes
every key value pair that this function
called pairs returns you. So for every k element,
so every key value– in this case, I’m calling k and elem– in pairs, every key value pair
on the table snakeTiles do– and basically, if k is greater than 1– so if it’s not the first element, so
anything beyond the head element– set elem2 and elem1 in
our tileGrid index– so the y and the x tile
grid at that snake unit– set that to empty. So if I’m correct, and I get
[? up size ?] two, and then collide with this, it
got rid of the snake, but it didn’t get rid of the obstacle,
which is exactly the behavior that we were looking at. And it did decrement lives to 2. So let’s try it again. I’m going to go here, I’m
going to collide with this. Boom. And then I have one life left. Game over. Oh, awesome. And then there’s the game over screen. I can’t hit Spacebar,
but I can’t hit Enter. And unfortunately, we neglected to
set our lives to 3, so that’s a bug, as well. But lives are working
seemingly correctly. Let’s go ahead and fix that last issue. Let’s make sure when we do get
a game over after a collision– which is going to be here– gameOver is true, lives=3. Let’s do that. Oh, actually no. Because if we do that,
gameOver will be set to true, but when the game over
screen pops up, we’ll actually see 3 lives at the top-middle,
and that’s not what we want at all. So I’m going to go to where
we have the gameOver logic, and then here I’m going
to set it to lives=3. So where we set the
score back to 0, that’s where I’m going to set lives equal to 3. And if I run this– Game over. Enter. And now we have three lives again. Beautiful. Now, we are missing one detail. “Clear the snake,
start the level again.” Yep, exactly. We are missing a sound effect
that I think would be important, which would be the death sound effect. That’s a good one. I like that one. We’ll use that death.wav. I’m going to go ahead up into here. Set the local deathsound=
love.audio.newSource(‘death.wav’ , ‘static’). And I’m going to go where we have
the game over actually registering, which is here, I’m going
to set deathsound:play– actually, no. This should be where
we just die normally. So that’ll be that. We’re going to want a separate
sound for the game over. So we’re down here. Whoops. Cool. So now we have it now we have a sound
effect for when we actually die, which is nice. Little bit of feedback. [? Bavick ?] says, “Let’s
not hard code max lives, so we can make changes from one place.” Yes, that is good practice. Yeah, definitely avoid hard coding as
much as I’ve been doing in this demo. In future demos, we’ll try
to adopt a little bit more of the best practices
approach to programming, and more from an
engineering perspective. But in this case, since
this is very introductory, we’re focusing a little
bit more on the syntax, just getting everything up and running. But on Friday, we’ll be a little bit
more upfront with our engineering, so to speak. Excuse me. OK. So we have a death sound,
which is looking good. Then I, lastly, want a game over sound. There we go. I think that’s more
what I’m looking for. All right, game over. Sounds like a classic
Atari sound effect. But that should work. All right. So let’s go over here, gameover.wav. I’m going to come up to the very top. Yes, exactly the sound a
snake would make when it died. Absolutely. Just combustion. s OK. Then back up here. gameOverSound:play(). And we run the game– OK, cool. Cool. It’s good enough, right? It does the job. It does the job. All right. I think that’s pretty much
it, in terms of Snake, like a fully playable, fully robust
version with bells and whistles. We have obstacles, we have levels,
lives, increasing difficulty, which is important. But up to a certain extent. And of course, sound effects,
things of that nature. [? Bavick ?] says, “Yas, we did it.” Yes we did. Ended last Friday kind of incomplete,
but turned it around today. And now we have a, I would say,
very robust Snake implementation. Before we before anything
finishes here, I’m going to add everything
and commit everything. So we’ll just say this is final snake. With all the sound effects there,
all generated sound effects, with the non-generated music. But yeah, it came along. And we did it together. And that was part of the fun, right? So I think I’ll stick around for a
few minutes for questions and stuff. It looks like we finished
a little bit early. It’s a little bit hard to ballpark how
long exactly some of these projects will take to finish. Friday’s concentration stream, I’d say
it’ll probably approach three hours. It may or may not go
over the three-hour mark. Hard to say. Tomorrow, we have Kareem Zidane, if
any of you are familiar with Kareem from the Facebook group. He’s one of our full-time
staff members from Egypt, and he is going to be holding a stream
tomorrow with me on Git and GitHub. So we’ll talk about the
basics of Git, if any of you are unfamiliar with Git and/or
GitHub, how to use them. It’ll be a nice tie-in
to a lot of the streams that we have planned for
the future, mine included. So if you see what I’m doing with Git,
and you’re a little bit unsure how to do it, or how to use your own source
control, how to use Git and GitHub, how to make it work for you, Kareem
and I will go over it tomorrow. He’ll be leading the way, and I’ll
be sort of playing his assistant, and asking questions,
and sort of pretending like I’m not super
familiar with it, just to provide a different layer to it. As always, if you have any suggestions
on streams that we can host, games you want me to make, topics for
other people in the stream to make, we are in talks with some
other folks, potentially, about doing a web-based assignment. So something in JavaScript, maybe React. We’re talking about having possibly
a machine learning introduction. So maybe something like OpenCV or
scikit-learn, or something like that, using just a Python framework
probably for ML or AI. Those sorts of things. [? Bavick ?] says, “What time tomorrow?” Tomorrow we’ll be doing the stream
at 3:00 PM Eastern Standard time, so same time as today. And on Friday, the stream is
actually going to be at 1:00 PM, so a bit earlier in the day. So that folks that are watching
from farther out, farther abroad have a chance to tune
in before it’s too late. And we may or may not transition
to an earlier schedule. 3:00 PM, I know, for folks,
especially that are, say, possibly in India or Bangladesh
or other locations far away are having kind
of a tough time keeping up. But worst case, all these videos are
going to be on our YouTube channel, so you’ll be able to see
them a little bit later. And folks who don’t have
the chance to tune in live will be able to at
least stay up to date. If you haven’t subscribed to our
YouTube channel, do so as well. And if you’re here and
chatting, you’re already following the CS50TV Twitch.tv channel. But if you haven’t followed that,
and you’re watching from YouTube, do go to twitch.tv/cs50tv. Hit the follow button, so
that we can chat in real time and make some stuff together as a group. [? Bavick ?] says, “I’m from India.” Yeah, so I’m sure you’ll appreciate the
schedule being a little bit earlier, as well, I imagine. So thanks for tuning in all the
way from India, [? Bavick Night. ?] All right. Like I said, just to kind of
linger around for some questions. I see there’s 15 people in the stream. If you have any questions about game
programming, or Snake, or anything that’s coming up or will
come up, definitely ask now. I’d be curious, if anybody does– [? Bavick ?] says, “Adios.” Adios, [? Bavick. ?] Good to have you. Thanks so much for coming. I’ll see you next time, I’m sure. JP says, “I have a question.” Shoot, JP. Let’s see your question. “How are you affiliated with Harvard?” So I’m a full-time
technologist here at Harvard, and I spend all my
time working with CS50. This year I also taught an
Extension School course GD50, so CS50’s intro to game development. So if you go to CS50.edx.org/games,
you’ll see this here. Sorry. Don’t know my location. Pop-ups are terrible on here. CS50’s Introduction
to a Game Development, where we talk about how to
make a lot of classic games, like Mario, Zelda, Pokemon,
Angry Birds, a lot of classics. And we use Love2D and Lua for
the first few lectures, as well. So this is on edX, and you have a
chance to explore that at your leisure. Bavick Night, “I’ll be
there to learn Git, GitHub.” Awesome. Yeah, join. Let all your friends know. Suggest that they follow
us and join the live chat. Happy to increase the pool
of folks contributing. The more people we have
in the stream, the more we can maybe look at doing
like live collaborative stuff, which would be interesting. “How to show a big apple
for a few seconds?” “Not a question, but a suggestion. Is it possible for you to
make a shorter, bit easier a game or video for
beginners [INAUDIBLE] I’ve never heard of that term before.” Says [? JPGuy ?] Elias, “How to show a big
apple for a few seconds.” Well, to show a big apple, it
would be a little bit trickier. But there’s a few things you could do. So right now, what we’re doing
is a grid-based approach. So this might be something
we could better illustrate if we did a sprite-based game. Because then you could
just scale the sprite, or you could just have
a larger sprite file. But we’re just drawing
rectangles, and everything is aligned on a very discrete grid. If we wanted to make apples
that were, let’s say, 2 by 2, or 3 by 3 blocks wide, you could– where do we have generateObstacle? So in generateObstacle,
you could say something like if obstacle is equal to,
let’s say, TILE_BIG_APPLE– or just in this case, BIG_APPLE,
since it won’t be a tile. I guess it would be
TILE_BIG_APPLE, because we want to make it maybe worth more. Then you would set tileGrid at
obstacleY obstacleX to TILE_BIG_APPLE, and then tileGrid obstacleY obstacleX
plus 1 equal to TILE_BIG_APPLE. And so on with the y, and
then so on with the xy. And that would have the result of
populating four of your grid indices with the apple tile. And then you could just detect the
intersection with your snake head in one of those, and just
trigger it as a big apple. And then maybe add three
points, instead of four points. The only issue then is you have to clear
all of the all of those four tiles. And so you have to keep track somewhere
else of where those four tiles are, so that you can delete
them from your game scene. Additionally, you have to also
check to see whether or not all four of those tiles
happen to be empty. Because if you’re writing
an individual tile, it’s not a big problem,
because here we’re checking to see if the obstacleY
and X are at that one tile empty. You would actually have to do
this same thing, but for all four of those big apple tiles,
rather than just the one. So you would basically
choose four random pairs– so one, two, three, four– and then do basically a combo if
statement, that’s basically this time is 4, as well. And it’s a little bit trickier also
for the random number generator, especially with larger
concentrations of stones, to be able to find
empty blocks of tiles. Kind of in the way memory
fragmentation works, where if you have a pool of memory,
and you have a bunch of little files and little blocks of code
stashed away amongst it, and you’re looking for a big
continuous chunk of memory, it takes a little bit more
time to find that chunk, if your memory is super fragmented and
kind of split up all over the place. Good question. I don’t think we have enough time
quite to implement it from scratch, but it would be a great exercise,
if you are potentially looking to implement that sort of thing. And maybe in the future, if we have
time, we’ll come back to it, as well. But good question. “”[INAUDIBLE] we make shorter, a bit
easier game video for beginners.” Let’s see. So Pong is the first game that I made. So David kindly added
a little excerpt there. You might like some of the earliest
weeks of [INAUDIBLE] some other games, as well. And in those chunk of
videos, we do cover things like Pong, which is a
pretty straightforward game. A little bit simpler. So we can maybe explore
that on another stream. The memory game is going to be
very easy, as well, on Friday. So maybe join us for that stream. And that should give us,
I think, a little bit easier of a time getting
into some of these details. This is a bit longer, because Snake
is a deceptively complex game. We’re looking at 321 lines
of code at the very end. The memory game is probably
going to be maybe half of that. So shouldn’t be too difficult.
Also a grid-based game. Yeah, technologists– It’s
a very flexible term, JP. The one, I guess, I’d be categorized
as is an educational technologist. So it’s trying to find
and work with tools that help us improve
the way we distribute educational material with
CS50, and just content creators and educators all around, I would say. But it’s a very flexible role, for sure. Quick question about tomorrow’s stream. What software will I be needing? I am [? mobile ?] today formatting PC,
and setting up new development stuff. So [? Bavick, ?] if you just
can install Git on your machine. If you’re on a Mac, it’s really easy. I think it comes by
default. I’m not 100% sure. You might need the Xcode install tools. Either way, you can
download it fairly easily. I think they recommend
Homebrew for Macs. Oh no, they have a Git for Mac on here. So if you go to
atlassian.com/git/tutorials/install-git. It says, if you installed
Xcode or Command Line Tools, Git’s already installed. There’s a Git for Mac installer. There’s Git for Windows. And Linux, as well, has an installer. We’ll just look up Ubuntu, just to
get a sense of what that looks like. Yeah, you could use
your Package Manager. It looks like for Ubuntu,
it’s apt-get install git-core. So it depends on your operating system. But yeah, install Git. And then ideally, make
a GitHub account, if you don’t have a GitHub account already. So you can do that just by going to– if I were on a brand new
web browser– so GitHub.com. It’ll take you right to
where you can sign up. Heavily recommend
getting a GitHub account. Andre, “Hi, Colton. Just got here. I have a question that’s only
tangentially related, so no biggie if you don’t answer. But got any hot tips on where to
get cool 3D game assets and anything for audio?” So there is a few places. So if you’re using Unity– which
I recommend you probably do, if you’re getting into 3D– the Unity Asset Store
has a ton of free stuff. And you’ll see that actually
within your Unity editor. I believe it’s Window, General
Asset Store is the menu dropdown. And then there’s some other places
where you can get 3D models for free. If you just type in free
3D models, I believe most of the first few resources– I believe I’ve used TurboSquid, Free3D. These are all pretty legit. It’s a little bit trickier importing
certain file formats than other ones, but I would just fish around
and kind of get a sense. TurboSquid I think is good, Free3D. Those are the two that I think
I have actually messed with. But again, if you’re using
Unity, Asset Store is great. Lot of great free ones. And the standard assets pack
has a lot of really cool stuff. And there’s a lot of
good paid stuff, too. If you end up going down
the Unity dev route, and you want to actually
pay for your resources, or you don’t mind paying
for your resources, definitely worth spending
a little bit of money. Metal Eagle, “I have a question. Is Lua good programming
language to create games, or is it just limited to
just the simple 2D games? What is Lua actually better at, compared
to the other programming languages?” So Lua is used very
often, very frequently throughout the games industry. Not only just for 2D
stuff, but for 3D stuff. A ton of engines use Lua commercially. Just recently, I saw a game [INAUDIBLE]
was being modded– which is a 2D game– but it was being modded in Lua. World of Warcraft was
modded traditionally in Lua. I’m not sure if they still mod
in Lua, but I think they might. A ton of game engines,
3D and 2D, use Lua. The two primary game engines now,
Unity and Unreal, don’t use Lua, but you can get Lua embedded into your
Unity projects with a special DLL. So it’s totally usable in there. And Lua is absolutely perfect
for 2D game development, using Love2D and using some
other frameworks that utilize it. Yeah. No, it’s a totally great language. One of the fastest scripting
languages, too, using what’s called LuaJIT, which is
the Just-In-Time compiler. And I believe Love ships
with this by default. So you get really fast, dynamic
recompilation of your Lua code. And it’s very fast, compared
to other scripting languages. Good question. It has some weird syntactical oddities
that are specific to Lua itself, but it’s very easy to get over that. And the syntax, at large, is actually
quite nice and pleasant to work in. And the Love2D API especially is very
robust and just pleasant to work with. “Appreciate the response.” Yeah, no problem, everybody. “I’m on Windows 10, have GitHub. Friday CS50 Cool, we’ll
get it installed.” So, [? Bavick– ?]
have GitHub from CS50. Oh, from CS50. Yeah. Tomorrow, to be clear,
is the stream with GitHub at 3:00 PM with Kareem
and I. And then on Friday will be another stream by myself,
where we a brand new game, Concentration, the memory card game. So yes. Metal Eagle, “I did not know that. Thanks, CS50TV.” No problem. No problem at all. All right. I’ll stick around for just
a couple more questions. We’re at 5:55. We have another five
minutes before we adjourn. If anybody wants to talk about
anything or has any questions. But other than that, we’re getting
close to the wrapping up point. “Have a good day everyone. Pleased to be here. See you tomorrow,” says [? Bavick. ?]
Thanks, [? Bavick, ?] for coming in. Have a good day to you, as well. I’ll see you tomorrow. Bella [INAUDIBLE] says, “That was fun. Thank you for this amazing stream.” Thanks, Bella, for coming in. Appreciate it. Come join us tomorrow. Come join us Friday. Make some more games, and
do some more cool stuff. “I have heard that game programming jobs
have long hours and very little pay. It may depend on the area, but
what do you know about it?” I’ve heard mixed things, as well. So it depends on what
studio you work for. If you work for a AAA studio, chances
are you will be working long hours, you’re probably working
60 hours, especially towards the end of a game’s
development lifecycle. It’s not uncommon, based on what I’ve
read, for most studios to approach, for their core engineering staff,
somewhere around 60 hours for work weeks. Rumors are going around
about Red Dead Redemption 2 having 100-hour work weeks. But the creators of that
have come out and said that it was just the writers
who initially had that, and that the engineering team were not
expected to work 100-hour work weeks. But if you’re an indie
developer or you’re working for a company that maybe does
other types of games, like not AAA games, maybe like a mobile
development company, you can expect to see something more
along the lines of 40 to 50 hours. If you’re an indie developer, you might
still end up spending more than 40, 50, 60 hours finishing your game. Especially as you approach the
end of the development lifecycle, if you have a hard set deadline. Just because games are
notorious for taking a long time and having a lot of
hard to track down bugs that manifest themselves
at unfortunate times. I mean, just as you saw, making Snake
wasn’t a terribly easy thing to do. And we saw a lot of weird bugs
that we didn’t anticipate. And like I said, my first time also
implementing Snake from scratch. But we hopefully got
a chance to see just how true this is with even the
most simple and game development. Extrapolate this out to something
massive, like modern AAA titles, and yeah. It gets it gets out of hand
very quickly, especially if you’re not programming and something
is easy to rapidly developing as Lua. If you’re maybe working with a C++
code base and you were doing engine development, and you’re having to
recompile everything every time you make any adjustments. It gets tricky. “Thank you, and good night,
because it’s 11:00 in Morocco.” Thanks, Elias. Like I said, we’ll try
and get our streams set up maybe a little bit earlier. Fridays will be at 1:00
PM Eastern Standard Time. So they’ll be two hours
earlier, so you’ll hopefully be ending a little bit sooner. “Yes, programming games myself, I see
bugs that are very hard to correct.” Yeah, and you’ll get better at it. And you’ll also be able to anticipate
things a little bit better. But it can be tricky, for sure. But I love games, and I
think they’re a lot of fun. I don’t know. [INAUDIBLE] from Serbia, “Which book
would you recommend for C learning? Thanks,” says [INAUDIBLE]—- oh man, this
is going to be a hard one to pronounce. [INAUDIBLE] I don’t know. I’m sorry. I’m having a really hard
time pronouncing that one. [INAUDIBLE] Books for learning C. So the very first
book that I ever learned C on was– let’s see if my Amazon– OK, I’m not signed in. So C programming. I heard this book is good,
the Programming in C book. I haven’t actually read it. Everybody talks about The
C Programming Language. This is by Brian Kernighan
and Dennis Ritchie, who are the creators of C and Unix. So I would probably recommend that. David’s got some links
in the chat there. CS50 has an official C programming list. So definitely pop into
that and see what’s up. Oh, this is a new book. I haven’t seen this one before. 21st Century C. That
might be worth looking at. I’ll have to maybe check that one out. C in a Nutshell. There’s a ton of books. Honestly, any of these
books will probably work. C Primer Plus is good. I’ve read through parts of that one. Read through several books,
watch videos, watch tutorials. There’s a lot of resources now. Book learning isn’t strictly
necessary for all of your learning. Honestly, the first book
that I ever learned– and I’m not too ashamed to admit it– was actually C for Dummies. This was the first book–
is it even on here? Yeah. C for Dummies. This was the first book that I ever
used to learn programming formally. I guess you could say formally. Not in the context of
like game programming, I guess, or like a
book that was teaching scripting, but actual programming. This is the first book that I
learned, and it’s pretty good. I remember I actually enjoyed it. It’s got good reviews. C++ for Dummies, as well. I looked at that one. But yeah. Whatever book works for you. Honestly, different people are
going to get different things out of different books. So different resources
work for different people. And YouTube is rife with
tutorials, and even CS50 itself is a good resource for
learning C. So there’s a ton. [? Andre ?] [INAUDIBLE] I’m
not sure what language that is. Is that Czech? [INAUDIBLE] “Thank you for hanging out
with us in chat Professor [? Malin,” ?] says JP. [INAUDIBLE] do not look back, son.” “I do have to head out,
but see you next time.” Thanks, David, for coming into the chat. Everybody bid farewell to David. Give him some more Twitch emotes. Give him some kappas. The kappa’s my favorite, so
I’m good for kappa every time. 24/7. There we go. JP’s always happy to
oblige with the kappas. [INAUDIBLE] as well. All right. With that, it’s 6:02. So we’re going to call it here. This is the end of our stream. [? Andre ?] has got the meatboy emoji. There we go, that’s a good one. Thanks so much, everybody, for coming
out for the conclusion to Snake. We’ve come a long way from the
beginning of the day, where we had an Etch A Sketch. And the end of the day, we have a
pretty much full, polished game. Tomorrow, again, Kareem
Zidane and I, Git and GitHub. And on Friday, Concentration. So this is CS50 on Twitch. My name is Colton Ogden. It was a pleasure. I will see all of you later. Thanks so much, again.

4 comments / Add your comment below

  1. To be honest this is the best tutorial on Love framework I've seen =) I'm sure that I'll watch other videos from Colton as he is a fantastic tutor.
    I really like the idea of tileGrid which is different than other 'how to make a snake game' videos I've seen.

    After watching both parts I was able to spend some time to add a few new elements to this game (like drawing images from .png instead of squares). If anybody's curious about the results, everything is on my github 😉
    https://github.com/trivvz/Snake-Game

Leave a Reply

Your email address will not be published. Required fields are marked *