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