Placeholder Image

Subtitles section Play video

  • COLTON OGDEN: All right, we should be live here on CS50's Twitch channel.

  • Apologies for the delay.

  • I'm afraid our PC actually had a blue screen of death.

  • So we brought in a reserve machine.

  • So thanks to Dan Coffey for helping out with that.

  • But, yes, welcome to everybody who's been patiently waiting in the chat

  • here.

  • First of all, for folks who don't know who I am, my name is Colton Ogden.

  • I work full-time with CS50.

  • And I thought as part of our new Twitch channel,

  • we'd take some time today, about a few hours, to from scratch code a game

  • called Snake, which is sort of an old school 2D game.

  • If you ever grew up having a Nokia phone or the like, you've probably played it.

  • And it was played on sort of older computers back in the day.

  • And we'll take a look at what that looks like.

  • But, anyways, thanks so much for tuning in.

  • Let's dive into what we're doing and start to take a look at everything.

  • So I'm going to pull up my computer here.

  • And now we have a little twitch.tv/cs50tv graphic there.

  • So if you're not following the CS50TV account on Twitch

  • so that you can join the live chat with us

  • and we'll have some back and forth conversation,

  • definitely go ahead and follow that link.

  • Hit Follow.

  • If you're watching this on YouTube after the fact,

  • we will be pushing this video to YouTube later.

  • But there is a 10 minute waiting period, I believe,

  • for if you want to actually chat.

  • And I'll see your chat messages here as we go ahead and program.

  • All right, so what you see here is a text editor called VS Code.

  • This is what I'll be using today to program,

  • but you can use sort of any text editor that you want.

  • You could even theoretically use, like, Word Pad on Windows or whatever.

  • I would definitely recommend getting something like VS Code or Atom

  • any of the free sort of text editors that are out now.

  • I personal like VS Code.

  • And it has some cool plugins, one of which I will use for the framework

  • that we'll be taking a look at today to make Snake.

  • Let's take a look at what Snake actually is, I think,

  • before we go too in-depth into what we're doing programming wise.

  • So Snake kind of looks like this.

  • It's basically a grid, right, where you have this green long thing that

  • goes in various directions.

  • And it continuously moves throughout the sort of grid space in the game.

  • And then every time you pick up these red dots or whatever

  • object that you want to frame in the game,

  • you're a snake creature which starts off with just one piece will actually

  • get another piece at the end of it at the tail, and you'll grow.

  • And so the catch is the longer your snake grows

  • sort of the more you run the risk of biting your own tail or your own body,

  • so to speak, because you're continuously moving.

  • And when you bite yourself, the game actually ends.

  • And so the goal is to essentially grow the snake as long as possible

  • by eating apples or however you want to visualize the red dot in the game.

  • Hello to Bhavik Knight there in the stream.

  • Thank you for visiting the chat today, visiting the stream.

  • But, yeah, the game that we're going to be making

  • will look very similar to this.

  • We're not going to get too fancy with it.

  • I actually haven't done any prep or any research

  • into how we're going to make the game.

  • I figured we'll just kind of dive into figuring it out on the fly.

  • So that's Dan Coffey in the stream, everybody.

  • Say thanks to Dan for fixing our broken machine today and getting everything

  • up and running.

  • All right, so first things first.

  • In order to start programming something visual or to program a game,

  • we'll need to find some tool, some game engine, to do this for us.

  • And I'm a big fan of a 2D game engine, a 2D framework, called LOVE, which

  • uses Lua as its scripting language.

  • What's up, Steve Benner?

  • Good to see you again.

  • Thanks for coming by.

  • So LOVE is a 2D framework.

  • It's an OpenGL framework strictly catered towards 2D games.

  • And it has a very nice API for game programming,

  • which makes it pretty easy, nice and simple to get into.

  • But it does allow you a lot of low level flexibility

  • that some bigger engines kind of take away from you

  • or at least kind of hide behind layers of abstraction.

  • I'm thinking of things like UNITY, where there's a big learning curve associated

  • just with kind of getting the engine right, getting the UI correct,

  • and not necessarily diving too deeply into code for at least a while.

  • Hello, Elias from Morocco.

  • Good to see you.

  • D. McDermott, good to see you.

  • OK.

  • So we're going to go ahead and take a look at LOVE2D and sort of Lua

  • and its syntax and the different functions therein.

  • In order to get started, you'll need to if you

  • want to follow along whichever version of LOVE

  • corresponds to your operating system.

  • Today, we'll be using the newest version which is version 11, or 11.1 rather.

  • I'm on a Mac.

  • I'm running Mojave.

  • If you are on a Mac running Mojave, you might have some bugs

  • with actually running version 11.

  • So make sure in your Accessibility panel, which

  • I'll take a look at here just for those running on a Mac,

  • if you go to your Security and Privacy, rather, and then your Privacy section

  • here, there is a section of apps that basically--

  • part of Mohave, it seems, allows a lot more

  • apps to have accessibility features.

  • I'm not exactly too sure of the low level aspect of what they do.

  • But they basically allow the apps to have more control over your computer

  • than normal.

  • So in this Accessibility panel here, under the Privacy panel,

  • just make sure that LOVE and Terminal and also whatever text

  • editor you're using are checked.

  • And this is Mac OS X Mojave specific, not specific to Windows per se

  • or Linux.

  • And that's also on version 11.

  • I've gone ahead and downloaded it already.

  • So if I go into my console here, assuming

  • that I've put my executable in the right spot, I should be able to just of LOVE

  • normally.

  • And you'll see this sort of thing with a balloon and a tail that says,

  • "no game" and that's perfectly normal.

  • If you're on a Windows machine or if you're on a Mac or if you're on Linux

  • and you run the binary, you should get the exact same thing.

  • And I have a shortcut to that on my desktop here.

  • Let me just hide everything.

  • That doesn't want to hide.

  • But I have a shortcut to love here.

  • If I double click on that, I get the same exact window there as well.

  • So the reason I'm able to access the binary through my command line

  • is because I've actually aliased it.

  • So if anybody is curious, there's actually

  • instructions for this on the Wiki page.

  • But if I go to nano home directory bash profile,

  • you can see that I've aliased to the command to love to be

  • Application.love.app /Contents/MacOS/love.

  • This is another Mac slash Linux specific thing.

  • But for today's examples, it suffices just

  • to double click the executable that you download from LOVE2D

  • and/or drag your project folder onto that executable.

  • And, again, the instructions available for how to do what I did

  • are on the Wiki.

  • I've got to figure out where the exact page is.

  • I think it's on the Wiki here and then the Getting Started page, yeah.

  • So on the love2d.org/wiki/gettingstarted,

  • you can get some more instructions on to actually pull up LOVE2D,

  • you get it to work by itself, get it to work with files specific

  • to your operating system.

  • Again, I'm using Mac OS X today.

  • Mac OS X is similar to Linux, but Windows will be slightly different.

  • But that's LOVE2D.

  • So once you have that all set up and ready to go,

  • if you've double clicked the executable and you've got that window,

  • then we should be ready.

  • So I'm going to open up my text editor here.

  • And I'm just going to fire up a very simple LOVE2D application just

  • so we can see sort of the backbone of how it works.

  • By the way, sorry, I haven't been paying attention to the chat.

  • OK.

  • So Bhavik says, "I will try Atom.

  • I know a bit of Vim.

  • David uses it.

  • I'd like to useful plugins for Vim."

  • I don't use Vim too often, so I can't actually suggest useful plugins.

  • I know that Jordan Hayashi, one of our alums, uses Vim a lot

  • and has a lot of plugin help.

  • Maybe on another stream, if David gets on,

  • we can maybe ask him what plugins he likes to use.

  • I am not a Vim expert by any stretch of the imagination.

  • But, yeah, Atom is great.

  • Visual Studio Code, VS Code, is great as well.

  • So I'll be using those.

  • But perhaps in the future, we'll take a look.

  • And then Sugo Gruber from Germany, Bhavik Knight says, yeah.

  • OK, awesome.

  • So we're going to go ahead and let's say that I

  • want to create a new LOVE2D application on some location on my hard drive.

  • So I'm going to assume that I have a folder called Dev somewhere.

  • Within that, I'm going to say just for the sake of today

  • I have a stream subfolder.

  • And I'm going to create a new folder called Snake.

  • And in that new folder called Snake, I'm first

  • of all going to open that folder with my text editor.

  • So on Mac, you can just click and drag it onto your text editor.

  • And that will open that text editor in a Project view.

  • I'm going to click and close out the other window that I had available.

  • And I'm going to right click, do a New File.

  • And I'm going to call this file main.lua.

  • Now, in LOVE2D, main.lua is sort of like the entry point of your game.

  • And that's going to be necessary in order to tell LOVE2D

  • where to start running your game.

  • And in C, and even in Python in certain situations,

  • you have the same exact idea.

  • In C, you have this function called main,

  • which the C compiler knows to look for and then bootstrap your application.

  • In Python, you can do this check that says, if name equals main.

  • And, basically, that will launch that Script

  • file as the bootstrap of your application

  • if you have multiple script files.

  • In LOVE2D, the same aspect applies.

  • But it looks for a file called main.lua.

  • And this should be in the root of your project directory.

  • We've got some other people, Blow Into The Cartridge,

  • has a Heart Symbol, love the name, Belacura,

  • Bhavik Knight says, "sure, hello all."

  • Thanks very much for coming to today's stream.

  • Hope you're ready to code some Lua, some Snake.

  • OK.

  • So I have my main.lua file, but it's completely empty.

  • So it's obviously not going to be useful for anything.

  • I'm going to just write a couple of functions here.

  • So function love.load is one of them.

  • And this is using Lua, the language Lua.

  • So we've got Lua syntax.

  • So we have function, the keyword that says

  • this is going to be a function called love.load.

  • Notice the function ends with this end keyword, which is a Lua specific thing.

  • I want to make a function called love.update.

  • Notice that load does not take a parameter,

  • but update does take a parameter called dt

  • which means that love.update will have access to this variable within it.

  • And we'll take a look at what that means a little bit later.

  • I'm going to define a function called love.draw.

  • And this is the function that's going to handle everything related to actually

  • rendering things onto the screen.

  • And then that should be it for now.

  • Oh, actually, one more function, I'm going

  • to do this just out of convention.

  • It doesn't really matter where you define these functions

  • in terms of their order.

  • But I'm going to define a function called love.keypressed.

  • And this one takes a key parameter.

  • So this function, as you might surmise, is

  • used to check whether a given key is input on any given frame.

  • And with that key, we can then do some sort of logic.

  • In this case, very simply, I'm going to do a condition here that says,

  • if the key is equivalent to this string called escape, which

  • means if the user pressed Escape key, then

  • I want to call this function called love.event.quit.

  • And very simply, this will allow us to test whether our application is

  • running correctly.

  • So I'm going to, first of all, make sure where I am.

  • I'm in my home directory.

  • I'm going to cd into that folder that we created.

  • So cd dev/streams/snake and then ls there

  • for list, which means I can see sort of what files are there.

  • I can see this main.lua file that I created.

  • And so if I just type love space dot, what I should see

  • is a black window, because I haven't defined any rendering.

  • But if I press Escape, which I did, the window closes.

  • So everything seems to working appropriately.

  • Black screen is completely normal.

  • Notice that when we open that up, it said sort of this untitled word

  • at the top of the window where the window bar is.

  • So if I do love.window.setTitle to, let's say, snake, or snake50 rather,

  • and if I rerun this as I did before, notice that, oh, it did not work.

  • Did I forget to save it?

  • Make sure I'm in the right place.

  • love.window.setTitle snake50 should work.

  • Huh.

  • Strange.

  • OK.

  • Not sure why that's not working, hiccup, minor hiccup.

  • So this is in the Snake directory.

  • Let me make sure.

  • Oh, LOVE 11 may have changed the function actually.

  • Sorry, LOVE2D v11 or love window setTitle.

  • There are a couple of things that LOVE 11 has changed versus LOVE 10.

  • So LOVE 10 was what I taught the Extension School course, the games

  • course.

  • By the way, I'll plug that here, cs50.edxorg/games if you're interested.

  • This course teaches LOVE2D and Lua with version 10.2.

  • And in the middle of the course, they actually updated LOVE to version 11.

  • And they changed a lot of the functions therein.

  • So part of that is going to be me figuring out

  • what some of these new functions are.

  • But it looks like they didn't change the setTitle function,

  • so I'm a little bit curious as to what's going on here.

  • If I run this--

  • oh, you know what?

  • Hold on.

  • Let's do a drawing example just we can figure out

  • why exactly it's not working.

  • "What is the name of the software you are using," says Elias.

  • So Elias, this text editor it's called VS Code.

  • This is just a text editor.

  • And it's made by Microsoft.

  • And it's kind of like a simplified version of Visual Studio,

  • hence the VS part of it.

  • And then the terminal here is just a shell

  • that you would use on a Mac or PC.

  • So on a PC, it's called CMD for Command prompt.

  • And on a Mac, it's called Terminal.

  • And I'm actually not entirely sure what it's called on a Linux machine.

  • I think it's called Terminal as well.

  • Dan, do you happen to know?

  • It's called Terminal?

  • Yeah, it's called Terminal on a Linux machine as well.

  • And then, obviously-- Chrome to browse the web.

  • But, yeah, the main two things are just the VS Code and the Terminal there.

  • "Black text on black screens," says Dan Coffey.

  • Oh, did I write that in the love.keypress function?

  • Oh.

  • Pff.

  • OK.

  • Sorry, yes.

  • I thought I was in the love.load function.

  • Thanks, Irene, for pointing that out.

  • The love.window.setTitle wasn't working, because I thought I was writing it

  • in the love.load function.

  • But the logic that I was just writing was that if I press the Escape key,

  • set the title to snake, and then quit the game.

  • So we weren't actually seeing it.

  • So that was a silly issue on my part.

  • If I run the code now, now it's says snake50.

  • So thanks, Irene, for pointing that out.

  • And thanks for joining this stream.

  • I appreciate it.

  • I might need you to keep me in check while I make all these mistakes today.

  • Great.

  • So that was how to get a window up and running more or less

  • and how to test for keyboard input.

  • Does anybody have any questions before we

  • get into maybe doing some stuff related to graphics?

  • Is anybody having issue getting LOVE2D or Lua running on their machine?

  • By the way, you can click and drag a folder onto LOVE

  • if you're not comfy with the command line

  • and you would prefer to just use more of a drag and drop interface.

  • If you have a shortcut on Mac or PC and you

  • have a folder within which is your main.lua,

  • you can click and drag the folder onto the executable.

  • And that will function the same way.

  • In this case, interesting--

  • OK, hold on.

  • There we go.

  • So if you click and drag the--

  • I'm not sure why it didn't work the first time--

  • but, normally, you click and drag the folder itself, not the main.lua.

  • Don't drag the main.lua, because the LOVE executable

  • is going to look for a folder.

  • Click and drag the folder onto your shortcut on your desktop

  • if you have it there.

  • And then that will trigger LOVE2D running the application.

  • Blow Into The Cartridge says, "all good.

  • Thanks for this awesome video.

  • Stumbled upon it and already super hooked.

  • Keep it up."

  • Thanks so much, Blow Into The Cartridge, appreciate it.

  • Thanks for coming by.

  • I'm going to go ahead and start messing around a little bit with graphics.

  • So I know that Snake is generally a grid-based game.

  • It looks like it's mostly just made out of squares.

  • So we can sort of visualize our game space as being this 2D grid, right?

  • And in LOVE2D, we can visualize a coordinate system where on the top left

  • corner is our origin or 0, 0 point.

  • And anything in the positive direction is going downwards--

  • sorry, on the y-axis is going downwards.

  • And on the x-axis, anything that's going sort of to the right

  • would be positive as well on the x-axis.

  • Inversely, if you were to go to the left side past the edge of the screen,

  • that would be negative on the x-axis.

  • And if you were to go above the screen on the y-axis,

  • that would be negative on the y-axis.

  • So you can always visualize anything that you put in your game world,

  • whether it's a sprite or a shape, as having an x, y coordinate.

  • So I want to start thinking about maybe how

  • I can fill this space with my game world with a bunch of squares

  • that sort of represent, you know, the ground and the snake as well.

  • So there's a function in LOVE2D called love.graphics.rectangle,

  • which takes a mode first of all, so whether I want a filled rectangle,

  • so something like filled with color or just an outline of a rectangle.

  • In this case, I do want a filled rectangle.

  • But we're probably going to want something with maybe an edge

  • as well, so that I can see the individual pieces of the snake.

  • And now I just have one contiguous snake if it's

  • just all a bunch of squares sort of chain together, right?

  • And I'm probably thinking I want some way

  • to visualize where the head of the snake is, too.

  • Because especially as the snake gets really big,

  • I'm going to kind of want to keep track of where it is.

  • Otherwise, it'll get lost amongst all the green squares

  • that I'm seeing throughout the game.

  • So I'm going to just pass in a string called fill,

  • which just tells this function we're in fill mode not in line mode.

  • The other one would be line.

  • And then I'm just going to say, I want to draw a square just

  • in the upper left.

  • So I'm going to say 0, 0.

  • And let's just say my snake is going to be 16 pixels--

  • this square, rather, is going to be 16 pixels wide on this x-axis

  • and 16 pixels tall as by the y-axis.

  • I'm going to save it.

  • I'm going to rerun it.

  • And then notice that up at the very top left--

  • it's kind of small-- we do have a rectangle,

  • but it's white which is interesting.

  • So I didn't have an option to specify a color here.

  • It just sort of defaulted to, oh, whatever I draw just make it white.

  • It turns out that LOVE2D, just like OpenGL,

  • is based on a state machine, state machine just meaning

  • that the LOVE2D kind of has any one given state at one time.

  • It has a set of variables internally that it

  • manages that allows us to just change the way that it renders things.

  • In this case, I want to do love.graphics.setColor to some value.

  • And that will change the state of LOVE2D such that anything

  • I draw after that will abide by that state transformation.

  • So any color that I pass into this function will apply to this rectangle

  • that I just tried to draw right afterwards.

  • And this was different in LOVE version 10.

  • But in version 11, all of these variables are now between zero and one

  • that I pass into this function.

  • And it's going to take four variables, a red, green, blue,

  • and an alpha component.

  • And these four variables are what determine the color of something.

  • So in this case, I want a green square, because the snake generally is green.

  • So I'm going to say 0 red, 1 green, 0 blue, and 1 alpha.

  • And what that will translate to is no red at all, full green, no blue,

  • and full alpha.

  • And for those unfamiliar what alpha is, is basically

  • how transparent or opaque that color is.

  • So at 1, it's fully opaque, meaning I can see it completely.

  • But if I were to say 0.1 or 0, it would be invisible or slightly visible.

  • Bhavik Knight says, "my Scratch project for CS50 P set 0 was based on Snake,

  • but tail wasn't dynamically increasing."

  • Well, hopefully, we can demonstrate some techniques

  • for potentially making that work today.

  • And there's multiple different ways we could do it.

  • I have a couple ideas in mind, but we'll see what may be best

  • and what we can figure out live.

  • OK.

  • So I've specified the color of my state.

  • Whoop, sorry.

  • I'm going to save it first.

  • Always make sure you save your project and rerun it.

  • And then when I rerun it, notice that, indeed, we have at the top

  • left no longer a white rectangle, but a green one.

  • So it seems to be working quite well.

  • Now, this isn't all that interesting, because, you know,

  • we can't move the snake.

  • It's not doing anything.

  • It's just a static rectangle at the top left.

  • But we do have something that we're drawing to the screen.

  • That's a good start.

  • But let's say now I want to move my snake to the right.

  • We'll do it continuously, I suppose.

  • And so we've been Messing around with a love.draw

  • function, which is where you can define everything that you draw to your scene.

  • We messed around with love.keypressed.

  • Now, we want to sort of take a look at love.update,

  • because it feels like now I want to actually update

  • what's going on in my game.

  • I want to change something in the scene.

  • So in my love.update, I want to think about how I want this green rectangle

  • to move.

  • And I'm thinking I kind of want it to move to the right

  • maybe just infinitely.

  • I'll just infinitely move it to the right.

  • So since we talked earlier about how everything exists on an x, y plane,

  • sort of like this coordinate system, I know

  • that if I want to move it to the right, the right is positive.

  • And it's a positive transformation on the x-axis.

  • So whatever I draw on this x-axis, which is here, this third or fourth variable

  • in the rectangle function which is this x-coordinate, if I just increase that

  • over time, that will have the result of my rectangle moving to the right,

  • right?

  • And if I were to decrease it, it would move the rectangle to the left

  • and it would come out of view.

  • So let's say I want to make this a variable, maybe call it snakeX.

  • And let's go ahead, first of all, declare snakeX up here.

  • local snakeX equals 16.

  • So it's going to initialize itself to the value 16 which we had before.

  • If I rerun it, notice that it stays the exact same.

  • But I want to change this over time.

  • So what I'm going to do is this variable dt

  • is what's probably one of the most important variables in game programming

  • generally.

  • But specifically here in LOVE2D within the update function,

  • this will allow us to update anything by multiplying dt by some value.

  • Because every frame, dt is essentially the amount of time that's

  • passed from one frame to the next.

  • So between frame one and two, about, what is it,

  • 0.16 seconds elapses, which is 1/60 of a second.

  • And if we define a constant and multiply it, it'll move that constant

  • or manipulate it based on how much time has passed between each frame.

  • SP Vendor says, "isn't that the size of the rectangle you're

  • changing rather than the position?"

  • Oh, yes.

  • So sorry, yes.

  • That's correct, my bad.

  • The x, y is actually these first two numbers, not the last two numbers.

  • And in this case, this would need to be 0.

  • So good call, Steve.

  • Thanks for pointing that out.

  • snakeX should be that first parameter.

  • This is why we do it live, so we can see all these wonderful,

  • wonderful blunders.

  • OK.

  • So, anyway, dt is basically going to allow

  • us to multiply any transformation, any value,

  • by the amount of time that's passed between frame one and two

  • or between two and three.

  • Just every frame, this will be a new value.

  • Let's say I want my speed of my snake to be,

  • oh, I don't know, 100 pixels a second, which it would effectively be.

  • This will effectively be how much that moves per second.

  • And if we multiply--

  • sorry, if we were to assign snakeX to itself plus the snake speed times delta

  • time, what that'll effectively do, just sort of like plus

  • equals in other languages like Java or C--

  • unfortunately, Lua doesn't have a plus equals operator which is unfortunate.

  • But we can say take my value of snakeX and then add

  • that speed value times delta time.

  • So this will take place every frame, so 1/60 of a second.

  • So this will be 0.016 approximately multiplying by 100.

  • So over the course of 60 frames or 1 second, we'll have moved 100 pixels.

  • It'll basically just update itself consistently.

  • So now if I were to do that, notice that we

  • are moving, indeed, about 100 pixels every second

  • if you were to calculate it out.

  • And the beautiful thing about this is it'll actually stay consistent

  • between different hardware.

  • So if you're running on a machine that's kind of slow

  • and it can only process one frame for every three frames

  • that another machine produces, the dt will be triple the amount

  • in between the individual frames.

  • And so this will still move it 100 pixels per second.

  • Got a few messages in the chat--

  • "isn't this written-- yes, that's why I guess

  • he's using the function update for moving positions."

  • That's correct, Zad.

  • We are using update to move positions.

  • We do it so that we have access to this dt parameter.

  • Because if I were just to do it without dt, right,

  • and we're not scaling it by the amount of time per frame,

  • this will run absurdly fast.

  • First of all, you didn't even see it move-- well, hardly.

  • Because it's just moving at an insane rate, basically as fast as my CPU

  • is capable of running this operation.

  • Or I guess, rather, I think, it's hard cap to 60 frames in LOVE2D.

  • Often, with game engines, if you don't cap the FPS, this won't run at a cap.

  • It'll run hundreds of times.

  • But in LOVE2D, I do believe that this actually

  • only runs 1/60 of a second either way.

  • So if we're running at 60 frames, we are moving per second, that would be,

  • 6,000 pixels.

  • So we moved 6,000 pixels in the span of a second versus 100 pixels, which

  • is not what we want, generally.

  • That's too fast for the human eye to see.

  • So we use dt to scale everything based on the time that's elapsed per frame.

  • Time difference, Bhavik Knight, it's short for delta time,

  • so, yeah, effectively the time difference

  • between one frame and another, the delta between frame x and frame x plus 1--

  • I should say, rather, frame x and x minus 1.

  • Awesome.

  • OK.

  • Sounds like everybody's on the same page here.

  • So I have a moving snake, but it's not really responsive.

  • I can't do anything if I were to run this.

  • And I'm pressing the keys.

  • I kind of want to have control of where the snake goes, right?

  • I want it to move down, left, up, and right if I hit those keys.

  • And I'll just kind of infinitely move in one direction.

  • Moreover, I want it to move in such a way

  • that, if it were to go past the right edge of the screen,

  • it should actually come back--

  • sorry, reverse, mirror's flip.

  • It should actually come back on the left side.

  • So if the snake goes all the way to the right, it should come back to the left.

  • And if it goes all the way up, it should come back from the bottom going up,

  • so that we kind of have this looping world space.

  • Because, otherwise, we won't see where the snake is if it doesn't come back,

  • right?

  • It won't work that way.

  • So we have a few problems that we need to bite off here.

  • The first thing that I think I'd like to tackle

  • is changing the direction which the snake is moving in.

  • OK.

  • So we're going to need a couple of things here.

  • So if we can move in not only the x direction, but also the y direction,

  • I should probably make another variable called snakeY and also set

  • that to zero, right?

  • Because we're going to potentially not just move the x, but also move the y.

  • Andre, "is LOVE's delta time constant, or will it theoretically

  • be a larger value if the frame rate drops?

  • Is there an equivalent to UNITY's fixed delta time in LOVE2D?"

  • The first answer-- yes, it will be larger value if the frame rate drops.

  • And in terms of fixed delta time, I do believe it does.

  • Let's look up the LOVE2D Wiki, LOVE2D fixed timestep.

  • This is somebody using it, I think, in a project, but not actually-- oh,

  • this is like somebody manually creating their own fixed timestep.

  • It may be something that you have to fix yourself.

  • Typically, you use this sort of thing in physics calculations

  • if you need to apply some physics transformation step by step rather

  • than having it apply to some, have it be like, a multiplicative value.

  • In this case, I don't know if LOVE2D--

  • I actually haven't used it myself.

  • Or at least I think I did once, but it's been a long time.

  • I don't remember offhand.

  • I think you do actually have to write your own fixed timestep.

  • And I think you can actually mess with a LOVE run function-- sorry,

  • not LOVE runs out, LOVE2D run function--

  • which, underneath the hood, this is LOVE's function.

  • Yeah, you can see it here.

  • I think this is where you would ideally write your own fixed timestep

  • code where you sort of perform physics calculations

  • and whatnot and do sort of like minusing of the dt off of that larger timestep

  • and then apply the update changes but this

  • is where you can sort of see what the overall sort

  • of main function of LOVE2D, or rather the game loop of LOVE2D, looks like.

  • For those unfamiliar, every game has to have a loop where it runs,

  • like I said, every sort of frame over, you know, usually 60 frames per second,

  • every single frame doing some updating, doing some rendering, all that stuff.

  • This love.run is LOVE's example or LOVE's implementation

  • of this game loop.

  • But, yeah, to answer the second part of your question,

  • Andre, I believe in LOVE2D you have to actually implement

  • fixed time step manually, not as it normally exists with update and dt.

  • Good question.

  • OK.

  • So to move our snake, we have the ability to currently just move it

  • in one direction.

  • Let's test it first with being able to move it left and right.

  • So if I were to, for example, press left,

  • then instead of adding the snake speed to my snake,

  • I should probably subtract it.

  • Because that'll take me from going to the right

  • and increasing my x value to going to the left, decreasing the x value.

  • And I realized, again, that my character on the screen is flipped.

  • So if I do key =, for example, left here, then I should be able to say--

  • let's say I need to keep a variable of this, whoops.

  • snakeMoving = left, right?

  • And then-- else if key = right, snakeMoving = right.

  • And then up here I'm going to need to keep track of that,

  • so snakeMoving = right.

  • So now I have a variable that it'll at least let me toggle back and forth.

  • It's given my snake kind of this state where I can say, OK,

  • am I moving right or left?

  • And if I am moving right, add the value.

  • If I'm moving left, subtract the value.

  • To do that actual math, that's going to take place here

  • in the love.update function.

  • And then what I can do is basically say, if snakeMoving is equal to left,

  • then taking this statement that I wrote before, I should probably,

  • instead of adding the snake speed times delta time, subtract it like that.

  • And then else, it's going to only be for now left or right.

  • I can say snakeX = snakeX + SNAKE_SPEED times delta time.

  • And if everything is going according to plan here, I'm moving right right now.

  • If I press left, oh, indeed, now my cube is moving to the left

  • instead of to the right.

  • And I can switch back and forth as needed.

  • And it's got this sort of continuous motion.

  • So we've taken a step closer in the route

  • that we need to actually get a sort of snake moving around the screen.

  • Does anybody have any questions so far as to sort of what we've been doing?

  • Looks like you haven't had any questions in the chat just yet.

  • Feel free, ask as many questions as you want.

  • I'll be monitoring the chat.

  • I'm happy to answer, happy to talk about whatever folks are interested in.

  • But if there are no questions, we'll just go, I think,

  • dive into sort of how we might do this by moving the snake up and down

  • as well as left and right.

  • "Would it work to do something like snake moving = key without the if else

  • if?"

  • Yes, in theory.

  • But this key can be anything, right?

  • So if I were to hit A, or Escape, or Spacebar, or Shift, or Return,

  • it's going to get that value.

  • So it would only work in this particular case if I input one of those two keys.

  • Now, we do have an else if key-- oh, actually, it's

  • not going to evaluate either these statements as true in that situation.

  • So it's just going to stay on whatever its last value was

  • even if we tap a weird key.

  • But, generally, that's not an ideal practice

  • if you expect that you can get a lot of other values

  • and if you're sort of parsing those values in fairly complicated ways.

  • In this case, it would kind of work.

  • But I would recommend against it probably.

  • It's all a little bit more obvious to know what's going on here, too.

  • Good question, though.

  • It's a good thing to sort of try and stay conscious of.

  • You know, a lot of like hash map related logic

  • can derive from that sort of way of thinking

  • which can optimize your workflow.

  • OK.

  • So let's go ahead and do the following.

  • I'm going to add a couple more conditions here.

  • So I added two more conditions.

  • And these two conditions are just to take into consideration

  • whether you're moving up or down.

  • So in this case, snakeMoving = up, snakeMoving = down.

  • And also, another thing about this particular instance

  • is we're using strings to test for values.

  • Sorry, we're using strings to assign sort of state to something.

  • Generally, you wouldn't always do this, because are a little bit more

  • weighty to use as state variables.

  • In this case, for the key checking, they've

  • defined a bunch of different states.

  • But if you have a sort of snake class or snake

  • object that has maybe 15, 20, 30 different states,

  • I would probably use an int or some value and not a string just

  • to save memory, just be a little more efficient in terms

  • of actually checking for comparison.

  • Because it is a little bit more intensive to check

  • against a string than it is to check against some integer

  • that we've defined.

  • For example, if I've declared a table up here-- by the way,

  • we'll get into tables in a little bit.

  • If I have a States table, for example, up, down, left, right, now I've

  • actually created--

  • sorry, and an up in this case.

  • These wouldn't exist.

  • But let's say one, two, three, four.

  • And let's say maybe those are defined elsewhere as up, down, left, or right.

  • Now, I can just, for example, up = 1, down = 2, et cetera.

  • Now, we can actually do a comparison against an integer rather than a string

  • and just save us a little bit of processing power.

  • But we won't worry too much about those kinds of optimizations

  • today, because those are not something that we need to worry

  • about for a project this small.

  • That would be for something a little bit larger scale.

  • But, anyways, the next part of our logic--

  • so we have, you know, the simple basically take a key,

  • assign whatever direction you're moving to its state, right?

  • We have some sort of state with our snake

  • whether its moving up, down, left, or right.

  • But I want to not only test for left or right.

  • I also want to test for up and down.

  • So we can kind of do the same logic that we

  • did before, else if snakeMoving is equal to right, we can do that.

  • Else if-- whoops, it wants to auto-indent

  • on me-- snakeMoving is equal to up, then do this, whatever that might be.

  • Else if snakeMoving is equal to down, then do the following.

  • Now, these are going to differ, because we are no longer going

  • to add or subtract from our x value.

  • We want to add or subtract from our y value.

  • Because our y value is what's representing our

  • up and down in the game.

  • So I'm going to go ahead, I'm going to copy this.

  • I want to just paste this in here, change snakeX to snakeY.

  • Notice that my editor lets me select multiple things and sort of edit them,

  • which is kind of nice.

  • And then this will function in sort of the same way,

  • except this needs to be plus.

  • So if we're going down, remember y increases as it goes down

  • and decreases as it goes up.

  • And, here, don't forget we're always going

  • to be at zero unless you replace our y-coordinate with the hard-coded zero,

  • to the y variable that we're storing now.

  • OK.

  • So let's go ahead and run this.

  • Whoops.

  • Then expected near snakeX on line 30.

  • Ah, right.

  • I forgot a then keyword right here, very important.

  • So, now, I'm moving left or right.

  • If I hit Down, notice that now we can move down.

  • I can move up.

  • And I can keep moving sort of arbitrarily around the scene,

  • so pretty cool.

  • So we've taken a few steps in the right direction here.

  • Anybody have any questions on sort of what's going on here at all?

  • And if not, we'll go ahead and maybe talk

  • about getting everything working in a grid perhaps.

  • OK.

  • Cool.

  • And this will be where we have to spend a little bit more time

  • thinking about sort of the data structures and algorithms that

  • underlie the game.

  • But right now, we just have continuous movement along either the x or y-axis.

  • But I want to make my game, first of all, a little bit easier to deal with.

  • Because if I can have it in a grid, I don't

  • need to actually worry about continuous collision detection which would

  • be maybe a topic for another stream.

  • But I can just have a very discrete layout of squares

  • and basically check is there a snake piece in this square,

  • if there is, and then do whatever logic I need to do.

  • You know, check to see whether my head is biting the tail of the snake

  • or whether there's an apple in that tile, for example.

  • We basically need to think about that way of looking at the problem.

  • But it's just a little bit easier to also render it and just

  • check for those sorts of things, right?

  • Collision detection, if it's continuous, we have a little bit more

  • to worry about.

  • We have to actually put our snake into a series of bounding boxes.

  • Every single piece of this thing needs to have a rectangle that

  • represents its collision.

  • And we need to check to see whether any of those individual rectangles

  • are overlapping some other rectangles.

  • But, again, we won't worry about that today.

  • Because Snake is a grid-based game, we actually

  • get a little bit of that for free.

  • And the same logic sort of applies to tile-based games, too, thankfully.

  • ""[INAUDIBLE] is used all the time in this context, not a time derivative,

  • which would be an infinitesimally small value,

  • whereas delta time is just a small, but real, value, something like 0.01667."

  • Correct.

  • Thanks, Andre, for clarifying that.

  • OK.

  • So, now, we can sort of talk about tables,

  • because tables are very important.

  • In C, you get these things called arrays.

  • And in Python, you get lists.

  • And in Java, you get arrays as well.

  • And JavaScript gives you arrays.

  • Generally, you see them as arrays or lists.

  • In Lua, there isn't the notion of an array or a list,

  • but rather a table which is actually a combination

  • sort of of lists and things called hash tables or hash

  • maps where you can assign some sort of key with some value

  • and pair them together, and then look them up with a very quick lookup

  • time versus having to traverse a list and look at each individual value.

  • Today, we're going to use the table data structure as a list.

  • So I'm going to say I want a local tile grid.

  • And, again, local, I don't know if I talked about it.

  • Local is basically just saying I want this variable

  • to be exclusively within the scope of where it's defined.

  • For example, if I were to declare a value called local val = 1

  • in my love.load function--

  • sorry about that.

  • If I have a variable val that's defined as local within this love.load

  • function, I cannot access val outside of this function.

  • So if I were to try to do val = 3 right here--

  • well, actually, in this case, because there's no typing,

  • it's just going to override this value.

  • But they're not the same.

  • But if I were to do print val, for example,

  • where it needs to access a value, access of variable that's

  • been defined already, it's going to throw me an error

  • and say, oh, val is not declared anywhere.

  • It doesn't recognize that symbol.

  • In the case of local defined here, because we're defining it

  • at the very top of the file outside of any functions,

  • it just means if I have other Lua files in my project

  • they don't have access to that variable.

  • But if I were to say, tile grid without local, then other files

  • that import this module or make reference to it

  • actually can get access to that value which we don't want.

  • So I'm going to do local tileGrid = and then

  • these two curly brackets here which basically tells me I

  • want an empty table here.

  • And then we're going to fill it with whatever information we need.

  • ""[INAUDIBLE] relatively bigger time difference."

  • Correct.

  • OK.

  • So I have a empty table.

  • And I want this table to be representative

  • of my game space, my game window.

  • And I want to figure out how many tiles we can actually

  • fit within my game window.

  • And so I'm going to, I believe it's, love.window.setDimension--

  • SetMode, sorry.

  • And then this is where you can actually specify how large your window is

  • when you open it.

  • And I think the default, is it like 800 by 600?

  • I'm not entirely sure what the default size is.

  • But let's just say I want it to be 1,280 by 720.

  • And then what were the other parameters?

  • I think it also has some flags.

  • We could say full screen is false.

  • Resizeable is true, those sorts of things, which allow us just to resize.

  • I'm actually going to set realizable to its default which I believe is false.

  • And then I'm going to run this, make sure I used it correctly.

  • Now, notice that it fills my entire screen.

  • Since my resolution on my monitor is currently 1,280 by 720, the window

  • itself now fills the entire monitor which is useful.

  • Now, that I know what my resolution is, I can say, OK, maybe

  • I want to have all of my squares be 16 pixels.

  • Or, actually, let's do 32 pixels.

  • Let's make our square size 32.

  • So I'm going to say TILE_SIZE gets 32.

  • And then just to have good style, I'm going to say window width is 1,280

  • and window height is 720.

  • I'm going to come down here, replace this with window width,

  • replace this with window height.

  • And now that I have these values, actually I'm

  • going to put this down here since this is gameplay.

  • Whoops.

  • That was not what I meant to do.

  • I didn't copy it.

  • Copy.

  • Paste.

  • I'm separating sort of the more rendering

  • stuff from the gameplay stuff.

  • So the snake speed is 100.

  • The window width and the tile size are up here.

  • I'm going to go ahead and say I need to figure out, basically,

  • how many tiles can I render on the x-axis

  • and how many tiles can I render on the y-axis.

  • Because I'll need to know this when I start assigning positions

  • of all the individual tiles, right?

  • So I'm going to say tiles--

  • rather, MAX_TILES_x = WINDOW_WIDTH divided by TILE_SIZE.

  • And MAX_TILES_y is WINDOW_HEIGHT divided by TILE_SIZE.

  • And assuming that those divide evenly, this should work out just fine.

  • I'm actually not sure offhand.

  • I think those divide evenly.

  • Let's figure that out.

  • So 1,280 divided by 32 is 40.

  • And 720 divided by 32 is-- oh, that's not, 22.5.

  • OK.

  • So I need to actually, because it's going to truncate it down to 22,

  • because it's an integer--

  • recall, 1,280 divided by 32.

  • No.

  • I apologize.

  • No, Lua actually does do floating point arithmetic I believe.

  • So toint I believe is the function plus 1.

  • So now this will take whatever the value of the expression that I'm

  • evaluating here is, assign it to a integer, and then add 1 to it.

  • So it's going to bring 22.5 to 22 and then add 1 Actually, as a sanity check,

  • let's make sure that's correct.

  • I'm actually not 100% sure offhand.

  • Lua, I'm going to say 22--

  • or I'm going to say, what's 1,280 divided by--

  • sorry, 720 divided by 32?

  • Print 720 divide by 32.

  • OK.

  • It does do floating point as I thought.

  • sorry

  • so integers and floats, they aren't really separate data types in Lua.

  • Lua has this data type called a number type, unlike languages like Python

  • which do have floats and ints.

  • And C, which actually enforces that you use floats and ints

  • and adhere to it more or less, well, with some type coercion,

  • the number type will just automatically do floating point arithmetic

  • when you divide.

  • So unlike some languages where it just gets turned into an integer,

  • in this case, it'll actually get turned into a float.

  • And actually I'm realizing now it's going to be like half of a tile

  • off of the bottom, which is going to look a little bit weird

  • if we get to the bottom.

  • So what I'm actually going to do is I'm not going to add one.

  • I'm just going to take the int of this, so that it'll

  • kind of come up from the bottom.

  • Because we're at a resolution where it doesn't evenly map 32 pixels

  • divided on both axes, we're going to deal with it.

  • There will be a little bit of black space at the bottom,

  • but that's OK, better than it sort of cutting off the snake when

  • it gets to the bottom of the screen.

  • OK.

  • So now we know how many tiles we need to render

  • on the x-axis, how many tiles to render on the y-axis.

  • I'm going to figure out a way to draw this.

  • I'm going to look at the chat really fast just

  • to make sure it didn't miss anything.

  • OK.

  • Hi, Nimble.

  • Good to see you.

  • Thank you.

  • OK.

  • Bhavik Knights says, "take the greatest common denominator?"

  • Yes.

  • OK.

  • Yeah, we could do that if we wanted to enforce our resolution sort of work.

  • But because, generally, 16 by 9 resolutions

  • are kind of the way that we want--

  • yeah, yeah tile size 40 would absolutely do it.

  • You don't really see tile sizes that are--

  • yes.

  • And, Jeffrey, you could use the round function as well.

  • That's correct.

  • But, sorry, the 40 pixel size on the tile it's something

  • you don't really see too often in games.

  • You could absolutely do it and be fine.

  • But a lot of the time, graphics, there's like sprites that you get online

  • or that you have your artist create adhere to--

  • oh, yeah.

  • Irene, good point.

  • Yeah, I would round to 23.

  • But, yeah, if you are looking to round something,

  • my approach is a little bit more--

  • oh, wait.

  • Actually, yeah, I would round to 23 which

  • was the same point that I made before.

  • Anyway, what I'm trying to say is 32 pixels, because it's- what is it--

  • a multiple of two, it's something that you'll just generally see more often

  • than something that's 40 pixels.

  • And it's not necessarily because of any technical reason, although this

  • was more so the case back in the day.

  • But, nowadays, you'll see a lot of retro art

  • is just kind of modeled after that old school approach.

  • And it's more of a convention than it is anything else.

  • OpenGL used to enforce that textures were a power of 2.

  • And old hardware just kind of enforced that blocks were drawn in tiles of 32,

  • just because that's the way the hardware was modeled.

  • Because it was just circuit, you know, binary circuitry effectively.

  • But, yeah, suffice to say, 40 pixels would work here.

  • But it's not a trend that you would often see,

  • I don't think, in games unless you wanted to do it as a creative decision.

  • And it wouldn't strictly map well to changing resolutions to weird other--

  • like, because resolutions can vary all over the place, too.

  • You kind of just have to take one sort of trend for your art or one size

  • and make these sort of decisions where you maybe

  • don't display half of the bottom row of the grid to make 720P work, basically.

  • So good points all around, though.

  • "True, we will need a custom function that

  • will get a second parameter to specify if we are going up or down?

  • True, yeah.

  • And you can you can also use math.ceiling or the ceiling function

  • and the floor function which they will give you the highest integer around it

  • and the lowest integer around it, respectively, regardless

  • of where the number actually is.

  • "Don't forget to change the tile size in the last function," says Steve.

  • OK.

  • Let's take a look here.

  • Oh, right.

  • The actual rectangle being rendered?

  • Correct.

  • Yes, good point.

  • Thank you for bringing that up.

  • And that is why we defined that up there,

  • so TILE_SIZE and TILE_SIZE, right.

  • So what we were getting at is now that we've defined how many grid spaces we

  • want sort of on the x and the y-axis, we should just sort of like sort of draw

  • out everything.

  • We can make sure that we've aligned everything appropriately.

  • So what I'm going to do is I'm going to use a for loop, a nested for loop.

  • I'm going to say for every sort of column in the game.

  • And then within each column, I'm going to say for every row, right--

  • I guess it's be backwards.

  • For every column and then every row in that column,

  • I want to render a tile in that x, y position, right, times tile size.

  • And we effectively looking in those increments of 32 pixels.

  • So I'm going to set my color for debugging purposes to blue, actually.

  • I'm going to say RGB to 1, right?

  • So if we render this-- whoops, toint--

  • oh, sorry.

  • Oh, sorry.

  • Is it to number?

  • Oh, sorry, no.

  • You have to use floor, math.floor in this case or math.round, not toint.

  • Is that correct?

  • Hold on.

  • Oh, yeah.

  • Because, duh, there's no int in Lua.

  • So you can't actually do that.

  • So in this case, I'm going to set it to math.floor.

  • And that should work.

  • OK.

  • So now we have a 32 pixel by 32 pixel blue tile moving around,

  • but we're not actually trying to figure out where the blue tile is.

  • I want to make it cyan.

  • I feel like that's easier to see on screen.

  • So I'm going to do something like this.

  • For y = 1 for the--

  • what do we define it as--

  • MAX_TILES_y do for x = 1 MAX_TILES_x do.

  • And what this is doing is it's a for loop.

  • This is Lua's version of a for loop.

  • But we're saying we have our initializer here.

  • For y gets 1 until it reaches the value of MAX_TILES_y

  • do whatever's in here and then the same thing

  • for every x-coordinate, x position.

  • For x = 1 until MAX_TILES_x, do this.

  • And so if our number of tiles that we can fit on, let's say,

  • the x-axis is like maybe 40, it'll go from 1 to 40.

  • And then same on the Y--

  • 1 to 30 or 1 to 40, what have you.

  • Don't forget to do--

  • OK-- making sure I'm up to date on the chat.

  • And then what I want to do is, for every one of these positions,

  • I wanted to call love.graphics.rectangle.

  • And I just want a line function right now.

  • I want to see my grid visually.

  • So I'm just going to use lined rectangles.

  • I'm not going to use filled rectangles.

  • And then I'm going to hide that left panel there,

  • so we have a little more room here to work with.

  • I'm going to say, because we need to do a x, y first,

  • I'm going to say x minus 1.

  • And this is an important thing to be conscious of, because normally

  • loops in Lua they start at 1.

  • And, also, array indices, tables, start indexing

  • at 1, which is a kind of a weird Lua-based thing.

  • So we want to actually subtract 1 from x and y,

  • so that it maps to our coordinate system.

  • Because our coordinate system actually starts at 0 just like most things in CS

  • usually do.

  • Lua is kind of a black sheep in that it conventionally and syntactically

  • a lot of its things start with 1.

  • So I'm going to say x minus 1 times TILE_SIZE, y minus 1 times TILE_SIZE.

  • I'm going to then say the size of that rectangle on the x-axis and the y-axis,

  • which should be just that.

  • And if I've written everything correctly,

  • I should now see a cyan grid.

  • And I do.

  • Perfect.

  • So this is an indication of what my grid looks like.

  • It kind of gets cut off at the bottom, because my monitor is not

  • quite tall enough for me to see the whole window.

  • But, clearly, we're moving not only anymore in just the, like, black space.

  • But we've outlined this grid that we're going to start adhering to.

  • Now, if you notice, the square doesn't adhere

  • to this grid in any kind of discrete way.

  • It's sort of continuously moving throughout the world space.

  • And you can see it can kind of get like caught up in between coordinate points

  • and a little bit weird.

  • So what we need to do is lock our square to this grid.

  • We need to hard lock it.

  • And I'm actually going to minus 1 off of the tile size on the y-axis,

  • just so we can see it a little bit better.

  • And it looks perfect.

  • There, now, it maps perfectly to it.

  • We're not going down a little bit too far.

  • But, yeah, again, we're going to need to align this square with our grid

  • so that it's completely locked into it.

  • Let's keep the grid active for a little bit.

  • And so what I'm going to do, actually, I'm going to take this code.

  • I'm going to take it out of here.

  • And I'm going to define a new function called drawGrid.

  • And I'm just going to paste that into there just like that.

  • And then I'm going to define another function called drawSnake.

  • And I'm going to call it here.

  • I'm going to say drawGrid, drawSnake, right?

  • Because I want the grid to draw first, and then

  • my snake to draw on top of the grid.

  • I'm going to paste that bit of snake drawing

  • code into the drawSnake function.

  • And then just so I know the difference, the clear difference between them,

  • I'm going to set the color to green on the snake just like that.

  • So if everything's correct, now I have a green snake rendering.

  • And it might be hard to see on stream, but I have a green snake rendering

  • on top of a blue gridded background.

  • So if anybody has any questions, definitely let me know in the chat.

  • The next thing that we'll tackle I think is

  • sort of aligning our snake with this grid, and then apples,

  • and then eating apples, and growing the snake.

  • Well, we'll start with just eating apples.

  • And then we'll add to the end of the snake.

  • So any questions at all?

  • I'll monitor chat for a little bit just in the corner of my eye while we--

  • oh, I also kept this local val = 1.

  • Let's get that out of there.

  • Don't need that anymore.

  • And, yeah, perfect.

  • So we have this grid table.

  • In this, we're not actually using the grid table for anything.

  • But we will start using the grid table.

  • I'm going to assume that each grid tile, each tile in my grid,

  • should have some value whether that's a 0, meaning that there's nothing there,

  • no snake, no apple, a 1, meaning that--

  • or maybe a 1 for the snake just so I can differentiate the color.

  • And then a 2 for the snake and then maybe a piece of the snake body.

  • And then 3 for the apple that I want to eat with the snake.

  • And let's just say that that's the extent

  • of our game, the mechanics of our game.

  • You have apples.

  • You have the snake head, and then the snake body which can move around.

  • Basically, what I want to do is I want to take this table.

  • I want to set all of its values to the 0, 1, 2, or 3.

  • And then I want to iterate over the table, basically, and do the same thing

  • that I did down here in drawGrid.

  • So what I need to do is initialize the table.

  • So what I'm going to do is I'm going to make a function called initializeGrid.

  • And I'm not going to make it take any parameters,

  • because our grid is global in this case.

  • So we'll just assume that it stays global.

  • I'm going to go ahead and kind of just do the same thing.

  • I'm actually going to just take this here, right?

  • I'm going to leave it in there, because we're

  • going to sort of need that same logic.

  • But I'm going to copy this.

  • And then I want to do something in here where I

  • can initialize the table to something.

  • Now, for each column I'm basically going to want a table.

  • In order for this to work, for my table to work

  • as a data structure for this example, I'm

  • going to want what's called a 2D array.

  • I'm going to want a 2D grid of integers, basically.

  • So it's 2D array in C. I get a 2D list in Python,

  • although Python's a very flexible language where you can have lists

  • of tables of all sorts of weird stuff.

  • Lua's kind of the same way, but we're going

  • to have just a bunch of tables within this table.

  • And then those tables are going to kind of represent our rows rather.

  • So I'm going to say table.insert into my--

  • what did I call it--

  • a tileGrid.

  • I'm going to insert into tileGrid an empty table.

  • So the result of this is that for every row,

  • I'm basically counting down on the different rows.

  • For every single row for every y value, I'm going to add to an empty table

  • into my grid, my initially empty grid variable or table variable.

  • And then within the x loop of this initialize function,

  • I know that I can index into this table my tileGrid at the index that y is.

  • Because whenever I insert a new table into that grid, tileGrid, I will have,

  • basically, tileGrid 1 if y is 1 equal to a new table.

  • If y is equal to 2, this will exist.

  • This will exist.

  • This will exist every time I increment y.

  • So for that reason, I can go into the tileGrid1--

  • or, rather, y, which will be 1, 2, 3, 4, or 5, until MAX_TILES_y.

  • And then I can insert some value there, because that's

  • where we're actually going to store the integers, the 0, 1, 2, or 3.

  • So I'm going to set them all by default to 0, right?

  • And I should be a little bit better about this.

  • What I'm actually going to do is I'm going to say TILE_EMPTY = 0,

  • TILE_SNAKE_HEAD = 1, TILE_SNAKE_BODY = 2, and TILE_APPLE = 3.

  • So, now, I'm not using what are called magic numbers, which we teach in CS50.

  • They are sort of constant variables.

  • And I can replace this with TILE_EMPTY--

  • so a nested loop basically going through our entire grid

  • for the size of the tiles on the y times x effectively

  • and then initializing all of those to 0, all of the tile indices to 0.

  • "I've never had the chance to get into Lua,

  • but I saw you easily control the snake with your keyboard.

  • Is it a code you did, or something the LOVE library allows you by default?"

  • How I was able to control the snake was the keypressed function here.

  • Basically, I'm looking to see--

  • anytime you press any key in LOVE, it's going to call this function keypressed.

  • And it's going to have some value that this key variable gets.

  • And it's going to be a string.

  • And in this case, I can check, for example, is it escape.

  • Did the user press Escape if they pressed a key?

  • And if it is, I can quit.

  • If it's left, right, up, or down, I can change some value

  • that my code lets me do.

  • So snakeMoving is a variable that I declared,

  • which allows me to move left, right, up, or down.

  • And then in my update function, which is a function that occurs every 1/60

  • of a second approximately depending on how your computer can handle LOVE2D,

  • which generally it's fine, every frame that

  • elapses love.update gets called with a parameter

  • called dt, which is the amount of time that passed

  • since this frame and the last frame.

  • And the snakeMoving variable will have left, right, up, or down

  • set to it, right?

  • So then if it's moving left, I can decrease this snakeX variable

  • that I declared earlier.

  • The snakeX variable is just my position in the 2D coordinate system,

  • so its value on the left and right, the horizontal axis.

  • And same thing with up or down-- letting me

  • manipulate my snakeY variable, so whether the snake is going up or down.

  • And then in this case, what we're doing is

  • we've defined some speed that I want my snake to move,

  • but this will be sort of relevant still.

  • But it won't work exactly the same going forward,

  • because we're going to start transitioning

  • into a grid and discrete movement.

  • But the SNAKE_SPEED variable here we're multiplying

  • by delta time, which means that no matter how many frames have elapsed

  • between now and the last frame, or sort of how many frames are skipped

  • if you're running on a machine that is a little bit slower potentially,

  • this value will be consistent.

  • It will consistently move you to the left,

  • right, up, or down by some value over time.

  • And so we, therefore, use snakeX and snakeY

  • in our love.graphics.rectangle function call down here when we call drawSnake.

  • And then notice that drawSnake itself is called up here with drawGrid

  • in our love.draw function.

  • So there's a lot of pieces.

  • We've put this all together.

  • This isn't something necessarily that LOVE2D has allowed us to do.

  • But it's made it fairly easy in as much as we can check for user input

  • and how much time has elapsed since the last frame.

  • And we have these nice convenient functions for drawing simple graphics.

  • These are the things that LOVE2D gives you.

  • And it's up to you to take them and program

  • them to fit the needs of your particular game, so good question.

  • "How to do a main menu, like a start, play, or option?"

  • That's a little bit more complicated.

  • You would generally use what's called a game state machine

  • and have a sort of certain object that keeps track of what state you're in.

  • So am I in the main menu?

  • Am I in the play state?

  • Am I in the game over state?

  • And if you are in any particular state, you

  • render a specific set of things, whether it's the interface options,

  • like pause game, start game, continue game,

  • whether it's a character running around a field, anything that you name it.

  • But, basically, you have different things

  • you render upon a different state being active at once.

  • And you toggle some variable that keeps track of what state you're in,

  • so good question.

  • We probably won't do this ourselves today.

  • But in a future stream, I'd be happy to talk about implementing a state machine

  • and going into maybe a menu for a game or something like that.

  • Shayna says, "did you align it vertically to the grid,

  • or it's not necessary when the snake moves?"

  • We did not align it vertically yet.

  • So if we run it, we can actually see that we can sort of move it

  • wherever we want to.

  • And see, now it's kind of stuck between different axes, different grid lines.

  • So it's up to us as our next step to actually do this alignment, which I'll

  • demonstrate how we do very shortly.

  • Irene says, "to draw row by row, otherwise

  • you draw a column by column I guess."

  • Bhavik Knight says, "I don't understand why column is the outer loop

  • and the row is the inner loop."

  • You can technically do it either way, but the trend is generally

  • to do the y on the outside.

  • Because if you were to visualize a table as being like this, for example--

  • and this is essentially what our table's going to look like.

  • If we have a smaller version of our table,

  • you can kind of see that, if we're iterating over the y,

  • we're going table by table by table.

  • Basically, if we were to look at our table row by row,

  • we're actually looking at this being an element, this being an element,

  • and this being an element.

  • So it's easy to iterate first over y, adding a table, adding a table,

  • adding a table on the horizontal and then,

  • once we've added those tables, to iterate over the x

  • and populate them with these values just like this if that makes sense.

  • It's generally easier just to think about the problem that way,

  • but you can, I believe, just technically do it either way and just

  • have your rendering code address it properly.

  • But this is what you'll see vastly more often is the y first and then the x.

  • Good question.

  • OK.

  • So we have a grid available.

  • Let me just make sure I haven't added any unnecessary code.

  • So, now, we've done a little bit of modularizing our code.

  • We've taken some of these statements out.

  • We've put them into functions.

  • We have an initializeGrid function, drawSnake, drawGrid,

  • all these sorts of things.

  • In the love.load, this is where I actually want to initialize the grid.

  • So I actually have to call it somewhere before it'll actually execute,

  • because all we've done is just initialize a function--

  • or rather, sorry, declare a function.

  • But we haven't called it.

  • So I'm going to call the initialized grid function here,

  • which will actually run that code and populate our tile

  • grid with all that information.

  • So, now, we have a grid of 0s, effectively,

  • being the size in the x and the y of our game window.

  • So what I want to do is, in our drawing code,

  • this is where now I probably want to-- or rather, for the drawGrid function,

  • this is probably where I want to check to see what exists in that grid, right?

  • What's going to be the value at any particular index in our 2D array?

  • Because if it's a 0, remember, I want to draw an empty tile.

  • And if it's a snake head, I want to draw the snake head.

  • If it's a snake body, if it's an apple, I

  • want to draw different things, basically change the color of my OpenGL state,

  • of my LOVE2D state.

  • So I can do if the tileGrid y, x is equal to, let's say--

  • how did I have them--

  • TILE_EMPTY, do-- sorry, not do, then.

  • I always get those confused.

  • If the tileGrid at index y, x-- which, remember, arrays slash tables,

  • they start at 1 in Lua by default.

  • If it's equal to TILE_EMPTY at whatever y, x,

  • I want to call love.graphics.rectangle line.

  • And then I'm just going to draw a white grid, actually.

  • So I'm going to set this up here to be 1, 1, 1, 1,

  • the love.graphics.set color.

  • That will make the entire grid white.

  • So all 1s is white.

  • And all 0s is black.

  • And, specifically, these three, this value here is just the transparency.

  • This has to be 1 for it to be pure white.

  • If it's any lower, it will just be kind of a transparent white.

  • But black will render the same either way whether this is 1 or 0.

  • I'm going to draw the x minus 1 times TILE_SIZE y minus 1 times TILE_SIZE.

  • Remember-- because the coordinate system is 0-based, not 1-based,

  • even though we're 1-based currently in LOVE2D's tables.

  • And then I'm going to do TILE_SIZE and TILE_SIZE, right?

  • So if this is correct, then the grid is only

  • going to draw if that index at that grid is a 0,

  • is TILE_EMPTY variable, which we have declared up here to be 0.

  • So if I run this, notice that it indeed works.

  • We now have a white grid instead of a cyan grid.

  • And if I were to go here, else if love.graphics-- or sorry,

  • if tileGrid y, x is equal to TILE_APPLE, for example,

  • then I want to do this same exact thing.

  • I'm just going to copy and paste it.

  • Actually, no, the same thing is going to happen.

  • Sorry.

  • I have the logic mixed up.

  • This is the important part.

  • We're going to draw this either way.

  • Although that's not technically true, because sometimes we

  • want to draw a filled and sometimes we want to draw a solid.

  • OK.

  • Never mind, scratch that.

  • I'm going to do this.

  • I'm actually going to take this setColor function.

  • That's going to go into the if statement.

  • So change the color to white for the grid.

  • And notice that I'm using comments with a dash dash.

  • So that's how you do comments in Lua is using the dash dash.

  • To do block comments would be dash dash square brackets square bracket.

  • And then now anything within that will be considered a block comment.

  • But I'm just going to use a single line comment for now.

  • So we're going to set it to white and then draw a line

  • rectangle if it's equal to TILE_EMPTY.

  • But if it's equal to TILE_APPLE-- which you don't actually

  • have any in the scene yet, but we will add one just to demonstrate.

  • love.graphics.rectangle fill, we want it to be a solid red rectangle, not

  • a line rectangle in this case.

  • I'm going to the same exact thing--

  • times TILE_SIZE y minus 1 times TILE_SIZE and then

  • TILE_SIZE and TILE_SIZE, right?

  • And then I want to also make sure that I set the color to red,

  • which is the 1 in the R, 0, 0, 1.

  • So red is 1.

  • Green is 0.

  • Blue is 0.

  • Alpha is 1.

  • Change the color to red for an apple.

  • So just a little bit of commenting, documentation goes a long way.

  • Make sure, as you're writing your application,

  • that you try to comment things that are maybe not super obvious in this case.

  • These are pretty fairly obvious things to comment,

  • but it's still helpful at a glance to know what's going on.

  • Elias says, "do you stream every Friday?"

  • Not yet.

  • This is only our second stream, but the goal

  • is definitely to establish a consistent streaming schedule.

  • And I'll probably be doing a lot of game related stuff.

  • But we have other people that were talking about

  • to maybe do some web development or Python stuff,

  • maybe even get some GitHub stuff.

  • So definitely, if you have any ideas and you

  • want us to stream any other type of content, let us know.

  • Write us here or comment on our Facebook.

  • And just let us know sort of what would be useful.

  • Anyways, the logic is now in place for drawing an apple.

  • So if we run this, notice that it's not going to draw any apples at all.

  • Because we don't have any grid indices in our table

  • that are set to 1-- or, sorry, it's 3 I believe is what I made it.

  • Apple to 3, right?

  • So in order to do this, we can initialize our grid.

  • Let's go down to the initialized grid function.

  • As part of our grid initialization, let's

  • just say I want to choose a random x, y and make that an apple

  • so this is actually pretty easy to do.

  • I'm just going to say local appleX, appleY = math.random MAX_TILES_X

  • and then math.random MAX_TILES_Y.

  • And notice that I'm doing dual assignment here.

  • I have appleX, appleY with a comma.

  • I'm assigning it to math.random MAX_TILES_X

  • and math.random MAX_TILES_Y.

  • This basically lets me create two variables and initialize two variables.

  • And the two will match up, assuming that there's the same number of arguments

  • on each side or variables on each side.

  • And there are certain situations where this won't exactly

  • work if you have variables that equate to multiple return types, return

  • variables.

  • But we won't get into that.

  • But basically what this is doing is it's saying get an x and a y-coordinate

  • for the apple that we want to place.

  • And then give us a random value between 1 and some value, so MAX_TILES_X.

  • So give us some value between 1 and MAX_TILES_X,

  • which will be anywhere on the horizontal plane within our game.

  • And then same thing for the y-axis--

  • math.random MAX_TILES_Y.

  • And if I go into tileGrid y, x, I can assign it to--

  • sorry, appleY, appleX, I can assign it to TILE_APPLE, right?

  • So I've initialized these two, this X and the Y. And I've added them.

  • Remember Y comes before X.

  • And then I run it.

  • And then, boom, I get a random apple there

  • or some representation of what an apple is in our scene somewhere.

  • And obviously, if I try to go over to it with my current implementation,

  • nothing's going to happen.

  • I should just overlap it, but that is how

  • you would randomly assign something in your game, very

  • simple random generation.

  • OK.

  • So we've done that.

  • Now, something to think about--

  • every time I run this, pay attention to where the apple is.

  • Notice that it's going to be in the exact same spot every time.

  • That's not very random, because the random number generator in LOVE2D

  • gets initialized to some value that's consistent every time we run the game.

  • So appleX and appleY are always going to get the same value no matter

  • how many times you run this application, even though it says math.random.

  • In order to fix this, we need to do what's called seeding our random number

  • generator.

  • We give it a seed value.

  • And that seed value is going to influence the algorithm that

  • takes place underneath the hood when it comes to actually generating

  • our random value, our random x and y value.

  • The random number generator does a bunch of stuff

  • where it uses some math that gives us the illusion of a random number It's

  • not really random in the technical sense.

  • But it works well enough for us to perceive it as being random.

  • Elias, "thanks for teaching us.

  • Keep up the amazing--" thank you so much for your comment, Elias, really

  • appreciate it.

  • Thank you for tuning in today.

  • I'm going to go ahead and do what's called seeding this random number

  • generator.

  • I'm going to do it in my load function, because that's where it should ideally

  • take place before I initialize the grid, obviously,

  • so that it doesn't use this default seed and then seed the random number

  • generator, right?

  • So I can do math.randomseed.

  • That will allow us to seed the random number generator

  • and give it some value.

  • And then we need to actually give it a value that varies

  • every time we run this game, right?

  • Because if we seed the random number generator with some 0,

  • 1, 2, 3, value, that's effectively the same thing as just letting it

  • give us a random seed that its pre-chosen, right?

  • And so one of the things that we can do to accomplish this is just

  • give this function, this value, called os.time,

  • which is a function that returns the number of milliseconds that

  • have elapsed since, I believe, it's the creation

  • of the first Unix system, which should be some along value that

  • looks that basically looks like that which

  • will be different every single time we run this application.

  • So the next time it might be like that or, you know, whatever.

  • But it will always be different every time when we run our application.

  • So now that I've called math.randomseed os.time,

  • notice that now the apple's in a different spot.

  • And if I run it again, the apple's in another spot.

  • And if I run it again, the apple's in another spot.

  • And so now we've effectively accomplished actually

  • randomly assigning the apple to some location.

  • It's not consistent across every run of our game

  • which would not be interesting.

  • Although you would want this.

  • You would want this behavior if you were trying to debug something that you see.

  • Let's say your debugging Minecraft.

  • And you have a particular world, and you notice this weird graphical glitch.

  • You might not necessarily run into it if you just

  • allow it to initialize to some random value every time.

  • You want to consistently determine how to generate that world the same way

  • every time.

  • And so you would keep that same seed and then

  • rerun it over and over again to debug.

  • The JP Guy says, "hey, everyone, has the stream been live for long?"

  • We've been live for about an hour and a half.

  • So we've been coding Snake from scratch.

  • I'll put all of this in a GitHub repo, so everybody can take a look at it.

  • It'll be github.com.

  • Let me see if I can log in here.

  • Whoops.

  • It's a little that small, or a little bit large I should say.

  • I want to sign in.

  • It's probably going to make me use two-factor authentication,

  • but that is OK.

  • Yup, authentication.

  • So give me just a second.

  • I'll put this on GitHub, so everybody can download it.

  • Let me just go off of the screen here for just a moment

  • while I get this all set up.

  • One, two, seven, eight , six, da, da, da.

  • Verify.

  • OK.

  • I'm going to create a new repo.

  • What will we call this?

  • Twitch stream-- no, we'll call it Snake 50,

  • screw it, Snake demo talk during Twitch livestream.

  • Make this public, create repo.

  • So I don't know if I have Git actually installed on this account.

  • But if you go to this repo, github.com/coltonoscopy--

  • here, let me bring up my thing here. github.com/coltonoscopy/snake50,

  • you will see that live.

  • And I'll post all of the code to the repo in real time.

  • I might have it available.

  • So I do have Git on here, but I don't think it's functioning.

  • So I'm going to commit and push everything

  • after we're done, because I think I'll get an error if I try to do that.

  • I'll try.

  • So let's see.

  • I'm in-- OK, blah, blah blah.

  • Git init, git add, git commit add, first commit, git remote add origin.

  • I apologize if this is a little dead at the moment. git push origin master,

  • will it work?

  • Crap.

  • So this is where I run into issues.

  • Yeah.

  • Because I have two-factor authentication enabled to my account,

  • there's a few weird things I have to do to get this working.

  • And I'd rather not take too much time.

  • But I'll do this on my other account that I have that I'll set up on,

  • and we'll be good to go.

  • But for now, I'll just run through the code, I guess, a little bit.

  • I think you can also skim through the VOD if you want

  • and sort look at what's happened.

  • But-- bunch of constants for our tile types,

  • our resolution, how many tiles on the x and y-axis

  • we're going to render in a grid, variables

  • to keep track of whether our snake is moving in a particular direction, the x

  • and the y, the grid itself, a function to initialize everything in LOVE2D.

  • So if you're not familiar, LOVE2D is what's

  • going to be the framework which we're using to do all this game programming.

  • And then keypressed, which will press a key,

  • move the snake left, right, up, or down.

  • Update that snake, edit its x and y based on this thing called delta time,

  • which is a floating point value which will give you

  • the amount of time that's elapsed since this frame and the last frame.

  • Functions to draw a grid, which is just a

  • nested for loop to iterate over our grid and its numbers

  • and then render different squares depending

  • on what we've set in that grid.

  • The snake, which will be a rectangle which has our snakeX and snakeY.

  • And then the function that initializes our grid set it to empty.

  • Sets it to a bunch of 0s.

  • "I'm familiar with using C#, but I recently took an interest in this CS50

  • into course."

  • Oh, awesome.

  • Glad to have you, JP Guy.

  • If you want, I teach this framework on an edX course.

  • So it is cs50.edx.org/games.

  • And we go through all this stuff and make a bunch of different games based

  • on sort of like famous retro games, like Super Mario, Pokémon, Zelda,

  • all those kinds of games.

  • And we use LOVE2D and Lua for most of it and some UNITY towards the end,

  • which you're probably familiar with if you've programmed in C#.

  • Jeffrey Hughes, yes.

  • JP Guy, thanks for sharing the link to the GitHub repo in the chat there,

  • appreciate it.

  • OK.

  • So let's go back to the game.

  • So the next thing we need to do-- so we got our random apple working fine.

  • So now we have our grid that's being rendered.

  • We have a snake which is moving continuously,

  • which we're going to change.

  • It's going to be moving discretely before long.

  • What's the time?

  • 4:38?

  • Good.

  • OK.

  • We still have an hour and a half about.

  • And then we have our apple, which it's basically

  • iterating over this massive grid here and then

  • just checking each individual spot.

  • What's this equal to?

  • Is this a 0?

  • Then render a white empty rectangle, 0, 0, 0, 0, 0.

  • And then eventually, it gets down to here.

  • And this is actually equal to a 3 I believe.

  • Yeah, 3.

  • And instead of drawing an empty white rectangle there,

  • it draws a filled red rectangle.

  • So that's effectively what we're doing.

  • You can kind of visualize this grid as being a grid of numbers

  • that we've just converted to different types of squares.

  • And that's the overall basis behind Snake.

  • Jeffrey Hughes, ""[INAUDIBLE] I love the way you teach.

  • You sound just like Bob Ross."

  • Awesome, thank you.

  • Well, hopefully, I'll maybe be good falling asleep voice for people then.

  • OK.

  • Let's go ahead and figure out now how I want

  • to take this snake that we have moving around

  • and then, instead of making it continuous, I want to make it discrete.

  • I want it to move sort of grid unit by grid unit,

  • not just like continuously, so that it won't get trapped in

  • between different grid lines, right?

  • It'll always be aligned perfectly.

  • It'll be what's called axis aligned, I guess, in this case.

  • Well, this isn't strictly axis aligned, but discreetly aligned

  • based on some tile increment, which in this case is 32 pixels.

  • So that means that we're kind of going to have to get rid of our snake

  • in the sense that it's no longer going to have just an independent x and a y.

  • It's going to have a grid x and a grid y.

  • I guess we're not really getting rid of it

  • as much as we're going to have to do some pretty significant changes to it.

  • First and foremost, it will no longer do what

  • it's been doing in the update function, whereby the x and the y

  • get updated with the snake speed variable.

  • That's no longer going to be the case, right?

  • The logic is going to kind of be the same.

  • But instead of adding delta time to the variable

  • and then adding or subtracting that from the x and the y,

  • we're going to effectively need to have a timer, which that timer can then

  • see has a certain amount of time elapsed in between each individual tile

  • movement.

  • Jeffrey Hughes, "not in that way, but mostly you help people

  • love what you do."

  • Thanks, Jeffrey, really appreciate it.

  • Thank you, man.

  • So instead of having this continuous value, which

  • is going to be moving very cleanly throughout the space,

  • we are going to move it in chunks.

  • So we're going to need to align it with our grid.

  • And to do that, we're going to have some sort of timer, some sort of amount

  • of time in which we determine exists between each separate discrete movement

  • on the axis.

  • So let's spitball and say, maybe I want the snake

  • to move one increment every half a second for now, 500 milliseconds.

  • So we're going to need a couple of things.

  • So I'm going to define a constant.

  • And by the way, these are being capitalized,

  • because these are variables that I'm basically saying

  • they should never be altered at all.

  • In an actual game that you ship, these will probably

  • get altered, honestly, 1,280 by 720 being your window width and window

  • height.

  • Because people change their resolutions.

  • But most of the other ones are not going to be changed.

  • This MAX_TILES_X and MAX_TILES_Y would also probably

  • be changed if these got changed.

  • But we're going to assume that for the sake of this game, this demo,

  • these are all going to be constant.

  • So anytime you see something capitalized and underscored like this,

  • it's a constant variable that will never get changed or should never get change.

  • There are some languages and environments

  • in which you can declare that something is a constant and can't be altered.

  • But in this case, Lua is a dynamic programming language.

  • It's interpreted.

  • And at runtime, it does not enforce whether some variable

  • is to never be altered or not.

  • it.

  • Will just accept whatever variables you put in your namespace

  • and let you do whatever you want with them.

  • And so I'm going to set a variable that I

  • want to assume I'm not going to edit for now, although this can be adjusted

  • later for gameplay purposes.

  • I'm just going to use SNAKE_SPEED actually.

  • And I'm going to set that to 0.5.

  • Now, 0.5, I'm setting it to 0.5 instead of 100,

  • because, previously, we used 100 to be the number of pixels--

  • excuse me-- per second that we want it to move.

  • But, now, I'm going to basically say the snake speed or, I guess, maybe--

  • yeah, snake speed is going to be 0.5.

  • So time in seconds that the snake moves one tile, right?

  • So snake speed-- 0.5.

  • And so, now, I can have sort of a counter,

  • like a timer variable that basically takes delta time, which we're

  • using in update and previously we were multiplying by our speed

  • variable, our speed constant.

  • I can take delta time and just add it to counter every frame,

  • to this counter timer variable, every frame.

  • And then once it's added up to 0.5--

  • because, remember, delta time is going to be

  • given to you in seconds as a floating point value,

  • so usually point 0.1667 whatever every frame.

  • Once that's added up to 0.5, that means that half a second has elapsed.

  • And therefore, we can move the snake one tile in whatever direction, right?

  • So to go along with that, we're going to need some value.

  • I'm actually going to declare it underneath our other snake

  • local variables, or our variables that actually change.

  • And I'm going to say snakeTimer, I going to set that to 0.

  • OK.

  • So, uh-oh, we have a visitor.

  • Everybody say hi to Dan Coffey in the chat, everybody.

  • This is actually Dan Coffey.

  • He's a man of few words.

  • So we have a timer that we've declared.

  • So we need to actually sum dt to that.

  • So all we have to do in this case is say--

  • whoops, sorry, got a little lost there-- snakeTimer = snakeTimer plus delta

  • time.

  • Remember, Lua does not have the plus equals.

  • Otherwise, you'd probably use that.

  • So snakeTimer = snakeTimer plus delta time.

  • And so now all we need to do-- it looks like you got a little bit of love

  • there in the chat, Dan.

  • JP Guy's showing you some love.

  • We're going to say if snakeTimer is, remember, greater than--

  • and then up here we declared snake speed, right?

  • We'll say greater than or equal to SNAKE_SPEED,

  • then now this is where we're going to move our snake.

  • Except we're not going to move our snake along pixel wise.

  • We want to move it in increments of 32, so that it stays adhered to our grid.

  • Looking good, Dan.

  • Got a lot of fans for Dan.

  • snakeTimer is greater than or equal to SNAKE_SPEED.

  • We're going to repurpose the snakeX and snakeY

  • that we had before, except now these are going to be indices into the grid,

  • right?

  • So I'm going to say 1, 1.

  • Because, remember, everything is 1 indexed in Lua.

  • And these are going to map to our grid positions later on.

  • We're going to actually keep a record of where our snake is in the grid as well.

  • If it's greater than or equal to SNAKE_SPEED, then snakeX--

  • oh, and then here we also have to check to see what direction we're moving in.

  • So if snakeMoving is equal to up, then else if snakeMoving is equal to down,

  • then else if snakeMoving if equal to left, then else--

  • we're not going to check for the last condition,

  • because it will always be one of these four, up, down, left, or right.

  • So it'll be right otherwise.

  • snakeY equals snakeY minus 1.

  • snakeY equals snakeY plus 1.

  • snakeX equals snakeX minus 1.

  • And then lastly, snakeX equals snakeX plus 1.

  • OK.

  • So now we have our snake moving with some timer.

  • And it's going to move every time this snake timer increases past the snake

  • speed constant that we declared before.

  • "There can only be one Bob Ross, but you are pretty awesome.

  • Thanks so much."

  • Thanks, [INAUDIBLE].

  • I appreciate it.

  • I'll take being second to Bob Ross.

  • That's OK.

  • OK.

  • Well, there's a couple of things here.

  • So I'm going to run it.

  • It should run.

  • It should not run, main 109 saying--

  • oh, I think I missed a bit of syntax.

  • Let's see.

  • Else-- oh, right.

  • I forgot the end here.

  • So every if needs to have an end.

  • If you see some EOF issue, then you do need an end statement there.

  • Irene, thank you.

  • Irene, appreciate it.

  • OK.

  • So now it's fine.

  • So now notice, though, we're not moving discretely,

  • because we're still keeping track of snakeX, snakeY, still rendering them.

  • But we did get a bit of a delay, a 0.5 second delay before we started moving.

  • So that part is working.

  • But we don't want to move in increments of one.

  • We want to move in increments of 32.

  • And so this is where we need to go in our update function--

  • sorry, rather, our drawSnake function.

  • And instead of drawing snake here, the snakeX, we're

  • going to treat x and y as grid indices.

  • So I'm going to say just like we did before--

  • because, remember, coordinate systems are 0 indexed,

  • but tables are generally 1 indexed.

  • So snake minus 1 time TILE_SIZE. so now we're multiplying it by TILE_SIZE.

  • So this 1, 2, 3 pixels that we're moving before continuously

  • are now going to map to perfectly slotted places in the grid.

  • We'll do the same thing here.

  • snakeY minus 1 times TILE_SIZE.

  • I'm going to bring this down here just for readability.

  • We're going to run this.

  • And now notice, though, we're moving so fast

  • that we can't even see the green square after a certain amount of time.

  • It just moves astronomically fast.

  • But we're supposed to be moving, you know, every 0.5 seconds.

  • So what's the issue here?

  • Does anybody know what the main problem is with my logic?

  • It should be working, right?

  • Remember the end at the end of the if, else if, yeah.

  • "What extensions do you use to code in Lua in VS Code?"

  • I use the Pixelbyte LOVE2D extension which allows

  • me to hit Command L on my MacBook.

  • And it will just run.

  • Rather than having to go back to my terminal, which you

  • see me been kind of doing it hidden.

  • But if I were to instead do this, it does the same thing, right?

  • But I don't have to actually go to my terminal.

  • If I had Command L with the extension I have, it gives me that for free.

  • So that's the main extension that I use for Lua.

  • And I think there's other ones.

  • But, for example, if I were doing C# stuff,

  • OmniSharp is a really good extension which allows you to do sort of a live

  • compilation and debugging, which is pretty cool.

  • But, yeah, there's clearly an issue here with my movement of the snake, right?

  • And that is simply the fact that when my timer gets to be 0.5 and higher,

  • it never resets to 0.

  • So in this case, I just want to make sure that whenever you get to 0.5,

  • I go back down to 0.

  • I reinitialize my timer.

  • So I'm going to set my snakeTimer to 0 in here.

  • So if we get to the point where I've gone up to 0.5 or 0.51

  • or whatever it equates to on that particular frame,

  • I'm going to be back to 0 on the next frame.

  • So if I rerun this, notice that now we are indeed

  • moving along the x direction.

  • And if I hi Down, which I just did, notice that now I'm moving down.

  • And I hit Right, I go right, Left, I go left.

  • And then Up, I go up.

  • So now it's kind of getting there.

  • We're on the right track.

  • It's a bit slow, though, to be quite honest.

  • So I'm going to bump it down.

  • Let's say 0.1, see if that's good enough.

  • OK.

  • Not bad, right?

  • So now this is more or less a workable solution, right?

  • And so if I walk over the apple, nothing happens.

  • We don't have any logic just yet to keep track of whether we've

  • consumed an apple or not.

  • That's going to be kind of one of the next steps, right?

  • The next thing that I probably want to do is grab the apple.

  • And if I eat the apple, I want to respawn a new apple somewhere, right?

  • And then the next part after that is going

  • to be I need a way to map my snake onto the grid,

  • so that I can check in the grid at any given index

  • whether that's owned by a snake tile or not.

  • So that I can, therefore, do collision between my snake head

  • and any part of my snake body if that makes sense.

  • So anybody, let me know if you have any questions thus far

  • about how this works.

  • Otherwise, we'll do a couple of things to get started

  • on actually getting the apples to work.

  • I thought I had a water in here somewhere.

  • Did I?

  • Did I have a water in here, by chance?

  • That's OK All right.

  • Actually, I'm getting a little bit parched.

  • I think I'm going to grab--

  • SPEAKER 1: [INAUDIBLE]

  • COLTON OGDEN: It's over--

  • where is it?

  • SPEAKER 1: I'll get it for you.

  • [INAUDIBLE]

  • COLTON OGDEN: You sure?

  • Oh.

  • OK, thanks.

  • Appreciate it.

  • OK.

  • Oh, thanks.

  • Sweet.

  • All right, it's thirsty work.

  • Oh, sorry.

  • Irene says, "does the square continue moving when you're not pressing?

  • I can't tell from here."

  • Correct, yeah.

  • The square will always move, because in our logic

  • we've just basically said when the key is pressed just change the state.

  • But it's not actually checking an update whether the key is being pressed.

  • It's only checking that state variable.

  • So if it's up, down, left, or right and the timer

  • has gone past a certain amount, it'll move it on its own basically.

  • OK.

  • So I'm kind of getting a little bit tired of looking at the grid I think.

  • So what I'm going to do is I'm actually going to nix drawing the grid.

  • Actually, well, no, that's not true.

  • Because what I need to do is I need to draw the grid.

  • But also, if the grid contains the snake and/or the apple, which eventually it

  • will, I still will need to draw it.

  • So what I'm going to do is I'm just going to get rid of this bit of code

  • here.

  • I'm going to comment it out.

  • I can do command/ on my computer.

  • And so now it's a comment, and so it won't actually execute.

  • So, now, I don't have a grid, but I do have a black background and the snake

  • sort of going around.

  • And I can go on the apples or whatnot, but it's not

  • interacting with the apple.

  • So let's go ahead and figure out how we can get the apple rendering--

  • or, sorry, get the collision between the snake and apple working,

  • and therefore get new snake components and then maybe even have a score.

  • So the score will be easier to start off with.

  • Adding a new element to the end of the snake

  • is going to be a little bit trickier.

  • Oh, and there's actually one more thing that we should take into consideration.

  • And that is the fact that as it stands, if I go up,

  • I don't actually loop back from the bottom.

  • I have to hit down again, and then eventually my snake will come.

  • So what I'm going to do is a simple set of if

  • statements where I have my snake being edited, which should be here.

  • So in my update function, should I put this all into its own function?

  • Most of it's going to be update code for the snake anyway.

  • As your functions get longer, it's generally

  • better practice to sort of break them out into different pieces.

  • But I'll keep most of it in here unless it gets really unwieldy.

  • So what I want to do is notice that we're just

  • kind of moving the snake minus 1 and snakeY plus 1, snakeX minus 1,

  • snakeX plus 1.

  • We're kind of just moving them without really checking for bounds,

  • for checking boundaries on whether we're at the edge of the screen on the up,

  • down, left, or right axes.

  • So what we should do is do some simple checking.

  • If I want to move up, for example, and I'm at index one,

  • well, I should actually move my snake to the bottom index.

  • And if I'm, for example, moving to the right

  • and I happen to hit the last index in the grid,

  • I should probably move my snake back to the index one on the x-axis.

  • So it looks like I went from right to left.

  • So I looped around, came full circle.

  • So let's do that logic here.

  • So I'm debating whether we should make the snake part of the grid first

  • or whether we should just do it.

  • I'm thinking.

  • OK.

  • So we can get rid of draw snake altogether.

  • So let's do that.

  • So let's make the snake part of the grid first,

  • because this will actually be fairly easy to do.

  • If I go to else if in my render function, else if tileGrid y,

  • x is equal to TILE_SNAKE_HEAD.

  • Then I'm going to just copy and paste the apple code.

  • I'm going to set that to 0, R, G.

  • So I'm going to set it to that.

  • But I'm going to make it kind of cyan colored a little bit

  • and just have that be my code here.

  • So I'm actually not going to draw the snake anymore as a separate function.

  • We're just going to make the grid draw the snake.

  • The snake is actually going to be part of our grid

  • just like the apples are just to keep everything kind of simple.

  • And then that way we can check to see.

  • We can look at any particular index in the grid at y, x

  • and know, oh, that's actually a snake body part, for example.

  • And then, yes.

  • Jeffrey Hughes, "you can create a function

  • to remove the redundancy of that code."

  • Yes.

  • Yeah, you probably could.

  • In this particular instance, these are kind of the same.

  • It differs a little bit if you have this code here,

  • which is drawing it as a line.

  • But you could define a function called draw tile,

  • for example, that takes in the draw mode of this, the x and the y,

  • and then the color and then have it do that as well.

  • Just for the sake of time, we won't modularize things to a super extent.

  • Maybe if we have some extra time we'll do that as a creative exercise.

  • But, yeah, good point.

  • You very much could and should do that in a proper, like, full project.

  • Just for illustration, though, we're just going to use a little simple copy

  • and paste just in sake of time.

  • Because we have about an hour left, and it'd be nice to get as close to the end

  • as we can.

  • OK.

  • So we have TILE_SNAKE_HEAD being here.

  • And so now what we want to do is set the--

  • we need variables, essentially, to keep track of where the tile snake head is.

  • And we can use snakeX and snakeY to do that.

  • So do we want to do it that way?

  • Because the snake is going to actually have to move.

  • So each individual-- oh, wait.

  • No, that's not true.

  • That is not necessarily true, because, yeah, we

  • will need to keep track of each individual snake piece.

  • So let's go ahead.

  • Like I said, I haven't spent a terrible amount

  • of time working through this, because it'd be fun to I think kind of figure

  • it out on the fly.

  • We're going to make a new variable called

  • snakeTiles, which is going to kind of go hand-in-hand with our tile grid,

  • I guess.

  • And then we're going to initialize the first piece here

  • to be snakeX and snakeY.

  • And actually this should go below this piece here, snake data structure.

  • And then every time we want to add a new piece,

  • we're effectively going to take whatever the last piece is,

  • and then we're just going to add--

  • well, what we're effectively going to do is move this down to here.

  • And then we're going to take the next position,

  • depending on whether we're moving left, right, up, or down,

  • and we're going to fill it in here.

  • But since we only have one index at the moment,

  • we only are just going to put in the snakeX and the snakeY right there.

  • So that works just fine.

  • We might be able to even just keep track of this snake data structure

  • independent of the grid, but it kind of makes

  • it easier for checking the grid for different y,

  • x if we also just kind of add them to the grid.

  • So we'll try it that way.

  • OK.

  • So we have our snake data structure.

  • So in this case, we only really need to keep track of the head of the snake

  • in terms of moving that.

  • Because we're going to move that.

  • And we're going to take the tail, and we're going to basically take the tail

  • and put that where the neck of the snake is.

  • Basically, where the head just moved, we're going to take the tail

  • and move it there.

  • And that'll have the effect of sort of moving

  • the snake all around the map pretty seamlessly

  • without having to manually move every single one of these snake pieces.

  • Because you can sort of visualize if I can pull up Chrome and have

  • the image here, snake game images.

  • If the head is here-- which is kind of actually

  • hard to see in this particular instance.

  • If the head is here and it moves one up this way,

  • really the entire snake is going to stay the same overall.

  • Excuse me.

  • The vast majority of it's going to stay the same.

  • The only thing that's going to change is this tail right here, right?

  • So we can effectively take this tail, remove it from the bottom,

  • and then plug it here.

  • And then that will have the effect of our snake moving one forward

  • when all we've really done is just move the head forward and taken the tail

  • and sort of made it the neck of our snake.

  • So as a performance optimization, now we don't have

  • to manually move every single tile.

  • If our snake is moving, we don't have to move every single one of them

  • every frame.

  • We can just move one piece at a time.

  • So this is a bit of an optimization and just

  • like an easier way to program the snake as well.

  • So that's the algorithm that we'll take to actually solve this.

  • So this will be my head, right, the head of the snake.

  • And every time it moves, whatever is in our tail--

  • this is, first of all, the head is basically

  • going to shift to be whatever the next title is in the direction we're moving.

  • And then this is going to come down here and fill in that spot basically,

  • should work.

  • Well, we'll see how it looks when we actually implement it.

  • OK.

  • Just make sure everything is working still.

  • It is not.

  • Oh, right.

  • OK, right.

  • Because we're no longer drawing things.

  • OK.

  • So we're no longer drawing the snake, but let's

  • go ahead and change that by making it part of the grid.

  • We should make part of the grid here first.

  • So I'm going to say actually after the grid is initialized,

  • grid at snakeTiles 1, 0 and then snakeTiles 1, 1.

  • Whoop, sorry.

  • I'm 0 indexing when it's not 0 index.

  • There we go.

  • So the grid at snakeTiles 1, 1--

  • so the first inner table of our snake data structure.

  • And then the first subelement of that table, so snakeX--

  • or, actually, this should be reverse.

  • Because, remember, it's y, x, not x, y.

  • Oh, actually.

  • Interesting.

  • So snake [INAUDIBLE] 1, 2.

  • No, sorry.

  • I have that backwards.

  • Sorry, having a mild brain fart here.

  • Let's figure this out.

  • So as an aside, "what retro game do you enjoy remaking the most?

  • What about the least?"

  • Game that I enjoy remaking the most?

  • I really like RPGs, so I like a Final Fantasy kind of game I think

  • would be a lot of fun.

  • I did something similar to that.

  • Well, we started with Pokémon in the edx.org course, the games course.

  • The least?

  • I'm not sure.

  • I like a lot of different games, a lot different game types.

  • I'm not a huge fan of physics-based games, I guess, like Angry Birds.

  • We had to make Angry Birds for the course as well.

  • And I didn't find it super interesting I guess.

  • A lot of that is taken care of for you with the physics framework.

  • And I think it's kind of cool to make the different composite physics objects

  • and whatnot.

  • But the actual-- yeah, I don't know.

  • It wasn't as interesting as something a little bit more from scratch I think.

  • But what about you, JP?

  • What would you like to make, I guess, in terms of a game?

  • In the meantime, let me figure out exactly what's going on here.

  • So grid at-- so streamer game fart.

  • Apologize.

  • I'll figure it out.

  • OK.

  • Oh, right.

  • OK.

  • No, I had it. snakeTiles 1, 2 = TILE_SNAKE_HEAD.

  • That's what we needed to do.

  • So basically, take the x and the y from these snakeTiles first inner table

  • and then map that to our grid by putting--

  • basically, this is the first table within our table and then

  • the second subelement, so the y here, and then the same thing here,

  • but the first subelement, so then this snakeX variable.

  • And then I'm going to assign that to TILE_SNAKE_HEAD,

  • which we've shown down here, right?

  • And so if I run this, it should start off with a-- whoops.

  • Grid 38-- oh, yeah.

  • Not grid, it should be, what is it? tileGrid.

  • So let's call this tileGrid.

  • Run it.

  • OK.

  • So it did work.

  • So it started off with a green tile at the very top.

  • But it's not moving anymore, because we're

  • no longer editing that grid be the tile snake head or tile snake body,

  • however we want to do it, right?

  • So we're going to need to alter that.

  • So let's go ahead.

  • And in our update function, that's where we're going to need to do it.

  • snakeY = snakeY minus 1.

  • So we do still to keep track of our snakeY.

  • And so what we're going to do is that's still fine.

  • We need to do tileGrid snakeY snakeX is equal to TILE_SNAKE_HEAD here, right?

  • So this is still moving and still updating

  • that x and y value that we had before, which should still

  • be being edited underneath the hood.

  • But now notice that we do get our snake rendering.

  • However, its rendering in such a way that it doesn't actually discretely

  • change position.

  • It does, but it keeps the old previous values being written in our grid

  • for the title snake head, which is not what we want.

  • We want to basically set those values to 0--

  • not to 0, but to whatever the last element is in our grid, right?

  • Yeah.

  • Because the snake head's going to move forward.

  • It's going to create a gap.

  • And if it's just by itself, it's not going to create a gap.

  • It's just going to be moving by itself.

  • But assuming that we have a full snake longer than one segment,

  • it's going to create a gap where the neck is.

  • And so to fill that gap, recall we can take the tail

  • and we can put it where that gap is.

  • And that'll have the effect of moving the snake.

  • So let's go ahead and tileGrid if.

  • Let's do an if statement here, because if we try to index into the table when

  • it's only a size one, if we're trying to get the tail,

  • it's not really going to work.

  • I guess it would work.

  • It would just use the head, which we've already moved forward.

  • So that would have the result of moving it backwards,

  • which wouldn't work at all, right?

  • Because we want to use a tail or nothing at all if it's one segment long.

  • And we can use this thing called this pound symbol, which

  • is a length operator for tables.

  • And we can say if length of tileGrid is greater than 1, then--

  • whoops.

  • Actually, what we should do--

  • we're going to need to cache our old values so that we can take the--

  • so we have to remember their old value of our neck,

  • so that we can put our new variable into that neck, right?

  • So let's go ahead and say--

  • hold on, let me read the stream real quick.

  • I haven't been keeping up.

  • Dungeon Siege II.

  • Yup.

  • Dungeon Siege II.

  • I've played Dungeon Siege I. I haven't played Dungeon Siege II.

  • But I liked it a lot.

  • It was a lot of fun.

  • Good question.

  • I think I'm on the opposite side.

  • If I could program 2D games, I'd probably make something physicsy,

  • like some 2D Kerbal Space program or Asteroids or something.

  • Yeah, that'd be a cool idea.

  • I think physics games are really cool.

  • And I don't dislike programming them.

  • I think it was just in comparison to, say, Zelda or Mario or Final Fantasy.

  • Those are the kind of games that I enjoyed growing up.

  • So I might have a soft spot for them.

  • But, yeah I know a 2D physics game--

  • Kerbal Space Program in 2D would be pretty cool, actually.

  • That'd be really cool to see.

  • Shayna, "but each time the snake eats the apple,

  • will the snake become longer by adding new tiles,

  • or the head tile will go back to the tail?"

  • So we will add a new tile.

  • Basically, we're going to need to-- oh, you know, actually what we can do,

  • we can pop off the tail, right?

  • Because we can just get rid of the tail.

  • And we'll just add a new grid element where the head was, so super easy.

  • Yeah.

  • That's a much easier way to do it.

  • "Why not just cut the tail piece and paste it

  • in front of the snake's current head in the appropriate direction

  • as it propagates?"

  • JP Guy, good idea.

  • That would work, too, actually.

  • That's actually probably better.

  • We just need to then flip the snakeX and snakeY

  • variables that are currently pointing at the head to point to the new head,

  • right?

  • But that should be easy enough, right?

  • I actually think that already happens.

  • "Sorry, I just found this channel.

  • Why are you programming this game in Lua instead of, let's say, C#?

  • Just curious, Salmedo."

  • Thanks for joining the stream, Salmedo.

  • Short answer, because it's easy to get into compared to C#.

  • Because C#, you're presumably using it in the context of either X and A,

  • a 2D framework, or using in the context of UNITY most likely of those two

  • options.

  • And UNITY is something that I would like to start teaching on stream,

  • but there's a bit more overhead with that.

  • And I actually myself should spend a little bit more time

  • sort of getting all the nuts and bolts set right with that in my mind.

  • And X and A is honestly a framework that I'm just not super familiar with,

  • although I think is somewhat comparable to what we're doing here with LOVE2D.

  • But good question.

  • I would anticipate seeing UNITY streams in the future,

  • because it's a game engine that I would really like

  • to teach and get really familiar with.

  • But this is a framework that I taught in the game course that took place this

  • last year, cs50.edx.org/games.

  • All right, so, yeah.

  • So whose suggestion was it?

  • "Why not just cut the tail piece and paste it

  • in front of the snake's current head in the appropriate direction

  • as it propagates?"

  • That's an excellent question.

  • And I think we are going to do that probably.

  • "OK.

  • Thank you very much, Salmedo?"

  • No problem, Salmedo.

  • Thanks for the question.

  • Thanks for tuning in.

  • OK.

  • So pop tail, put it 2x tail behind the head.

  • Pop the tail.

  • Put it two times tail behind the head.

  • Put it two times behind the--

  • I'm not sure I follow, Bhavik Knight what you're saying there.

  • Would you mind elaborating a little bit?

  • OK.

  • Hopefully, we have enough time to do this.

  • Hopefully, I'm not going to blunder my way too far past 6:00 PM here.

  • But let's say, OK.

  • Let me just get reacquainted here with the code.

  • So if we rerun it, we have just the snake moving.

  • OK.

  • So the thing we needed to do was clear where the head used to be.

  • And yes, JP Guy.

  • "You mean when the snake eats an apple?"

  • Yeah, presumably.

  • I think that's what he's talking about.

  • OK.

  • So basically, let's erase, first of all, where

  • the head was before which we can do.

  • So we will need to store all of these snake pieces together.

  • Because we're going to keep track of all of them as we pop the tail.

  • That's going to be important.

  • So we can't just store the information in the grid blindly and use that.

  • We actually have to keep a record of everything.

  • So we're going to keep the same model that we're doing.

  • And then if the tile grid, number of the length of the tile grid is 1-- so

  • this is where we're talking about if we're just ahead or not.

  • If we're just ahead, then we just erase what was behind us.

  • And if we were not, if we were more than 1, then we need to pop the tail.

  • And if we take JP's suggestion, we can put that in front of the neck.

  • And then that will be our new head, basically.

  • But we need to keep track of where the head was before we actually move it.

  • So I'm going to add a couple new local variables here.

  • I'm going to call it priorHeadX and priorHeadY.

  • And I'm going to set that to snakeX and snakeY.

  • So that is going to let me keep a record of where I had the head before,

  • so that I can do something like this.

  • If the number of the tile grid is greater than 1,

  • right, I'm going to-- well, I'm going to leave that.

  • That's going to be a to-do.

  • Else tileGrid at priorSnakeY and priorSnakeX

  • is going to be equal to TILE_EMPTY, right?

  • And so this should work off the bat in terms of giving-- oh, no, it doesn't.

  • OK.

  • OK.

  • priorSnakeY, priorSnakeX is equal to TILE_EMPTY.

  • If the number of the tile grid is greater than 1,

  • which will be in the case that we have a head, and then we have--

  • oh, this is the tile grid.

  • Sorry.

  • We need to look at the snake grid, the snake tiles.

  • Sorry, that's my bad, snake tiles.

  • So the length of the snake tile is not the tile grid.

  • The tile grid will always be the x times y number of tiles on the screen.

  • Oh, index a nil value, main 78.

  • tileGrid priorSnakeY priorSnakeX is equal to TILE_EMPTY.

  • If snake is moving, so snake will be moving right.

  • OK.

  • Now, this should work.

  • That's interesting.

  • OK.

  • Let's figure this out.

  • This is some live debugging.

  • This is why we're here.

  • So priorSnakeY at priorSnakeX is equal to TILE_EMPTY.

  • And it started to render, but it stopped rendering

  • as soon as we tried to move to the right it looked like.

  • So if that's the case, then snakeX priorHead-- oh,

  • because I called it priorHead, my bad--

  • priorHead, not priorSnake.

  • OK.

  • That should work.

  • There we go.

  • So, now, notice that we have our snake moving around.

  • And it's part of our grid.

  • Now, what happens if I try to move it beyond the bounds?

  • It doesn't work.

  • And the reason that it doesn't work is because, if it

  • gets to be 0 or some negative value on either axis

  • or a value that exceeds the bounds of the grid,

  • remember we haven't programmed to bounds check for these things.

  • It's going to try to access index 0, or index negative 1, or index, like, 60.

  • And that's not within the range of the game, exactly, out of bounds error.

  • So what we need to do is start checking for bounds.

  • If we're trying to move up, down, left, or right and we're at an edge,

  • then we should accommodate that.

  • So we're going to go to snakeY is snake minus 1.

  • So this is all where we actually update the snake in its position.

  • So if it's up, snakeY is snake y minus 1.

  • Now, let's do an error check with that.

  • We're moving up, right?

  • We need to check to see if we get to be 0.

  • If we're at 0 or less than 0, then we need

  • to loop back down to the bottom of the map.

  • So if snakeY is less than or equal to 0, then snakeY

  • is going to be equal to MAX_TILES_Y else snakeY

  • is going to be equal to snakeY minus 1.

  • And so this will at least check for an out of bounds error going up.

  • Now, if we want to check for down, left, and right, we

  • have to do essentially the same thing just with a little bit different logic.

  • So if snakeY is greater than the MAX_TILES_Y, then--

  • whoops-- else.

  • So this is if we're going down, right?

  • So if we go down to the bounds, if we go down and our index is at MAX_TILES_Y

  • and we try to go one past that, we're just going to set our snakeY to 1.

  • Because, remember, everything is 1 index, not 0 indexed in Lua.

  • And then this is the same logic as before when we're moving up,

  • but it's on the x-axis.

  • So if snakeX is--

  • oh, sorry.

  • Actually for less than or equal to 0, we need

  • to check to see if it's less than equal to 1, not less than or equal to 0.

  • Because we don't want to set it to 0 to begin with.

  • We want to make sure when it's at 1 that we loop back around, not 0.

  • Sorry.

  • And then if it's greater than or equal to MAX_TILES_Y, then set it to 1.

  • If snakeX is less than or equal to 1, then I'm

  • going to set snakeX to be MAX_TILES_X else.

  • Whoops.

  • And then I'm going to just copy that, bring that over there.

  • And then we're going to do the same thing here.

  • If snake is greater than or equal to MAX_TILES_X else.

  • And then snakeX equals 1, rather.

  • Now, if I've done this correctly, my logic isn't incorrect,

  • I should be able to go out of bounds and loop

  • back to the bottom, which it looks like I do.

  • I go down as well.

  • Awesome.

  • So, now, we have that basic functionality.

  • So whenever we reach the left, right, bottom, or top edge,

  • we're appropriately moving to the other side of the screen--

  • so a fairly simple problem to solve.

  • That's a big thing that we needed to correct, but now it's been corrected.

  • "Snake tiles in the tile grid."

  • Yeah.

  • Thanks, Andre.

  • Yeah.

  • Those kinds of things are so easy to miss

  • when you're actually programming it.

  • And then I know how frustrating it is to see

  • someone else miss something so obvious.

  • So apologies for that.

  • JP Guy, sweet.

  • Thanks for the support.

  • OK.

  • So we have now our bounds checking and, also, setting our head on the snake

  • or whatever it previously was to TILE_EMPTY, which is great.

  • Now, we need to go through and write the logic for actually eating the apple.

  • So once we eat the apple, let's also have some score variable, right?

  • And let's monitor that.

  • So we can check how many apples we've eaten.

  • I'm going to create a new variable here called score set to 0.

  • And then down here in the draw function, I'm going to call love.graphics.draw--

  • or, sorry, just print.

  • I think that should be fine, score: tostringscore.

  • I'm going to draw this at 0, 0.

  • Yeah.

  • I'll draw it at 0, 0 for now.

  • That's easy enough.

  • Rather, I'll draw it at 10, 10.

  • And that should be it actually.

  • So let's draw that.

  • Score is 0.

  • So you see it in red there, because it's actually technically

  • after I've called love.graphics.setColor to red for the apple.

  • But that's at the end of drawGrid, right?

  • That's the last thing it drew in this case.

  • So I need to actually reset the color, so love.graphics.setColor 1, 1, 1, 1.

  • So for white there, we got score 0.

  • So we know that we haven't eaten any apples yet.

  • It's a little bit small.

  • And actually, offhand, I know you used to create a new font.

  • But if we don't have a Font file, love.graphics.new font.

  • Let me just make sure that we can create a new font without a font file type.

  • Can we do that?

  • OK.

  • Cool.

  • Yeah.

  • We can create a new font with a default size using-- it's called I guess--

  • Vera Sans is the default font in LOVE2D.

  • So let's go ahead and do that.

  • So the font was way too small.

  • In order to draw something at a larger font size in LOVE2D what

  • we need to do is up here, I'm just going to-- actually,

  • I'm going to make it a global font up here.

  • So I'm going to say--

  • let's do it up here--

  • local largeFont = love.graphics.newFont size 32.

  • I believe the default size is 16.

  • And then in here, we can say love.graphics.setFont largeFont,

  • because you have to actually set the font.

  • We've created a font object up here, this large font.

  • But we have to actually set it in the actual UNITY, the state machine

  • aspect of UNITY.

  • Sorry, I keep saying UNITY.

  • I just had a UNITY workshop yesterday, so part of it

  • is still kind of in my mind.

  • But in LOVE2D, we need to specify the creation of the font object and then

  • the actual using it, setting the font for the LOVE2D state machine.

  • So this is what we're doing here.

  • And if I'm correct in my programming, we should now see score is 0.

  • It looks actually pretty nice and clean I will say.

  • So cool.

  • So now we have a score meter always visible on our screen,

  • perfectly functional inasmuch as it renders text, not functional in as much

  • as we're actually keeping track of score.

  • "If there's no Comic Sans, I'm unsubbing."

  • JP, I'll get you some Comic Sans in there next time.

  • If we've got to make this stream legit, we're going to get some Comic Sans.

  • We'll get a nice a nice Comic Sans "This is CS50" as well just for fun maybe.

  • But, anyway, I'm going to think about now

  • how we want to start detecting whether we've reached an apple with the snake.

  • And if we have, we should increment our score, right?

  • I'm going to be kind of bummed if we don't get this full thing working

  • by today, by 6:00.

  • But I'll put it on GitHub if not.

  • And then we'll do a follow-up stream maybe on Monday

  • just to finish it off and then maybe talk about some other stuff,

  • and then maybe next Friday start making another game that's

  • maybe a little more complicated.

  • But I'll do my best to see how far we can get.

  • Games take a long time.

  • Anyways, OK, so we have a snake head.

  • We have variables snakeX and snakeY that point

  • to our snake head that are keeping track of where it is at all times.

  • So what I'm going to do is, in our update function,

  • this is sort of where we need to check to see whether we've reached an apple,

  • right?

  • So before we actually set that place in our tile grid,

  • that's where we want to do the check.

  • We want to say, OK, so if tileGrid snakeY, snakeX is equal to TILE_APPLE,

  • then--

  • and this will be the same either way.

  • We'll still need to update the head location.

  • But check for apple and add to score, right?

  • And this will eventually be where we also add a node to the head, right?

  • "Love me some sans serif font."

  • Yeah.

  • Me, too, Shayna.

  • Excuse me.

  • OK.

  • So if in the tileGrid at snakeY, snakeX, which is where the head currently is,

  • we're going to--

  • sorry, we're checking it.

  • If it's equal to TILE_APPLE, score equals score plus 1.

  • And then we're going to overwrite it down here.

  • So we don't actually need to change the tile,

  • but we do need to initialize another apple, right?

  • So what we need to do here is say--

  • and this is kind of where we need to be a little bit careful.

  • We need to basically say if--

  • or we should rather say, local newAppleX, newAppleY

  • equals math.random MAX_TILES_X and math.random MAX_TILES_Y, right?

  • We're going to get new random variables to be our x, y for our apple,

  • because we want to place in a new random spot.

  • And then we're going to say tileGrid newAppleY, newAppleX = TILE_APPLE.

  • And what this will do is it will blindly look throughout our entire level.

  • Or it won't look through the level.

  • It will just blindly choose two random variables

  • between the limits of our level and set the apple to that value.

  • But there is going to be an issue with that,

  • especially as our snake gets larger.

  • And that's the fact that it could just overwrite one of the snake body parts,

  • right?

  • So we want to be careful with that.

  • We want to make sure that we're actually checking

  • for whether a snake body part exists in the grid at that location.

  • So we can do that.

  • We can absolutely do that.

  • For now, we're just going to blindly do this and make sure that it

  • works, the score and everything else.

  • So I have a syntax error.

  • Oh, right.

  • I'm doing a novice mistake.

  • If tileGrid snakeY, snakeX double equals TILE_APPLE.

  • OK.

  • So now we're moving around.

  • So if I eat the apple, if I can, boom.

  • Cool, we increased our score to 1.

  • And the apple moved to a new location.

  • Awesome.

  • Boom, did it again, did it again, did it again.

  • So it works.

  • It works perfectly well.

  • And it's not an issue that we're not checking

  • for whether it's in the snake's location,

  • because, well, our snake's head is only one segment long at the moment.

  • So it's very unlikely that it'll actually appear in that location,

  • but it's possible.

  • So we're going to add some bounds, or rather, some error checking

  • here soon in order to make that work.

  • But the next piece of the puzzle is going

  • to be how do we start adding segments to our snake,

  • because that's very important, right?

  • If we don't check for segments, we'd never

  • run the risk of ever losing the game.

  • So that's something that we need to take into consideration.

  • And another thing to take into consideration

  • is the fact that we shouldn't be able to move in the reverse direction which

  • we're already moving.

  • Because if we do that, then we'll collide with ourself instantly, right?

  • Because we'll have had a segment in the past direction

  • that we were moving from the last frame, right?

  • So-- two things that we should take into consideration.

  • Snake is a surprisingly complex game when you get down to it.

  • I mean fairly simple and straightforward,

  • but there's a lot of little things you've got to pay attention to

  • in order to get it working just right.

  • All right, so let's think about how we can start getting

  • new segments added onto the snake.

  • So the new segment, the actual part where we add a new segment

  • is going to take place here between lines 96 and 101 where I actually

  • have the snake data structure look at its tail, right?

  • And then put the new piece effectively onto the front of the--

  • "snake is going to be huge."

  • Yup, that's correct.

  • Well, if I'm good at Snake, it will be.

  • If I'm terrible at Snake, maybe not.

  • So in here, once we have consumed the apple

  • and we've placed it in a new location, we

  • want to actually add a new component to the snake.

  • So what we can do is I'm just going to kind of code through this.

  • And we can figure it out.

  • But I know that I'm probably going to want to insert where our--

  • I mean, for the sake of ease, I guess I can probably do an error check here

  • first, actually.

  • If the length of the snakeTiles is greater than 1, then do this.

  • Otherwise, it's just going to add to the snake tail, right?

  • So take tail and add to head if greater than one segment.

  • Otherwise, we want to just add a new head segment effectively, right?

  • Because if we only have one segment, then

  • we have no tail besides the current segment we have to add to the front,

  • right?

  • Otherwise, it's kind of like an infinitely recursive operation

  • at that point.

  • So if the number of snake tiles is 1, OK, else.

  • Do that, OK.

  • So if it's greater than 1, I'm not going to worry about that case for now.

  • I'm going to worry about adding a new head

  • segment if we are just at one segment.

  • So our snakeTiles, snakeX and snakeY, is going to be here.

  • And we aren't actually editing the snakeTiles table if I'm not mistaken.

  • All we're doing is we're editing the tile grid.

  • And so what we need to do is actually edit the tiles within the snake itself,

  • too, right?

  • Because that's how we keep track of the tail

  • that's how we pop the tail off the snake.

  • We take the tail.

  • There's going to be a new tail after that.

  • And then we need to put it to the front--

  • rather, is that true?

  • That's it we're moving.

  • Sorry, that's if we're moving.

  • Then we're going to do that.

  • We take the tail, put it to the front.

  • If we're eating an apple, then we actually keep the tail,

  • we keep the head, and we just add a new snake element to the front.

  • I believe that's correct.

  • Yeah, that would be correct.

  • I'm trying to remember in my head actually how

  • it works when you eat a new segment.

  • Does it add it to the tail, or does it add it to the front?

  • I think it adds it to the front.

  • Let's see.

  • So open a new incognito window.

  • Go to snake game play.

  • And then, oh, it's looking for like Snake classic game play.

  • OK.

  • Snake game.

  • Let's see what it does.

  • So it moves.

  • So the tail's being moved, added to the front.

  • Oh, interesting.

  • OK.

  • So it moved.

  • And on the next frame, it just added a new head without popping the tail.

  • OK.

  • So it doesn't pop the tail on that frame.

  • So that's how we're going to do it.

  • We're just going to keep the tail and not pop it when we add the new element.

  • And that'll work.

  • Cool, easy enough.

  • OK.

  • So priorHead, priorHead is TILE_EMPTY.

  • Whoops.

  • No, this isn't where we need to be.

  • OK.

  • So if we're at just one segment--

  • and apologies if I'm going a little bit slowly.

  • I just want to make sure I think through this correctly.

  • We're going to move forward.

  • And then we're going to keep the head where it is.

  • So snakeX and snakeY is still going to exist.

  • And table.insert at index 1, the new snakeX and snakeY, correct, and the--

  • oh, actually what we need to do is snakeTiles--

  • oh, sorry-- at index 1, this is going to be table.insert

  • into snakeTiles at index 1.

  • I believe this is correct syntax.

  • table.insert takes a optional parameter to tell you where to insert.

  • I have to remember exactly where.

  • table.insert A 115--

  • OK.

  • So it takes the table, then the index, then the value.

  • Got it.

  • I had that backwards.

  • Table, index-- so we're going to insert into snakeTiles at index 1,

  • so at the beginning of the 2D array, or, sorry, at the beginning of the-- yeah,

  • at the beginning of the table, not a 2D array, just the Snake table.

  • A new element, which is going to have our new head, right?

  • And then the snakeTiles at index 2 is going to be equal to snake--

  • what is it?

  • snakeTiles 2 is equal to snake--

  • sorry.

  • I keep having a couple of brain farts.

  • I might need to drink some coffee or something.

  • Let me read the stream for a second.

  • "Add to the front."

  • OK.

  • "It would be cooler if the snake's body had

  • to travel all the way until the original apple tile was

  • right behind this snake's tail--

  • cool if the snake's body had to travel--

  • oh, you mean in order for it to fully eat the apple?

  • Like it goes through the entire snake?

  • Because that'd be really difficult. That'd be like expert mode Snake.

  • Expert mode Snake, got to basically draw the snake with the apple kind of.

  • Oh, prior.

  • We got to use prior X, right?

  • So it'd be priorHeadX, priorHeadY.

  • And then it would be we have to erase as well the old--

  • yeah, see, down here, we're setting it to empty.

  • Oh, but then this case, we're actually going to be at greater than 1.

  • OK.

  • Oh, but that we do still need to--

  • yeah.

  • This is part of the equation.

  • Part of the actual algorithm for moving the snake

  • is that when it's greater than 1, this is where we actually

  • have to do the thing where we pop the tail,

  • and then we add that to the front of the snake as well.

  • So it's a little bit more complicated than what we did down here,

  • whereby we took the priorHeadY, priorHeadX index of the tile grid,

  • and then we set that TILE_EMPTY.

  • If we're greater than 1, then we need to basically do the pop operation

  • every time.

  • "Will it be easier if you create two apple tiles, one when it's eaten

  • and second one each time it disappears?

  • Example, when the snake eats apple one, it disappears and apple two appears.

  • And it goes on vice versa."

  • Yeah.

  • Oh, wait.

  • "Create two apples, tiles, [INAUDIBLE]."

  • If I'm understanding correctly, I think what we're doing is what you're asking,

  • whereby we just have the single tile apple

  • and its indices that are being-- well, rather, it's being stored in the grid.

  • And then when that happens, we just overwrite the tile with the snake head.

  • And then we just generate a new random tile for the apple.

  • I think it functions kind of similarly to what you're asking

  • if I'm understanding you correctly.

  • Shayna, thanks for the question.

  • OK.

  • Sorry, getting a little distracted.

  • Let's see, tileGrid priorHeadY, priorHeadX.

  • So this part was working fine.

  • And it's this part when we need to start popping and adding.

  • OK.

  • So first of all, let me see if this is even correct.

  • So part of this is the snake tiles itself is just a data

  • structure that we're using to keep track of,

  • effectively, where the tail location is at all times.

  • But it needs to maintain a history of where all the tiles are so that we

  • can change the tail accordingly.

  • So table.insert snakeTiles 1 snakeX, snakeY,

  • so that part is adding the new head to the front, so the snakeX,

  • snakeY variable that we created before.

  • And then snakeTiles 2 is going to be our priorHeadX, priorHeadY.

  • So now that's going to be the new tail, basically,

  • where our previous head used to be.

  • And then once we have a new tail, what we're going to do

  • is we need to set that in our grid.

  • We need to say tileGrid at snakeTiles 2 index 2.

  • And snakeTiles 2 index 1 is going to get equal to TILE_SNAKE.

  • Because it's not going to be TILE_SNAKE_HEAD at this point.

  • It's going to be TILE_SNAKE.

  • And to make it a little clearer, I'm going

  • to also have to change the drawing function for this.

  • Where do I do that?

  • In love.draw, which is--

  • sorry, rather, the drawGrid function.

  • So else if tileGrid y, x is equal to TILE_SNAKE--

  • oh, it's body, sorry.

  • Rather, we've defined it to be body.

  • Whoops.

  • Then I'm going to do basically all this same stuff,

  • but I'm going to change the color a little bit.

  • I'm going to change the snake head to be a little bit more cyan color.

  • And then the body is just going to be green.

  • And then I need to go up here where I actually wrote that, so TILE_SNAKE_HEAD

  • and then TILE_SNAKE_BODY right there.

  • And then that will have the effect of basically making the grid

  • reflect where the snake body is.

  • So let's go ahead and see if this actually works first of all.

  • OK.

  • So it is working.

  • So, now, it's registering that we have a body, but it's not moving.

  • So what we're doing is we're effectively doing what we did before.

  • Whoa, that's going to be impossible to get.

  • Oh, yup.

  • I think I screwed myself.

  • Oh, yup.

  • See, I did.

  • That's fine.

  • So we kind of have a step in the right direction where we now recognize,

  • when we eat an apple, we add a new element to our body.

  • But the snake head is overwriting all those other variables--

  • or sorry, those other tiles in our grid.

  • And it's not sort of keeping track of where the tail is,

  • which was part of this last piece down here.

  • Was it down here, the updating the head location?

  • Because if our snake is only of size one,

  • then our head location is just going to be wherever snakeY,

  • snakeX is, which we do here.

  • We update it in the tile grid.

  • But to erase it, what we did before is we just

  • set the tileGrid at priorHeadY, priorHeadX to be TILE_EMPTY.

  • The logic is different if we have greater than one element.

  • So what we need to do is basically erase wherever the tail was, right?

  • And by setting tile grid, for now we'll just assume that we're of size 2,

  • right?

  • Ba, ba, ba, ba.

  • In this case, we'll assume we're working just with a 2 unit snake,

  • and then we'll move up to multiple.

  • But we're going to need to set tileGrid at snakeTiles 2, 2

  • and snakeTiles 2, 1, equal to TILE_EMPTY.

  • And then we need set snakeTiles to--

  • we need this to update basically.

  • So we need to set TILE_SNAKE tiles 2 be equal to priorHeadX and priorHeadY.

  • And so that should work.

  • Let's run it.

  • Let me make sure I'm not crazy.

  • Yup, perfect.

  • So now we have 2 unit snake, but it's only going to work for 2 units, right?

  • Whoops, if I can pick it up.

  • Whoop.

  • OK.

  • So it's still 2 units, right?

  • We've made progress, though.

  • We've basically said, OK, in our tile grid

  • we want to erase whatever the tail tile was last time.

  • In this case, we're hard-coding 2 in, because we're only

  • experimenting with the snake of size 2.

  • And then we want to set that last unit to be equal to whatever the priorHeadX

  • and priorHeadY was equal to.

  • But this isn't going to work for a snake of size, you know, 3 onwards.

  • Because this is hard-coded just for 2.

  • We need slightly different logic in order to handle that use case.

  • OK.

  • "I mean, essentially having an object pool of two apples,

  • it'd probably be harder.

  • You'd have to keep track of which one is active.

  • The tiles here are being rendered on the fly.

  • It's not like, say, UNITY, where you can deactivate the mesh renderer

  • and make objects not be rendered."

  • Yes.

  • Often, object pooling is very good use of--

  • excuse me-- resources, but typically only

  • when you have a ton of different objects that you need to keep instantiated.

  • And in this case, per Andre's point, yeah, they're being drawn in real time

  • with the size of the grid and not being actually stored necessarily in memory.

  • Although the grid itself is being started in memory,

  • but it's not consuming more memory by having the apples there.

  • There's going to be an integer, a 0, 1, 2, or 3 regardless of what we're doing.

  • "I'm assuming there will also be a bug where

  • an apple spawns onto a tile that's already occupied by a snake tile."

  • Exactly.

  • Yeah, I mentioned that previously.

  • And that's something that we'll have to sort of take into consideration.

  • When we spawn the apple, we want to basically look and say, OK,

  • is tileGrid y, x = apple?

  • Sorry, is it equal to snake head or snake body?

  • And if it is, we need to generate another random x, y value basically.

  • Just keep doing that in a loop until we find a value that actually works.

  • OK.

  • So we have a two part snake here.

  • Let me take a sip of water, little bit parched.

  • How much time we have left?

  • Oh, we have 12 minutes.

  • Oh, well, I'll go a little bit over just because we were a little bit

  • late to start.

  • But now we can sort of think about, OK, I have a two part snake.

  • But let's take the problem to the next level.

  • Let's take it to where we have three parts to my snake.

  • And let's actually do the logic where we take the tail of the snake

  • and then put it in front, essentially keeping the head where it was.

  • And then let's write to the grid those two new locations, right?

  • We're going to need to erase our tail location.

  • That needs to get erased from the grid.

  • And then we need to write our new sort of head location, which

  • will be an empty tile presumably.

  • OK.

  • So let's go ahead and do that.

  • So the first thing I said, we need to pop the tail of the snake.

  • Also, am I actually adding the last piece appropriately?

  • Let's see.

  • Take tail and add to head if greater than one segment.

  • Oh, you know what?

  • We're not actually going to take the tail.

  • We're going to just add a head and keep the tail where it is, right?

  • Because this is in the context of eating an apple.

  • Because we don't want our snake to get smaller.

  • We want our snake to actually increase in size.

  • And so it makes sense for us to take a brand new tile

  • and add it to our snake, not the other way around that we did before.

  • So if the length of snakeTiles is greater than 1, let's go ahead

  • and, first, we need to add to the table.

  • So table.insert into snakeTiles at 1 snakeX and snakeY, right?

  • Because we're going to basically keep what we had before.

  • And then in the case of having a new head segment, we didn't have a tail.

  • But that honestly doesn't really matter.

  • Like I said, yeah, I think we can just add a new--

  • I think I might have gotten this confused with the actual code

  • with moving.

  • But I think we should just be able to tileGrid at--

  • sorry-- snakeX-- rather, snakeY, snakeX.

  • We can set that to tile TILE_SNAKE_HEAD.

  • And then at tileGrid where we had priorSnakeY and prior--

  • whoops, sorry, priorHead.

  • I did the same exact typo before--

  • priorHeadX.

  • We can set this to TILE_SNAKE_BODY.

  • And this should accomplish the same thing if I'm not mistaken, right?

  • And then the only thing is the head location needs to change.

  • So this is a hard-coded use case where we have a snake of size 2.

  • "That's not a bug.

  • It's a worm."

  • Yes, correct.

  • "I'm assuming [INAUDIBLE]."

  • Yeah, that's true.

  • Very clever, Shayna, very clever.

  • OK.

  • So when we're moving, remember, we got to take the tail.

  • So table.insert-- remember, we're going to insert a new head location, right?

  • Or actually it's going to be the tail.

  • So what we're going to do is we're going to say

  • we're going to say tileGrid priorHead--

  • oh, whoops.

  • OK.

  • So we're already doing it up here.

  • We're already adding the head location.

  • And then we're going to-- oh, no.

  • This is updating the head location in the actual grid itself.

  • And we need to do this in the snake data structure as well.

  • So snakeTiles, because the head's moved, right?

  • So the head doesn't really need to move I guess.

  • Oh, actually, yes, it does.

  • Does it actually?

  • Because we're always going to look at the tail.

  • And. if the snake is greater than 1, the algorithm kind of changes.

  • So I guess we don't really need to worry too much

  • about checking if we're only a size 1.

  • Or rather, I don't think we have to update the snake's head automatically.

  • OK.

  • So if we're greater than 1, let's take the tail.

  • So local tail gets snakeTiles at length snakeTiles.

  • So this will be our last into the--

  • do we even need a reference to it?

  • Because we're just popping it, right?

  • Well, I guess we do, because we want to set tileGrid tail 1--

  • or 2 rather.

  • Did I do that correctly?

  • Yeah.

  • Tail 1 to TILE_EMPTY, right?

  • Because, remember, we're taking the tail and we're making that empty.

  • And then we're going to take the head, and we're just

  • going to add another element to the head, right?

  • So in this case, table.insert into snakeTiles at index 1 a new head

  • called--

  • because we don't actually necessarily need to move the head to the--

  • do I need to move the head to--

  • or, sorry, we don't need to move the tail to the head.

  • We just need to add a new element to the head.

  • Because the tail element is going to have different x,

  • y coordinates than the head element is going to have x, y coordinates.

  • Because the tail is way over here.

  • And the head is over here.

  • By moving the tail up to here, all we've done

  • is basically just added another table with some random numbers that

  • aren't terribly useful if they're completely

  • disparate from what that actual nodule supposed to represent.

  • So I'm just going to add another unit to the front of the head, snakeTiles

  • at index 1.

  • And I'm going to say this gets snakeY, snakeX.

  • This is where our head is right now.

  • So we popped the tail and then added another element to the front.

  • It'll have the result of growing the snake fairly seamlessly.

  • Let's see if I have all the pieces in play here.

  • I'm guessing I probably screwed something up.

  • Yeah, looks like I did.

  • OK.

  • So I'm not actually growing the snake in this case.

  • So why are we not growing the snake?

  • Oh.

  • I don't have an else statement here to grow the snake.

  • Oh, actually because, remember, we basically separated this out.

  • We don't need an if statement.

  • So I can just copy that out, whoops, paste that into here.

  • Make sure these are indented the same way.

  • Let's try that.

  • Does that work?

  • Whoops.

  • Oh.

  • No, crap.

  • It's still not completely working, because we're not erasing.

  • Notice that whenever we get an apple, it's adding a body element,

  • but it's not moving around which is kind of an issue.

  • Because that's another thing, too.

  • We do need to move the snake.

  • Well, actually, we are doing that already.

  • But we're not reflecting the correct tile when we do that.

  • We're drawing the head instead of the body.

  • So let's figure out why that's happening.

  • snakeY, snakeX is equal to TILE_SNAKE_HEAD.

  • And then the previous one-- oh, right.

  • Because if there is a previous one, then we need to draw the body at--

  • like, our last location needs to be the color of the body, right?

  • So the tail is going to be empty.

  • And then the location of the prior head needs to be the body color.

  • So tileGrid at priorHeadY, priorHeadX is going to be equal to TILE_SNAKE_BODY,

  • right?

  • And if I'm correct, this should at least draw

  • the snake body, the green color that we're looking for,

  • which it does perfectly.

  • I'm noticing, though, that the head is still kind of hard

  • to see relative to the body, because the color's a little bit too similar.

  • But it works well enough.

  • It works well enough.

  • I can make probably make it a little bit easier if I set the body

  • to be something like this, maybe 0.5.

  • Oh, sorry.

  • Yeah, that might work actually.

  • Let's try that.

  • Yeah, there we go.

  • That's good.

  • That's a good color.

  • Now, unfortunately, the snake isn't actually moving.

  • And we can still collide with ourself which isn't ideal.

  • But it's a step in the right direction.

  • So let's go ahead now and figure out why the body is still rendering.

  • Clearly, the tail is not being taken away

  • and not being erased from the grid.

  • So we kind of need to fix that up, right?

  • So if I go to updating the head location, so tail 2,

  • tail 1 is equal to TILE_EMPTY.

  • Now, presumably, this is the case, because tail 2, tail 1

  • is not getting updated I'm guessing.

  • So let's figure that out.

  • Let me look at the chat and make sure I'm not mentioning it.

  • "Strange, it worked earlier when you limited the total length to two tiles."

  • Yeah, it did.

  • I had a slightly different hard-coded logic in there.

  • That made it a little bit simpler, because we

  • weren't messing with like taking a tail and appending

  • a new element to the table.

  • I think I just kind of added a new element to the thing.

  • And then I was keeping track of them.

  • But we'll figure it out.

  • We're here.

  • We have a few more minutes.

  • It's 6:00 now, but I'll go for a few more minutes so maybe we

  • can get a little bit closer.

  • And then maybe on a Monday follow-up stream

  • we can finish this up sort of live and then

  • have a completed game to at least look at.

  • And then by putting this on GitHub, maybe

  • if everyone wants to experiment on their own and try to figure it out,

  • it'll be a nice cool exercise.

  • We'll come back together on Monday and figure it out.

  • But before I adjourn, I would like to try and make a little bit of leeway.

  • I won't keep folks for too much longer.

  • We'll maybe give another 10 minutes or so.

  • Let's go ahead and--

  • by the way, the risk of live coding is you never know what to expect.

  • And like I said, I did no research into all of this,

  • but that's part of the fun of programming and problem solving is,

  • you know, sort of trying to figure things out on the fly,

  • figure out a problem that you've never tried to before.

  • It's kind of interesting, right?

  • I get to embarrass myself a little bit live in front of everybody, you know?

  • OK.

  • So the head location is working fine.

  • We can see that by seeing the game and seeing that the cyan

  • sort of colored square is updating.

  • But the tail isn't erasing what's behind it, which is the important thing.

  • And so tail, get snake tails, snakeTiles,

  • priorHeadY, priorHeadX is equal to the TILE_SNAKE_BODY.

  • So we're moving that.

  • So we could see that working.

  • It just needs to erase what's at the end of the tail, which

  • is what this line was intended to do, which

  • was take the last tile sort of in our grid and then edit the--

  • what is it?

  • Sorry, brain fart.

  • In the tile grid, change the number from 3 to 0 effectively.

  • "Maybe figure out where the head should be after delta time and add tail.

  • There we're making it head color.

  • Make head a part of the body.

  • Live coding's like a box of chocolates."

  • That's true, Andre.

  • There's very true.

  • It is.

  • I think it's fun.

  • I could see why someone might get frustrated watching, hopefully not.

  • "Life's just a huge p set that with a lot of subproblems--"

  • very true, very true, a ton of subproblems.

  • Every problem you solve sort of just adds new little problems.

  • And you know, there's people online that make this game.

  • And they live code it in like 10 or 15 minutes or whatever.

  • So they are a lot more impressive, I guess, to watch.

  • But I guess the point of this more is to kind of illustrate

  • like live problem solving, and to take questions,

  • and to sort of have a conversation about it

  • and facilitate back and forth communication.

  • "Thank you, Colton.

  • I will join you again on Monday.

  • Have a great weekend."

  • Thanks, Steve, appreciate you coming by again.

  • Yeah, we'll, do it again.

  • We'll do a follow up on Monday, and we'll figure it out.

  • "[INAUDIBLE] it's really good, though."

  • Yeah, thank you, appreciate it.

  • Yeah.

  • It's a work in progress for sure.

  • But I would say it's 3/4 of the way there.

  • And on Monday, we can get it to 100%.

  • "Am I the only one scared by the amount of figuring out this requires?"

  • Nowanda 3333.

  • Yeah.

  • I mean, that's kind of, I guess, the point of doing this sort of thing

  • is to kind of have like a demonstration of what coding and solving

  • a new problem feels like.

  • I mean, some people might be better at this than I am.

  • And I'm certainly not the greatest programmer,

  • but I've definitely programmed a bunch of games.

  • So I kind of have a general idea more or less of how to do stuff.

  • But a lot of it, especially if you've done no research,

  • is just kind of figuring out like, OK, what works?

  • What can I guess would work?

  • How can I structure my data to make it work?

  • There's a lot of solutions to every problem,

  • like when we're talking about taking the tail and appending it to the head,

  • adding a brand new head element.

  • Replacing the neck was my initial inclination

  • rather than adding to the head.

  • The head is probably a much cleaner way to do it.

  • There's so many different ways to do it.

  • And even simple games like this-- games are particularly difficult,

  • because they have a lot of stuff going on.

  • You have things occurring over time.

  • And you're messing with graphics.

  • And you're solving abstract problems.

  • It can be difficult. But I find it a lot of fun

  • even if we didn't get 100% of the way there today.

  • I think we can close the loop and do a lot more fun stuff in the future

  • as well.

  • "I couldn't make it work for my Scratch project

  • either, making the snake dynamically grow."

  • Well, we'll definitely get it to work.

  • We'll get it to work on Monday.

  • And I don't know.

  • I guess maybe I can in advance take a look at some algorithms

  • for this kind of stuff.

  • But the figuring it out live kind of feels a little bit more authentic.

  • And I guess it allows us to also have this sort of like two-way conversation

  • which I kind of like, like taking suggestions from everybody

  • and being able to say, oh, maybe this solution is actually better.

  • Maybe this suggestion could be changed in this way.

  • You could do it this way.

  • And then we actually end up making a project together versus me

  • just kind of saying, oh, here is how I figured out how to do it.

  • This is how you do it.

  • Let's do it and kind of like me talking to a wall almost.

  • Not really a wall, but, you know, you get the idea, sort of idea.

  • So I don't know, I personally like it.

  • But let me know what your thoughts are.

  • "I'm a bit confused as to what the difference is

  • between the blocks between 112 and 119."

  • 112 and 119, OK.

  • So this is updating the head location.

  • And 102 and 107, excuse me, 102 and 107.

  • In this case, when we have an apple and we want to add it to our score,

  • we want to not take from the tail.

  • And we're just going to add a new head element to our snake.

  • Because our snake is growing at that point.

  • If we're just moving, the goal of moving is to take the tail element

  • and think about putting it at the front of the snake.

  • So we're kind of like constantly doing this shift operation with the snake.

  • But in reality, we don't really need to take the tail and put at the front,

  • because the front should kind of have the information about where that head

  • element is.

  • And if we take the tail, the tail element has the x, y of the tail.

  • So it doesn't really work.

  • So we kind of just have to pop the tail and then push a new head

  • element with the new x, y information to the front

  • of the snake, so good question.

  • "Thank you for your time.

  • See you Monday.

  • Time in Morocco is 11:00 PM.

  • I need to go to sleep."

  • Thanks, Elias, appreciate you tuning in.

  • I'll see about maybe starting a stream a little bit earlier,

  • maybe like at 12:00 my time, noon here, which

  • should be ending a little bit more around 7:00 or 8:00 your time.

  • We'll see.

  • So thanks for tuning in even though it's really late, appreciate it.

  • JP, "the code's also been writing to look and read intuitively.

  • The movement's pretty straightforward.

  • Drawing the snake is also straightforward."

  • Yeah, Lua's a great language.

  • I really love Lua a lot for that reason.

  • It's pretty easy.

  • It's kind of almost C-like.

  • But it also has a lot of readability.

  • And it's just kind of clean and nice.

  • A lot of the things about it I find--

  • or not a lot of the things about it.

  • But a couple of the things about it I find a little bit questionable,

  • like the do and then the end keywords and stuff like that.

  • I'd probably change that about it if I were redesigning Lua

  • from the ground up, but it's a good language.

  • I like it.

  • It's fun.

  • Shayna says, love the live coding session.

  • You did a great presentation, very interesting and authentic."

  • Thanks, Shayna, appreciate it.

  • Would have preferred it be a little cleaner,

  • but, you know, we'll get there.

  • This is only stream number two.

  • We got a lot to go.

  • So on Monday, hopefully, we can fix everything up.

  • Nowanda, "it's very fun.

  • I forgot about my tea.

  • And it got cold while watching you twice."

  • Thanks, Nowanda, appreciate it.

  • I hope you get to drink your tea.

  • I hope it's not too late.

  • "You're streaming on Monday is the same time, 3:00 Eastern Standard time?"

  • I think it might be, Elias.

  • I'm going to see about the logistics of getting it up and running

  • at 12:00 Eastern Standard time instead, so it's

  • a little bit easier for folks that are overseas to maybe tune in.

  • You know, we'll see.

  • We'll see what I can do.

  • "My gut feeling tells me that keeping track of the snake's body

  • coordinates in an array or list would actually

  • make the code less readable, but more abstract and most likely

  • more editable."

  • Yeah.

  • Yeah.

  • That's effectively what we are doing.

  • We just need to store them, because we need

  • to keep track of where they all are at any given time.

  • But we also need to edit them.

  • And that might be part of the thing that we're not doing

  • is actually going back through all the snakes.

  • Well, actually, is that true?

  • We don't really need to edit them.

  • Or do we?

  • That might be part of the problem.

  • I think we might actually have to edit them,

  • because the tail information is constantly changing.

  • I don't know.

  • I have to think about that a little bit more.

  • I have to think about that a little bit more.

  • But we do need to keep track of all of the different nodes,

  • so we have the tail from which to, I guess, pop off.

  • I have to think about a little bit more to be quite honest with you.

  • My brain's a little bit fried after the three hours of programming.

  • So I'll spend like an hour or two looking at this

  • and figuring out a plan of action.

  • And then on Monday, we can come take a look at it and finish tackling it.

  • "Great job on the stream."

  • Thanks, appreciate it JP.

  • Thanks for coming in.

  • I guess here is where we'll cut the stream.

  • Again, Monday I'll set up an event.

  • I'll set up a stream to finish this up.

  • I'll look at it over the weekend.

  • We'll get it all sorted and situated.

  • Get it on GitHub as well, so that all of you can download it

  • and mess around with it.

  • If you have suggestions on a next game to implement next week,

  • maybe starting on like a Thursday or Friday, let me know.

  • Someone suggested Pac-Man.

  • So Pac-Man might be fun.

  • That would be a little bit more complicated.

  • So that one for sure would probably take I want to say like four or five

  • streams to fully implement.

  • I could be overshooting it, but it's a bit more complicated than this.

  • And this took more than one stream.

  • So we'll see.

  • But, yeah, let me know on Facebook and on Twitch, wherever.

  • Let me know your suggestions for more games to make.

  • I'd be happy to create some new ones.

  • Thanks, Irene.

  • "It's been great fun today.

  • Thank you.

  • Will probably not be able to catch it on Monday, but we'll watch later."

  • Yeah, we'll post to VOD, so you'll be able to see what's up.

  • Thanks, Irene for tuning in, appreciate it.

  • OK.

  • So I think with that, we're going to call this stream.

  • So, again, this is part one of Snake.

  • We got the snake sort of rendering around, moving around, consuming

  • apples.

  • We have a score.

  • We have a grid.

  • But we do not currently have the snake moving correctly.

  • And we do not have collision with the snake and itself.

  • But those will be the things that we tackle in the next stream.

  • So thanks again so much, everybody, for coming.

  • I will see all of you at some point soon.

  • Good bye.

COLTON OGDEN: All right, we should be live here on CS50's Twitch channel.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it