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 namede_counter
that is used for displaying hints to the player and to remind him how many trials remain. In addition, we have anInserter
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 elemente_box_menu
could have been confused with aMenu
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 isEVENT_INSERT
. The reaction function is the methodreac_insert_func
ofMyGame
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 theel
attribute of the event correspond to thee_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).
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 athorpy.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 theadd
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 thetext
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 optionallaunched_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()
|