COLTON OGDEN: All right.
Hello, world.
This is CS50 on Twitch.
My name is Colton Ogden.
Today we're going to resume what we did last week with Minesweeper.
Prior to today's stream, we had a lot of awesome language chat
in the, uh-- well, language-- we had a lot of language
discussion in the chat, a lot of folks from different parts of the world.
Going to shout out--
[INAUDIBLE] long discussion, here.
Shout out to everybody who popped in early.
So let me just make sure I'm going to the right place.
[INAUDIBLE]-- regulars, of course.
Acid Jack, who said "Greetings from Germany,"
and we had a little bit of a German discussion.
Learned that "nabend" means "guten abend."
It's a shorthand version of that, which is awesome.
Who else do we have, here?
We had-- let me scroll down--
[INAUDIBLE],, from Uzbekistan-- so, some representation
from Uzbekistan, which is awesome.
We have, in addition to that--
Not Sure, of course.
We have [INAUDIBLE],, representing the Hindi language-- didn't
specify the country, but Hindi.
[INAUDIBLE] namaste.
[INAUDIBLE] actually was the one who said "namaste," so, apologies.
And then [INAUDIBLE].
And speaking of [INAUDIBLE],, happy belated birthday, [INAUDIBLE]..
We missed saying happy birthday to you on Friday.
I don't remember if it was Friday that you were sick
or Thursday that you were sick.
Hope you're feeling much better, but happy birthday.
Happy belated birthday.
Sorry that I missed it on the actual day itself.
Hope you had an amazing birthday, and hope you're feeling fantastic again.
Mission Vision-- greeting from Austria.
There we go.
A lot of European representation today, which is awesome.
We're going to find a list of upcoming Twitch broadcasts.
That's facebook.com/CS50.
They're all listed as events and will also
be listed as video, live video events, in addition to that.
People are talking about where they've been to in Europe.
So, awesome.
I think we're all caught up.
We had a lot of awesome prestream chat.
But, yeah, we're all set.
So last week what we did--
I'm going to switch to my computer, here-- boom.
We talked about the game Minesweeper, and we implemented a similar,
beginning version of it-- not the full game, not the whole gameplay loop,
but it looked something similar to this--
If I can get this working.
So this is just the grid, kind of by itself.
The last feature that we implemented actually
was being able to highlight a given rectangle on the screen,
so that visually you can see where you're trying to click on a given
tile-- which is pretty important.
You know, visual feedback like that.
Subtle little things add up.
So the next thing, I guess, the next big feature, would be--
and probably going to be the majority of today's actual--
the more challenging aspect of today's stream
is going to be the recursive sort of reveal loop that goes on
and, when we click a tile, showing all the tiles that we--
assuming we haven't clicked on a bomb, which would be unfortunate.
But if we haven't clicked on a bomb and we
click on a blank tile or a numbered tile, if it's a numbered tile
we just reveal the number.
If it's a blank tile, we reveal all the blank tiles that are adjacent to it
and recursively keep revealing tiles until we reveal numbers or blank tiles.
Right?
And so that is the gist of what we have to implement, today, the core of what
we have to implement, today.
Nice things to also implement, per what some folks mentioned last time,
would be flags.
So, in the actual game, you can click on a given tile
to indicate that it's a bomb, that you've
predicted that's a bomb, based on the neighboring numbers and whatnot,
so that you don't accidentally click on it in the future.
Which would be unfortunate.
We can even make it such that, if you even try to click on a flagged tile,
it just won't let you do that, if it's flagged.
It'll just be completely error-proof, in that way.
And then having a game-over screen, if we do click on a bomb-- so,
you know, showing that we've lost, showing how many points we had,
and then maybe allowing us to restart the map.
So these will be all features that we could try to implement today,
as we go along.
And probably the biggest piece would be the reveal loop to the game.
So that's probably what we're going to start with, today.
I think if we bite that off first, everything else will be kind of easy.
[INAUDIBLE] says "Hello from Gujarat, India."
Awesome!
Hello.
Thank you for joining us.
We got some--
[INAUDIBLE] I'm not sure what language that is.
[LAUGH] [INAUDIBLE] says "genuinely blessed."
That's awesome.
Well, again, apologies we didn't wish you a happy birthday on Friday.
It would have been much better, but it was indeed Friday, right?
If I'm not mistaken?
But, yes, happy belated birthday.
OK, cool.
So this is the game I'm going to.
So, again, we had some little sort of debug output,
at the bottom of the window, there, the x-y of our cursor,
the highlighting tile, whether it was true or not,
and then the actual grid index.
And that's kind of a common thing, in games.
You know, it's not always super-easy to write to a console,
you know, to write to a terminal like this
and debug your game at the same time.
It makes sense, a lot of the time, to actually integrate
the output of your game, the debugging output, into the window itself.
So, in this case, this is actually drawn text,
using a font, just like we would have drawn anything else.
And it's not CLI output, in the normal debugging sense.
This is a different type of output.
But it works better, and it allows us to more instantaneously see things, too.
Because we were making a change every time we move the mouse,
and doing that through the CLI would get ugly and weighty.
It's just much easier.
You've probably seen it in many games that have a debug menu or a debug mode.
And that's kind of the purpose of that, to actually integrate the debugging
output into the executable-- make your life a lot easier,
if you're testing things.
Games like Skyrim and Fallout actually let you examine variables at runtime,
when you're playing the game, through a special integrated console.
And that's cool, too.
And that gives you a lot of freedom.
You can actually mess with the game objects
and do all sorts of really crazy fun stuff.
Let me just make sure that I haven't missed anything in the chat.
Cool, cool.
All right!
Awesome.
Well, I'm glad we have a healthy number of folks
already, so let's get implementing the next feature.
So what we have so far, again, is this grid of squares.
And my goal is, when I click any of these squares, I want it to--
the first step, the very first thing that we should do,
is we should just reveal it.
Right?
So just set Reveal to True.
And that should be pretty easy.
Right?
So let me just go over to--
it should be in the game grids.
So, if we recall, the game grid had an update function,
and in the update function we essentially had--
we were checking the x pause and y pause of the mouse.
And that's where these two variables are, here, up top.
The x pause and y pause both get returned from the push:toGame function,
which basically gets the love.mouse.getposition function.
So this returns our actual mouse in its native pixel size--
so, in our 1,280-by-720p window, it'll give us the 1,280-by-720p pixels,
the coordinates for our mouse, and--
But push, remember, it's a virtual resolution,
so that actually isn't going to map evenly to our 1,280p-by-720.
And so, instead, we wanted to convert that 1280-by-720
into this 384-by-216 pixel size.
And we do that using the--
foo go back to the game grid.
We did it with the push:toGame function.
So that takes in native resolution and gives us
our virtual-resolution x-y mouse coordinate.
So you can actually interact with our game
and pretend sort of like we're using it at a native resolution,
but we're actually just spoofing it, using texturing--
using a stretch texture, rather.
So, in here, we're already sort of looking for the mouse.
And what we want to do is we want to check basically on click.
And normally how we do that is we override the love.mouse--
I believe it's love.mousepressed.
So this function, love.mousepressed.
And it takes in a key, I believe.
And I'm not 100% sure, so what I'm going to do--
and this is what I encourage you to do, as you're developing in Lua
and Love2D--
is to go to the Love2D docs.
So I would type in "Love2D mouse"--
uh, what would it be?
"mousepressed, I guess-- one word.
Because that's the name of the function.
You can see, here, it's the first link in Google.
Going to click on that.
And it actually takes in a x, a y button is touch and presses.
Interesting.
OK.
So why is it returning an x and a y, if it just--
interesting.
Oh, OK, no, I was getting confused.
So these are all values that it gives you back.
So, just like-- how do we say--
just like the love.keypressed function gives you the key back,
so that you can use it in your function, this gives you not only just
the button-- so, for example, 1, I think, by default is left-click--
Yeah, it says right here.
1 is the primary mouse button, so primary mouse button
is usually left-click.
2, being the secondary mouse button, that's right-click.
3, being the middle mouse button--
And if you have a mouse with a ton of buttons,
you'll get more indices that you can access through this API.
But we only really care about button.
But if we wanted to, if we wanted to say,
OK, is the x and the y within a certain bounds, and have we
pressed a certain button?
We have access to that in here.
But we're not going to really worry about that too much, right now.
All I want to do is say "love.mousepressed,"
and then I just want the--
so, "x, y, button, istouch," and "presses."
All right, presses is cool, too, because that actually
lets you detect whether or not it's a double-click versus a single-click.
And this might be important for certain games.
It's more of a GUI feature than anything else, but, I mean, games like--
even I've seen it in, like, Minecraft, for example.
Like, you double-click a specific block, it does something different
than a single-click.
We don't care about that.
We just care about the button.
So what I'm going to do is I'm actually going
to do something very similar to what we've done before,
with keeping the ability to press keys outside of main.lua,
which is a thing that you need to do a lot of the time.
So normally you would have this function, right, love.keypress,
which takes in a key, and you can define what behavior
you want to have happen when a particular key is pressed.
But this only works in main.lua.
You only have access to this love.keypressed function in main.lua.
And so, if I wanted to check for a single key press outside of main,
I need a function that I can use globally--
a different kind of function.
--our streaming server did, so Facebook probably just went down.
YouTube and Twitch should be back up, but let me know in the chat
if you can hear me.
We make sure that we're back up and running successfully.
It looks like we're up and running.
[LAUGH] We have some people in the chat saying that they can't see it,
but I think it's a little bit delayed.
Let's just make sure.
Cool.
OK, so people can see it.
Yeah, that's a bug with Facebook.
Facebook, for some reason, keeps kicking us
off the platf-- whenever we stream to Facebook,
we keep getting booted off, essentially.
And I'm not sure why, but I just took a screenshot of the bug.
I'm going to send it to somebody on our team, and they can take a look at it.
Anyway, we're back up and running.
What I was saying before the stream got kicked off
was that, in Love2D, you have a single place
to define key a keypressed callback function,
but we want that behavior across multiple files for our mouse.
Right?
So I'm going to do just that, by first doing a couple of things.
I'm going to say love.mouse.keysPressed equals an empty table.
And we've done this before.
We did this in another stream, with keys.
At least I think we did this in another stream, with keys.
But I'm going to define an empty table, here, called "keysPressed," as part
of the mouse namespace.
And this is just my own table.
This does not exist in love.mouse but, because Lua's a dynamically typed
language, I can add this easily.
So I just say love.mouse.keysPressed is equal to an empty table.
And then I can say, in here, whenever we do press a button on our mouse,
I can say love.mouse--
actually, what I should do, I'm going to call this "buttonspressed," just
to keep the verbiage consistent.
I'm going to say love.mouse.buttonspressed
at index button is equal to True.
So now, whenever we press a mouse button, at any time--
because this is in main, this will happen no matter which class
we're in, in which object we're referring to--
this table will get updated with--
on just that one frame.
Because, at the end of update, we're going to clear it.
I'm going to say love.mouse.buttonspressed is
equal to an empty table, again.
And what this does is this actually lets me click a mouse, and then
the next frame it'll be gone but we can therefore
check, on a frame-by-frame basis, whether a given key or a given button
on our mouse was pressed just one time.
Right?
Because that's the only way that this mousepressed key fires.
It was just the one time, right?
I'm going to, after that, define another function.
So I have to say love.mouse.waspressed, button.
And then this is a brand-new function, so I'm writing this myself,
and I'm adding it to love.mouse, just like I added the buttonspressed table.
In this case, this is a function, a Boolean function,
which is just going to return love.mouse.buttonspressed, button.
Right?
So I can say love.mouse.waspressed 1, at any time, any class,
and it will refer to this function, here, and basically check that table,
on that frame, if that button was pressed.
And if it was, then we did a single-click,
and we can check for it anywhere, not just in main.lua.
So let me make sure I have that chat open, here, so I can keep up with it.
So, sorry that the stream went down.
I do apologize for that.
Could you also do it by saying "equals False," says [INAUDIBLE]..
Um-- in this case, no, because we're going to-- basically this, what this
is, here, this return, love.mouse.buttonspressed
at index button, we want this to be true, because we're basically saying,
was that pressed, on that given frame?
"Was" kind of implies a truthy Boolean return value,
and so it's kind of ultimately what we want to do.
And every other button is going to be false,
assuming that we haven't pressed any other button, anyway.
And so we only really care about the individual button
that we've pressed on that frame-- just the one.
We don't want to worry about the other ones,
and therefore set them all to False.
JL97 says hello.
Hello, JL, glad to have you with us.
"Hello from New Delhi, India.
Is it still worth watching the stream, if I missed the previous one?
I really loved CS50X 2018, by the way.
So props to the team, for that."
I would say, you're going to be a little bit lost, in this stream, I think,
[INAUDIBLE].
You can tune in and maybe get some stuff kind of passively by watching it,
but it'd be better for you to watch the first part and then the second part.
So, that way, you have the full context.
Optionally, you can download the repo.
So I actually have everything added to GitHub at the following you URL.
Let me go ahead and write that.
Github.com/coltonoscopy/minesweeper stream.
So that URL has the code for what we have as our starting point today.
So if you want to clone that--
and you are already familiar with game programming, at all--
then definitely check it out.
But if you haven't done any game programming yet,
I would maybe watch some of the earlier streams,
just to familiarize yourself beforehand.
OK.
"This is a bit too early for me.
Might tune in after the kids in bed," [INAUDIBLE]..
I totally understand, [INAUDIBLE].
Thanks for popping in.
[INAUDIBLE] says "Hey, everyone.
Hi, Colton."
Hey, [INAUDIBLE],, good to have you with us.
[INAUDIBLE] says, "didn't know you had a kid."
Um--?
"No, I mean, when you put the empty table afterwards,
to make it work for only one frame," says [INAUDIBLE]..
So the empty table is an update.
And we wouldn't really be able to make that work.
Right?
Because in update, what we're doing is we're clearing the entire table.
And when we set a given key or a given button to True or False,
that's just for one key in that table.
It's not for the whole table at large.
So we couldn't really set it to False.
It wouldn't really make sense.
And, if you're referring to setting this to False,
then this wouldn't work on the next frame--
if that makes sense.
Because, now, love.mouse.buttonspressed would be a Boolean value.
It wouldn't be a table.
And so we couldn't index into it, with this.
If that's what you're referring to--
if I'm understanding your suggestion correctly.
[INAUDIBLE] says "I lose.
Which language is it?"
This is Lua.
So, if you're unfamiliar, it's Lua.
And we're using the Love framework, which is at love2d.org.
You can download it right here.
Super-awesome game framework.
If you're completely new, check out maybe part 1 to this stream
or maybe some of the other streams that we've looked at in the future.
And [INAUDIBLE] and Indraready5, thank you very much for following.
Appreciate it.
[INAUDIBLE] says that everything is good.
All right, perfect.
So, back to where we were.
Now, I have this function that I've defined--
love.mouse.waspressed.
Just to make sure that it's working, I'm going to, in my gamegrid.lua,
basically say, if love.mouse.waspressed 1--
whoops-- then, [LAUGH] I should say--
I'm just going to do a quit, right here.
So this means that the love.mouse.waspressed function
is globally accessible, and so I can use it from within the gamegrid class.
Going to run this, going to just move around and then click.
And it does indeed work, so that's great.
Now, since we have the ability to click anywhere on our screen,
from any class--
which is awesome-- we can say, basically, in here,
if love.mouse.waspressed 1--
because the x and the y position are going to be--
we can basically figure out which tile we're looking at, given
this loop that we've already pre setup.
So we basically are iterating over the width and height of the grid,
and then we're checking our x and y position
and sort of multiplying where the grids x and y are by the tile size,
and also the offsets that we calculated.
Remember, we have a horizontal and a vertical offset, to center our grid.
And so we're doing a little bit of math, there.
And the highlighting tile-- so basically what we can do now is say--
let me do it in here--
if love.mouse.waspressed 1, then--
Right?
Because we're in here. we know that we're
highlighting a given tile on our grid.
We have the x and the y that we care about.
I can say--
Um, how did we do it?
What's the member, here, that I care about?
It's grid, right?
Self.grid.
So I need to go to self.grid, at index y, x, and I need to set this tile to--
the ishidden needs to be set to False.
Right?
Because, by default, it's set to True, which allows all of the tiles
to be hidden at first glance.
But I want to test revealing a given tile, one at a time.
So I can instead say .ishidden is equal to False.
And what this should do, if everything is correct,
is reveal tiles, one by one.
Which it does indeed do.
So this is pretty cool, right?
Now, it doesn't work quite the way we want it
to when we play the actual game.
When we play the actual game, if we click on a tile, like this-- a 1,
or a 2--
any number-- it should reveal the number.
Right?
That's fine.
That's totally expected behavior.
But if we click on an empty square, like this, what we want to have happen
is to actually recursively reveal every other tile that's
adjacent to that tile that's either empty or a number.
If it's one or the other, then two different things might happen.
If it's a number, then we stop the recursion at that point.
That tile doesn't spawn out and reveal any of its neighbors.
If it's another empty tile, then that empty tile
spawns out to all four directions around it, to check those tiles.
And then those will spawn out, and so on, and so forth.
And it's like a fill sort of algorithm, in that sense.
That's the behavior that we're going to.
"Or an edge," says Mojodojo101.
Yes.
And that's an important part of the recursive algorithm,
is it needs to check to determine whether or not any of its neighbors
are at the top, bottom, left, or right edges of the screen.
Because, if they are, then it shouldn't spawn off--
it shouldn't fork off that recursion.
[INAUDIBLE]?
Mojodojo101 also with the clap-- appreciate it.
A little bit of Coke Zero.
Anyway.
So, because this is the point at which we're actually clicking and revealing
titles one at a time, this is a great place for us
to actually perform that recursion.
Right?
We have access to all the other tiles, so we can sort of, kind of,
inject into here that recursive formula.
Now, what we need to do is write a function
that can be called, on a given tile, and then pretty much just
have that function call itself for all of its neighbors.
Right?
So we can do that--
we can do that here.
So I'm going to make it part of the gamegrid class.
I'm going to say "revealTile" x, y.
And-- well, the important thing is that we, first and foremost,
do what we just exactly did-- self.grid oix, dot--
um--
So there's a thing, here.
So, if we click on a bomb, we want that to have separate behavior.
And that's not going to be part of the recursive formula.
The recursive formula doesn't reveal the bomb.
It only reveals non bombs, and--
it only reveals basically clear tiles and numbered tiles.
And so, as a result of that, we don't want this recursive formula
dealing with that.
That's going to exclusively be in here.
Right?
So that's OK.
And then--
But what we do want to do is we want to check to see whether or not
it's a bomb or a number, because that's going to determine
the behavior of the recursion.
So I can say--
um-- and we can assume that, in here, we'll
actually perform the logic for ensuring that we don't go outside the bounds.
So I'm going to say something like--
let's see, OK.
First thing we're going to do--
top tile, bottom tile, left tile, right tile.
And it's going to look something similar to this.
It's going to be self:revealTile.
So the top one would be x, and then y minus 1.
The bottom tile would be self:revealTile x and y plus 1.
Self:revealTile x minus 1 and y.
And then self:revealTile x plus 1 and y.
And these will only reveal to their direct neighbors,
so it won't reveal diagonally.
It will just reveal horizontally and vertically.
And, in order for this to work appropriately,
we kind of have to do a couple of things.
So the first thing we're going to do--
if this tile is a number, we don't want to do this at all.
We're just going to basically reveal it and be done with it.
So we can essentially say if self.grid y dot x dot-- and I forget what the--
OK, so it's numBombNeighbors.
So numBombNeighbors is greater than 0--
So, if that's the case, that the number of neighbors
that are bombs greater than 0, another thing we have to check is to make sure
that's not a bomb.
Because if it's a bomb, we don't want to reveal it.
Right?
That would kind of defeat the purpose of the game.
So what we also should do--
just to make this a little bit cleaner, I'm
going to say local tile is equal to self.grid at y, x.
Just like that.
And, this way, I can do this.
Tile.ishidden, and tile.numBombNeighbors is greater than 0, and tile--
and not tile, dot, is--
what is it? --isBomb?
Yeah.
IsBomb.
So don't recurse--
I don't know if that's a word.
Don't recurse if this is a number tile or bomb.
Right?
And actually--
Actually, no, even more importantly, if--
uh-- we don't need this.
We don't need this at all.
Because what we're going to do is we're going to say, if, um--
if tile.isBomb, then return end.
Right?
So return end.
You have to say "end," because that's the end of an IF block.
But basically this is going to immediately exit if bomb no recursion.
Right?
No recursion or reveal.
And then, after that-- so basically, if it's a bomb,
it'll skip doing everything completely in its function.
The next step is going to be reveal yourself.
Right?
Should reveal yourself.
Because this means that it's either an empty tile or a number tile.
In both of those cases, we want that tile to reveal itself.
But we only want to do this for every other tile when it's greater than 0--
number-- sorry, if it's, uh--
if not tile, uh--
no, if it's not the case that the number of tiles is greater than 0,
then we want to recurse.
Right?
So, if we have no neighbors, if it is 0, then we
recurse, because, if it has anything greater than 0,
then it's a number tile.
Right?
And so we don't want that to have happen.
OK, so that should be pretty straightforward.
There'd be one check, in here, before we actually
do this, which is going to be--
um-- self.revealTile, x, y.
There's maybe one more check, in here--
Oh, actually, a couple more things we want to do.
There's a couple more things we want to do.
So, to someone else's point, the walls--
first, that's a big problem.
"Greater than or equal to 0, or less than or equal to 0," says [INAUDIBLE]..
Less than or equal to 0.
It's never going to be less than 0.
In this case, we want to make sure that it's greater than 0, that we skip it.
So, if it's not--
we could do, if tile numNeighbors is equal to 0, that would work.
It's never going to be less than 0, so we
don't really need to check to see less than or equal to 0.
We're not going to ever have negative-1 number of neighbors.
NACLEric says "This is a vague question, but how do you decide
when to use recursion over iteration?"
it kind of depends.
Like, something like this, where you have
sort of like the same behavior that spawns off and doing multiple things,
it's kind of a natural place to do recursion.
We also did recursion when we did the stream with Rodrigo,
for the Game of 15 solver, and we used a breadth-first search.
That was also recursive.
It's kind of dependent on your data and your algorithm
and how you think of the problem.
In this case, we are sort of like basically
doing the same thing for a bunch of-- you
know, for every neighbor tile, and then those all branch off
to their own neighbor tiles, and then so on and so forth.
So the problem kind of grows, exponentially, in that sort of way.
And so that's kind of the perfect place to do a recursive algorithm.
It makes a little bit less sense when you're
doing something kind of sequentially and it's a little bit more straightforward.
I would say that's probably not good for recursion.
Recursion's also dangerous in some programming languages,
when you're dealing with massive data, because you'll get stack overflows.
Depends on whether your language supports tail call optimization.
Which just means that it doesn't return the entire stack frame.
It, like, merges the prior stack frame and the current stack frame
in this interesting way.
So it depends, ultimately.
But this is a great use case for recursion.
Anything dealing with trees tends to be really good with recursion.
Yeah.
It's not something I jump to very often, but when
it fits the problem it makes for a very simple, elegant solution.
"Don't you want to show the bomb?
Or do you reveal the entire board if you've found the bomb?"
says Mojodojo101.
Couple of things.
So if you click on a bomb, yeah, you will show the bomb and the board.
If you clear the entire board, as well, without getting any of the bombs,
you will also show all the bombs.
So both of those we kind of need to take into consideration.
And we will do that, I think, ultimately.
"Recursive function is needed when we can
reduce a problem to a smaller problem of the same kind," says NACLEric.
Yeah, that's a nice way of putting it [INAUDIBLE]..
But there's a couple of things wrong with this recursive algorithm.
So the first thing is that we're blindly assuming we can go on forever
on the x- and y-axis.
And so what we need to do is, essentially,
make sure that we're not going less than--
Like, basically, for all of these four different paths,
we want to make sure that we're not going outside of our bounds.
So, four y minus 1, we basically want to say, if y is greater than 1,
then we can do that.
Right?
We can do the same thing, here.
So, if y is less than or--
is greater than 1, and if it is less than--
what was it? --grid--
what was the constant called? --gridwidth?
So, if it's-- or, height-- gridheight.
Right?
Same thing for the left and the right.
So I can say, if x is greater than 1--
because we're going to subtract 1 from it,
so we'd want to make sure that it's greater than 1--
because our tables are one index, so we can't go any less than 1.
We can't go to 0, for example.
So we have to make sure that our x is greater than 1 or y is greater than 1,
et cetera, et cetera, when we actually perform this function, here.
And then, if x is less than grid width, we can go up to our grid width.
So we want to make sure x is less than it, when we increment by 1.
So this should work, as far as I'm aware.
Hopefully, I didn't mess anything up too badly, here.
And let's try it out.
Whoa, stack overflow!
Oof.
That's rough.
OK.
And that's the proverbial [LAUGH] stack overflow that I just referred to.
So let's figure out what's causing that.
That's interesting.
"If tile"-- so we don't have a base case, here, it sounds like.
Well, I mean we do have the--
Hmm.
We have to return, if--
So what we need to do is we need to also return if the tile is already revealed.
So that's the thing, too.
So, if tile.isBomb or tile, dot, is not tile, dot, is hidden--
right?
Because if the tile is revealed, then we should just return,
because we've already looked at it.
Right?
Let's try that.
Oop!
Got a 2.
Ooh!
That's some nice stuff, right?
Clicked on that-- got all these tiles revealed.
Isn't that awesome?
So I'm assuming this is a bomb, which means--
oh, man, I don't know.
One of these is a bomb.
It's hard to say.
Clearly this one's a bomb.
We know that.
We know this one is a bomb, for sure.
Oh.
This is the tricky part.
OK, so this is also a bomb, for sure, because these two are only bordering
this tile, which is unrevealed.
So that's definitely a bomb.
So this is going to show you just how terrible I
am at Minesweeper, which is a completely different problem
to solve than what we're doing.
Oh, we've got a few people that followed.
I apologize for not reading that out.
So let me go ahead and do that, while we just enjoy
this amazing feature we just added.
So, Dougie Johns, [INAUDIBLE],, Alex1304, and JabsWithDangerNoodles.
Wow, that's a great name.
Thank you all for following-- much appreciated.
OK.
Hoo.
OK, got a 3, there.
That's a little bit worrisome, right?
Let's try this corner.
OK, that's a 1.
One of these is a bomb.
OK?
OK, that's all OK.
[HISS] Oh, man, this is-- this is stressful!
This is more stressful than actually writing the code.
OK?
All right, all right.
Let's make sure that the bombs are still rendering
and I'm not just getting lucky.
Oh, it won't actually let me do that, OK.
Oh, right, because, if it's a bomb, it won't actually let me click it,
right now, because it's not part of the recursive algorithm.
So that's cool.
Oof, this is a bomb, right here.
It won't let me click it.
1, 1, 1-- oh, that's a bomb, and that's--
OK.
All right, cool!
So this is great.
We have a recursive reveal function.
That was very easy, because of recursion.
Made it-- it was a very simple thing to write.
We didn't take into consideration the base case,
which is very important, because this, without this condition, here,
it will recursively go over the entire board, every time,
and not basically skip the tiles that are empty.
And, because of that, it'll just basically over and over again just
loop around the board, infinitely, and never terminate.
And that's a stack overflow.
That's what causes a stack-overflow issue.
So let's go ahead and figure this out.
So what I want to do is, if I click the mouse, and before I reveal,
I basically want to check to see whether or not
it's a bomb that was on that tile.
And, if it was a bomb, we should probably switch to Game Over.
And this will actually be pretty easy to do with game states.
We can do this with game states.
So we can integrate a state machine, like we did in prior games.
So I think we'll do that.
I'll kind of copy over the code from a prior stream,
and we'll go over it kind of fast, and then
we'll implement a game state to allow us to transition.
And then we'll start implementing flags and doing all that sort of thing.
But what I want to do is at least implement being able to open--
being able to detect and click open a--
reveal a bomb.
So what I can say is, if self.grid y x dot isBomb, then--
let's see.
What we want to do, here?
Basically, self.grid y x dot ishidden is going to be equal to False.
I guess that will be good enough.
Like, that will at least reveal it.
Ooh!
There we go.
It looks the exact same.
Are we not seeding our random-number generator, which
should be a very important thing to do?
Yeah, it looks like we are.
Why is it the exact same grid?
That was weird.
It's the exact same grid, every time.
Why is it--
Oh.
Oh, that's why.
So we're seeding the random-number generator
after we're generating our grid-- which is kind of unproductive.
So I'm just going to do that right there--
make sure it's different every time.
Oh!
And I revealed a bomb, very first tile.
So, congrats to me.
I'm an expert Minesweeper player.
And I realized we didn't actually set the title of our game.
That's kind of an ugly feature.
Why don't we do that.
So we'll say, love.window.setTitle Minesweeper--
Minesweeper 50.
There we go.
That's better.
Right?
Very important step-- never forget to set the window title.
[LAUGH]
Let me make sure that I am keeping up with the chat, here.
Oh, sorry [INAUDIBLE].
I missed your suggestion.
"This is so much fun.
We should have watched Colton play Minesweeper ages ago."
I don't know about that!
"This is how you play the game.
I thought it was just clicking on all the 1s," says LJ97.
No, you indeed are trying to avoid the mines, the bombs,
and the 1s are just a clue that tell you how many neighboring bombs there
are to that tile.
Right?
So, in this case, all of these 1s are bordering a bomb of some kind.
So these three, right here, are bordering this tile,
so that means that that is definitely a bomb.
So, knowing that that is a bomb, I can click on this one,
because I know that it can't be a bomb.
Right?
And so that one is also a 1.
And, because I know that this is a 1, from this, because this is also a 1,
it means that these two can't be bombs.
Right?
They can't be bombs, because this one was already bordering this,
and because this one's bordering the same one as this one,
and we know this one only has one bomb neighboring it.
Therefore, this one only has one bomb neighboring it.
It's the same bomb.
Therefore, these two below it that were also neighboring this one
are not bombs.
Right?
And, because these two are bordering this one, and we know this is a bomb,
then we know that these three aren't, and so on.
And it kind of goes on and on, and you keep doing that over and over again,
right?
Same thing for this one and this one and this one and this one.
And so you rinse and repeat this sort of line of thinking.
So, in the next feature that we would add, which would be the flag,
we would mark this one as being a flag, and we
would mark this one as being a flag.
Because we know that those are for sure bombs.
We shouldn't click on them.
And, until you get all the flags or the bombs,
you just keep trying to deduce what the bombs are.
"This is the first time I understood what
the empty tile does in Minesweeper."
Yeah it's a weird game.
If you're not familiar with the rules, it looks very arcane.
"Yellow, definitely bomb.
Green, definitely not a bomb."
"Well, I've just arrived.
Your Minesweeper grew so much.
Last part I saw was drawing the grid and centering it."
Yeah?
I know!
Isn't it cool?
We have this whole recursive functionality in our game, now,
so we can click on a tile-- reveal all the ones that are bombs.
In this particular instance, this is tricky.
Right?
Because-- well, I guess it's not terribly tricky.
We know that this is a bomb, for sure.
So we know that this one can't be a bomb.
But now we know that this is a 2.
So how do we determine--
well, I guess this--
this, for sure, we know is a bomb, to be fair.
And we know this is a bomb, so this can't be a bomb.
So, there, we deduced it.
We figured it out.
And so both these 2s being here, since they
both have these two tiles as neighbors, [INAUDIBLE] on only those two tiles,
we know those are definitely bombs.
We know that this is definitely a bomb, right here.
So this isn't a bomb.
This, I'm not sure, actually.
So I'm not particularly great at Minesweeper,
and so me playing this is going to be more
painful than actually implementing it.
But why don't we go ahead, now, and talk about transitioning states
when we lose.
That's an important feature of the game.
So I can pull our state machine from the last game we did that used it, which--
I think it was Space Invaders, if I'm not 100% mistaken.
It was indeed Space Invaders.
Awesome.
So what I'm going to do is, I have a state-machine class
from a prior example.
I'm going to copy and paste it into Minesweeper, right in here.
I'm going to open it up, so we can take a look at it.
So this is a state machine.
And the purpose of a state machine-- if you're unfamiliar,
check out the Space Invaders stream, where we talked about it.
But a state machine's goal is to point at something that's active,
an active state, at one time, and only one thing can be pointed to at a time.
And so, when we want to change state, we essentially
change our pointer to something else.
We perform a transition.
During that transition, we might do something.
Right?
And then we have conditions that satisfy those transitions
across all of our states.
So, if I'm in the title screen of my game,
and I want to go to the actually playing the game screen,
I need to satisfy the condition for that transition.
So that condition might be, user presses the space bar.
So user presses the space bar, while I'm in the title screen,
transitions over to the play state.
So the play state might have several transitions
that let me go to other states.
Maybe there are two branching states from the play state.
Maybe one is victory and one is game over.
To get to the victory state, the transition criteria
must be, for example, clearing the entire board
without clicking on a bomb.
Right?
Once that's fulfilled, I transition to victory.
If maybe I fulfill the criterion of transition
or, uh, this click on a bomb, and that's the criterion
for transitioning to the game-over state, if that's the case,
then I go to that state instead.
And those two states have their own sort of spin-off states.
Maybe the Game Over and the Victory both have a restart state.
And both of their criteria might be press Space bar, press Enter, right?
And so, when you fulfill those criteria, you transition to those states,
as well.
So that's the gist of the state machine.
Let me read the chat, and then I'll explain the code, here.
"That one's a bomb, for sure."
"What is the algorithm"--
"Why does the algorithm stop at a number," says Remote.
Because when we get to a number--
if we look at the game grid--
bum-bum-bum-bum-- where was it at?
Where are you at?
It never executes the actual recursive part of the formula,
if it's the case that the number of neighbors is 0.
If the number of neighbors is 0, then it skips these four recursive statements,
right?
Because these four statements are just this same exact function,
which itself just calls tile.hidden--
ishidden is equal to False.
Right?
But if it's a number, it's not going to spin those off.
And that's why we get that behavior of stopping at the numbers.
That's one of the base cases for our algorithm.
Right?
Hopefully that makes sense.
[INAUDIBLE] "Add cheat codes-- the best way to test a game without being good."
Yeah, good point.
Good point.
If one bomb is confirmed, all 1s surrounding the bomb
can't have any bomb in their neighbors."
"All 1's"-- [INAUDIBLE] yes, correct.
BartleD says "Hey."
Hey, BartleD, good to have you with us.
All right, [INAUDIBLE] excited about state machines,
so why don't we talk about what a state machine is.
Oh, we talked about what a state machine is.
Why don't we talk about the code.
So the state-machine class that I typically use, that I teach with,
takes in a series of states--
and I'm going to go ahead and include that, right now, in my main.lua.
So if I go over here, and I say gStateMachine is equal to StateMachine,
and it takes in--
it gets constructed with a table.
Right?
And so this table is going to take in a few different states.
I want a title state.
And so this is kind of interesting syntax, here.
What we're doing is we're creating a table with string keys,
and each of the string keys points to an anonymous function.
That anonymous function, every time we call, basically in our state machine,
the change function, which calls basically self.currentstate
is equal to self.states at state, and then these parentheses,
that will call that function and return it and then add it and make it
our current state.
So, in this case, if we call change to the title state,
it will return a new title state, a brand-new title state,
completely initialized.
And it'll set our state machine's current state-- remember the pointer--
it'll set that to this title-state object that we're returning.
Right?
We haven't implemented the title-state class, the play-state class,
the game-over, victory-state classes yet, but we are going to very soon.
We're going to do the same thing for play.
Play state's going to return an anonymous play state.
Our victory is going to return an anonymous victory state.
And the game-over is going to return a game-over state.
And these are really the only states that we need, I think, for a game
like this.
You can envision a high-score table, a high-score screen state.
You could do something very similar to that.
We're not going to go into that, today.
But these are the anonymous sort of objects that this is going to return.
So we call-- anytime we want to change our state machine--
so, for example, right here, gStateMachine
change to the title state--
right?
The new title state--
it's going to-- if we have a current state and currently active,
it's going to call the Exit function on that.
So that's part of the transition that I told you about, before.
So every state has an Enter and an Exit.
So, when we want to get rid of a state, we'll call Exit on it.
And I usually don't use it for a lot of things.
As your games get more and more complicated,
you might want to do stuff with Enter and Exit, but in today's example
we're not really going to use Exit a whole lot.
We will use Enter, and I'll show you why we're going to use Enter.
Enter takes in some parameters and lets us pass data between states.
And this is great, for example, when we go
from the play state to the victory state or the play state
to the game-over state.
We can tell the user in those states what our score was,
from the play state.
We sort of pass that data along, down the line, sort of, um--
what's the word-- propagates, I guess you could think of it like that.
The important line is line 13, where we say the self.current state is equal
to--
remember that table that we initialized--
self.states at index state--
which, in this case, state would be this string title,
which maps to this string, here, for these keys.
And then we actually execute it.
We call it like a function-- which, it is
a function of storing a function object, with these parentheses, right here.
So, again, these are actually storing these functions as data.
These are anonymous functions.
And so we can call them, just like we can call any function we've predefined.
And, as a result of that--
Well, first thing we want to do-- we want to do a couple of things.
So we've added a new class.
So I'm going to, in here, say require source, slash, state machine.
And then another thing we're going to need
to do, in advance, I'm going to say, require source states BaseState require
source states TitleState, require source states PlayState.
We want a game-over state, source states GameOver state,
and then, lastly, a victory state.
And we haven't actually implemented these yet, ourselves.
We're going to do that very shortly, here.
So, in source, I'm going to create a new folder, called "states."
This is how I typically like to organize my states,
when I'm dealing with state machines.
It's just a little bit cleaner.
I'm going to create a base state, first.
The base state is important, because the base state is actually
what all the other states are going to inherit from.
So the thing about the state-machine class
is that it assumes that all of these states have certain functions on them.
It assumes that every state has this Exit function.
It assumes that every state has this Enter function,
has this Update function, has this Render function.
But if we don't want to explicitly define
an Exit or an Enter state for a state, or an Enter or Exit
function for a state, then we don't want to go
through the tedium of hand-writing empty functions
all the time for all of our states.
Right?
So what we can do is we can instead say, I'm
going to create a base state which just has all the functions of every state--
all the functions that a state would want.
Right?
So, an Init function, which is just the constructor,
which just basically creates the object--
function base state, Enter, which takes in params--
now, notice, these are all empty functions.
I'm just saying "end," right after them.
So they're completely empty.
The purpose of this is just to provide a foundation
upon which we can create new, more specific states that
copy all of these functions into them, so we
don't have to write them ourselves if they're empty.
It'll just keep those empty versions of the functions for itself.
Call Exit.
I'm going to call--
or, rather, I'm going to define, not call.
I'm not calling these functions, yet, I'm just defining them,
and they're just empty.
And, lastly, "render."
And we did this in Space Invaders, so this should all
be pretty familiar, for most folks, I think,
if you watched the Space Invaders stream.
But, if not, that's why I'm kind of going a little bit--
kind of elaborating over it, just a little bit.
Now, once we have the base state, we can go ahead and define the title state.
So titleState.lua.
Now, here's where it's kind of interesting.
I'm going to say, titleState is equal to a new class,
but I'm going to specify something inside the curly brackets.
I'm going to actually say, underscore underscore includes
equals base state, which just means copy all the functions and all the data
that base state has and make title-state versions of those.
So, now, for free, I get empty versions of all those functions
that belong to titleState.
For example, that's the equivalent of me basically saying--
basically rewriting all of those empty functions-- like, you know,
all five of them, with their specific names--
and copying them over here, so they exist without me needing to do so.
However, I want to overwrite the ones that I care about--
the functional ones that I worry about.
For example, the Update and the Render and all those ones.
And so I have to do those manually.
I have to actually specify how I want that to operate for the title state.
Which isn't going to be empty; otherwise, it would be a useless state.
But the Enter and the Exit, those I can probably
leave empty, because I don't care about them, but they need to exist.
The title state, Init--
I don't know if I'm going to need anything done in the title state,
just yet.
I know that I will need an Update function.
And I know that I will need a Render function.
And so what I can do is I can say, in the Update function,
if love.keyboard was pressed--
and I'm going to say, space.
Let's just say space is our-- well, we'll say Enter.
Enter or love.keyboard.waspressed, return.
This is to make it usable on Mac and Windows.
On Mac, by default, Return is basically the Enter, for Mac.
So I can say, if love.keyboard.waspressed, Enter,
[INAUDIBLE] love.keyboard.waspressed, Return, gStateMachine change
to the play state.
I want to go to the play state from the title state.
Right?
And so then I'll have my separate play state, which has all that stuff.
We're going to show how to actually do all of that, in just a second.
Let me go ahead and make sure that I've kept up, here,
with the chat, which I don't think I have.
I think there's a little bit of the chat that I've missed, here.
OK.
UnitedStatesofLove2D, "all right, I'll see [LAUGH] myself out."
Interesting.
OK.
I'm not sure I get the joke.
UnitedStatesofLove2D.
What is the joke, there?
I apologize.
I'm missing-- I'm blanking on that one.
"I've been trying to beat Minesweeper for the past 15 minutes.
No luck," says NACLEric.
[LAUGH] That's part of the fun, you know?
The educational stream, replete with, you know,
teaching you how to play a complicated game--
Minesweeper.
It's a bundle deal.
"15 minutes?
Whoa.
My best time is 13 seconds for 10-bomb mode."
Interesting.
Are you trying to brag, [INAUDIBLE]?
Is that a brag?
Or are you saying that you're horrible at the game?
I'm not sure how to read that.
I'm not too familiar.
JL97, "Inheritance."
Exactly.
This is a concept called "inheritance," in object-oriented programming.
And we're talking about doing an object-oriented stream,
in the future, that goes over a lot of these details.
So stay tuned for that.
Hopefully we can do that to give folks more of a foundation.
"How is this include different?
Is it inheritance?"
Correct.
Oh, NACLEric [LAUGH] "Humble brag."
Andre, "Two for the price of one."
Anyway, OK, great.
So this is how we can sort of isolate behavior in our game.
We can take out bits and pieces, different parts of the game,
different screens, different states-- however you want to think about it.
Certain engines will say they're screens.
Certain engines will say that they are states.
We typically use "states."
We use that nomenclature.
So, yeah.
If love.keyboard.waspressed, Return.
We're going to transition.
Let's test that this works.
I'm going to say "love.graphics.print, Minesweeper."
And I'm just going to print that right in the middle of the screen--
or right at the top of the screen, rather.
Oh, let's center it.
So, 0 virtual height divided by 2 minus 8 minus 8.
And then virtual width, center.
So this is just the printf function, which takes in a string,
takes in a starting point, a height, or a y--
a starting x, a starting y, a amount of space to format,
and then the format style.
So, in this case, we're centering it across the width of the screen,
starting at the far-left side, about halfway down.
And then I'm going to do "press Enter to play."
Same thing we just did, but I'm going to add 16.
I'm going to say "virtual width" and "center."
And I'm just going to create empty states, here.
So, play state, game-over state, victory state.
Is that all of them?
I think that's all of them.
And I'm just going to make them classes.
So I'm going to say, um--
yeah, because these only do exist for main.
Because it instantiates a brand-new copy of each class,
each object, and those anonymous functions that we just looked at.
So I need to define that these are at least classes
that inherit from base state.
So I'm going to say VictoryState Class.
I'm going to say VictoryState is equal to class,
underscore, underscore, includes is equal to BaseState.
GameOver state.
The less-exciting part of programming, but this is the cost involved
in actually engineering your game, your code base-- whatever you're doing--
making it look better, making it more extensible, more robust.
This is the up-front cost you pay, but you reap rewards down the line,
especially as you get larger code bases.
And so this is important that we look at this.
We don't do this every stream, because it's a little bit weighty,
but for something like Minesweeper, for things
like the big games that we make--
Minesweeper and Space Invaders-- those types of games--
it kind of makes sense to illustrate how it works.
Right?
So I'm going to say, includes is equal to BaseState.
Oh!
That's not what I wanted to do.
I quit VS Code, completely.
But thankfully it remembers which directory
you were in before you did so.
PlayState, class, PlayState is equal to class, includes equals BaseState.
And I think we're OK to at least start.
Uh--
Oh, you know what I'm doing?
In main.lua, everything is in main.
So it's just drawing everything.
So we have to do a few things.
So the first thing is, I'm actually going to take-- before we test this,
I'm actually going to take everything out of our main.
I'm going to put it into the play state.
Right?
Just-- because we can't really test this whole thing
until we've finished kind of building it out and refactoring it a little bit.
So I'm going to say PlayState update and PlayState render.
And then, in main.lua, I'm going to to open up main, which is down here--
and, let's see, I'm going to copy these.
So, first step, in the play state--
thankfully, we isolated a lot of the behavior of our game to the game grid.
And so we don't actually have to copy and paste
that code into our play state.
We just have to copy and paste where we instantiated the game
grid, which was at the top of main.
Right?
But the game grid itself, the class, contains all the logic it needs,
so that can go in any state we want it to.
We don't actually have to copy and paste any of that.
So, in the Init function of the play state,
I'm going to copy these two things.
And I'm actually going to get rid of this and put this in main,
because I realize, every time we create a play state it's going
to reseed the random-number generator.
So save this.
Bring this back over to main.
Put this at the very top of load, just like that.
And now, in our play state--
Now, there's another couple of things we need to be aware of.
So now we're actually using classes to represent our states.
We want to make sure that all of the important variables that we were using
in main, sort of at the top level of our application,
are now member fields of these classes.
So, in this case, the play state has its own game grid
that it has a reference to.
There isn't a global game grid or some game grid
outside of play state's definitions.
It should be a self.gamegrid.
The play state should own every variable that's
important to the play state's operation-- in this case,
self.gamegrid.
Right?
So we do that.
Let's go back over to main.
We need to get rid of grid title-- game grid.
I need to get rid of some of these modules,
just to keep things a little bit simpler.
So these all can stay right in here.
This is totally fine.
Grid is, I think, no longer used at all, if I'm not mistaken.
But is it-- um--
yeah, it's not used at all.
So we get rid of that.
Yeah, same thing with this.
Yeah, we're not using grid at all, anymore.
So that's old code.
That's actually been completely unused, this whole time.
StateMachine stays in here.
All of this stuff stays.
Now, the important thing is--
now, instead of gameGrid update, we take this, copy that,
put that in the play state, just like that, get rid of this,
and say, gStateMachine update dt.
And in here, this becomes self.gamegrid update.
Remember, because the game grid is now a member of this play-state class.
It belongs to the play state itself.
If we didn't have self, this gameGrid variable
would be defined everywhere-- everywhere in our entire application.
Which we don't want that to be the case.
And if we made it local, it would only be
accessible within this Init function.
So, not behavior that we want in either of those situations.
And then, lastly, the important thing that we want to do
is we want to render our game grid.
And that should be in here, right, just like this--
self.GameGrid.
Save that.
And then gStateMachine render--
just like that.
And we can actually get rid of the--
no, we can't get rid of these.
These are actually going to be part of our play state, now.
So, right in here, just like that.
And now, all of this code should work fairly well.
So I'm going to run this.
Uh-- OK, waspressed--
OK, that's on title-state line 12.
Let's see what that is.
Oh, right, I haven't defined the waspressed function.
So we did that for the mouse, but we haven't done that for the keyboard.
Very important.
So, love.keyboard.keysPressed is equal to an empty table.
We're doing the same thing that we did for the mouse.
We're adding a new table that gets updated
with every key press on each frame, that main manages for us.
And this lets us use it in any state, any class,
wherever we want to in our application, because it's global.
We'll have this global object.
Right?
So I'm going to say, love.keyboard-- in the love.keypressed function, that's
where we get a reference to every single key we pressed,
the entire life cycle of the game.
I'm going to say love.keyboard.keysPressed key is
equal to True, just like we did for the mouse.
And then I'm going to define love.keyboard.waspressed key.
And I'm going to return love.keyboard.keysPressed key.
And then I'm going to say love.keyboard.keysPressed is
equal to an empty table.
Now, that should work.
Works great.
If I press Enter, we do indeed go from our title state over to our play state.
We performed a transition.
So that's all working quite well.
We now have the ability to very easily take our game,
point it to any number of states however we want to.
For example, if I'm in my play state--
and this is how we do game over, now--
rather, I should go to the game grid.
That's the important place for it.
If I'm in my game grid, and I go over to--
let's see, where is it?
It is here.
So, if it's the case that, where a bomb-- if that current tile that we
clicked on is a bomb, I'm going to say self,
dot-- uh, what was I going to say?
No, sorry, g, rather--
um-- well, we want to be able to show that we're in a-- that we
got the bomb all cleared up.
Right?
So we have to actually do a couple of things.
So, for ease of demonstration, the first thing I'm going to do
is I'm going to say gStateMachine change to game-over.
I'm not going to implement, just yet, the revealing all the tiles.
We'll do that after we have the grid sort of revealing itself.
But that is something that we do want to do in the near future.
And then we also, of course, want to be able to set flags down.
That's another feature that was requested.
And we have plenty of time left to do both of those things.
But now, notice that, anywhere we want to transition
from one state to another state, I can just very easily
say gStateMachine change to game-over, and that
will perform that logic for us.
So, in the game-over state--
right?
Let's go over back to game over.
And I apologize.
I'll keep up with the chat, here, in just a second.
Once this is going to the game over, I'll read over all the comments
that I missed--
which it looks like I missed a few.
So, function GameOver State--
let's see-- update and render.
So, often, it'll be the case that you don't really need the Exit
and Enter but you do need the Update and the Render, almost always.
And so that's what we're doing, here.
And in the Render I'm going to basically copy what I had
in the title state, which is here.
Instead of saying "Minesweeper," it's going to say "Game Over."
Whoops.
"Game Over."
And then, here, I'm going to say, if love.keyboard.wasPressed--
well, rather, I can just do the exact same thing
that I did over here, also here.
Notice that there's quite a lot of duplication of code,
here, so you could very easily make this,
like, a message state that lets you go to the game.
Because both of these are the exact same.
The only thing that's different is that this string is different,
so you could just pass in this string as a Enter parameter, to that state.
But, because we're only doing it for two different ones,
and because often it would be the case that your game-over, your title,
and your victory would all kind of have pretty different messages,
we're not going to do that.
We're just going to keep it like this.
I was just thinking about game over.
We could essentially copy the game grid over to game over but just
get rid of the input.
And then output a game-over message.
We might be able to do that.
That might actually save us a bit of work.
So we'll take a look at doing that.
Or we'll talk about passing.
That'll give us a nice demonstration on passing data
back and forth between states, as well-- large data, even,
which would be our grid, in this case.
This is not large, per se, but large enough.
Let's go ahead and run this.
So hopefully I click on a bomb.
So this is definitely a bomb.
And then I clicked on it, game over, press Enter to play.
We press Enter-- brand-new board.
It works great.
Right?
So now we have our game loop.
We have the ability to lose.
We have the ability to click.
We get a new board when we do lose.
Everything flows really nicely.
Everything's very modular.
This is what the play state looks like-- very clean, only 20 lines of code
for our play state.
Because, remember, we deferred all of the logic to our grid.
Our grid actually has all of the logic for doing the recursion,
for highlighting-- all that stuff.
Right?
That's all taken care of by the grid, in its Update function.
And so the actual state logic, the actual transitions between whether
we're at the title, whether we're playing, whether it's a game over,
and whether it's a victory-- which we'll come to--
and we need to calculate whether we've gotten to victory, as well--
all that part of our game is very clean, very isolated from,
for example, the logic of how do we detect whether or not
I've clicked on a tile and whether I'm highlighting a tile
and doing the recursion-- all that stuff.
So, everything's great.
OK.
Let's take a look.
What did I miss, here?
"So what's the benefit of using a state machine over an architecture
such as MVC," says [INAUDIBLE].
MVC is a bit different.
So you could think of a state machine as kind
of being a potential layer on top of MVC.
MVC-- games are already kind of like MVC.
I mean, you have entities that get rendered, every frame.
And they get updated, every frame, which would be your controller, right?
But, with games, you have to go a step beyond that
and you transition between different layouts, different scenes,
different modes of presentation.
And so games are almost like, if you took MVC and added
an extra, extra layer on top of that.
So I kind of don't equate them to be the exact same.
I don't equate them to be, you know, apples to apples.
It's a pretty different use case.
[INAUDIBLE]
[INAUDIBLE]---- CS50 [INAUDIBLE] [LAUGH] doesn't even know what it is.
It's our Intro to CS, here at Harvard University.
So we're actually streaming live from Harvard, right now.
And David Malan teaches CS50.
It's an awesome course.
I've been working for David for a long time.
But somebody kind of linked [INAUDIBLE] linked in the chat--
CS50.edx.org.
Definitely check that out.
Check out our YouTube channel.
We do a lot of videos here.
David helps teach a lot of topics with me.
So, yeah, CS50 is great course, in my opinion.
Really like the culture and the style of presentation.
A lot of the folks, I think in here are familiar with it,
but we surely probably will attract people that haven't seen CS50 yet.
So definitely check out our resources, our assets.
We have a lot of content up.
Yeah, and to Martin's point, we also have other classes.
I teach a games course, which actually just went live today.
Our spring version, here at the Extension School, went live.
Brian Yu is a teaching fellow here.
He teaches the web course, a CS50 follow-on web course.
And then Jordan Hayashi taught a React Native course,
which went into JavaScript and React, React Native.
It was a good course.
[INAUDIBLE]---- "This setup of reminds me of the YouTuber code parade.
Feels good, man."
I actually haven't seen that channel.
I should take a look at it.
I've seen a couple of coding channels, here and there,
mostly VODs that were on YouTube, but I haven't actually
paid attention to too many streams.
But that's awesome.
Based on what you said, there, it seems like a good channel,
so I'll take that as a compliment.
HippoDefense says "Love" [INAUDIBLE].
I like that name.
"Nothing like that.
This game happened to be a game that I was better at than any other game,"
says [LAUGH] [INAUDIBLE].
"I can't help to ignore the evil villain face starring in a comedy movie brought
by the haircut."
[LAUGH] Uh-- FeelsAmazingMan--
"You should be an actor, man.
[INAUDIBLE] watch you anytime, bro."
[LAUGH] Oh, man.
I, uh-- if that is--
FeelsAmazingMan's not a name of a--
oh, is that-- is that, like, a private emote-- the FeelsAmazingMan?
But, yeah, I appreciated it.
It did sound-- overall, that did sound like a compliment.
So thank you very much.
All right.
Um, what do we want to do, here?
"Emoticons by add-ons."
Yeah, OK.
That's what it looked like.
I wasn't 100% sure I wasn't 100% sure.
Thank you.
Thank you for the compliment.
Even though I've been described as an evil villain--
hey, you know, it's a role that we need in movies, you know?
We have to have good versus evil, light versus darkness.
You know, someone's got to do it.
Someone's got to take one for the team.
All right-- next step.
What's the next step?
We have the game loseable.
So I guess the next step would be, figure out whether we've won
and go to a victory.
And that's going to be kind of tricky, just
because I'm terrible at Minesweeper.
So I guess cheat codes [LAUGH] would be important, here.
[INAUDIBLE] says "That's exactly what a villain would say."
I guess it kind of is, isn't it?
The victory state.
It's an important part of the game.
We should implement that, so let's do that.
So-- victory state--
[LAUGH] I think it's going to be identical to the other ones,
to be honest, the game-over, victory--
what was it?
Victory-- where are you at?
Nope, [INAUDIBLE] title state.
I'm just going to copy both of these functions--
function, VictoryState, update, and VictoryState render.
All right.
And then let's go ahead and copy these and do that.
So it's the exact same state as we've used for the game over.
We used it for the title.
We're going to change the text-- again, only thing different amongst these
is the string.
So if you see something like this in real code,
this is a great opportunity to kind of make it more abstract,
make, like, a kind of a base class that just takes in a string that does this
and you can change the message.
That would be a lot more effective.
But, in this case, we're going to take the easy way out.
We're not going to do that.
But you would want to do that for a more complicated game.
And actually the victory state is going to be a little bit
different, because it's going to include our score.
And, I think, so will the game-over state.
So we'll do that.
Oh, another thing we have to do is we have
to actually add a timer to the game.
We haven't done that yet.
So that's something to definitely take into consideration.
So, "You win!"-- we're going to do that.
And then we need to actually test to see whether we won, which
is going to be a little bit trickier.
So I can just write a function that says "function"--
um--
So what I'm going to do, first, is I'm going to say "reveal all."
So, GameGrid, reveal all.
What this is going to do is just going to go over the entire grid
and just reveal every single tile.
So I can say, for y is equal to 1, until self.height do--
um-- for x is equal to 1 self.width do--
and then self, dot--
is it grid, or is it tile? --self.grid, right?
self.grid at y x, dot, is hidden, is equal to False.
And then, to test that this is working, as soon as we enter--
as soon as we--
um-- how do I do this?
I guess I can do this as soon as we instantiate it.
That's fine.
So I'm going to say calculate numbers, and then I'm
going to say self revealAll.
So we're going to start off the game--
completely reveal.
That's totally fine.
So now we can see the board.
We can see that it's working.
Everything's great.
This is, like, kind of the debugging or the cheat codes,
I guess you could envision.
And if I click on the 2, that's fine.
If I click on the bomb, it does let me at least test the game over,
which is cool.
It's not going to test whether or not we have a victory.
And so what I think we should do is, we need a separate function that basically
says, have we won?
So-- isVictory.
Right?
And, in order to do this, we basically need to iterate over the entire grid
and check to see if we have any hidden tiles that are not bombs.
If we have any hidden tiles that are not bombs, then we have not won yet.
But if all of our hidden tiles are bombs, then we have won.
So it's kind of the same thing.
So what I can do is I can assume, at the very beginning,
I can assume that we've won.
I'll say, yep, we won.
That's true.
And then I'm going to do what I did before--
y is 1, self.height, do, x is 1, self.width, do.
And then if self.grid at y x, dot, isHidden--
right?
If it's hidden, and not self.grid why, dot, x, dot, isBomb, then won is False.
We haven't won, because we've found a hidden tile that's not a bomb.
Right?
That was the one thing we needed to check for.
And so, at the very end of all this, I can just return won.
I can return whether we won or not.
And, on click, which we have down here, uh, I've lost track of where I am.
Where is the click?
It's in update, here, right?
Cool.
We'll do an else, here, for the isBomb.
So else if self, dot-- or-- else self--
what do I name the function?
isVictory?
Yep, isVictory.
Right?
So, if self isVictory, then gStateMachine change to Victory.
Right?
So this will only trigger when we click.
So actually we can still use cheat codes and test that this is working.
Right?
So everything is revealed, completely revealed.
So, if I do this, we won!
We revealed everything.
Right?
That was easy.
Press Enter to play, brand-new board, et cetera, et cetera.
And now, if we actually have the game not cheating from the very beginning--
so I'll comment this revealAll out, and I run it, and then I click on--
Uh, I clicked [LAUGH] on a bomb, on the first one!
That's not good.
There we go.
So now we can clearly see that the game functions.
It doesn't take us to the victory right away,
but, if we click on the board, when everything is already
revealed that's not a bomb, it was glitched, before,
just because those were--
it reveals every tile.
But normally those would have been unrevealed.
Then we win, essentially.
Right?
That's the criterion for winning.
So that was easy.
Let me, just to make sure that I'm not missing any chat, here.
"How can we use Unity on Ubuntu or some Linux distro?"
Good question!
So, "unity ubuntu."
"How to install Unity on Ubuntu 18.4 and 17.10."
So I'm not super-familiar with unity, but it
says that they switched to Gnome, which I'm guessing is a windowing program.
And according to this article, it's been around
for at least six years on Ubuntu.
So, yeah, I would--
it looks like there's videos and resources.
I would definitely check those out and see.
I haven't installed it manually, myself, so I'm not 100% sure.
If you're using a desktop version of Ubuntu with a windowing system,
it should be a window manager.
It looks like it's probably pretty straightforward.
"I've just finished learning C. The next step is learning a graphics library,"
says [INAUDIBLE].
"Apparently SDL is a good choice."
Yeah.
SDL's a good choice.
If you're looking to get into game programming in C,
I would say probably the best choice, in terms of, like,
market share in the C gaming industry--
I mean, there's a lot of engines.
There's a lot of frameworks.
SFML, which sort of sounds like an inappropriate acronym,
but SFML is a really awesome C++ graphical game-development,
multimedia--
I mean, it's Simple and Fast Multimedia Library, I believe, is the acronym.
It literally says it right there.
[LAUGH]
But it's designed to be used for pretty much anything
that you can think of that would use graphics, sound, input--
any of those sorts of things.
It wraps OpenGL.
You can do a lot of great stuff with it.
I would probably recommend using it.
And it has bindings for C, as well.
But SDL couldn't hurt you to use.
SDL is just a little bit weirder.
It's a little more arcane, a little more low-level
in certain ways that aren't appealing.
SFML kind of abstracts a little bit of that out,
to make it more manageable to do C and C++ game development.
Still quite a bit trickier than getting into something like Love,
which handles a lot of tedium for you.
Memory management and data structures in C and C++ isn't fun at all.
So I would probably recommend sticking to Love until you really need to use C
or C++.
But it's a valuable skill to have, I suppose.
But, yeah, that's my two cents.
It's a very opinionated landscape.
So just kind of try different things out and see what you enjoy.
Oh, that's my dad.
Everybody remembers.
"Hello, everyone.
Just checking in for a short visit."
Good to see you, Dad.
Thanks for popping in.
Why isn't it unity3d.com," says [INAUDIBLE]..
Are you referring to the Linux version?
Oh, that's interesting.
I'm not sure why they don't have unity--
Linux, kind of super-easily--
[INAUDIBLE] asking me for my location.
Dunno why they have it at the very--
Oh, where is this?
Get Started.
I also should probably look at the newest version.
Personal, Try Personal, OK, Install it for--
OK.
Oh, yeah, [LAUGH] you're right!
They just have Mac OS and Windows, on their front page.
Yeah, I'm not sure, but it seems like they do make it usable for Linux.
So I would look at the video and the article we just looked at together
and see what's going on.
Everyone's chatting on my dad.
Everyone's super, super-awesome.
All right, cool.
So we have--
And then, just to demonstrate, again, we have Minesweeper.
Press Enter to play.
You click on a thing.
I clicked on a [LAUGH] bomb.
Click on that.
You reveal the entire grid.
Everything looks great.
You can see that this is probably a bomb.
Well, it's most certainly a bomb, because all of these are 1s.
They say that, for sure--
So there's only one bomb neighboring all these tiles,
and there's only one unrevealed tile.
It's probably that.
Same with this one.
These [LAUGH] are a little bit trickier, I would say.
Oh, I got lucky, there.
Ooh, I got really lucky, there.
Um--
I'm not sure.
It could be this one or this one, for both of these.
That's a good question.
Hoo!
Oh, man.
OK, well, OK, so we know that these two belong to this one.
Right?
And, because of that, I can click on this one, safely.
Oh, but it sucks when you reveal a 2.
OK.
Yeah, that would be the end of this particular iteration.
Judy1984-- "Whilst we all love Colton, we also wish to hail in the Malan."
I'm not sure where he is!
Hopefully he pops by, at some point.
You know what?
On stream, I'm going to text him to pop by.
This is on stream, for the record.
Peer pressure.
All right, I just texted him.
We'll see if he pops in-- see if we get a shout-out from--
we'll see if [LAUGH] we can hail in the Malan.
Anyway.
Let's go ahead and do the next feature, which was, I think, the victory--
not victory-- game over, or showing-- oh, actually, no, we do a timer.
Right?
So we have the game, but we're not locked into any time limit.
We just have 120 at the very top left.
And also I can probably get rid of all that debug output.
It's not really that compelling to look at.
So I'm going to do that, actually, too.
So let's get rid of all this, all this debug output.
Don't need it.
Run the game.
Looks nice and clean-- super-simple.
Let's go ahead, now, and talk about what we need to do to look at the game over.
What I want to do is, per what [INAUDIBLE] said earlier,
I want to transition us to a view of the game board revealed completely.
And that's kind of why I wrote the reveal function, over in game grid,
because I wanted to be able to just call that right away, once we transition
to the game-over state, and not have to do any kind of iteration and copy
and paste anything.
So what we can do--
and this will illustrate the state machine a little bit--
is, if we get a game over, what I'm going to do
is over in the-- it's in the game-grid class, down here.
So this is where it happens.
Now, the nice thing about the state machine that we have
is that I can actually pass in a table, to change--
the Change function.
And this table is going to let me pass in any data I want,
in order to be accessible by the class that is--
sorry, the state that we're transitioning to.
So I can pass her in something that I want game over to have a reference to,
the game-over screen, the game-over state.
And what I wanted to do is have a reference
to-- well, a couple of things, ultimately, but, first and foremost,
the game grid.
So what I'll do is I'll say, gameGrid equals gameGrid.
And so what this is going to do is, I'm creating a table which has the key game
grid and the value of our actual game grid-- in this case,
it needs to be self.gameGrid.
Right?
So the game grid that belongs to this game grid-- um--
[INAUDIBLE] not self.gameGrid, uh, self--
no, just self.
Right?
Yeah.
Sorry.
Just self.
We're just passing, in this game grid, this actual game
grid that's calling this function.
Because self, in this case, is the game grid itself.
Right?
Because that's where we're calling it.
We're calling this from within the game grid's own update function,
so self is this game grid.
So gameGrid is going to be equal to self.
So we're going to have a reference to the whole grid,
when we're in the game-over state.
And, before I do that, I'm actually going to call self.revealAll.
Right?
And then, after that, I'm going to go over to game over.
And this is where we want to use that Enter
function that I talked about before.
So, function GameOverState enter params.
And these params are what we just looked at, in this case.
So, game grid--
So params is this table, right here.
In this case, it's going to have just a single key in a single value--
gameGrid, which maps to the game-grid object that we're looking at.
So I can say, self.gameGrid is going to be equal to params.gameGrid.
Right?
And then, once that's finished, we don't want to update anything,
but what I do want to do is call self.gameGrid render.
And now, what that is going to do is call the same rendering code
that we used in our play state, but now, when we go to the game-over state,
it's going to actually show our completely revealed game
grid that we just lost on, so that the user can see where they clicked
and where all the bombs were, where all the numbers were, just
as some feedback.
Right?
So, if I-- and again, Enter itself, if you're confused on where that gets
called--
If we go back to our state-machine class, here,
when we call Change notice that we do self.currentState
enter on this current state object.
Remember, the new-- in this case, the new game--
in this case, it would be the game-over state-- right?
The new game-over state object?
If we go back to main, right here.
All of these are-- remember, these are the state objects
that get returned from self.states.
So, if you go back to--
where were we?
We were just in the state-machine class.
So currentState is going to be equal to a brand-new, anonymous function's
return value of that either a game-over state
or a victory state or a play state, depending
on where we're calling state machine.
In this case, we're calling change on the game-over state.
So we're going to get a new game-over state.
We're going to pass in-- because, remember, we took in the params
as that table, that second parameter.
We're going to call enter, pass in those params.
And then, in game-over state, we therefore have access to those params,
here, which we can then--
that affords us the ability to take the game grid essentially
from our game, our play state and include
that in a completely separate state.
So we can transfer whatever data we want,
while keeping everything nicely decoupled and organized.
So let's test, to make sure this is actually working.
And I'm actually going to do this before the output,
just so that our text doesn't get obscured.
See if I can lose.
Oh!
I messed up, there.
OK, gameGrid on line 38.
Um, where is 38?
Oh, do we not have self.height and self, dot-- well,
we do have self.height and self.width.
Hold on a second.
Self.height, self.width?
Huh!
What is going on?
OK.
So it looks like it's when we lose.
OK, it's when we lose.
And let's look at our stack trace, here.
So, play state going 149.
Oh, in the Reveal All function.
Game grid, line 38, in Reveal All.
Why is that-- interesting.
OK.
And where were we calling revealAll?
Did I mess something up there?
In revealAll-- so, on line 149--
we're calling revealAll.
OK.
Let's go to 149.
Oh, I called it [LAUGH] self.revealAll.
That is a big nuance problem, in Lua, is the difference between dot and colon.
Dot essentially doesn't pass anything into revealAll
it's an object-oriented, weird thing with Lua.
Essentially, if you type in colon, it's effectively
the same thing as calling revealAll and passing self in as a first parameter,
so that the function has access, implicit access, to self.
It does that sort of behind the scenes with colon,
but if you don't do it with the period then you
have to explicitly type-- you have to basically
pass in self as an explicit parameter, to have access
to that object it's a little bit weird.
It's a prototype inheritance model that has a lot of weird quirks
to it, very similar to JavaScript.
But now if we click on a bomb, somewhere,
notice that we do indeed have that functionality,
where we're in the game-over state that we just looked at--
right?
That's all the same.
But we have our full grid in the background.
Now, I would say that the text going over the grid is pretty ugly.
So we're going to fix that, by changing the text offsets, here.
So I'm just going to--
where do I have--
in the game-over state is where I'm going to change that.
So this will be at 0, and then we'll say 16.
And then this will be at virtual height minus 32.
And let's try it one more time.
That looks better.
It's not ideal.
The offset, the grid offset, could be a little lower, but that's the gist of--
now we can actually see where all the tiles were.
And it looks nice.
It looks-- it's not cluttered with text and our visuals.
Ideally, you would style that a little bit better and probably use
a better font, to begin with, and more interesting background.
And there is an argument to be made about whether we should
do love.graphics.clear to maybe some other color, like maybe a really
pale shade of red, for example.
Let's try something like that, maybe.
Yeah.
So, just a little bit more of a visual feedback, to tell you, oh, you know,
you lost.
Like, red's a bad color-- like, not good that you lost, right?
And then, in our victory state, we could do the opposite of that.
We could actually call love.graphics.clear,
to maybe something like green.
We could say RGB--
like that.
And then I'm going to start off, right away, as everything revealed,
so we can test victory again.
So I'm going to go into gameGrid, just come
in and start with everything revealed, just to make it nice and easy.
We'll test that.
Press Enter to play.
Click on anything.
"You win!
Press Enter to Play."
It's kind of an iffy background, so I'm probably
going to change that a little bit-- to 0.2, maybe, 0.3.
And then we have to style this just like we did the other things.
So I'm going to say 16 and then virtual height minus 32, just like that.
Click a tile.
Oh!
And an important thing-- we're not actually passing in the board
to our victory state--
which is-- that's the only way we can actually
get it to render on the screen.
Right?
So we should probably do that, too, just like we did with the game-over state.
We should call--
We should pass in, here, our game grid, as self.
Right?
And then, remember, we have to actually get access to this.
We have to define Enter, so that we can extract that data from change
and then store it and use it for our victory-state functions.
So I can say VictoryState, enter params.
And then this will be self.gameGrid is equal to params.gameGrid.
And then, did I--
I did that correctly in the other one, right?
Yep, I did that correctly in the other one.
Now I can say self.gameGrid render, just like that.
Boom.
Enter, click, [LAUGH] and fail.
OK.
Not what I was expecting.
Oh, because I called clear before--
or, after-- I called clear after I called the game grid to render.
Which, it'll just wipe the whole screen with that color.
Not ideal.
Cool!
So that looks great!
[LAUGH] My dad says "Blood on the screen?"
Yeah, that's kind of what it looks like.
JL97-- "hey, by the way, did you get a haircut?"
[SIGH] Man, I did not.
Still haven't gotten a haircut.
I'm a little ashamed, to be quite honest with you.
HippoDefense says "Are states in Lua like classes in Java?"
So, no, a state isn't a Lua-specific construct.
A state is just an abstraction, over separating our game
into multiple sort of domains.
Right?
We have the title of the game, we have the play state of the game,
we have the game-over part of the game, the victory part of the game.
These are all different things.
They all, in most games, typically look different, act differently.
If you wanted to do this all in, like, one monolithic area,
with IF statements that say, if we're at the title, if we're
playing the game, if we won, if we-- you know,
whatever-- it would get to be monstrously
long and convoluted and hard to work on as a team, especially.
And so a state is just something that we invented
that's kind of like a higher level than anything within Lua itself.
The actual thing that's most similar to Java classes
would be the class that we are using as an actual library.
So lib, and then class.lua.
And so that is something that we covered in part 1.
If you go back to part 1 of the stream, we actually
covered how we got that, how we implemented that.
Lua doesn't actually have, like, Java-style classes.
It has JavaScript-style prototypes, and you can do much of the same stuff
that you can in JavaScript, but in Lua it's a little bit weird and weighty.
And people made a library that lets you just
use it kind of like you would use classes in something like Java.
The syntax is very similar.
And you just have to basically use only colons
for all of your manipulation, all of your function calls for everything.
And not using dot is preferable, because it gets a little bit bulky.
Passing in self as an explicit parameter isn't particularly fun or interesting.
So, yeah, the ability to just basically say, this game grid is a class,
and then define its constructor and then all of its functions.
They're all colon functions.
They all expect an implicit self.
And so, if you don't know much about what this is,
go on the Lua programming manual and look up
the difference between dot and colon.
You can look up self, you can look up tables,
how tables let you do this, and meta tables, and all that stuff.
It's not very interesting.
You don't need to really remember it.
You really only, really, to do good development in Lua and Love2D,
you kind of just need this, and then you can use it like you would use Java
or Python or any other language with what's called "classical-style"
object-oriented programming.
[INAUDIBLE] says "That's a nice explanation.
I was getting a bit confused."
Oh, I'm glad-- if anybody wants clarification, definitely let me know.
[INAUDIBLE] says "Hey, guys."
Hello, [INAUDIBLE].
Good to have you with us.
Cool!
So we have visual feedback, now, on our states.
It's not pretty.
It's not beautiful.
That's not our goal, though.
Our goal's not to be pretty.
I mean, our game grid itself is actually pretty nice.
Right?
Like, this looks pretty good.
And, last stream, if you want a nice crash course
in making excellent, excellent pixel art,
you can definitely check out what we did.
I hand-drew all of these tiles, the 1, all the way up through 8, and then
the bomb.
And that was a great time.
Now, another big feature that was requested
is the ability to actually have flags that you can mark individual tiles for,
so that I could say, I don't want to click on this tile.
I don't want to reveal it.
I want to set this to be a flag.
Right?
And we can do this fairly easily.
Let's go ahead and see how I might do that.
So a couple of things are going to need to happen.
So we need to go probably add some stuff to GridTile.
So I'm going to open up GridTile.
And then I can say self.isflagged is equal to False.
And let me just make sure this is good.
Cool.
Now, this actually-- we could do this actually
in a fairly straightforward way.
I can say, down where we click, where we actually do the clicking on our tile,
to reveal it, I can say--
you know, because this is checking to see if the mouse pressed was 1.
But I can say, if--
or, rather, I should do this in an elseif, so we don't do them both.
Whoops!
I can say, elseif love.mouse.waspressed--
was that how I wrote it?
Yep.
--waspressed 2, we'll assume that right-click is to place the flag.
And I'm [LAUGH] actually going to have to draw the flag, which
is going to be fantastic.
Can't wait for that.
But what we can do is we can say, self.grid at y x,
dot, isflagged is true.
And, now, what I basically want to do is say if love.mouse.waspressed 1 and not
self.grid y x, dot, isflagged--
right?
Because, if it's flagged, I basically want to just ignore it.
I want to ignore the left-click.
Because that's the whole point of the flagging,
is to make sure that I don't accidentally click a bomb.
So, by right-clicking it, by flagging it,
I'm not going to even be able to do that.
I'm going to completely ignore all of the logic in here
that checks to see if it's a bomb, reveals it,
calls the recursive reveal algorithm.
All that's going to be gone.
All that's going be bypassed.
And, in here, what I need to do is say--
actually, no, even better-- even better, we'll toggle.
We'll say, self.grid at y x, dot, isflagged equals not self.grid at y
x, dot, isflagged.
Right?
So now I'm just calling a NOT operation on that variable.
So it's going to toggle on or off.
So this will allow me to just use right-click to go back and forth, if I
want to unflag something or whatever.
Completely have that option.
Now, in the actual GridTile--
oh, and another thing.
Another thing we do have to keep in mind is
we have to make sure that it's not revealed.
So, if love.mouse.waspressed 2 and not self.grid y x, dot, ishidden--
or, rather, and self.grid y x ishidden--
We want to only be able to flag hidden tiles.
It doesn't make sense for us to flag anything else [LAUGH],,
because bombs can't be revealed, ever.
So we're not going to let that happen.
We're just going to say, make sure that, if we're checking for the right-click,
that we're also checking to make sure that that tile is hidden.
OK?
So, super-easy.
The next thing is that we're going to actually have to make it
so that the flags draw onto the screen.
Which is very important.
We'll do that in GridTile, because GridTile
manages all of its own rendering.
Right?
It draws the bomb, if it is a bomb.
If it's hidden, it'll just draw the tile.
If self.hidden is that, and then if self.isflagged--
so now I need to actually draw the flag texture, which is right here.
And I don't have a flag texture yet, so this
is a great opportunity for us to go back to our lovely, lovely ASprite program.
And let's pull up, I guess, bomb.png.
We'll just zoom in nice and big, there, so you can see.
So my dad's actually pretty good at Photoshop,
so he's going to probably cringe, seeing me working in a 2D drawing application.
But this is how we do excellent, excellent sprite work.
So I'm [LAUGH] going to just kind of erase all of this.
And I don't know what the flag looks like, in regular Minesweeper,
so I'm going to kind of draw whatever I want, I guess.
Let's go ahead.
I'm going to load up a palette.
I'm going to load up--
I really am a huge fan of the DB32 palette, DawnBringer 32,
so I'm going to load that.
And then get rid of that.
And then I'm just going to choose maybe this red color.
So we'll say something like this.
Uh-- I guess that's OK?
Maybe we'll do, like, a dash of pink, there, just to be cute.
And then we'll do some thing like that.
And then maybe a little bit of green, just right there, like that.
What do we think?
Think that's acceptable?
That's a flag?
It's not amazing, but it'll work.
Right?
So we'll call this "flag.png."
Save it.
Go back here.
And, when we load it, we should be able to just right-click on things.
And I think I can right-click with my trackpad, here.
Another thing that we want to do, and a thing to take into consideration,
is we want to get rid of the flagging of a tile if we click on it
and it's not a bomb.
So I can say, um--
And now, here's an interesting thing.
If we flag a tile that's not a bomb--
[LAUGH] Dad says "Perfect."
If we flag a tile that's not a bomb, and we reveal that through recursion,
I guess we want to just get rid of it.
Yeah, I guess that makes sense.
We don't want false flags anywhere.
So, yeah, I think that's what we're going to do.
We're just going to say tile.hidden is equal to False,
and tile.isflagged is equal to False.
So now, at least if it ever was flagged, it won't be flagged eventually.
Right?
Let's try and run this and see if I got it right.
[LAUGH] Everything's set to reveal.
OK, that's not right.
Let's go ahead and unreveal everything, so we actually have a game to play.
I'm going to try right-clicking this.
Ooh!
Oh, I didn't load it.
I didn't load it.
That's an important step.
So, dependencies.lua-- come down here, into our textures table.
I'm going to say, flag is equal to love.graphics.newimage,
graphics/flag.png.
Do that.
Rerun it.
Center.
Nice!
Oh, but you know what I did?
Foolishly, I drew over the depressed tile.
So [LAUGH] that's the wrong tile.
It's not what I wanted to do.
So let's try that again.
Open Recent.
Go all the way up to--
where was it?
was it o.png?
Oh, no, that's the, um--
wait, where was--
Where was the empty tile?
I thought I had an empty-tile one.
Really?
Well, I can just open it up in, uh--
what did I call it?
Tile, dot-- it's not--
OK, interesting.
Let's open it with ASprite.
Let's zoom in.
We get more practice drawing flags.
Here we go.
So I'm going to--
I mean, I guess I could just copy it, but [INAUDIBLE] ah, it's fine.
Uh--
Cool.
Let's see if that works--
see if I did a better job, this time.
Yes.
Nice.
OK.
That's the correct style.
Dad says "That flag looks great."
Thank you very much for your kindness.
I appreciate it.
And now, if I try to click on it, no matter
how many times I try to click on it, it won't let me click on it.
Now, if I unflag it and I click, it works.
So I think it was [INAUDIBLE] who suggested
it there is your feature, [INAUDIBLE].
We have flags, indeed, in our game.
Pretty exciting!
Now, I know that's definitely a bomb, 100%.
So is that one.
So is-- uh, is it?
Uh, this might not be a bomb.
Actually, it's definitely not a bomb.
Yeah.
It can't be.
This is a bomb, because this 2 is right here,
and these are the only two adjacent tiles.
So these two are bombs, for sure.
And, because this one is touching this one, this is not a bomb.
This is also not a bomb.
Pretty sweet!
It's pretty sweet.
"So all bombs flagged is equal to victory?
Did we do this functionality?"
Yeah, we already implemented the loop that goes over--
it's a little bit hard to test organically,
but we implemented it such that, when you click and there are no tiles that
are hidden that aren't bombs--
so, any number tiles, any clear tiles--
you'll go to the victory state.
So we already tested that.
Cool, cool.
"Do you plan on doing a lecture on procedural development,
sometime in the future?"
Procedural development-- um, [INAUDIBLE],,
can you elaborate on what you mean by that?
If you mean, like, procedural programming in C?
Or is there another methodology that you're alluding to?
"Please play to the end once," says [INAUDIBLE]..
We'll be here all day, if I try to do that.
[INAUDIBLE]---- we should get rid of the hover sprite on buttons that can't be
pressed anymore."
Um-- that wouldn't be too hard, actually.
And I think you might be right on that.
I think we might be better off doing that.
Let's try that.
So we do that over here, right?
Um-- hmm-hmm-hmm-hmm-- if self.grid at y x, dot, is--
um-- ishidden-- rather, if not self.grid y x, dot, ishidden, then--
um-- mmm--
Is this correct?
If not self.grid y x, dot, ishidden, then it's not hidden.
We want to highlight it.
No, we want to highlight only if it's hidden.
So, will this work?
No, it's still doing that.
Self.ishighlighting is equal to True.
Oh, and we've got to do else self.ishighlighting is equal to False.
Cool.
There you go.
Easy feature, implemented.
Procedural generation-- oh, procedural generation!
Not procedural development.
I love procedural generation.
In my Mario pset in my games course, we actually
cover procedural generation for game levels.
It's pretty cool.
For Unity, I'd have to think about what would be fun to illustrate it with.
There's a lot of really good tutorials, actually,
online that go over procedural generation.
Catlike Coding has a few, so check out Catlike Coding
for some procedural generation in Unity.
Super-excellent website.
I'll actually recommend that right now.
So, catlikecoding.com Check that out for-- they
have a series on tile maps, procedure-generated tile maps,
like Civilization style, but 3D, in Unity.
So that's a great way to get a handle on it.
I haven't gone through all of them.
It's a massive amount of material, and it's very intricate, very deep.
But if you're looking to get good--
If you go through Catlike Coding and you can do all of it,
you'll be really good by the end of that.
Promise you.
[LAUGH] "Maybe a super-secret stream of" me failing at Minesweeper?
I don't know if people want to be subjected to that kind of torture
for that long.
We're actually almost done.
So this is great.
We have a pretty robust game, here.
Now, the only thing that's different, the only thing
that we need to worry about, is that the timer needs to get implemented.
And so, in order to do that, what we should do is probably go over here.
Hmm.
Well, this is functionality that we can sort of use at the state level.
So I can say, so this will be in our update.
So this is important.
So, first of all, the most important thing we need to do
is actually include our timer class.
So I'm going to do that.
I'm going to say timer equals require lib/knife.timer.
And I don't think I have timer or knife in here at all,
but Space Invaders has it, I believe.
No?
Space Invaders does not have it!
I think Typing Game might have it.
Yeah, there we go.
Typing Game.
I have the entire knife library.
If you're unfamiliar with the knife library, super-great library for Love.
Just type "knife library love2d."
And it is Airstruck who created it.
So Airstruck knife on GitHub.
Load this page, here.
Yeah.
So, github.com/airstruck/knife.
And it's got a lot of great micro modules, as they describe it.
There's a lot of great ones.
But what I really enjoy are the timer and the event classes.
And also chain's pretty cool, too.
But we're going to use just timer.
So, actually, I didn't copy it yet.
So I'm going to go back into Minesweeper, into lib-- copy that.
So I have the whole knife library in there.
And that's why I'm saying "knife," dot, "timer,"
because "dot" is sort of a way, when you're acquiring a library,
so you can go into a directory and get a module.
So, lib/knife.timer.
And then I'm going to-- in my main.lua, in update, I'm going to say,
timer.update delta time--
Takes in the dot, because it's a global object.
It's not an instantiated object.
It's not a part of a class.
It's just an actual, one entity, one timer variable.
Dot, update, DT.
And then, if we go into, for example, play state, the play state--
we currently are just drawing this 120 as a constant string, which
isn't helpful.
And so what I want to do--
Also, one thing that we didn't do is include a score, actually,
which is interesting.
So we should probably do that, as well.
But what I'm going to do is I'm going to say "tostring self.time."
And then that's actually sufficient.
Then what I can do from there is I can say self.time is equal to 0.
And here's the cool thing about knife.
I can say "timer"--
or, sorry, time, not knife--
timer, the submodule of knife.
I can say, timer, dot, every 1 second, call this anonymous block of code.
I can say self.time equals self.time minus 1, if self.time is equal to 0,
I want to gStateMachine change to the game-over state,
passing in our game grid.
Remember?
Like so.
So we're going to not only have the ability
to go to game over from clicking on a bomb
but also by letting the timer run out.
And so, if I test this, if we run, we'll see that we
start at [LAUGH] 0, which is not great.
We want to start at 120.
So let's try that again.
So now we're starting at 120 to 119, 118.
And I notice it actually didn't even transition to, uh, to the, um--
oh, because it called this function after it had already been 0.
So it wouldn't work.
Now let's try setting it to 5, just so we
can test to make sure that it does indeed work when it gets to 0.
So 3, 2, 1, and 0-- game over.
Cool!
It didn't reveal the board, though.
That's important.
So let's make sure we do that, too.
Um-- we'll do self.gameGrid revealAll.
Which, actually, that should be probably part
of the game-over transition, itself, [INAUDIBLE]
when it goes into game over, it will reveal the board.
But there you have it.
That's the game-over state, using a timer, now.
So now you have the element of the clock also kind of keeping you,
I guess, nervous, uncomfortable, in a rush.
Now, this is another thing that we need-- tostring score.
And we're going to actually need to make this part of the game grid,
because we're doing all of the score--
we're going to be doing all of the actual score modification
inside of the gameGrid class itself.
So it makes sense to keep the score within the grid.
Which is a little bit weird, but it kind of makes sense.
And then we can reference the string version of this
from anywhere in here, you know, once we've used it.
Now, we have to make sure that we actually defined it.
Whoops-- didn't mean to do that.
So it'll be just part of our initialization.
So self.score is equal to 0.
And I don't know if there is a special way that you score Minesweeper,
but we'll just say that every tile that you reveal
is going to be worth five points.
Right?
So we'll say, over here, in our revealTile function--
So, right here, where we reveal the tile,
we'll just say self.score equals self.score plus 5.
So, anytime we reveal a tile, now, we get five points.
Right?
10-- and [LAUGH] game over.
That's unfortunate.
And I actually really like the fact that, when we click the bomb,
the revealed tile stays highlighted like that.
And that's just an accident, but that's an amazing accident.
That, like, works perfectly.
And that's because we're using, if I'm not mistaken--
no, we're just highlighting it.
Yeah, I was going to say, it looked almost
like we were using the subtile, the flat subtile, but no, we're just using--
it's just the highlighted rect.
It stays active.
Which is cool.
I actually didn't realize that was going to happen.
Anyway, let's try and get a better--
oh, [LAUGH] this one has four bombs around it.
That's crazy.
Ooh, yeah, there we go--
355.
So it seems to be working.
Uh, well, is that true?
Is that really 355?
1, 2, 3, 4, 5, 6 7, 8, 9, 10-- oh, I guess, yeah, it'd be about 355.
It would add up, quite a bit.
Cool!
That is the scoring and the timer.
I don't know if I'm missing anything.
I think that's all of the features of Minesweeper, right?
Correct me if I'm wrong.
All the ones that we at least thought we were going to implement.
Let me commit everything, so that it's on GitHub,
so you can all download it and try it out.
Let me make sure I'm in the right place, which it looks like I am.
Get status.
Get add-- blah-blah-blah.
Cool.
So that's on GitHub now.
So all of you can clone it for yourself and try it out.
My DS Store little meta file is in there, unfortunately
[LAUGH] [INAUDIBLE] says "It's a feature, not an accident."
Yeah.
I would say it's kind of both, in that situation.
"I'm fine with you getting a bomb.
I just want a genuine try."
Yeah, I'll give it a genuine try, with our last bit of time.
"Timer's not required unless you want do a best time or score?"
Oh, I didn't-- OK, I thought it was a feature of Minesweeper.
I am mistaken.
Well, now you have the restriction of 120 seconds.
So hopefully you can finish it in that length of time.
"Minesweeper I looked at only calculated your time.
You didn't have a limit."
Oh, OK, that's interesting.
Some variations score according to time taken-- less time, high score,
et cetera.
Others just do best time, with no score.
Easy mode is 9-by-9 grid with 10 bombs and a fixed
score for victory, bonus for time, et cetera, but they are just variations."
Yeah.
I would say--
[LAUGH] I would say, give that a shot--
implement that on your own.
A nine-by-nine grid with 50 bombs sounds insane.
That sounds really hard.
But that'd be cool.
That'd be a cool, sort of, I guess, branching-off feature you could,
add like different difficulty levels.
Certainly you could take this codebase and run with it
and do whatever you want, add all kinds of stuff, but the core of the game
is certainly there.
And it doesn't look half bad for what it is--
you know, a Minesweeper clone that we built in a couple hours, a few hours.
You know, it looks pretty much like the real game,
and it functions identically.
Unfortunately, it seems like David might have been in a meeting or something,
so he was unable to join us.
But that's the game.
Since I'm doing so well already, on this run, I'll just take this as my run,
here.
So I know this one only is neighbor to this one, so this is a bomb, for sure.
Which means this can't be a bomb and this can't be a bomb.
This is a bomb.
Which means this can't be a bomb.
Oof.
So these, all three of these, are bombs.
OK.
Which means this isn't a bomb.
So this is a bomb.
Which means this is not a bomb.
This is a bomb.
I'm so bad at Minesweeper.
OK, how do we definitively--
OK, so this is a bomb, for sure, so that means this is not.
OK.
This is a bomb.
Which means this is not.
Hmm.
I only have 42 seconds!
OK, uh-- hmm.
This is a bomb.
Oh, this is terrible.
Oh, wait, OK, so this is a bomb, which means this is not a bomb.
[INAUDIBLE] this is not a bomb.
So this is not a bomb.
Oh, so these two aren't bombs?
OK.
Shoot.
Shoot, shoot, shoot.
Uh, OK.
How do we-- [SIGH] Crap!
Oh, did we-- oh, so it is not actually working, when we--
so I got them.
I did win, at the last second, but it looks like there's a bug.
So, for the record, I did beat it.
[LAUGH] Um, interesting.
OK.
At least, I think I beat it, unless I made a mistake.
[LAUGH] But that means that there's a bug with the actual win detection,
so we need to fix that.
So--
So the isVictory function is not correct.
OK.
I could have sworn that this was--
this is correct.
So I'm just going to leave, like, the first--
So, in the revealAll function, I'm just going to keep the first three--
but like, the first row, I'm just going to reveal the first row.
Right?
So--
But OK.
So that's a bomb.
So these are bombs.
Oh, wait, no, no, no.
That's not true.
These three are definitely bombs.
Which means this is not a bomb.
These are bombs.
Those are bombs.
OK.
All right, well, that should have definitely taken me to a--
OK, interesting.
So where is the logic bug, here?
So, OK, it's probably something very obvious that I'm just missing, here.
OK, so we're going through the entire grid,
basically saying, assuming that we've won--
so, for every single tile in the grid, if that tile is--
hmm.
Interesting.
Oh, did I have it--
did I screw that one up, the logic?
Or my play?
Because, the first time I played, I thought I had gotten every single bomb.
Right?
Am I tripping?
That's a bomb-- oh, no, no, no.
That's not a bomb.
[LAUGH] That's not a bomb.
That's not a bomb.
See, I-- yeah.
Um-- there's something very simple, here, and I'm having a brain fart.
[LAUGH] [INAUDIBLE] says "Tried my latest, and it works there."
That's weird!
That doesn't make sense.
"If the victory is called, then certainly something stays hidden,
I think."
Hmm.
Oh, wait.
We're not-- we're not checking it after the click.
That's the problem.
We're checking it before, or--
sorry, we're not checking the logic before the tile gets revealed.
We're checking it-- sorry, we're not checking it
after the title gets revealed.
We're checking it before it gets revealed.
And so that tile that we're clicking that should help us win
is actually not--
it's actually still hidden.
Uh-- yeah.
So we have to check isVictory after it gets revealed.
That's what it is.
Yeah, [LAUGH] see, that's what it is.
That's what it is, right here.
OK.
OK!
All right.
OK.
So--
We got this.
That's a bomb.
Uh-- hey!
[LAUGH] That was easy.
There we go.
MojoDojo101-- "Yo, goodnight, dude.
Thank you for streaming.
Really enjoy these.
See you, chat."
Thanks, Mojo, for popping in--
really appreciate it.
You're leaving at a good time, because we just fixed the last bug, I think,
of the repo.
Cool-- so we did it, chat.
Couldn't have done it without you.
"It was a journey that we all embarked on together."
We started with nothing and ended with Minesweeper.
It was fun.
We had good times.
Hopefully I didn't just mute myself.
No, I didn't, I don't think.
Yeah, I think I'm all right.
I'm going to stick around for just a couple questions, I think.
And then I will--
well, one thing I'll do is I'll push that change.
Right?
So, fixed victory bug.
So I'll take any questions.
[LAUGH] StayPeaceful89, oh you're about to--
[LAUGH] sorry, yeah, we're about to peace out.
But we have the VODs up, and they'll be on YouTube.
So, if you want to check it out.
The next few streams are--
so, no stream tomorrow.
On Wednesday, Kareem is streaming, where he's going to be talking about Flask.
So, if you're into back-end web development,
there is going to be a super-cool stream to tune into.
It's back-end Python library.
If you are into web development on the front end,
I'm going to be doing a CSS stream on Thursday.
And then, on Friday--
I'm super-excited for this-- we're going to actually be looking at people's code
submissions.
So there's a form, and I'm going to plug it,
in case anybody here is unfamiliar with it.
bit.ly/cs50twitchcodereview.
So David and I are going to do that one together.
We're going to take a look at some people's repos,
some GitHub repos, and gists.
And we're going to critique them for design and style.
We're not going to do any bug testing or performance testing
or anything like that.
But we're just going to look at the code itself, just the code,
and determine if there's any stylistic changes that we would make
or any stylistic things that we think are really good about your code.
So, if you're a beginner, an intermediate, or even
advanced, but probably more towards the first two,
definitely submit that link, that form.
Submit your code-- anything that you're comfortable with having
us show on stream.
Preferably not-- as we're thinking more about it--
preferably not CS50x problem sets and things like that,
just so that we don't release solutions and answers to them live, online.
But anything that you've done yourself or has
been inspired by CS50, or any code that you'd like some words of wisdom,
primarily from David, on--
who, again, unfortunately couldn't make it today--
definitely toss us some links.
We'll be happy to take a look at them.
NACLEric says "Sent mine out the other day."
Awesome.
Really shortly before I leave, "Do you have a Twitter I could follow?"
I don't use my Twitter at all, but I have
to change it to a different user name.
It's an old Twitter I had when I wanted to make music production.
So it has a music-production name.
But I think I might just rechange it to be a more personal Twitter account,
for myself.
When I do, remind me in the future and I'll plug it.
But I'm going to wait until it changes.
My handle is going to change.
I might even just make a new one.
But my handle's probably just going to change.
I appreciate your interest in doing so.
Thank you very much.
Can we tweet git not to add some file folders, not to all commit, like,
py cache, if you know at the top of the head,
like, that file you don't need to push?"
Yeah, so gitignore will do that.
So you can create a gitignore.
And what that will do, if we're back in here,
is, you can just specify whatever files you want here.
So dot, ds, store.
So, if I do a git rm, dot, ds store, and then I do git status, OK, git-- oh,
git add dot, git commit, git ignore--
now, this is how you do it.
You can you can use patterns.
You can use asterisk patterns, glob patterns,
to specify file types and naming types--
things of that nature.
If you refresh, then you can see that the ds store is indeed gone,
and now we have a dot gitignore file that replaced it.
So, yes, you can absolutely do that.
Oh, and-- [INAUDIBLE] kindly plugged the Minesweeper link, there, in the chat.
"If you could choose any of them, which game
would you wish to have created yourself?"
Probably [LAUGH] Minecraft.
Fortnite is great, it's been very successful,
but I have more of a personal attachment to Minecraft.
So I think if I had created that I'd be pretty happy with myself.
But, I mean, [LAUGH] I'd take Fortnite, too.
That'd be fine.
Fortnite's fun.
[INAUDIBLE] says "Hey, everybody, I'm late."
Oof-- we are just now winding down, Adam.
I apologize.
But I'm glad you tuned in, nonetheless.
Definitely check the vod out-- watch the Minesweeper part 1, part 2.
It was actually-- it was pretty good.
I'm happy with how this turned out.
It ended up looking pretty nice.
"This really was a great game we did."
I think so!
I think it was pretty cool.
Right?
Like, it's kind of a real-world--
I mean, it's very simple, all things considered, but it looks pretty good.
Just one more time, for the folks that just showed up, the game
we implemented was Minesweeper.
And it's a little bit skewed, because we have it kind of in debug mode.
So I can do is, I can go to my game grid, go all the way up here,
and we'll just comment this out.
So, folks like Adam who just tuned in, we have Minesweeper.
So your goal is to avoid the mines.
So notice we have highlighted tiles.
You can click on the grid.
This 1 means that one of these neighboring tiles has a bomb in it,
so don't click--
Basically, we have to be careful.
We can't really click any of these and know for certain whether it's a bomb
or not.
And you kind of just have to take some guesses.
Oh, [LAUGH] it looks like the revealAll function is still messed up,
so I need to set this to 1.
So I'll commit those two changes, here, very shortly.
But, if we do this, and, for example, I click on an empty tile,
like there is over here, it will recursively go out amongst all tiles
and reveal it if it's either empty or a number.
And if it's not an empty or a number, which means it's a bomb,
it will just leave it like this.
And if it's a number, it'll stop recursing at that number.
So it'll only reveal up to a number or an empty tile.
And the empty tiles will just keep on expanding
until they hit numbers or bombs.
And so we know for sure, this right here is a bomb, because all of these tiles
say they have one neighboring bomb and it's the only unrevealed tile.
So we have the functionality where you can right-click that tile,
to basically tell yourself and the game that that's a bomb--
mark it.
Don't let me click it.
And so, now, no matter how many times I try to click this tile,
it won't let me accidentally click on it.
Which is a nice failsafe.
Now, it gets a little bit trickier when we're out here,
because, a lot of these, we're not entirely sure which of the tiles
is the bomb.
The only one we definitively know--
that we can definitively ascertain is a bomb for sure is this tile, right here.
Because there's just this one, right here, and it's bordering this tile.
That's the only tile that it's bordering that's unrevealed.
So I can mark that as a bomb.
And, as result, any of the tiles that this is next to that have a 1,
we can get rid of, because we've already determined that this is the one.
This is the bomb.
So I'll click this here and click this here.
And we're sort of expanding out--
This is kind of the game loop, the center, core aspect of the game.
You kind of go out and use deduction to figure out, oh, OK, this
has this neighbor.
What can I do?
How can I figure out which tiles there are?
Yeah, it's pretty sweet.
It's pretty sweet.
But, yeah, that's Minesweeper.
If we click on a bomb--
I can try and detonate a bomb.
It looks like that's a bomb, so I'll unclick it and then do that.
If we click on a bomb, and we blow it up--
uh, we win.
OK, that's a bug!
That's not-- oh--
[LAUGH] and we lost.
Uh, I'm not--
OK, there's still a bug, here.
That was kind of hilarious--
Oh, and I spoke too soon!
And we have a special guest, today.
You want to play some Minesweeper?
DAVID MALAN: Is the game ready to play?
Hello, everyone!
COLTON OGDEN: In a sense.
DAVID MALAN: Uh-oh.
COLTON OGDEN: [LAUGH] I just discovered a bug, actually,
right as I was wrapping it up.
But if you want to press Enter, you can click with the trackpad.
Left-click is to-- do you know how to play Minesweeper?
DAVID MALAN: It's been a while.
COLTON OGDEN: OK.
DAVID MALAN: Yes.
OK, so let's go here.
So now there's two bombs around me, I think?
COLTON OGDEN: Yep.
DAVID MALAN: So maybe, hopefully, not there.
COLTON OGDEN: [LAUGH]
DAVID MALAN: OK, now--
uh-oh.
There's one right around me?
COLTON OGDEN: There's one, yes.
[INTERPOSING VOICES]
DAVID MALAN: Wait, I won?
[LAUGH]
COLTON OGDEN: That's the bug.
DAVID MALAN: That is the opposite of correct!
COLTON OGDEN: Watch-- wait.
Oh, wait, never mind, actually.
There's another bug where it turns into You Lose, from a timer type thing.
So that was the thing we just discovered.
It was working, before, and I did something, and now it's inverted.
DAVID MALAN: Well, I hope you all learned a little something, today,
about-- after the past three hours-- of how to implement Minesweeper.
COLTON OGDEN: Yeah.
DAVID MALAN: Nice.
COLTON OGDEN: It's been good, though.
It looks pretty nice, though.
I'm pretty happy with how it looked.
It was functionally a little bit better, before,
when it actually said "You Lose," here, instead of "You Win."
DAVID MALAN: And then it would be red, instead of green?
COLTON OGDEN: Yeah.
[LAUGH] But, all things considered, the core aspect of the game does work.
So that's pretty cool.
DAVID MALAN: Uh-huh?
Is that true?
Chime in, if you agree, if you could.
COLTON OGDEN: We have video proof.
DAVID MALAN: All right.
COLTON OGDEN: But, yeah, we have a couple of things we need to fix.
DAVID MALAN: Good to see everyone.
I'll say goodbye in the chat, as well, but thanks for having me.
COLTON OGDEN: Cool!
Thanks for popping by-- appreciate it.
DAVID MALAN: See you soon.
COLTON OGDEN: I was getting my--my hopes were down!
I thought you weren't going to make an appearance.
DAVID MALAN: Oh, well, I came to see the big finish, but, you know--
COLTON OGDEN: [LAUGH] not such a big finish.
All right.
Cool, cool.
Well, I'm very happy that we got an appearance in.
Let's figure out why [LAUGH] that's happening.
So our logic is clearly messed up, here.
So, in the game grid--
I believe it's in the game grid--
and, you know, just when we thought we were done, right, now
we have another thing to pluck off.
But this is kind of the perpetual cycle of game development.
We didn't test thoroughly enough.
But there's a lot of corner cases and a lot of things,
and you change one small thing, and it can propagate
and really sort of evolve and, um--
it can get kind of nasty.
Now, this victory thing, here, this whole thing needs to be in an Else.
So this is the problem.
We're going through this code, right here,
and we are doing this whole is-a-bomb thing, et cetera, et cetera.
And then, at whether or not it was a bomb, and we triggered a game over,
we actually go straight down into here, where we reveal the tile,
and then we check victory.
And because, when it's a bomb, the whole thing gets revealed,
it actually turns out that that's a victory condition, as well.
Because the game, the whole board gets cleared when we get a victory.
Right?
So this needs to be, indeed, in Else.
And let's go ahead and do this, like that.
And let's make sure everything still works.
There we go.
So-- Game Over.
Now it's working.
Everything's in the right order.
And it was a pretty easy fix, all things considered.
Now, with those last three changes that I made, which were just a few,
I'm just going to commit them--
bug fixes, bug and debugging fixes, et cetera, et cetera, et cetera.
But-- nice, easy fix.
Glad that didn't take another 45 minutes, which would be unfortunate.
Everybody had some nice things to say for David.
Ooh, man, we've got a lot of comments.
OK.
"To update you, Colton, I'm on week 2 of the Gave Dev course,
and it's definitely gotten more difficult.
I'm going to be working on it today," says Adam.
Awesome.
Well, thank you, for definitely sticking with it.
Let me know if you have any difficulties with it.
If you work hard, it'll be pretty doable.
It's a robust course.
It's a-- games are-- you know, it's meant
to get you introduced to larger code bases.
And it's also meant to kind of keep you interested.
Like, there's games plucked from all sorts of corners classic gaming
history, from the old to the new.
And I think that's an important thing to do,
to keep people sort of engaged in a course like games,
is to actually focus on the games.
Because, at the end of the day, that's what you're going for.
You're going into this with the goal of trying
to be able to make something yourself, so it only
makes sense to get some real hands-on experience doing that.
So, good on you for sticking through it.
Keep us up to date, as we go on through the streams.
"We did modularized code, state-machine classes, timing, scoring, everything
a game needs.
This and sound and levels will be in the to-do [INAUDIBLE] different language,"
says [INAUDIBLE].
Yeah, I know, it's a it's a it's pretty robust.
A game like Minesweeper, I mean, we could have done, like,
an explosion-type thing or whatnot.
But, at this point, probably not the most crucial feature.
And it's fairly easy to do.
You could easily do something like that on your own--
and maybe even an animation, which would be pretty cool,
like, an explosion when you lose, and then it reveals the map.
That'd be pretty cool, actually.
That would be a little bit more work than just, you know, a sound effect,
but pretty awesome.
Oh, [INAUDIBLE],, that's the bug I told you about.
OK, I missed that.
I missed that [INAUDIBLE].
And, yeah, the timer function keeps going.
Yeah.
"The timer needs to be reset."
Does it actually get reset when we, um--
when we reset the game?
It does.
Oh, right, because we're resetting 120 every time
we reinitialize the play state.
So, yeah, that works great.
Everyone's got some nice words for David.
Nice, nice.
"It works.
[INAUDIBLE]"" [LAUGH] Yeah, it was [INAUDIBLE]..
I appreciate everybody for supporting me in the chat.
It's very nice of you.
"The bomb is not hidden, so it doesn't check if it is a bomb.
In the condition, maybe it would be OK if we changed the check sequence?"
Yeah, no, I think the problem was the order in which we were calling things,
and we were changing from the--
we were changing to the game over but then, immediately after that--
It wasn't a binary operation.
We were kind of doing both of them.
And so we would go to the game over, but then we'd
go to victory, after the board was cleared,
because the victory condition was checking the cleared board,
after we went to game over.
Because game over clears the map.
It was kind of a chain of events that turned out to be unideal.
But everything's working, everything's great.
Twin Tower Power [LAUGH] Did I just thank myself?
Yeah, that's what it looks like.
"I think play state needs an update, because that one keeps running
even though there is a victory?"
Um-- no, it doesn't keep running, I don't think.
Right?
Am I--
I don't think the play state keeps running, does it?
Because, if there's a victory, then it'll
get transferred over to the victory state
and the play state's gone, at that point.
So it's actually impossible for that to happen.
But the timer will keep running, because the timer is a global variable that
always maintains a counter.
But the timer itself, the time that we have in the game, the play state, gets
reset upon the instantiation of the play state, which
takes place in the state-machine class.
So, altogether, everything works pretty well.
But, yeah!
That was Minesweeper.
That was the second, I think, big, awesome game that we implemented.
We've done a few, so far, but I think Space Invaders and Minesweeper
are two sort of big, chunky, meaty sort of representative games
that give you a sense of what goes into, like, to [INAUDIBLE] point,
making a full game, something that's actually feature-rich,
not just a toy example or an illustration.
So it was good.
It was good fun.
I enjoyed it.
And I think it was [INAUDIBLE] who initially suggested Minesweeper,
so shout-outs to [INAUDIBLE].
If you're not the one who suggested it, [LAUGH] I apologize.
And a shout-out to whoever did.
But I'm pretty sure [INAUDIBLE] suggested Minesweeper.
So.
Credit where credit's due.
"Sorry I missed it, but does this game use a state-machine class?"
It does.
It does, yeah.
In main, over here, you can see that we are indeed
initializing a state machine.
And we used the state-machine class that we used from Space Invaders-- sorry,
was it Space Invaders?
I don't remember.
Oh, yeah, it was Space Invaders.
Oop, I just almost fell.
Yeah, we used the same state-machine class
that we just used in Space Invaders.
JL97-- "I would love to see a stream on deployment methods."
We did talk about Travis.
Kareem and I did a Travis stream.
But if you have more specific ideas, definitely let us know.
Also, [INAUDIBLE] did it.
Oh, OK, shout-out to [INAUDIBLE].
Haven't seen her in a little bit.
[INAUDIBLE] says "Good job."
Thank, you, [INAUDIBLE].
Yeah.
And, as always, if you have any suggestions
for this stream, any exciting topics or videos,
games that you want me to program, or anything any of our other staff
can program--
and we will be having some more staff soon, and Nick is coming back,
and that's exciting.
And then we'll have other folks and some new folks, as well-- like,
Emily Hong will be joining us for web scraping.
We're going to have some good stuff.
Yeah, let me know, either here in Twitch, on Facebook, YouTube comments.
[INAUDIBLE] says "A machine-learning talk?"
Yeah, Nick actually did a binary classifier in TensorFlow.
So we have done a little bit of machine learning,
but we could do some more, more machine learning, I'm sure.
Machine learning is a hot topic.
One of these days, I should learn some machine learning.
But, yeah, if you're on Twitch, then let me know via Twitch.
If you're on YouTube, and you're watching this, which many people will
be, leave a comment with some ideas.
And subscribe to our YouTube channel.
And, if you're on YouTube and you're watching this video, there,
but you haven't yet checked out our Twitch channel,
definitely go to twitch.tv/cs50tv, which is right down here-- whoops--
I'm getting cut off-- right over that way, on the bottom left of the screen.
Go there, so that we can have a lovely conversation
while we do all of these awesome programming streams.
Because that's the awesome thing about Twitch, is the sort of the--
bi-- the, uh--
what's the word I'm looking for?
Sort of the bilateral communication layer?
I don't know.
It's a little bit too fancy.
"It was a complete project.
Thanks for making these streams."
Ah!
My pleasure.
[INAUDIBLE] "the game does turn to game over, after a victory, with the latest.
Nice challenge, to figure out why that is."
"It turns to game over, after a victory."
Oh, you know why that is?
I think because it's still in play state.
Doing it here.
See this?
This block of code is still going to exit every--
or it's going to execute outside of--
because timer's global.
So this anonymous function is still going to exist, even
when you're within the, um--
even when you're within the-- buh-buh-buh-buh-buh-buh-buh--
some other state.
Right?
And so, I think what the appropriate thing to do would be,
in the victory state, it would probably be timer.clear.
And what that should do--
oh, I'm not on the update function.
Actually, you could do this in the game grid, I think.
So, in the update--
oh, I guess in the victory state, here, you would do--
and I'll probably leave this as an exercise,
because debugging is very important.
So you would do something like timer.clear.
And I don't remember exactly offhand if that's the function name
or if it's something else, but this just clears
all of the time-based functions that are associated with the timer.
So you can do this in the victory state.
You could do this in the play state.
We don't need to do it in basically anything but the game-over
and the victory state, I think.
But we can do that in all of them.
Timer.clear.
So try that out, test that out, Martin, and let me know.
And also, thank you for testing so thoroughly.
In any case, what I was saying was, if you're on YouTube,
check out our Twitch.
If you're on Facebook--
sorry, I should say, if you're not on Facebook, [LAUGH] follow us on Fa--
or, you know, uh, yeah, I guess, follow us on Facebook--
Facebook.com/cs50.
We have a Facebook group, as well.
I think it's Facebook.com/group/cs50, if I'm not 100% mistaken.
Facebook.com/group/cs50.
Is that right?
It is.
OK.
So, definitely check that out.
So, Facebook.com/cs50, Facebook.com/group/cs50,
and then youtube.com/cs50.
Go to all those links.
Most of the people that are here in the chat probably know already,
but this is more for folks that are watching
on YouTube and through other means.
We do Facebook, where-- we're figuring out our Facebook Live shenanigans.
But we stream to YouTube, and we stream to Twitch.
And we do this every week.
So hit us up with your ideas.
We're always interested in providing a useful content.
We'll do a little transition, here.
So thanks, everybody, who's here, who attended, who watched.
Special thanks if you watched both parts all the way, which is very good.
And especially if you coded along-- that's a great way to learn--
through, [LAUGH] for better or worse, given all the bugs that we did.
But, yeah, [INAUDIBLE],, yeah, that was the end.
So-- my pleasure.
"[INAUDIBLE] for the stream."
Thank you.
Yeah!
I'll take any last questions, before we adjourn for today.
Again, to repeat, Thursday--
rather, Wednesday-- Kareem and Flask.
So Kareem and I will talk about Flask.
On Thursday, I will show you the beautiful ways of basic CSS.
I'm not a CSS wizard.
Again, this is the year for me to grow as a web developer,
but I do know the basics of CSS, and I can teach you how to use them.
And then, Friday, David and I are going to review your code.
So submit your source code to bit.ly/cs50twitchcodereview.
Again, so that it's visible on the final video, cs50twitchcodereview.
Many of you probably already did this in the chat.
But, if any of you have not done so, and if any of you watching on YouTube
haven't done so, and you want to get this into us before Friday,
for our first episode of that show, of that-- well, it's
going to be CS50 on Twitch, but of that particular version of the show,
definitely do so.
Cool!
All right, everybody.
Enjoy the rest of your day.
I will see all of you on Wednesday.
Ta-ta.