Placeholder Image

字幕列表 影片播放

  • 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.

COLTON OGDEN: All right, I think we're alive now.

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

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

  • 1 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字