Emoji Clicker¶
In 2013, the web-based “Cookie Clicker” game took the world by storm. The game was exceedingly simple: players clicked on a giant cookie in order to earn points. Although the game had a lot of other bells and whistles, the simplicity of the core concept was a major selling point.
You are going to make your own Cookie Clicker type of game, called “Emoji Clicker”. Instead of earning points, your game will feature an emoji slowly growing bigger and bigger until it gets too big. However, you can also click the emoji to make the emoji get smaller.
In order to make this game, we’ll need to learn about Designer’s Events, along with knowing how to define functions and write if statements.
1. Drawing an Emoji¶
We need to start by just drawing the emoji on the screen. We can use what we learned before to
call the emoji
function, passing in a string value; then we pass the result of the emoji
function into
the draw
function. When you run this code, a window should appear with an emoji in the middle.
from designer import *
draw(emoji("dog face"))
Problem 1: Change the string value so that a different emoji is drawn. You can find
emojis here; you can use either the formal name of the
emoji (e.g., "dog face"
) or the emoji itself ("🐶"
).
2. Growing the Emoji¶
On its own, we have not made a very exciting game. Nothing happens when we run it! Let’s change that by binding a function to an event. Specifically, we are going to make the emoji grow (increase scale) every update of the game. Let’s look at the new code, and then we’ll explain what we are seeing.
from designer import *
def grow_picture(picture: DesignerObject):
change_scale(picture, .5)
when("updating", grow_picture)
draw(emoji("dog face"))
The new function grow_picture
consumes a picture
and then calls the change_scale
function
using it and the value .5
. The function does not return anything; instead, it just makes a
tiny little change to the picture
it was given.
On its own, defining the grow_picture
function does not do anything. We also cannot call the
function on our own - that would simply make the emoji a tiny bit bigger, and what argument
would we use for picture
anyway? We can’t just make a new emoji("dog face")
each time, or it won’t keep growing.
Instead we have to bind the "updating"
event to our grow_picture
function using
the when
built-in function. The result almost reads like a sentence: “When the game is updating,
grow the picture.” This is why it is necessary to only grow the picture a tiny amount:
the game updates many times a second (usually 30 times, in fact), so we only have to describe a very
small, incremental change to our world.
Notice that the when
function takes a string representing the event ("updating"
) and
the name of a function (grow_picture
). Critically, we are NOT calling the
function grow_picture
on that line - we don’t ever call the function ourselves!
Instead, we are handing the function to Designer and politely asking Designer to call it on our behalf,
every update of the game. In return, Designer provides us the same picture
that we originally passed as an argument
to draw
.
This idea of defining functions that make small changes in the world, and
then binding them with the when
function is at the heart of Designer games. We call this “Event Handling”.
Problem 2: Change the second argument of change_scale
to be a much smaller decimal, so that the picture
does not grow as fast.
3. Picture Grows Too Big¶
When the emoji gets too big, the game should stop. We need to add some logic to the game to check the current scale
of the picture
; when the horizontal scale gets too big, we will pause
the game. Since this could happen at any
update, we will add another when("updating", ...)
to our program, this time binding a function check_picture
.
from designer import *
def grow_picture(picture: DesignerObject):
change_scale(picture, .5)
def check_picture(picture: DesignerObject):
if get_scale_x(picture) > 8:
pause()
when("updating", grow_picture)
when("updating", check_picture)
draw(emoji("dog face"))
The check_picture
function uses the get_scale_x
function to check the current horizontal scale of the picture
. If
the scale has exceeded 8 (aka is now 8 times bigger than the original size), we pause
the game. We pause
instead of
stop
so that the window stays open afterwards (stop
will completely end the game).
Why do we check the horizontal scale specifically, instead of the vertical? It doesn’t actually matter - since we are
using change_scale
, both the horizontal and vertical scales will be change at the same time, and we could check either
one.
Problem 3: Add a new line to the if
statement’s body to change the picture
using the set_emoji_name
function.
The set_emoji_name(object, new_name)
function consumes a Designer Object (i.e., picture
) and a string value
representing the new emoji name to use. You are free to use whatever emoji you want, such as a Cross Mark or an
Explosion or a Frowny Face. Remember, if
statements can have multiple lines of code in their body, but each
line INSIDE the body is indented the same amount!
4. Shrinking the Emoji¶
It’s not really a game if all you can do is watch the Emoji get bigger and bigger until you lose. Next we’ll add
some interactivity. Specifically, when the user clicks on the emoji, the picture should get a little bit smaller.
Making the picture smaller can be done by passing a negative value to the change_scale
function. But to make the
emoji shrink only when we click the mouse instead of every update, we need to bind the "clicking"
event to our new
shrink_picture
function.
from designer import *
def grow_picture(picture: DesignerObject):
change_scale(picture, .5)
def check_picture(picture: DesignerObject):
if get_scale_x(picture) > 8:
pause()
def shrink_picture(picture: DesignerObject):
change_scale(picture, -.5)
when("updating", grow_picture)
when("updating", check_picture)
when("clicking", shrink_picture)
draw(emoji("dog face"))
The event "clicking"
only activates when the user clicks the mouse button anywhere on the screen. Once again,
Designer will provide us with the current picture
, so we can use that as the argument to change_scale
along with
the negative value. However, we actually want the emoji to shrink only IF the player clicks on the Emoji (as opposed
to anywhere else on the screen).
Problem 4: Use the colliding_with_mouse
and if
statement to make the picture
shrink ONLY if the
user actually clicks on the emoji. The colliding_with_mouse(object)
function consumes a Designer Object, and produces
a boolean indicating whether or not the mouse is currently on top of the given object. Hint: you will not need
any ==
operators because the colliding_with_mouse
function already returns a boolean.
5. Changing the Emoji¶
At this point, our game is technically playable and could be shown to other people. But we have some cute features
to add before that. The first new feature is the ability to switch what emoji is currently active. When the player
presses the right arrow on their keyboard, the emoji’s picture should cycle through a few different images. Using
when
, we can bind a "typing"
event to our new change_picture
function. However, that function will be a little
more complicated since it will depend on another new function next_picture
. Let us look at the new code.
from designer import *
def grow_picture(picture: DesignerObject):
change_scale(picture, .5)
def check_picture(picture: DesignerObject):
if get_scale_x(picture) > 8:
pause()
def shrink_picture(picture: DesignerObject):
change_scale(picture, -.5)
def change_picture(picture: DesignerObject, key: str):
if key == "right":
next_picture(picture)
def next_picture(picture: DesignerObject):
if get_emoji_name(picture) == "___":
set_emoji_name(picture, "___")
elif get_emoji_name(picture) == "___":
set_emoji_name(picture, "___")
else:
set_emoji_name(picture, "___")
when("updating", grow_picture)
when("updating", check_picture)
when("clicking", shrink_picture)
when("typing", change_picture)
draw(emoji("dog face"))
The change_picture
function is unusual compared to the other Event Handling functions, since in addition to the
picture
parameter there is also a key
parameter. The key
is a string value representing what keyboard key the user
pressed. The "right"
, "left"
, "up"
, and "down"
keys are often very useful for making games, but there are many
other options. In our change_picture
function, we check if the user typed the "right"
arrow key; if so, then we
call our next_picture
function passing in our picture
as the argument.
The next_picture
function consumes the current picture
and uses a chain of if
, elif
, and else
statements
to update the emoji based on the current emoji’s name. Remember, these conditional statements are “mutually exclusive”
to each other - the first one that is triggered (has a True
condition) means the rest are skipped. We’ve left
the string values to compare against the current picture
name AND the string values to use as the argument for
set_emoji_name
to be blanks ("___"
), since the choice of which emojis to use is up to you.
Problem 5.a: Fill in the blanks of the next_picture
function so that the Emoji advances through three
different pictures. You can choose which pictures to use. Let’s think carefully about an example of the logic:
Let us say that if the current picture is "Dog Face"
, then the next picture should be "Cat Face"
. Then the if
statement should ask whether the current picture is "Dog Face"
and if so then its body should call set_emoji_name
with "Cat Face"
. Then the elif
checks if the current emoji is "Cat Face"
, and if so changes it to something else.
The final else
condition should always set the Emoji back to the original one (in this case "Dog Face"
).
Once you have the logic figured out, you should be able to press the right arrow key to cycle through all the emojis you’ve selected. But what if we want to also cycle backwards?
Problem 5.b: Define a new function previous_picture
that performs the inverse of the next_picture
function.
Like the other function, the previous_picture
function should consume a picture
and produce nothing, instead
calling the set_emoji_name
function. The logic should be reversed. Call the previous_picture
function correctly
inside of the change_picture
function IF the player types the "left"
arrow key.
6. Varying Growth Rates¶
We are almost done, we just have one more feature to add. Since we have different emojis, we will make them grow at different rates, essentially having different difficulty levels.
Problem 6: Define a new function named get_growth_rate
that consumes a picture
and produces a
float value indicating how much that emoji should grow in one step. Then, call that function in your existing
grow_picture
function so that the picture
grows at the varying rate instead of the constant rate.
7. Going Beyond¶
At this point, you have achieved all of the features you need for your game! Show it to someone else and see what they think! Here are some other ways to extend the game.
If you’re not happy with your game having no way to win, you are free to add another condition to
check_picture
that determines if the Emoji has shrunk down small enough, and then end the game the same way.Change the background image (
set_window_image(path)
) and window size (set_window_size(width, height)
) to be something more thematically appropriate to your game.Look up the
randint
function, which allows you to make random integers. Use this function to have the emoji move around the screen at random times and to random positions.