Computer Science Canada Programming C, C++, Java, PHP, Ruby, Turing, VB   Username:   Password: Wiki   Blog   Search   Turing   Chat Room  Members
[Tutorial] Basic Game Programming: Minesweeper, Part 2
Author Message
DemonWasp

Posted: Thu Jul 23, 2009 12:36 pm   Post subject: [Tutorial] Basic Game Programming: Minesweeper, Part 2

Prerequisites
This is part 2 of the Basic Game Programming tutorial. Part one can be found here, and should be completed before beginning this part.

Program Design - Laying the Foundation, Continued
We have a bit more to do before we can even start on the game logic (game rules and how to play, etc). For example, even though we have images, we still don't have any way of drawing the map accurately. To do this, let's establish a set of requirements for how we draw the map:

• For simplicity, the map's squares will consume the entire run window.
• We will base the game on a defined number of squares horizontally and vertically, and will scale our images so they fit on the given space (our run window).
• We will define the map to be square (number of rows is the same as the number of columns), and our run window will also be square.
• Since input will be mostly with the mouse, we will hide the cursor.
• We will manually update the screen to prevent flickering.

Scaling the Images
The requirements imply that we will be required to scale our images before using them. Helpfully, the Pic module built into Turing has a method for just this purpose: Pic.Scale(). This allows us to re-scale images; however, it creates a new image each time, so we should probably do this as rarely as possible. So, we'll do it once right as the program starts, rather than each time we draw the map.

The code is relatively straight-forward:
 Turing: var game_size_x : int := 20 var game_size_y : int := 20 % Determine real-valued scaling ratios var scale_x, scale_y : real scale_x := maxx / game_size_x scale_y := maxy / game_size_y     % Integer scaling ratios var scale_x_int : int := floor ( scale_x ) var scale_y_int : int := floor ( scale_y )     pic_flag := Pic.Scale ( pic_base_flag, scale_x_int, scale_y_int ) pic_none := Pic.Scale ( pic_base_none, scale_x_int, scale_y_int ) pic_boom := Pic.Scale ( pic_base_boom, scale_x_int, scale_y_int ) pic_bomb := Pic.Scale ( pic_base_bomb, scale_x_int, scale_y_int ) pic_question := Pic.Scale ( pic_base_question, scale_x_int, scale_y_int ) pic_flagged_empty := Pic.Scale ( pic_base_flagged_empty, scale_x_int, scale_y_int ) % This part uses the same clever trick described in the last part - since the numbers are all sequential, we can handle them neatly this way.    for i : 0..8     if ( pic_number(i) >= 0 ) then         Pic.Free ( pic_number(i) )     end if     pic_number(i) := Pic.Scale ( pic_base_number(i), scale_x_int, scale_y_int ) end for

Setting up our Run Window
This part is pretty simple and ends up being our very first bit of code:

 Turing: View.Set ( "graphics:800;800; offscreenonly; nocursor" )

This tells Turing to set up a window for graphics (rather than text), with a resolution of 800x800 pixels. This window will not display a flashing input cursor, and it won't redraw itself until we call View.Update().

Drawing the Map
To draw our map, we will simply "visit" every square of the map, drawing it on the screen as appropriate. This will require two for-loops. We will also wrap the map-drawing up into a procedure so we can refer to it simply; we'll call this procedure draw_map().

I'll separate this into two main parts: drawing the icons for each square of the map, and drawing a set of rulers to neatly separate icons.

