ThorPy

A GUI library for pygame

Tutorials - Minigame

Our main objective in this tutorial is to write a code using all of the three main ingredients of ThorPy : elements, reactions and storage. As a pretext, we aim to create a minigame that is called 'Guess the number'. In this game, user has a certain number of trials to find an unknown random number generated by the computer. Each time he is wrong, the machine tells the player if the value he guessed was too high or too low.

In a first part, we will write a code for simply playing the minigame. Then we will add a start menu in which player can adjust some parameters of the game via an option menu: number of trials, min and max values, player's name. Note that this code contains voluntarily many 'optional details' like player name, and other optional parametrization of the game. This make the code longer, but also more useful in order to understand how things work.

Game code

We write the game in a file called 'mygame.py'. In this code, we will use a class named MyGame that define our minigame. The use of a class permit to avoid tedious use of global variables or passing many parameters to the reaction functions ; it makes the code more clear. Below are given code explanations and then the code itself.

  • Lines 8-15: Declaration of the parameters of the game.
  • Lines 16-30: Declaration of the ThorPy elements of the game. There is nothing new compared to what we have seen in previous tutorials : elements initialization and storage.
        Basically, we have two buttons : one for quitting the game and another to restart the game. These buttons are themselves elements of a Ghost in order to store them horizontally as a single element. We have also a textual element named e_counter that is used for displaying hints to the player and to remind him how many trials remain. In addition, we have an Inserter instance that provides a way for player to insert the guessed numbers. Finally, like always, we put all elements into a background element.
        Note that we use the convention that ThorPy elements begins with 'e_' for reasons of clarity. For example, the element e_box_menu could have been confused with a Menu instance otherwise.
  • Lines 32-37: Creation of a reaction. This is the only reaction that we explicitely create. We want this reaction to occurs each time the player has inserted something into the inserter element : thus, the reaction reacts to a THORPY_EVENT whose identifiant is EVENT_INSERT. The reaction function is the method reac_insert_func of MyGame and will be commented below. The reactions is added to the background, though it could have been added to any other element. Note also that, to write clean code, we specify that the el attribute of the event correspond to the e_insert, though this is not necessary since it is the only inserter of the menu.
  • Lines 48-73: This is the code for the reaction function. First of all, we need to get the value inserted by the player into the inserter element (line 49). We will use it in a few lines. Then, we set the inserter value to an empty string and redraw it (lines 50-51), so the user doesn't have to manually delete what he previously wrote. At lines 52-55, we try to cast the inserted value as an int. If this is not possible, we simply quit the function, and the player will be able to write something else. Then, at lines 56-57, we unblit the counter element because we will change its content in the following lines. The new content is decided in lines 58-65, and we set and redraw the counter elements in lines 66-69. Note that we re-center the element along the x-axis, since its width has changed. Finally, if the game is finished, we restart it after waiting a while (lines 71-73).
Note that here we used the default style for elements. You can change it, for example by setting another theme before creating elements, adding the line thorpy.set_theme('human').

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#ThorPy minigame tutorial. Guess the number : first version
import thorpy, pygame, random #pygame for wait() function, random for randint()

class MyGame(object):

    def __init__(self, player_name, min_val, max_val, trials):
        #init some parameters of the game ...
        self.player_name = player_name
        self.min_val = min_val #the minimum value to guess
        self.max_val = max_val #the maximum value to guess
        self.init_trials = trials #keep the original trials amount in memory
        self.trials = trials #remaining number of trials
        #the number to guess:
        self.number = random.randint(self.min_val, self.max_val)
        self.guess = None #the current player guess
        self.e_quit = thorpy.make_button("Quit",
                                         func=thorpy.functions.quit_menu_func)
        self.e_restart = thorpy.make_button("Restart", func=self.restart)
        #a ghost for storing quit and restart:
        self.e_group_menu = thorpy.make_group([self.e_quit, self.e_restart])
        #a counter displaying the trials and some hint/infos about the game
        self.e_counter = thorpy.make_text(text=self.get_trials_text(),
                                          font_color=(0,0,255))
        #the inserter element in which player can insert his guess
        self.e_insert = thorpy.Inserter(name="Try:")
        self.e_background = thorpy.Background( color=(200, 200, 255),
                                                    elements=[self.e_counter,
                                                            self.e_insert,
                                                            self.e_group_menu])
        thorpy.store(self.e_background, gap=20)
        #reaction called each time the player has inserted something
        reaction_insert = thorpy.ConstantReaction(
                            reacts_to=thorpy.constants.THORPY_EVENT,
                            reac_func=self.reac_insert_func,
                            event_args={"id":thorpy.constants.EVENT_INSERT,
                                        "el":self.e_insert})
        self.e_background.add_reaction(reaction_insert)

    def get_trials_text(self):
        return "You have " + str(self.trials) + " more chances."

    def get_hint_text(self):
        if self.number > self.guess:
            return "The number to guess is larger..."
        else:
            return "The number to guess is smaller..."

    def reac_insert_func(self): #here is all the dynamics of the game
        value = self.e_insert.get_value() #get text inserted by player
        self.e_insert.set_value("") #wathever happens, we flush the inserter
        self.e_insert.unblit_and_reblit() #redraw inserter
        try: #try to cast the inserted value as int number
            self.guess = int(value)
        except ValueError: #occurs for example when trying int("some text")
            return
        self.e_counter.unblit()
        self.e_counter.update()
        if self.guess == self.number:
            new_text = "You won! Congratulations, " + self.player_name
        else:
            self.trials -= 1
            if self.trials <= 0:
                new_text = "You lost! The correct number was "+str(self.number)
            else:
                new_text = self.get_trials_text() + " " + self.get_hint_text()
        self.e_counter.set_text(new_text)
        self.e_counter.center(axis=(True,False)) #center on screen only x-axis
        self.e_counter.blit()
        self.e_counter.update()
        self.e_insert.enter() #inserter keeps the focus
        if self.guess == self.number or self.trials <= 0:
            pygame.time.wait(1000)
            self.restart()

    def restart(self):
        self.e_background.unblit() #first unblit the current game
        self.e_background.update()
        self.__init__(self.player_name, self.min_val, self.max_val,
                      self.init_trials) #re-init the game
        thorpy.functions.quit_menu_func() #quit the current menu
        self.launch_game() #relaunch the game

    def launch_game(self):
        self.e_insert.enter() #giv the focus to inserter
        menu = thorpy.Menu(self.e_background) #create and launch the menu
        menu.play()

