Saturday, December 20, 2014

PyTutorial: Minesweeper, anyone?

So, you've written a text-based Minesweeper game, and now it's time to add some nice little graphics to it.  The big question though, is how? :)

I hope you spent some time looking around for some packages, have you seen anything you liked?
But before delving into specific packages, let's take a moment to think about our requirements:
  • We want to add graphics (images) and sounds
Anything else?  Perhaps:
  • Add some graphic controls, like a menu and a way to chose the size of the board, etc.
As with any programming task, the more you search the web for help, the more you'll learn about the problem itself.  You may find out that in tech-speak, the graphics of a program is called a GUI (Graphical User Interface).  Furthermore, typical GUI controls (like menu items, buttons, check boxes, etc.) are called widgets.

Now, if you think about it, Minesweeper can be made to work entirely with widgets!  Your main board is basically just a bunch of buttons: left-click to see what's inside it, right-click to flag as a mine.  Not much more complicated than that!  Therefore, we may want to refine our requirements to include:
  • Support for widgets
Now, let's take a look at some packages out there:
  • pygame: a cute little package that seems to be designed for game making.  Although no direct support for widgets, there are other packages that provide such functionality (see pygu and pygameui), although their documentations seems to be lacking.
  • pyjs: an interesting tool that lets you automatically convert your python code into javascript, and thus have the game run in a standard web browser.
  • kivy: a sophisticated package designed to have your python code run on smartphones and tablets.
  • TkInter: seems to be the "standard" for Python widget programming.  Although not designed for games, it should be fine for Minesweeper.  Also, it's easy to find tutorials for it.
The above four options (and there are lots more, of course), raise another question: how do we want our game to be played?  Or more precisely, where?  Do we want our game to be played as a regular desktop application (a bit old fashioned, perhaps?)  Or as an online in-browser game?  Or even as a mobile app?  As you probably imagine, each of these options mean not only a a different way for your code to look, but also a different way for your actual game to look!  There's no "right click" on the smartphone, and no "saving high scores to disk" on browser games (they usually save on a remote web server.)  These differences, although may be slight, are still different enough for you to have to consider.

But there's more.  I want you to spend about 15 to 30 minutes studying each of the above four packages. Browse through some tutorials, look at their APIs (Application Programming Interface, or tech speak for the details on how one can use these packages.)  I want you to notice how these packages deeply influence the way you will write your code!

Go ahead and browse through them already.  Go! :)

Back already?  Great!  I hope you noticed that there's much more here than choosing between "urllib2.urlopen()" and "requests.get()" (from the previous lesson) that basically do the same thing in slightly different ways.  Each of the four packages above will dictate how you will even think about writing a Minesweeper game!  They involve completely different APIs (modules, classes and functions), and even use different terminology.  They may overlap in the sense that they let you achieve similar things (in different ways), but also differ in that each will offer you the abilities to do important things that the others just aren't capable of.  They influence your program so deeply that instead of being called mere "packages", they get to be called "frameworks"