Drawing the Icons
 Turing: % Draw icons for individual squares     var draw_x, draw_y : int     for x : 1..game_size_x         for y : 1..game_size_y             draw_x := round ( (x-1)*scale_x )             draw_y := round ( (y-1)*scale_y )                        % Case statements are like if statements that test against a variety of values - here, we test the user_state of (x, y) against each possible condition.             case user_state ( x, y ) of                 label USER_NONE :                     Pic.Draw ( pic_none, draw_x, draw_y, picCopy )                                     label USER_REVEALED :                     % If the user revealed a bomb, show an explosion. If the user revealed a non-bomb, show the numeral (or nothing if it's zero).                     if map ( x, y ) = MAP_MINE then                         Pic.Draw ( pic_boom, draw_x, draw_y, picCopy )                     else                         Pic.Draw ( pic_number(map(x,y)), draw_x, draw_y, picCopy )                     end if                                     label USER_FLAGGED :                     Pic.Draw ( pic_flag, draw_x, draw_y, picCopy )                                     label USER_QUESTION :                     Pic.Draw ( pic_question, draw_x, draw_y, picCopy )             end case                  end for     end for

Drawing the Rulers
 Turing: % Draw a ruler to separate squares visually     for x : 0..game_size_x         Draw.Line ( round(x * scale_x), 0, round(x * scale_x), maxy, black)     end for         for y : 0..game_size_y         Draw.Line ( 0, round(y * scale_y), maxx, round(y * scale_y), black)     end for

Putting it Together
 Turing: % DRAW THE MAP % Taking into account which squares have been revealed by the user. procedure draw_map()     % Draw icons for individual squares     var draw_x, draw_y : int     for x : 1..game_size_x         for y : 1..game_size_y             draw_x := round ( (x-1)*scale_x )             draw_y := round ( (y-1)*scale_y )                        case user_state ( x, y ) of                 label USER_NONE :                     Pic.Draw ( pic_none, draw_x, draw_y, picCopy )                                     label USER_REVEALED :                     % If the user revealed a bomb, show an explosion. If the user revealed a non-bomb, show the numeral (or nothing if it's zero).                     if map ( x, y ) = MAP_MINE then                         Pic.Draw ( pic_boom, draw_x, draw_y, picCopy )                     else                         Pic.Draw ( pic_number(map(x,y)), draw_x, draw_y, picCopy )                     end if                                     label USER_FLAGGED :                     Pic.Draw ( pic_flag, draw_x, draw_y, picCopy )                                     label USER_QUESTION :                     Pic.Draw ( pic_question, draw_x, draw_y, picCopy )             end case                  end for     end for         % Draw a ruler to separate squares visually     for x : 0..game_size_x         Draw.Line ( round(x * scale_x), 0, round(x * scale_x), maxy, black)     end for         for y : 0..game_size_y         Draw.Line ( 0, round(y * scale_y), maxx, round(y * scale_y), black)     end for         % Show our updates on the screen     View.Update() end draw_map

You might wonder why we draw the grid AFTER we draw the icons. Why not draw it first? The reason is because when we draw the images, they would overwrite our rulers, whereas we want our rulers to appear on top of the icons.

Clever observers will note that we're missing some possible states in our drawing mechanism here - for example, there's no code for "incorrectly placed flag", represented by the image in pic_flagged_empty. This will come later, as it requires some game logic first.

Starting the Game Logic
Game logic refers to the game rules and game play. This is what makes the game "Minesweeper" rather than "Battleship", which requires largely the same stuff up until now.

Placing the Mines
Now that we can draw the map, we need to place some mines on the game board. This is more complicated than it sounds - we need to place a specific number of mines randomly, without putting two mines in one square. We also need to determine the numbers in each square. For our purposes, we'll have one mine for every ten squares in the game.

Here's an idea of how we'll do this:
1. For each mine, randomly choose locations until we find one that's empty.
2. Once we've found an empty location, mark the mine there (MAP_MINE) and add one to all the surrounding non-mine squares - this determines the number shown in each square.

The code is pretty simple. We'll separate the task into two procedures. The first, place_mines(), does part 1, above. It uses the second, place_mine ( x, y : int ), to do part 2.
 Turing: % PLACES A MINE at the specified location. % Also handles the counters at each corner. procedure place_mine ( x, y : int )     map ( x, y ) := MAP_MINE     var check_x, check_y : int     % Add one to surrounding squares     for x_off : -1 .. 1         for y_off : -1 .. 1             check_x := x+x_off             check_y := y+y_off             if check_x > 0 and check_x <= game_size_x and check_y > 0 and check_y <= game_size_y  % Stay inside the map - don't go off the edges.                 and map ( check_x, check_y ) not= MAP_MINE then                 map ( check_x, check_y ) += 1             end if         end for     end for end place_mine % PLACE ALL THE MINES % Places the specified number of mines about the map at random, ensuring no overlaps. procedure place_mines ()     var num_mines : int := game_size_x * game_size_y div 10  % One mine per ten squares     var place_x, place_y : int     for i : 1..num_mines         loop             place_x := Rand.Int ( 1, game_size_x )             place_y := Rand.Int ( 1, game_size_y )             if map ( place_x, place_y ) not= MAP_MINE then                 place_mine ( place_x, place_y )                 exit             end if         end loop     end for end place_mines

Why do I have them in this order (part 2 first, then part 1)? Because the code for part 1 has to be able to call the code for part 2, which means that part 2 must have already been specified before part 1 appears.

Getting some Results
This is all great, but we still haven't seen anything interesting out of our program! Let's do a little bit of work to get it to draw the map we've worked so hard on...

 Turing: % TEMPORARY CODE TO TEST for x : 1..100     for y : 1..100         map ( x, y ) := MAP_EMPTY         user_state ( x, y ) := USER_REVEALED  % We want to be able to see the map!     end for end for place_mines() draw_map()

Running this, we notice that - hooray! - it draws a map with a bunch of mines shown, and the numbers around them are correct! (The mines have exploded because we set every square to be revealed, just as if you'd clicked on it).

Next Lesson
In the next lesson, we'll start detecting user input (from the mouse) and continue implementing game logic. Users will be able to click to reveal squares and right-click to cycle between flags, question marks, and unmarked. As always, questions, comments and concerns are welcome.

You can find the cumulative code from Part 1 and Part 2 attached.

Minesweeper Part1-2.t
Description:
 Code so far, parts 1-2.

Filename:  Minesweeper Part1-2.t
Filesize:  5.51 KB

chopperdudes

Posted: Thu Jul 23, 2009 6:17 pm   Post subject: RE:[Tutorial] Basic Game Programming: Minesweeper, Part 2

hmm i'm interested to see how you would code the fields. when i made my version (in 2 hours of boredom), i think the most logical (and simple) way to code that would be DFS or BFS. dunno if you'd consider that "basic".
DemonWasp

Posted: Thu Jul 23, 2009 6:20 pm   Post subject: RE:[Tutorial] Basic Game Programming: Minesweeper, Part 2

If by "fields" you mean "sections without any mines", then it's actually really easy to do with a really simple recursive procedure that's about 5-10 lines long; we'll be seeing that in either Part 3 or Part 4.
chopperdudes

Posted: Thu Jul 23, 2009 6:38 pm   Post subject: RE:[Tutorial] Basic Game Programming: Minesweeper, Part 2

yeah that was what i meant, and yeah with a recursive procedure, like i said perhaps depth first search type, then it'd be short and simple, but originally i thought you weren't gonna use recursion so i was interested to see how you would do it non-recursively.
DemonWasp

Posted: Thu Jul 23, 2009 7:10 pm   Post subject: RE:[Tutorial] Basic Game Programming: Minesweeper, Part 2

It can be done without recursion, but it's not as easy. Basically, you end up emulating recursion with a manually-managed stack.

The method I've worked up isn't tail-recursive, but if it were it could be refactored into a loop rather than a recursive case.

I went with the easiest-possible algorithm for this game: speed is a non-issue, but complexity and algorithmic analysis are largely beyond beginner programmers.
 Display posts from previous: All Posts1 Day7 Days2 Weeks1 Month3 Months6 Months1 Year Oldest FirstNewest First

Page 1 of 1  [ 5 Posts ]
 Jump to:  Select a forum  CompSci.ca ------------ - Network News - General Discussion     General Forums   -----------------   - Hello World   - Featured Poll   - Contests     Contest Forums   -----------------   - DWITE   - [FP] Contest 2006/2008   - [FP] 2005/2006 Archive   - [FP] 2004/2005 Archive   - Off Topic     Lounges   ---------   - User Lounge   - VIP Lounge     Programming -------------- - General Programming     General Programming Forums   --------------------------------   - Functional Programming   - Logical Programming   - C     C   --   - C Help   - C Tutorials   - C Submissions   - C++     C++   ----   - C++ Help   - C++ Tutorials   - C++ Submissions   - Java     Java   -----   - Java Help   - Java Tutorials   - Java Submissions   - Ruby     Ruby   -----   - Ruby Help   - Ruby Tutorials   - Ruby Submissions   - Turing     Turing   --------   - Turing Help   - Turing Tutorials   - Turing Submissions   - PHP     PHP   ----   - PHP Help   - PHP Tutorials   - PHP Submissions   - Python     Python   --------   - Python Help   - Python Tutorials   - Python Submissions   - Visual Basic and Other Basics     VB   ---   - Visual Basic Help   - Visual Basic Tutorials   - Visual Basic Submissions     Education ----------- - Student Life   Graphics and Design ----------------------- - Web Design     Web Design Forums   ---------------------   - (X)HTML Help   - (X)HTML Tutorials   - Flash MX Help   - Flash MX Tutorials   - Graphics     Graphics Forums   ------------------   - Photoshop Tutorials   - The Showroom   - 2D Graphics   - 3D Graphics     Teams ------ - dTeam Public

 Style: Appalachia blueSilver eMJay subAppalachia subBlue subCanvas subEmjay subGrey subSilver subVereor Search: