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