Here is an example of how to actually instanciate the game. In another file in the same directory as 'mygame.py', put the code below. The only point here is to create the game after the application has been created, since the game create elements at initialization and you must always create elements after the application has been created.

1
2
3
4
5
6
7
#ThorPy minigame tutorial. Guess the number : play game
import thorpy, mygame #note that mygame.py must be in the same folder

application = thorpy.Application(size=(600, 400), caption="Guess the number")
mygame = mygame.MyGame(player_name="Jack", min_val=0, max_val=100, trials=5)
mygame.launch_game()
application.quit()

Now the game is fully working. In the next section, we will see how to give the player a way to parametrize the game through an options menu.

Start Menu

What we want is a start menu providing three choices for the player : play the game, set options or quit the game. The first one will call a function that instanciate and launch a game, in a similar way as what we did in the code for launching game a few lines above, while the second one will launch an option box that is called ParamSetter in ThorPy.

  • Lines 4-13: This function will be called when the play button is clicked. Basically, it does the same as the game launcher written above. However, there are two differences : the first one is that we initialize the game with parameters given by an object named varset. This object is not a graphical element and will be explained below. The second difference is that after the game ended, we unblit the game and we redraw the start menu background.
  • Lines 26-31: Here is the new point. We want an option box in which the player can find elements permitting the player to set the following parameters of the game : number of trials, minimum and maximum value, and player name. We could perfectly add these elements manually. However, in order to write a short and clean code, we choose to use a thorpy.ParamSetter that will automatically provide a box permitting to set parameters. These parameters are provided through a thorpy.VarSet object. The varset automatically determine the type of the parameter to set according to the given value and limits. For instance, at line 28 we give it a number value and two number limits, while at line 30 we give a string value and no limits. The former will then use a slider by default, while the latter will use a text inserter. As you can see, each time we add a parameter with the add method of the varset, we first give a string : this is the identifier of the parameter and it will not be seen by the player ; the actual text of the element corresponding to the parameter is given by the text argument. Finally, at line 31 we instanciate a ParamSetter. ParamSetters can be linked to multiple varset, for this reason we give a list of varsets to the ParamSetter. Also, we give it an optional launched_txt argument, that will be the title of the launched box options.

To finish, one could improve the code in several ways. In particular, we can note that the parameters values are not checked (in fact, their type is checked since the VarSet automatically provide right elements for setting them). For instance, the player could choose a min value that is larger than the max value, resulting in a ValueError at the random generation of the number to guess.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ThorPy minigame tutorial. Guess the number : start menu
import thorpy, mygame

def launch_game(): #launch the game using parameters from varset
    global varset, e_background
    game = mygame.MyGame(player_name=varset.get_value("player name"),
                         min_val=varset.get_value("minval"),
                         max_val=varset.get_value("maxval"),
                         trials=varset.get_value("trials"))
    game.launch_game()
    game.e_background.unblit() #unblit the game when finished
    game.e_background.update()
    e_background.unblit_and_reblit() #reblit the start menu


application = thorpy.Application(size=(600, 400), caption="Guess the number")

thorpy.set_theme("human")

e_title = thorpy.make_text("My Minigame", font_size=20, font_color=(0,0,150))
e_title.center() #center the title on the screen
e_title.set_topleft((None, 10)) #set the y-coord at 10

e_play = thorpy.make_button("Play!", func=launch_game) #launch the game

varset = thorpy.VarSet() #here we will declare options that user can set
varset.add("trials", value=5, text="Trials:", limits=(1, 20))
varset.add("minval", value=0, text="Min value:", limits=(0, 100))
varset.add("maxval", value=100, text="Max value:", limits=(0, 100))
varset.add("player name", value="Jack", text="Player name:")
e_options = thorpy.ParamSetterLauncher.make([varset], "Options", "Options")

e_quit = thorpy.make_button("Quit", func=thorpy.functions.quit_menu_func)

e_background = thorpy.Background(color=(200, 200, 255),
                                 elements=[e_title, e_play, e_options, e_quit])
thorpy.store(e_background, [e_play, e_options, e_quit])

menu = thorpy.Menu(e_background)
menu.play()

application.quit()