----------------------------------- DemonWasp Fri Aug 21, 2009 7:20 pm [Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- Prerequisites This is part 4 of the Basic Game Programming tutorial. Find the previous parts here: Detecting when a Player has Won or Lost The conditions for when a player wins or loses are fairly simple in Minesweeper: the player loses if they ever uncover a mine, and win if they reveal the whole map correctly without exploding. We've already got code that will detect whenever a player explodes (this is in our main game loop), and it outputs "Boom, you're done!", then exits the game. This means we really only need to check for a player winning the game. First, we need to answer some important questions about how and when: When do we need to detect whether a player has won or lost? Well, a player could (theoretically) win on any click, whether they reveal a square or not, so clearly we'll have to check after every click, before continuing the game. How do we check? Well, we know what the underlying map looks like, and what the player's map-status looks like. We need to make sure that if the map at each square is MAP_MINE, then the player has USER_FLAGGED; if it's less than 10 (that is, either empty or a numbered square) then it needs to be USER_REVEALED. We'll work on implementing the "how" part first. It's fairly easy to check that each square is correctly marked by the player. If the player has won, we'll have our new function return true...if they haven't won yet, it can return false. Remember, false doesn't imply that they've lost the game, just that they haven't won yet (losing is handled separately). % CHECK WHETHER THE USER has won yet. % Returns true if the user has revealed the entire map correctly; returns false otherwise. function check_for_win () : boolean for x : 1..game_size_x for y : 1..game_size_y if map ( x, y ) = MAP_MINE then if user_state ( x, y ) not= USER_FLAGGED then result false end if else if user_state ( x, y ) not= USER_REVEALED then result false end if end if end for end for result true end check_for_win Now we need to insert a call to this function in our main loop, right after handling each click, and deal with the result from it. We put the following after the if statement dealing with mouse clicks: if check_for_win() then Text.Locate ( 1, 1 ) put "YOU WIN!!" exit end if Open Fields / Contiguous Empty Regions Next, we need to handle what happens when a player clicks on an empty square - it reveals all adjacent squares, possibly opening up entire fields to the player. This makes the start of the game much more bearable, as you don't have to spend all your time opening up huge areas. This is the most advanced part of this entire tutorial, so be forewarned: this requires a bit of unorthodox thinking. Specifically, we'll be using % REVEAL AN AREA of contiguous empty spaces % Called every time the user clicks on an empty space. procedure reveal_area( x, y : int) user_state ( x, y ) := USER_REVEALED for x_off : -1 .. 1 for y_off : -1 .. 1 if ( x_off not= 0 or y_off not= 0 ) and x+x_off > 0 and x+x_off 0 and y+y_off game_size_x or mouse_grid_y < 1 or mouse_grid_y > game_size_y then mouse_button := 0 end if % Handle valid clicks (within the play area) if last_mouse_button = 1 and mouse_button = 0 and user_state ( mouse_grid_x, mouse_grid_y ) = USER_NONE then % left-click = reveal that square user_state ( mouse_grid_x, mouse_grid_y ) := USER_REVEALED case map ( mouse_grid_x, mouse_grid_y ) of label MAP_MINE : game_over := true reveal_map() draw_map() Text.Locate ( 1, 1 ) %put "Boom, you're done!" Font.Draw ( "Boom, you're done!", 10, maxy - 50, large_font, brightred ) exit label MAP_EMPTY : reveal_area ( mouse_grid_x, mouse_grid_y ) draw_map() label : draw_map() end case elsif last_mouse_button = 100 and mouse_button = 0 then % right-click = cycle between none / flag / question case user_state ( mouse_grid_x, mouse_grid_y ) of label USER_NONE : user_state ( mouse_grid_x, mouse_grid_y ) := USER_FLAGGED label USER_FLAGGED : user_state ( mouse_grid_x, mouse_grid_y ) := USER_QUESTION label USER_QUESTION : user_state ( mouse_grid_x, mouse_grid_y ) := USER_NONE label USER_REVEALED : % Do nothing otherwise end case draw_map() end if if check_for_win() then %Text.Locate ( 1, 1 ) %put "YOU WIN!!" Font.Draw ( "YOU WIN!", 10, maxy - 50, large_font, green ) exit end if last_mouse_button := mouse_button end loop Number 3 - Nicer Win/Lose Messages We can pretty up our game a little using the Font package, supplied with Turing. To do so, we'll draw our Win and Lose messages using fancy fonts. We'll have two fonts, one large and one small: % FONTS var large_font : int := Font.New ( "Arial:32" ) var small_font : int := Font.New ( "Arial:16" ) And we'll change our messages to be more appealing: %Text.Locate ( 1, 1 ) %put "Boom, you're done!" Font.Draw ( "Boom, you're done!", 10, maxy - 50, large_font, brightred ) if check_for_win() then %Text.Locate ( 1, 1 ) %put "YOU WIN!!" Font.Draw ( "YOU WIN!", 10, maxy - 50, large_font, green ) exit end if Number 4 - Allowing Replays We want the player to be able to choose to replay after every win or loss. This is most easily done by putting our main game loop inside another loop; this other loop will only exit if the player DOESN'T want to play again. This also implies that we'll need to do a re-setup every time we launch the game For this reason, we'll also put all of our setup code into a procedure we can call easily: setup( size_x, size_y : int). This new procedure is mostly just code we already had, but with the slight modification that it can handle changing the scale of game tiles before each round of the game. This isn't possible in our version of the game, but may be a modification you'd like to try on your own. % PREPARE FOR THE GAME % This is called whenever we want to begin a game. It clears old data and places new mines. procedure setup (size_x, size_y : int) game_over := false % Determine scaling game_size_x := size_x game_size_y := size_y scale_x := maxx / game_size_x scale_y := maxy / game_size_y var scale_x_int : int := floor (scale_x) var scale_y_int : int := floor (scale_y) if (pic_flag >= 0) then % Clean up after the previous setup() call... Pic.Free (pic_flag) Pic.Free (pic_none) Pic.Free (pic_boom) Pic.Free (pic_bomb) Pic.Free (pic_question) end if 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) 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 % Initialize the map for x : 1 .. 100 for y : 1 .. 100 map (x, y) := MAP_EMPTY user_state (x, y) := USER_NONE end for end for end setup Our new game loop is wrapped with the following: loop % Set up for the next game setup( 30, 20 ) Draw.Cls() View.Update() place_mines() draw_map() last_mouse_button := 0 % Main game loop goes here... var response : char %put "Try again? Y/N: ".. Font.Draw ( "Try again? Y/N: ", 10, maxy - 75, small_font, green ) View.Update() response := getchar() exit when response = "n" or response = "N" end loop What's Next? Well, this is the end of the Basic Game Programming tutorial. The game we've made so far is functional and gets the job done, but it isn't perfect. There are a lot of things missing or not quite right that you're welcome to try to fiddle with on your own. If you followed the lessons, you should now know the code well enough to continue working on the game without guidance. Suggestions on how to make this game Better: 1. Add a timer. This would require that you draw the map as a smaller part of a screen and have a way of drawing the timer and a way of figuring out how many seconds have passed since the game started. I would recommend using Time.Elapsed(). 2. Prevent the game from cycling so quickly - by default, the game will loop through the main game loop very very quickly - so fast that it's well beyond human perception. However, this leads to the Turing process using up the majority of the machine's CPU power, which can hinder or slow other applications, or force the machine to consume more power (laptops beware). You can fairly easily edit the game code so that each iteration of the main loop takes a minimum amount of time - see Time.DelaySinceLast() - to solve this problem. 3. Add controls to change the game size. Consider also changing the game window's size based on other settings. As always, questions, comments and concerns are welcomed. This will not be my last tutorial, so please give any advice you have for me to take into consideration when I work on the next tutorial. ----------------------------------- DemonWasp Fri Aug 21, 2009 9:11 pm Re: [Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- Oops, forgot to post the completed source code. ----------------------------------- corriep Sun Aug 23, 2009 6:01 pm Re: [Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- The only thing this is missing from the real minesweeper is that you can get a bomb on your first click. If I could suggest anything it would be to wait until the player makes the first click, and then position the mines, making sure to not put any mines in the player's first square. Bits 'n Karma for a great set of tutorials :) ----------------------------------- DemonWasp Sun Aug 23, 2009 6:39 pm RE:[Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- I'm becoming something of a karma junkie now...I write tutorials just to get my next fix! Thanks. As for not hitting a bomb the first time, I'd never noticed that, but that's certainly a feature on the "could be added" list, as is a bomb counter, which I also failed to mention. ----------------------------------- Johnny19931993 Mon Dec 07, 2009 3:42 pm RE:[Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- lol what a coincidence I posted a minesweeper game that I made four days before you posted the first part of this tutorial http://compsci.ca/v3/viewtopic.php?t=21593 ----------------------------------- DemonWasp Mon Dec 07, 2009 4:06 pm RE:[Tutorial] Basic Game Programming: Minesweeper, Part 4 ----------------------------------- So you had. Maybe that was my inspiration for creating the tutorial, though I seem to recall that I wrote the program over the course of several 90-minute bus rides, so maybe I'd started before you posted. Good job on yours as well.