It's not always clear when a package becomes a framework.  The rule of thumb is that if a package requires you to organize your code differently, then it's most likely a framework!  Another is that if one package is deeply incompatible with another (that is, you can't use both at the same time, nor would you want to), then again, most likely both of them are frameworks.  If instead of examples, these packages require entire tutorials (perhaps even more than one), then most likely you're dealing with a framework.

Frameworks are entire worlds. They are not simple to learn.  They have many quirks.  You may never feel like you know everything or even most about them.

Then why bother to use them?  The short answer is that time is money, and frameworks can save you lots and lots and lots of valuable time.   More than that, good frameworks teach you a good way (but usually not the only good way) to achieve a very complicated goal.

Just like you would much prefer to use "urllib2.urlopen()" to fetch some data from the web (instead of figuring out how to do that yourself), a good framework will take care of a lot of the dirty details involved in writing complicated applications, leaving you to concentrate on what you want to get done.

Having said all that, how do we chose one framework among many?  There's no simple answer here, and it really depends on your requirements, what the different frameworks offer, as well as which framework best fits your personal programming tastes.  Since framework affect how you think about programming, you may as well find a framework that thinks like you do.  A good framework needs to earn your respect. :)

Unless, of course, you're going through some online programming tutorial.  In that case, whoever wrote that tutorial will simply choose one for you! :)  Very nice.

And the winner is.......

PyGame!!!  I like it best for this tutorial because it doesn't require you to learn any major new technology (such as web or mobile programming).  But also because it's a truly fun package that helps keep the spirit of programming alive!  Minesweeper is a game.  Let's use a framework designed for writing games!

You can get pygame from here, or if you're running Ubuntu, just run:
> sudo apt-get -q -y install python-pygame

I would start by looking over (and typing in, of course), this cheat-sheet that someone (not me) made.  There's also a newbie guide that helps understand some key pygame concepts.  Of course, there's also the main documentation page with lots of tutorials and references.

But how does one really get started with such a package?

  • Start small: make the simplest, dirtiest prototype just to get started
  • Trial and error: assume that you're going to learn things as you go.  So just get started.
This approach is good if you've already made sure that the package will over-all meet your needs. For instance, if you want the game to run on your phone, you don't want to find out that PyGame doesn't support that after you put in all the effort to learn it.  But who cares about mobile?!  Let's get started!

So what's small? The good news about PyGame is that it works with the regular text-console that you're used to work with.  This way, you can still use print for debug, as well as raw_input to get input from the users.  This way, you can add the graphics part of the game incrementally, which is nice when you're learning.

You can start by writing the code just to display the mine-field, with all other user-input happening in the text-console.  The first step to achieving that is to draw a bunch of pictures!  You can use any old graphic editor for the job, including Gimp.  Thing of all the different kind of cells you're going to want to display: unknown cell, flagged, cell, clicked cell with no mine, clicked cell with one neighboring mine, etc.  Draw all of those, and save them as PNG.  Make sure they're all the same size, of course.

Now comes the fun part, use pygame to display a minesweeper grid!

First, you're going to want to init pygame.  You can look at the cheat-sheet to see how to do that.

Then, you're going to want to load the graphic files, you'll want to use:

Btw, if you want to use the more efficient "convert" function (as the newbie guide) recommends, you're going to need to call pygame.display.set_mode() first (see just below.)  While for most animation games this is probably important, in Minesweeper, this is less so.

Next, you'll have to figure out the size of the window that you're going to need.  Basically it's going to be width_of_image_cell * num_cols by height_of_image_cell * num_rows.  Then create such a window using:
display_surface = pygame.display.set_mode((desired_width, desired_height)

Now, there are many ways to draw the cells onto the window.  Minesweeper isn't animation-intense, and just about anything you chose to do will probably work fine.  PyGame works with Surface objects.  For instance, both pygame.image.load() as and pygame.display.set_mode() return Surface objects.  If you want to draw one surface onto another, you use the blit class-method:

Thus you can draw directly on the display surface (as returned by pygame.display.set_mode), or draw on an intermediary surface, and draw that on the display surface.  It's up to you!  I prefer the latter as it gives you a little more flexibility (imagine wanting to display more things besides the minesweeper grid.)  You can also use sprites, but at this point, they may be somewhat of an over-kill.  You can go back to them later. 

Remember that blitting surfaces does not actually make anything get displayed on the screen.  To achieve that, you need to blit on the display surface, and then call update() on the display, like so:

Update is actually a bit more sophisticated than it seems.  Calling it with no parameters tells it to update the entire display.  For something like Minesweeper, this is probably good enough.  That's because you only need to call update() when something changes in the display, and in Minesweeper, this doesn't happen very often (only when someone clicks or flags a cell.)  But imagine a game like Pac-Man or The Legend of Zelda.  These games have lots of little animations, and painting the entire display, 30 times a second (for the animations to look smooth), is very inefficient.  Inefficient enough that it may actually be noticeable.  For those games, you only want to update parts of the display, and pygame.display.update() allows for that as well.  It is these sort of games that PyGame's sprites really make a difference, and if you want to make such a game, then definitely look into them.  Minesweeper, on the other hand, is not that kind of game.  A player may click or flag a cell at most at around 1 a second, and at these rates, updating the entire display will not be a problem.

So there you have it, now you should be able to actually display a minesweeper grid.  But that's not much of a game.  You also want to be able to handle mouse-clicks.  

That's actually fairly straight-forward.

Mouse-clicks are events.  Things that happen out of your control, as the player decides if, when, where, and how (right or left mouse button) to click.  PyGame handles these and other such events in a very simple event-loop.  The cheat-sheet goes into all the details you need, but basically, at any time you want, you can check if an event has occurred calling pygame.event.get() from inside a for loop.  Each event is an object with a type attribute that you can check for, and other attributes as well depending on the type of event that it is.  For example, a MOUSEBUTTONUP event has a pos attribute which is a tuple containing the (x, y) coordinates where the mouse button went up.  

Well... that's pretty much all you need to do to actually have a graphical interface for the game-play!  If you want to go all-out with all user-input being graphical (as a normal game is), then you'll want to find a package for pygame that can do widgets.  I'm not going to go into that here, but if you really want to do this, I recommend you download the pgu package.  It has zero documentation, but lots of nice little examples that you can run to see how it works. :)

Well, there you have it!  By now you should be able to figure out how to write a nice little Minesweeper game!  That's no small feat, and I truly hope you enjoyed the journey.

Of course, you only just began your adventure into programming land!  But now, you have enough wisdom to help guide wherever you may go.

Where to go from here?

As you probably imagine, that largely depends on you.  But here are some random ideas:
  • Make another game! How about checkers or chess?  I haven't taught you how to do artificial intelligence yet (I probably will in one of my advanced lessons), but you can definitely make a two human player game.  But you can also make other kinds of single-player games, like some crazy Pac-Man or something. 
  • If you're into math / programming riddles, check out Project Euler.  It's lots of fun, but hard as all hell.  So good luck, but don't expect to finish it all in a week.
  • Check out my blog, as every now and then I'll add another "advanced" lesson.  I already have on one writing web-apps, check it out!
  • Learn another language!  Nothing helps hone your programming skills by learning some other language.  If nothing else, it'll give you an ever deeper appreciation of Python! :)  You can learn Javascript, which is fairly popular these days.  Java and Swift are also popular.  You can also learn ChucK, a wacky language specifically designed for music.  A completely useless (yet interesting) language to just muck-around with a bit is lisp.  And of-course, there will always be logo.  :)
  • Python has a very wide selection of packages that do some amazing things!  Just search for one that does something you find interesting, and play with it!  
But no matter what you chose to do with your new-found knowledge (and mad skillz), just remember not to work too hard, to take naps, and to make the time to smell the roses.


No comments:

Post a Comment