Placeholder Image

Subtitles section Play video

  • COLTON OGDEN: Hello, world.

  • How's everybody doing?

  • My name is Colton Ogden, and this is CS50 on Twitch.

  • So today, we are going to be putting together a game that was actually

  • suggested by somebody on our Facebook.

  • So shout out to Asli Tumorkin.

  • Hope I'm pronouncing your name correctly.

  • So last week, we implemented-- well, last week and this week,

  • we implemented Snake.

  • Last week we did the first part, where we had the Snake moving around.

  • And then on Monday, we fixed it up.

  • Added a bunch of the bells and whistles, and had a great time.

  • And then early this week, we had a few other streams.

  • Next week we'll have a few other streams.

  • But today, we're actually going implementing

  • what's called concentration or the memory card game,

  • and so let's take a look at--

  • I've been doing a little bit of research here on potential sprites to use,

  • but let's take a look at what the memory card game, or as it's called,

  • concentration, actually looks like.

  • And shutouts to Asli, there, in the stream.

  • She says Hi, Colton.

  • Thanks for taking my suggestion.

  • Asli Tumerkan, that was a very good pronunciation.

  • Thank you.

  • Hopefully I didn't butcher it too hard.

  • But this is what that game looks like in one of its many incarnations.

  • It's a game that you can see in a lot of other games, a lot of video games.

  • And for example, what I think what comes to mind--

  • I think I saw it in Mario Party a long time ago.

  • For the N64, they had a version of it.

  • But essentially, it involves a bunch of face

  • down cards or other surfaces with some sort of picture

  • or unique iconography on the other side of the card,

  • and your goal is to match pairs of these cards that are alike,

  • as by flipping over only two at a time.

  • If you flip over two cards that are the same, they get to remain flipped over.

  • So you can sort of see what cards that you've successfully matched.

  • And if you flip over two cards that are unique--

  • they're both different images or whatnot--

  • then they get flipped back over, and you sort of

  • have to remember which cards you flipped over erroneously, such that later on,

  • if you flip over one card that you happened to also flip over somewhere

  • else, you can sort of remember where the two were

  • so you can flip them over permanently.

  • Drnkjawa says hello, random Twitch people.

  • Hello, Drnkjawa.

  • Good to see you.

  • And hello to Ilyass as well, who spoke in the chat before the stream began.

  • And so that's going to be today's goal--

  • implement a version of this in Lua and Love2D.

  • So recall Love2D or Love is the framework

  • that we used on Friday and Monday of this last week,

  • and it's a really nice, simple 2D focused

  • framework that allows us to code in Lua, which is also

  • a lightweight, fast scripting language used extensively in game programming

  • and throughout the industry.

  • And today, we'll use the same framework.

  • Next week, actually though, we'll start taking a look at Unity and C sharp,

  • which is quite a bit different, but I know

  • many people are interested in Unity, and I myself am very interested in Unity.

  • And sorry, I might have hit my mic.

  • I myself am very interested in Unity and improving my skills there.

  • So it will fun to do some live coding in that.

  • So let's go ahead and dive right into the implementation of concentration,

  • or pairs, or the memory card game--

  • however we want to name it today.

  • I'll call it concentration just because on Wikipedia,

  • that's the official name of it.

  • And if we go to Wikipedia, just so that we can tie it all together,

  • this is where I initially looked it up to see

  • what the game was officially called.

  • And it's here-- concentration the game.

  • And so there's many different iterations.

  • You can play in a circle--

  • oops, the chat is there.

  • So we actually can't see it too well, but yeah, you can play it in a circle.

  • You can play it in a sort of rectangular arrangement.

  • An important thing to take into consideration

  • when playing pairs or concentration is that if you have an odd number of cards

  • laid out, as by maybe having three by three rows of cards on your table

  • or whatnot, then you're actually going to need some sort of joker

  • card or some other wild card to fill in that extra slot because the game does

  • rely on you making pairs of cards.

  • So if it's a not even number of cards, it won't work.

  • But just by having that wild card and maybe making

  • that an instant game over, or an instant sort of flip back over,

  • you can sort of mitigate that.

  • So you can have a three by three.

  • Today, we're going to focus on having at least one of our axes of our cards

  • be even so that no matter what, we'll always have an even number of cards

  • to flip over just to avoid that issue.

  • But that would be an interesting way to introduce a new game mechanic.

  • OK, so anyway, that aside, I'm going to go and make a new folder in--

  • I have a folder called streams on my hard drive.

  • I'm going to call this concentration.

  • This is going to be my Love2D folder in my project folder.

  • I'm going to drag that into my editor, which is VS Code.

  • I'm going to open it up, close the window that was behind it,

  • expand this a bit so that it takes up the whole screen.

  • And notice that I have nothing in here, so I'm going to create a new file.

  • Remember, main.lua is the file that Love2D

  • will look for to actually begin execution of your game.

  • I'm going to, as best practice, go ahead and just give it a top level comment.

  • So remember, a dash, dash, open bracket, open bracket.

  • The square bracket lets you start a block comment

  • so that you can comment over multiple lines of code,

  • as opposed to just one line of code.

  • I'm going to give it my name and game where the goal is

  • to match pairs of face down cards.

  • So pretty simple.

  • Not going to have an official super hardcore

  • top level comment block, but this is, I think, something

  • that we didn't do on the last game--

  • the Snake game-- which is something that you probably should do,

  • just so all your information and the gist of your game or application

  • is there, located at the very top.

  • So remember, in Love2D, there are a bunch of core functions

  • that are necessary in your main.lua in order for Love2D

  • to read your game appropriately.

  • Those functions are, if we remember from last game,

  • load, love.load, love.update, love.draw, and there

  • are a few other ones like love.keypress, which we'll go ahead and write in here.

  • And one of the things that I'd like to do today

  • is given the fact that concentration is a card based game

  • where your goal is to sort of select whatever card you want to flip over,

  • I thought it would be good to introduce maybe some mouse movement.

  • So we'll go ahead and we'll do that.

  • And for that we'll need--

  • I believe it's love.mousepressed, and it takes a button.

  • So we'll use that.

  • We'll implement that in a little bit, but that'll essentially

  • be when we left-click our mouse to select a card, when we're

  • going to check a button, it should be equal to one

  • because the number one-- love2D assigns a number to each button on your mouse.

  • So some mice have only two buttons, a left and a right click.

  • Some have three, a left, a right, and a middle.

  • And then some have like 10 buttons, like the pro-gaming mice,

  • but we're only going to assume the use of the left click for this game.

  • All right, so this is sort of the scaffold of our application.

  • We have all the core functions that are needed in order for us to build upon.

  • We're going to do a little bit more in terms of engineering

  • this time when we're making the game.

  • We're actually going to develop what are called classes to represent

  • a couple of things in our game.

  • I think, really, what we're only going to need is the card class.

  • So that instead of having to have a bunch of functions like draw card,

  • or draw Snake, draw grid, like we did last time, we'll be able to say

  • I'm going to create an object called a card that's

  • going to have its own draw and its own update function.

  • And then in the code where I want to actually do

  • all of the logic for rendering and updating,

  • I can just say card colon update, card colon render,

  • and so forth, and save my main file from getting super bloated.

  • Because remember, last time, our main file was like 300 lines of code

  • long, which is not ideal when shipping a game like this.

  • Depending on the complexity of your game,

  • maybe your main module is 300 lines, but we put all of our logic in main.lua,

  • and that's a bad practice.

  • You want to avoid doing that as much as possible.

  • Mahjabin says hi, Colton.

  • Thanks for sharing this with us.

  • My pleasure.

  • Thanks for joining us on the stream.

  • Good to have you.

  • I'm also going to put this on GitHub.

  • So if you're unfamiliar, we also did it GitHub live stream this week

  • with Kareem Zadane, one of CS50s core staff.

  • So go ahead and check that out if you haven't watched that already.

  • I'm going to create a new repo on GitHub,

  • and I'm going to call this Concentration50.

  • I'm going to say a card game where the goal is to match pairs of face

  • down cards.

  • And I'm going to make this a public repo,

  • so anybody can go ahead and clone it.

  • I'm going to get this link here, copy that, open my terminal,

  • clear my terminal.

  • I'm going to cd into the directory that I just created to store my game.

  • I'm going to make sure I'm in the right spot first.

  • I'm not.

  • I'm in the GitHub repo, I think.

  • I'm going to go into concentration.

  • I want to git init, git add dot so that I

  • add all the files in my repo, git comit dash m

  • initial comit, git remote add origin.

  • That link that I got from my GitHub repo so that now I

  • know that it knows to push this code to GitHub storage of my project,

  • and then I'm going to push dash u origin master.

  • And what happened?

  • What happened here?

  • Git remote at origin.

  • Repo not found.

  • That's weird.

  • Not found.

  • What happened here?

  • Oh, is it because it's--

  • I didn't do it over.

  • Wait, that's weird.

  • I'm not sure-- this is another account.

  • Let me see if I did it correctly.

  • Git remote add origin.

  • Yeah, I did all that.

  • Dash u origin master.

  • I might figure this out later, but this is weird.

  • Get status on branch master.

  • That's weird.

  • Why did it do that?

  • Hold on.

  • It 404'd my repo for some reason.

  • OK, let's figure that out.

  • Oh, it's because somebody-- wait, no.

  • No, that couldn't be possible.

  • That's fascinating.

  • The repo just deleted itself right as we were doing that.

  • OK, let's try that again.

  • Concentration50.

  • Already exists on this account.

  • OK.

  • Sorry, it's really strange.

  • That was a strange bug.

  • I've never seen that happen before.

  • I'm going to go into my repositories.

  • It said it already exists on that account, but it's not on here.

  • OK, GitHub is being weird for some reason.

  • Let's ignore that and let's assume that I'll figure this out later,

  • and push to the repo.

  • But you saw I added the repo, and then it deleted itself,

  • which was very strange.

  • And it claims that I have access to that repo, but I'm doing a search for it,

  • and it's not coming up in my repo.

  • So I don't know.

  • Yeah, that's fascinating.

  • I don't know if that's a GitHub bug or what.

  • Probably because of the credentials.

  • Oh, Kareem's in the chat, everybody, if you want to give a shout out to Kareem.

  • He and I did a stream on Tuesday--

  • when was it?

  • Yeah, Tuesday.

  • Probably because of the credentials.

  • No, because I-- no, I added the--

  • what was it?

  • On setting up a new account, you get a-- what's it called?

  • What's the thing?

  • What is it?

  • I forgot.

  • Profile.

  • Not profile.

  • Account.

  • Settings.

  • The developer settings.

  • It's a personal access token, yeah.

  • And I enabled my--

  • I set up a personal access token on here and it was working perfectly last week.

  • I don't know.

  • I'll figure it out later.

  • Suffice to say, it'll be up.

  • Let's say let's move forward just so that we

  • don't spend too much time on that.

  • So assuming that we get it all up to GitHub later,

  • you'll be able to at least download it and follow along retroactively

  • but there are a few things that we want to take into consideration here

  • as we get started.

  • So our goal is to sort of represent a game space

  • where we have cards face down.

  • And so we can sort of think of cards as being just essentially drawing

  • rectangles, but rather than drawing rectangles in the same way

  • that we did for Snake, we probably want them

  • to look a little bit more card-like.

  • So we're going to implement a little bit of fancyness.

  • Let's say maybe give them rounded borders.

  • And rather than just drawing flat colored rectangles,

  • which is kind of boring, it might be fun to actually add images to them.

  • I saw this on OpenGameArt.org just as I was browsing before the stream.

  • So I'm going to try it out.

  • Open Game Art is a great resource for downloading free sprites

  • and free 3D models, even free sounds.

  • So if you want to get some placeholder artwork or even

  • real artwork for your game, depending on the license, definitely check that out.

  • I'm going to go in here.

  • So it's got a donate URL, all this other stuff.

  • I'm interested in the pings because I want all my images

  • to be separate for this stream.

  • In another stream, we can maybe look at how to separate sprite sheets out,

  • which would be kind of cool.

  • Oh yeah, Kareem is saying he sees the repo in my thing, but it 404s.

  • I'm not sure what's going on.

  • It's weird.

  • It almost feels like a bug or something.

  • Bella_kirs says hello.

  • Hello, Bella.

  • Good to have you again with us.

  • And thanks, Kareem, also for helping debug live.

  • I'm not sure what the issue is with that,

  • but I'll worry about that later, I suppose.

  • OK, so if I'm going into that folder that I got.

  • So I got that animal pack folder from Open Game Art.

  • I got a square folder in here, which looks like it has all the square PNGs.

  • I'm going to enable--

  • is this the Preview button?

  • Let me figure out how do we actually enable preview on here.

  • I don't remember.

  • I think this is how we do it.

  • Because we want PNG files.

  • Basically, you want PNGs.

  • It doesn't matter what image you typically use,

  • but PNGs tend to be the most commonly used in 2D game development,

  • and they have transparency enabled as just a default

  • feature on them, which is nice.

  • Because when we draw all of these PNG files to the screen,

  • we're going to want to not ideally draw them

  • with any kind of black background or anything like that,

  • but these are all good.

  • So we have elephant, giraffe, hippo.

  • I'm going to take all of these.

  • I'm going to command C. Go back to my folder

  • where my repo is, here, where my project is, rather.

  • Concentration.

  • I'm going to create a new folder in there called Graphics,

  • and within that, I'm going to paste all of the images

  • that I grab off of Open Game Art.

  • So now, I have an image graphics folder full of all these PNG

  • files with all these different animals.

  • And the size of them is 396 by 314.

  • That's pretty big.

  • I thought that they were smaller than that.

  • Hold on.

  • I thought they were 88.

  • Oh, it's saying that the PNGs are size 88.

  • That's fine.

  • We might be able to make that work.

  • If not, I can resize them.

  • Can I resize them programmatically easily on this account?

  • Probably.

  • If not, I'll scale them with love's love.graphic.draw function, which

  • we'll take a look at in just a second.

  • So suffice to say, I now have a project folder with a graphic sub

  • folder with all my artwork that I'll be using for this game project.

  • Nuwanda says OpenGameArt.org is awesome, thanks.

  • Yeah, no problem.

  • It's a site that I use, actually, for almost all of the game artwork

  • that I used in the Games 50 course, the GD50 course.

  • Which if you're tuning in and aren't familiar with it,

  • see csstudio.edx.org/games is similar to what we're doing here,

  • but a more academic approach to game development with lecture videos

  • and whatnot, but we cover Love2D and Lua in that course as well.

  • So definitely check that out if interested

  • in games like Super Mario Brothers, Pong, Flappy Bird, Break Out, and even

  • some Unity stuff as well.

  • All right, I'm going to go back to my project here.

  • So I can see it now in my Visual Studio code.

  • I can see here on the left, I do have a graphics sub folder.

  • And it's green, which tells me-- which is a nice thing about vs code,

  • by the way, and a lot of modern editors will have git integration.

  • So it tells me that since I last committed,

  • main.lua has changed because it's this yellow color,

  • and graphics and all the files in here are brand new files.

  • I haven't even added them to my git project yet.

  • So it's a nice visual way to keep track of what changes

  • you've made to your project, and what you need to commit

  • and/or add to your project.

  • OK, so let's think about maybe the next few steps,

  • and improving upon the design of what we did in Snake last week.

  • So recall that in order to load pretty much anything, whether it was a--

  • actually, I think the only resources we actually loaded into Snake

  • were sound resources, but generally, loading resources

  • should fall under the control of a--

  • the u also means untracked, m means modified for the color vision impaired.

  • Oh yes, good observation, Swarmlogic.

  • The m here in the circle for untracked, which is super handy.

  • Whipstreak says just joined.

  • Great to be here.

  • Good to have you, Whipstreak.

  • I remember you.

  • You joined us last time as well.

  • All right, so what I was saying was that we loaded all of our resources.

  • In the case of Snake, it was just our sound resources,

  • but we loaded those in our main file at the very top.

  • A better design would be to have some file that basically

  • says this is going to be the location where all the resources are loaded

  • so that I can just kind of contain all of that logic in one place,

  • and refer to it in only one place, and not have to worry

  • about it being in my main.lua file.

  • And what I typically like to do for that is to have some file

  • called dependencies.lua just in the parent level of the directory,

  • or the source folder of the directory.

  • If I have a main.lua, and then have a--

  • if I want to maybe have my main.lua, and then have all my other source files

  • in a sub folder, just to keep my parent level--

  • the top level of the directory-- kind of clean.

  • I'll have a source directory that contains everything but main.lua.

  • But in today's example, we're just going to have main.lua and then

  • have every other source file in the same level.

  • So dependencies.lua, same level as the main.lua, in this case.

  • Shayaaan says hey, thanks for these amazing sessions.

  • No problem.

  • Thanks, Shayaan for joining us.

  • OK, so dependencies.lua.

  • This is going to be--

  • I'm just going to get into a habit on today's stream

  • of kind of declaring at the top of the folder, which is kind of self

  • evident by the name of the source file, but nonetheless, I'm

  • going to declare upfront what the purpose of the file is.

  • So stores references to all loaded resources and source code

  • dependencies libraries.

  • So this module is actually two part in that I

  • use it not only to keep track of resources

  • that I load into the game, like sounds, and graphics, and things like that,

  • but also libraries that I might use from somewhere else,

  • or even my own source code files that I need to include in order for main.lua

  • to be able to recognize them.

  • In this case, I can load them all in dependencies.lua,

  • and then by going into main.lua--

  • and up here, doing require dependencies, just like

  • that, they'll all be included as a result of that,

  • assuming that I've included all of them in here.

  • So what I'm going to do is I have all of these different texture files.

  • What I typically like to do--

  • for something like resources that exist throughout the whole project,

  • I like to declare some value, some table--

  • prefix with a lowercase g, which says this is going to be a global variable,

  • and I'm going to call it gTextures.

  • And for every texture that I want to use in my project,

  • I'm going to use this hard bracket syntax with a string in there,

  • and I'm going to name every single one of them.

  • So going through my examples here, elephant gets love.graphics.newimage

  • graphics/elephant.png.

  • And what this is going to do, I can just copy this a bunch of times.

  • So lets see.

  • I have one, two, three, four, five, six, seven, eight, nine, ten,

  • eleven different ones.

  • So two, three, four, five, six, seven, eight, nine, ten, eleven.

  • So I just copied and duplicated that line a bunch of times.

  • I'm going to go into here.

  • This is going to be giraffe, and actually what I should have done

  • is Command D to highlight the other one.

  • So if I go over to here to elephant, and I highlight elephant,

  • and then I hit Command D, notice that it highlighted the next elephant

  • on that line.

  • So I can actually write both of those at once,

  • and just kind of do that, and save myself a little bit of work.

  • Not much, but-- whoops, I didn't do it that time.

  • Let's do that.

  • Monkey.

  • Go over here.

  • Panda.

  • Over here, we'll do parrot.

  • Just going through all of the sprites, one by one.

  • Penguin.

  • That time I didn't hit Command D. Penguin.

  • Whoops, hit the wrong line.

  • Parrot, penguin, elephant here.

  • Next one's pig.

  • Did I miss one?

  • Elephant, giraffe, hippo, monkey, panda, parrot, penguin.

  • I must have miscounted.

  • Yeah, I counted 11.

  • There's actually 10.

  • Basic arithmetic lesson for today.

  • Elephant, pig, and so rabbit is the next one, and then snake,

  • and then the last one is not actually a sprite file.

  • So now, I have a table that I can access anywhere

  • because I called it g textures without calling it local.

  • So remember, last time we talked about local.

  • So in this case, a local variable wouldn't be accessible through anywhere

  • in the project if I had to import it.

  • It required it somewhere else.

  • But since I declared it as gTextures without local as a specifier,

  • I can actually use it anywhere in my whole project, which is super nice.

  • So I've created a gTextures table, and notice

  • the g is important because it tells me, at a glance,

  • oh, this is a global table, so I should expect it to be accessible anywhere.

  • But if I had just called it, for example, textures,

  • it's unclear that it's global right off the bat,

  • and that's where you can get a lot of subtle bugs related to global variables

  • throughout your project, especially if you use a lot of them.

  • And especially if you happen to forget using local within a function,

  • or within a loop, or something like that,

  • it can be a source of many headaches, and that's

  • why most people recommend not using global variables because it

  • can lead to unwanted behavior.

  • But we've created a new table called gTextures.

  • It's global.

  • And we've called this function called love.graphics.newimage.

  • So this is a function we didn't used last time,

  • which will allow us to look for a path that we specify with a string.

  • So notice that we have graphics/elephant.png as the string

  • in this first function call, and that will allow you to--

  • it'll basically look at wherever, basically, the main.lua

  • is in your project.

  • It will, relative to that, look for a folder called Graphics,

  • and then within that, a file called elephant.png.

  • And if we had just written elephant.png, it

  • wouldn't work because at the parent level where main is located,

  • there is no elephant.png.

  • You actually have to go within the graphics folder

  • in order to locate that.

  • So make sure that the path is correct or you'll get an error when

  • you try to run the program.

  • And then all love.graphics.newimage does is basically just stores

  • some texture information in an object that you can then draw to the screen

  • any time you want to with a file called love--

  • or with a function called love.graphics.draw.

  • Whipstreak23, do you know if there are any online versions of Visual Studio?

  • You can look at the GitHub project Theta.

  • Online versions of Visual Studio.

  • I don't think they have an online only version of it, unless I'm wrong.

  • I'm not sure.

  • Online vs code.

  • Maybe?

  • Stack Blitz looks like an app that's powered by vs code,

  • but I'm not sure if it would come with Lua support because it--

  • nuwanda says can you give an example of a global variable creating a problem?

  • I don't understand why we don't make all of them

  • global to not have problems with local variables.

  • Offhand, it's going to be possibly-- let me think.

  • If maybe I called this--

  • or maybe I have a global variable called--

  • like if I said health is equal to 10, like here,

  • but then I have another function somewhere.

  • Or maybe I just use this x is equal to 10,

  • and maybe this x is used to keep track of score or something,

  • or pretty much anything you want to in your main.lua file,

  • at the top level of your application.

  • Let's say a score, for example.

  • And in a loop or in a function, maybe you have something like function--

  • let me think.

  • Just I don't know.

  • It can be anything that has an effect on x,

  • but let's say all you do is x equals x plus 10.

  • Now, you've basically changed this to 20,

  • and presumably, this could be anywhere in your whole project.

  • It could be in some random file, it can be in main.lua,

  • it can be in dependencies.lua.

  • But we've changed this 10 to 20, and maybe we

  • didn't intend to do that, or maybe in here

  • we just did OK, x is equal to five.

  • And then I'm going to say some loop where do some loop,

  • and then x gets turned into 10, into 15, into 30, and then back in main,

  • we're printing this as our score or something.

  • Or maybe it's not named x.

  • Maybe it is named score, but maybe in another loop

  • we want to calculate some other score, and we also called it score.

  • Because they're named the same, and because one's global and one's local,

  • but we're not specifying local here, this will just get overwritten.

  • And it's a habit that's easy to do if you come from Python where local isn't

  • a thing, and all variables are sort of locally scoped,

  • unless you specify otherwise.

  • So that's kind of a gotcha that you should worry about in Lua more so

  • than in Python, and it's especially a problem if you're coming from Python--

  • not used to the scoping semantics of Lua.

  • So good question.

  • A little bit hard to fully illustrate, but it's

  • something you'll inevitably run into if you actually

  • do start using all global variables.

  • It's something that I ran into several times

  • myself, even as I was putting together the curriculum for the games

  • course on EdX.

  • So good question.

  • Do you know how to use the Command D on Windows operating systems,

  • says Cauapaz?

  • It should be the same.

  • Control D on Windows if you're in Vs Code.

  • I'm not sure about other text editors.

  • it kind of varies from text editor to text editor.

  • I think Adam also used Command D or Control D,

  • but generally, between Macs and PCs, when

  • you're using editors and other programs, control and command are more or less

  • synonymous, though not always.

  • All right, so I'm going to rename this back to g textures

  • just so we're clear that it's a global variable.

  • I have all of my artwork now here, and I can reference it in the future.

  • Nuwanda says get it now, thank you.

  • Absolutely, no problem.

  • Keep the questions coming.

  • I'm going to go back to main.lua, and we're going to go into load,

  • and we're just going to set up our window here just so we can test that we

  • have our graphics images--

  • our graphics data ready to be rendered to the screen,

  • and that we can actually do that.

  • So love.load, love.window.setTitle.

  • This is a mistake I made in the original Snake.

  • I set this in love.keypressed, but this is just a nice little thing

  • to sort of tie the game together.

  • Love.window.setMode, 1080 by 720, full screen is false, resizable is false.

  • And then I'm going to, in my love.draw, call this function

  • called love.graphics.draw, which takes in as its first argument, a texture.

  • So an image object.

  • So love.graphics.draw gTextures.

  • And remember that I specified them all as having this string here.

  • So this is basically Lua syntax for a key.

  • Set a key here within these square brackets.

  • It needs to be square brackets if you're setting strings, and then

  • an equal sign, and then the actual image data itself.

  • So anytime I call gTextures bracket elephant or gTextures bracket giraffe,

  • it'll know to reference this image data, or this image data,

  • assuming that I have actually named all of these correctly, which

  • it looks like I have.

  • So I can go back to main.lua, and I can call love.graphics.draw gTexture.

  • Let's say elephant because that's the only one I can remember right now.

  • And actually, that's it.

  • I'm not going to specify any other command line parameters,

  • and I should be able to hit Command L. And when I draw it,

  • I do indeed get a big elephant drawn onto the screen.

  • So it's a bit large.

  • I think it's like 300 by 250 pixels, which

  • isn't the greatest size in the world, but it works for now.

  • But that's how you can get an image from some file--

  • ideally a PNG file, but it can be a GIF, or a JPEG, bunches of other-- bitmap

  • files, whatever files you want.

  • But PNG is kind of like the de facto image type for 2D games.

  • So try and stick with that if you can, if only for the out

  • of the box transparency, which is nice.

  • I'm going to go ahead and say whoops.

  • Here, my love.keypress function if key is

  • equal to escape, then love.event.quit.

  • Just so that I have that ready to escape from the program.

  • I don't have to hit Command Q, or Control Q on Windows, or whatnot.

  • Swarmlogic says haven't ever looked at Lua before with the love.x.

  • Are you defining functions like you might on the Proto in JS?

  • Or impl or a struct in rust?

  • So it's like JavaScript.

  • So for classes, you do prototype inheritance, and it's a bit ugly.

  • We're going to actually use a library in a second that

  • allows us to get very similar class syntax to what

  • you'd expect in something like Python.

  • But yeah, it's prototype based in Lua.

  • Chitsutote says what is Lua usually used for?

  • So it's used for a lot of things.

  • It initially started off as a config language

  • for large compiled applications.

  • And back in the late 80s, early 90s, you didn't have to actually compile

  • a complete like C++ or C program from scratch every time you wanted to change

  • some functionality.

  • So they allowed Lua to be used as a runtime that

  • would trigger certain functions that you defined in your compiled program

  • that you can then trigger in a script at runtime,

  • and change the parameters of those functions.

  • It allowed designers to program applications that way

  • because it's also easier to understand, but it also

  • allowed more rapid iteration of things like game engines

  • or large business type applications that are a pain to compile, especially back

  • in the day when it took 30 minutes or an hour to program something large.

  • But now, it's probably most famous for being in the games industry.

  • Typically, used in game engines that are themselves in C++.

  • Love2D itself is actually in C++, but it's used all over the place.

  • It's used in pretty much anywhere you're going to use a scripting language.

  • It can be used even in embedded systems, and it's very fast compared to--

  • I think it outperforms Python, and Ruby, and other similar scripting languages.

  • Yes, Swarmlogic, World of Warcraft add-ons.

  • That is correct.

  • It was.

  • I'm not sure if it still is.

  • It may still be used for World of Warcraft add-ons,

  • but certainly back in the day, it was used for World of Warcraft add-ons.

  • Good questions.

  • OK, so now we have our cards.

  • I'm not super pleased with how large these images are.

  • So I'm actually going to scale them a little bit.

  • So in Love, in your love.graphics.draw function,

  • specify a scale parameter as one of the--

  • well, as two of the parameters.

  • You can scale on the x and the y axes.

  • So I'm going to do that.

  • I'm going to scale it.

  • I'll try scaling maybe half down.

  • So I'm going to go--

  • OK, so I think first you need to specify the x and the y.

  • So I'm going to assume it's the same on the--

  • we're going to say zero, zero, which is the top left corner of every image

  • that you draw.

  • It's always relative to its top left by default.

  • You can change that by setting its origin.

  • I'm going to specify zero rotation because I don't want it to rotate,

  • and then I'm going to specify 0.5 and 0.5 for the x and the y scale value,

  • and this should work.

  • I might have the signature wrong, but it looks like I, indeed, got it right.

  • So now our elephant is drawing at half size.

  • So again, xy value for actually where I want to draw it.

  • So if I move that x100, y100, and rerun it,

  • we notice that now it's been shifted a little bit downwards.

  • And then a rotation value, which-- so if I specify--

  • I think it's in radians.

  • I don't remember offhand, but let's say 10 there.

  • It got a little bit wacky.

  • It rotates around its top left too, by the way.

  • So it ended up rotating kind of around, and being a little bit weird.

  • Let's try one, see if that works.

  • Yeah.

  • So it looks like it rotated around it's top left,

  • and it's rotating counterclockwise, I believe.

  • But we're going to say no rotation, and just have everything rendered just

  • like that.

  • So now it's drawing it at a pretty good size.

  • Let's sort of mock up how I want the game to look.

  • So I'm going to call this two, three, four, five, six.

  • Let's say I want to have four by three.

  • So one, two, three, four, five, six.

  • One, two, there, four, five, six.

  • So I want to draw--

  • remember, I want two of each.

  • So I'm going to say penguin--

  • this one will be elephant, this one will be pig, this one will be giraffe,

  • if I'm remembering all the ones I have.

  • I'm going to bring this up so I have a reference.

  • This one will be snake.

  • This one will be snake.

  • This one will be penguin.

  • This one will be rabbit.

  • This one will be rabbit.

  • So I have two elephants.

  • I have two penguins I do not have two pigs.

  • I'm going to say pig.

  • And then if I have this correct-- oh, I didn't even do parrot as well.

  • OK, we don't have enough to do all of the different animals,

  • but we have enough to do most of them.

  • So I have two penguins, two pigs, two snakes.

  • I do not have two giraffes.

  • So I'm going to specify a giraffe here.

  • And so if I do this, this is obviously not

  • going to work because it's going to just draw

  • every single image on top of each other, which isn't the desired behavior.

  • So I need to figure out how to space apart all of the images appropriately.

  • VectorWise says is this Python?

  • No, this is Lua using the Love2D framework,

  • which you can grab at Love2d.org, here.

  • And we had a prior stream on, where we implemented Snake.

  • If curious how we did that, there's a VOD here,

  • and it's also on our YouTube channel.

  • So check that out.

  • We covered more of the basics of Lua in there,

  • and we're kind of taking it slow today as well,

  • but diving into some of the aspects of Love2D

  • we didn't have time to look at for the Snake stream.

  • All right, I have all of my stuff.

  • So now, I kind of need to draw everything out in a grid.

  • This is going to be unwieldy really quickly here.

  • But let's just say 200, 100, 300, 100, and this is going to be four by three.

  • So 400, 100.

  • I'm going to say 100, 200, 200, 200, 200, 300.

  • Wait, sorry.

  • I have that mixed up.

  • 300, 200, and 400, 200.

  • And then lastly, I'm going to have 100, 300, 200, 300, 300, 300, and 400, 300.

  • Now, this should roughly be in a grid.

  • It looks like they're still a little bit clumped together.

  • So maybe I want that to actually be in increments of 200.

  • So let's do 300, 500, 700, and then 300, 500, 700, and then 300, 500, 700.

  • OK, the unfortunate thing is that--

  • well, it's not super unfortunate.

  • The heads kind of misaligned because they're--

  • it's the case that some heads have ears that are higher up,

  • and that's kind of like where the top left is, and then they have--

  • like the pig, for example, it doesn't have the ears,

  • so it's a little smaller.

  • And then the rabbit is a lot taller because it has ears, I guess.

  • So they aren't a uniform size, but we can sort of make it work.

  • We don't necessarily need to align everything with the heads

  • perfectly well.

  • As long as we draw everything spaced apart enough on cards,

  • it'll function well enough.

  • But that's kind of the idea.

  • We're going to space it out a lot better than that

  • when we actually get into rendering, but that's kind of the idea.

  • So I'm going to have all these cards, but they're going to be hidden.

  • It looks like that it needs to be as wide as the largest wide card possible,

  • which looks like the elephant.

  • So whatever the elephant's width is, we're

  • going to need to specify that as our card width for everything.

  • And then whatever the tallest card is, which I'm guessing

  • is probably the rabbit, that'll be the tallest.

  • At least as tall as the tallest card as well.

  • So we'll worry about that in a second here.

  • So if anybody has questions of what we've done so far,

  • definitely throw them in the chat.

  • We have drawing on the screen, so far, and scaling.

  • So two important things when dealing with sprite images.

  • I might actually shrink them even more, to be honest.

  • I might make them all 0.25.

  • Let's see how that works out.

  • So I'll just do that.

  • That's nice.

  • I kind of like that.

  • So again, notice that I used Command D, which was super nice.

  • I was able to highlight all of those, and then just sort of move

  • my cursor because now I can move this cursor on every single line,

  • separately, which is really cool.

  • So again, a nice, cool feature of the VS Code,

  • and actually Adam and Sublime Text have this feature as well.

  • So definitely check those out.

  • If you're curious on getting VS Code, which I recommend,

  • it's short for Visual Studio Code.

  • It's at Code.VisualStudioCode.com.

  • So that will allow you to download whatever

  • operating systems version of VS Code you need so you

  • can mess with the features of that.

  • OK.

  • So I have the actual images.

  • They're not necessarily semantically the same.

  • I could store the string for each card in some variables

  • so that it knows OK, this is penguin, this is elephant.

  • We might end up doing that just because we

  • don't have to worry too much about saving bytes for four times

  • three number of strings.

  • Small strings.

  • So only 12.

  • Or however many we want.

  • Even if we had 10 by 10, that's still only 100 strings.

  • It's not much.

  • It's 100 times maybe 10 times 10 bytes.

  • So whatever that is.

  • 1,000 bytes.

  • Not too much, right?

  • The other way we could do it is have, like we did last time with the tiles,

  • and have some sort of ID.

  • Remember, we had like tile snake body gets the value--

  • I think it was two, right?

  • And that lets us sort of semantically signify

  • that we have a constant variable here.

  • So we shouldn't change it.

  • It should kind of stayed the same no matter

  • what, and this will function as an ID so that we can say this card's ID

  • is equal to tile snake body, which gives it the value two, ultimately.

  • Whipstreak says how long is each of these sessions,

  • if you don't mind me asking?

  • Don't mind at all.

  • Generally, the ones that I'll be doing are probably

  • going to be about three hours long.

  • Sometimes they might go longer.

  • Today, we might go a little bit longer if it ends up being the case

  • that it takes a little bit longer to finish

  • it, which would be nice to finish it.

  • Snake took two sections, which were each three hours.

  • But Kareem and I, we did a stream on Tuesday that was about two hours long,

  • and some folks might do a stream that's for an hour and a half or so.

  • It kind of depends.

  • But generally, expect an hour and a half to three hours,

  • with the games ones being three hours-ish.

  • So we should figure out, basically, how we

  • want to differentiate the cards as by strings, as by ints.

  • Might be building up to it.

  • Apologies if I run a reveal, but why not use a loop

  • to control the render position?

  • Swarmlogic, good intuition.

  • That's exactly what we will be doing.

  • This is just to mock things up at the moment.

  • But good sort of premonition there.

  • Whipstreak.

  • Wow, you guys put in a lot of work.

  • Yeah.

  • No, it's fun stuff.

  • Live coding is nice because you don't have

  • to actually do much prep in advance.

  • So easier to come up with a lot of stuff,

  • but we'll be doing a lot of these things--

  • solving a lot of these problems live.

  • So it's kind of a trade in that sense.

  • Hopefully, we don't run into any stumbling blocks

  • like we did with GitHub earlier.

  • Chitsutote, do you have a stream schedule or something?

  • Not a formal schedule just yet, but probably the case

  • will be that we stream every Friday at 1:00 to 4:00.

  • And I'll probably leading a session on every Friday from 1:00 to 4:00,

  • and it'll be games related.

  • And then on other days, it will be fairly variable,

  • depending on who's streaming, and what their schedule looks like.

  • So I'll be co-hosting the majority of videos with whoever is streaming,

  • but whoever we have as the host--

  • for example, Kareem on Tuesday--

  • it'll sort of be up to them to decide when they can stream.

  • So it will be variable.

  • Between 1:00 and 3:00 will generally be the frame at which we start

  • the stream, and as per what I said before, it will go for about an hour

  • and a half to three hours in length.

  • And once we have a formal stream schedule setup,

  • we'll put it all down below in our Twitch--

  • sort of the banners there, and have everything set up.

  • Mentor27.

  • Is there a subscription?

  • We don't currently have subscriptions setup.

  • We probably will at some point in the future

  • when we become what's called a Twitch affiliate, where we actually

  • have monetization of the channel.

  • But in order to watch the content, no, it's all completely free.

  • You can chat live if you're a follower.

  • No subscription.

  • No premium features as of now.

  • Whipstreak.

  • Yes, there is.

  • Yeah.

  • You can follow, which isn't quite the same as a subscription on Twitch.

  • But no, everything will be available to watch.

  • Yeah, Whipstreak.

  • Yeah, you can follow.

  • And if you aren't following and you're watching along on YouTube,

  • definitely do follow us so that you can participate in the chat with us

  • as we do more of these sessions in the future.

  • All right, so as I was alluding to before,

  • we're going to need some sort of way to differentiate

  • between the different cards so that we can match up between them,

  • and compare, basically, oh, did I flip over an elephant card

  • with another elephant card?

  • Or did I flip over an elephant card with a penguin card?

  • And are the two, therefore, the same and should stay up?

  • Or are they different, and should be flipped back over?

  • So what I'm going to do is create a new file called card.lua,

  • and this is actually going to be the class represents a card with its image

  • and ID information.

  • So our card is actually going to be kind of a holistic representation

  • of our card.

  • It's going to basically store a string that

  • references its position in our g textures table

  • so that we can call a render on it, and then know what to render.

  • And it'll also represent an ID, just to keep track of what kind of card

  • it is so that we can compare it with some other card.

  • Now, object oriented programming in Lua is a bit different

  • than it is in Python, or Java, or some other languages.

  • It's based on prototype inheritance.

  • We won't take a deep dive into that today, although we might in the future.

  • Instead, I'm going to download a library.

  • Love2D class library.

  • And there is a class library that I like to use.

  • It's part of an unfortunately named library here, which you can pull up.

  • Helper utilities for a multitude of problems, and it's on GitHub.

  • So what I can do, hopefully, is clone it.

  • And this is a trend that you see in the Love2D library.

  • They have some unfortunately named code sometimes.

  • It's part of an inside joke, but I'm going to grab that library.

  • So it's here if you want.

  • I will refrain from saying it on stream.

  • But if you go to this URL and clone their code,

  • I'm going to go into my streams folder.

  • Yeah, let's clone it here.

  • Hopefully, it gets working.

  • I'm going to take from that folder, just the--

  • let's see where it is, here.

  • I'm going to take in the class.lua.

  • This file here.

  • This is the library file that we're going to need.

  • So I'm going to copy that from the library

  • that we just downloaded, and then I'm going

  • to go over into my concentration folder, and I'm just

  • going to paste it right in there.

  • Class.lua.

  • So now I have this new dot Lua file in my actual project called

  • class.lua, which is a library that somebody else wrote

  • that will allow us to use more classical style object oriented programming,

  • or at least make it look like it just so that we have an easy way to transition

  • into doing that, rather than talk about meta tables, and all of the semantics

  • that go into making object oriented programming work

  • in Lua, which can be a mouthful.

  • So in my dependencies.lua, before we can do that, at the very top,

  • I'm going to say class equals--

  • not in a string.

  • Require class.

  • Just like that.

  • So now in my main.lua, I have the class.lua.

  • So remember, every require statement allows

  • you to reference a Lua file without the dot Lua suffix like that,

  • but this is effectively what we're doing.

  • We're just requiring-- we're importing.

  • Basically, copying in the class.lua code, but you just need to use class.

  • You don't need to use dot Lua, and I'm assigning it to this variable

  • called Class with a capital C.

  • And what that's going to allow me to do is

  • it's going to basically use Class almost like a new keyword.

  • What it is is it's a table, effectively.

  • Pretty much everything in Lua is a table, but in this case,

  • the table will allow me to create new objects that

  • are similar to objects and classes from other languages like Python or Java,

  • which allow me to represent abstract concepts--

  • for example, cards-- just like there were types like numbers, and strings,

  • and other data types, or tables.

  • So if I go back to card.lua, and I say Card equals class, just like that,

  • notice that I capitalize card to semantically tell me

  • oh, this is a class.

  • By default, in a lot of languages--

  • Java among them probably most prominently,

  • but also adapted in other object oriented languages.

  • Anything that's capitalized is generally a class.

  • So Card is a class, and a class is just basically

  • a blueprint that says every card object I make is going to have these functions

  • and these pieces of data.

  • So I can keep track of everything that belongs to a card within an object,

  • rather than have a bunch of different variables that say oh, card1.image,

  • or card1--

  • rather, it wouldn't be dot image, but card1 image

  • equals elephant, or card1 ID equals elephant ID, whatever.

  • I can keep all of that with a class--

  • within an object that contains itself.

  • So everything is more modular that way.

  • So I can now declare some functions.

  • So If I say card init.

  • Basically, this function here is going to-- every time

  • I want to make a new card, this is the function that's going to get called.

  • And let's say that when I create a new card, I want to pass in a string.

  • So let's say I wanted to be called card type.

  • It's going to be of type string, we'll assume.

  • So I can say self.cardType.

  • Or what would be the best way to handle it?

  • Maybe face is equal to cardType.

  • And so what self will do is basically say for this card object,

  • its face is going to be whatever we passed into this init function

  • when we called it in our code.

  • And some other important things that we're going to need

  • are, for example, maybe an x and a y to keep track of where

  • to actually draw it onto the screen.

  • So I can say x and a y, there, in my function signature.

  • So whenever I call init, it actually doesn't get called init.

  • It just gets called with parentheses.

  • Whenever I call this function, like a new card, I can pass in the string.

  • So this will be elephant.

  • I'll pass in an x for its x position and a y for its y position,

  • and then set self.face to cardType, self.x to x, and self.y to y.

  • And so now, in any other function that I declare in my card class here--

  • for example, this render function--

  • I can reference any of these self variables.

  • Which is super important because now, basically, I've

  • kept track of some internal state that persists amongst all of the functions

  • core to this object-- this class.

  • RehabThis says CS50 is love.

  • I've learned so much from CS50.

  • Great work.

  • So glad you are on Twitch now.

  • Thanks Rehab.

  • Appreciate it very much.

  • Thanks for joining us today.

  • Hope you enjoy us programming some concentration.

  • All right, so we have this scaffold in place for this card class.

  • We can now reference individual card objects,

  • not keep track of all these variables that are

  • going to get unwieldy pretty quickly.

  • If you remember, for example, from Snake,

  • we had a bunch of variables that represented

  • what direction we were moving in, what our current x and y

  • were for the head, and even the table that itself

  • kept track of all of the individual nodes that comprised our Snake.

  • And these were all separate variables in main,

  • and it started to take up a substantial amount of space,

  • and we don't necessarily want this, especially in our main.lua.

  • So rather, delegate all of that or relegate all of that

  • to an individual class file, and thus, the objects themselves

  • can manage their state, rather than us managing

  • some combination of states in main.lua.

  • So if you're unfamiliar with object oriented programming,

  • definitely feel free to ask some questions in the chat.

  • We'll try to clarify anything that we've looked over that's potentially unclear.

  • But object oriented programming is a very common paradigm,

  • especially in game programming just because by virtue of what games are,

  • typically simulations of some sort of reality, some sort of world,

  • some sort of game space, they do tend to involve a lot of objects interacting

  • with one another in various ways.

  • So by virtue, the paradigm maps pretty well to game programming.

  • All right, so I have an empty update, an empty render function.

  • If I go back to main, in my love.load, maybe I want to say cards equals--

  • and I'm declaring this as a global variable,

  • in this case, although it would be better practice to say gCards.

  • So we'll do that.

  • gCards is going to be some table, and then I'm going to initialize.

  • And this is per--

  • I forget who said it.

  • It was Swarmlogic who said why not use a loop to control the rendering position.

  • Well, we're going to do that, and we're also

  • going to use a loop to instantiate.

  • That's the term for creating a new object of a particular class.

  • We're going to use a loop to instantiate a bunch of cards,

  • rather than have to sort of keep track of a bunch of arbitrary numbers.

  • These are all what are called magic numbers because they

  • exist written in and out of the blue somewhere, not stored in any variable.

  • This is something that you tend to want to avoid in programming.

  • So I'm going to use a loop.

  • I'm going to say we're going to need four by three.

  • So let's say I want to have--

  • should I store this in a 2D array?

  • I probably should.

  • So gCards.

  • So for y equals one, and I'm going to essentially disregard--

  • do I want to do that?

  • No.

  • We're going to do best practices today.

  • So constants.lua.

  • So I created a new file in my parent directory called constants.lua,

  • and this is an improvement over last time because if you remember,

  • our Snake game had a ton of constants at the top of the file

  • to represent things like window width, and the number of Snake

  • tiles we could fit on our grid, and a few other things.

  • I think the speed was one of them, even though the speed ended up

  • becoming an actual proper variable, not a constant variable.

  • But having a file called constants.lua-- notice,

  • that it's lowercase because it's actually a--

  • and dependencies could also be lowercase as well,

  • but the trend, typically, is to lowercase.

  • Files that contain data versus files that contain a class.

  • Files that contain a class, like our Card.lua,

  • will typically be capitalized.

  • Always be capitalized, rather, in object oriented languages.

  • Python is kind of an exception.

  • You will see some Python files that will have a class in them that aren't always

  • capitalized, but that will tell you at a glance,

  • oh, this has a card class in it.

  • Dependencies.lua probably shouldn't be capitalized.

  • I kind of capitalize it because it's like the one big file

  • that I import into my main file, and so I think for me,

  • mentally, it kind of has that importance.

  • But you can have this be lowercase or capitalized.

  • Probably, technically correct to have it lowercase over having it capitalized.

  • But constants.lua does not need to be capitalized.

  • I'm going to do a few things here.

  • Window width equals 1280.

  • Window height is 720.

  • These are constants because they are--

  • not enforced constants in that the compiler will yell at us

  • because there is no compiler in Lua.

  • It's an interpreted language, and it has all dynamic typing for its data.

  • But by capitalizing all of our words and having underscores

  • like we talked about last time, it basically tells us, at a glance,

  • these are constants.

  • Probably don't edit them.

  • Don't change their value.

  • Let's say CARDS_WIDE is going to be four and CARDS_TALL is going to be three.

  • And we might not need any more constants for a while,

  • but these constants will help us with rendering our grid.

  • We'll be able to say, rather than having magic numbers in our code,

  • which is a bad thing which we just mentioned,

  • put it in some constant call CARDS_WIDE.

  • And I can actually change these, and this will change the game logic

  • if I were to want to do maybe more cards on the x-axis,

  • more on the y-axis, however I want to structure it.

  • Now, I can't actually see this file because I haven't imported it yet.

  • So I'm going to go into my dependencies.lua,

  • and then require constants, which will import all of the--

  • because remember, these aren't being declared as local.

  • So they're global.

  • So if I import them, they'll be visible.

  • So these are now included in dependencies,

  • and dependencies is included in main.

  • So all of these constants, thereby, have propagated

  • to main, while being able to keep everything fairly modular.

  • So we have all of our data here, and then our constants here,

  • and then our card class here.

  • As opposed to putting all of this in main.lua, which will get very bloated,

  • very quickly, and then we just have a monstrous file

  • to browse through, which is not ideal.

  • All right, as we were saying earlier, for y

  • is one until CARDS_TALL do for x is one CARDS_WIDE do.

  • And we did this last week in the Snake example, when we set up our grid.

  • Our tile grid.

  • But I'm going to say table.insert into gCards an empty table.

  • So this is how you do two-dimensional tables in Lua.

  • And then I'm going to say table.insert into gCards y, a card object.

  • And this is sort of where, now, we can take a look at how we instantiate cards

  • after we've declared the class.

  • So we had our class that we declared and defined earlier, although not

  • in its entirety.

  • It's still incomplete, but we do have a class.

  • It is functional.

  • Although, one thing.

  • It is not actually in our project.

  • So what we need to do is say I'm going to keep it alphabetical.

  • So require card.

  • So now it will actually be visible because, again,

  • if you look at our card.lua, this card declaration, here--

  • card gets class curly brackets-- is a global variable.

  • So just like everything else, it will propagate when

  • we require it amongst multiple modules.

  • So here is where we can actually call what's called

  • the constructor for the card class.

  • It's going to be what actually makes the new card object.

  • So I can say card, just as it was declared-- card capital

  • C. This is making a new card.

  • It gets called like a function, so it has two parentheses.

  • And this is where I actually specify that string--

  • because remember, we need a string to determine what its face is going

  • to be-- what's going to render when it's flipped over--

  • and then it's actual x and y value.

  • So those will get plugged into the x and the y field of our card object.

  • And those are what are called arguments to the constructor,

  • and they're defined as-- in the class, they're defined as parameters.

  • So arguments when they're passed in, and when

  • they're defined in the actual card.lua in the signature of the function,

  • they're called parameters.

  • So now, I want to say maybe I want to make all elephants just to test it.

  • This will be random later, although it should be in random pairs, of course.

  • I'm going to say I need a random--

  • or I need an x and a y to render it.

  • So an actual place to store the pixel value.

  • So I can say x times--

  • or it has to be x minus 1 times 100, and then maybe y minus 1 times 100.

  • Although, I think that might be too small.

  • Will that be too small?

  • No, because we're going to resize it by 0.25.

  • We're going to resize it by 75%, such that everything

  • is of size 25% of the original image, which

  • I believe does fall within 100 pixels.

  • So this should work, at least for mock up purposes.

  • We can more fancily arrange everything in more like a grid

  • later with equal margins on the top and bottom.

  • But that's how you call the constructor for the card class.

  • So now, every one of these is going to get an elephant string.

  • It's going to get an x.

  • That's a function of whatever x we're on in our 2D loop.

  • And then a y that's the same.

  • Whatever our y is in our 2D loop, it's going to minus one

  • and then multiply by 100 to convert it from Lua one index semantics

  • for tables, which if you're unfamiliar with Lua,

  • the way they do it is all tables, all arrays, start with one instead of zero.

  • And then convert that to our coordinate system

  • because the coordinate system, which is based on the top left corner,

  • is zero indexed.

  • So it's a little bit of a weird thing, but that's the way

  • that Lua does business.

  • OK.

  • So now we have all of the cards instantiated in a loop.

  • So this table is going to be filled with these card objects.

  • They're all going to have a string, and they're all going to have an x and a y,

  • but they're not going to really be able to do

  • much because they don't have an update function,

  • and they don't have a render function.

  • Now, their render function is essentially what we did before.

  • So I can kind of go into main.lua.

  • Take a look down here at the--

  • for example, this line of code.

  • Delete all of these.

  • Go back into card.lua and copy this.

  • So love.graphics.draw gTextures, and then it's hard coded to the elephant

  • here.

  • But remember, in our constructor, that's what

  • this is, we said self.face is equal to the card type.

  • So what I can do is say self.cardType there.

  • And so whether this was elephant, penguin, pig, snake, whatever,

  • now I can dynamically figure that out.

  • And I can draw it not at 100, but I probably

  • want to draw it at self.x and self.y.

  • Easy.

  • And if I go back to main, we still have to actually do all the drawing.

  • So if I say for y is one CARDS_TALL do, for x is one until CARDS_WIDE do,

  • gCards y 1 draw or render, I should say.

  • Not one, sorry, y x render.

  • Because remember, that's the 2D array, and 2D arrays

  • are typically built with y being the first parameter,

  • x being the second parameter just because the rows are

  • built before the columns are placed just because of the way

  • the objects are laid out, the tables are laid out,

  • or the 2D arrays are laid out if you were to write them out in your editor.

  • Self.face.

  • Oh, did I?

  • Oh, you're right.

  • You're right.

  • Sorry, good point.

  • Not self.cardType.

  • Self.face, and that's why, typically it's better to just name in the same.

  • So I'm just going to do that just to avoid confusion.

  • Instead of having it be card type here, and then face here,

  • just make it face and then self.face equals face.

  • So yeah, thanks very much Tmarg for pointing that out,

  • and then d3ah for confirming.

  • OK, good observation.

  • So now, if I go back to main, I have everything laid out.

  • So I have my 2D loop, which is what Swarmlogic said earlier.

  • The loop to control the actual rendering position.

  • We have no more magic numbers.

  • Everything is calculated here in our 2D loop.

  • We can probably take this out, put this into a function, which

  • we'll do in a second.

  • But if I run this, assuming that I've done everything correctly,

  • which we've done a lot, so I might have screwed something up,

  • and it looks like I did.

  • So main on line 48, which says gCards at y x renders.

  • That's getting a null value.

  • OK, no problem.

  • Let's figure that out.

  • So y until CARDS_TALL.

  • We are doing a table.insert into gCards y.

  • A new card-- and make sure I got all my parentheses

  • correct as well, which I did.

  • Make sure I didn't do something subtle and incorrect here.

  • Table.insert into gCards.

  • So gCards is an empty table.

  • We're looping over this.

  • What is it?

  • What would be the easiest way to do this?

  • I guess just print the length of gCards.

  • Make sure that we have the correct number, and then also gCards one.

  • So I'm printing out, basically, however many rows we have in our table,

  • and however many columns the first row has just to kind of--

  • what's the word-- sanity check myself to make sure my looping logic is correct.

  • And then I have to actually run this through the console.

  • So I'm going to do that, otherwise it won't run through Visual Studio Code's

  • thing here.

  • So if I go into--

  • is it just the double parentheses on render?

  • Oh, that's probably it.

  • I didn't even see that.

  • Wow, good observation, Tmarg.

  • Let's try that.

  • Yeah.

  • OK, I totally missed that I did a double parentheses there.

  • I'm curious what that was actually doing.

  • Oh, because it returns a null value when you call a function.

  • So a function that has no return type calls a null value,

  • and then null value is trying to call itself after that,

  • and so that's why it was a null value.

  • But yeah, so we have our game rendering, and everything is sort of nicely

  • laid out in a four by three grid.

  • So let's take a moment.

  • If anybody has any questions on what we've

  • done just to spend a couple of seconds there.

  • Would you please move the chat room to the top?

  • It overlaps some code.

  • Sure.

  • Yeah, let me do that.

  • Let's see if we can move this up here.

  • Got to go to the editor.

  • Give me just a second.

  • Let me know if that's fairly readable still.

  • If that's any better.

  • It's still going to overlap a little bit, I think, but not super terrible.

  • Might be able to make it a little bit smaller maybe.

  • See if I make it a little bit smaller, and then--

  • that's a little too small.

  • Let's try that.

  • Let me know if that's too small and unreadable?

  • Hopefully, that's decent.

  • So now we've gone from having a ton of hard coded

  • draw statements with various strings.

  • Now we have a sequence of animals--

  • well, rather, sequence of the same animal being rendered as by a string

  • value that we pass into a constructor, and an x and y value

  • that we pass into a constructor.

  • So super handy.

  • We don't have to do a bunch of manual work

  • in terms of calling that render function over,

  • and over, and over again with just some slightly changed values.

  • But the problem that we do have is that we're only getting an elephant

  • rendering, but what we need is to have pairs of the same animal rendered.

  • Only two of the one animal, but in random positions.

  • As we're instantiating this grid of cards,

  • we need to figure out how can figure out, in advance, what animals

  • and what positions they should take in my 2D grid.

  • So let's spend a second to think about that.

  • So I know that I'm probably going to do it here

  • before I actually instantiate everything,

  • and I'm going to take this print statement out of here as well.

  • I'm going to copy all of this from the gCards onwards.

  • I'm going to go down here at the bottom, and I'm

  • going to declare a new function called generate cards,

  • and I'm just going to paste in all of that code.

  • And then up in love.load, where I had all of that,

  • I'm going to call the function.

  • So now, it's at least a little bit more modular.

  • Love.load isn't super bloated, and it's all down at the bottom.

  • And I know that it's probably in here that I want

  • to worry about the different cards.

  • Maybe a map with keys as animal names and values as integer.

  • As you use a random key decrement, and if is zero, drop the key.

  • That is a very good approach to it.

  • So I think we'll actually do that.

  • So good proposition there, Swarmlogic.

  • So we're going to need a map--

  • so a table.

  • So Lua tables are maps, or hash maps, or dictionaries

  • if you're coming from Python, or objects if you're coming from JavaScript.

  • Although, objects, they have a different connotation

  • in most programming languages.

  • So yeah, we need a map.

  • So we can just say local animal map gets an empty table, and then we need to--

  • since we have four times three, we're going

  • to have four times three divided by two numbers of animals in our grid.

  • So what we can do is say local numAnimals

  • equals cards tall times cards wide, and then divide

  • the products of that by two.

  • And so that will result in a number of unique animals

  • that we want in our grid.

  • So to get unique animals out of the list of animals that we have,

  • I basically want to--

  • let's say, in constants.lua, let's say I will have a global table that's

  • going to have all of the strings of all of the possible animals in our graphics

  • folder.

  • So that's going to be elephant, giraffe, hippo, monkey, panda, parrot, penguin,

  • pig, rabbit, and snake.

  • So now we have an actual list of all possible animals,

  • which we're going to need.

  • Before we do anything with randomization,

  • let's not forget to do a really important thing

  • before we call a random number, math.random,

  • and that is seed our random number generator with the current time

  • value of the operating system.

  • So we do that with math.randomSeed, and we pass an os.time.

  • And what the result of that is os.time gives us

  • a really long number, which will always be different every time

  • we run our game, and then we pass that into the math.random seed function,

  • which takes in a number, and will influence the random number generator,

  • thereby.

  • It's called seeding our random number generator.

  • And so what we can do is say for--

  • Shaina with the Bob Ross emote.

  • What's up, Shaina?

  • Good to see you.

  • Thanks for the Bob Ross reference there.

  • Per Swarmlogic's suggestion, we're going to need, basically,

  • a map that has however number of numAnimals keys.

  • They're going to all be unique strings, and each of those keys

  • is going to have a reference to a number.

  • It's going to be initialized with the number two.

  • And then every time we basically get a random animal from this table,

  • it's going to decrement two.

  • And then when we've done it twice, it's going to be zero.

  • And when it's zero, we should delete that key from the table

  • so that we can no longer reference it.

  • So it'll just be empty, as by setting it to nil

  • is that is the way to do it in Lua.

  • So let's think about that.

  • So basically, what I want to do is I want to get four unique keys.

  • Well, not four.

  • It'll be six.

  • It'll be this number.

  • So numAnimals keys.

  • So for i equals one until numAnimals do.

  • So get a unique animal for every pair of animals.

  • And then for each of those, I want to get a unique string from the animals

  • constant that we declared in our constants.lua.

  • I want to get a random string, but I want to make sure

  • that I haven't gotten it already.

  • So I do need to check that it exists in our table--

  • our animal map.

  • It'll be this, right here.

  • And I don't think we need to store this global at all.

  • So that's why we're making it local, because all of these cards

  • are going to have a reference to the string.

  • So yeah, we don't need to--

  • this can just be something that we use here,

  • and then just discard after this function runs.

  • It doesn't need to be stored anywhere, unlike our gCards.

  • The gCards do need to be kept because we need to use them in update,

  • and we need to use them in draw.

  • So we're going to keep those.

  • So I want to say local do or repeat, rather.

  • Local animalStr for animal string is going

  • to be animals at a random index in animals.

  • So remember, the hash symbol is a length operator.

  • So I want to say into animals, index it by its length.

  • So it'll give me a random--

  • or rather, I need to do this.

  • Math.random number of animals.

  • So now, it'll generate a random number between one

  • and the number of animals in that table because we're using number animals.

  • So as virtue of that, by indexing into it by that value,

  • we'll just get a random key from that table.

  • And then I'm going to--

  • actually, no.

  • What I need to do say local animal string until.

  • So basically, I need to declare this up here, because this variable will be--

  • because we're declaring a local in here, it

  • would be just deleted as soon as we left this loop.

  • When we got to this until statement, it actually

  • wouldn't be visible, or outside of the block and the until statement.

  • So I declare it up here, local animal string, which

  • means that the repeat block will have access to it,

  • and anything after the repeat block will have access to it

  • until we get outside of this for loop.

  • So basically, declare an animal string.

  • Get a random animal string until not gCards at animal string.

  • And so what that'll do is--

  • I believe that is correct.

  • The syntax highlighting is being weird, so I'm just

  • going to sanity check myself.

  • Lua not.

  • That's correct, right?

  • Yeah, OK.

  • I get a little bit tripped up occasionally

  • with that syntax highlighting.

  • So not is valid.

  • I'm not sure why this doesn't get highlighted, for some reason.

  • I might not have the syntax highlighting plugin enabled on here--

  • the better one.

  • I might just have some non-fully fleshed out one.

  • But yeah, not will basically check to see whether or not something--

  • if something is falsey or truthy, it'll equate to--

  • basically, if not something, then that'll

  • be true if whatever it tried to say not on was some false value like zero,

  • or nil, or an empty table.

  • Those are all equal to--

  • those both equate to not being true.

  • So until not g cards animal string.

  • So basically, this is checking to see do we have the key animal

  • string in animals already?

  • If we do, then this will equate to false because not

  • will become false, and then instead of true, because we're testing for not.

  • Everybody give a shout out to Davidjmalan in the chat there.

  • Tossing me some extensions for Lua.

  • I'll take a look at those.

  • I probably need to install it.

  • That's always the-- it happens with some other syntax too.

  • I don't think is a key word in here.

  • It's not.

  • But whenever I see not not highlighted, I always second guessed myself.

  • Not is valid, though.

  • OK, so until not animal string.

  • And then what we want to do, once we've broken out of this repeat until,

  • which is the same thing as a do while in other languages, by the way,

  • I want to actually assign it in my animal map.

  • So I want to say animal map animal string gets the value two, which

  • is, again, what Swarmlogic suggested as a means of keeping

  • track of whether we've placed the animals in our grid.

  • And so as a sanity check, I'm going to print animal map here,

  • and I'm going to--

  • I believe its exit, and I'm going to go into my concentration love dot.

  • Lua exit.

  • No, just love.event.quit.

  • OK, let's try that.

  • Oh, it's just a table.

  • Right.

  • It's not going to actually print the table in a very prettified way.

  • So what I can do is I can say for underscore entry--

  • well, actually, no.

  • I'll say for k, v in pairs animalMap do.

  • And what this will do is remember, pairs is our iterator.

  • The function that we use to actually iterate over

  • a table to get every key value pair within that table.

  • So I can say for k, v in pairs animalMap do, print kv, and then

  • I'll call love.event.quit.

  • And what that'll do-- if I run it again--

  • is now I can see I do, indeed, have elephant, I do have giraffe,

  • and I have rabbit, and I have parrot, and they all also

  • have a value of two set as their value, which we'll use to decrement.

  • Now, that's not the right number of animals,

  • I'm realizing, because if we're doing four

  • by three divided by-- four times three divided by two,

  • that should have given us six.

  • CARDS_TALL times CARDS_WIDE.

  • Is that correct?

  • CARDS_WIDE by CARDS_TALL, which that's going to be 12 divided by two

  • is going to be six.

  • Let's figure that out.

  • I don't think we've even checked to see whether they were unique.

  • Which they are, in this particular case, but let's test several times.

  • So in this case, we got pig, hippo, monkey, penguin.

  • This time it was--

  • OK, that's fascinating.

  • OK, so there's a bug in our logic here, it looks like.

  • animalMap instead of gCards.

  • No.

  • Did I call gCards?

  • Yeah.

  • g we're calling animalMap, here.

  • Swarmlogic.

  • Everybody's showing David lots of love, which is nice.

  • OK, so there's an error with my logic.

  • It looks like here, potentially.

  • We're getting variable output here.

  • So I have one, two, three, four, five, in this case.

  • I have one, two, three, four, five, six, in this case.

  • I have only four in this case, and only four again in this case.

  • They are all unique, which is good, but we do have a bug.

  • So let's investigate why we have a bug.

  • Are we overwriting?

  • So it looks like what we're doing-- my intuition is correct.

  • We are overwriting an index in our table.

  • So they actually aren't unique.

  • It's just the case where you can't have the same key mapped

  • to some other value in a table.

  • It's just going to overwrite the old key if you do that.

  • So this logic actually isn't working completely.

  • So what we need to do--

  • so repeat animal string.

  • So we're getting a random animal string, right?

  • Oh, that's what you're referring to.

  • OK, got it.

  • Until not animalMap index animal string.

  • That is correct.

  • And Ireneaya joined us today.

  • Thanks for joining us, Irenenaya.

  • I know you said you weren't going to potentially join us.

  • Happy to have you here.

  • I didn't even realize that until right now,

  • that you're referring to that line.

  • But yes, indeed, I do believe that was the problem.

  • So now we have six, six, six, and six.

  • So that's working perfectly well.

  • So don't mix up your variable names, which is what I did.

  • OK, so great.

  • So now we have our map.

  • So that's going to keep track of the actual cards

  • not only as they are inserted into the--

  • well, actually, we're going to have to have a slightly different logic

  • for inserting the cards, I think.

  • Do we into gCards?

  • Oh, yeah, it's just for inserting.

  • All it is is for inserting the card.

  • That's right.

  • OK, perfect.

  • So let's delete the iteration.

  • Some more lovely notes for David in the chat.

  • Swarmlogic says I'd like to say thanks.

  • I took CS50x a few years ago, and it launched my learning to program.

  • I'm now a senior engineer at a company in New York City.

  • Pretty good for a self-taught army vet.

  • That's really impressive, Swarmlogic.

  • Good job.

  • Awesome job.

  • You've been providing a lot of great suggestions too.

  • So I can see it's definitely paid off.

  • We have our table.

  • Now, what we were going to do is we were going to take that animalMap,

  • and we were going to use that in our logic

  • for actually instantiating the cards here.

  • What we're going to need to do is we're going

  • to need to grab the actual face from the card itself,

  • and we're going to do that.

  • It's going to be the actual key of the table.

  • So I can say local face equals.

  • Well, first, what I want to do is I'm probably

  • going to say local random index.

  • We're going to need to index into our table to get the random face,

  • and we're also going to need to get the--

  • well, actually, is that going to work out with the keys?

  • We're going to have to--

  • we're going to use a random--

  • we're going to index into it randomly.

  • That's correct.

  • OK, that's what it's going to be.

  • So local random index equals animalMap at index math.random number animalMap.

  • So this will do the same thing that we did before when

  • we got a random animal from the--

  • bhavik_knight says hey guys, has it started early?

  • Seems like going on for a long while.

  • Completely forget about it starting early.

  • Yeah, we started at 1:00 today.

  • We're going to probably start trying to stream a little bit earlier,

  • and it'll probably be the case that we do

  • begin more consistently at 1:00 PM instead of 3:00 PM

  • on Fridays just because more people, I think, abroad can tune in to see us.

  • But some streams will take place a little bit later.

  • Monday's stream with David will take place at 3:00,

  • and then Tuesdays will also be at 3:30, and then next Wednesdays we'll

  • be at 2:30.

  • So it's kind of on the later side for the first three days next week,

  • and then next Friday we'll be doing this again at 1:00.

  • So it's going to be kind of flexible.

  • OK, so we're going to get a random index into our animalMap.

  • So we're going to say animalMap math.random

  • animalMap at length animalMap.

  • And then we're going to say local face equals--

  • that's going to be the key, which is going to be--

  • right, because it's going to be the actual key itself.

  • And so if we're going to do that, it's going to be a little trickier.

  • Is it?

  • Trying to think.

  • We want to be able to, right now, get a random animal from the animalMap,

  • and then I guess this will work because Lua tables are--

  • they are indexed.

  • It's going to be hard to get the key from the value.

  • Let's see.

  • Is there a way in Lua?

  • Lua get key from table.

  • If there's a way to get the key--

  • I guess we could do a list of keys.

  • Oh yeah, that's what we could do.

  • OK, I guess.

  • Yeah.

  • It's a little unwieldy, but we can say local animalKeys is

  • equal to where is it?

  • Table.sort keyset.

  • Because we're storing the keys directly--

  • I guess you get the animal name randomly from animals and use that as key

  • from animalMap.

  • Magedrifaat says I think you should get the animal name randomly from animals,

  • and then use that as the key for the animalMap.

  • The only problem is that we aren't always

  • necessarily going to have every--

  • we're not going to have every animal from animals in the animalMap.

  • So we could get a nil reference, in that case, trying to index

  • where we don't have data, basically.

  • Tmarg, you could reference pairs animalMap.

  • Yeah, it's going to iterate over the entire table only once, though,

  • and we're going to need to iterate over it randomly and twice.

  • So I'm trying to think the best way to do that because we could do it

  • through iterating over it because I believe I can still index

  • into it with a number.

  • So I could do--

  • yeah, I believe all tables are numerically indexed and string indexed.

  • So if I were to just do something like--

  • well, let's test that in Lua, actually, because I'm not 100% sure about that.

  • So if I did t gets hello world, and I do--

  • whoops, what did I do?

  • Oh, I did the wrong syntax.

  • Hello equals world, and I believe this needs to be in a bracket,

  • and then I do t1.

  • Sorry, Lua t equals hello equals world, and then I print t1.

  • It's nil.

  • OK, so they're not numerically indexed and hashed.

  • So we can't actually do it that way.

  • Looks like key set should give you the keys of the table.

  • Yeah, I guess we could do it that way, and then just iterate over keyset.

  • Just a random one from keyset.

  • If we do that, then we're all not only going to have to decrement the two

  • and delete the key from the table, we're also

  • going to have to delete it from keyset.

  • Yeah, we can we can iterate over the table,

  • but that's not going to let us do the logic that we're trying to do,

  • which is maintain a counter variable that we decrease in the table

  • when we assign from the table to something else.

  • We need to basically iterate over it twice

  • and randomly, not in the order at which it's

  • declared because then if we do it in the order it's declared,

  • it's going to have everything drawn basically mirrored in our grid, which

  • is not the look we're going for.

  • Swarmlogic says animalMap key set math.random keyset

  • according to Stack Overflow.

  • Interesting.

  • OK, let's take a look at that.

  • And so that would get us a--

  • first of all, let's look at keyset.

  • So keyset is just a table that they're using.

  • It's not a syntax.

  • I guess what we could do is we could just maintain reference

  • to both in our grid, and if we did it that way,

  • we were just pop it off of keyset and pop it off of the proper grid,

  • and just kind of maintain a relationship between the two.

  • It's a little cumbersome, but I think it'll work.

  • Local animal keys.

  • So basically, create the keyset of animals.

  • We want to do this above, actually, we don't want to do this here.

  • Let's do it here.

  • Create keyset of animals.

  • And then for k, v in pairs animalMap, do k or table.insert v, which would work.

  • No, k.

  • Sorry.

  • So this we'll iterate over our array, our map,

  • and then create a new table that just has the string, here.

  • And so then what we can do down here is say--

  • not in animal map, but in animal keys, we're going to get a random animal,

  • but it's going to be a valid animal.

  • It's going to be an animal that we actually have in our table

  • in our animalMap.

  • And then we're going to say animal keys here.

  • So this will give us a random animal.

  • So get random animal, and then the next part of our code that we want to do

  • is, essentially, say insert new card with animal face in grid.

  • Check for value being zero after decrementing animalMap,

  • then delete from keys and map if so.

  • So basically, take the reference-- the two

  • that we have as a reference in our animal

  • map, get a unique animal first, from our animal keys, up here,

  • which is what we do.

  • We created up here just by getting all the unique keys from our animalMap,

  • and just making it, basically, a one-dimensional array of those keys.

  • And then once we've gotten a random animal from that animal keys thing

  • every time we want to make a card-- this will be penguin, this will be elephant,

  • this will be whatever string.

  • We basically want to do this.

  • We will insert a new card with--

  • should we get a--

  • it'll be the same key here, actually.

  • So randomAnimal.

  • It will be randomAnimal and then after we do that, we

  • want to do if animalMap randomAnimal.

  • Oh, first all, what we want to do is say animalMap randomAnimal equals

  • animalMap map randomAnimal minus one.

  • We want to decrement whatever is in our table at that index,

  • and then we want to say if it's equal to zero,

  • then we're going to delete the animal from keys and map, which

  • will have the result of now there's not that relationship between the two.

  • We don't even need to necessarily worry about it.

  • There's probably a much cleaner way to do this, by the way.

  • We could probably just restructure our table

  • to be numeric, be able to iterate through that,

  • and have a key for the string and the number,

  • but we're already in a little bit too deep,

  • and I won't take up too much time refactoring it.

  • So this will work.

  • And then we're going to say animalMap or animalKeys randomAnimal equals nil.

  • Not numAnimals, nil.

  • And then animalMap randomAnimal equals nil.

  • And when you assign a value to nil in a table,

  • it deletes the reference in there.

  • So now we won't be able to access it.

  • And what that should do, if my instinct is correct, which hopefully it is,

  • table expected got a string main 67.

  • Table insert into animalKeys here.

  • Attempt to perform arithmetic on a nil value line 83.

  • AnimalMap or randomAnimal.

  • OK, first of all, let's print what randomAnimal is, here.

  • Let's quit Lua, and then let's run love dot.

  • OK, it's getting the strings.

  • So here, we see elephant, parrot, giraffe, pig, parrot, pig, penguin,

  • snake, parrot, and then getting an error.

  • Attempt to perform arithmetic on a nil value.

  • If it's equal to zero--

  • so we're setting it to nil, which shouldn't be a problem.

  • Let me just think for a second.

  • It looks like it's doing the logic correctly because it's

  • going through it, and it does look like it did it

  • on the third iteration of parrot, here.

  • So because we did parrot here, then we did parrot here,

  • and then the third time we did parrot was here.

  • It looks like the first animal that actually had that issue.

  • It looks like it's trying to still do this.

  • Oh, wait a second.

  • I think what we need to do here is just say if animalMap randomAnimal, then do

  • that.

  • So basically check to see if it's if it's in our table.

  • Does that work?

  • It works, but it doesn't look like it's generating unique animals properly.

  • OK, so that's interesting.

  • So if we look at our code, then.

  • So we were getting random animals, but it is definitely not--

  • and it looks like the parrot is because they

  • have the zero point in such a weird spot,

  • it's actually quite too close to the elephant there.

  • Let's see.

  • So somewhere, we screwed up here in our logic.

  • We have a bunch of the same animals being copied.

  • So let's go ahead back to our code here, and figure out where we screwed up.

  • So let's go ahead and take a look first at our animalMap.

  • AnimalKeys, rather.

  • Sorry.

  • We'll print k here, just so we know that our actual table.

  • Our key table--

  • OK, wait a second.

  • OK, I think I'm printing somewhere else as well.

  • So let's fix that.

  • Generate cards.

  • Do we have another print statement in here?

  • Or is that literally just from the animalKeys?

  • Oh, it is.

  • It's right here.

  • Let's get rid of that.

  • OK, let's try that again.

  • Make sure this is all unique.

  • Penguin, parrot, snake, hippo, rabbit, giraffe.

  • So those are all unique.

  • So our animal keys are good.

  • It could load the keys chosen when you add the keys to the animalMap.

  • You can also load the keys chosen when you add the keys to the animalMap.

  • Can you elaborate, Swarmlogic, on what you mean?

  • So the keys is working good.

  • It's this part, here, that's messed up.

  • Rather than iterate the keys to make animalKeys,

  • add them when you load them into animalMap too.

  • Oh, I see what you mean.

  • Yeah, you could do that.

  • That would be a better design.

  • AnimalKeys, animal string.

  • Sorry, rather, it would be table.insert animalKeys animal string.

  • Yeah, that's a much better design.

  • Good point.

  • So we have the keys working fine.

  • They're all unique, and it was generating

  • six of them, which is correct.

  • So it looks like it's the actual getting the animal.

  • AnimalKeys, math.random, number of animal keys.

  • Are we deleting everything from the--

  • Give me just a second to think about the logic here.

  • We, unfortunately, obfuscated it a little bit more

  • than we probably should have.

  • I think changing to nil doesn't resize the table,

  • and the nil value is still there, but I'm not sure.

  • So per what I've read, Lua should just resize the table.

  • Lua set key to nil in table.

  • OK, yeah.

  • I figured it out.

  • I'm supposed to be calling table.remove, not setting the key to nil.

  • But I'm trying to figure out--

  • OK, so Lua table remove key.

  • So normally, lua.remove-- setting the key's value

  • is the accepted way of removing item from the hash table.

  • Yeah.

  • No, that only works for integer-based keys.

  • So you can do table.remove on Lua with a numerical index,

  • and that will remove it from the table.

  • But if you try to do it with a hash, it actually won't work.

  • So the actual proper way is, indeed, to set the value to nil.

  • Did I mess this up?

  • AnimalKeys.

  • AnimalMap at randomAnimal, then animalMap randomAnimal equals--

  • because this is what we're doing here, right?

  • We did the randomAnimal, and I guess--

  • because it did get parrot the third time.

  • So it actually wasn't deleting it appropriately.

  • To close the loop, changing the key to hash two nil per Lua

  • is supposed to actually resize the table, but for numerical indices,

  • it will work with table.remove.

  • One of those things--

  • the Lua specific things.

  • It was still picking the ones with the nil value.

  • That's why we added an if and will map randomAnimal.

  • Yeah, that is correct.

  • So I think my logic here is screwed up somehow.

  • I'm trying to figure out how.

  • This should be working, based on what I understand.

  • This is fascinating.

  • AnimalMap randomAnimal, then animalMap randomAnimal

  • equals animalMap minus one, which is correct

  • because we have our animalMap, which is assigning everything to two.

  • So we should decrement that.

  • If we get this local randomAnimal, it's going

  • to get one from keys, which we saw earlier was correctly

  • generating or correctly placing all the unique keys for the animals.

  • We get a random one, here.

  • Table.insert into gCards y a new card with random animal.

  • If animalMap randomAnimal.

  • Wow, I'm perplexed as to why this isn't working.

  • I apologize that it's taking so long.

  • It should be working.

  • Thanks for popping in, David.

  • Maybe move the table.insert for the card into the if?

  • Into the if.

  • We'll, no, because if we do that, then because we're

  • at a y x, or because we're iterating only one time over all rows

  • and columns for our grid, if we do any skipping of assigning a card,

  • we'll be missing a card at that grid index, which wouldn't work.

  • That if animalMap randomAnimal should be when you select a random animal,

  • not when you decrement.

  • The thing is, it should be grabbing a proper animal

  • key because we're setting the value to zero, and then

  • and then assigning it to nil in the animalMap, right?

  • Why is this not working?

  • I am astounded.

  • This is why we code live.

  • It will never hit zero.

  • Won't it?

  • Oh, wait.

  • If animalMap random value, then animalMap randomAnimal or randomAnimal.

  • That's wrong, sorry.

  • No, I think it will hit zero, for sure.

  • Is there some weird thing about Lua that I'm missing right now?

  • Because assigning something to nil in a table

  • is supposed to negate the hash of that entry,

  • and therefore, we should be able to--

  • the next time we iterate over our-- the next time we do this,

  • basically, we should be getting an actual random thing.

  • Line 71.

  • No, we want an animal--

  • we want the string itself because this randomAnimal is what we plug in here,

  • into the card constructor, and then is the key

  • that we use for indexing into all of these other arrays.

  • Oh, wait.

  • That's what it is.

  • OK.

  • OK.

  • Local randomIndex equals math.random number animalKeys randomIndex

  • into there because the keys, it's not a--

  • well, actually, what I can do is table.remove animalKeys randomIndex.

  • It's not a hash map, the animalKeys table.

  • 61.

  • Table.insert insert into animalKeys.

  • Oh, right, local animalKeys equals that.

  • There we go.

  • That looks a little bit better.

  • So I was treating animalKeys as a hash map.

  • It wasn't a hash map.

  • We were using it as an array, and so that's

  • why I created this reference to the randomIndex that we were using,

  • and then I was able to plug that into getting

  • the random animal from animalKeys.

  • And then once we did all that, we can have all the same logic

  • that we had before, but now we have, down here,

  • instead of setting animalKeys at index randomAnimal

  • to nil, which wouldn't have done anything because we

  • weren't using that as a hash map.

  • We'll use it as an array.

  • We can instead call table.remove on the numerical index

  • that we created before on animalKeys.

  • Now that will work.

  • Live coding.

  • That's why we're here.

  • So thanks for your patience.

  • We figured it out on the fly, live.

  • Can't wait for the next screw up.

  • It's coming.

  • I can feel it OK.

  • So fantastic.

  • So let's take a second to just soak that one in and appreciate why we're here.

  • It won't be the first time that it happens, bhavik,

  • I can promise you that.

  • It happened, to an extent, in Snake, but I, for some reason,

  • could not see what was going on.

  • But now, it's quite obvious.

  • I could feel the stress right on my screen, says Shaina.

  • Well, this is what programming is.

  • Programming is having a hard time tracking down things, sometimes.

  • Next time, can you try to use the debugger?

  • The debugger, in this case, would have probably been more print statements.

  • So yeah, I probably could have used some more print statements.

  • I don't have an actual debugger, but that

  • might be worth trying out because there's IDEs for Love and Lua.

  • ZeroBrane Studio is an example of an IDE.

  • I'll pitch it.

  • It has a debugger.

  • I've seen it in class.

  • ZeroBrane Studio.

  • So this is pretty cool, if you're interested in it.

  • This doesn't do a great job of showing what it looks like.

  • Let's go back to their main page, maybe.

  • So it looks like this, I think.

  • So it's kind of nice.

  • It has a built in debugger.

  • Oh, the VS Code debugger.

  • So the VS Code debugger, I don't think has the Lua support.

  • For every language that you want to debug,

  • you need to have the actual debugging tools for it.

  • You can debug C# and stuff because you can download OmniSharp and whatnot,

  • but I don't think Lua and Love2D have debugging features baked into VS Code

  • at all.

  • You have to download a plugin, and I honestly

  • don't know if there's a plugin for Love2D debugging.

  • I'd have to look into that a little bit.

  • But yeah, a debugger probably would have caught that earlier.

  • Part of it is having to know where to look to debug your code, but we did it.

  • And they say the best debugger is the one between your eyes.

  • I don't know if that's true for me, but that's

  • an adage that you'll hear occasionally.

  • It doesn't have Lua support.

  • It has support for syntax highlighting, I believe, but not for debugging.

  • For anything that you want to debug in VS Code or for any IDE,

  • typically, you need to install the right debugging binary.

  • All right, so we have everything drawing.

  • Let's do another thing.

  • So in my render function, I'm going to call--

  • right now, we have the animals drawing, but we need

  • to kind of have card backs on them.

  • So let's call love.graphics.rectangle, and let's say self.x, self.y.

  • Let's say each card is going to have--

  • I don't know the size offhand.

  • Maybe 75 by 75?

  • First of all, we need a form of rendering.

  • So fill would be one of them.

  • And then for the last argument, we need to have--

  • whether or not we want rounded corners.

  • Let's say I want to round the corners.

  • I forget what the number itself actually represents.

  • I think circle edges.

  • Let's say three for now.

  • It's going to be kind of square-ish looking, I think, but that'll be fine.

  • And then let's also say love.graphics.setColor to a gray-ish.

  • So let's say 0.20, 0.21.

  • And then after this, love.graphics.setColor.

  • Oh, wait, no.

  • Don't forget to do it here.

  • Love.graphics.setColor, 1, 1, 1, 1.

  • All right, so what this will do is it will set a grayish color.

  • Remember, we used this last week, and then we'll draw a rectangle.

  • And then after that, we'll set our color back to white

  • so that we can draw sprites at their regular color

  • because if we have a color set in the state that's not white,

  • it'll tint any image that we draw.

  • So I'm going to bring it back to full white,

  • and then I'm going to draw the texture, and then I'm going to run this.

  • And we have kind of a background that's working.

  • It's not quite large enough, though, and everything isn't really

  • space out quite big enough.

  • So of fix that a little bit, I think.

  • So let's go back into our main, and we can maybe

  • work on centering this a little bit.

  • So let's say we're going to have a local margin,

  • and we're going to say window width minus,

  • and then however many pixels all of our cards

  • are going to take on the horizontal axis Irenenaya says Lua seems particularly

  • prone to confusions with this everything is a table way of doing things.

  • In other languages, the fact that use different data structures

  • makes it easier to keep track of what is what, I think.

  • Yeah, that's true.

  • I appreciate Python, I think, for this reason

  • for having lists versus dictionaries.

  • That would be a nice thing, I think.

  • Lua is very simple and straightforward because of its everything

  • is a table paradigm, but it does lead do, occasionally, some tough debugging.

  • Tmarg says it's a big part of why it's faster than other scripting

  • languages as well, though.

  • And that's correct because hash maps are faster than linked lists just

  • by virtue of how they're programmed.

  • So you're going to have to take the good with the bad sometimes.

  • OK, so I want to margin.

  • I want to say margin x, and I'm going to say it's

  • going to be the window width minus--

  • and I need to figure out how much space all the cards are

  • going to take on the x-axis.

  • So we can say each one-- let's say each one's going to be about 100.

  • Let's say each one's going to be 150 by 200.

  • And this is not where we want to code this.

  • So we're going say CARD_HEIGHT equals 200 and CARD_WIDTH equals 150,

  • and then I'm going to go back into main.

  • I'm going to say CARD_HEIGHT.

  • Sorry, CARD_WIDTH times-- let me make sure I get my math right for this.

  • We're going to take the window width, and we're

  • going to subtract the size of the total width of the cards

  • as they're rendered onto the screen, including

  • the spacing that we put between them.

  • So CARD_WIDTH, which is nearly 150 times CARD_WIDE,

  • and then that's going to be the way that we're drawing that.

  • We should be spacing them out, right?

  • Well, we're going to have to fix this a little bit.

  • This is also going to be divided by two when we actually draw them

  • because we're going to draw it at half that window margin

  • because you can almost imagine putting all of the--

  • or this side, rather-- all of the cards on the left side,

  • and then taking whatever white space at the end, considering that your margin.

  • Split that in half, and put that on either side of your cards,

  • and that will let you draw them around the middle of the screen.

  • So that's kind of what we're going to do.

  • Margin x equals WINDOW_WIDTH minus CARD_WIDTH times CARDS_WIDE,

  • which is going to be 150 times four.

  • Each one of the cards is going to have a little bit of spacing in between them.

  • So let's say 30 pixels of padding.

  • So in my constants, I'm going to say CARD_PADDING equals 30.

  • I'm going to go back to main, and I'm going

  • to say plus CARD_PADDING times CARDS_WIDE minus one

  • because we're not actually going to need to add that padding to the first card

  • because it's going to be drawn by its left coordinate.

  • So that should be all of the margin on the x-axis.

  • And then the margin y, and I'll get rid of that

  • so we can see a little better, will be height

  • minus CARD_HEIGHT times CARDS_TALL plus the CARD_PADDING times CARDS_TALL

  • minus one.

  • Should be correct.

  • I'm hoping that it is.

  • Hoping that my math is correct there.

  • Basically, just take the number of cards wide by their width,

  • and add the amount of padding, which is 30 pixels times the number of cards

  • minus one, which would be all of the padding between them.

  • Is that correct?

  • One, two, three.

  • Yes, that is correct.

  • Should be correct.

  • And then the exact same thing for the height, basically, and we'll

  • use the same amount of padding on the x and the y axes.

  • And then let's go down into--

  • so we have our margins declared.

  • So that's going to be good.

  • We can use that, then, to start.

  • By dividing them by two, we can start where they draw.

  • So we can say marginX divided by two.

  • That will be wherever the left card is.

  • And then that'll be plus--

  • so this is where we have to take x into consideration.

  • I'm not sure why OBS decided to cause some problems.

  • Let me just make sure everything is looking OK.

  • It looks like it's working OK on Twitch.

  • So I'm going to continue.

  • Basically, what we were doing was getting the margin setup

  • for drawing our grid in a sort of symmetrical view.

  • So what I'm going to do is say--

  • oh, chat is missing for some reason.

  • Maybe because when it refreshed, it deleted the chat.

  • Sorry about that.

  • Thanks everybody.

  • I'm not sure why it disconnected.

  • We have it piping through a script, and it looked like the script failed,

  • and then OBS crashed after that.

  • So hopefully we'll splice it together with them.

  • Oh, you're right.

  • You're right.

  • OK, what is going on?

  • Hold on.

  • It's using a different widget.

  • It didn't save the settings correctly.

  • I had to restart OBS, and that's what happened.

  • Let me see if I can fix that.

  • OK, properties, theme clean.

  • No, boxed.

  • That's annoying.

  • For some reason, it didn't save the settings for the chat.

  • It looks like a completely different widget.

  • It's not clean against the white background, but that's all right.

  • Anyway, we'll go back to the view.

  • It's a little bit weird, but it's fine.

  • Bhavik says let's focus on coding.

  • Yes, let's focus on coding.

  • FrameOfRef says what are you building?

  • We're building a game called Concentration, which looks like this.

  • So sort of like a memory card game, where

  • you have to flip over pairs of cards.

  • So we just finished building the bit of code that actually compiles the grid

  • and fills it with alike pairs.

  • So the next step that we're doing is going to draw everything to the center,

  • and then after that, we should be able to just click on pairs,

  • reveal them, and then keep them up if they're alike.

  • So thanks for joining us today.

  • And apologies, again, for the stream behavior.

  • Not sure what's going on, but hopefully it doesn't happen again.

  • OK, so marginX divided by two.

  • So this is where we calculated our margin as being,

  • basically, the width of the window minus the cards times their width,

  • plus some padding times the number of cards minus one.

  • And so what we're going to do is divide our margin by two.

  • So get the left side of the margins padding,

  • and then we're going to add to that whatever our card is, basically,

  • times CARD_WIDTH plus CARD_PADDING times--

  • I think this is correct.

  • This GUI code is always a little bit cumbersome to think about.

  • CARD_WIDTH plus CARD_PADDING times the number

  • of cards, which would be x minus one.

  • Perfect.

  • And then marginY, it'll be the same thing as this, just slightly different.

  • So marginY.

  • And then y CARD_HEIGHT plus CARD_PADDING times y minus one.

  • OK, that should work.

  • Let's see if it actually did.

  • 82.

  • I might not have the right number of parentheses at the end of that.

  • Starting to look a little bit like Lisp.

  • Ambiguous syntax, function called x, new statement on line 82.

  • Do they have a function call type syntax?

  • What's going on here?

  • Oh, I forget a comma.

  • That's very important.

  • All right, that should work.

  • And now I have the wrong number of parentheses somewhere.

  • Oh, one last one.

  • Forgot the beginning one.

  • OK, cool.

  • So now we have a--

  • it's more spaced out, but it's not quite proper.

  • It looks like it's actually exponentially increasing

  • the padding between the tiles.

  • So we got that part wrong.

  • So we can fix that fairly easily, I think.

  • I think just this should work, and maybe that.

  • Let's try that out.

  • Yeah, that works pretty well.

  • We're not getting that marginY amount working super well, which is OK.

  • Unless I screwed this up.

  • CARDS_WIDE minus one.

  • Maybe that's why.

  • OK, so we have a, more or less, symmetrical looking grid.

  • It looks like the marginY at the top isn't quite-- oh wait,

  • you know what it is?

  • Oh wait, no.

  • It's not that.

  • I was going to say we can't see the last one below, but that's not the case.

  • The card backing isn't quite drawing well enough, though.

  • So if I remember correctly, in card, I actually--

  • well, rather, we declared the CARD_WIDTH and CARD_HEIGHT to be different values.

  • So if I say CARD_WIDTH and then CARD_HEIGHT here for our rectangle,

  • notice that now, we're actually getting cards that are more or less good.

  • They're getting better, but they're not centered super well.

  • Gotta go.

  • Ciao, Colton.

  • Have a nice coding session.

  • Bye.

  • Thanks Shayaana Thanks for coming in.

  • Appreciate it.

  • Let's go ahead and maybe do a better job of centering the animals a little bit.

  • They're a little bit off.

  • And we won't spend too much time, I think,

  • getting the orientation of everything perfectly

  • just because we spent a little bit of time

  • getting the logic working for the grid, and also the stream going down.

  • So what we're going to do is I'm going to draw the animals just

  • by a little bit of an offset.

  • So in here, I'm going to bring this on the next line.

  • I'm going to say self.x plus, and let's just say--

  • I'm not sure if all of the animals are of different size or not,

  • but I'm just going to say 64.

  • Nope, that's not quite right.

  • It looks like the x is not right.

  • So we'll say the x is 32.

  • It's more or less OK.

  • These sprites are a little bit iffy.

  • It looks like they're kind of variable sized.

  • If you were working with sprites that were uniform, like say 64 by 64,

  • and not angled in different ways, I think it would work better,

  • but this will work for now.

  • This will be fine.

  • So we'll say X_OFFSET and Y_OFFSET, and we'll go into constants.

  • We'll say X_OFFSET is 32, Y_OFFSET is 64, which is what we had before.

  • Remember, trying to keep everything kind of modular.

  • And then we now have our grid, but we have no interaction.

  • So the next big piece, and hopefully, a piece

  • that shouldn't take us too much work, is the actual interaction

  • of the game board.

  • So first thing that we should do is probably

  • say OK, every card should have a visible flag.

  • And if the card is visible, self.visible, then that's

  • when we actually draw the animal part.

  • So now, because we've set everything to not be visible,

  • we don't get any animals actually drawing.

  • The part where we actually need to decide if we have clicked on a card

  • and want to make it visible, needs to be, probably,

  • in our update function of our card.

  • So we can call this update function iteratively for every card in our grid

  • within our main function.

  • So we can do something like in our main.lua,

  • for y equals one CARDS_TALL do, for x equals one CARDS_WIDE do,

  • and then we can do gCards y x update delta time.

  • And remember, delta time was that value that

  • is the amount of time that's passed since the last frame.

  • It's not super important in here because we're not really

  • going to be doing a lot of time based stuff.

  • This is a game that's kind of based on just when you actually click on stuff.

  • It's more almost like callback based.

  • So what we're going to do is just kind of use checking for key input or mouse

  • input, basically.

  • So let's do that.

  • So the next stage will be in our--

  • basically, what I want it to be able to do is if love.mouse is down one,

  • then if not self.visible, then self.visible equals true.

  • So if it's not the case, or if it is the case

  • that we're pressing down on button one on our mouse,

  • then we should be able to toggle that visible flag if it's off.

  • If it's set to false.

  • So if I run this, and then I click--

  • well, I clicked on all of them, and it set all of them to false.

  • So that's not what we want to do.

  • Oh, right, because it's not checking for position.

  • So yeah, since we're just testing for a press period,

  • it's going to update all of them.

  • And so what we need to actually do is to kind of say if we're pressing it,

  • and we're pressing it within the bounds of the rectangle,

  • then we can set it to visible.

  • So I can say local mouseX mouseY equals love.mouse.getPosition.

  • That should give us the x and the y.

  • Self.x plus CARD_WIDTH divided by two minus SPRITE_WIDTH divided by two

  • to center the sprite on the card.

  • Yes, correct.

  • It's just that these sprites are a little weird looking.

  • But yeah, I think we have to also specify the size for all of them.

  • I think some of them are different sizes than other ones.

  • I didn't look too closely at all of them.

  • This one is, for example, 396 by 314.

  • This one is 354 by 342.

  • So they're all different sizes, but you can call a texture get width.

  • So we could do it that way, and it'll know it at runtime,

  • but I'm not going to spend too much more time on, I think, the prettiness of it,

  • just in the interest of trying to get more

  • of the functionality of the game going.

  • Yeah, if you get the width, you should be able to just.

  • Yeah, we could definitely do that using the-- every image

  • has a getWidth and getHeight function.

  • If you're curious, you can go to Love2D image object,

  • and then this image object, here, which is

  • what we've been storing in our gTextures table,

  • has all of these different functions.

  • So you can see it has it getHeight function, for example,

  • and also a getWidth function, and you could call that at runtime,

  • if you're using different textures, to determine what size they are,

  • rather than having to necessarily hard code all of them.

  • But it's generally the case that you'll use sprites

  • that are more of a uniform size.

  • We just happen to kind of find this arbitrary

  • collection of stuff kind of quickly, and they were all different,

  • but we could make it do it that way.

  • Yeah, definitely.

  • Good point, though, Swarmlogic.

  • OK, so if mouseX greater than or equal to self.x,

  • and mouseY is greater than or equal to self.y,

  • and mouseX is less than or equal to self.x plus--

  • what is it?

  • What's the width?

  • CARD_WIDTH.

  • And mouseY is less than equal to self.y plus CARD_HEIGHT, then--

  • let me make sure that this aligns with the if statement.

  • Then we can do this.

  • But rather, it's better practice if we, instead,

  • probably do the opposite of that by saying basically, have this if

  • statement inside the checking for the mouse

  • because we don't want to check all those if statements every single time we

  • check the mouse.

  • Or every time we don't we don't check the mouse because then it's

  • kind of like wasted cycles.

  • It's not a huge thing to worry about, but you probably

  • want to be conscious of it.

  • So basically, if we're pressing the mouse down,

  • then check to see if our mouseX is greater than or equal to our self.x.

  • MouseY is greater or equal to self.y.

  • If neither of those are true, and it's also the case it's not less than x

  • plus the width and the y plus the height,

  • then we're outside the bounds of the x somewhere, and we shouldn't toggle it.

  • So if I go back and click.

  • Now, I can actually click on each individual card

  • and reveal it in the proper way.

  • So it's working properly in that context.

  • Now, unfortunately, we don't have the actual game functionality,

  • whereby we check to see if we're getting the same two cards revealed.

  • So what we probably want to do is say self--

  • we basically want to keep track of which cards we should permanently leave up,

  • and which cards are temporarily up because we're

  • checking to see a new pair of cards.

  • So we can have a variable that maybe is called revealed equals false.

  • This revealed variable is maybe the permanent--

  • we'll just call it permanently revealed.

  • How's that?

  • Just because visible and revealed are kind of synonymous.

  • Permanently revealed means that card has been found in a pair

  • and should stay visible.

  • And then this just means is visible temporarily while looking for a pair.

  • So what we should do is, basically, in our main,

  • we want to check to see if we're looking at two--

  • I guess we should probably figure that out.

  • So card will update and check to see the mouse input.

  • I guess what we could do is just in our main update, we can say update.

  • We're going to update every card, and then for y equals one CARDS_TALL do,

  • for x equals one CARDS_WIDE do.

  • So this allows us to perform any clicking on cards to find pairs.

  • And then this loop will be check all cards to see if two visible are true.

  • So self.visible is true.

  • So if it's the case, we'll say local numVisible equals--

  • numVisible is equal to zero, and we'll say local cardsFlipped is

  • equal to just an empty table.

  • And we'll say if gCards at yx.visible then.

  • So basically, if we've temporarily flipped this card,

  • we want to add it to a card table to check.

  • So we want to say cardsFlipped, rather table.insert

  • into cardsFlipped, and then x and y.

  • So all we're going to do is basically add a new entry

  • to our cardsFlipped that just keeps track of the x and y.

  • And what we're going to do is we're going to-- if it's equal to two, right.

  • So what we can do is say refresh all--

  • at the end of this, we're going to say refresh all cards that

  • are visible back to invisible.

  • So for y equals one cards--

  • or rather, what we can do is say for k, v in pairs--

  • or wait, actually, that's not true.

  • That's not what I'm going to do.

  • If we only have one.

  • If the length of cardsFlipped is equal to two,

  • that's when we want to basically do a check to see if the cards match.

  • And then what we can do is we can say for k, v in pairs of cardsFlipped do.

  • What we should do is say--

  • it might be a little bit easier, I think, if we did v.x and v.y.

  • So what we can do is we can say x equals x and y equals y,

  • and what that will allow us to do is say v.x, here, rather than v bracket

  • two or v bracket one for x and y.

  • So we'll leave that for a second.

  • So this is where we're going to check.

  • If we've flipped two cards, check if they're equivalent, which

  • is as redundant comment with this.

  • Examine the xy pairs for similar face value.

  • That's what we want to do.

  • We basically want to say--

  • or rather, here's what we do.

  • We say local firstCard, secondCard equals cardsFlipped one,

  • cardsFlipped two.

  • So that will give us both the first and the second card.

  • And so then what we can do is we can say if firstCard.face face is

  • equal to secondCard.face, then we can say firstCard-- rather,

  • gCards firstCard.y, firstCard.x dot permanently visible is true.

  • So now, that'll set that to permanently revealed, rather.

  • Permanently revealed.

  • So if the first card and the second card are the same,

  • we should do that for both the first card and the second card.

  • secondCard.x dot permanently revealed is equal to true.

  • And we can then say gCards firstCard.y firstCard.x is dot visible is true,

  • and gCards firstCard--

  • secondCard, rather, dot y secondCard.x dot visible is equal to true.

  • False.

  • These should be false.

  • Sorry, not true.

  • False.

  • So revert the pair of cards to not visible either way,

  • and then keep cards permanently revealed if they match faces.

  • So we have that bit of logic there, and then add it to a card table to check.

  • So this was iterating over all the cards.

  • If it's visible, insert it.

  • I think that's correct.

  • The only thing that I would want us to be cognizant of is if not.self.visible

  • and not self.permanentlyRevealed because if it's permanently revealed,

  • we don't want to set it to visible because visible is going to be--

  • we're going to check, specifically, for two cards being visible,

  • and it's at that point do that we want to take those two cards,

  • perform the check, add them to the table,

  • set them to permanently revealed, if they are permanently revealed,

  • and then so forth.

  • And then one other thing we do need to do is say basically, after all this,

  • if the number of cards flipped is equal to two, we're going to say for y

  • is 1 until a card is tall.

  • We're going to do another loop over all of our cards.

  • For x equals one CARDS_WIDE do.

  • And then, basically, have a flag that says local all revealed is false.

  • Or rather, we want this to be true.

  • And then if there are any cards that are not permanently revealed,

  • we want to set that to false.

  • And then we can break out of that, actually.

  • Well, it's nested loop.

  • We can actually break out of it, I don't think.

  • It'll just go back to the other loop.

  • So what we can say is if all revealed, then we'll just quit for now, just

  • so we can test it.

  • But then we can we can add a victory screen otherwise.

  • So we'll say if gCards.

  • yx dot permanentlyRevealed, then--

  • sorry, if not gCards, then we'll say all revealed is false.

  • If any of the cards do not have a permanently revealed flag on them,

  • it will set that to false, and then this will not execute.

  • So we won't quit if it's the case that any of them

  • haven't been permanently revealed yet.

  • I think that's all of the main pieces.

  • I could be missing something.

  • We'll find out in just a second.

  • Oh, cardFlipped main 60.

  • cardsFlipped, not cardFlipped.

  • OK, so now we have our blank grid, one again.

  • We'll see if this works.

  • So I have a penguin and a--

  • OK, so that didn't work.

  • So we have a slight bug where if you don't match the card,

  • it will not show you the second card.

  • So you don't actually know whether you matched or not.

  • So we're going to have to kind of delay that a little bit.

  • So we're going to have to delay the second card being visible,

  • and also delaying whether we can input, and then

  • have it go back to the invisible state.

  • So elephant, I don't know.

  • Not sure what this is.

  • Bunny, elephant, and it looks like there's

  • another bug where I can't actually click on any of these

  • because it's not set the--

  • OK, so there's some erroneous logic here.

  • We're on the way, but it looks like one of the flags

  • is not being set appropriately.

  • We need to fix that.

  • And actually, I wonder, because I think we're able to currently click

  • on the same face.

  • Are we able to click on the same face?

  • No.

  • Because I think in card.lua, we say if not visible.

  • Yeah.

  • If not self.visible, and not self.permanentlyRevealed,

  • then self.visible is true.

  • OK.

  • So we do our update here.

  • So we go through all of our clicking of the cards to find pairs,

  • and we call card.card colon update, in which case we get the position.

  • We check to see whether we're clicking down.

  • If we're within a card's boundary, we check to see whether it's not visible

  • and not permanently revealed.

  • If it's the case that both of those are the case, we set self.visible to true.

  • Back to main.

  • After that, we check to see--

  • oh, are we not incrementing number of visible?

  • Did I not do that?

  • I might have not done that.

  • I think I might have forgotten that step, actually.

  • Not that that really matters because it looks like cardsFlipped

  • is equal to two.

  • That'll kind of be the same thing as visible,

  • in this case, because visible is synonymous with flipped,

  • and we're using permanently revealed for the other notion, which

  • was having the card be flipped for the remainder of the game.

  • We will need a timer.

  • Basically, if readyToSelect, then--

  • and this readyToSelect is going to be an update function that just processes--

  • it takes into consideration some timed input.

  • So readyToSelect.

  • We'll just call it a second.

  • So we'll say readyToSelect is equal to false.

  • And up here, we're going to say selectTimer

  • is selectTimer plus delta time.

  • If selectTimer is greater than 1, then selectTimer readyToSelect is true.

  • Actually, we can make this an if statement at the end of this.

  • S we'll say, else--

  • again, this will pause the cards so that when we click two cards,

  • we can actually see which cards we chose because right now, we're not actually

  • sure what cards we picked.

  • So if select timer is greater than one, then readyToSelect is true,

  • and then selectTimer is zero.

  • OK, that's great.

  • That should work.

  • Let's try it out.

  • SelectTimer main 92.

  • I didn't actually declare it.

  • So this is where we're going to do that.

  • So selectTimer zero.

  • We're going to need to keep track of that

  • as a global variable in our main.lua, just so that we know.

  • Actually, we don't have to keep global.

  • We can declare it up here.

  • That way, at least this won't clobber other modules.

  • It'll just be visible within the scope of main.

  • But if I click these two, looks like it's still not working.

  • Let me see.

  • Where did I screw that one up?

  • If readyToSelect.

  • I set readyToSelect to false here, right?

  • Yeah, I did.

  • Oh, because I convert the pairs of cards here.

  • OK, yeah.

  • That's why.

  • Because I actually go back into the--

  • OK, then that means we probably need to keep the cards--

  • we need to keep them--

  • OK, here's what we can do.

  • Take this out of here.

  • Bring it down into here.

  • Try that.

  • Oh, but firstCard isn't--

  • OK, I see.

  • So we need a reference, then, to firstCard

  • at the top level of our program.

  • So we have reference to it there.

  • Let's see.

  • Not at the top level our program, just in our update.

  • So local firstCard, secondCard.

  • Where is firstCard?

  • FirstCard and secondCard are--

  • oh, wait, cardsFlipped one and two.

  • Are we getting rid of that table every time as well?

  • Oh, OK.

  • That's why.

  • Shifting everything around a little bit here.

  • So local cardsFlipped just to make this work.

  • Local firstCard.

  • It's still not working a little bit here.

  • The issue that you run into when having everything go from sort of discrete

  • to timer-based, you have to keep references to everything a little bit

  • higher up.

  • So that's why we're taking some of these variables out,

  • and putting them up here, instead of lower

  • where they were because these are going out of scope because we're

  • in another else statement, basically.

  • And so, remember, each if statement has its own scope.

  • So that's why we're doing that.

  • What was the exact area we're getting again?

  • Click on that.

  • Attempt to index local firstCard and nil value on 99.

  • So readyToSelect.

  • Oh, is readyToSelect-- did we not do that as well?

  • Local readyToSelect equals true.

  • 67, cardsFlipped.

  • Let's see.

  • Maybe add a match to a variable to the card class.

  • Let the card display itself permanently if matched,

  • rather than maintain another data structure.

  • That's what the permanently revealed flag is for on the card.

  • So that's effectively what we're doing.

  • OK, let's see.

  • [INAUDIBLE] have local cardsFlipped and nil value on 67.

  • So cardsFlipped.

  • Oh, is that why?

  • OK, getting closer.

  • We had them up for a second.

  • Index local firstCard and nil value main 100.

  • OK, let's take a look at that.

  • FirstCard x and y set to false.

  • OK.

  • So if readyToSelect, then basically, we don't want to do all of this stuff.

  • So we did that.

  • ReadyToSelect is true.

  • If it is two, and the cards aren't already set to permanently revealed,

  • have the cards maintain a visibility timer.

  • Yeah, we could do that too.

  • We could do that too.

  • The only issue with that is then we'd have cards that we--

  • we'd have cards that we'd click on, and we'd be able to click on other cards,

  • and if they were still setting themselves to visible, then

  • if we have our same logic where we're looking for visible cards, we'd have,

  • potentially, three or four cards that are invisible,

  • and it would screw up the matching a little bit.

  • So then you need to introduce additional visibility logic and the logic

  • for detecting what pairs we selected temporarily.

  • It would get a little bit more complicated,

  • but I could see that working with a little bit of work.

  • I don't know if Love has pub sub.

  • So that may not work.

  • It doesn't have pub sub directly, but it has

  • kind of the same idea with something called event library, which

  • is part of the knife class--

  • the knife library, sorry The event class,

  • which is part of the knife library, which

  • has the same pub sub model as the JavaScript library,

  • but it's a little bit different.

  • Same kind of semantics, though.

  • You do event.on on some string, and then an anonymous function,

  • and then you would have event.dispatch, which

  • is the same thing as event.publish part of the pub sub,

  • and the sub part is the event.on with the string and the anonymous function.

  • We'll potentially explore a little bit of that, I think, with a future game.

  • We'll take a look at that, potentially, because that

  • is much better for architecting larger programs, larger games.

  • But for something like this, it's not quite as essential.

  • Although, it would definitely work, I think.

  • I have to figure out why these are set to nil.

  • So we have the things working.

  • OK, so then on line 100, [INAUDIBLE] firstCard and nil value.

  • So the first card is being sent to nil, which I'm not sure

  • how that's possible because they're just being assigned here,

  • and then the block where they're actually testing--

  • oh, that's why.

  • Because they're getting instantiated every time this starts, of course.

  • We have to declare those up here.

  • That didn't work.

  • It was actually not.

  • The timer's working, but now we're not getting the same--

  • some of these aren't even working at all for some reason.

  • So cardsFlipped.

  • What we need to do is we need to instantiate cardsFlipped to a table,

  • but not anything else.

  • So there we go.

  • So we do that.

  • They're all getting set permanently, and then they're not flipping back.

  • You declare them at the top, but don't give them values.

  • Yeah.

  • I also don't think we need this mousePressed function anymore.

  • The mousePressed would have been an OK place to put, I guess,

  • the logic for looking at the cards individually.

  • Currently, the cards are--

  • it's not the permanent visibility permanently revealed.

  • Right, because we chose two different cards.

  • Those are two different cards, completely different faces.

  • Now when I try and click on them again, I can't, but I can click on other ones.

  • Oh, I got that one, but it didn't keep it.

  • If self.visible or self.permanentlyRevealed then.

  • OK, so its setting permanentlyRevealed to any of the ones that we click on,

  • which is not the correct logic.

  • So that's why it was doing that.

  • So if I go back up to where it sets permanentlyRevealed on the card.

  • So if I go to main firstCard.Y, firstCard.X. PermanentlyRevealed

  • is true.

  • That's if the first card is equal to the second one dot face.

  • But that's not true, right?

  • Yeah, because this is saying if they're equal--

  • if they have the same face, then set those cards

  • to permanentlyRevealed as true if they have the same face,

  • but they don't, necessarily.

  • So if I did like print firstCard.face secondCard.face.

  • If I run this from the command line and then I click on these two.

  • Oh, they're both nil.

  • Interesting.

  • OK, that explains it.

  • So cardsFlipped.

  • Right, because cardsFlipped was the--

  • it's just the x and the y.

  • It's not the face.

  • That's not what we want.

  • So local card one card two equals gCards firstCard.y,

  • secondCard.y gCards secondCard.y, secondCard.x.

  • This should be dot x, not dot y.

  • Now, we can do if card one dot face is equal to card two dot face.

  • Now, I can do it.

  • Hey, it remembered it.

  • It was the monkey.

  • So it remembered the monkeys.

  • Were these pandas?

  • Those are not the same.

  • That is a bug.

  • Why did it do that?

  • It remembered the pigs and the hippos.

  • Wait, why did that get set to permanently revealed?

  • That's weird.

  • That's peculiar.

  • It's close.

  • FirstCard.y dot x, secondCard.y.

  • Where are you seeing that?

  • I see y x for both of them.

  • Yeah, I'm seeing both of them as firstCard.y.

  • 72.

  • Oh, yes.

  • Yes, thank you.

  • All right, let's try that again.

  • OK, that worked.

  • I have terrible memory, by the way.

  • There we go.

  • It was that one.

  • Elephants.

  • And then boom, we did it.

  • Thanks for all the help you guys have given me.

  • I've needed it a lot.

  • Whipstreak23 says I'm back.

  • Sorry I was away for a while.

  • What we can do, I think, to top it off is in our--

  • we do have escape, right, as our quit?

  • Bella_kirs says great.

  • Thank you.

  • Thanks, everybody, for pointing out that subtle typo.

  • Those kinds of things kill you, I swear.

  • Those are the kinds of things that will just ruin you.

  • Just so that we kind of finish it on a somewhat climactic note, when

  • we reveal a pair of cards, let's do--

  • we're going to say else if game--

  • rather, what we're going to do is we're going to say if game over, then return.

  • So we're just going to completely bypass the game over.

  • Or bypass input if we are on a game over.

  • We're going to set local gameOver is equal to false.

  • And then in our draw function, we're going

  • to say love.graphics.print you win.

  • Printf, rather.

  • At zero VIRTUAL_HEIGHT divided by two minus 32, or rather, 64.

  • Or not VIRTUAL_HEIGHT, WINDOW_HEIGHT.

  • Then WINDOW_WIDTH, and then center.

  • And then the last thing I need to do is in my dependencies,

  • I'm going to create something called gFonts.

  • I'm going to create a huge font.

  • Call it love.graphics.newFont.

  • 128 pixels.

  • Going to go back into main.lua.

  • I'm going to say love.graphics.set--

  • actually, I missed the very top.

  • Love.graphics.setFont gFonts huge, and then now let's try one more time.

  • That's not correct because I need to say if game over, then.

  • And then, now, hopefully get through this without too much time.

  • I'll do that, do that.

  • No, those two.

  • Will do that.

  • Nope.

  • Do that.

  • We'll do that and then we'll do that.

  • Damn it.

  • OK, we're quitting here somewhere.

  • Where are we quitting here?

  • If gameOver-- we're not setting gameOver to true, are we?

  • OK, if it's the case, if all revealed, then gameOver is equal to true.

  • There we go.

  • All right, one last time.

  • One last time.

  • Do that.

  • OK, no luck so far.

  • I'm getting a lot of different ones.

  • There we go.

  • OK.

  • OK, nope.

  • OK, and then boom, and then we win.

  • All right, it was a bit of a rough period here,

  • getting from start to finish.

  • We had a couple of stumbling blocks, but hopefully, that

  • is an interesting thing, and an opportunity for all of us

  • to come together, and sort of debug these games together,

  • and these programs together, and get a little bit of programming experience

  • collaboratively, which I think is fun.

  • Apologies for the stream breaking out, which that was weird.

  • I'm not sure why that was.

  • And apologies that we took a little bit too much time on the actual grid part.

  • Hopefully, we won't have that issue again.

  • I think I might do a little bit of prevetting for some of these,

  • just to make sure we don't have too many weird hang ups,

  • but you can't avoid like a lot of subtle bugs sometimes, even live.

  • So that's just kind of part of the charm of it, right?

  • It's a good time.

  • So I'll stick around for just a couple of minutes.

  • And this game will go up on GitHub if I can figure out

  • the weirdness of the URL.

  • It should be Coltonogden/concentration50,

  • when I can get it working.

  • Let's see if it's working now, actually.

  • GitHub.com.

  • This is the Snake50 one.

  • We'll do Concentration50.

  • OK, so this is not a 404 anymore.

  • So if I go into here, git status, git add, get comit -am "finished game."

  • There's a lot more we could do to it.

  • Git remote at origin.

  • Git pushed upstream origin master.

  • OK, there we go.

  • Now it's working.

  • OK, so if anybody wants to download the code and mess around with it,

  • it's now live.

  • So at GitHub.com/coltonoscopy/concentration50.

  • You can download the entire project that we'd

  • messed around with today, including the graphics, which

  • were from OpenGameArt.org.

  • You can add new features to it.

  • We don't have a score.

  • We don't have a timer, which would have been nice to implement

  • like a timer counting down from 60.

  • But we have, in three hours, the whole basic gist of the game, and it works.

  • So thanks again to Asli--

  • what was the last name again?

  • It was like Tumerkan or something.

  • I believe it was Tumerkan right?

  • Asli Tumerkan for the suggestion, which we implemented today.

  • Again, next week on Friday, we'll be doing 3D Pong in Unity.

  • So if you want an introduction to 3D game development using Unity,

  • we'll be teaching that, and I have already prevetted that one.

  • So I'm confident we won't run into any long bugs like we did today,

  • but you never know.

  • Weird things happen.

  • GitHub crashes, stream crashes.

  • It's whatever.

  • Tumerkan, that's right.

  • Bella_kirs, really enjoyed the stream.

  • Thank you, Colton.

  • Thanks for coming, Bella.

  • Appreciate it.

  • Glad you tuned in.

  • Asli says Tumerkan.

  • Bhavik says it's cool.

  • Thanks for taking my suggestion.

  • Yeah, no problem.

  • It was a good time.

  • I enjoy implementing things that I haven't

  • had a chance to implement myself, and I enjoy coming here, and figuring it out

  • live.

  • I think it's a nice high stakes environment, seeing if we can indeed

  • whip something up in three hours.

  • So it worked out.

  • It was a good time.

  • We all had fun.

  • I think I might have missed Whipstreaks comment on [INAUDIBLE]

  • because I was away for a while.

  • The code is all in GitHub now, though.

  • So definitely follow along.

  • If interested, if you'd like to, an exercise

  • or some homework for some people it might be

  • implement a timer for the entire game such

  • that maybe 60 seconds-- if somebody doesn't

  • match all of the pairs in 60 seconds, they go not to the you win screen,

  • but to the game over screen instead.

  • Maybe have it be the same logic, and this would be a pretty simple thing

  • to add to the game too using pieces that we've already talked about.

  • Timers, and the print statements, and what not.

  • Andre says cool, looking forward to Unity.

  • Me too, actually.

  • I would like to get more well versed in Unity myself.

  • The Pong is easy, but we could work our way up to lots of cool stuff.

  • Maybe making an FPS.

  • Maybe a like a family friendly FPS game or a simple platforming type game.

  • I've always wanted to make a Banjo Kazooie style collect-a-thon N64 type

  • game.

  • So that would be fun.

  • It would be many streams, though.

  • That wouldn't be one stream.

  • Bhavik says will I need C# knowledge?

  • Not a lot.

  • When we do Unity next week, it'll be pretty minimal on the C#.

  • I think we only have one script with about six lines of code,

  • and I'll go over all of them pretty thoroughly.

  • Is that true?

  • The paddles need a script, the ball does not need a script, I don't think.

  • I don't remember.

  • But either way, it's very minimal coding next week.

  • If you've coded in Java at all or C, it's fairly similar.

  • Irenenaya says C# has a very clean syntax,

  • and Unity usually doesn't require too much coding anyway.

  • That's true.

  • For the examples that we're working on next Friday,

  • it'll be pretty lightweight in the coding.

  • You can definitely get into some pretty hardcore coding with it,

  • but that'll be for a much later stream, I think.

  • The very next week will be kind of like a hands on intro tutorial,

  • much less a hardcore coding session like today.

  • Today was pure coding, for the most part.

  • Bhavik, if you're learning Java, then you'll find that C# is actually very

  • similar to Java.

  • Almost identical.

  • From MS courses on EdX and following algorithms--

  • Princeton on Coursera.

  • Yeah, you should be able to follow along quite well.

  • We did it, guys.

  • We made a game.

  • It was kind of painful at parts, but we did it.

  • C# is a cleaner, less verbose Java.

  • Yes, I agree.

  • It has a bit of a few weird naming conventions, but I'll take it.

  • Swarmlogic, sorry for derailing a bit with the map idea.

  • The map idea?

  • Oh no, not at all.

  • That was a clever way of solving the problem.

  • I think I approached it a little bit kind of blind.

  • A little too blind, and screwed up in very subtle ways,

  • and it ended up just being hard to track down in the moment.

  • Bugs are notoriously harder to catch when you're coding in front of people,

  • I'm noticing as well, but that's part of the fun.

  • And I like the fact that everybody who's in the chat

  • can make suggestions in real time, and sort of help out.

  • It's almost like we're making it together.

  • The map idea, I probably wouldn't have done myself.

  • I probably would've done something else.

  • So it was kind of cool that we took a community approach to it.

  • By next Friday, I will be knowing much more in Java.

  • Yeah, true.

  • And again, Bhavik, don't feel too pressured.

  • The C# that we'll be doing is very easy to understand, and a lot of it is,

  • honestly, Unity specific because we're mostly just going to be calling Unity

  • core functions, and not doing a whole lot of hardcore programming.

  • We can maybe look at more actual C# programming,

  • and more fancy algorithmic stuff on a future stream,

  • but next week's will be pretty tame.

  • All right, well, we're 10 after 4:00.

  • We've gone on for a little over three hours.

  • We had a few setbacks.

  • We had a lot of success and we came out with a full game--

  • concentration, memory card.

  • Shout out again to Asli Tumerkan for her suggestion.

  • If you have any more suggestions on games that you want to implement live,

  • and for me to stumble over live in front of a whole bunch of people,

  • and for people on YouTube to see afterwards, definitely let me know.

  • If you're watching on YouTube later after we've uploaded the video,

  • then post in the comments.

  • If you're not following our Twitch channel, go to twitch.tv/cs50tv.

  • We do streams every week, and I probably will end up

  • doing a stream every Friday, if not every Monday and Friday.

  • But next week, on Monday, we have David Malan himself.

  • He's going to be talking about a SQL.

  • On Tuesday, we're going to have Nick Wong, who

  • was in here last time who's going to be doing a basic binary

  • classifier using Python.

  • And then on Wednesday, we're going to have CS50's Brian Yu giving us

  • an introduction to the web framework React.

  • So come next week.

  • Stay tuned.

  • Join the live chat, and then come on Friday too for some Unity programming.

  • It's going to be a good time.

  • So thanks a lot, everybody.

  • Appreciate it.

  • I will see all of you on Monday.

  • Have a good day.

COLTON OGDEN: Hello, world.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it